From 7eb65439914a5ad03e0c6065fad8dcd44acc556a Mon Sep 17 00:00:00 2001 From: QIDI TECH <893239786@qq.com> Date: Thu, 28 Nov 2024 15:19:12 +0800 Subject: [PATCH] update slic3r --- src/slic3r/CMakeLists.txt | 35 + src/slic3r/GUI/3DBed.cpp | 11 +- src/slic3r/GUI/3DBed.hpp | 4 +- src/slic3r/GUI/3DScene.cpp | 235 +- src/slic3r/GUI/3DScene.hpp | 60 +- src/slic3r/GUI/AMSMaterialsSetting.cpp | 136 +- src/slic3r/GUI/AMSSetting.cpp | 2 +- src/slic3r/GUI/AVVideoDecoder.cpp | 152 ++ src/slic3r/GUI/AVVideoDecoder.hpp | 46 + src/slic3r/GUI/AmsMappingPopup.cpp | 2 +- src/slic3r/GUI/BedShapeDialog.cpp | 2 +- src/slic3r/GUI/BindDialog.cpp | 6 +- src/slic3r/GUI/BitmapCache.cpp | 1 + src/slic3r/GUI/Camera.cpp | 223 +- src/slic3r/GUI/Camera.hpp | 20 +- src/slic3r/GUI/CameraPopup.cpp | 4 +- src/slic3r/GUI/CameraUtils.cpp | 2 +- src/slic3r/GUI/CameraUtils.hpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 56 +- src/slic3r/GUI/ConfigWizard.cpp | 2 +- src/slic3r/GUI/CreatePresetsDialog.cpp | 89 +- src/slic3r/GUI/CreatePresetsDialog.hpp | 2 + src/slic3r/GUI/DeviceManager.cpp | 341 ++- src/slic3r/GUI/DeviceManager.hpp | 44 +- src/slic3r/GUI/DownloadProgressDialog.cpp | 31 +- src/slic3r/GUI/DownloadProgressDialog.hpp | 4 +- src/slic3r/GUI/Field.cpp | 13 +- src/slic3r/GUI/GCodeViewer.cpp | 46 +- src/slic3r/GUI/GCodeViewer.hpp | 5 +- src/slic3r/GUI/GLCanvas3D.cpp | 498 +++- src/slic3r/GUI/GLCanvas3D.hpp | 38 +- src/slic3r/GUI/GLModel.cpp | 424 +++- src/slic3r/GUI/GLModel.hpp | 21 +- src/slic3r/GUI/GLShader.cpp | 20 +- src/slic3r/GUI/GLShadersManager.cpp | 5 +- src/slic3r/GUI/GLToolbar.cpp | 7 +- src/slic3r/GUI/GLToolbar.hpp | 3 + src/slic3r/GUI/GUI.cpp | 47 +- src/slic3r/GUI/GUI_App.cpp | 420 +++- src/slic3r/GUI/GUI_App.hpp | 13 +- src/slic3r/GUI/GUI_Factories.cpp | 253 +- src/slic3r/GUI/GUI_Factories.hpp | 17 +- src/slic3r/GUI/GUI_ObjectList.cpp | 230 +- src/slic3r/GUI/GUI_ObjectList.hpp | 6 + src/slic3r/GUI/GUI_ObjectSettings.cpp | 2 +- src/slic3r/GUI/GUI_ObjectTableSettings.cpp | 4 +- src/slic3r/GUI/GUI_Preview.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp | 129 +- src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 149 +- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 34 +- src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp | 1070 ++++++++ src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp | 178 ++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 15 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 67 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp | 113 +- src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp | 16 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 108 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 98 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 55 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 30 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 2198 +++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 175 ++ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 265 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 26 +- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 23 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 11 +- src/slic3r/GUI/Gizmos/GLGizmoText.cpp | 543 ++-- src/slic3r/GUI/Gizmos/GLGizmoText.hpp | 43 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 17 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 174 +- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 13 +- .../GUI/Gizmos/GizmoObjectManipulation.cpp | 561 ++++- .../GUI/Gizmos/GizmoObjectManipulation.hpp | 27 +- src/slic3r/GUI/HMS.cpp | 18 +- src/slic3r/GUI/HMS.hpp | 4 +- src/slic3r/GUI/IMSlider.cpp | 2 +- src/slic3r/GUI/IconManager.cpp | 413 ++++ src/slic3r/GUI/IconManager.hpp | 134 + src/slic3r/GUI/ImGuiWrapper.cpp | 194 +- src/slic3r/GUI/ImGuiWrapper.hpp | 77 +- src/slic3r/GUI/InstanceCheck.cpp | 12 +- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 119 +- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 3 + src/slic3r/GUI/Jobs/BindJob.cpp | 4 +- src/slic3r/GUI/Jobs/BoostThreadWorker.cpp | 182 ++ src/slic3r/GUI/Jobs/BoostThreadWorker.hpp | 155 ++ src/slic3r/GUI/Jobs/BusyCursorJob.hpp | 54 + src/slic3r/GUI/Jobs/EmbossJob.cpp | 1153 +++++++++ src/slic3r/GUI/Jobs/EmbossJob.hpp | 351 +++ src/slic3r/GUI/Jobs/FillBedJob.cpp | 13 +- src/slic3r/GUI/Jobs/Job.cpp | 8 +- src/slic3r/GUI/Jobs/Job.hpp | 3 +- src/slic3r/GUI/Jobs/JobNew.hpp | 68 + .../Jobs/NotificationProgressIndicator.cpp | 5 +- .../Jobs/NotificationProgressIndicator.hpp | 1 + src/slic3r/GUI/Jobs/PlaterWorker.hpp | 155 ++ src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp | 124 + src/slic3r/GUI/Jobs/Worker.hpp | 140 ++ src/slic3r/GUI/KBShortcutsDialog.cpp | 28 +- src/slic3r/GUI/MainFrame.cpp | 229 +- src/slic3r/GUI/MainFrame.hpp | 9 +- src/slic3r/GUI/MediaFilePanel.cpp | 24 +- src/slic3r/GUI/MediaFilePanel.h | 4 +- src/slic3r/GUI/MediaPlayCtrl.cpp | 100 +- src/slic3r/GUI/MediaPlayCtrl.h | 15 +- src/slic3r/GUI/MeshUtils.hpp | 2 +- src/slic3r/GUI/Monitor.cpp | 26 +- src/slic3r/GUI/Monitor.hpp | 1 - src/slic3r/GUI/MsgDialog.cpp | 129 +- src/slic3r/GUI/MsgDialog.hpp | 21 +- src/slic3r/GUI/NetworkTestDialog.cpp | 46 +- src/slic3r/GUI/NetworkTestDialog.hpp | 6 +- src/slic3r/GUI/NotificationManager.cpp | 39 +- src/slic3r/GUI/NotificationManager.hpp | 13 +- src/slic3r/GUI/ObjColorDialog.cpp | 27 +- src/slic3r/GUI/ObjColorDialog.hpp | 3 + src/slic3r/GUI/ObjectDataViewModel.cpp | 87 +- src/slic3r/GUI/ObjectDataViewModel.hpp | 26 +- src/slic3r/GUI/OpenGLManager.cpp | 1 + src/slic3r/GUI/OptionsGroup.cpp | 12 +- src/slic3r/GUI/OptionsGroup.hpp | 2 + src/slic3r/GUI/PartPlate.cpp | 1529 ++++++------ src/slic3r/GUI/PartPlate.hpp | 144 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 10 +- src/slic3r/GUI/Plater.cpp | 922 +++++-- src/slic3r/GUI/Plater.hpp | 22 +- src/slic3r/GUI/Preferences.cpp | 175 +- src/slic3r/GUI/Preferences.hpp | 2 + src/slic3r/GUI/PresetComboBoxes.cpp | 83 +- src/slic3r/GUI/PrintOptionsDialog.cpp | 8 +- src/slic3r/GUI/PrintOptionsDialog.hpp | 1 + src/slic3r/GUI/Printer/PrinterFileSystem.cpp | 46 +- src/slic3r/GUI/Printer/gstQIDIsrc.c | 14 +- src/slic3r/GUI/Printer/gstQIDIsrc.h | 2 +- src/slic3r/GUI/PrinterWebView.cpp | 9 +- src/slic3r/GUI/Project.cpp | 45 +- src/slic3r/GUI/Project.hpp | 4 +- src/slic3r/GUI/ReleaseNote.cpp | 496 ++-- src/slic3r/GUI/ReleaseNote.hpp | 24 +- src/slic3r/GUI/SelectMachine.cpp | 134 +- src/slic3r/GUI/SelectMachine.hpp | 5 +- src/slic3r/GUI/Selection.cpp | 486 ++-- src/slic3r/GUI/Selection.hpp | 56 +- src/slic3r/GUI/StatusPanel.cpp | 365 ++- src/slic3r/GUI/StatusPanel.hpp | 30 +- src/slic3r/GUI/StepMeshDialog.cpp | 357 +++ src/slic3r/GUI/StepMeshDialog.hpp | 50 + src/slic3r/GUI/SurfaceDrag.cpp | 716 ++++++ src/slic3r/GUI/SurfaceDrag.hpp | 170 ++ src/slic3r/GUI/Tab.cpp | 251 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 1 + src/slic3r/GUI/WebGuideDialog.cpp | 8 +- src/slic3r/GUI/WebUserLoginDialog.hpp | 1 - src/slic3r/GUI/WebViewDialog.cpp | 305 ++- src/slic3r/GUI/WebViewDialog.hpp | 20 +- src/slic3r/GUI/Widgets/AMSControl.cpp | 174 +- src/slic3r/GUI/Widgets/AMSControl.hpp | 25 + src/slic3r/GUI/Widgets/Button.cpp | 8 +- src/slic3r/GUI/Widgets/ComboBox.cpp | 11 +- src/slic3r/GUI/Widgets/DropDown.cpp | 3 +- src/slic3r/GUI/Widgets/ImageSwitchButton.cpp | 37 +- src/slic3r/GUI/Widgets/Label.cpp | 7 +- src/slic3r/GUI/Widgets/ProgressDialog.hpp | 2 +- src/slic3r/GUI/Widgets/SideTools.cpp | 2 +- src/slic3r/GUI/Widgets/TabCtrl.cpp | 5 +- src/slic3r/GUI/Widgets/TempInput.cpp | 34 +- src/slic3r/GUI/Widgets/WebView.cpp | 5 + src/slic3r/GUI/WipeTowerDialog.cpp | 12 +- src/slic3r/GUI/calib_dlg.cpp | 2 +- src/slic3r/GUI/wxMediaCtrl2.cpp | 16 +- src/slic3r/GUI/wxMediaCtrl2.h | 2 + src/slic3r/GUI/wxMediaCtrl2.mm | 12 + src/slic3r/GUI/wxMediaCtrl3.cpp | 289 +++ src/slic3r/GUI/wxMediaCtrl3.h | 91 + src/slic3r/Utils/.vscode/settings.json | 5 + src/slic3r/Utils/CalibUtils.cpp | 39 +- src/slic3r/Utils/CalibUtils.hpp | 2 + src/slic3r/Utils/FontUtils.cpp | 5 +- src/slic3r/Utils/Http.cpp | 4 +- src/slic3r/Utils/NetworkAgent.cpp | 52 +- src/slic3r/Utils/NetworkAgent.hpp | 19 +- src/slic3r/Utils/PresetUpdater.cpp | 515 ++-- src/slic3r/Utils/ProfileDescription.hpp | 12 + src/slic3r/Utils/RaycastManager.cpp | 391 +++ src/slic3r/Utils/RaycastManager.hpp | 188 ++ src/slic3r/Utils/qidi_networking.hpp | 16 +- src/slic3r/pchheader.hpp | 5 + 196 files changed, 18701 insertions(+), 3803 deletions(-) create mode 100644 src/slic3r/GUI/AVVideoDecoder.cpp create mode 100644 src/slic3r/GUI/AVVideoDecoder.hpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp create mode 100644 src/slic3r/GUI/IconManager.cpp create mode 100644 src/slic3r/GUI/IconManager.hpp create mode 100644 src/slic3r/GUI/Jobs/BoostThreadWorker.cpp create mode 100644 src/slic3r/GUI/Jobs/BoostThreadWorker.hpp create mode 100644 src/slic3r/GUI/Jobs/BusyCursorJob.hpp create mode 100644 src/slic3r/GUI/Jobs/EmbossJob.cpp create mode 100644 src/slic3r/GUI/Jobs/EmbossJob.hpp create mode 100644 src/slic3r/GUI/Jobs/JobNew.hpp create mode 100644 src/slic3r/GUI/Jobs/PlaterWorker.hpp create mode 100644 src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp create mode 100644 src/slic3r/GUI/Jobs/Worker.hpp create mode 100644 src/slic3r/GUI/StepMeshDialog.cpp create mode 100644 src/slic3r/GUI/StepMeshDialog.hpp create mode 100644 src/slic3r/GUI/SurfaceDrag.cpp create mode 100644 src/slic3r/GUI/SurfaceDrag.hpp create mode 100644 src/slic3r/GUI/wxMediaCtrl3.cpp create mode 100644 src/slic3r/GUI/wxMediaCtrl3.h create mode 100644 src/slic3r/Utils/.vscode/settings.json create mode 100644 src/slic3r/Utils/RaycastManager.cpp create mode 100644 src/slic3r/Utils/RaycastManager.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 8695440..1623977 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -141,12 +141,16 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoFaceDetector.hpp GUI/Gizmos/GLGizmoMeasure.cpp GUI/Gizmos/GLGizmoMeasure.hpp + GUI/Gizmos/GLGizmoBrimEars.cpp + GUI/Gizmos/GLGizmoBrimEars.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/GLGizmoSVG.cpp + GUI/Gizmos/GLGizmoSVG.hpp GUI/Gizmos/GLGizmoMeshBoolean.cpp GUI/Gizmos/GLGizmoMeshBoolean.hpp GUI/GLSelectionRectangle.cpp @@ -187,6 +191,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_Utils.hpp GUI/I18N.cpp GUI/I18N.hpp + GUI/IconManager.cpp + GUI/IconManager.hpp GUI/MainFrame.cpp GUI/MainFrame.hpp GUI/QDTTopbar.cpp @@ -297,6 +303,10 @@ set(SLIC3R_GUI_SOURCES GUI/RemovableDriveManager.hpp GUI/SendSystemInfoDialog.cpp GUI/SendSystemInfoDialog.hpp + GUI/StepMeshDialog.cpp + GUI/StepMeshDialog.hpp + GUI/SurfaceDrag.cpp + GUI/SurfaceDrag.hpp GUI/PlateSettingsDialog.cpp GUI/PlateSettingsDialog.hpp GUI/ImGuiWrapper.hpp @@ -326,6 +336,15 @@ set(SLIC3R_GUI_SOURCES GUI/UpdateDialogs.hpp GUI/Jobs/Job.hpp GUI/Jobs/Job.cpp + GUI/Jobs/JobNew.hpp + GUI/Jobs/Worker.hpp + GUI/Jobs/BoostThreadWorker.hpp + GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/BusyCursorJob.hpp + GUI/Jobs/ThreadSafeQueue.hpp + GUI/Jobs/PlaterWorker.hpp + GUI/Jobs/EmbossJob.cpp + GUI/Jobs/EmbossJob.hpp GUI/Jobs/PlaterJob.hpp GUI/Jobs/PlaterJob.cpp GUI/Jobs/UpgradeNetworkJob.hpp @@ -466,6 +485,8 @@ set(SLIC3R_GUI_SOURCES Utils/PresetUpdater.hpp Utils/Process.cpp Utils/Process.hpp + Utils/RaycastManager.cpp + Utils/RaycastManager.hpp Utils/Profile.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp @@ -535,12 +556,17 @@ if (APPLE) GUI/InstanceCheckMac.h GUI/wxMediaCtrl2.mm GUI/wxMediaCtrl2.h + GUI/wxMediaCtrl3.h ) FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration) else () list(APPEND SLIC3R_GUI_SOURCES + GUI/AVVideoDecoder.cpp + GUI/AVVideoDecoder.hpp GUI/wxMediaCtrl2.cpp GUI/wxMediaCtrl2.h + GUI/wxMediaCtrl3.cpp + GUI/wxMediaCtrl3.h ) endif () @@ -597,6 +623,15 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () +if (NOT APPLE) + pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET + libavcodec + libswscale + libavutil + ) + target_link_libraries(libslic3r_gui PkgConfig::LIBAV) +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. diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index fa7ebd1..05bb705 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -249,6 +249,7 @@ bool Bed3D::set_shape(const Pointfs& printable_area, const double printable_heig m_type = type; //m_texture_filename = texture_filename; m_model_filename = model_filename; + std::replace(m_model_filename.begin(), m_model_filename.end(), '\\', '/'); //QDS: add part plate logic m_extended_bounding_box = this->calc_extended_bounding_box(false); @@ -321,8 +322,14 @@ 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) +BoundingBoxf3 Bed3D::get_cur_bed_model_box() { + BoundingBoxf3 model_bb = m_model.get_bounding_box(); + model_bb.translate(m_model_offset); + return model_bb; +} + +void Bed3D::render(GLCanvas3D &canvas, bool bottom, float scale_factor, bool show_axes) { render_internal(canvas, bottom, scale_factor, show_axes); } @@ -559,7 +566,7 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const 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"); + GLint tex_coords_id = shader->get_attrib_location("v_tex_coord"); // show the temporary texture while no compressed data is available GLuint tex_id = (GLuint)temp_texture->get_id(); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index a4f4061..5d7f1a9 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -133,7 +133,9 @@ public: // Bounding box around the print bed, axes and model, for rendering. const BoundingBoxf3& extended_bounding_box() const { return m_extended_bounding_box; } - + BoundingBoxf3 get_cur_bed_model_box(); + const std::string & get_model_filename() { return m_model_filename; } + const GLModel & get_model() { return m_model; } // Check against an expanded 2d bounding box. //FIXME shall one check against the real build volume? bool contains(const Point& point) const; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f655a2c..2fbda57 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -10,6 +10,7 @@ #include "GLShader.hpp" #include "GUI_App.hpp" #include "GUI_Colors.hpp" + #include "Plater.hpp" #include "BitmapCache.hpp" @@ -27,6 +28,7 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/QuadricEdgeCollapse.hpp" #include #include @@ -415,6 +417,44 @@ std::array, 5> GLVolume::MODEL_COLOR = { { { 1.0f, 1.0f, 0.0f, 1.f } } }; +float GLVolume::LOD_HIGH_ZOOM = 3.5f; +float GLVolume::LOD_MIDDLE_ZOOM = 2.8f; +float GLVolume::LOD_SMALL_ZOOM = 1.4f; +float GLVolume::LAST_CAMERA_ZOOM_VALUE = 0.0f; +const float ZOOM_THRESHOLD = 0.3f; +const unsigned char LOD_UPDATE_FREQUENCY = 20; +const Vec2i LOD_SCREEN_MIN = Vec2i(45, 35); +const Vec2i LOD_SCREEN_MAX = Vec2i(65, 55); + +Vec2f calc_pt_in_screen(const Vec3d &pt, const Matrix4d &view_proj_mat, int window_width, int window_height) +{ + auto tran = view_proj_mat; + Vec4d temp_center(pt.x(), pt.y(), pt.z(), 1.0); + Vec4d temp_ndc = tran * temp_center; + Vec3d screen_box_center = Vec3d(temp_ndc.x(), temp_ndc.y(), temp_ndc.z()) / temp_ndc.w(); + + float x = 0.5f * (1 + screen_box_center(0)) * window_width; + float y = 0.5f * (1 - screen_box_center(1)) * window_height; + return Vec2f(x, y); +} + +LOD_LEVEL calc_volume_box_in_screen_bigger_than_threshold(const BoundingBoxf3 &v_box_in_world, const Matrix4d &view_proj_mat, + int window_width, int window_height) +{ + auto s_min = calc_pt_in_screen(v_box_in_world.min, view_proj_mat, window_width, window_height); + auto s_max = calc_pt_in_screen(v_box_in_world.max, view_proj_mat, window_width, window_height); + auto size_x = abs(s_max.x() - s_min.x()); + auto size_y = abs(s_max.y() - s_min.y()); + if (size_x >= LOD_SCREEN_MAX.x() || size_y >= LOD_SCREEN_MAX.y()) { + return LOD_LEVEL::HIGH; + } + if (size_x <= LOD_SCREEN_MIN.x() && size_y <= LOD_SCREEN_MIN.y()) { + return LOD_LEVEL::SMALL; + } else { + return LOD_LEVEL::MIDDLE; + } +} + void GLVolume::update_render_colors() { GLVolume::DISABLED_COLOR = GLColor(RenderColor::colors[RenderCol_Model_Disable]); @@ -463,6 +503,8 @@ GLVolume::GLVolume(float r, float g, float b, float a, bool create_index_data) , force_sinking_contours(false) , tverts_range(0, size_t(-1)) , qverts_range(0, size_t(-1)) + , tverts_range_lod(0, size_t(-1)) + , qverts_range_lod(0, size_t(-1)) { color = { r, g, b, a }; set_render_color(color); @@ -580,6 +622,93 @@ std::array color_from_model_volume(const ModelVolume& model_volume) return color; } +bool GLVolume::simplify_mesh(const TriangleMesh &mesh, std::shared_ptr va, LOD_LEVEL lod) const { + return simplify_mesh(mesh.its,va,lod); +} +#define SUPER_LARGE_FACES 500000 +#define LARGE_FACES 100000 +bool GLVolume::simplify_mesh(const indexed_triangle_set &_its, std::shared_ptr va, LOD_LEVEL lod) const +{ + if (_its.indices.size() == 0 || _its.vertices.size() == 0) { return false; } + auto its = std::make_unique(_its); + auto m_state = std::make_unique(); + if (lod == LOD_LEVEL::MIDDLE) { + m_state->config.max_error = 0.5f; + if (_its.indices.size() > SUPER_LARGE_FACES) { + m_state->config.max_error = 0.4f; + } else if (_its.indices.size() > LARGE_FACES) { + m_state->config.max_error = 0.3f; + } + } + if (lod == LOD_LEVEL::SMALL) { + m_state->config.max_error = 0.1f; + if (_its.indices.size() > SUPER_LARGE_FACES) { + m_state->config.max_error = 0.08f; + } else if (_its.indices.size() > LARGE_FACES) { + m_state->config.max_error = 0.05f; + } + } + + //std::mutex m_state_mutex; + std::thread m_worker = std::thread( + [va,this](std::unique_ptr its, std::unique_ptr state) { + // Checks that the UI thread did not request cancellation, throws if so. + std::function throw_on_cancel = []() { + }; + std::function statusfn = [&state](int percent) { + state->progress = percent; + }; + // Initialize. + uint32_t triangle_count = 0; + float max_error = std::numeric_limits::max(); + { + if (state->config.use_count) + triangle_count = state->config.wanted_count; + if (!state->config.use_count) + max_error = state->config.max_error; + state->progress = 0; + state->result.reset(); + state->status = State::Status::running; + } + int init_face_count = its->indices.size(); + TriangleMesh origin_mesh(*its); + try { // Start the actual calculation. + its_quadric_edge_collapse(*its, triangle_count, &max_error, throw_on_cancel, statusfn); + } catch (std::exception&) { + state->status = State::idle; + } + if (state->status == State::Status::running) { + // We were not cancelled, the result is valid. + state->status = State::Status::idle; + state->result = std::move(its); + } + if (state->result) { + int end_face_count = (*state->result).indices.size(); + if (init_face_count < 200 || (init_face_count < 1000 && end_face_count < init_face_count * 0.5)) { + return; + } + TriangleMesh mesh(*state->result); + float eps = 1.0f; + Vec3f origin_min = origin_mesh.stats().min - Vec3f(eps, eps, eps); + Vec3f origin_max = origin_mesh.stats().max + Vec3f(eps, eps, eps); + if (origin_min.x() < mesh.stats().min.x() && origin_min.y() < mesh.stats().min.y() && origin_min.z() < mesh.stats().min.z()&& + origin_max.x() > mesh.stats().max.x() && origin_max.y() > mesh.stats().max.y() && origin_max.z() > mesh.stats().max.z()) { + if (va && va.use_count() >= 2) { + va->load_mesh(mesh); + } + } + else { + state->status = State::cancelling; + } + } + }, + std::move(its),std::move(m_state)); + if (m_worker.joinable()) { + m_worker.detach(); + } + return true; +} + Transform3d GLVolume::world_matrix() const { Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); @@ -688,6 +817,12 @@ void GLVolume::render(bool with_outline, const std::array& body_color) glsafe(::glCullFace(GL_BACK)); glsafe(::glPushMatrix()); + auto camera = GUI::wxGetApp().plater()->get_camera(); + auto zoom = camera.get_zoom(); + Transform3d vier_mat = camera.get_view_matrix(); + Matrix4d vier_proj_mat = camera.get_projection_matrix().matrix() * vier_mat.matrix(); + const std::array &viewport = camera.get_viewport(); + // QDS: add logic for mmu segmentation rendering auto render_body = [&]() { bool color_volume = false; @@ -704,6 +839,11 @@ void GLVolume::render(bool with_outline, const std::array& body_color) if (mv->mmu_segmentation_facets.empty()) break; + std::vector> colors = get_extruders_colors(); + if (colors.size() == 1) { + break; + } + color_volume = true; if (mv->mmu_segmentation_facets.timestamp() != mmuseg_ts) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1%, name %2%, current mmuseg_ts %3%, current color size %4%") @@ -713,10 +853,11 @@ void GLVolume::render(bool with_outline, const std::array& body_color) mv->mmu_segmentation_facets.get_facets(*mv, its_per_color); mmuseg_ivas.resize(its_per_color.size()); for (int idx = 0; idx < its_per_color.size(); idx++) { - mmuseg_ivas[idx].load_its_flat_shading(its_per_color[idx]); - mmuseg_ivas[idx].finalize_geometry(true); + if (its_per_color[idx].indices.size() > 0) { + mmuseg_ivas[idx].load_its_flat_shading(its_per_color[idx]); + mmuseg_ivas[idx].finalize_geometry(true); + } } - mmuseg_ts = mv->mmu_segmentation_facets.timestamp(); BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1%, name %2%, new mmuseg_ts %3%, new color size %4%") %this %this->name %mmuseg_ts %mmuseg_ivas.size(); @@ -734,8 +875,8 @@ void GLVolume::render(bool with_outline, const std::array& body_color) } glsafe(::glMultMatrixd(world_matrix().data())); for (int idx = 0; idx < mmuseg_ivas.size(); idx++) { - GLIndexedVertexArray& iva = mmuseg_ivas[idx]; - if (iva.triangle_indices_size == 0 && iva.quad_indices_size == 0) + GLIndexedVertexArray* iva = &mmuseg_ivas[idx]; + if (iva->triangle_indices_size == 0 && iva->quad_indices_size == 0) continue; if (shader) { @@ -745,6 +886,7 @@ void GLVolume::render(bool with_outline, const std::array& body_color) int extruder_id = mv->extruder_id(); //shader->set_uniform("uniform_color", colors[extruder_id - 1]); //to make black not too hard too see + if (extruder_id <= 0) { extruder_id = 1; } std::array new_color = adjust_color_for_rendering(colors[extruder_id - 1]); shader->set_uniform("uniform_color", new_color); } @@ -763,7 +905,7 @@ void GLVolume::render(bool with_outline, const std::array& body_color) } } } - iva.render(this->tverts_range, this->qverts_range); + iva->render(this->tverts_range, this->qverts_range); /*if (force_native_color && (render_color[3] < 1.0)) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< boost::format(", this %1%, name %2%, tverts_range {%3,%4}, qverts_range{%5%, %6%}") %this %this->name %this->tverts_range.first %this->tverts_range.second @@ -773,7 +915,32 @@ void GLVolume::render(bool with_outline, const std::array& body_color) } else { glsafe(::glMultMatrixd(world_matrix().data())); - this->indexed_vertex_array->render(this->tverts_range, this->qverts_range); + auto render_which = [this](std::shared_ptr cur) { + if (cur->vertices_and_normals_interleaved_VBO_id > 0) { + cur->render(tverts_range_lod, qverts_range_lod); + } else {// if (cur->vertices_and_normals_interleaved_VBO_id == 0) + if (cur->triangle_indices.size() > 0) { + cur->finalize_geometry(true); + cur->render(tverts_range_lod, qverts_range_lod); + } else { + indexed_vertex_array->render(this->tverts_range, this->qverts_range); + } + } + }; + Transform3d world_tran = world_matrix(); + m_lod_update_index++; + if (abs(zoom - LAST_CAMERA_ZOOM_VALUE) > ZOOM_THRESHOLD || m_lod_update_index >= LOD_UPDATE_FREQUENCY){ + m_lod_update_index = 0; + LAST_CAMERA_ZOOM_VALUE = zoom; + m_cur_lod_level = calc_volume_box_in_screen_bigger_than_threshold(transformed_bounding_box(), vier_proj_mat, viewport[2], viewport[3]); + } + if (m_cur_lod_level == LOD_LEVEL::SMALL && indexed_vertex_array_small) { + render_which(indexed_vertex_array_small); + } else if (m_cur_lod_level == LOD_LEVEL::MIDDLE && indexed_vertex_array_middle) { + render_which(indexed_vertex_array_middle); + } else { + this->indexed_vertex_array->render(this->tverts_range, this->qverts_range); + } } }; @@ -887,9 +1054,22 @@ void GLVolume::render(bool with_outline, const std::array& body_color) glsafe(::glPushMatrix()); Transform3d matrix = world_matrix(); + Transform3d world_tran = matrix; matrix.scale(scale); glsafe(::glMultMatrixd(matrix.data())); - this->indexed_vertex_array->render(this->tverts_range, this->qverts_range); + m_lod_update_index++; + if (abs(zoom - LAST_CAMERA_ZOOM_VALUE) > ZOOM_THRESHOLD || m_lod_update_index >= LOD_UPDATE_FREQUENCY) { + m_lod_update_index = 0; + LAST_CAMERA_ZOOM_VALUE = zoom; + m_cur_lod_level = calc_volume_box_in_screen_bigger_than_threshold(transformed_bounding_box(), vier_proj_mat, viewport[2], viewport[3]); + } + if (m_cur_lod_level == LOD_LEVEL::SMALL && indexed_vertex_array_small && indexed_vertex_array_small->vertices_and_normals_interleaved_VBO_id > 0) { + this->indexed_vertex_array_small->render(this->tverts_range_lod, this->qverts_range_lod); + } else if (m_cur_lod_level == LOD_LEVEL::MIDDLE && indexed_vertex_array_middle && indexed_vertex_array_middle->vertices_and_normals_interleaved_VBO_id > 0) { + this->indexed_vertex_array_middle->render(this->tverts_range_lod, this->qverts_range_lod); + } else { + this->indexed_vertex_array->render(this->tverts_range, this->qverts_range); + } //BOOST_LOG_TRIVIAL(info) << boost::format(": %1%, outline render for body, shader name %2%")%__LINE__ %shader->get_name(); shader->set_uniform("is_outline", false); @@ -957,6 +1137,7 @@ void GLVolume::simple_render(GLShaderProgram *shader, ModelObjectPtrs &model_obj if (shader) { if (idx == 0) { int extruder_id = model_volume->extruder_id(); + if (extruder_id <= 0) { extruder_id = 1; } //to make black not too hard too see std::array new_color = adjust_color_for_rendering(extruder_colors[extruder_id - 1]); if (ban_light) { @@ -998,6 +1179,14 @@ void GLVolume::simple_render(GLShaderProgram *shader, ModelObjectPtrs &model_obj glFrontFace(GL_CCW); } +void GLVolume::set_bounding_boxes_as_dirty() +{ + m_lod_update_index = LOD_UPDATE_FREQUENCY; + m_transformed_bounding_box.reset(); + m_transformed_convex_hull_bounding_box.reset(); + m_transformed_non_sinking_bounding_box.reset(); +} + bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); } @@ -1067,12 +1256,13 @@ std::vector GLVolumeCollection::load_object( int obj_idx, const std::vector &instance_idxs, const std::string &color_by, - bool opengl_initialized) + bool opengl_initialized, + bool lod_enabled) { std::vector volumes_idx; for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) - volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized)); + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized, false, lod_enabled)); return volumes_idx; } @@ -1084,7 +1274,8 @@ int GLVolumeCollection::load_object_volume( const std::string &color_by, bool opengl_initialized, bool in_assemble_view, - bool use_loaded_id) + bool use_loaded_id, + bool lod_enabled) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); @@ -1112,6 +1303,8 @@ int GLVolumeCollection::load_object_volume( else { GLVolume* first_volume = *(volume_set.begin()); new_volume->indexed_vertex_array = first_volume->indexed_vertex_array; + new_volume->indexed_vertex_array_middle = first_volume->indexed_vertex_array_middle; + new_volume->indexed_vertex_array_small = first_volume->indexed_vertex_array_small; need_create_mesh = false; } volume_set.emplace(new_volume); @@ -1127,6 +1320,21 @@ int GLVolumeCollection::load_object_volume( #if ENABLE_SMOOTH_NORMALS v.indexed_vertex_array->load_mesh(mesh, true); #else + if (lod_enabled) { + if (v.indexed_vertex_array_middle == nullptr) + { + v.indexed_vertex_array_middle = std::make_shared(); + } + + v.simplify_mesh(mesh, v.indexed_vertex_array_middle, LOD_LEVEL::MIDDLE); // include finalize_geometry + + if (v.indexed_vertex_array_small == nullptr) + { + v.indexed_vertex_array_small = std::make_shared(); + } + v.simplify_mesh(mesh, v.indexed_vertex_array_small, LOD_LEVEL::SMALL); + } + v.indexed_vertex_array->load_mesh(mesh); #endif // ENABLE_SMOOTH_NORMALS v.indexed_vertex_array->finalize_geometry(opengl_initialized); @@ -1369,7 +1577,12 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, if (disable_cullface) glsafe(::glDisable(GL_CULL_FACE)); + auto camera = GUI::wxGetApp().plater()->get_camera(); for (GLVolumeWithIdAndZ& volume : to_render) { + auto world_box = volume.first->transformed_bounding_box(); + if (!camera.getFrustum().intersects(world_box, camera.get_type_as_string() == "perspective")) { + continue; + } #if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT if (type == ERenderType::Transparent) { volume.first->force_transparent = true; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index c9f3eb8..a3533ed 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -10,7 +10,7 @@ // QDS #include "libslic3r/ObjectID.hpp" -#include "GLModel.hpp" +#include "MeshUtils.hpp" #include "GLShader.hpp" #include @@ -257,7 +257,19 @@ private: BoundingBox m_bounding_box; }; +enum LOD_LEVEL { + HIGH, // Origin data + MIDDLE, + SMALL, +}; + class GLVolume { + static float LOD_HIGH_ZOOM; + static float LOD_MIDDLE_ZOOM; + static float LOD_SMALL_ZOOM; + static float LAST_CAMERA_ZOOM_VALUE; + mutable LOD_LEVEL m_cur_lod_level = LOD_LEVEL::HIGH; + mutable unsigned char m_lod_update_index = 0; public: std::string name; bool is_text_shape{false}; @@ -292,6 +304,9 @@ public: virtual ~GLVolume() = default; // QDS + bool simplify_mesh(const TriangleMesh &mesh, std::shared_ptr va, LOD_LEVEL lod) const; + bool simplify_mesh(const indexed_triangle_set &_its, std::shared_ptr va, LOD_LEVEL lod) const; + protected: Geometry::Transformation m_instance_transformation; Geometry::Transformation m_volume_transformation; @@ -327,6 +342,30 @@ protected: SinkingContours m_sinking_contours; + // guards m_state + struct Configuration + { + bool use_count = false;//diff with glgizmoSimplify + float decimate_ratio = 50.f; // in percent + uint32_t wanted_count = 0; // initialize by percents + float max_error = 1.; // maximal quadric error + + bool operator==(const Configuration &rhs) + { + return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio && wanted_count == rhs.wanted_count && max_error == rhs.max_error); + } + bool operator!=(const Configuration &rhs) { return !(*this == rhs); } + }; + struct State + { + enum Status { idle, running, cancelling }; + + Status status = idle; + int progress = 0; // percent of done work + Configuration config; // Configuration we started with. + const ModelVolume * mv = nullptr; + std::unique_ptr result; + }; public: // Color of the triangles / quads held by this volume. std::array color; @@ -400,7 +439,9 @@ public: EHoverState hover; // Interleaved triangles & normals with indexed triangles & quads. - std::shared_ptr indexed_vertex_array; + std::shared_ptr indexed_vertex_array; + std::shared_ptr indexed_vertex_array_middle; + std::shared_ptr indexed_vertex_array_small; const TriangleMesh * ori_mesh{nullptr}; // QDS mutable std::vector mmuseg_ivas; @@ -409,7 +450,8 @@ public: // Ranges of triangle and quad indices to be rendered. std::pair tverts_range; std::pair qverts_range; - + std::pair tverts_range_lod; + std::pair qverts_range_lod; // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts // of the extrusions per layer. std::vector print_zs; @@ -533,11 +575,7 @@ public: 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(); - } + void set_bounding_boxes_as_dirty(); bool is_sla_support() const; bool is_sla_pad() const; @@ -634,7 +672,8 @@ public: int obj_idx, const std::vector &instance_idxs, const std::string &color_by, - bool opengl_initialized); + bool opengl_initialized, + bool lod_enabled); int load_object_volume( const ModelObject *model_object, @@ -644,7 +683,8 @@ public: const std::string &color_by, bool opengl_initialized, bool in_assemble_view = false, - bool use_loaded_id = false); + bool use_loaded_id = false, + bool lod_enabled = true); // Load SLA auxiliary GLVolumes (for support trees or pad). void load_object_auxiliary( diff --git a/src/slic3r/GUI/AMSMaterialsSetting.cpp b/src/slic3r/GUI/AMSMaterialsSetting.cpp index 0ba90b4..c884aa8 100644 --- a/src/slic3r/GUI/AMSMaterialsSetting.cpp +++ b/src/slic3r/GUI/AMSMaterialsSetting.cpp @@ -259,7 +259,7 @@ void AMSMaterialsSetting::create_panel_normal(wxWindow* parent) m_panel_SN->Fit(); wxBoxSizer* m_tip_sizer = new wxBoxSizer(wxHORIZONTAL); - m_tip_readonly = new Label(parent, _L("Setting AMS slot information while printing is not supported")); + m_tip_readonly = new Label(parent, _L("")); m_tip_readonly->SetForegroundColour(*wxBLACK); m_tip_readonly->SetBackgroundColour(*wxWHITE); m_tip_readonly->SetMinSize(wxSize(FromDIP(380), -1)); @@ -293,7 +293,7 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent) m_ratio_text->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); m_ratio_text->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { - wxLaunchDefaultBrowser(wxT("https://wiki.qidilab.com/en/software/qidi-studio/calibration_pa")); + wxLaunchDefaultBrowser(wxT("https://wiki.qidi3d.com/en/software/qidi-studio/calibration_pa")); }); @@ -354,11 +354,11 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent) parent->SetSizer(sizer); } -void AMSMaterialsSetting::paintEvent(wxPaintEvent &evt) +void AMSMaterialsSetting::paintEvent(wxPaintEvent &evt) { auto size = GetSize(); wxPaintDC dc(this); - dc.SetPen(wxPen(StateColor::darkModeColorFor(wxColour("#000000")), 1, wxSOLID)); + dc.SetPen(wxPen(StateColor::darkModeColorFor(wxColour("#000000")), 1, wxPENSTYLE_SOLID)); dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH)); dc.DrawRectangle(0, 0, size.x, size.y); } @@ -369,7 +369,7 @@ AMSMaterialsSetting::~AMSMaterialsSetting() m_comboBox_cali_result->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this); } -void AMSMaterialsSetting::input_min_finish() +void AMSMaterialsSetting::input_min_finish() { if (m_input_nozzle_min->GetTextCtrl()->GetValue().empty()) return; @@ -414,20 +414,27 @@ void AMSMaterialsSetting::update() void AMSMaterialsSetting::enable_confirm_button(bool en) { - m_button_confirm->Show(en); - if (!m_is_third) { - m_tip_readonly->Hide(); + m_tip_readonly->SetLabelText(wxEmptyString); + + if (!en) { + m_button_confirm->Show(obj->is_support_filament_setting_inprinting); } else { - //m_comboBox_filament->Show(en); - //m_readonly_filament->Show(!en); + m_button_confirm->Show(en); + } - if ( !is_virtual_tray() ) { - m_tip_readonly->SetLabelText(_L("Setting AMS slot information while printing is not supported")); - } - else { - m_tip_readonly->SetLabelText(_L("Setting Virtual slot information while printing is not supported")); + if (!m_is_third) { + m_tip_readonly->Hide(); + } + else { + if (!obj->is_support_filament_setting_inprinting) { + if (!is_virtual_tray()) { + m_tip_readonly->SetLabelText(_L("Setting AMS slot information while printing is not supported")); + } else { + m_tip_readonly->SetLabelText(_L("Setting Virtual slot information while printing is not supported")); + } } + m_tip_readonly->Wrap(FromDIP(380)); m_tip_readonly->Show(!en); } @@ -704,11 +711,16 @@ void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event) set_color(wxColour(color_num>>24&0xFF, color_num>>16&0xFF, color_num>>8&0xFF, color_num&0xFF)); } -void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event) +void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event) { - if(!m_is_third || obj->is_in_printing() || obj->can_resume()) + if(!m_is_third) return; + if (obj->is_in_printing() || obj->can_resume()) { + if (!obj->is_support_filament_setting_inprinting) { + return; + } + } std::vector ams_colors; for (auto ams_it = obj->amsList.begin(); ams_it != obj->amsList.end(); ++ams_it) { @@ -758,8 +770,8 @@ void AMSMaterialsSetting::update_widgets() Layout(); } -bool AMSMaterialsSetting::Show(bool show) -{ +bool AMSMaterialsSetting::Show(bool show) +{ if (show) { m_button_confirm->SetMinSize(AMS_MATERIALS_SETTING_BUTTON_SIZE); m_input_nozzle_max->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(20))); @@ -783,7 +795,7 @@ bool AMSMaterialsSetting::Show(bool show) Fit(); wxGetApp().UpdateDarkUI(this); } - return DPIDialog::Show(show); + return DPIDialog::Show(show); } void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_min, wxString temp_max, wxString k, wxString n) @@ -923,8 +935,8 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi } m_button_reset->Show(); - m_button_confirm->Show(); - } + //m_button_confirm->Show(); + } m_comboBox_filament->Set(filament_items); m_comboBox_filament->SetSelection(selection_idx); @@ -934,6 +946,9 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi m_comboBox_filament->SetValue(wxEmptyString); } + // Set the flag whether to open the filament setting dialog from the device page + m_comboBox_filament->SetClientData(new int(1)); + update(); Layout(); Fit(); @@ -967,6 +982,9 @@ void AMSMaterialsSetting::on_select_cali_result(wxCommandEvent &evt) void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) { + // Get the flag whether to open the filament setting dialog from the device page + int *from_printer = static_cast(m_comboBox_filament->GetClientData()); + m_filament_type = ""; PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (preset_bundle) { @@ -1036,7 +1054,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) m_filament_selection = evt.GetSelection(); //reset cali - int cali_select_idx; + int cali_select_idx = -1; if ( !this->obj || m_filament_selection < 0) { m_input_k_val->Enable(false); @@ -1048,6 +1066,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) m_comboBox_cali_result->SetValue(wxEmptyString); m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString); m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString); + m_comboBox_filament->SetClientData(new int(0)); return; } else { @@ -1081,8 +1100,23 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) wxArrayString items; m_pa_profile_items.clear(); m_comboBox_cali_result->SetValue(wxEmptyString); - + + auto get_cali_index = [this](const std::string &str) -> int { + for (int i = 0; i < int(m_pa_profile_items.size()); ++i) { + if (m_pa_profile_items[i].name == str) return i; + } + return 0; + }; + if (obj->cali_version >= 0) { + // add default item + PACalibResult default_item; + default_item.filament_id = ams_filament_id; + default_item.cali_idx = -1; + get_default_k_n_value(ams_filament_id, default_item.k_value, default_item.n_coef); + m_pa_profile_items.emplace_back(default_item); + items.push_back(_L("Default")); + m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString); std::vector cali_history = this->obj->pa_calib_tab; for (auto cali_item : cali_history) { @@ -1094,19 +1128,43 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) m_comboBox_cali_result->Set(items); if (tray_id == VIRTUAL_TRAY_ID) { - AmsTray selected_tray = this->obj->vt_tray; - cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items,selected_tray.cali_idx); - if (cali_select_idx >= 0) { + if (from_printer && (*from_printer == 1)) { + AmsTray selected_tray = this->obj->vt_tray; + cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items, selected_tray.cali_idx); + if (cali_select_idx >= 0) { + m_comboBox_cali_result->SetSelection(cali_select_idx); + } else { + m_comboBox_cali_result->SetSelection(0); + } + } + else { +#ifdef __APPLE__ + cali_select_idx = get_cali_index(m_comboBox_filament->GetValue().ToStdString()); +#else + cali_select_idx = get_cali_index(m_comboBox_filament->GetLabel().ToStdString()); +#endif m_comboBox_cali_result->SetSelection(cali_select_idx); } } else { - Ams* selected_ams = this->obj->amsList[std::to_string(ams_id)]; - if(!selected_ams) return; - AmsTray* selected_tray = selected_ams->trayList[std::to_string(tray_id)]; - if(!selected_tray) return; - cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items, selected_tray->cali_idx); - if (cali_select_idx >= 0) { + if (from_printer && (*from_printer == 1)) { + Ams *selected_ams = this->obj->amsList[std::to_string(ams_id)]; + if (!selected_ams) return; + AmsTray *selected_tray = selected_ams->trayList[std::to_string(tray_id)]; + if (!selected_tray) return; + cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items, selected_tray->cali_idx); + if (cali_select_idx >= 0) { + m_comboBox_cali_result->SetSelection(cali_select_idx); + } else { + m_comboBox_cali_result->SetSelection(0); + } + } + else { +#ifdef __APPLE__ + cali_select_idx = get_cali_index(m_comboBox_filament->GetValue().ToStdString()); +#else + cali_select_idx = get_cali_index(m_comboBox_filament->GetLabel().ToStdString()); +#endif m_comboBox_cali_result->SetSelection(cali_select_idx); } } @@ -1115,6 +1173,10 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) m_input_k_val->GetTextCtrl()->SetValue(float_to_string_with_precision(m_pa_profile_items[cali_select_idx].k_value)); m_input_n_val->GetTextCtrl()->SetValue(float_to_string_with_precision(m_pa_profile_items[cali_select_idx].n_coef)); } + else { + m_input_k_val->GetTextCtrl()->SetValue(float_to_string_with_precision(m_pa_profile_items[0].k_value)); + m_input_n_val->GetTextCtrl()->SetValue(float_to_string_with_precision(m_pa_profile_items[0].n_coef)); + } } else { if (!ams_filament_id.empty()) { @@ -1126,10 +1188,12 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) m_input_k_val->Disable(); } } + + m_comboBox_filament->SetClientData(new int(0)); } -void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect) -{ +void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect) +{ m_input_nozzle_max->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(20))); m_input_nozzle_min->GetTextCtrl()->SetSize(wxSize(-1, FromDIP(20))); //m_clr_picker->msw_rescale(); @@ -1142,7 +1206,7 @@ void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect) m_button_confirm->SetCornerRadius(FromDIP(12)); m_button_close->SetMinSize(AMS_MATERIALS_SETTING_BUTTON_SIZE); m_button_close->SetCornerRadius(FromDIP(12)); - this->Refresh(); + this->Refresh(); } ColorPicker::ColorPicker(wxWindow* parent, wxWindowID id, const wxPoint& pos /*= wxDefaultPosition*/, const wxSize& size /*= wxDefaultSize*/) diff --git a/src/slic3r/GUI/AMSSetting.cpp b/src/slic3r/GUI/AMSSetting.cpp index 6325f41..8034d5e 100644 --- a/src/slic3r/GUI/AMSSetting.cpp +++ b/src/slic3r/GUI/AMSSetting.cpp @@ -56,7 +56,7 @@ void AMSSetting::create() // 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.") + _L("The AMS will automatically read the filament information when inserting a new QIDI Tech filament. This takes about 20 seconds.") ); m_tip_Insert_material_line1->SetFont(::Label::Body_13); m_tip_Insert_material_line1->SetForegroundColour(AMS_SETTING_GREY700); diff --git a/src/slic3r/GUI/AVVideoDecoder.cpp b/src/slic3r/GUI/AVVideoDecoder.cpp new file mode 100644 index 0000000..bc2fab5 --- /dev/null +++ b/src/slic3r/GUI/AVVideoDecoder.cpp @@ -0,0 +1,152 @@ +#include "AVVideoDecoder.hpp" + +#include + +extern "C" +{ + #include + #include +} + +AVVideoDecoder::AVVideoDecoder() +{ + codec_ctx_ = avcodec_alloc_context3(nullptr); +} + +AVVideoDecoder::~AVVideoDecoder() +{ + if (sws_ctx_) + sws_freeContext(sws_ctx_); + if (frame_) + av_frame_free(&frame_); + if (codec_ctx_) + avcodec_free_context(&codec_ctx_); +} + +int AVVideoDecoder::open(QIDI_StreamInfo const &info) +{ + auto codec_id = info.sub_type == AVC1 ? AV_CODEC_ID_H264 : AV_CODEC_ID_MJPEG; + auto codec = avcodec_find_decoder(codec_id); + if (codec == nullptr) { + fprintf(stderr, "AVVideoDecoder: unsupported codec!\n"); + return -1; // Codec not found + } + /* open the coderc */ + if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) { + fprintf(stderr, "AVVideoDecoder: could not open codec\n"); + return -1; + } + + // Allocate an AVFrame structure + frame_ = av_frame_alloc(); + if (frame_ == nullptr) + return -1; + + return 0; +} + +int AVVideoDecoder::decode(const QIDI_Sample &sample) +{ + auto pkt = av_packet_alloc(); + int ret = av_new_packet(pkt, sample.size); + if (ret == 0) + memcpy(pkt->data, sample.buffer, size_t(sample.size)); + got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0; + ret = avcodec_send_packet(codec_ctx_, pkt); + return ret; +} + +int AVVideoDecoder::flush() +{ + int ret = avcodec_send_packet(codec_ctx_, nullptr); + got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0; + return ret; +} + +void AVVideoDecoder::close() +{ +} + +bool AVVideoDecoder::toWxImage(wxImage &image, wxSize const &size2) +{ + if (!got_frame_) + return false; + + auto size1 = size2; + if (!size1.IsFullySpecified()) + size1 = {frame_->width, frame_->height }; + auto size = size1; + if (size.GetWidth() & 0x0f) { + size.SetWidth((size.GetWidth() & ~0x0f) + 0x10); + if (size.GetWidth() != width_) { + std::fill(bits_.begin(), bits_.end(), 0); + width_ = size.GetWidth(); + } + } + AVPixelFormat wxFmt = AV_PIX_FMT_RGB24; + sws_ctx_ = sws_getCachedContext(sws_ctx_, + frame_->width, frame_->height, AVPixelFormat(frame_->format), + size1.GetWidth(), size1.GetHeight(), wxFmt, + SWS_GAUSS, + nullptr, nullptr, nullptr); + if (sws_ctx_ == nullptr) + return false; + int length = size.GetWidth() * size.GetHeight() * 3; + if (bits_.size() < length) + bits_.resize(length); + uint8_t * datas[] = { bits_.data() }; + int strides[] = { size.GetWidth() * 3 }; + int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides); + if (result_h != size.GetHeight()) { + return false; + } + image = wxImage(size.GetWidth(), size.GetHeight(), bits_.data(), true); + if (!image.IsOk()) { + fprintf(stderr, "AVVideoDecoder: image not ok %dx%d\n", size.GetWidth(), size.GetHeight()); + return false; + } + return true; +} + +bool AVVideoDecoder::toWxBitmap(wxBitmap &bitmap, wxSize const &size2) +{ + if (!got_frame_) + return false; + + auto size1 = size2; + if (!size1.IsFullySpecified()) + size1 = {frame_->width, frame_->height }; + auto size = size1; + if (size.GetWidth() & 0x0f) { + size.SetWidth((size.GetWidth() & ~0x0f) + 0x10); + if (size.GetWidth() != width_) { + std::fill(bits_.begin(), bits_.end(), 0); + width_ = size.GetWidth(); + } + } + AVPixelFormat wxFmt = AV_PIX_FMT_RGB32; + sws_ctx_ = sws_getCachedContext(sws_ctx_, + frame_->width, frame_->height, AVPixelFormat(frame_->format), + size1.GetWidth(), size1.GetHeight(), wxFmt, + SWS_GAUSS, + nullptr, nullptr, nullptr); + if (sws_ctx_ == nullptr) + return false; + int length = size.GetWidth() * size.GetHeight() * 4; + if (bits_.size() < length) + bits_.resize(length); + uint8_t *datas[] = { bits_.data() }; + int strides[] = { size.GetWidth() * 4 }; + int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides); + if (result_h != size.GetHeight()) { + fprintf(stderr, "AVVideoDecoder: result_h %d %d\n", result_h, size.GetHeight()); + return false; + } + bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32); + assert(bitmap.IsOk()); + if (!bitmap.IsOk()) { + fprintf(stderr, "AVVideoDecoder: bitmap not ok %dx%d\n", size.GetWidth(), size.GetHeight()); + return false; + } + return true; +} diff --git a/src/slic3r/GUI/AVVideoDecoder.hpp b/src/slic3r/GUI/AVVideoDecoder.hpp new file mode 100644 index 0000000..98d279e --- /dev/null +++ b/src/slic3r/GUI/AVVideoDecoder.hpp @@ -0,0 +1,46 @@ +#ifndef AVVIDEODECODER_HPP +#define AVVIDEODECODER_HPP + +#include "Printer/QIDITunnel.h" + +extern "C" { + #include + #include +} +#include +#include +#include +#include + +class wxBitmap; + +class AVVideoDecoder +{ +public: + AVVideoDecoder(); + + ~AVVideoDecoder(); + +public: + int open(QIDI_StreamInfo const &info); + + int decode(QIDI_Sample const &sample); + + int flush(); + + void close(); + + bool toWxImage(wxImage &image, wxSize const &size); + + bool toWxBitmap(wxBitmap &bitmap, wxSize const & size); + +private: + AVCodecContext *codec_ctx_ = nullptr; + AVFrame * frame_ = nullptr; + SwsContext * sws_ctx_ = nullptr; + bool got_frame_ = false; + int width_ { 0 }; // scale result width + std::vector bits_; +}; + +#endif // AVVIDEODECODER_HPP diff --git a/src/slic3r/GUI/AmsMappingPopup.cpp b/src/slic3r/GUI/AmsMappingPopup.cpp index f8b6b60..61f712e 100644 --- a/src/slic3r/GUI/AmsMappingPopup.cpp +++ b/src/slic3r/GUI/AmsMappingPopup.cpp @@ -907,7 +907,7 @@ AmsHumidityTipPopup::AmsHumidityTipPopup(wxWindow* parent) close_img = ScalableBitmap(this, "hum_popup_close", 24); - m_staticText = new Label(this, _L("Current Cabin humidity")); + m_staticText = new Label(this, _L("Current AMS humidity")); m_staticText->SetFont(::Label::Head_24); humidity_level_list = new AmsHumidityLevelList(this); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 4ccb630..f66c2fe 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -295,7 +295,7 @@ wxPanel *BedShapePanel::init_texture_panel() load_btn->SetBackgroundColor(btn_bg_white); load_btn->SetBorderColor(btn_bd_white); load_btn->SetBackgroundColour(*wxWHITE); - load_btn->Enable(m_can_edit); + load_btn->Enable(true); wxSizer * load_sizer = new wxBoxSizer(wxHORIZONTAL); load_sizer->Add(load_btn, 1, wxEXPAND); diff --git a/src/slic3r/GUI/BindDialog.cpp b/src/slic3r/GUI/BindDialog.cpp index 2311f9e..a58cbaf 100644 --- a/src/slic3r/GUI/BindDialog.cpp +++ b/src/slic3r/GUI/BindDialog.cpp @@ -107,7 +107,7 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/) m_link_show_ping_code_wiki->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); m_link_show_ping_code_wiki->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { - m_ping_code_wiki = "https://wiki.qidilab.com/en/qidi-studio/manual/pin-code"; + m_ping_code_wiki = "https://wiki.qidi3d.com/en/qidi-studio/manual/pin-code"; wxLaunchDefaultBrowser(m_ping_code_wiki); }); @@ -482,7 +482,7 @@ PingCodeBindDialog::~PingCodeBindDialog() { m_link_Terms_title->Wrap(FromDIP(450)); m_link_Terms_title->SetForegroundColour(wxColour(0x4479FB)); // y96 m_link_Terms_title->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { - wxString txt = _L("Thank you for purchasing a QIDI Lab device.Before using your QIDI Lab device, please read the termsand conditions.By clicking to agree to use your QIDI Lab device, you agree to abide by the Privacy Policy and Terms of Use(collectively, the \"Terms\"). If you do not comply with or agree to the QIDI Lab Privacy Policy, please do not use QIDI Lab equipment and services."); + wxString txt = _L("Thank you for purchasing a QIDI Tech device.Before using your QIDI Tech device, please read the termsand conditions.By clicking to agree to use your QIDI Tech device, you agree to abide by the Privacy Policy and Terms of Use(collectively, the \"Terms\"). If you do not comply with or agree to the QIDI Tech Privacy Policy, please do not use QIDI Tech equipment and services."); ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Terms and Conditions"), ConfirmBeforeSendDialog::ButtonStyle::ONLY_CONFIRM); confirm_dlg.update_text(txt); confirm_dlg.CenterOnParent(); @@ -757,7 +757,7 @@ PingCodeBindDialog::~PingCodeBindDialog() { json j = json::parse(str.utf8_string()); if (j.contains("err_code")) { int error_code = j["err_code"].get(); - extra = wxGetApp().get_hms_query()->query_print_error_msg(error_code); + wxGetApp().get_hms_query()->query_print_error_msg(error_code, extra); } } catch (...) { diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index e4b1f15..36f1b7c 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -6,6 +6,7 @@ #include "GUI_Utils.hpp" #include +#include #ifdef __WXGTK2__ // Broken alpha workaround diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index c6e9931..77d93fb 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -3,10 +3,9 @@ #include "Camera.hpp" #include "GUI_App.hpp" -#if ENABLE_CAMERA_STATISTICS + #include "Mouse3DController.hpp" #include "Plater.hpp" -#endif // ENABLE_CAMERA_STATISTICS #include @@ -56,7 +55,7 @@ void Camera::select_next_type() void Camera::translate(const Vec3d& displacement) { if (!displacement.isApprox(Vec3d::Zero())) { m_view_matrix.translate(-displacement); - update_target(); + update_target(); } } @@ -107,6 +106,168 @@ void Camera::select_view(const std::string& direction) } } +//how to use +//BoundingBox bbox = mesh.aabb.transform(transform); +//return camera_->getFrustum().intersects(bbox); +void Camera::debug_frustum() +{ + ImGuiWrapper &imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Camera debug_frusm"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + Vec3f frustum_min = m_frustum.bbox.min.cast(); + Vec3f frustum_max = m_frustum.bbox.max.cast(); + Vec3f _0_normal = m_frustum.planes[0].getNormal().cast(); + Vec3f _0_corner = m_frustum.corners[0].cast(); + Vec3f _1_corner = m_frustum.corners[1].cast(); + + ImGui::InputFloat3("m_last_eye", m_last_eye.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("m_last_center", m_last_center.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("m_last_up", m_last_up.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("frustum_min", frustum_min.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("frustum_max", frustum_max.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + for (size_t i = 0; i < 8; i++) { + std::string name = "corner" + std::to_string(i); + ImGui::InputFloat3(name.c_str(), m_frustum.corners[i].data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + } + for (size_t i = 0; i < 6; i++) { + std::string name = "plane_normal" + std::to_string(i); + Vec3f normal = m_frustum.planes[i].getNormal(); + ImGui::InputFloat3(name.c_str(), normal.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + + name = "plane_center" + std::to_string(i); + Vec3f center = m_frustum.planes[i].getCenter(); + ImGui::InputFloat3(name.c_str(), center.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + } + imgui.end(); +} + +void Camera::update_frustum() +{ + Vec3f eye_ = get_position().cast(); + Vec3f center_ = get_target().cast(); + Vec3f up_ = get_dir_up().cast(); + float near_ = m_frustrum_zs.first; + float far_ = m_frustrum_zs.second; + float aspect_ = m_viewport[2] / (double)m_viewport[3]; + float fov_ = (float) Geometry::deg2rad(get_fov()); + float eps = 0.01; + if (m_last_eye.isApprox(eye_) && m_last_center.isApprox(center_) && m_last_up.isApprox(up_) + && abs(m_last_near - near_) < eps && abs(m_last_far - far_) < eps && + abs(m_last_aspect - aspect_) < eps && abs(m_last_fov - fov_) < eps && abs(m_last_zoom - m_zoom) < eps) { + return; + } + m_last_eye = eye_; + m_last_center = center_; + m_last_up = up_; + m_last_near = near_; + m_last_far = far_; + m_last_aspect = aspect_; + m_last_fov = fov_; + m_last_zoom = m_zoom; + Vec3f forward((center_ - eye_).normalized()); + Vec3f side((forward.cross(up_)).normalized()); + Vec3f up((side.cross(forward)).normalized()); + + float nearHeightHalf = near_ * std::tan(fov_ / 2.f); + float farHeightHalf = far_ * std::tan(fov_ / 2.f); + float nearWidthHalf = nearHeightHalf * aspect_; + float farWidthHalf = farHeightHalf * aspect_; + + // near plane + Vec3f nearCenter = eye_ + forward * near_; + Vec3f nearNormal = forward; + m_frustum.planes[0].set(nearNormal, nearCenter); + + // far plane + Vec3f farCenter = eye_ + forward * far_; + Vec3f farNormal = -forward; + m_frustum.planes[1].set(farNormal, farCenter); + if (m_type == EType::Ortho) { + double right = 1.0 / m_projection_matrix.matrix()(0, 0) - 0.5 * m_projection_matrix.matrix()(0, 0) * m_projection_matrix.matrix()(0, 3); + double top = 1.0 / m_projection_matrix.matrix()(1, 1) - 0.5 * m_projection_matrix.matrix()(1, 1) * m_projection_matrix.matrix()(1, 3); + auto dz = (far_ - near_) / 2.0; + Vec3f center = eye_ + forward * (far_ + near_) /2.0f; + m_frustum.bbox.reset(); + Vec3f corner = farCenter + up * top + side * right; + m_frustum.bbox.merge(corner.cast()); + + corner = farCenter - up * top + side * right; + m_frustum.bbox.merge(corner.cast()); + + corner = farCenter + up * top - side * right; + m_frustum.bbox.merge(corner.cast()); + + corner = farCenter - up * top - side * right; + m_frustum.bbox.merge(corner.cast()); + //nearCenter + corner = nearCenter + up * top + side * right; + m_frustum.bbox.merge(corner.cast()); + + corner = nearCenter - up * top + side * right; + m_frustum.bbox.merge(corner.cast()); + + corner = nearCenter + up * top - side * right; + m_frustum.bbox.merge(corner.cast()); + + corner = nearCenter - up * top - side * right; + m_frustum.bbox.merge(corner.cast()); + return; + } + // top plane + Vec3f topCenter = nearCenter + up * nearHeightHalf; + Vec3f topNormal = (topCenter - eye_).normalized().cross(side); + m_frustum.planes[2].set(topNormal, topCenter); + + // bottom plane + Vec3f bottomCenter = nearCenter - up * nearHeightHalf; + Vec3f bottomNormal = side.cross((bottomCenter - eye_).normalized()); + m_frustum.planes[3].set(bottomNormal, bottomCenter); + + // left plane + Vec3f leftCenter = nearCenter - side * nearWidthHalf; + Vec3f leftNormal = (leftCenter - eye_).normalized().cross(up); + m_frustum.planes[4].set(leftNormal, leftCenter); + + // right plane + Vec3f rightCenter = nearCenter + side * nearWidthHalf; + Vec3f rightNormal = up.cross((rightCenter - eye_).normalized()); + m_frustum.planes[5].set(rightNormal, rightCenter); + + //// 8 corners + Vec3f nearTopLeft = nearCenter + up * nearHeightHalf - side * nearWidthHalf; + Vec3f nearTopRight = nearCenter + up * nearHeightHalf + side * nearWidthHalf; + Vec3f nearBottomLeft = nearCenter - up * nearHeightHalf - side * nearWidthHalf; + Vec3f nearBottomRight = nearCenter - up * nearHeightHalf + side * nearWidthHalf; + + Vec3f farTopLeft = farCenter + up * farHeightHalf - side * farWidthHalf; + Vec3f farTopRight = farCenter + up * farHeightHalf + side * farWidthHalf; + Vec3f farBottomLeft = farCenter - up * farHeightHalf - side * farWidthHalf; + Vec3f farBottomRight = farCenter - up * farHeightHalf + side * farWidthHalf; + + m_frustum.corners[0] = nearTopLeft; + m_frustum.corners[1] = nearTopRight; + m_frustum.corners[2] = nearBottomLeft; + m_frustum.corners[3] = nearBottomRight; + m_frustum.corners[4] = farTopLeft; + m_frustum.corners[5] = farTopRight; + m_frustum.corners[6] = farBottomLeft; + m_frustum.corners[7] = farBottomRight; + // bounding box + auto double_min = std::numeric_limits::min(); + auto double_max = std::numeric_limits::max(); + m_frustum.bbox.min = Vec3d(double_max, double_max, double_max); + m_frustum.bbox.max = Vec3d(double_min, double_min, double_min); + for (auto &corner : m_frustum.corners) { + m_frustum.bbox.min[0] = std::min((float)m_frustum.bbox.min.x(), corner.x()); + m_frustum.bbox.min[1] = std::min((float)m_frustum.bbox.min.y(), corner.y()); + m_frustum.bbox.min[2] = std::min((float)m_frustum.bbox.min.z(), corner.z()); + + m_frustum.bbox.max[0] = std::max((float)m_frustum.bbox.max.x(), corner.x()); + m_frustum.bbox.max[1] = std::max((float)m_frustum.bbox.max.y(), corner.y()); + m_frustum.bbox.max[2] = std::max((float)m_frustum.bbox.max.z(), corner.z()); + } +} + double Camera::get_fov() const { switch (m_type) @@ -219,8 +380,7 @@ void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor) } } -#if ENABLE_CAMERA_STATISTICS -void Camera::debug_render() const +void Camera::debug_render() { ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); @@ -274,7 +434,6 @@ void Camera::debug_render() const 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) { @@ -333,6 +492,19 @@ void Camera::rotate_local_with_target(const Vec3d& rotation_rad, Vec3d target) } } +void Camera::calc_horizontal_rotate_rad(float &rotation_rad) { + if (is_looking_front()) { + auto right = get_dir_right(); + auto temp_rotation_rad = acos(right.dot(Vec3d(1, 0, 0))); + auto value = Vec3d(1, 0, 0).cross(right); + if (value.z() > 0.01) { + temp_rotation_rad = -temp_rotation_rad; + } + rotation_rad = temp_rotation_rad; + } +} + + // 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) { @@ -351,7 +523,7 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo { std::pair ret; auto& [near_z, far_z] = ret; - + m_scene_box_radius = box.radius(); // box in eye space const BoundingBoxf3 eye_box = box.transformed(m_view_matrix); near_z = -eye_box.max(2); @@ -372,18 +544,15 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo if (near_z < FrustrumMinNearZ) { const double delta = FrustrumMinNearZ - near_z; set_distance(m_distance + delta); + m_last_scene_box_radius = m_scene_box_radius; near_z += delta; far_z += delta; + } else { + if (abs(m_last_scene_box_radius - m_scene_box_radius) > 1) { + m_last_scene_box_radius = m_scene_box_radius; + set_distance(DefaultDistance); + } } -// 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; } @@ -445,6 +614,16 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double 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(); + } +} + double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor) const { if (volumes.empty()) @@ -503,16 +682,6 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& c 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(); @@ -614,7 +783,7 @@ void Camera::update_target() { Vec3d temptarget = get_position() + m_distance * get_dir_forward(); if (!(temptarget-m_target).isApprox(Vec3d::Zero())){ m_target = temptarget; - } + } } } // GUI diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 5b57ebc..738dec1 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -2,6 +2,7 @@ #define slic3r_Camera_hpp_ #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Frustum.hpp" #include "3DScene.hpp" #include @@ -55,6 +56,11 @@ private: std::pair m_frustrum_zs; BoundingBoxf3 m_scene_box; + float m_scene_box_radius{0}; + float m_last_scene_box_radius{0}; + Frustum m_frustum; + Vec3f m_last_eye, m_last_center, m_last_up; + float m_last_near, m_last_far, m_last_aspect, m_last_fov,m_last_zoom; public: Camera() { set_default_orientation(); } @@ -69,7 +75,7 @@ public: 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() { + const Vec3d& get_target() { update_target(); return m_target; } void set_target(const Vec3d& target); @@ -100,9 +106,9 @@ public: 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); } - + const Frustum & getFrustum() const { return m_frustum; } + void update_frustum(); double get_near_z() const { return m_frustrum_zs.first; } double get_far_z() const { return m_frustrum_zs.second; } const std::pair& get_z_range() const { return m_frustrum_zs; } @@ -117,10 +123,8 @@ public: 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 + void debug_frustum(); + void debug_render(); // translate the camera in world space void translate_world(const Vec3d& displacement) { set_target(m_target + displacement); } @@ -128,7 +132,7 @@ public: // 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); - + void calc_horizontal_rotate_rad(float &rotation_rad); // 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 diff --git a/src/slic3r/GUI/CameraPopup.cpp b/src/slic3r/GUI/CameraPopup.cpp index 75e1935..ce3ee48 100644 --- a/src/slic3r/GUI/CameraPopup.cpp +++ b/src/slic3r/GUI/CameraPopup.cpp @@ -99,7 +99,7 @@ CameraPopup::CameraPopup(wxWindow *parent) 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 url = wxString::Format(L"https://wiki.qidi3d.com/%s/software/qidi-studio/virtual-camera", L"en"); auto text = _L("Show \"Live Video\" guide page."); wxBoxSizer* link_sizer = new wxBoxSizer(wxVERTICAL); @@ -415,7 +415,7 @@ void CameraPopup::OnLeftUp(wxMouseEvent &event) //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"); + auto url = wxString::Format(L"https://wiki.qidi3d.com/%s/software/qidi-studio/virtual-camera", L"en"); wxLaunchDefaultBrowser(url); } } diff --git a/src/slic3r/GUI/CameraUtils.cpp b/src/slic3r/GUI/CameraUtils.cpp index 99d022e..0e0dba1 100644 --- a/src/slic3r/GUI/CameraUtils.cpp +++ b/src/slic3r/GUI/CameraUtils.cpp @@ -38,7 +38,7 @@ Points CameraUtils::project(const Camera & camera, return result; } -Point CameraUtils::project(const Camera &camera, const Vec3d &point) +Slic3r::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(); diff --git a/src/slic3r/GUI/CameraUtils.hpp b/src/slic3r/GUI/CameraUtils.hpp index c3e938e..6e953bf 100644 --- a/src/slic3r/GUI/CameraUtils.hpp +++ b/src/slic3r/GUI/CameraUtils.hpp @@ -25,7 +25,7 @@ public: /// projected points by camera into coordinate of camera. /// x(from left to right), y(from top to bottom) static Points project(const Camera& camera, const std::vector &points); - static Point project(const Camera& camera, const Vec3d &point); + static Slic3r::Point project(const Camera& camera, const Vec3d &point); /// /// Create hull around GLVolume in 2d space of camera diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d4b9686..6362b43 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -148,6 +148,7 @@ void ConfigManipulation::check_chamber_temperature(DynamicPrintConfig* config) {"PLA-CF",45}, {"PVA",45}, {"TPU",50}, + {"TPU-AMS",50}, {"PETG",55}, {"PCTG",55}, {"PETG-CF",55} @@ -206,37 +207,6 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con is_msg_dlg_already_exist = false; } - //QDS: limit scarf seam start height range - bool apply_scarf_seam = config->opt_enum("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("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("top_one_wall_type") == TopOneWallType::None; toggle_line("top_area_threshold", !top_one_wall_apply); @@ -632,7 +602,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_brim = (config->opt_enum("brim_type") != btNoBrim); toggle_field("brim_object_gap", have_brim); - bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim; + bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim && config->opt_enum("brim_type") != btBrimEars; 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); @@ -658,12 +628,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool support_is_tree = config->opt_bool("enable_support") && is_tree(support_type); //1.9.5 - for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter"}) + for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter", "tree_support_branch_diameter_angle"}) toggle_field(el, support_is_tree); // hide tree support settings when normal is selected //1.9.5 - for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter", "max_bridge_length"}) +for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter", "tree_support_branch_diameter_angle", "max_bridge_length"}) toggle_line(el, support_is_tree); toggle_line("support_critical_regions_only", is_auto(support_type) && support_is_tree); @@ -687,17 +657,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("support_filament", have_support_material || have_skirt); toggle_line("raft_contact_distance", have_raft && !have_support_soluble); - //1.9.5 - for (auto el : { "raft_first_layer_density"}) - toggle_line(el, have_raft); bool has_ironing = (config->opt_enum("ironing_type") != IroningType::NoIroning); for (auto el : { - "ironing_pattern","ironing_speed", "ironing_flow", "ironing_spacing", "ironing_direction"}) + "ironing_pattern","ironing_speed", "ironing_flow", "ironing_spacing", "ironing_direction", "ironing_inset"}) toggle_line(el, has_ironing); // bool have_sequential_printing = (config->opt_enum("print_sequence") == PrintSequence::ByObject); - // for (auto el : { "extruder_clearance_radius", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" }) + // for (auto el : { "extruder_clearance_dist_to_rod", "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"); @@ -747,17 +714,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co } toggle_line("exclude_object", gcflavor == gcfKlipper); - toggle_field("seam_slope_type", !has_spiral_vase); - bool has_seam_slope = !has_spiral_vase && config->opt_enum("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); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4cb9ff2..f137553 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2700,7 +2700,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) //QDS: add QDT as default const auto qdt_it = p->bundles.find("QDT"); - wxCHECK_RET(qdt_it != p->bundles.cend(), "Vendor QIDI Lab not found"); + wxCHECK_RET(qdt_it != p->bundles.cend(), "Vendor QIDI Tech not found"); const VendorProfile * vendor_qdt = qdt_it->second.vendor_profile; p->only_sla_mode = false; diff --git a/src/slic3r/GUI/CreatePresetsDialog.cpp b/src/slic3r/GUI/CreatePresetsDialog.cpp index f82d323..16939ed 100644 --- a/src/slic3r/GUI/CreatePresetsDialog.cpp +++ b/src/slic3r/GUI/CreatePresetsDialog.cpp @@ -42,7 +42,7 @@ static const std::vector filament_vendors = {"Polymaker", "OVERTURE static const std::vector filament_types = {"PLA", "PLA+", "PLA Tough", "PETG", "ABS", "ASA", "FLEX", "HIPS", "PA", "PACF", "NYLON", "PVA", "PC", "PCABS", "PCTG", "PCCF", "PP", "PEI", "PET", "PETG", - "PETGCF", "PTBA", "PTBA90A", "PEEK", "TPU93A", "TPU75D", "TPU", "TPU92A", "TPU98A", "Misc", + "PETGCF", "PTBA", "PTBA90A", "PEEK", "TPU93A", "TPU75D", "TPU","TPU-AMS", "TPU92A", "TPU98A", "Misc", "TPE", "GLAZE", "Nylon", "CPE", "METAL", "ABST", "Carbon Fiber"}; static const std::vector printer_vendors = {"Anycubic", "Artillery", "BIBO", "BIQU", "Creality ENDER", "Creality CR", "Creality SERMOON", @@ -303,17 +303,29 @@ static wxBoxSizer *create_preset_tree(wxWindow *parent, std::pairtype == Preset::Type::TYPE_FILAMENT || preset->type == Preset::Type::TYPE_PRINTER); + if (preset->type != Preset::Type::TYPE_FILAMENT && preset->type != Preset::Type::TYPE_PRINTER) return ""; + + if (preset->type == Preset::Type::TYPE_FILAMENT) { + auto vender = preset->config.option("filament_vendor"); + if (vender && vender->values.size()) return vender->values[0]; } + + if (preset->type == Preset::Type::TYPE_PRINTER) { + auto vender = preset->config.option("printer_model"); + if (vender && vender->values.size()) return vender->values[0]; + } + + assert(false); + auto preset_name = preset->name; + int index = preset_name.find_first_of(' '); + if (std::string::npos == index) + return preset_name; + else + return preset_name.substr(0, index); } static wxBoxSizer *create_select_filament_preset_checkbox(wxWindow * parent, @@ -1497,7 +1509,7 @@ wxBoxSizer *CreatePrinterPresetDialog::create_step_switch_item() { wxBoxSizer *step_switch_sizer = new wxBoxSizer(wxVERTICAL); - std::string wiki_url = "https://wiki.qidilab.com/en/software/qidi-studio/3rd-party-printer-profile"; + std::string wiki_url = "https://wiki.qidi3d.com/en/software/qidi-studio/3rd-party-printer-profile"; wxHyperlinkCtrl *m_download_hyperlink = new wxHyperlinkCtrl(this, wxID_ANY, _L("wiki"), wiki_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE); step_switch_sizer->Add(m_download_hyperlink, 0, wxRIGHT | wxALIGN_RIGHT, FromDIP(5)); @@ -3433,7 +3445,7 @@ bool ExportConfigsDialog::preset_is_not_compatible_qdt_printer(Preset *preset) PresetBundle * preset_bundle = wxGetApp().preset_bundle; vector printers; get_filament_compatible_printer(preset, printers); - if (printers.empty()) return true; + if (printers.empty()) return false; // no compatable printer preset must be compatable qdt printer Preset *printer_preset = preset_bundle->printers.find_preset(printers[0], false); if (!printer_preset) return true; if (!printer_preset->is_qdt_vendor_preset(preset_bundle)) return true; @@ -3617,6 +3629,17 @@ wxBoxSizer *ExportConfigsDialog::create_radio_item(wxString title, wxWindow *par return horizontal_sizer; } +std::string ExportConfigsDialog::create_structure_file(json & structure) +{ + if (structure.is_null()) return ""; + + ostringstream oss; + oss << std::setw(4) << structure << std::endl; + std::string bundle_structure = oss.str(); + + return bundle_structure; +} + mz_bool ExportConfigsDialog::initial_zip_archive(mz_zip_archive &zip_archive, const std::string &file_path) { mz_zip_zero_struct(&zip_archive); @@ -3719,7 +3742,8 @@ void ExportConfigsDialog::select_curr_radiobox(std::vector filament_preset : filament_name_to_preset.second) { - if (!preset_is_not_compatible_qdt_printer(filament_preset.second)) + //w37 + //if (!preset_is_not_compatible_qdt_printer(filament_preset.second)) all_preset_is_compatible_third_printer = false; } if (all_preset_is_compatible_third_printer) continue; @@ -3866,7 +3890,7 @@ ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_preset_bundle_to_fi bundle_structure["filament_config"] = filament_configs; bundle_structure["process_config"] = process_configs; - std::string bundle_structure_str = bundle_structure.dump(); + std::string bundle_structure_str = create_structure_file(bundle_structure); status = mz_zip_writer_add_mem(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, bundle_structure_str.data(), bundle_structure_str.size(), MZ_DEFAULT_COMPRESSION); if (MZ_FALSE == status) { BOOST_LOG_TRIVIAL(info) << " Failed to add file: " << BUNDLE_STRUCTURE_JSON_NAME; @@ -3921,32 +3945,32 @@ ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_filament_bundle_to_ continue; } std::set> vendor_to_filament_name; - for (std::pair printer_name_to_preset : iter->second) { - std::string printer_vendor = printer_name_to_preset.first; - if (printer_vendor.empty()) continue; - Preset * filament_preset = printer_name_to_preset.second; - // if (preset_is_not_compatible_qdt_printer(filament_preset)) continue; - if (vendor_to_filament_name.find(std::make_pair(printer_vendor, filament_preset->name)) != vendor_to_filament_name.end()) continue; - vendor_to_filament_name.insert(std::make_pair(printer_vendor, filament_preset->name)); + for (std::pair filament_vendor_to_preset : iter->second) { + std::string filament_vendor = filament_vendor_to_preset.first; + if (filament_vendor.empty()) continue; + Preset *filament_preset = filament_vendor_to_preset.second; + //if (preset_is_not_compatible_qdt_printer(filament_preset)) continue; + if (vendor_to_filament_name.find(std::make_pair(filament_vendor, filament_preset->name)) != vendor_to_filament_name.end()) continue; + vendor_to_filament_name.insert(std::make_pair(filament_vendor, filament_preset->name)); std::string preset_path = boost::filesystem::path(filament_preset->file).make_preferred().string(); if (preset_path.empty()) { BOOST_LOG_TRIVIAL(info) << "Export printer preset: " << filament_preset->name << " skip because of the preset file path is empty."; continue; } // Add a file to the ZIP file - std::string file_name = printer_vendor + "/" + filament_preset->name + ".json"; - status = mz_zip_writer_add_file(&zip_archive, file_name.c_str(), encode_path(preset_path.c_str()).c_str(), NULL, 0, MZ_DEFAULT_COMPRESSION); + std::string file_name = filament_vendor + "/" + filament_preset->name + ".json"; + status = mz_zip_writer_add_file(&zip_archive, file_name.c_str(), encode_path(preset_path.c_str()).c_str(), NULL, 0, MZ_DEFAULT_COMPRESSION); // status = mz_zip_writer_add_mem(&zip_archive, ("printer/" + printer_preset->name + ".json").c_str(), json_contents, strlen(json_contents), MZ_DEFAULT_COMPRESSION); if (MZ_FALSE == status) { BOOST_LOG_TRIVIAL(info) << filament_preset->name << " Failed to add file to ZIP archive"; mz_zip_writer_end(&zip_archive); return ExportCase::ADD_FILE_FAIL; } - std::unordered_map::iterator iter = vendor_structure.find(printer_vendor); + std::unordered_map::iterator iter = vendor_structure.find(filament_vendor); if (vendor_structure.end() == iter) { json j = json::array(); j.push_back(file_name); - vendor_structure[printer_vendor] = j; + vendor_structure[filament_vendor] = j; } else { iter->second.push_back(file_name); } @@ -3955,13 +3979,13 @@ ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_filament_bundle_to_ for (const std::pair& vendor_name_to_json : vendor_structure) { json j; - std::string printer_vendor = vendor_name_to_json.first; - j["vendor"] = printer_vendor; + std::string filament_vendor = vendor_name_to_json.first; + j["vendor"] = filament_vendor; j["filament_path"] = vendor_name_to_json.second; - bundle_structure["printer_vendor"].push_back(j); + bundle_structure["filament_vendor"].push_back(j); } - std::string bundle_structure_str = bundle_structure.dump(); + std::string bundle_structure_str = create_structure_file(bundle_structure); status = mz_zip_writer_add_mem(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, bundle_structure_str.data(), bundle_structure_str.size(), MZ_DEFAULT_COMPRESSION); if (MZ_FALSE == status) { BOOST_LOG_TRIVIAL(info) << " Failed to add file: " << BUNDLE_STRUCTURE_JSON_NAME; @@ -4032,7 +4056,8 @@ ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_filament_preset_to_ } for (std::pair printer_name_preset : iter->second) { Preset * filament_preset = printer_name_preset.second; - if (preset_is_not_compatible_qdt_printer(filament_preset)) continue; + //w37 + //if (preset_is_not_compatible_qdt_printer(filament_preset)) continue; if (filament_presets.find(filament_preset->name) != filament_presets.end()) continue; filament_presets.insert(filament_preset->name); std::string preset_path = boost::filesystem::path(filament_preset->file).make_preferred().string(); @@ -4267,7 +4292,7 @@ void ExportConfigsDialog::data_init() std::string filament_preset_name = base_filament_preset->name; std::string machine_name = get_machine_name(filament_preset_name); - m_filament_name_to_presets[get_filament_name(filament_preset_name)].push_back(std::make_pair(get_vendor_name(machine_name), new_filament_preset)); + m_filament_name_to_presets[get_filament_name(filament_preset_name)].push_back(std::make_pair(get_vendor_name(new_filament_preset), new_filament_preset)); } } @@ -5003,7 +5028,7 @@ wxPanel *PresetTree::get_child_item(wxPanel *parent, std::shared_ptr pre bool base_id_error = false; if (preset->inherits() == "" && preset->base_id != "") base_id_error = true; if (base_id_error) { - std::string wiki_url = "https://wiki.qidilab.com/en/software/qidi-studio/custom-filament-issue"; + std::string wiki_url = "https://wiki.qidi3d.com/en/software/qidi-studio/custom-filament-issue"; wxHyperlinkCtrl *m_download_hyperlink = new wxHyperlinkCtrl(panel, wxID_ANY, _L("[Delete Required]"), wiki_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE); m_download_hyperlink->SetFont(Label::Body_10); sizer->Add(m_download_hyperlink, 0, wxEXPAND | wxALL, 5); diff --git a/src/slic3r/GUI/CreatePresetsDialog.hpp b/src/slic3r/GUI/CreatePresetsDialog.hpp index 59db67c..5e3619a 100644 --- a/src/slic3r/GUI/CreatePresetsDialog.hpp +++ b/src/slic3r/GUI/CreatePresetsDialog.hpp @@ -12,6 +12,7 @@ #include "Widgets/ComboBox.hpp" #include "miniz.h" #include "ParamsDialog.hpp" +#include "json_diff.hpp" namespace Slic3r { namespace GUI { @@ -271,6 +272,7 @@ private: wxBoxSizer *create_button_item(wxWindow *parent); wxBoxSizer *create_select_printer(wxWindow *parent); wxBoxSizer *create_radio_item(wxString title, wxWindow *parent, wxString tooltip, std::vector> &radiobox_list); + std::string create_structure_file(json &structure); 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> &config_paths); diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index fa2be18..0341e3b 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -274,6 +274,8 @@ std::string AmsTray::get_display_filament_type() return "Sup.PLA"; else if (type == "PA-S") return "Sup.PA"; + else if (type == "ABS-S") + return "Sup.ABS"; else return type; return type; @@ -285,6 +287,8 @@ std::string AmsTray::get_filament_type() return "PLA-S"; } else if (type == "Sup.PA") { return "PA-S"; + } else if (type == "Sup.ABS") { + return "ABS-S"; } else if (type == "Support W") { return "PLA-S"; } else if (type == "Support G") { @@ -917,7 +921,6 @@ int MachineObject::ams_filament_mapping(std::vector filaments, std val.tray_id = tray->second.id; wxColour c = wxColour(filaments[i].color); wxColour tray_c = AmsTray::decode_color(tray->second.color); - //1.9.5 val.distance = GUI::calc_color_distance(c, tray_c); if (filaments[i].type != tray->second.type) { val.distance = 999999; @@ -1435,6 +1438,10 @@ void MachineObject::parse_status(int flag) } sdcard_state = MachineObject::SdcardState((flag >> 8) & 0x11); + + is_support_agora = ((flag >> 30) & 0x1) != 0; + if (is_support_agora) + is_support_tunnel_mqtt = false; } PrintingSpeedLevel MachineObject::_parse_printing_speed_lvl(int lvl) @@ -1887,7 +1894,8 @@ int MachineObject::command_ams_calibrate(int ams_id) int MachineObject::command_ams_filament_settings(int ams_id, int tray_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max) { BOOST_LOG_TRIVIAL(info) << "command_ams_filament_settings, ams_id = " << ams_id << ", tray_id = " << tray_id << ", tray_color = " << tray_color - << ", tray_type = " << tray_type << ", setting_id = " << setting_id << ", temp_min: = " << nozzle_temp_min << ", temp_max: = " << nozzle_temp_max; + << ", tray_type = " << tray_type << ", filament_id = " << filament_id + << ", setting_id = " << setting_id << ", temp_min: = " << nozzle_temp_min << ", temp_max: = " << nozzle_temp_max; json j; j["print"]["command"] = "ams_filament_setting"; j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); @@ -2960,7 +2968,7 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) } if (!key_field_only) { - if (!DeviceManager::EnableMultiMachine) { + if (!DeviceManager::EnableMultiMachine && !is_support_agora) { if (jj.contains("support_tunnel_mqtt")) { if (jj["support_tunnel_mqtt"].is_boolean()) { is_support_tunnel_mqtt = jj["support_tunnel_mqtt"].get(); @@ -3221,7 +3229,12 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) mc_print_line_number = atoi(jj["mc_print_line_number"].get().c_str()); } } - //1.9.5 + if (!key_field_only) { + if (jj.contains("flag3")) { + int flag3 = jj["flag3"].get(); + is_support_filament_setting_inprinting = get_flag_bits(flag3, 3); + } + } if (!key_field_only) { if (jj.contains("net")) { if (jj["net"].contains("conf")) { @@ -3750,11 +3763,18 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) if (ipcam.contains("liveview")) { char const *local_protos[] = {"none", "disabled", "local", "rtsps", "rtsp"}; liveview_local = enum_index_of(ipcam["liveview"].value("local", "none").c_str(), local_protos, 5, LiveviewLocal::LVL_None); - liveview_remote = ipcam["liveview"].value("remote", "disabled") == "enabled"; + char const *remote_protos[] = {"none", "tutk", "agora", "tutk_agaro"}; + liveview_remote = enum_index_of(ipcam["liveview"].value("remote", "none").c_str(), remote_protos, 4, LiveviewRemote::LVR_None); + if (is_support_agora) + liveview_remote = liveview_remote == LVR_None ? LVR_Agora : liveview_remote == LVR_Tutk ? LVR_TutkAgora : liveview_remote; } if (ipcam.contains("file")) { - file_local = ipcam["file"].value("local", "disabled") == "enabled"; - file_remote = ipcam["file"].value("remote", "disabled") == "enabled"; + char const *local_protos[] = {"none", "local"}; + file_local = enum_index_of(ipcam["file"].value("local", "none").c_str(), local_protos, 2, FileLocal::FL_None); + char const *remote_protos[] = {"none", "tutk", "agora", "tutk_agaro"}; + file_remote = enum_index_of(ipcam["file"].value("remote", "none").c_str(), remote_protos, 4, FileRemote::FR_None); + if (is_support_agora) + file_remote = file_remote == FR_None ? FR_Agora : file_remote == FR_Tutk ? FR_TutkAgora : file_remote; file_model_download = ipcam["file"].value("model_download", "disabled") == "enabled"; } virtual_camera = ipcam.value("virtual_camera", "disabled") == "enabled"; @@ -3867,10 +3887,13 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) ; } PresetBundle *preset_bundle = Slic3r::GUI::wxGetApp().preset_bundle; - std::map> filament_list = preset_bundle->filaments.get_filament_presets(); std::ostringstream stream; stream << std::fixed << std::setprecision(1) << nozzle_diameter; std::string nozzle_diameter_str = stream.str(); + if (m_printer_preset_name.find(nozzle_diameter_str + " nozzle") == std::string::npos) + update_printer_preset_name(nozzle_diameter_str); + update_filament_list(); + std::set need_checked_filament_id; if (jj.contains("ams")) { if (jj["ams"].contains("ams")) { long int last_ams_exist_bits = ams_exist_bits; @@ -4032,15 +4055,24 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) } else { curr_tray->type = type; } - if (filament_list.find(curr_tray->setting_id) == filament_list.end()) { - wxColour color = *wxWHITE; - char col_buf[10]; - sprintf(col_buf, "%02X%02X%02XFF", (int) color.Red(), (int) color.Green(), (int) color.Blue()); - try { - this->command_ams_filament_settings(std::stoi(ams_id), std::stoi(tray_id), "", "", std::string(col_buf), "", 0, 0); - continue; - } catch (...) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " stoi error and ams_id: " << ams_id << " tray_id" << tray_id; + // settings_id is not exist in filament_list + if (curr_tray->setting_id.size() == 8 && curr_tray->setting_id[0] == 'P' && + m_filament_list.find(curr_tray->setting_id) == m_filament_list.end()) { + if (m_checked_filament.find(curr_tray->setting_id) == m_checked_filament.end()) { + need_checked_filament_id.insert(curr_tray->setting_id); + wxColour color = *wxWHITE; + char col_buf[10]; + sprintf(col_buf, "%02X%02X%02XFF", (int) color.Red(), (int) color.Green(), (int) color.Blue()); + try { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ + << " ams settings_id is not exist in filament_list and reset, ams_id: " << ams_id + << " tray_id" << tray_id << "filament_id: " << curr_tray->setting_id; + this->command_ams_filament_settings(std::stoi(ams_id), std::stoi(tray_id), "", "", std::string(col_buf), "", 0, 0); + continue; + } catch (...) { + BOOST_LOG_TRIVIAL(info) + << __FUNCTION__ << " " << __LINE__ << " stoi error and ams_id: " << ams_id << " tray_id" << tray_id; + } } } } else { @@ -4090,20 +4122,27 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) curr_tray->nozzle_temp_min = (*tray_it)["nozzle_temp_min"].get(); else curr_tray->nozzle_temp_min = ""; - if (curr_tray->nozzle_temp_min != "" && curr_tray->nozzle_temp_max != "") { - try { - std::string preset_setting_id; - bool is_equation = preset_bundle->check_filament_temp_equation_by_printer_type_and_nozzle_for_mas_tray( - MachineObject::get_preset_printer_model_name(this->printer_type), nozzle_diameter_str, curr_tray->setting_id, - curr_tray->tag_uid, curr_tray->nozzle_temp_min, curr_tray->nozzle_temp_max, preset_setting_id); - if (!is_equation) { - command_ams_filament_settings(std::stoi(ams_id), std::stoi(tray_id), curr_tray->setting_id, preset_setting_id, - curr_tray->color, curr_tray->type, - std::stoi(curr_tray->nozzle_temp_min), - std::stoi(curr_tray->nozzle_temp_max)); + if (curr_tray->setting_id.size() == 8 && curr_tray->setting_id[0] == 'P' && curr_tray->nozzle_temp_min != "" && curr_tray->nozzle_temp_max != "") { + if (m_checked_filament.find(vt_tray.setting_id) == m_checked_filament.end()) { + need_checked_filament_id.insert(vt_tray.setting_id); + try { + std::string preset_setting_id; + bool is_equation = preset_bundle->check_filament_temp_equation_by_printer_type_and_nozzle_for_mas_tray( + MachineObject::get_preset_printer_model_name(this->printer_type), nozzle_diameter_str, curr_tray->setting_id, + curr_tray->tag_uid, curr_tray->nozzle_temp_min, curr_tray->nozzle_temp_max, preset_setting_id); + if (!is_equation) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ + << " ams filament is not match min max temp and reset, ams_id: " << ams_id + << " tray_id" << tray_id << "filament_id: " << curr_tray->setting_id; + command_ams_filament_settings(std::stoi(ams_id), std::stoi(tray_id), curr_tray->setting_id, preset_setting_id, + curr_tray->color, curr_tray->type, + std::stoi(curr_tray->nozzle_temp_min), + std::stoi(curr_tray->nozzle_temp_max)); + } + continue; + } catch (...) { + BOOST_LOG_TRIVIAL(info) << "check fail and curr_tray ams_id" << ams_id << " curr_tray tray_id"<contains("xcam_info")) @@ -4227,15 +4266,19 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) else { vt_tray.type = type; } - if (filament_list.find(vt_tray.setting_id) == filament_list.end()) { - wxColour color = *wxWHITE; - char col_buf[10]; - sprintf(col_buf, "%02X%02X%02XFF", (int) color.Red(), (int) color.Green(), (int) color.Blue()); - try { - BOOST_LOG_TRIVIAL(info) << "no filament_id in filament_list and reset vt_tray and the filament_id is: " << vt_tray.setting_id; - this->command_ams_filament_settings(255, std::stoi(vt_tray.id), "", "", std::string(col_buf), "", 0, 0); - } catch (...) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " stoi error and tray_id" << vt_tray.id; + if (vt_tray.setting_id.size() == 8 && vt_tray.setting_id[0] == 'P' && + m_filament_list.find(vt_tray.setting_id) == m_filament_list.end()) { + if (m_checked_filament.find(vt_tray.setting_id) == m_checked_filament.end()) { + need_checked_filament_id.insert(vt_tray.setting_id); + wxColour color = *wxWHITE; + char col_buf[10]; + sprintf(col_buf, "%02X%02X%02XFF", (int) color.Red(), (int) color.Green(), (int) color.Blue()); + try { + BOOST_LOG_TRIVIAL(info) << "vt_tray.setting_id is not exist in filament_list and reset vt_tray and the filament_id is: " << vt_tray.setting_id; + this->command_ams_filament_settings(255, std::stoi(vt_tray.id), "", "", std::string(col_buf), "", 0, 0); + } catch (...) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " stoi error and tray_id" << vt_tray.id; + } } } } @@ -4285,21 +4328,23 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) vt_tray.nozzle_temp_min = jj["vt_tray"]["nozzle_temp_min"].get(); else vt_tray.nozzle_temp_min = ""; - if (vt_tray.nozzle_temp_min != "" && vt_tray.nozzle_temp_max != "") { - try { - std::string preset_setting_id; - bool is_equation = preset_bundle->check_filament_temp_equation_by_printer_type_and_nozzle_for_mas_tray( - MachineObject::get_preset_printer_model_name(this->printer_type), nozzle_diameter_str, vt_tray.setting_id, vt_tray.tag_uid, - vt_tray.nozzle_temp_min, vt_tray.nozzle_temp_max, preset_setting_id); - if (!is_equation) { - command_ams_filament_settings(255, std::stoi(vt_tray.id), vt_tray.setting_id, preset_setting_id, vt_tray.color, vt_tray.type, - std::stoi(vt_tray.nozzle_temp_min), std::stoi(vt_tray.nozzle_temp_max)); + if (vt_tray.setting_id.size() == 8 && vt_tray.setting_id[0] == 'P' && vt_tray.nozzle_temp_min != "" && vt_tray.nozzle_temp_max != "") { + if (m_checked_filament.find(vt_tray.setting_id) == m_checked_filament.end()) { + need_checked_filament_id.insert(vt_tray.setting_id); + try { + std::string preset_setting_id; + bool is_equation = preset_bundle->check_filament_temp_equation_by_printer_type_and_nozzle_for_mas_tray( + MachineObject::get_preset_printer_model_name(this->printer_type), nozzle_diameter_str, vt_tray.setting_id, vt_tray.tag_uid, + vt_tray.nozzle_temp_min, vt_tray.nozzle_temp_max, preset_setting_id); + if (!is_equation) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " vt_tray filament is not match min max temp and reset, filament_id: " << vt_tray.setting_id; + command_ams_filament_settings(255, std::stoi(vt_tray.id), vt_tray.setting_id, preset_setting_id, vt_tray.color, vt_tray.type, + std::stoi(vt_tray.nozzle_temp_min), std::stoi(vt_tray.nozzle_temp_max)); + } + } catch (...) { + BOOST_LOG_TRIVIAL(info) << "check fail and vt_tray.id" << vt_tray.id; } } - catch(...) { - BOOST_LOG_TRIVIAL(info) << "check fail and vt_tray.id" << vt_tray.id; - } - } if (jj["vt_tray"].contains("xcam_info")) vt_tray.xcam_info = jj["vt_tray"]["xcam_info"].get(); @@ -4339,6 +4384,8 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) ; } } + for (auto &filament_id : need_checked_filament_id) + m_checked_filament.insert(filament_id); #pragma endregion } else if (jj["command"].get() == "gcode_line") { //ack of gcode_line @@ -4377,6 +4424,14 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) } } } else if (jj["command"].get() == "ams_filament_setting" && !key_field_only) { + if (jj.contains("result") && jj.contains("reason")) { + if (jj["result"].get() == "fail") { + auto err_code = jj["err_code"].get(); + print_error = err_code; + } + } + + // QDS trigger ams UI update ams_version = -1; @@ -4510,6 +4565,12 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) } } } else if (jj["command"].get() == "extrusion_cali_set") { + if (jj.contains("result") && jj.contains("reason")) { + if (jj["result"].get() == "fail") { + auto err_code = jj["err_code"].get(); + print_error = err_code; + } + } #ifdef CALI_DEBUG std::string str = jj.dump(); BOOST_LOG_TRIVIAL(info) << "extrusion_cali_set: " << str; @@ -4554,6 +4615,13 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) extrusion_cali_set_hold_start = std::chrono::system_clock::now(); } else if (jj["command"].get() == "extrusion_cali_sel") { + if (jj.contains("result") && jj.contains("reason")) { + if (jj["result"].get() == "fail") { + auto err_code = jj["err_code"].get(); + print_error = err_code; + } + } + #ifdef CALI_DEBUG std::string str = jj.dump(); BOOST_LOG_TRIVIAL(info) << "extrusion_cali_sel: " << str; @@ -4595,6 +4663,13 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) } } else if (jj["command"].get() == "extrusion_cali_get") { + if (jj.contains("result") && jj.contains("reason")) { + if (jj["result"].get() == "fail") { + auto err_code = jj["err_code"].get(); + print_error = err_code; + } + } + reset_pa_cali_history_result(); has_get_pa_calib_tab = true; @@ -4658,6 +4733,13 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) // notify cali history to update } else if (jj["command"].get() == "extrusion_cali_get_result") { + if (jj.contains("result") && jj.contains("reason")) { + if (jj["result"].get() == "fail") { + auto err_code = jj["err_code"].get(); + print_error = err_code; + } + } + reset_pa_cali_result(); get_pa_calib_result = true; @@ -5173,6 +5255,93 @@ std::string MachineObject::get_string_from_fantype(FanType type) return ""; } +void MachineObject::update_filament_list() +{ + PresetBundle *preset_bundle = Slic3r::GUI::wxGetApp().preset_bundle; + + // custom filament + std::map> filament_list; + for (auto &preset : preset_bundle->filaments()) { + if (preset.is_user() && preset.inherits() == "") { + ConfigOption * printer_opt = const_cast(preset).config.option("compatible_printers"); + ConfigOptionStrings *printer_strs = dynamic_cast(printer_opt); + for (const std::string &printer_str : printer_strs->values) { + if (printer_str == m_printer_preset_name) { + ConfigOption *opt_min = const_cast(preset).config.option("nozzle_temperature_range_low"); + int min_temp = -1; + if (opt_min) { + ConfigOptionInts *opt_min_ints = dynamic_cast(opt_min); + min_temp = opt_min_ints->get_at(0); + } + ConfigOption *opt_max = const_cast(preset).config.option("nozzle_temperature_range_high"); + int max_temp = -1; + if (opt_max) { + ConfigOptionInts *opt_max_ints = dynamic_cast(opt_max); + max_temp = opt_max_ints->get_at(0); + } + filament_list[preset.filament_id] = std::make_pair(min_temp, max_temp); + break; + } + } + } + } + + for (auto it = filament_list.begin(); it != filament_list.end(); it++) { + if (m_filament_list.find(it->first) != m_filament_list.end()) { + assert(it->first.size() == 8 && it->first[0] == 'P'); + + if (it->second.first != m_filament_list[it->first].first) { + BOOST_LOG_TRIVIAL(info) << "old min temp is not equal to new min temp and filament id: " << it->first; + continue; + } + + if (it->second.second != m_filament_list[it->first].second) { + BOOST_LOG_TRIVIAL(info) << "old max temp is not equal to new max temp and filament id: " << it->first; + continue; + } + + m_filament_list.erase(it->first); + } + } + + for (auto it = m_filament_list.begin(); it != m_filament_list.end(); it++) { + m_checked_filament.erase(it->first); + } + + m_filament_list = filament_list; +} + +int MachineObject::get_flag_bits(std::string str, int start, int count) +{ + int decimal_value = std::stoi(str, nullptr, 16); + int mask = 0; + for (int i = 0; i < count; i++) { mask += 1 << (start + i); } + + int flag = (decimal_value & (mask)) >> start; + return flag; +} + +int MachineObject::get_flag_bits(int num, int start, int count) +{ + int decimal_value = num; + int mask = 0; + for (int i = 0; i < count; i++) { mask += 1 << (start + i); } + + int flag = (decimal_value & (mask)) >> start; + return flag; +} + +void MachineObject::update_printer_preset_name(const std::string &nozzle_diameter_str) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << "start update preset_name"; + auto preset_boundle = Slic3r::GUI::wxGetApp().preset_bundle; + auto printer_set = preset_boundle->get_printer_names_by_printer_type_and_nozzle(MachineObject::get_preset_printer_model_name(this->printer_type), nozzle_diameter_str); + if (printer_set.size() > 0) + m_printer_preset_name = *printer_set.begin(); + else + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " update printer preset name failed "; +} + bool DeviceManager::EnableMultiMachine = false; bool DeviceManager::key_field_only = false; @@ -5256,6 +5425,12 @@ void DeviceManager::on_machine_alive(std::string json_str) std::string printer_signal = j["dev_signal"].get(); std::string connect_type = j["connect_type"].get(); std::string bind_state = j["bind_state"].get(); + + if (connect_type == "farm") { + connect_type ="lan"; + bind_state = "free"; + } + std::string sec_link = ""; std::string ssdp_version = ""; if (j.contains("sec_link")) { @@ -5359,6 +5534,21 @@ void DeviceManager::on_machine_alive(std::string json_str) } } +MachineObject* DeviceManager::insert_local_device(std::string dev_name, std::string dev_id, std::string dev_ip, std::string connection_type, std::string bind_state, std::string version, std::string access_code) +{ + MachineObject* obj; + obj = new MachineObject(m_agent, dev_name, dev_id, dev_ip); + obj->printer_type = MachineObject::parse_printer_type("C11"); + obj->dev_connection_type = connection_type; + obj->bind_state = bind_state; + obj->bind_sec_link = "secure"; + obj->bind_ssdp_version = version; + obj->m_is_online = true; + obj->set_access_code(access_code, false); + obj->set_user_access_code(access_code, false); + return obj; +} + void DeviceManager::disconnect_all() { @@ -5553,6 +5743,7 @@ bool DeviceManager::set_selected_machine(std::string dev_id, bool need_disconnec } } } + it->second->m_checked_filament.clear(); } selected_machine = dev_id; return true; @@ -5845,6 +6036,7 @@ std::string DeviceManager::get_printer_ams_img(std::string type_str) bool DeviceManager::get_printer_is_enclosed(std::string type_str) { return get_value_from_config(type_str, "printer_is_enclosed"); } + std::vector DeviceManager::get_resolution_supported(std::string type_str) { std::vector resolution_supported; @@ -5890,6 +6082,49 @@ std::vector DeviceManager::get_compatible_machine(std::string type_ return compatible_machine; } +boost::bimaps::bimap DeviceManager::get_all_model_id_with_name() +{ + boost::bimaps::bimap models; + std::vector m_files; + + wxDir dir(Slic3r::resources_dir() + "/printers/"); + if (!dir.IsOpened()) { + return models; + } + + wxString filename; + bool hasFile = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES); + while (hasFile) { + m_files.push_back(filename); + hasFile = dir.GetNext(&filename); + } + + for (wxString file : m_files) { + std::string config_file = Slic3r::resources_dir() + "/printers/" + file.ToStdString(); + boost::nowide::ifstream json_file(config_file.c_str()); + + try { + json jj; + if (json_file.is_open()) { + json_file >> jj; + if (jj.contains("00.00.00.00")) { + json const &printer = jj["00.00.00.00"]; + + std::string model_id; + std::string display_name; + if (printer.contains("model_id")) {model_id = printer["model_id"].get();} + if (printer.contains("display_name")) {display_name = printer["display_name"].get();} + + if (!model_id.empty() && !display_name.empty()) { + models.left.insert(make_pair(model_id, display_name)); + } + } + } + } catch (...) {} + } + + return models; +} bool DeviceManager::load_filaments_blacklist_config() { @@ -5957,7 +6192,7 @@ void DeviceManager::check_filaments_in_blacklist(std::string tag_vendor, std::st //third party if (vendor == "third party") { - if ("qidilab" != vendor && tag_type == type) { + if ("qidi3d" != vendor && tag_type == type) { in_blacklist = true; ac = action; info = description; diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 0003155..46aaeb5 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -13,6 +13,7 @@ #include "libslic3r/ProjectTask.hpp" #include "slic3r/Utils/json_diff.hpp" #include "slic3r/Utils/NetworkAgent.hpp" +#include "boost/bimap/bimap.hpp" #include "CameraPopup.hpp" #include "libslic3r/Calib.hpp" #include "libslic3r/Utils.hpp" @@ -313,6 +314,7 @@ struct DisValue { bool is_type_match = true; }; +class Preset; class MachineObject { private: @@ -412,9 +414,10 @@ public: /* properties */ std::string dev_name; - std::string dev_url; std::string dev_ip; std::string dev_id; + //y + std::string dev_url; std::string dev_apikey; bool local_use_ssl_for_mqtt { true }; bool local_use_ssl_for_ftp { true }; @@ -710,9 +713,22 @@ public: LVL_Rtsps, LVL_Rtsp } liveview_local{ LVL_None }; - bool liveview_remote{false}; - bool file_local{false}; - bool file_remote{false}; + enum LiveviewRemote { + LVR_None, + LVR_Tutk, + LVR_Agora, + LVR_TutkAgora + } liveview_remote{ LVR_None }; + enum FileLocal { + FL_None, + FL_Local + } file_local{ FL_None }; + enum FileRemote { + FR_None, + FR_Tutk, + FR_Agora, + FR_TutkAgora + } file_remote{ FR_None }; bool file_model_download{false}; bool virtual_camera{false}; @@ -764,6 +780,8 @@ public: bool is_support_p1s_plus{false}; bool is_support_nozzle_blob_detection{false}; bool is_support_air_print_detection{false}; + bool is_support_filament_setting_inprinting{false}; + bool is_support_agora{false}; int nozzle_max_temperature = -1; int bed_temperature_limit = -1; @@ -961,6 +979,15 @@ public: void get_firmware_info(); bool is_firmware_info_valid(); std::string get_string_from_fantype(FanType type); + + /* Device Filament Check */ + std::set m_checked_filament; + std::string m_printer_preset_name; + std::map> m_filament_list; // filament_id, pair + void update_filament_list(); + int get_flag_bits(std::string str, int start, int count = 1); + int get_flag_bits(int num, int start, int count = 1); + void update_printer_preset_name(const std::string &nozzle_diameter_str); }; class DeviceManager @@ -1009,7 +1036,7 @@ public: /* create machine or update machine properties */ void on_machine_alive(std::string json_str); - + MachineObject* insert_local_device(std::string dev_name, std::string dev_id, std::string dev_ip, std::string connection_type, std::string bind_state, std::string version, std::string access_code); /* disconnect all machine connections */ void disconnect_all(); int query_bind_status(std::string &msg); @@ -1055,11 +1082,12 @@ public: static std::string get_printer_ams_img(std::string type_str); static PrinterArch get_printer_arch(std::string type_str); static std::string get_ftp_folder(std::string type_str); - static bool get_printer_is_enclosed(std::string type_str); - static std::vector get_resolution_supported(std::string type_str); - static std::vector get_compatible_machine(std::string type_str); + static bool get_printer_is_enclosed(std::string type_str); static bool load_filaments_blacklist_config(); static void check_filaments_in_blacklist(std::string tag_vendor, std::string tag_type, bool& in_blacklist, std::string& ac, std::string& info); + static std::vector get_resolution_supported(std::string type_str); + static std::vector get_compatible_machine(std::string type_str); + static boost::bimaps::bimap get_all_model_id_with_name(); static std::string load_gcode(std::string type_str, std::string gcode_file); }; diff --git a/src/slic3r/GUI/DownloadProgressDialog.cpp b/src/slic3r/GUI/DownloadProgressDialog.cpp index 335e34b..14727d3 100644 --- a/src/slic3r/GUI/DownloadProgressDialog.cpp +++ b/src/slic3r/GUI/DownloadProgressDialog.cpp @@ -16,7 +16,6 @@ #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" @@ -26,13 +25,12 @@ namespace Slic3r { namespace GUI { - - -DownloadProgressDialog::DownloadProgressDialog(wxString title) +DownloadProgressDialog::DownloadProgressDialog(wxString title, bool post_login) : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) + , m_post_login(post_login) { - 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_url = wxT("https://wiki.qidi3d.com/en/software/qidi-studio/failed-to-get-network-plugin"); + wxString install_failed_url = wxT("https://wiki.qidi3d.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."); @@ -148,13 +146,22 @@ bool DownloadProgressDialog::Show(bool show) 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")); + m_status_bar->change_button_label(_L("OK")); on_finish(); - m_status_bar->set_cancel_callback_fina( - [this]() { - this->Close(); - } - ); + if (m_post_login) { + m_status_bar->set_cancel_callback_fina( + [this]() { + this->Close(); + wxGetApp().request_login(); + } + ); + } else { + m_status_bar->set_cancel_callback_fina( + [this]() { + this->Close(); + } + ); + } }); //download failed diff --git a/src/slic3r/GUI/DownloadProgressDialog.hpp b/src/slic3r/GUI/DownloadProgressDialog.hpp index 056cd35..714f7ea 100644 --- a/src/slic3r/GUI/DownloadProgressDialog.hpp +++ b/src/slic3r/GUI/DownloadProgressDialog.hpp @@ -37,7 +37,7 @@ protected: void on_close(wxCloseEvent& event); public: - DownloadProgressDialog(wxString title); + DownloadProgressDialog(wxString title, bool post_login = false); wxString format_text(wxStaticText* st, wxString str, int warp); ~DownloadProgressDialog(); @@ -53,6 +53,8 @@ public: protected: virtual std::shared_ptr make_job(std::shared_ptr pri); virtual void on_finish(); + + bool m_post_login { false }; }; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 0c6f296..f6ffe73 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -146,7 +146,7 @@ void Field::PostInitialize() // For the mode, when settings are in non-modal dialog, neither dialog nor tabpanel doesn't receive wxEVT_KEY_UP event, when some field is selected. // So, like a workaround check wxEVT_KEY_UP event for the Filed and switch between tabs if Ctrl+(1-4) was pressed if (getWindow()) { - if (m_opt.readonly) { + if (m_opt.readonly) { this->disable(); } else { this->enable(); @@ -357,8 +357,9 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true m_value = val; break; } case coString: - case coStrings: - case coFloatOrPercent: { + case coStrings: + case coFloatOrPercent: + case coFloatsOrPercents: { if (m_opt.type == coFloatOrPercent && !str.IsEmpty() && str.Last() != '%') { double val = 0.; @@ -613,9 +614,9 @@ struct myEvtHandler : wxEvtHandler // In Field, All Bind has id, but for TextInput, ComboBox, SpinInput, all not if (entry->m_id != wxID_ANY && entry->m_lastId == wxID_ANY) Unbind(entry->m_eventType, - wxEventFunctorRef{entry->m_fn}, - entry->m_id, - entry->m_lastId, + wxEventFunctorRef{entry->m_fn}, + entry->m_id, + entry->m_lastId, entry->m_callbackUserData); //DoUnbind(entry->m_id, entry->m_lastId, entry->m_eventType, *entry->m_fn, entry->m_callbackUserData); } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ad04293..910aa39 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1123,6 +1123,7 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr m_conflict_result = gcode_result.conflict_result; if (m_conflict_result) { m_conflict_result.value().layer = m_layers.get_l_at(m_conflict_result.value()._height); } + filament_printable_reuslt = gcode_result.filament_printable_reuslt; //QDS: add mutex for protection of gcode result gcode_result.unlock(); //QDS: add logs @@ -1291,13 +1292,13 @@ void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin) #endif // ENABLE_GCODE_VIEWER_STATISTICS //QDS: always render shells in preview window + glsafe(::glEnable(GL_DEPTH_TEST)); render_shells(); m_legend_height = 0.0f; if (m_roles.empty()) return; - glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); //render_shells(); render_legend(m_legend_height, canvas_width, canvas_height, right_margin); @@ -3167,6 +3168,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p // QDS: fix the issue that object_idx is not assigned as index of Model.objects array int object_count = 0; const ModelObjectPtrs& model_objs = wxGetApp().model().objects; + bool enable_lod = GUI::wxGetApp().app_config->get("enable_lod") == "true"; for (const PrintObject* obj : print.objects()) { const ModelObject* model_obj = obj->model_object(); @@ -3193,7 +3195,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p instance_ids.resize(instance_index); size_t current_volumes_count = m_shells.volumes.volumes.size(); - m_shells.volumes.load_object(model_obj, object_idx, instance_ids, "object", initialized); + m_shells.volumes.load_object(model_obj, object_idx, instance_ids, "object", initialized, enable_lod); // adjust shells' z if raft is present const SlicingParameters& slicing_parameters = obj->slicing_parameters(); @@ -3258,6 +3260,29 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p % m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size(); } +void GUI::GCodeViewer::set_shells_on_preview(bool is_previewing) { + if (is_previewing) { + delete_wipe_tower(); + } + m_shells.previewing = is_previewing; +} + +void GUI::GCodeViewer::delete_wipe_tower() +{ + size_t current_volumes_count = m_shells.volumes.volumes.size(); + if (current_volumes_count >= 1) { + for (size_t i = current_volumes_count - 1; i > 0; i--) { + GLVolume *v = m_shells.volumes.volumes[i]; + if (v->is_wipe_tower) { + m_shells.volumes.release_volume(v); + delete v; + m_shells.volumes.volumes.erase(m_shells.volumes.volumes.begin() + i); + break; + } + } + } +} + void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const { #if ENABLE_GCODE_VIEWER_STATISTICS @@ -4139,16 +4164,14 @@ void GCodeViewer::render_shells() if (!v->indexed_vertex_array->has_VBOs()) v->finalize_geometry(true); } - - glsafe(::glEnable(GL_DEPTH_TEST)); -// glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glDepthMask(GL_FALSE)); shader->start_using(); //QDS: reopen cul faces m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); shader->stop_using(); -// glsafe(::glDepthMask(GL_TRUE)); + glsafe(::glDepthMask(GL_TRUE)); } //QDS @@ -4363,10 +4386,9 @@ void GCodeViewer::render_all_plates_stats(const std::vector longest_str) - longest_str = i; + longest_str += i; } - ::sprintf(buff, "%.2f", longest_str); + ::sprintf(buff, imperial_units ? "%.2f oz" : "%.2f g", longest_str / unit_conver); std::vector>> title_columns; if (displayed_columns & ColumnData::Model) { @@ -4479,10 +4501,10 @@ void GCodeViewer::render_all_plates_stats(const std::vector> columns_offsets; - columns_offsets.push_back({ _u8L("Plate") + " " + std::to_string(it->first), offsets[_u8L("Filament")]}); + columns_offsets.push_back({ _u8L("Plate") + " " + std::to_string(it->first + 1), offsets[_u8L("Filament")]}); columns_offsets.push_back({ short_time(get_time_dhms(it->second)), offsets[_u8L("Model")] }); append_item(false, m_tools.m_tool_colors[0], columns_offsets); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 8762cdb..0bb0d48 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -738,6 +738,8 @@ public: //QDS ConflictResultOpt m_conflict_result; + FilamentPrintableResult filament_printable_reuslt; + private: std::vector m_plater_extruder; bool m_gl_data_initialized{ false }; @@ -821,7 +823,8 @@ public: //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; } + void set_shells_on_preview(bool is_previewing); + void delete_wipe_tower(); //QDS: add all plates filament statistics void render_all_plates_stats(const std::vector& gcode_result_list, bool show = true) const; //QDS: GUI refactor: add canvas width and height diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f1e79f1..1b9ce07 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -701,9 +701,7 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_ return; Transform3d world_to_eye = camera.get_view_matrix(); - Transform3d world_to_screen = camera.get_projection_matrix() * world_to_eye; const std::array& viewport = camera.get_viewport(); - struct Owner { int obj_idx; @@ -783,17 +781,14 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_ ImGuiWrapper& imgui = *wxGetApp().imgui(); // render info windows + Matrix4d world_to_screen = camera.get_projection_matrix().matrix() * world_to_eye.matrix(); for (const Owner& owner : owners) { - Vec3d screen_box_center = world_to_screen * owner.world_box.center(); - float x = 0.0f; - float y = 0.0f; - if (camera.get_type() == Camera::EType::Perspective) { - x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2]; - y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3]; - } else { - x = (0.5f + 0.5f * (float)screen_box_center(0)) * viewport[2]; - y = (0.5f - 0.5f * (float)screen_box_center(1)) * viewport[3]; - } + Vec4d temp_center(owner.world_box.center().x(), owner.world_box.center().y(), owner.world_box.center().z(), 1.0); + Vec4d temp_ndc = world_to_screen * temp_center; + Vec3d screen_box_center = Vec3d(temp_ndc.x(), temp_ndc.y(), temp_ndc.z()) / temp_ndc.w(); + + float x = 0.5f * (1 + screen_box_center(0)) * viewport[2]; + float y = 0.5f * (1 - screen_box_center(1)) * viewport[3]; if (x < 0.0f || viewport[2] < x || y < 0.0f || viewport[3] < y) continue; @@ -1175,9 +1170,10 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) m_selection.set_volumes(&m_volumes.volumes); + const wxString alt = GUI::shortkey_alt_prefix(); m_assembly_view_desc["object_selection_caption"] = _L("Left mouse button"); m_assembly_view_desc["object_selection"] = _L("object selection"); - m_assembly_view_desc["part_selection_caption"] = "Alt +" + _L("Left mouse button"); + m_assembly_view_desc["part_selection_caption"] = alt + _L("Left mouse button"); m_assembly_view_desc["part_selection"] = _L("part selection"); m_assembly_view_desc["number_key_caption"] = "1~16 " + _L("number keys"); m_assembly_view_desc["number_key"] = _L("number keys can quickly change the color of objects"); @@ -1474,6 +1470,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject || gizmo_type == GLGizmosManager::Seam) && ! vol->is_modifier) vol->force_neutral_color = true; + else if (gizmo_type == GLGizmosManager::BrimEars) + vol->force_neutral_color = false; else if (gizmo_type == GLGizmosManager::MmuSegmentation) vol->is_active = false; else if (gizmo_type == GLGizmosManager::Text) { @@ -1572,12 +1570,29 @@ void GLCanvas3D::refresh_camera_scene_box() wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box()); } +BoundingBoxf3 GLCanvas3D::assembly_view_cur_bounding_box() const { + return m_model->bounding_box_in_assembly_view(); +} + BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const { BoundingBoxf3 bb; - for (const GLVolume* volume : m_volumes.volumes) { - if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) - bb.merge(volume->transformed_bounding_box()); + BoundingBoxf3 expand_part_plate_list_box; + bool is_limit = m_canvas_type != ECanvasType::CanvasAssembleView; + if (is_limit) { + auto plate_list_box = wxGetApp().plater()->get_partplate_list().get_bounding_box(); + auto horizontal_radius = 0.5 * sqrt(std::pow(plate_list_box.min[0] - plate_list_box.max[0], 2) + std::pow(plate_list_box.min[1] - plate_list_box.max[1], 2)); + const float scale = 2; + expand_part_plate_list_box.merge(plate_list_box.min - scale * Vec3d(horizontal_radius, horizontal_radius, 0)); + expand_part_plate_list_box.merge(plate_list_box.max + scale * Vec3d(horizontal_radius, horizontal_radius, 0)); + } + for (const GLVolume *volume : m_volumes.volumes) { + if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) { + const auto v_bb = volume->transformed_bounding_box(); + if (is_limit && !expand_part_plate_list_box.overlap(v_bb)) + continue; + bb.merge(v_bb); + } } return bb; } @@ -1920,17 +1935,21 @@ void GLCanvas3D::render(bool only_init) //QDS add partplater rendering logic bool only_current = false, only_body = false, show_axes = true, no_partplate = false; + bool show_grid = true; GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type(); if (!m_main_toolbar.is_enabled() || m_gizmos.is_show_only_active_plate()) { //only_body = true; if (m_gizmos.get_object_located_outside_plate()) { no_partplate = true; - } else { + } + else { only_current = true; } } else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmuSegmentation)) no_partplate = true; + else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward()) + show_grid = false; /* view3D render*/ int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1; @@ -1942,7 +1961,7 @@ void GLCanvas3D::render(bool only_init) if (!no_partplate) _render_bed(!camera.is_looking_downward(), show_axes); if (!no_partplate) //QDS: add outline logic - _render_platelist(!camera.is_looking_downward(), only_current, only_body, hover_id, true); + _render_platelist(!camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid); _render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running()); } /* preview render */ @@ -1992,6 +2011,7 @@ void GLCanvas3D::render(bool only_init) #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET + camera.update_frustum(); if (m_picking_enabled && m_rectangle_selection.is_dragging()) m_rectangle_selection.render(*this); @@ -2022,9 +2042,10 @@ void GLCanvas3D::render(bool only_init) wxGetApp().plater()->render_project_state_debug_window(); #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW -#if ENABLE_CAMERA_STATISTICS - camera.debug_render(); -#endif // ENABLE_CAMERA_STATISTICS + if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { + camera.debug_render(); + camera.debug_frustum(); + } #if ENABLE_IMGUI_STYLE_EDITOR if (wxGetApp().get_mode() == ConfigOptionMode::comDevelop) @@ -2159,6 +2180,9 @@ void GLCanvas3D::update_plate_thumbnails() void GLCanvas3D::select_all() { + if (!m_gizmos.is_allow_select_all()) { + return; + } m_selection.add_all(); m_dirty = true; } @@ -2251,22 +2275,22 @@ void GLCanvas3D::set_volumes_z_range(const std::array& range) m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); } -std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs) +std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs, bool lod_enabled) { if (instance_idxs.empty()) { for (unsigned int i = 0; i < model_object.instances.size(); ++i) { instance_idxs.emplace_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized, lod_enabled); } -std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) +std::vector GLCanvas3D::load_object(const Model& model, int obj_idx, bool lod_enabled) { if (0 <= obj_idx && obj_idx < (int)model.objects.size()) { const ModelObject* model_object = model.objects[obj_idx]; if (model_object != nullptr) - return load_object(*model_object, obj_idx, std::vector()); + return load_object(*model_object, obj_idx, std::vector(), lod_enabled); } return std::vector(); @@ -2274,7 +2298,11 @@ std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) void GLCanvas3D::mirror_selection(Axis axis) { - m_selection.mirror(axis); + TransformationType transformation_type; + //transformation_type.set_world(); + transformation_type.set_relative(); + m_selection.setup_cache(); + m_selection.mirror(axis, transformation_type); do_mirror(L("Mirror Object")); // QDS //wxGetApp().obj_manipul()->set_dirty(); @@ -2550,6 +2578,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } } m_volumes.volumes = std::move(glvolumes_new); + bool enable_lod = GUI::wxGetApp().app_config->get("enable_lod") == "true"; for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { const ModelObject &model_object = *m_model->objects[obj_idx]; for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { @@ -2570,7 +2599,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh // later in this function. it->volume_idx = m_volumes.volumes.size(); - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized, m_canvas_type == ECanvasType::CanvasAssembleView); + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized, m_canvas_type == ECanvasType::CanvasAssembleView, false, enable_lod); m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; } else { @@ -2782,6 +2811,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re //QDS:exclude the assmble view if (m_canvas_type != ECanvasType::CanvasAssembleView) { _set_warning_notification_if_needed(EWarning::GCodeConflict); + _set_warning_notification(EWarning::FilamentUnPrintableOnFirstLayer, false); // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { ModelInstanceEPrintVolumeState state; @@ -2870,6 +2900,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co _set_warning_notification_if_needed(EWarning::ToolHeightOutside); _set_warning_notification_if_needed(EWarning::ToolpathOutside); _set_warning_notification_if_needed(EWarning::GCodeConflict); + _set_warning_notification_if_needed(EWarning::FilamentUnPrintableOnFirstLayer); } m_gcode_viewer.refresh(gcode_result, str_tool_colors); @@ -3106,7 +3137,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #ifdef __APPLE__ case 'm': case 'M': -#else /* __APPLE__ */ +#else /* __APPLE__ */ case WXK_CONTROL_M: #endif /* __APPLE__ */ { @@ -3298,8 +3329,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) post_event(SimpleEvent(EVT_GLCANVAS_ORIENT)); break; } - //case 'B': - //case 'b': { zoom_to_bed(); break; } #if !QDT_RELEASE_TO_PUBLIC case 'C': case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } @@ -3460,7 +3489,10 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) m_dirty = true; }, [this](const Vec3d& direction, bool slow, bool camera_space) { - m_selection.start_dragging(); + if (m_gizmos.is_ban_move_glvolume()) { + return; + } + m_selection.setup_cache(); double multiplier = slow ? 1.0 : 10.0; Vec3d displacement; @@ -3472,8 +3504,9 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) else displacement = multiplier * direction; - m_selection.translate(displacement); - m_selection.stop_dragging(); + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(displacement, trafo_type); m_dirty = true; } );} @@ -3497,6 +3530,9 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) evt.ShiftDown() && evt.AltDown() && keyCode == WXK_RETURN) { wxGetApp().plater()->toggle_show_wireframe(); m_dirty = true; + } else if ((evt.ShiftDown() && evt.ControlDown() && keyCode == 'L')) { + wxGetApp().plater()->toggle_non_manifold_edges(); + m_dirty = true; } else if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { // Enable switching between 3D and Preview with Tab @@ -3570,16 +3606,32 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) { select_view("bottom"); break; } case '3': case WXK_NUMPAD3: //3 on numpad - { select_view("front"); break; } + { + select_view("front"); + m_gizmos.update_paint_base_camera_rotate_rad(); + break; + } case '4': case WXK_NUMPAD4: //4 on numpad - { select_view("rear"); break; } + { + select_view("rear"); + m_gizmos.update_paint_base_camera_rotate_rad(); + break; + } case '5': case WXK_NUMPAD5: //5 on numpad - { select_view("left"); break; } + { + select_view("left"); + m_gizmos.update_paint_base_camera_rotate_rad(); + break; + } case '6': case WXK_NUMPAD6: //6 on numpad - { select_view("right"); break; } + { + select_view("right"); + m_gizmos.update_paint_base_camera_rotate_rad(); + break; + } case '7': case WXK_NUMPAD7: //7 on numpad { select_plate(); break; } @@ -3612,9 +3664,14 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); } else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) { auto _do_rotate = [this](double angle_z_rad) { - m_selection.start_dragging(); + if (m_gizmos.is_ban_move_glvolume()) { + return; + } + if (!m_gizmos.get_gizmo_active_condition(GLGizmosManager::EType::Rotate)) { + return; + } + m_selection.setup_cache(); m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint)); - m_selection.stop_dragging(); m_dirty = true; // wxGetApp().obj_manipul()->set_dirty(); }; @@ -4058,16 +4115,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.position = pos.cast(); if (evt.Dragging() && current_printer_technology() == ptFFF && (fff_print()->config().print_sequence == PrintSequence::ByObject)) { - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Move: - case GLGizmosManager::EType::Scale: - case GLGizmosManager::EType::Rotate: - { + if (can_sequential_clearance_show_in_gizmo()) { update_sequential_clearance(); - break; - } - default: { break; } } } else if (evt.Dragging()) { @@ -4182,11 +4231,22 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int volume_idx = get_first_hover_volume_idx(); bool already_selected = m_selection.contains_volume(volume_idx); bool ctrl_down = evt.CmdDown(); - + bool alt_down = evt.AltDown(); Selection::IndicesList curr_idxs = m_selection.get_volume_idxs(); if (already_selected && ctrl_down) m_selection.remove(volume_idx); + else if (alt_down) { + Selection::EMode mode = Selection::Volume; + if (already_selected) { + std::vector volume_idxs; + for (auto idx : curr_idxs) { volume_idxs.emplace_back(idx); } + m_selection.remove_volumes(mode, volume_idxs); + } + std::vector add_volume_idxs; + add_volume_idxs.emplace_back(volume_idx); + m_selection.add_volumes(mode, add_volume_idxs, true); + } else { m_selection.add(volume_idx, !ctrl_down, true); m_mouse.drag.move_requires_threshold = !already_selected; @@ -4216,7 +4276,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int volume_idx = get_first_hover_volume_idx(); BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); volume_bbox.offset(1.0); - if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) { + if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !m_selection.is_any_connector()) { m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; // The dragging operation is initiated. m_mouse.drag.move_volume_idx = volume_idx; @@ -4238,7 +4298,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag if (m_selection.contains_volume(get_first_hover_volume_idx())) { const Camera& camera = wxGetApp().plater()->get_camera(); - if (std::abs(camera.get_dir_forward()(2)) < EPSILON) { + auto camera_up_down_rad_limit = abs(asin(camera.get_dir_forward()(2) / 1.0f)); + if (camera_up_down_rad_limit < PI/20.0f) { // side view -> move selected volumes orthogonally to camera view direction Linef3 ray = mouse_ray(pos); Vec3d dir = ray.unit_vector(); @@ -4259,6 +4320,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // apply offset cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up; + cur_pos[2] = m_mouse.drag.start_position_3D(2); } else { // Generic view @@ -4268,8 +4330,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2)); } } - - m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); if (current_printer_technology() == ptFFF && (fff_print()->config().print_sequence == PrintSequence::ByObject)) update_sequential_clearance(); // QDS @@ -4506,6 +4569,39 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else evt.Skip(); + // Detection of doubleclick on text to open emboss edit window + auto type = m_gizmos.get_current_type(); + if (evt.LeftDClick() && !m_hover_volume_idxs.empty() && + (type == GLGizmosManager::EType::Undefined //||type == GLGizmosManager::EType::Text || + //type == GLGizmosManager::EType::Svg + )) { + for (int hover_volume_id : m_hover_volume_idxs) { + const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id]; + int object_idx = hover_gl_volume.object_idx(); + if (object_idx < 0 || static_cast(object_idx) >= m_model->objects.size()) + continue; + const ModelObject *hover_object = m_model->objects[object_idx]; + int hover_volume_idx = hover_gl_volume.volume_idx(); + if (hover_volume_idx < 0 || static_cast(hover_volume_idx) >= hover_object->volumes.size()) + continue; + const ModelVolume *hover_volume = hover_object->volumes[hover_volume_idx]; + + /* if (hover_volume->text_configuration.has_value()) { + m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + if (type != GLGizmosManager::EType::Emboss) m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss); + wxGetApp().obj_list()->update_selections(); + return; + } else*/ if (hover_volume->emboss_shape.has_value()) { + m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + if (type != GLGizmosManager::EType::Svg) + m_gizmos.open_gizmo(GLGizmosManager::EType::Svg); + wxGetApp().obj_list()->update_selections(); + return; + } + } + } + + if (m_moving) show_sinking_contours(); @@ -4580,7 +4676,7 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) m_tooltip.set_text(tooltip); } -void GLCanvas3D::do_move(const std::string& snapshot_type) +void GLCanvas3D::do_move(const std::string &snapshot_type) { if (m_model == nullptr) return; @@ -4620,8 +4716,9 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) } } else if (selection_mode == Selection::Volume) { - if (model_object->volumes[volume_idx]->get_offset() != v->get_volume_offset()) { - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + auto cur_mv = model_object->volumes[volume_idx]; + if (cur_mv->get_offset() != v->get_volume_offset()) { + cur_mv->set_transformation(v->get_volume_transformation()); // QDS: backup Slic3r::save_object_mesh(*model_object); } @@ -4726,17 +4823,15 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) if (model_object != nullptr) { if (selection_mode == Selection::Instance) { if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { - model_object->instances[instance_idx]->set_assemble_rotation(v->get_instance_rotation()); - model_object->instances[instance_idx]->set_assemble_offset(v->get_instance_offset()); + model_object->instances[instance_idx]->set_assemble_from_transform(v->get_instance_transformation().get_matrix()); } else { - model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); } } else if (selection_mode == Selection::Volume) { - if (model_object->volumes[volume_idx]->get_rotation() != v->get_volume_rotation()) { - model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + auto cur_mv = model_object->volumes[volume_idx]; + if (cur_mv->get_rotation() != v->get_volume_rotation()) { + cur_mv->set_transformation(v->get_volume_transformation()); // QDS: backup Slic3r::save_object_mesh(*model_object); } @@ -4812,14 +4907,13 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { - model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); } else if (selection_mode == Selection::Volume) { - if (model_object->volumes[volume_idx]->get_scaling_factor() != v->get_volume_scaling_factor()) { - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); - model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); - model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + auto cur_mv = model_object->volumes[volume_idx]; + if (cur_mv->get_scaling_factor() != v->get_volume_scaling_factor()) { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + cur_mv->set_transformation(v->get_volume_transformation()); // QDS: backup Slic3r::save_object_mesh(*model_object); } @@ -4919,10 +5013,10 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) - model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); else if (selection_mode == Selection::Volume) { - if (model_object->volumes[volume_idx]->get_mirror() != v->get_volume_mirror()) { - model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); + if (model_object->volumes[volume_idx]->get_transformation() != v->get_volume_transformation()) { + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); // QDS: backup Slic3r::save_object_mesh(*model_object); } @@ -5177,6 +5271,17 @@ void GLCanvas3D::mouse_up_cleanup() m_canvas->ReleaseMouse(); } +bool GLCanvas3D::can_sequential_clearance_show_in_gizmo() { + switch (m_gizmos.get_current_type()) { + case GLGizmosManager::EType::Move: + case GLGizmosManager::EType::Scale: + case GLGizmosManager::EType::Rotate: { + return true; + } + } + return false; +} + void GLCanvas3D::update_sequential_clearance() { if (current_printer_technology() != ptFFF || (fff_print()->config().print_sequence == PrintSequence::ByLayer)) @@ -5232,8 +5337,8 @@ void GLCanvas3D::update_sequential_clearance() Polygon hull_no_offset = model_object->convex_hull_2d(Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())); auto tmp = offset(hull_no_offset, - // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects - // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + // Shrink the extruder_clearance_max_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_max_radius, this test will not trigger collision. shrink_factor, jtRound, mitter_limit); Polygon hull_2d = !tmp.empty() ? tmp.front() : hull_no_offset;// tmp may be empty due to clipper's bug, see STUDIO-2452 @@ -5387,6 +5492,14 @@ bool GLCanvas3D::is_object_sinking(int object_idx) const return false; } +void GLCanvas3D::apply_retina_scale(Vec2d &screen_coordinate) const +{ +#if ENABLE_RETINA_GL + double scale = static_cast(m_retina_helper->get_scale_factor()); + screen_coordinate *= scale; +#endif // ENABLE_RETINA_GL} +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; @@ -6299,6 +6412,7 @@ bool GLCanvas3D::_init_main_toolbar() item.icon_filename = m_is_dark ? "toolbar_add_plate_dark.svg" : "toolbar_add_plate.svg"; item.tooltip = _utf8(L("Add plate")); item.sprite_id++; + item.continuous_click = true; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD_PLATE)); }; item.enabling_callback = []()->bool {return wxGetApp().plater()->can_add_plate(); }; if (!m_main_toolbar.add_item(item)) @@ -6309,7 +6423,7 @@ bool GLCanvas3D::_init_main_toolbar() item.tooltip = _utf8(L("Auto orient")); item.sprite_id++; item.left.render_callback = nullptr; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_do_ui_job(); }; item.left.toggable = false; // allow right mouse click //QDS: GUI refactor: adjust the main toolbar position item.left.action_callback = [this]() { @@ -6336,7 +6450,7 @@ bool GLCanvas3D::_init_main_toolbar() if (agent) agent->track_update_property("auto_arrange", std::to_string(++auto_arrange_count)); } }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_do_ui_job(); }; item.left.toggable = true; //QDS: GUI refactor: adjust the main toolbar position item.left.render_callback = [this](float left, float right, float bottom, float top) { @@ -6970,9 +7084,10 @@ void GLCanvas3D::_render_bed_for_picking(bool bottom) //m_bed.render_for_picking(*this, bottom, scale_factor); } -void GLCanvas3D::_render_platelist(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) const +void GLCanvas3D::_render_platelist(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid) const { - wxGetApp().plater()->get_partplate_list().render(bottom, only_current, only_body, hover_id, render_cali); + wxGetApp().plater()->get_partplate_list().render(bottom, only_current, only_body, hover_id, render_cali, show_grid, + wxGetApp().app_config->get_bool("enable_opengl_multi_instance")); } void GLCanvas3D::_render_plates_for_picking() const @@ -7193,19 +7308,11 @@ void GLCanvas3D::_render_sequential_clearance() { if (m_gizmos.is_dragging()) return; - - switch (m_gizmos.get_current_type()) - { - case GLGizmosManager::EType::Flatten: - case GLGizmosManager::EType::Cut: - case GLGizmosManager::EType::Hollow: - case GLGizmosManager::EType::SlaSupports: - case GLGizmosManager::EType::FdmSupports: - case GLGizmosManager::EType::Seam: { return; } - default: { break; } + auto type = m_gizmos.get_current_type(); + if (type == GLGizmosManager::EType::Undefined + || can_sequential_clearance_show_in_gizmo()) { + m_sequential_print_clearance.render(); } - - m_sequential_print_clearance.render(); } #if ENABLE_RENDER_SELECTION_CENTER @@ -7973,7 +8080,18 @@ void GLCanvas3D::_render_return_toolbar() float window_height = button_icon_size.y + imgui.scaled(2.0f); float window_pos_x = 30.0f; float window_pos_y = 14.0f; - + {//solve ui overlap issue + if (m_canvas_type == ECanvasType::CanvasView3D) { + float zoom = (float) wxGetApp().plater()->get_camera().get_zoom(); + float left_pos = m_main_toolbar.get_item("add")->render_left_pos; + const float toolbar_x = 0.5 * canvas_w + left_pos * zoom; + const float margin = 5; + if (toolbar_x < window_width + margin * 3) { + window_pos_x = 5.0f; + window_pos_y = m_main_toolbar.get_height() + 2.0f; + } + } + } imgui.set_next_window_pos(window_pos_x, window_pos_y, ImGuiCond_Always, 0, 0); #ifdef __WINDOWS__ imgui.set_next_window_size(window_width, window_height, ImGuiCond_Always); @@ -8277,7 +8395,7 @@ float GLCanvas3D::_show_assembly_tooltip_information(float caption_max, float x, } //QDS -void GLCanvas3D::_render_assemble_control() const +void GLCanvas3D::_render_assemble_control() { if (m_canvas_type != ECanvasType::CanvasAssembleView) { GLVolume::explosion_ratio = m_explosion_ratio = 1.0; @@ -8298,7 +8416,7 @@ void GLCanvas3D::_render_assemble_control() const const float text_padding = 7.0f; const float text_size_x = std::max(imgui->calc_text_size(_L("Reset direction")).x + 2 * ImGui::GetStyle().FramePadding.x, std::max(imgui->calc_text_size(_L("Explosion Ratio")).x, imgui->calc_text_size(_L("Section View")).x)); - const float slider_width = 75.0f; + const float slider_width = 60.0f; const float value_size = imgui->calc_text_size("3.00").x + text_padding * 2; const float item_spacing = imgui->get_item_spacing().x; ImVec2 window_padding = ImGui::GetStyle().WindowPadding; @@ -8319,6 +8437,7 @@ void GLCanvas3D::_render_assemble_control() const float get_cur_y = pos.y - ImGui::GetFrameHeight() - 4 * text_y; tip_icon_size =_show_assembly_tooltip_information(caption_max, get_cur_x, get_cur_y); } + float same_line_width = tip_icon_size; { float clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position(); if (clp_dist == 0.f) { @@ -8332,32 +8451,77 @@ void GLCanvas3D::_render_assemble_control() const }); } } - - ImGui::SameLine(tip_icon_size + window_padding.x + text_size_x + item_spacing); + same_line_width += (text_size_x + item_spacing); + ImGui::SameLine(same_line_width); ImGui::PushItemWidth(slider_width); bool view_slider_changed = imgui->qdt_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(tip_icon_size +window_padding.x + text_size_x + slider_width + item_spacing * 2); + same_line_width += (slider_width + item_spacing); + ImGui::SameLine(same_line_width); ImGui::PushItemWidth(value_size); bool view_input_changed = ImGui::QDTDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); if (view_slider_changed || view_input_changed) m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(clp_dist, true); - } + same_line_width += (value_size + item_spacing * 2); + } { - ImGui::SameLine(tip_icon_size +window_padding.x + text_size_x + slider_width + item_spacing * 6 + value_size); + auto temp_x = imgui->calc_text_size(_L("Explosion Ratio")).x; + ImGui::SameLine(same_line_width); + ImGui::PushItemWidth(temp_x); imgui->text(_L("Explosion Ratio")); - ImGui::SameLine(tip_icon_size +window_padding.x + 2 * text_size_x + slider_width + item_spacing * 7 + value_size); + same_line_width += (temp_x + item_spacing); + ImGui::SameLine(same_line_width); ImGui::PushItemWidth(slider_width); bool explosion_slider_changed = imgui->qdt_slider_float_style("##ratio_slider", &m_explosion_ratio, 1.0f, 3.0f, "%1.2f"); - ImGui::SameLine(tip_icon_size +window_padding.x + 2 * text_size_x + 2 * slider_width + item_spacing * 8 + value_size); + same_line_width += (slider_width + item_spacing); + ImGui::SameLine(same_line_width); ImGui::PushItemWidth(value_size); bool explosion_input_changed = ImGui::QDTDragFloat("##ratio_input", &m_explosion_ratio, 0.1f, 1.0f, 3.0f, "%1.2f"); + same_line_width += (value_size + item_spacing*2); } + { + ImGui::SameLine(same_line_width); + // input + std::vector modes = {_u8L("Object"), _u8L("Part")}; + int selection_idx = m_selection.get_volume_selection_mode() == Selection::Instance ? 0 : 1; + auto label = _u8L("Selection Mode") + ":" ; + auto label_width = imgui->calc_text_size(label).x ; + auto item_width = imgui->calc_text_size(_L("Object")).x * 2.5 + imgui->calc_text_size("x").x+imgui->scaled(2); + //render imgui + ImGui::AlignTextToFramePadding(); + ImGui::PushItemWidth(label_width); + imgui->text(label); + same_line_width += (label_width + item_spacing); + ImGui::SameLine(same_line_width); + ImGui::PushItemWidth(item_width); + size_t selection_out = selection_idx; + const char *selected_str = (selection_idx >= 0 && selection_idx < int(modes.size())) ? modes[selection_idx].c_str() : ""; + ImGuiWrapper::push_combo_style(get_scale()); + if (ImGui::QDTBeginCombo(("##" + label).c_str(), selected_str, 0)) { + for (size_t line_idx = 0; line_idx < modes.size(); ++line_idx) { + ImGui::PushID(int(line_idx)); + if (ImGui::Selectable("", line_idx == selection_idx)) + selection_out = line_idx; + + ImGui::SameLine(); + ImGui::Text("%s", modes[line_idx].c_str()); + ImGui::PopID(); + } + ImGui::EndCombo(); + } + ImGuiWrapper::pop_combo_style(); + if (selection_idx != selection_out) {//do + if (selection_out == 0) { m_selection.unlock_volume_selection_mode(); } + m_selection.set_volume_selection_mode(selection_out == 1 ? Selection::Volume : Selection::Instance); + if (selection_out == 1) { m_selection.lock_volume_selection_mode(); } + } + same_line_width += (label_width + item_width); + } imgui->end(); ImGuiWrapper::pop_toolbar_style(); @@ -9293,16 +9457,17 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) if (current_printer_technology() != ptSLA) { unsigned int max_z_layer = m_gcode_viewer.get_layers_z_range().back(); //w18 - if (warning == EWarning::ToolHeightOutside) { // check if max z_layer height exceed max print height + if (warning == EWarning::ToolHeightOutside) { // check if max z_layer height exceed max print height if(wxGetApp().preset_bundle->printers.get_edited_preset().is_qdt_vendor_preset(wxGetApp().preset_bundle) ) show = m_gcode_viewer.has_data() && (m_gcode_viewer.get_layers_zs()[max_z_layer] - m_gcode_viewer.get_max_print_height() >= 1e-6); } else if (warning == EWarning::ToolpathOutside) { // check if max x,y coords exceed bed area show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed() && - (m_gcode_viewer.get_max_print_height() -m_gcode_viewer.get_layers_zs()[max_z_layer] >= 1e-6); - } - else if (warning == EWarning::GCodeConflict) + (m_gcode_viewer.get_max_print_height() - m_gcode_viewer.get_layers_zs()[max_z_layer] >= 1e-6); + } else if (warning == EWarning::GCodeConflict) show = m_gcode_viewer.has_data() && m_gcode_viewer.is_contained_in_bed() && m_gcode_viewer.m_conflict_result.has_value(); + else if (warning == EWarning::FilamentUnPrintableOnFirstLayer) + show = m_gcode_viewer.has_data() && m_gcode_viewer.filament_printable_reuslt.has_value(); } } } @@ -9338,7 +9503,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) PLATER_WARNING, PLATER_ERROR, SLICING_SERIOUS_WARNING, - SLICING_ERROR + SLICING_ERROR, }; std::string text; ErrorType error = ErrorType::PLATER_WARNING; @@ -9372,6 +9537,18 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) "Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume."); error = ErrorType::PLATER_ERROR; break; + case EWarning::FilamentUnPrintableOnFirstLayer: { + std::string warning; + const std::vector &conflict_filament = m_gcode_viewer.filament_printable_reuslt.conflict_filament; + auto iter = conflict_filament.begin(); + for (int filament : conflict_filament) { + warning += std::to_string(filament + 1); + warning+=" "; + } + text = (boost::format(_u8L("filaments %s cannot be printed directly on the surface of this plate.")) % warning ).str(); + error = ErrorType::SLICING_ERROR; + break; + } } //QDS: this may happened when exit the app, plater is null if (!wxGetApp().plater()) @@ -9403,6 +9580,20 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) notification_manager.push_slicing_error_notification(text, conflictObj ? std::vector{conflictObj} : std::vector{}); else notification_manager.close_slicing_error_notification(text); + if (warning == EWarning::FilamentUnPrintableOnFirstLayer) { + if (state) { + notification_manager.qdt_show_bed_filament_incompatible_notification(text); + } + else { + notification_manager.qdt_close_bed_filament_incompatible_notification(); + } + } + else { + if (state) + notification_manager.push_slicing_error_notification(text, conflictObj ? std::vector{conflictObj} : std::vector{}); + else + notification_manager.close_slicing_error_notification(text); + } break; default: break; @@ -9702,5 +9893,102 @@ void GLCanvas3D::GizmoHighlighter::blink() invalidate(); } +const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) +{ + const ModelVolume * ret = nullptr; + + if (v.object_idx() < (int)model.objects.size()) { + const ModelObject *obj = model.objects[v.object_idx()]; + if (v.volume_idx() < (int)obj->volumes.size()) + ret = obj->volumes[v.volume_idx()]; + } + + return ret; +} + +ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects) +{ + for (const ModelObject *obj : objects) + for (ModelVolume *vol : obj->volumes) + if (vol->id() == volume_id) + return vol; + return nullptr; +} + +ModelVolume *get_model_volume(const GLVolume &v, const ModelObject& object) { + if (v.volume_idx() < 0) + return nullptr; + + size_t volume_idx = static_cast(v.volume_idx()); + if (volume_idx >= object.volumes.size()) + return nullptr; + + return object.volumes[volume_idx]; +} + +ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects) +{ + if (v.object_idx() < 0) + return nullptr; + size_t objext_idx = static_cast(v.object_idx()); + if (objext_idx >= objects.size()) + return nullptr; + if (objects[objext_idx] == nullptr) + return nullptr; + return get_model_volume(v, *objects[objext_idx]); +} + +GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas) +{ + int hovered_id_signed = canvas.get_first_hover_volume_idx(); + if (hovered_id_signed < 0) + return nullptr; + + size_t hovered_id = static_cast(hovered_id_signed); + const GLVolumePtrs &volumes = canvas.get_volumes().volumes; + if (hovered_id >= volumes.size()) + return nullptr; + + return volumes[hovered_id]; +} + +GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas) +{ + const GLVolume *gl_volume = get_selected_gl_volume(canvas.get_selection()); + if (gl_volume == nullptr) return nullptr; + + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + for (GLVolume *v : gl_volumes) + if (v->composite_id == gl_volume->composite_id) return v; + return nullptr; +} + +ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model) { return get_model_object(gl_volume, model.objects); } + +ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects) +{ + if (gl_volume.object_idx() < 0) return nullptr; + size_t objext_idx = static_cast(gl_volume.object_idx()); + if (objext_idx >= objects.size()) return nullptr; + return objects[objext_idx]; +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model &model) { return get_model_instance(gl_volume, model.objects); } + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects) +{ + if (gl_volume.instance_idx() < 0) return nullptr; + ModelObject *object = get_model_object(gl_volume, objects); + return get_model_instance(gl_volume, *object); +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object) +{ + if (gl_volume.instance_idx() < 0) return nullptr; + size_t instance_idx = static_cast(gl_volume.instance_idx()); + if (instance_idx >= object.instances.size()) return nullptr; + return object.instances[instance_idx]; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6a904e5..2f48381 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -45,6 +45,7 @@ struct ThumbnailData; struct ThumbnailsParams; class ModelObject; class ModelInstance; +class TextInfo; class PrintObject; class Print; class SLAPrint; @@ -376,7 +377,8 @@ class GLCanvas3D SomethingNotShown, ObjectClashed, GCodeConflict, - ToolHeightOutside + ToolHeightOutside, + FilamentUnPrintableOnFirstLayer }; class RenderStats @@ -501,6 +503,7 @@ public: }; int GetHoverId(); + void set_ignore_left_up() { m_mouse.ignore_left_up = true; } private: bool m_is_dark = false; @@ -777,6 +780,7 @@ public: void set_show_world_axes(bool flag) { m_show_world_axes = flag; } void refresh_camera_scene_box(); + BoundingBoxf3 assembly_view_cur_bounding_box() const; BoundingBoxf3 volumes_bounding_box() const; BoundingBoxf3 scene_bounding_box() const; BoundingBoxf3 plate_scene_bounding_box(int plate_idx) const; @@ -908,8 +912,8 @@ public: std::vector& get_custom_gcode_per_print_z() { return m_gcode_viewer.get_custom_gcode_per_print_z(); } size_t get_gcode_extruders_count() { return m_gcode_viewer.get_extruders_count(); } - std::vector load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs); - std::vector load_object(const Model& model, int obj_idx); + std::vector load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs, bool lod_enabled); + std::vector load_object(const Model& model, int obj_idx, bool lod_enabled); void mirror_selection(Axis axis); @@ -946,6 +950,12 @@ public: Size get_canvas_size() const; Vec2d get_local_mouse_position() const; + // store opening position of menu + std::optional m_popup_menu_positon; // position of mouse right click + void set_popup_menu_position(const Vec2d &position) { m_popup_menu_positon = position; } + const std::optional &get_popup_menu_position() const { return m_popup_menu_positon; } + void clear_popup_menu_position() { m_popup_menu_positon.reset(); } + void set_tooltip(const std::string& tooltip); // the following methods add a snapshot to the undo/redo stack, unless the given string is empty @@ -1068,6 +1078,8 @@ public: m_sequential_print_clearance.set_polygons(polygons, height_polygons); } + bool can_sequential_clearance_show_in_gizmo(); + void update_sequential_clearance(); const Print* fff_print() const; @@ -1076,7 +1088,7 @@ public: void reset_old_size() { m_old_size = { 0, 0 }; } bool is_object_sinking(int object_idx) const; - + void apply_retina_scale(Vec2d &screen_coordinate) const; void _perform_layer_editing_action(wxMouseEvent* evt = nullptr); // Convert the screen space coordinate to an object space coordinate. @@ -1117,7 +1129,7 @@ private: void _render_bed(bool bottom, bool show_axes); void _render_bed_for_picking(bool bottom); //QDS: add part plate related logic - void _render_platelist(bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false) const; + void _render_platelist(bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true) const; void _render_plates_for_picking() const; //QDS: add outline drawing logic void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true); @@ -1147,7 +1159,7 @@ private: //void _render_view_toolbar() const; void _render_paint_toolbar() const; float _show_assembly_tooltip_information(float caption_max, float x, float y) const; - void _render_assemble_control() const; + void _render_assemble_control(); void _render_assemble_info() const; #if ENABLE_SHOW_CAMERA_TARGET void _render_camera_target() const; @@ -1220,6 +1232,20 @@ private: static std::vector> _parse_colors(const std::vector& colors); }; +const ModelVolume *get_model_volume(const GLVolume &v, const Model &model); +ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects); +ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects); +ModelVolume *get_model_volume(const GLVolume &v, const ModelObject &object); + +GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas); +GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas); + +ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model); +ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects); + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model &model); +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects); +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 82ea712..2241691 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -459,6 +459,20 @@ GLModel::~GLModel() if (mesh) { delete mesh; } } +size_t GLModel::get_vertices_count(int i) const { + if (m_render_data.empty() || i >= m_render_data.size()) { + return 0; + } + return m_render_data[i].vertices_count > 0 ? m_render_data[i].vertices_count : m_render_data[i].geometry.vertices_count(); +} + +size_t GLModel::get_indices_count(int i) const { + if (m_render_data.empty() || i >= m_render_data.size()) { + return 0; + } + return m_render_data[i].indices_count > 0 ? m_render_data[i].indices_count : m_render_data[i].geometry.indices_count(); +} + void GLModel::init_from(Geometry &&data, bool generate_mesh) { if (is_initialized()) { @@ -474,6 +488,7 @@ void GLModel::init_from(Geometry &&data, bool generate_mesh) m_render_data.clear(); m_render_data.push_back(RenderData()); m_render_data.back().indices_count = data.indices.size(); + m_render_data.back().vertices_count = data.vertices.size(); m_render_data.back().type = data.format.type; m_render_data.back().color = data.color.get_data(); if (generate_mesh) { @@ -623,22 +638,121 @@ bool GLModel::init_from_file(const std::string& filename) return true; } +bool GLModel::init_model_from_poly(const std::vector &triangles, float z, bool generate_mesh) +{ + if (triangles.empty() || triangles.size() % 3 != 0) + return false; + + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2}; + init_data.reserve_vertices(triangles.size()); + init_data.reserve_indices(triangles.size() / 3); + + Vec2f min = triangles.front(); + Vec2f max = min; + for (const Vec2f &v : triangles) { + min = min.cwiseMin(v).eval(); + max = max.cwiseMax(v).eval(); + } + + const Vec2f size = max - min; + if (size.x() <= 0.0f || size.y() <= 0.0f) + return false; + + Vec2f inv_size = size.cwiseInverse(); + inv_size.y() *= -1.0f; + + // vertices + indices + unsigned int vertices_counter = 0; + for (const Vec2f &v : triangles) { + const Vec3f p = {v.x(), v.y(), z}; + init_data.add_vertex(p, (Vec2f) (v - min).cwiseProduct(inv_size).eval()); + ++vertices_counter; + if (vertices_counter % 3 == 0) + init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + init_from(std::move(init_data), generate_mesh); + return true; +} + +bool GLModel::init_model_from_lines(const Lines &lines, float z, bool generate_mesh) +{ + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + init_data.reserve_vertices(2 * lines.size()); + init_data.reserve_indices(2 * lines.size()); + + for (const auto &l : lines) { + init_data.add_vertex(Vec3f(unscale(l.a.x()), unscale(l.a.y()), z)); + init_data.add_vertex(Vec3f(unscale(l.b.x()), unscale(l.b.y()), z)); + const unsigned int vertices_counter = (unsigned int) init_data.vertices_count(); + init_data.add_line(vertices_counter - 2, vertices_counter - 1); + } + init_from(std::move(init_data), generate_mesh); + return true; +} + +bool GLModel::init_model_from_lines(const Lines3 &lines, bool generate_mesh) +{ + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + init_data.reserve_vertices(2 * lines.size()); + init_data.reserve_indices(2 * lines.size()); + + for (const auto &l : lines) { + init_data.add_vertex(Vec3f(unscale(l.a.x()), unscale(l.a.y()), unscale(l.a.z()))); + init_data.add_vertex(Vec3f(unscale(l.b.x()), unscale(l.b.y()), unscale(l.b.z()))); + const unsigned int vertices_counter = (unsigned int) init_data.vertices_count(); + init_data.add_line(vertices_counter - 2, vertices_counter - 1); + } + init_from(std::move(init_data), generate_mesh); + + return true; +} + +bool GLModel::init_model_from_lines(const Line3floats &lines, bool generate_mesh) +{ + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + init_data.reserve_vertices(2 * lines.size()); + init_data.reserve_indices(2 * lines.size()); + + for (const auto &l : lines) { + init_data.add_vertex(l.a); + init_data.add_vertex(l.b); + const unsigned int vertices_counter = (unsigned int) init_data.vertices_count(); + init_data.add_line(vertices_counter - 2, vertices_counter - 1); + } + init_from(std::move(init_data), generate_mesh); + return true; +} + void GLModel::set_color(int entity_id, const std::array& color) { for (size_t i = 0; i < m_render_data.size(); ++i) { - if (entity_id == -1 || static_cast(i) == entity_id) + if (entity_id == -1 || static_cast(i) == entity_id) { m_render_data[i].color = color; + m_render_data[i].geometry.color = color; + } } } +void GLModel::set_color(const ColorRGBA &color) { + set_color(-1,color.get_data()); +} + void GLModel::reset() { for (RenderData& data : m_render_data) { // release gpu memory - if (data.ibo_id > 0) + if (data.ibo_id > 0) { glsafe(::glDeleteBuffers(1, &data.ibo_id)); - if (data.vbo_id > 0) + data.ibo_id = 0; + } + if (data.vbo_id > 0) { glsafe(::glDeleteBuffers(1, &data.vbo_id)); + data.vbo_id = 0; + } } m_render_data.clear(); @@ -646,6 +760,50 @@ void GLModel::reset() m_filename = std::string(); } +static GLenum get_primitive_mode(const GLModel::Geometry::Format &format) +{ + switch (format.type) { + case GLModel::PrimitiveType::Points: { + return GL_POINTS; + } + default: + case GLModel::PrimitiveType::Triangles: { + return GL_TRIANGLES; + } + case GLModel::PrimitiveType::TriangleStrip: { + return GL_TRIANGLE_STRIP; + } + case GLModel::PrimitiveType::TriangleFan: { + return GL_TRIANGLE_FAN; + } + case GLModel::PrimitiveType::Lines: { + return GL_LINES; + } + case GLModel::PrimitiveType::LineStrip: { + return GL_LINE_STRIP; + } + case GLModel::PrimitiveType::LineLoop: { + return GL_LINE_LOOP; + } + } +} + +static GLenum get_index_type(const GLModel::Geometry &data) +{ + switch (data.index_type) { + default: + case GLModel::Geometry::EIndexType::UINT: { + return GL_UNSIGNED_INT; + } + case GLModel::Geometry::EIndexType::USHORT: { + return GL_UNSIGNED_SHORT; + } + case GLModel::Geometry::EIndexType::UBYTE: { + return GL_UNSIGNED_BYTE; + } + } +} + void GLModel::render() const { GLShaderProgram* shader = wxGetApp().get_current_shader(); @@ -654,7 +812,7 @@ void GLModel::render() const // sends data to gpu if not done yet if (data.vbo_id == 0 || data.ibo_id == 0) { auto origin_data = const_cast(&data); - if (data.geometry.vertices_count() > 0 && data.geometry.indices_count() > 0 + if (data.geometry.vertices_count() > 0 && data.geometry.indices_count() > 0 && !send_to_gpu(*origin_data, data.geometry.vertices, data.geometry.indices)) continue; } @@ -701,6 +859,210 @@ void GLModel::render() const } } +void GLModel::render_geometry() { + render_geometry(0,std::make_pair(0, get_indices_count())); +} + +void GLModel::render_geometry(int i,const std::pair &range) +{ + if (range.second == range.first) return; + + GLShaderProgram *shader = wxGetApp().get_current_shader(); + if (shader == nullptr) return; + + auto &render_data = m_render_data[i]; + // sends data to gpu if not done yet + if (render_data.vbo_id == 0 || render_data.ibo_id == 0) { + if (render_data.geometry.vertices_count() > 0 && render_data.geometry.indices_count() > 0 && + !send_to_gpu(render_data, render_data.geometry.vertices, render_data.geometry.indices)) + return; + } + const Geometry &data = render_data.geometry; + + + const GLenum mode = get_primitive_mode(data.format); + const GLenum index_type = get_index_type(data); + + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + const bool tex_coord = Geometry::has_tex_coord(data.format); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, render_data.vbo_id)); + + int position_id = -1; + int normal_id = -1; + int tex_coord_id = -1; + + if (position) { + position_id = shader->get_attrib_location("v_position"); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, Geometry::position_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, + (const void *) Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(position_id)); + } + } + if (normal) { + normal_id = shader->get_attrib_location("v_normal"); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, Geometry::normal_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, + (const void *) Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + } + if (tex_coord) { + tex_coord_id = shader->get_attrib_location("v_tex_coord"); + if (tex_coord_id != -1) { + glsafe(::glVertexAttribPointer(tex_coord_id, Geometry::tex_coord_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, + (const void *) Geometry::tex_coord_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(tex_coord_id)); + } + } + + shader->set_uniform("uniform_color", data.color.get_data()); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, render_data.ibo_id)); + glsafe(::glDrawElements(mode, range.second - range.first, index_type, (const void *) (range.first * Geometry::index_stride_bytes(data)))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (tex_coord_id != -1) glsafe(::glDisableVertexAttribArray(tex_coord_id)); + if (normal_id != -1) glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) glsafe(::glDisableVertexAttribArray(position_id)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLModel::create_or_update_mats_vbo(unsigned int &vbo, const std::vector &mats) +{ // first bind + if (vbo>0) { + glsafe(::glDeleteBuffers(1, &vbo)); + vbo = 0; + } + std::vector out_mats; + out_mats.reserve(mats.size()); + for (size_t i = 0; i < mats.size(); i++) { + out_mats.emplace_back(mats[i].get_matrix().matrix().cast()); + } + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + auto one_mat_all_size = sizeof(float) * 16; + glBufferData(GL_ARRAY_BUFFER, mats.size() * one_mat_all_size, out_mats.data(), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void GLModel::bind_mats_vbo(unsigned int instance_mats_vbo, unsigned int instances_count, int location) +{ + if (instance_mats_vbo == 0 || instances_count == 0) { + return; + } + auto one_mat_all_size = sizeof(float) * 16; + auto one_mat_col_size = sizeof(float) * 4; + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instance_mats_vbo)); + for (unsigned int i = 0; i < instances_count; i++) { // set attribute pointers for matrix (4 times vec4) + glsafe(glEnableVertexAttribArray(location)); + glsafe(glVertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, one_mat_all_size, (void *) 0)); + glsafe(glEnableVertexAttribArray(location + 1)); + glsafe(glVertexAttribPointer(location + 1, 4, GL_FLOAT, GL_FALSE, one_mat_all_size, (void *) (one_mat_col_size))); + glsafe(glEnableVertexAttribArray(location + 2)); + glsafe(glVertexAttribPointer(location + 2, 4, GL_FLOAT, GL_FALSE, one_mat_all_size, (void *) (2 * one_mat_col_size))); + glsafe(glEnableVertexAttribArray(location + 3)); + glsafe(glVertexAttribPointer(location + 3, 4, GL_FLOAT, GL_FALSE, one_mat_all_size, (void *) (3 * one_mat_col_size))); + // Update the matrix every time after an object is drawn//useful + glsafe(glVertexAttribDivisor(location, 1)); + glsafe(glVertexAttribDivisor(location + 1, 1)); + glsafe(glVertexAttribDivisor(location + 2, 1)); + glsafe(glVertexAttribDivisor(location + 3, 1)); + } +} + +void GLModel::render_geometry_instance(unsigned int instance_mats_vbo, unsigned int instances_count) +{ + render_geometry_instance(instance_mats_vbo, instances_count,std::make_pair(0, get_indices_count())); +} + +void GLModel::render_geometry_instance(unsigned int instance_mats_vbo, unsigned int instances_count, const std::pair &range) +{ + if (instance_mats_vbo == 0 || instances_count == 0) { + return; + } + if (m_render_data.size() != 1) { return; } + GLShaderProgram *shader = wxGetApp().get_current_shader(); + if (shader == nullptr) return; + + auto &render_data = m_render_data[0]; + // sends data to gpu if not done yet + if (render_data.vbo_id == 0 || render_data.ibo_id == 0) { + if (render_data.geometry.vertices_count() > 0 && render_data.geometry.indices_count() > 0 && !send_to_gpu(render_data.geometry)) + return; + } + if (instance_mats_vbo == 0) { + return; + } + const Geometry &data = render_data.geometry; + + const GLenum mode = get_primitive_mode(data.format); + const GLenum index_type = get_index_type(data); + + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + const bool tex_coord = Geometry::has_tex_coord(data.format); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, render_data.vbo_id)); + + int position_id = -1; + int normal_id = -1; + int tex_coord_id = -1; + int instace_mats_id = -1; + if (position) { + position_id = shader->get_attrib_location("v_position"); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, Geometry::position_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, + (const void *) Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(position_id)); + } + } + if (normal) { + normal_id = shader->get_attrib_location("v_normal"); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, Geometry::normal_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, + (const void *) Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + } + if (tex_coord) { + tex_coord_id = shader->get_attrib_location("v_tex_coord"); + if (tex_coord_id != -1) { + glsafe(::glVertexAttribPointer(tex_coord_id, Geometry::tex_coord_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, + (const void *) Geometry::tex_coord_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(tex_coord_id)); + } + } + //glBindAttribLocation(shader->get_id(), 2, "instanceMatrix"); + //glBindAttribLocation(2, "instanceMatrix"); + //shader->bind(shaderProgram, 0, 'position'); + instace_mats_id = shader->get_attrib_location("instanceMatrix"); + if (instace_mats_id != -1) { + bind_mats_vbo(instance_mats_vbo, instances_count, instace_mats_id); + } + else { + return; + } + auto res = shader->set_uniform("uniform_color", render_data.color); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, render_data.ibo_id)); + glsafe(::glDrawElementsInstanced(mode, range.second - range.first, index_type, (const void *) (range.first * Geometry::index_stride_bytes(data)), instances_count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (instace_mats_id != -1) glsafe(::glDisableVertexAttribArray(instace_mats_id)); + if (tex_coord_id != -1) glsafe(::glDisableVertexAttribArray(tex_coord_id)); + if (normal_id != -1) glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) glsafe(::glDisableVertexAttribArray(position_id)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) const { if (instances_vbo == 0) @@ -778,7 +1140,59 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } -bool GLModel::send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices) const +bool GLModel::send_to_gpu(Geometry &geometry) +{ + if (m_render_data.size() != 1) { return false; } + auto& render_data = m_render_data[0]; + if (render_data.vbo_id > 0 || render_data.ibo_id > 0) { + assert(false); + return false; + } + + Geometry &data = render_data.geometry; + if (data.vertices.empty() || data.indices.empty()) { + assert(false); + return false; + } + + // vertices + glsafe(::glGenBuffers(1, &render_data.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, render_data.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, data.vertices_size_bytes(), data.vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + render_data.vertices_count = get_vertices_count(); + data.vertices = std::vector(); + + // indices + glsafe(::glGenBuffers(1, &render_data.ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, render_data.ibo_id)); + const size_t indices_count = data.indices.size(); + if (render_data.vertices_count <= 256) { + // convert indices to unsigned char to save gpu memory + std::vector reduced_indices(indices_count); + for (size_t i = 0; i < indices_count; ++i) { reduced_indices[i] = (unsigned char) data.indices[i]; } + data.index_type = Geometry::EIndexType::UBYTE; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_count * sizeof(unsigned char), reduced_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } else if (render_data.vertices_count <= 65536) { + // convert indices to unsigned short to save gpu memory + std::vector reduced_indices(indices_count); + for (size_t i = 0; i < data.indices.size(); ++i) { reduced_indices[i] = (unsigned short) data.indices[i]; } + data.index_type = Geometry::EIndexType::USHORT; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_count * sizeof(unsigned short), reduced_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } else { + data.index_type = Geometry::EIndexType::UINT; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices_size_bytes(), data.indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + render_data.indices_count = indices_count; + data.indices = std::vector(); + + return true; +} + +bool GLModel::send_to_gpu(RenderData &data, const std::vector &vertices, const std::vector &indices) const { if (data.vbo_id > 0 || data.ibo_id > 0) { assert(false); diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 1a341b2..c803205 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -4,6 +4,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/Color.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Geometry.hpp" #include #include @@ -22,7 +23,10 @@ namespace GUI { public: enum class PrimitiveType : unsigned char { + Points, Triangles, + TriangleStrip, + TriangleFan, Lines, LineStrip, LineLoop @@ -130,6 +134,7 @@ namespace GUI { PrimitiveType type; unsigned int vbo_id{0}; unsigned int ibo_id{0}; + size_t vertices_count{0}; size_t indices_count{0}; std::array color{1.0f, 1.0f, 1.0f, 1.0f}; }; @@ -165,6 +170,9 @@ namespace GUI { GLModel() = default; virtual ~GLModel(); + size_t get_vertices_count(int i = 0) const; + size_t get_indices_count(int i = 0) const; + TriangleMesh *mesh{nullptr}; void init_from(Geometry &&data, bool generate_mesh = false); @@ -173,12 +181,22 @@ namespace GUI { void init_from(const indexed_triangle_set& its); void init_from(const Polygons& polygons, float z); bool init_from_file(const std::string& filename); - + bool init_model_from_poly(const std::vector &triangles, float z, bool generate_mesh = false); + bool init_model_from_lines(const Lines &lines, float z, bool generate_mesh = false); + bool init_model_from_lines(const Lines3 &lines, bool generate_mesh = false); + bool init_model_from_lines(const Line3floats &lines, bool generate_mesh = false); // if entity_id == -1 set the color of all entities void set_color(int entity_id, const std::array& color); + void set_color(const ColorRGBA &color); void reset(); void render() const; + void render_geometry(); + void render_geometry(int i,const std::pair &range); + static void create_or_update_mats_vbo(unsigned int &vbo, const std::vector &mats); + void bind_mats_vbo(unsigned int instance_mats_vbo, unsigned int instances_count, int location); + void render_geometry_instance(unsigned int instance_mats_vbo, unsigned int instances_count); + void render_geometry_instance(unsigned int instance_mats_vbo, unsigned int instances_count, const std::pair &range); void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const; bool is_initialized() const { return !m_render_data.empty(); } @@ -187,6 +205,7 @@ namespace GUI { const std::string& get_filename() const { return m_filename; } private: + bool send_to_gpu(Geometry& geometry); bool send_to_gpu(RenderData &data, const std::vector &vertices, const std::vector &indices) const; }; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 9c1e936..3701eb5 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -59,7 +59,7 @@ bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilen // 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, + // 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); @@ -69,7 +69,7 @@ bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilen } bool valid = !sources[static_cast(EShaderType::Vertex)].empty() && !sources[static_cast(EShaderType::Fragment)].empty() && sources[static_cast(EShaderType::Compute)].empty(); - valid |= !sources[static_cast(EShaderType::Compute)].empty() && sources[static_cast(EShaderType::Vertex)].empty() && sources[static_cast(EShaderType::Fragment)].empty() && + valid |= !sources[static_cast(EShaderType::Compute)].empty() && sources[static_cast(EShaderType::Vertex)].empty() && sources[static_cast(EShaderType::Fragment)].empty() && sources[static_cast(EShaderType::Geometry)].empty() && sources[static_cast(EShaderType::TessEvaluation)].empty() && sources[static_cast(EShaderType::TessControl)].empty(); return valid ? init_from_texts(name, sources) : false; @@ -169,11 +169,15 @@ bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSourc glsafe(::glAttachShader(m_id, shader_ids[i])); } + if (boost::ends_with(name,"_instance")) { + glBindAttribLocation(m_id, 3, "instanceMatrix"); + } + glsafe(::glLinkProgram(m_id)); GLint params; glsafe(::glGetProgramiv(m_id, GL_LINK_STATUS, ¶ms)); if (params == GL_FALSE) { - // Linking failed. + // Linking failed. glsafe(::glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, ¶ms)); std::vector msg(params); glsafe(::glGetProgramInfoLog(m_id, params, ¶ms, msg.data())); @@ -364,13 +368,13 @@ int GLShaderProgram::get_attrib_location(const char* name) const // 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; + //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(this)->m_attrib_location_cache.push_back({ name, id }); + //const_cast(this)->m_attrib_location_cache.push_back({ name, id }); return id; } diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 059fc97..c9862b2 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -39,9 +39,10 @@ std::pair GLShadersManager::init() 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"}); + valid &= append_shader("flat", {"110/flat.vs", "110/flat.fs"}); + valid &= append_shader("flat_instance", {"110/flat_instance.vs", "110/flat.fs"}); // used to render printbed - valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); + valid &= append_shader("printbed", {"110/printbed.vs", "110/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" }); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 8b1e625..5b4aeaa 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -843,8 +843,11 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas case GLToolbarItem::Left: { item->do_left_action(); break; } case GLToolbarItem::Right: { item->do_right_action(); break; } } - - if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + if (item->get_continuous_click_flag()) { + item->set_state(GLToolbarItem::Hover); + parent.render(); + } + else if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled) && !item->get_continuous_click_flag()) { // the item may get disabled during the action, if not, set it back to normal state item->set_state(GLToolbarItem::Normal); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 3c67d08..ebcf25e 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -139,6 +139,7 @@ public: // mouse right click Option right; bool visible; + bool continuous_click{false}; VisibilityCallback visibility_callback; EnablingCallback enabling_callback; @@ -156,6 +157,7 @@ public: left = data.left; right = data.right; visible = data.visible; + continuous_click = data.continuous_click; visibility_callback = data.visibility_callback; enabling_callback = data.enabling_callback; image_data = data.image_data; @@ -225,6 +227,7 @@ public: bool update_enabled_state(); //QDS: GUI refactor: GLToolbar + bool get_continuous_click_flag() const { return m_data.continuous_click; } bool is_action() const { return m_type == Action; } bool is_action_with_text() const { return m_type == ActionWithText; } bool is_action_with_text_image() const { return m_type == ActionWithTextImage; } diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 85e1a7f..4d28d21 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -82,7 +82,7 @@ const std::string& shortkey_ctrl_prefix() { static const std::string str = #ifdef __APPLE__ - "⌘+" + "command+" //"⌘+" #else _u8L("Ctrl+") #endif @@ -94,7 +94,7 @@ const std::string& shortkey_alt_prefix() { static const std::string str = #ifdef __APPLE__ - "⌥+" + "option+"//"⌥+" #else "Alt+" #endif @@ -108,8 +108,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt try{ if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) { - ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); + auto vec_new = std::make_unique(std::vector{boost::any_cast(value)}); + config.option(opt_key)->set_at(vec_new.get(), opt_index, 0); return; } @@ -125,6 +125,17 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field) config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent)); break;} + case coFloatsOrPercents:{ + std::string str = boost::any_cast(value); + bool percent = false; + if (str.back() == '%') { + str.pop_back(); + percent = true; + } + double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field) + auto vec_new = std::make_unique(val, percent); + config.option(opt_key)->set_at(vec_new.get(), opt_index, opt_index); + break;} case coPercent: config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast(value))); break; @@ -134,13 +145,13 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt break; } case coPercents:{ - ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, opt_index); + auto vec_new = std::make_unique (boost::any_cast(value)); + config.option(opt_key)->set_at(vec_new.get(), opt_index, opt_index); break; } case coFloats:{ - ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, opt_index); + auto vec_new = std::make_unique(boost::any_cast(value)); + config.option(opt_key)->set_at(vec_new.get(), opt_index, opt_index); break; } case coString: @@ -165,8 +176,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.option(opt_key)->values = values; } else{ - ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); + auto vec_new = std::make_unique(boost::any_cast(value)); + config.option(opt_key)->set_at(vec_new.get(), opt_index, 0); } } break; @@ -174,15 +185,15 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast(value))); break; case coBools:{ - ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast(value) != 0 }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); + auto vec_new = std::make_unique(boost::any_cast(value) != 0); + config.option(opt_key)->set_at(vec_new.get(), opt_index, 0); break;} case coInt: config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast(value))); break; case coInts:{ - ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); + auto vec_new = std::make_unique(boost::any_cast(value)); + config.option(opt_key)->set_at(vec_new.get(), opt_index, 0); } break; case coEnum:{ @@ -193,9 +204,9 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt break; // QDS case coEnums:{ - ConfigOptionEnumsGeneric* vec_new = new ConfigOptionEnumsGeneric{ boost::any_cast(value) }; + auto vec_new = std::make_unique(std::vector{boost::any_cast(value)}); if (config.has(opt_key)) - config.option(opt_key)->set_at(vec_new, opt_index, 0); + config.option(opt_key)->set_at(vec_new.get(), opt_index, 0); } break; case coPoint:{ @@ -207,8 +218,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.option(opt_key)->values = boost::any_cast>(value); break; } - ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); + auto vec_new = std::make_unique(boost::any_cast(value)); + config.option(opt_key)->set_at(vec_new.get(), opt_index, 0); } break; case coNone: diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6c9e6f5..4ca70b3 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -144,6 +144,11 @@ using namespace std::literals; namespace pt = boost::property_tree; +struct StaticQIDILib +{ + static void reset(); +}; + namespace Slic3r { namespace GUI { @@ -175,7 +180,7 @@ void start_ping_test() for (int i = 0; i < output.size(); i++) { output_i = output[i].To8BitData(); output_temp = output_i.ToStdString(wxConvUTF8); - BOOST_LOG_TRIVIAL(info) << "ping qidilab:" << output_temp; + BOOST_LOG_TRIVIAL(info) << "ping qidi3d:" << output_temp; } //Get GateWay IP wxExecute("ping 192.168.0.1", output, wxEXEC_NODISABLE); @@ -1288,9 +1293,9 @@ void GUI_App::post_init() // This is ugly but I honestly found no better way to do it. // Neither wxShowEvent nor wxWindowCreateEvent work reliably. if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. - BOOST_LOG_TRIVIAL(info) << "before check_updates"; - this->check_updates(false); - BOOST_LOG_TRIVIAL(info) << "after check_updates"; + //BOOST_LOG_TRIVIAL(info) << "before check_updates"; + //this->check_updates(false); + //BOOST_LOG_TRIVIAL(info) << "after check_updates"; CallAfter([this] { bool cw_showed = this->config_wizard_startup(); @@ -1353,9 +1358,11 @@ void GUI_App::post_init() for (auto& it : boost::filesystem::directory_iterator(log_folder)) { auto temp_path = it.path(); try { - std::time_t lw_t = boost::filesystem::last_write_time(temp_path) ; - files_vec.push_back({ lw_t, temp_path.filename().string() }); - } catch (const std::exception &ex) { + if (it.status().type() == boost::filesystem::regular_file) { + std::time_t lw_t = boost::filesystem::last_write_time(temp_path) ; + files_vec.push_back({ lw_t, temp_path.filename().string() }); + } + } catch (const std::exception &) { } } std::sort(files_vec.begin(), files_vec.end(), []( @@ -1436,10 +1443,10 @@ std::string GUI_App::get_http_url(std::string country_code, std::string path) { std::string url; if (country_code == "US") { - url = "https://api.qidilab.com/"; + url = "https://api.qiditech.com/"; } else if (country_code == "CN") { - url = "https://api.qidilab.cn/"; + url = "https://api.qiditech.cn/"; } else if (country_code == "ENV_CN_DEV") { url = "https://api-dev.qidi-lab.com/"; @@ -1452,18 +1459,18 @@ std::string GUI_App::get_http_url(std::string country_code, std::string path) } else if (country_code == "NEW_ENV_DEV_HOST") { - url = "https://api-dev.qidilab.net/"; + url = "https://api-dev.qiditech.net/"; } else if (country_code == "NEW_ENV_QAT_HOST") { - url = "https://api-qa.qidilab.net/"; + url = "https://api-qa.qiditech.net/"; } else if (country_code == "NEW_ENV_PRE_HOST") { - url = "https://api-pre.qidilab.net/"; + url = "https://api-pre.qiditech.net/"; } else { - url = "https://api.qidilab.com/"; + url = "https://api.qiditech.com/"; } url += path.empty() ? "v1/iot-service/api/slicer/resource" : path; @@ -1490,15 +1497,15 @@ std::string GUI_App::get_model_http_url(std::string country_code) } else if (country_code == "NEW_ENV_DEV_HOST") { - url = "https://makerhub-dev.qidilab.net/"; + url = "https://makerhub-dev.qiditech.net/"; } else if (country_code == "NEW_ENV_QAT_HOST") { - url = "https://makerhub-qa.qidilab.net/"; + url = "https://makerhub-qa.qiditech.net/"; } else if (country_code == "NEW_ENV_PRE_HOST") { - url = "https://makerhub-pre.qidilab.net/"; + url = "https://makerhub-pre.qiditech.net/"; } else { url = "https://makerworld.com/"; @@ -1834,6 +1841,7 @@ void GUI_App::restart_networking() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, mainframe %1%")%mainframe; on_init_network(true); + StaticQIDILib::reset(); if(m_agent) { init_networking_callbacks(); m_agent->set_on_ssdp_msg_fn( @@ -1937,6 +1945,33 @@ void GUI_App::init_networking_callbacks() // GUI::wxGetApp().request_user_handle(online_login); // }); + m_agent->set_server_callback([this](std::string url, int status) { + + CallAfter([this]() { + if (!m_server_error_dialog) { + /*m_server_error_dialog->EndModal(wxCLOSE); + m_server_error_dialog->Destroy(); + m_server_error_dialog = nullptr;*/ + m_server_error_dialog = new NetworkErrorDialog(mainframe); + } + + if(plater()->get_select_machine_dialog() && plater()->get_select_machine_dialog()->IsShown()){ + return; + } + + if (m_server_error_dialog->m_show_again) { + return; + } + + if (m_server_error_dialog->IsShown()) { + return; + } + + m_server_error_dialog->ShowModal(); + }); + }); + + m_agent->set_on_server_connected_fn([this](int return_code, int reason_code) { if (m_is_closing) { return; @@ -2283,8 +2318,12 @@ void GUI_App::init_app_config() if (!boost::filesystem::exists(data_dir_path)) boost::filesystem::create_directory(data_dir_path); set_data_dir(data_dir); +#if defined(__WINDOWS__) // Change current dirtory of application + _chdir(encode_path((data_dir + "/log").c_str()).c_str()); +#else chdir(encode_path((data_dir + "/log").c_str()).c_str()); +#endif } else { m_datadir_redefined = true; } @@ -2313,6 +2352,8 @@ void GUI_App::init_app_config() std::string error = app_config->load(); if (!error.empty()) { // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ + << "Configuration file may be corrupted and is not able to be parsed.Please delete the file and try again."; throw Slic3r::RuntimeError( _u8L("QIDIStudio configuration file may be corrupted and is not able to be parsed." "Please delete the file and try again.") + @@ -2973,82 +3014,61 @@ void GUI_App::copy_network_if_available() { if (app_config->get("update_network_plugin") != "true") return; - std::string network_library, player_library, live555_library, network_library_dst, player_library_dst, live555_library_dst; std::string data_dir_str = data_dir(); - boost::filesystem::path data_dir_path(data_dir_str); + fs::path data_dir_path(data_dir_str); auto plugin_folder = data_dir_path / "plugins"; - auto cache_folder = data_dir_path / "ota"; - std::string changelog_file = cache_folder.string() + "/network_plugins.json"; + auto cache_folder = data_dir_path / "ota" / "plugins"; + //std::string changelog_file = cache_folder.string() + "/network_plugins.json"; #if defined(_MSC_VER) || defined(_WIN32) - network_library = cache_folder.string() + "/qidi_networking.dll"; - player_library = cache_folder.string() + "/QIDISource.dll"; - live555_library = cache_folder.string() + "/live555.dll"; - network_library_dst = plugin_folder.string() + "/qidi_networking.dll"; - player_library_dst = plugin_folder.string() + "/QIDISource.dll"; - live555_library_dst = plugin_folder.string() + "/live555.dll"; + const char* library_ext = ".dll"; #elif defined(__WXMAC__) - network_library = cache_folder.string() + "/libqidi_networking.dylib"; - player_library = cache_folder.string() + "/libQIDISource.dylib"; - live555_library = cache_folder.string() + "/liblive555.dylib"; - network_library_dst = plugin_folder.string() + "/libqidi_networking.dylib"; - player_library_dst = plugin_folder.string() + "/libQIDISource.dylib"; - live555_library_dst = plugin_folder.string() + "/liblive555.dylib"; + const char* library_ext = ".dylib"; #else - network_library = cache_folder.string() + "/libqidi_networking.so"; - player_library = cache_folder.string() + "/libQIDISource.so"; - live555_library = cache_folder.string() + "/liblive555.so"; - network_library_dst = plugin_folder.string() + "/libqidi_networking.so"; - player_library_dst = plugin_folder.string() + "/libQIDISource.so"; - live555_library_dst = plugin_folder.string() + "/liblive555.so"; + const char* library_ext = ".so"; #endif - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": checking network_library " << network_library << ", player_library " << player_library; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": checking network_library from ota directory"; if (!boost::filesystem::exists(plugin_folder)) { - BOOST_LOG_TRIVIAL(info)<< __FUNCTION__ << ": create directory "<set("update_network_plugin", "false"); + return; } - if (boost::filesystem::exists(player_library)) { - CopyFileResult cfr = copy_file(player_library, player_library_dst, error_message, false); - if (cfr != CopyFileResult::SUCCESS) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message; - return; + try { + std::string error_message; + for (auto& dir_entry : boost::filesystem::directory_iterator(cache_folder)) + { + const auto& path = dir_entry.path(); + std::string file_path = path.string(); + + if (boost::algorithm::iends_with(file_path, library_ext)) { + std::string file_name = path.filename().string(); + std::string dest_path = (plugin_folder / file_name).string(); + CopyFileResult cfr = copy_file(file_path, dest_path, error_message, false); + if (cfr != CopyFileResult::SUCCESS) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message; + return; + } + + static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; + fs::permissions(dest_path, perms); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying network library from" << file_path << " to " << dest_path << " successfully."; + } } - static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; - fs::permissions(player_library_dst, perms); - fs::remove(player_library); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying player library from" << player_library << " to " << player_library_dst<<" successfully."; + if (boost::filesystem::exists(cache_folder)) + fs::remove_all(cache_folder); } - - if (boost::filesystem::exists(live555_library)) { - CopyFileResult cfr = copy_file(live555_library, live555_library_dst, error_message, false); - if (cfr != CopyFileResult::SUCCESS) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message; - return; - } - - static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; - fs::permissions(live555_library_dst, perms); - fs::remove(live555_library); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying live555 library from" << live555_library << " to " << live555_library_dst<<" successfully."; + catch (...) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy plugins from ota"; } - if (boost::filesystem::exists(changelog_file)) - fs::remove(changelog_file); app_config->set("update_network_plugin", "false"); + + return; } bool GUI_App::on_init_network(bool try_backup) @@ -3236,8 +3256,6 @@ void GUI_App::update_label_colours_from_appconfig() void GUI_App::update_publish_status() { - mainframe->show_publish_button(has_model_mall()); - mainframe->m_webview->ResetWholePage(); } @@ -3511,13 +3529,30 @@ void GUI_App::link_to_network_check() if (country_code == "US") { - url = "https://status.qidilab.com"; + url = "https://status.qiditech.com"; } else if (country_code == "CN") { - url = "https://status.qidilab.cn"; + url = "https://status.qiditech.cn"; } else { - url = "https://status.qidilab.com"; + url = "https://status.qiditech.com"; + } + wxLaunchDefaultBrowser(url); +} + +void GUI_App::link_to_lan_only_wiki() +{ + std::string url; + std::string country_code = app_config->get_country_code(); + + if (country_code == "US") { + url = "https://wiki.qiditech.com/en/knowledge-sharing/enable-lan-mode"; + } + else if (country_code == "CN") { + url = "https://wiki.qiditech.com/zh/knowledge-sharing/enable-lan-mode"; + } + else { + url = "https://wiki.qiditech.com/en/knowledge-sharing/enable-lan-mode"; } wxLaunchDefaultBrowser(url); } @@ -3680,21 +3715,22 @@ void GUI_App::ShowUserGuide() { mainframe->refresh_plugin_tips(); // QDS: remove SLA related message } - } catch (std::exception &e) { + } catch (std::exception &) { // wxMessageBox(e.what(), "", MB_OK); } } -void GUI_App::ShowDownNetPluginDlg() { +void GUI_App::ShowDownNetPluginDlg(bool post_login) +{ try { auto iter = std::find_if(dialogStack.begin(), dialogStack.end(), [](auto dialog) { return dynamic_cast(dialog) != nullptr; }); if (iter != dialogStack.end()) return; - DownloadProgressDialog dlg(_L("Downloading QIDI Network Plug-in")); + DownloadProgressDialog dlg(_L("Downloading QIDI Network Plug-in"), post_login); dlg.ShowModal(); - } catch (std::exception &e) { + } catch (std::exception &) { ; } } @@ -3711,7 +3747,7 @@ void GUI_App::ShowUserLogin(bool show) login_dlg = new ZUserLogin(); } login_dlg->ShowModal(); - } catch (std::exception &e) { + } catch (std::exception &) { ; } } else { @@ -3744,7 +3780,7 @@ void GUI_App::ShowOnlyFilament() { // QDS: remove SLA related message } - } catch (std::exception &e) { + } catch (std::exception &) { // wxMessageBox(e.what(), "", MB_OK); } } @@ -3933,7 +3969,7 @@ void GUI_App::get_login_info() // GUI::wxGetApp().run_script_left(strJS); // } // else { - // m_agent->user_logout(); + // m_agent->user_logout(true); // std::string logout_cmd = m_agent->build_logout_cmd(); // wxString strJS = wxString::Format("window.postMessage(%s)", logout_cmd); // GUI::wxGetApp().run_script_left(strJS); @@ -4112,6 +4148,14 @@ std::string GUI_App::handle_web_request(std::string cmd) CallAfter([this] { get_login_info(); }); + // TODO: Fix home page not emit get_recent_projects on macOS + #ifdef __WXOSX__ + if (mainframe) { + if (mainframe->m_webview) { + mainframe->m_webview->SendRecentList(INT_MAX); + } + } + #endif } else if (command_str.compare("homepage_login_or_register") == 0) { @@ -4119,9 +4163,9 @@ std::string GUI_App::handle_web_request(std::string cmd) boost::optional ModelID = root.get_optional("makerworld_model_id"); if (ModelID.has_value()) { if (mainframe) { - if (mainframe->m_webview) - { - mainframe->m_webview->SetMakerworldModelID(ModelID.value()); + if (mainframe->m_webview) + { + mainframe->m_webview->SetMakerworldModelID(ModelID.value()); } } } @@ -4158,7 +4202,7 @@ std::string GUI_App::handle_web_request(std::string cmd) /*else if (command_str.compare("modelmall_model_advise_get") == 0) { if (mainframe && this->app_config->get("staff_pick_switch") == "true") { if (mainframe->m_webview) { - mainframe->m_webview->SendDesignStaffpick(has_model_mall()); + mainframe->m_webview->SendDesignStaffpick(has_model_mall()); } } }*/ @@ -4253,17 +4297,6 @@ std::string GUI_App::handle_web_request(std::string cmd) } } } - else if (command_str.compare("homepage_open_ccabin") == 0) { - if (root.get_child_optional("data") != boost::none) { - pt::ptree data_node = root.get_child("data"); - boost::optional path = data_node.get_optional("file"); - if (path.has_value()) { - std::string Fullpath = resources_dir() + "/web/homepage/model/" + path.value(); - - this->request_open_project(Fullpath); - } - } - } else if (command_str.compare("common_openurl") == 0) { boost::optional path = root.get_optional("url"); if (path.has_value()) { @@ -4300,19 +4333,70 @@ std::string GUI_App::handle_web_request(std::string cmd) if (mainframe && mainframe->m_webview) { mainframe->m_webview->OpenOneMakerlab(strUrl); } } - } - else if (command_str.compare("makerworld_model_open") == 0) + } + //y + // else if (command_str.compare("homepage_need_networkplugin") == 0) { + // bool post_login = true; + // if (mainframe) { + // if (mainframe->m_confirm_download_plugin_dlg == nullptr) { + // mainframe->m_confirm_download_plugin_dlg = new SecondaryCheckDialog(mainframe, wxID_ANY, _L("Install network plug-in"), SecondaryCheckDialog::ButtonStyle::ONLY_CONFIRM); + // mainframe->m_confirm_download_plugin_dlg->SetSize(wxSize(270, 158)); + // mainframe->m_confirm_download_plugin_dlg->update_text(_L("Please Install network plug-in before log in.")); + // mainframe->m_confirm_download_plugin_dlg->update_btn_label(_L("Install Network Plug-in"), _L("")); + + // mainframe->m_confirm_download_plugin_dlg->Bind(EVT_SECONDARY_CHECK_CONFIRM, [this, post_login](wxCommandEvent& e) { + // mainframe->m_confirm_download_plugin_dlg->Close(); + // ShowDownNetPluginDlg(post_login); + // return; + // }); + // } + // auto dlg_width = mainframe->m_confirm_download_plugin_dlg->GetSize(); + // int xPos = mainframe->GetRect().GetX() + (mainframe->GetSize().x - dlg_width.x) / 2; + // int yPos = mainframe->GetRect().GetY() + (mainframe->GetSize().y - dlg_width.y) / 2; + // mainframe->m_confirm_download_plugin_dlg->SetPosition(wxPoint(xPos, yPos)); + // mainframe->m_confirm_download_plugin_dlg->on_show(); + // } + // } + else if (command_str.compare("makerworld_model_open") == 0) { if (root.get_child_optional("model") != boost::none) { pt::ptree data_node = root.get_child("model"); boost::optional path = data_node.get_optional("url"); - if (path.has_value()) - { + if (path.has_value()) + { wxString realurl = from_u8(url_decode(path.value())); wxGetApp().request_model_download(realurl); } } } + else if (command_str.compare("homepage_online_search") == 0) { + if (root.get_child_optional("keyword") != boost::none) + { + std::string strKW = root.get_optional("keyword").value(); + + if (mainframe && mainframe->m_webview) + { + mainframe->m_webview->OpenMakerworldSearchPage(strKW); + } + } + } + else if (command_str.compare("homepage_printhistory_click") == 0) { + if (root.get_child_optional("taskid") != boost::none) { + int nTaskID = root.get("taskid"); + + if (mainframe && mainframe->m_webview) + { + mainframe->m_webview->SetPrintHistoryTaskID(nTaskID); + mainframe->m_webview->SwitchLeftMenu("printhistory"); + } + } + } + else if (command_str.compare("homepage_printhistory_get")==0) + { + if (mainframe && mainframe->m_webview) { + mainframe->m_webview->ShowUserPrintTask(true); + } + } } } catch (...) { @@ -4434,7 +4518,7 @@ void GUI_App::on_http_error(wxCommandEvent &evt) // Version limit if (code == HttpErrorVersionLimited) { if (!m_show_http_errpr_msgdlg) { - MessageDialog msg_dlg(nullptr, _L("The QIDI Studio version is too old to enable cloud service. Please download the latest version from QIDI Lab website."), "", wxAPPLY | wxOK); + MessageDialog msg_dlg(nullptr, _L("The QIDI Studio version is too old to enable cloud service. Please download the latest version from QIDI Tech website."), "", wxAPPLY | wxOK); m_show_http_errpr_msgdlg = true; auto modal_result = msg_dlg.ShowModal(); if (modal_result == wxOK || modal_result == wxCLOSE) { @@ -4579,8 +4663,10 @@ void GUI_App::reset_to_active() void GUI_App::check_update(bool show_tips, int by_user) { - if (version_info.version_str.empty()) return; - if (version_info.url.empty()) return; + if (version_info.version_str.empty() || version_info.url.empty()) { + check_beta_version(); + return; + } auto curr_version = Semver::parse(SLIC3R_VERSION); auto remote_version = Semver::parse(version_info.version_str); @@ -4598,8 +4684,12 @@ void GUI_App::check_update(bool show_tips, int by_user) } } else { wxGetApp().app_config->set("upgrade", "force_upgrade", false); - if (show_tips) + + if (show_tips) { this->no_new_version(); + } + + check_beta_version(); } } //B y41 @@ -4750,6 +4840,90 @@ void GUI_App::check_privacy_version(int online_login) }).perform(); } +void GUI_App::check_beta_version() +{ + if (app_config->get("enable_beta_version_update") != "true") { + return; + } + + std::string platform = "windows"; + +#ifdef __WINDOWS__ + platform = "windows"; +#endif +#ifdef __APPLE__ + platform = "macos"; +#endif +#ifdef __LINUX__ + platform = "linux"; +#endif + + std::string repoOwner = "qiditech"; // The owner of repository + std::string repoName = "QIDIStudio"; // The name of repository + //"https://api.github.com/repos/bqiditech/BQIDIStudio/releases" + std::string url = "https://api.github.com/repos/" + repoOwner + "/" + repoName + "/releases"; + + Slic3r::Http http = Slic3r::Http::get(url); + + http.header("accept", "application/json") + .timeout_connect(TIMEOUT_CONNECT) + .timeout_max(TIMEOUT_RESPONSE) + .on_complete([this, platform](std::string body, unsigned) { + try { + json versions = json::parse(body, nullptr, false); + for (auto version : versions){ + if (version.contains("prerelease") && version.contains("assets") && version.contains("tag_name") && version.contains("html_url")) { + bool is_beta_version = version["prerelease"]; + if (is_beta_version){ + std::regex version_regex(R"((\d+)\.(\d+)\.(\d+)\.(\d+))"); + std::smatch match; + std::string version_str = ""; + std::string tag_name = version["tag_name"]; + if (std::regex_search(tag_name, match, version_regex)) { + version_str = match[0]; + } + version_info.version_str = version_str; + auto assets = version["assets"]; + for (auto asset : assets){ + if (asset.contains("browser_download_url")){ + std::string url = asset["browser_download_url"]; + if ((platform == "windows" && url.find(".exe") != std::string::npos) + || (platform == "linux" && url.find(".AppImage") != std::string::npos) + || (platform == "macos" && url.find(".dmg") != std::string::npos)) + { + version_info.url = url; + version_info.description = "###" + std::string(version["html_url"]) + "###"; + version_info.force_upgrade = false; + CallAfter([this]() { + + if (version_info.version_str.empty() || version_info.url.empty()) { + return; + } + + auto curr_version = Semver::parse(SLIC3R_VERSION); + auto remote_version = Semver::parse(version_info.version_str); + if (curr_version && remote_version && (*remote_version > *curr_version)) { + GUI::wxGetApp().request_new_version(false); + } + }); + } + } + } + } + } + return; + } + } + catch (...) { + ; + } + }) + .on_error([this](std::string body, std::string error, unsigned int status) { + handle_http_error(status, body); + BOOST_LOG_TRIVIAL(error) << "check new version error" << body; + }).perform(); +} + void GUI_App::no_new_version() { wxCommandEvent* evt = new wxCommandEvent(EVT_SHOW_NO_NEW_VERSION); @@ -4887,7 +5061,7 @@ void GUI_App::sync_preset(Preset* preset) } } else { - BOOST_LOG_TRIVIAL(trace) << "[sync_preset]init: can not generate differed key-values"; + BOOST_LOG_TRIVIAL(info) << "[sync_preset]init: can not generate differed key-values and code: " << ret; result = 0; updated_info = "hold"; } @@ -4917,7 +5091,11 @@ void GUI_App::sync_preset(Preset* preset) } } else { - BOOST_LOG_TRIVIAL(trace) << "[sync_preset]create: can not generate differed preset"; + BOOST_LOG_TRIVIAL(info) << "[sync_preset]create: can not generate differed preset and code: " << ret; + if (ret == -2) { + result = 0; + updated_info = "hold"; + } } } else if (preset->sync_info.compare("update") == 0) { @@ -4944,8 +5122,9 @@ void GUI_App::sync_preset(Preset* preset) } else { - BOOST_LOG_TRIVIAL(trace) << "[sync_preset]update: can not generate differed key-values, we need to skip this preset "<< preset->name; + BOOST_LOG_TRIVIAL(info) << "[sync_preset]update: can not generate differed key-values, we need to skip this preset " << preset->name << " code: " << ret; result = 0; + if (ret == -2) updated_info = "hold"; } } else { @@ -6735,7 +6914,8 @@ void GUI_App::check_updates(const bool verbose) { PresetUpdater::UpdateResult updater_result; try { - updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); + //updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); + updater_result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::SHOW_TEXT_BOX); if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { mainframe->Close(); } @@ -6752,6 +6932,16 @@ void GUI_App::check_updates(const bool verbose) } } +void GUI_App::check_config_updates_from_updater() +{ + check_updates(false); +} + +void GUI_App::check_config_updates_from_menu() +{ + check_updates(true); +} + bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/) { return wxLaunchDefaultBrowser(url, flags); @@ -6837,8 +7027,6 @@ static bool del_win_registry(HKEY hkeyHive, const wchar_t *pszVar, const wchar_t return false; if (!bDidntExist) { - DWORD dwDisposition; - HKEY hkey; iRC = ::RegDeleteKeyExW(hkeyHive, pszVar, KEY_ALL_ACCESS, 0); if (iRC == ERROR_SUCCESS) { return true; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3b8830b..6d49bb8 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -84,7 +84,7 @@ class ParamsDialog; class HMSQuery; class ModelMallDialog; class PingCodeBindDialog; - +class NetworkErrorDialog; enum FileType { @@ -391,9 +391,10 @@ public: bool get_side_menu_popup_status(); void set_side_menu_popup_status(bool status); void link_to_network_check(); + void link_to_lan_only_wiki(); - const wxColour& get_label_clr_modified(){ return m_color_label_modified; } + const wxColour& get_label_clr_modified() { return m_color_label_modified; } const wxColour& get_label_clr_sys() { return m_color_label_sys; } const wxColour& get_label_clr_default() { return m_color_label_default; } const wxColour& get_window_default_clr(){ return m_color_window_default; } @@ -436,7 +437,7 @@ public: wxString transition_tridid(int trid_id); void ShowUserGuide(); - void ShowDownNetPluginDlg(); + void ShowDownNetPluginDlg(bool post_login = false); void ShowUserLogin(bool show = true); void SetOnlineLogin(bool status); void SetPresentChange(bool status); @@ -477,6 +478,7 @@ public: void check_update(bool show_tips, int by_user); void check_new_version(bool show_tips = false, int by_user = 0); + void check_beta_version(); void request_new_version(int by_user); void enter_force_upgrade(); void set_skip_version(bool skip = true); @@ -578,6 +580,8 @@ public: ModelMallDialog* m_mall_publish_dialog{ nullptr }; PingCodeBindDialog* m_ping_code_binding_dialog{ nullptr }; + NetworkErrorDialog* m_server_error_dialog { nullptr }; + void set_download_model_url(std::string url) {m_mall_model_download_url = url;} void set_download_model_name(std::string name) {m_mall_model_download_name = name;} std::string get_download_model_url() {return m_mall_model_download_url;} @@ -667,7 +671,8 @@ public: bool check_networking_version(); void cancel_networking_install(); void restart_networking(); - void check_config_updates_from_updater() { check_updates(false); } + void check_config_updates_from_updater(); + void check_config_updates_from_menu(); void update_versioninfo(QIDIVersion version); private: diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index cd50a3d..be60859 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -16,6 +16,8 @@ //QDS: add partplate related logic #include "PartPlate.hpp" +#include "Gizmos/GLGizmoSVG.hpp" + #include #include "slic3r/Utils/FixModelByWin10.hpp" #include "ParamsPanel.hpp" @@ -85,7 +87,7 @@ std::map> SettingsFactory::OBJECT_C {"enable_support", "",4},{"support_type", "",5},{"support_threshold_angle", "",6},{"support_on_build_plate_only", "",7}, {"support_filament", "",8},{"support_interface_filament", "",9},{"support_expansion", "",24},{"support_style", "",25}, //1.9.5 - {"tree_support_branch_angle", "",10}, {"tree_support_wall_count", "",11},//tree support + {"tree_support_branch_angle", "",10}, {"tree_support_wall_count", "",11},{"tree_support_branch_diameter_angle", "",11},//tree support {"support_top_z_distance", "",13},{"support_bottom_z_distance", "",12},{"support_base_pattern", "",14},{"support_base_pattern_spacing", "",15}, {"support_interface_top_layers", "",16},{"support_interface_bottom_layers", "",17},{"support_interface_spacing", "",18},{"support_bottom_interface_spacing", "",19}, {"support_object_xy_distance", "",20}, {"bridge_no_support", "",21},{"max_bridge_length", "",22},{"support_critical_regions_only", "",23},{"support_remove_small_overhang","",27}, @@ -96,7 +98,7 @@ std::map> SettingsFactory::OBJECT_C }; std::map> SettingsFactory::PART_CATEGORY_SETTINGS= - {{L("Quality"), {{"ironing_type", "", 8}, {"ironing_flow", "", 9}, {"ironing_spacing", "", 10}, {"ironing_speed", "", 11}, {"ironing_direction", "",12} + {{L("Quality"), {{"ironing_type", "", 8}, {"ironing_flow", "", 9}, {"ironing_spacing", "", 10}, {"ironing_inset", "", 11}, {"ironing_speed", "", 12}, {"ironing_direction", "",13} }}, { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", "",1},{"top_shell_thickness", "",1}, {"bottom_shell_layers", "",1}, {"bottom_shell_thickness", "",1}, {"sparse_infill_density", "",1}, @@ -279,24 +281,28 @@ wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, //------------------------------------- // Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important -#ifdef __WINDOWS__ -const std::vector> MenuFactory::ADD_VOLUME_MENU_ITEMS = { + +static const std::vector> ADD_VOLUME_MENU_ITEMS = { {L("Add part"), "menu_add_part" }, // ~ModelVolumeType::MODEL_PART {L("Add negative part"), "menu_add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME {L("Add modifier"), "menu_add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER {L("Add support blocker"), "menu_support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER {L("Add support enforcer"), "menu_support_enforcer"} // ~ModelVolumeType::SUPPORT_ENFORCER }; -#else -const std::vector> MenuFactory::ADD_VOLUME_MENU_ITEMS = { - {L("Add part"), "menu_add_part" }, // ~ModelVolumeType::MODEL_PART - {L("Add negative part"), "menu_add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME - {L("Add modifier"), "menu_add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER - {L("Add support blocker"), "menu_support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER - {L("Add support enforcer"), "menu_support_enforcer"} // ~ModelVolumeType::SUPPORT_ENFORCER -}; -#endif +// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important +static const constexpr std::array, 3> TEXT_VOLUME_ICONS{{ + // menu_item Name menu_item bitmap name + {L("Add text"), "add_text_part"}, // ~ModelVolumeType::MODEL_PART + {L("Add negative text"), "add_text_negative"}, // ~ModelVolumeType::NEGATIVE_VOLUME + {L("Add text modifier"), "add_text_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER +}}; +// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important +static const constexpr std::array, 3> SVG_VOLUME_ICONS{{ + {L("Add SVG part"), "svg_part"}, // ~ModelVolumeType::MODEL_PART + {L("Add negative SVG"), "svg_negative"}, // ~ModelVolumeType::NEGATIVE_VOLUME + {L("Add SVG modifier"), "svg_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER +}}; static Plater* plater() { @@ -434,14 +440,28 @@ std::vector MenuFactory::get_volume_bitmaps() { std::vector volume_bmps; volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size()); - for (auto item : ADD_VOLUME_MENU_ITEMS){ - if(!item.second.empty()){ - volume_bmps.push_back(create_scaled_bitmap(item.second)); - } + for (const auto &item : ADD_VOLUME_MENU_ITEMS) { + volume_bmps.push_back(create_scaled_bitmap(item.second)); } return volume_bmps; } +std::vector MenuFactory::get_text_volume_bitmaps() +{ + std::vector volume_bmps; + volume_bmps.reserve(TEXT_VOLUME_ICONS.size()); + for (const auto &item : TEXT_VOLUME_ICONS) volume_bmps.push_back(create_scaled_bitmap(item.second)); + return volume_bmps; +} + +std::vector MenuFactory::get_svg_volume_bitmaps() +{ + std::vector volume_bmps; + volume_bmps.reserve(SVG_VOLUME_ICONS.size()); + for (const auto &item : SVG_VOLUME_ICONS) volume_bmps.push_back(create_scaled_bitmap(item.second)); + return volume_bmps; +} + void MenuFactory::append_menu_item_set_visible(wxMenu* menu) { bool has_one_shown = false; @@ -468,6 +488,19 @@ void MenuFactory::append_menu_item_delete(wxMenu* menu) #endif } +void MenuFactory::append_menu_item_delete_all_cutter(wxMenu *menu) +{ +#ifdef __WINDOWS__ + append_menu_item( + menu, wxID_ANY, _L("Delete all cutter") + "\t" + _L("Del"), _L("Delete all cutter"), [](wxCommandEvent &) { plater()->remove_selected(); }, "menu_delete", nullptr, + []() { return plater()->can_delete(); }, m_parent); +#else + append_menu_item( + menu, wxID_ANY, _L("Delete all cutter") + "\tBackSpace", _L("Delete all cutter"), [](wxCommandEvent &) { plater()->remove_selected(); }, "", nullptr, + []() { return plater()->can_delete(); }, m_parent); +#endif +} + void MenuFactory::append_menu_item_edit_text(wxMenu *menu) { #ifdef __WINDOWS__ @@ -481,18 +514,49 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu) #endif } +void MenuFactory::append_menu_item_edit_svg(wxMenu *menu) +{ + wxString name = _L("Edit SVG"); + auto can_edit_svg = []() { + if (plater() == nullptr) return false; + const Selection &selection = plater()->get_selection(); + if (selection.volumes_count() != 1) return false; + const GLVolume *gl_volume = selection.get_first_volume(); + if (gl_volume == nullptr) return false; + const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects); + if (volume == nullptr) return false; + return volume->is_svg(); + }; + + if (menu != &m_svg_part_menu) { + const int menu_item_id = menu->FindItem(name); + if (menu_item_id != wxNOT_FOUND) menu->Destroy(menu_item_id); + if (!can_edit_svg()) return; + } + + wxString description = _L("Change SVG source file, projection, size, ..."); + std::string icon = "svg_part"; + auto open_svg = [](const wxCommandEvent &) { + GLGizmosManager &mng = plater()->get_view3D_canvas3D()->get_gizmos_manager(); + if (mng.get_current_type() == GLGizmosManager::Svg) mng.open_gizmo(GLGizmosManager::Svg); // close() and reopen - move to be visible + mng.open_gizmo(GLGizmosManager::Svg); + }; + append_menu_item(menu, wxID_ANY, name, description, open_svg, icon, nullptr, can_edit_svg, m_parent); +} + + wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) { auto sub_menu = new wxMenu; if (type != ModelVolumeType::INVALID) { append_menu_item(sub_menu, wxID_ANY, _L("Load..."), "", - [type](wxCommandEvent&) { obj_list()->load_subobject(type); }, "", menu); + [type](wxCommandEvent&) { obj_list()->load_subobject(type,true); }, "", menu); sub_menu->AppendSeparator(); } - std::vector icons = { "Cube", "Cylinder", "Sphere", "Cone", "Disc", "Torus", "rounded_rectangle" }; + std::vector icons = {"Cube", "Cylinder", "Sphere", "Cone", "double_tear_romboid_cylinder", "Disc", "Torus", "rounded_rectangle"}; size_t i = 0; - for (auto &item : {L("Cube"), L("Cylinder"), L("Sphere"), L("Cone"), L("Disc"),L("Torus"),L("Rounded Rectangle") }) + for (auto &item : {L("Cube"), L("Cylinder"), L("Sphere"), L("Cone"), L("Double Tear Romboid Cylinder"), L("Disc"), L("Torus"), L("Rounded Rectangle")}) { append_menu_item(sub_menu, wxID_ANY, _(item), "", [type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, Slic3r::resources_dir() + "/model/" + icons[i++] + ".png", menu); @@ -505,10 +569,60 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty sub_menu, wxID_ANY, _(item), "", [type, item](wxCommandEvent &) { obj_list()->load_generic_subobject(item, type); }, "", menu); } } - + append_menu_item_add_svg(sub_menu, type); return sub_menu; } +static void append_menu_itemm_add_(const wxString &name, GLGizmosManager::EType gizmo_type, wxMenu *menu, ModelVolumeType type, bool is_submenu_item) +{ + auto add_ = [type, gizmo_type](const wxCommandEvent & /*unnamed*/) { + const GLCanvas3D * canvas = plater()->canvas3D(); + const GLGizmosManager &mng = canvas->get_gizmos_manager(); + GLGizmoBase * gizmo_base = mng.get_gizmo(gizmo_type); + + ModelVolumeType volume_type = type; + // no selected object means create new object + if (volume_type == ModelVolumeType::INVALID) + volume_type = ModelVolumeType::MODEL_PART; + + auto screen_position = canvas->get_popup_menu_position(); + /* if (gizmo_type == GLGizmosManager::Emboss) {//todo + auto emboss = dynamic_cast(gizmo_base); + assert(emboss != nullptr); + if (emboss == nullptr) return; + if (screen_position.has_value()) { + emboss->create_volume(volume_type, *screen_position); + } else { + emboss->create_volume(volume_type); + } + } else*/ if (gizmo_type == GLGizmosManager::Svg) { + auto svg = dynamic_cast(gizmo_base); + assert(svg != nullptr); + if (svg == nullptr) return; + if (screen_position.has_value()) { + svg->create_volume(volume_type, *screen_position); + } else { + svg->create_volume(volume_type); + } + } + }; + + if (type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER || + type == ModelVolumeType::INVALID // cannot use gizmo without selected object + ) { + wxString item_name = wxString(is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": ") + name; + menu->AppendSeparator(); + auto def_icon_name = (gizmo_type == GLGizmosManager::Svg) ? "menu_obj_svg" : "menu_obj_text"; + const std::string icon_name = is_submenu_item ? def_icon_name : ADD_VOLUME_MENU_ITEMS[int(type)].second; + append_menu_item(menu, wxID_ANY, item_name, "", add_, icon_name, menu); + } +} + +void MenuFactory::append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item /* = true*/) +{ + append_menu_itemm_add_(_L("SVG"), GLGizmosManager::Svg, menu, type, is_submenu_item); +} + void MenuFactory::append_menu_items_add_volume(wxMenu* menu) { // Update "add" items(delete old & create new) settings popupmenu @@ -617,6 +731,10 @@ wxMenuItem* MenuFactory::append_menu_item_change_type(wxMenu* menu) return append_menu_item(menu, wxID_ANY, _L("Change type"), "", [](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu, []() { + const Selection &selection = plater()->canvas3D()->get_selection(); + if (selection.get_volume_idxs().size() != 1) { + return false; + } wxDataViewItem item = obj_list()->GetSelection(); return item.IsOk() || obj_list()->GetModel()->GetItemType(item) == itVolume; }, m_parent); @@ -1171,6 +1289,34 @@ void MenuFactory::create_part_menu() append_menu_item_per_object_settings(&m_part_menu); } +void MenuFactory::create_text_part_menu() +{ + wxMenu *menu = &m_text_part_menu; + + append_menu_item_edit_text(menu); + append_menu_item_delete(menu); + append_menu_item_fix_through_netfabb(menu); + append_menu_item_simplify(menu); + append_menu_items_mirror(menu); + menu->AppendSeparator(); + append_menu_item_per_object_settings(menu); + append_menu_item_change_type(menu); +} + +void MenuFactory::create_svg_part_menu() +{ + wxMenu *menu = &m_svg_part_menu; + + append_menu_item_edit_svg(menu); + append_menu_item_delete(menu); + append_menu_item_fix_through_netfabb(menu); + append_menu_item_simplify(menu); + append_menu_items_mirror(menu); + menu->AppendSeparator(); + append_menu_item_per_object_settings(menu); + append_menu_item_change_type(menu); +} + void MenuFactory::create_qdt_part_menu() { wxMenu* menu = &m_part_menu; @@ -1210,6 +1356,13 @@ void MenuFactory::create_qdt_assemble_part_menu() menu->AppendSeparator(); } +void MenuFactory::create_cut_cutter_menu() +{ + wxMenu *menu = &m_cut_cutter_menu; + append_menu_item_delete_all_cutter(menu); + append_menu_item_change_type(menu); +} + //QDS: add part plate related logic void MenuFactory::create_plate_menu() { @@ -1239,11 +1392,15 @@ void MenuFactory::create_plate_menu() [](wxCommandEvent&) { PartPlate* plate = plater()->get_partplate_list().get_selected_plate(); assert(plate); - plater()->set_prepare_state(Job::PREPARE_STATE_MENU); - plater()->arrange(); + + if (!plate->get_objects().empty() && !plater()->is_background_process_slicing()) + { + plater()->set_prepare_state(Job::PREPARE_STATE_MENU); + plater()->arrange(); + } }, "", nullptr, []() { - return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty(); + return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty() && !plater()->is_background_process_slicing(); }, m_parent); @@ -1252,12 +1409,15 @@ void MenuFactory::create_plate_menu() [](wxCommandEvent&) { PartPlate* plate = plater()->get_partplate_list().get_selected_plate(); assert(plate); - //QDS TODO call auto rotate for current plate - plater()->set_prepare_state(Job::PREPARE_STATE_MENU); - plater()->orient(); + if (!plate->get_objects().empty() && !plater()->is_background_process_slicing()) + { + //QDS TODO call auto rotate for current plate + plater()->set_prepare_state(Job::PREPARE_STATE_MENU); + plater()->orient(); + } }, "", nullptr, []() { - return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty(); + return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty() && !plater()->is_background_process_slicing(); }, m_parent); // delete current plate @@ -1297,12 +1457,12 @@ void MenuFactory::init(wxWindow* parent) //create_object_menu(); create_sla_object_menu(); //create_part_menu(); - + create_svg_part_menu(); create_qdt_object_menu(); create_qdt_part_menu(); create_qdt_assemble_object_menu(); create_qdt_assemble_part_menu(); - + create_cut_cutter_menu(); //QDS: add part plate related logic create_plate_menu(); @@ -1330,6 +1490,7 @@ wxMenu* MenuFactory::object_menu() append_menu_items_convert_unit(&m_object_menu); append_menu_items_flush_options(&m_object_menu); append_menu_item_invalidate_cut_info(&m_object_menu); + append_menu_item_edit_svg(&m_object_menu); append_menu_item_change_filament(&m_object_menu); { NetworkAgent* agent = GUI::wxGetApp().getAgent(); @@ -1342,6 +1503,8 @@ wxMenu* MenuFactory::sla_object_menu() { append_menu_items_convert_unit(&m_sla_object_menu); append_menu_item_settings(&m_sla_object_menu); + //append_menu_item_edit_text(&m_sla_object_menu); + append_menu_item_edit_svg(&m_object_menu); //update_menu_items_instance_manipulation(mtObjectSLA); return &m_sla_object_menu; } @@ -1358,6 +1521,27 @@ wxMenu* MenuFactory::part_menu() return &m_part_menu; } +wxMenu *MenuFactory::text_part_menu() +{ + append_menu_item_change_filament(&m_text_part_menu); + append_menu_item_per_object_settings(&m_text_part_menu); + + return &m_text_part_menu; +} + +wxMenu *MenuFactory::svg_part_menu() +{ + append_menu_item_change_filament(&m_svg_part_menu); + append_menu_item_per_object_settings(&m_svg_part_menu); + + return &m_svg_part_menu; +} + +wxMenu *MenuFactory::cut_connector_menu() +{ + return &m_cut_cutter_menu; +} + wxMenu* MenuFactory::instance_menu() { return &m_instance_menu; @@ -1537,13 +1721,20 @@ void MenuFactory::append_menu_item_center(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _L("Center") , "", [this](wxCommandEvent&) { + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(true); plater()->center_selection(); }, "", nullptr, []() { if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) return false; else { - Selection& selection = plater()->get_view3D_canvas3D()->get_selection(); + auto canvas3d = plater()->get_view3D_canvas3D(); + canvas3d->get_gizmos_manager().check_object_located_outside_plate(false); + if (canvas3d->get_gizmos_manager().get_object_located_outside_plate()) { //_outside_plate + return false; + } + Selection &selection = canvas3d->get_selection(); PartPlate* plate = plater()->get_partplate_list().get_selected_plate(); Vec3d model_pos = selection.get_bounding_box().center(); Vec3d center_pos = plate->get_center_origin(); diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index c372433..7f0ecdf 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -47,8 +47,9 @@ struct SettingsFactory class MenuFactory { public: - static const std::vector> ADD_VOLUME_MENU_ITEMS; static std::vector get_volume_bitmaps(); + static std::vector get_text_volume_bitmaps(); + static std::vector get_svg_volume_bitmaps(); MenuFactory(); ~MenuFactory() = default; @@ -66,6 +67,9 @@ public: wxMenu* object_menu(); wxMenu* sla_object_menu(); wxMenu* part_menu(); + wxMenu *text_part_menu(); + wxMenu *svg_part_menu(); + wxMenu* cut_connector_menu(); wxMenu* instance_menu(); wxMenu* layer_menu(); wxMenu* multi_selection_menu(); @@ -86,6 +90,9 @@ private: MenuWithSeparators m_object_menu; MenuWithSeparators m_part_menu; + MenuWithSeparators m_text_part_menu; + MenuWithSeparators m_svg_part_menu; + MenuWithSeparators m_cut_cutter_menu; MenuWithSeparators m_sla_object_menu; MenuWithSeparators m_default_menu; MenuWithSeparators m_instance_menu; @@ -112,6 +119,8 @@ private: void create_object_menu(); void create_sla_object_menu(); void create_part_menu(); + void create_text_part_menu(); + void create_svg_part_menu(); //QDS: add part plate related logic void create_plate_menu(); //QDS: add qdt object menu @@ -119,8 +128,10 @@ private: void create_qdt_part_menu(); void create_qdt_assemble_object_menu(); void create_qdt_assemble_part_menu(); + void create_cut_cutter_menu(); wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); + void append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item = true); void append_menu_items_add_volume(wxMenu* menu); wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); @@ -136,7 +147,7 @@ private: void append_menu_item_change_extruder(wxMenu* menu); void append_menu_item_set_visible(wxMenu* menu); void append_menu_item_delete(wxMenu* menu); - void append_menu_item_edit_text(wxMenu *menu); + void append_menu_item_delete_all_cutter(wxMenu *menu); void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); void append_menu_items_convert_unit(wxMenu* menu); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk" void append_menu_items_flush_options(wxMenu* menu); @@ -145,6 +156,8 @@ private: void append_menu_item_merge_parts_to_single_part(wxMenu *menu); void append_menu_items_mirror(wxMenu *menu); void append_menu_item_invalidate_cut_info(wxMenu *menu); + void append_menu_item_edit_text(wxMenu *menu); + void append_menu_item_edit_svg(wxMenu *menu); //void append_menu_items_instance_manipulation(wxMenu *menu); //void update_menu_items_instance_manipulation(MenuType type); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d63dd71..16c5efa 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -38,6 +38,7 @@ #include #endif /* __WXMSW__ */ #include "Gizmos/GLGizmoScale.hpp" +#include "Gizmos/GLGizmoMeshBoolean.hpp" namespace Slic3r { @@ -366,6 +367,7 @@ void ObjectList::create_objects_ctrl() m_columns_width.resize(colCount); m_columns_width[colName] = 22; + m_columns_width[colHeight] = 3; m_columns_width[colPrint] = 3; m_columns_width[colFilament] = 5; m_columns_width[colSupportPaint] = 3; @@ -387,6 +389,10 @@ void ObjectList::create_objects_ctrl() //name_col->SetBitmap(create_scaled_bitmap("organize", nullptr, FromDIP(18))); AppendColumn(name_col); + // column Variable height Property (Icon) of the view control: + AppendBitmapColumn(" ", colHeight, wxOSX ? wxDATAVIEW_CELL_EDITABLE : wxDATAVIEW_CELL_INERT, 3 * em, + wxALIGN_CENTER_HORIZONTAL, 0); + // column PrintableProperty (Icon) of the view control: AppendBitmapColumn(" ", colPrint, wxOSX ? wxDATAVIEW_CELL_EDITABLE : wxDATAVIEW_CELL_INERT, 3*em, wxALIGN_CENTER_HORIZONTAL, 0); @@ -866,6 +872,12 @@ void ObjectList::set_filament_column_hidden(const bool hide) const update_name_column_width(); } +void ObjectList::set_variable_height_column_hidden(const bool hide) const +{ + GetColumn(colHeight)->SetHidden(hide); + update_name_column_width(); +} + // QDS void ObjectList::set_color_paint_hidden(const bool hide) const { @@ -897,12 +909,15 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item) } else { const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetObject(item)); - if (item_type & itVolume) - { - const int volume_id = m_objects_model->GetVolumeIdByItem(item); - if (obj_idx < 0 || volume_id < 0) - return; - m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + if (item_type & itVolume){ + const int ui_volume_idx = m_objects_model->GetVolumeIdByItem(item); + if (obj_idx < 0 || ui_volume_idx < 0) + return; + auto &ui_and_3d_volume_map = m_objects_model->get_ui_and_3d_volume_map(); + if (ui_and_3d_volume_map.find(ui_volume_idx) == ui_and_3d_volume_map.end()) { + return; + } + m_config = &(*m_objects)[obj_idx]->volumes[ui_and_3d_volume_map[ui_volume_idx]]->config; } else if (item_type & itLayer) m_config = &get_item_config(item); @@ -1240,7 +1255,9 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me { const wxString title = col->GetTitle(); ColumnNumber col_num = (ColumnNumber)col->GetModelColumn(); - if (col_num == colPrint) + if (col_num == colHeight) + enable_layers_editing(); + else if (col_num == colPrint) toggle_printable_state(); else if (col_num == colSupportPaint) { ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); @@ -1336,20 +1353,27 @@ void ObjectList::show_context_menu(const bool evt_context_menu) if (volume_type != ModelVolumeType::MODEL_PART) return; } - menu = plater->assemble_multi_selection_menu(); } else { - menu = type & itPlate ? plater->plate_menu() : - type & itInstance ? plater->instance_menu() : - type & itVolume ? plater->part_menu() : - printer_technology() == ptFFF ? plater->object_menu() : - plater->sla_object_menu(); - plater->SetPlateIndexByRightMenuInLeftUI(-1); - if (type & itPlate) { - int plate_idx = -1; - const ItemType type0 = m_objects_model->GetItemType(item, plate_idx); - if (plate_idx >= 0) { - plater->SetPlateIndexByRightMenuInLeftUI(plate_idx); + if (type & itVolume) { + int obj_idx, vol_idx; + get_selected_item_indexes(obj_idx, vol_idx, item); + if (obj_idx < 0 || vol_idx < 0) return; + const ModelVolume *volume = object(obj_idx)->volumes[vol_idx]; + + menu = volume->is_svg() ? plater->svg_part_menu() : // ORCA fixes missing "Edit SVG" item for Add/Negative/Modifier SVG objects in object list + plater->part_menu(); + } + else { + menu = type & itPlate ? plater->plate_menu() : + type & itInstance ? plater->instance_menu() : + printer_technology() == ptFFF ? plater->object_menu() : + plater->sla_object_menu(); + plater->SetPlateIndexByRightMenuInLeftUI(-1); + if (type & itPlate) { + int plate_idx = -1; + const ItemType type0 = m_objects_model->GetItemType(item, plate_idx); + if (plate_idx >= 0) { plater->SetPlateIndexByRightMenuInLeftUI(plate_idx); } } } } @@ -1372,7 +1396,7 @@ void ObjectList::extruder_editing() wxPoint pos = this->get_mouse_position_in_control(); wxSize size = wxSize(column_width, -1); - pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5; + pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + GetColumn(colHeight)->GetWidth() + 5; pos.y -= GetTextExtent("m").y; apply_extruder_selector(&m_extruder_editor, this, "1", pos, size); @@ -2100,6 +2124,8 @@ static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf mesh = TriangleMesh(its_make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5)); else if (type_name == "Cone") mesh.ReadSTLFile((Slic3r::resources_dir() + "/model/cone.stl").c_str(), true, nullptr); + else if (type_name == "Double Tear Romboid Cylinder") + mesh.ReadSTLFile((Slic3r::resources_dir() + "/model/double_tear_romboid_cylinder.stl").c_str(), true, nullptr); else if (type_name == "Disc") mesh.ReadSTLFile((Slic3r::resources_dir() + "/model/Disc.stl").c_str(), true, nullptr); else if (type_name == "Torus") @@ -2280,8 +2306,7 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name new_object->ensure_on_bed(); //QDS init assmeble transformation - Geometry::Transformation t = new_object->instances[0]->get_transformation(); - new_object->instances[0]->set_assemble_transformation(t); + new_object->get_model()->set_assembly_pos(new_object); object_idxs.push_back(model.objects.size() - 1); #ifdef _DEBUG @@ -2345,6 +2370,55 @@ int ObjectList::load_mesh_part(const TriangleMesh &mesh, const wxString &name, c return mo->volumes.size() - 1; } +int GUI::ObjectList::add_text_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, const Transform3d &text_in_object_tran, bool is_temp) +{ + wxDataViewItem item = GetSelection(); + // we can add volumes for Object or Instance + if (!item || !(m_objects_model->GetItemType(item) & (itObject | itInstance))) + return -1; + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + + if (obj_idx < 0) + return -1; + + // Get object item, if Instance is selected + if (m_objects_model->GetItemType(item) & itInstance) + item = m_objects_model->GetItemById(obj_idx); + + ModelObject *mo = (*m_objects)[obj_idx]; + + Geometry::Transformation instance_transformation = mo->instances[0]->get_transformation(); + + ModelVolume *mv = mo->add_volume(mesh,false); + mv->set_transformation(text_in_object_tran); + mo->invalidate_bounding_box(); + mv->name = name.ToStdString(); + if (!text_info.m_text.empty()) + mv->set_text_info(text_info); + + if (!is_temp) { + std::vector volumes; + volumes.push_back(mv); + wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume *volume) { + return std::find(volumes.begin(), volumes.end(), volume) != volumes.end(); + }); + + wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object((size_t) obj_idx); + + if (items.size() > 1) { + m_selection_mode = smVolume; + m_last_selected_item = wxDataViewItem(nullptr); + } + select_items(items); + + selection_changed(); + } + + // QDS: notify partplate the modify + notify_instance_updated(obj_idx); + return mo->volumes.size() - 1; +} + //QDS bool ObjectList::del_object(const int obj_idx, bool refresh_immediately) { @@ -2636,6 +2710,7 @@ void ObjectList::split() for (const ModelVolume* volume : model_object->volumes) { const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name), volume->type(),// is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, + volume->is_svg(), get_warning_icon_name(volume->mesh().stats()), volume->config.has("extruder") ? volume->config.extruder() : 0, false); @@ -2966,7 +3041,7 @@ void ObjectList::boolean() Plater::TakeSnapshot snapshot(wxGetApp().plater(), "boolean"); ModelObject* object = (*m_objects)[obj_idxs.front()]; - TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1, [this](const std::string& msg) {return wxGetApp().notification_manager()->push_plater_error_notification(msg); }); + TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1, [this](const std::string &msg) { return wxGetApp().notification_manager()->push_plater_warning_notification(msg); }); // add mesh to model as a new object, keep the original object's name and config Model* model = object->get_model(); @@ -3391,7 +3466,16 @@ void ObjectList::part_selection_changed() m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_settings = true; - const ModelVolume *volume = (*m_objects)[obj_idx]->volumes[volume_id]; + ModelVolume *volume = (*m_objects)[obj_idx]->volumes[volume_id]; + + GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); + if (gizmos_mgr.get_current_type() == GLGizmosManager::EType::MeshBoolean) { + GLGizmoMeshBoolean* mesh_boolean = static_cast(gizmos_mgr.get_gizmo(GLGizmosManager::MeshBoolean)); + if (mesh_boolean->get_selecting_state() == MeshBooleanSelectingState::SelectSource) + mesh_boolean->set_src_volume(volume); + else if (mesh_boolean->get_selecting_state() == MeshBooleanSelectingState::SelectTool) + mesh_boolean->set_tool_volume(volume); + } enable_manipulation = !((*m_objects)[obj_idx]->is_cut() && (volume->is_cut_connector() || volume->is_model_part())); } else if (type & itInstance) { @@ -3538,6 +3622,18 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D #endif } +void ObjectList::update_variable_layer_obj_num(ObjectDataViewModelNode* obj_node, size_t layer_data_count) { + if (obj_node){ + if (obj_node->IsVaribaleHeight() == hiVariable && layer_data_count <= 4){ + m_variable_layer_obj_num--; + } + else if (obj_node->IsVaribaleHeight() == hiUnVariable && layer_data_count > 4){ + m_variable_layer_obj_num++; + } + GetColumn(colHeight)->SetHidden(m_variable_layer_obj_num == 0); + update_name_column_width(); + } +} void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/, bool added_object/* = false*/) { @@ -3576,6 +3672,14 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio } } + { + ObjectDataViewModelNode* obj_node = static_cast(item_obj.GetID()); + auto layer_data_count = model_object->layer_height_profile.get().size(); + update_variable_layer_obj_num(obj_node, layer_data_count); + // If the length of layer_height_profile is greater than 4, variable layer height is applied + m_objects_model->SetObjectVariableHeightState(layer_data_count > 4 ? hiVariable : hiUnVariable, item_obj); + } + { bool shows = m_objects_model->IsSupportPainted(item_obj); bool should_show = printer_technology() == ptFFF @@ -3738,6 +3842,8 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed, else m_objects_model->SetPrintableState(model_object->instances[0]->printable ? piPrintable : piUnprintable, obj_idx); + m_objects_model->SetObjectVariableHeightState(model_object->layer_height_profile.get().size() > 4 ? hiVariable : hiUnVariable, m_objects_model->GetItemById(obj_idx)); + // add settings to the object, if it has those add_settings_item(item, &model_object->config.get()); @@ -3803,6 +3909,7 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st object_item, from_u8(volume->name), volume->type(), + volume->is_svg(), get_warning_icon_name(volume->mesh().stats()), volume->config.has("extruder") ? volume->config.extruder() : 0, false); @@ -3855,10 +3962,16 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i if (type&itObject) { bool was_cut = object(obj_idx)->is_cut(); + // For variable layer height, the size of layer data is larger than 4 + bool vari_layer_height = (object(obj_idx)->layer_height_profile.get().size() > 4); if (del_object(obj_idx)) { delete_object_from_list(obj_idx); if (was_cut) update_lock_icons_for_model(); + if (vari_layer_height) { + m_variable_layer_obj_num--; + set_variable_height_column_hidden(m_variable_layer_obj_num == 0); + } } } else { @@ -3887,11 +4000,17 @@ void ObjectList::delete_from_model_and_list(const std::vector& it need_update = true; bool refresh_immediately = false; bool was_cut = object(item->obj_idx)->is_cut(); + // For variable layer height, the size of layer data is larger than 4 + bool vari_layer_height = (object(item->obj_idx)->layer_height_profile.get().size() > 4); if (!del_object(item->obj_idx, refresh_immediately)) return; m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx)); if (was_cut) update_lock_icons_for_model(); + if (vari_layer_height) { + m_variable_layer_obj_num--; + set_variable_height_column_hidden(m_variable_layer_obj_num == 0); + } } else { if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) @@ -4674,8 +4793,6 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); - if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) - selection.remove_all(); if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); @@ -4712,6 +4829,9 @@ void ObjectList::update_selections_on_canvas() // to avoid lost of some volumes in selection // check non-selected volumes only if selection mode wasn't changed // OR there is no single selection + if (is_connectors_item_selected() && !selection.is_empty()) { + return; + } if (selection.get_mode() == mode || !single_selection) volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs); Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add selected to list", UndoRedo::SnapshotType::Selection); @@ -4827,6 +4947,9 @@ void ObjectList::select_all() void ObjectList::select_item_all_children() { + if (wxGetApp().plater() && !wxGetApp().plater()->canvas3D()->get_gizmos_manager().is_allow_select_all()) { + return; + } wxDataViewItemArray sels; // There is no selection before OR some object is selected => select all objects @@ -5017,6 +5140,11 @@ void ObjectList::fix_cut_selection() m_prevent_list_events = false; } + auto canvas_type = wxGetApp().plater()->get_current_canvas3D()->get_canvas_type(); + if (canvas_type == GLCanvas3D::ECanvasType::CanvasView3D && is_connectors_item_selected()) { + Selection &selection = wxGetApp().plater()->get_view3D_canvas3D()->get_selection(); + selection.remove_all(); + } } bool ObjectList::fix_cut_selection(wxDataViewItemArray &sels) @@ -5072,9 +5200,16 @@ ModelVolume* ObjectList::get_selected_model_volume() void ObjectList::change_part_type() { ModelVolume* volume = get_selected_model_volume(); - if (!volume) - return; - + if (!volume) { + auto canvas_type = wxGetApp().plater()->get_current_canvas3D()->get_canvas_type(); + if (canvas_type == GLCanvas3D::ECanvasType::CanvasView3D && is_connectors_item_selected()) { + const Selection &selection = wxGetApp().plater()->get_view3D_canvas3D()->get_selection(); + const GLVolume * gl_volume = selection.get_first_volume(); + volume = get_model_volume(*gl_volume, selection.get_model()->objects); + } else { + return; + } + } const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; @@ -5093,8 +5228,19 @@ void ObjectList::change_part_type() } } - const wxString names[] = { _L("Part"), _L("Negative Part"), _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") }; - SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), wxArrayString(5, names), int(type)); + // ORCA: Fix crash when changing type of svg / text modifier + wxArrayString names; + names.Add(_L("Part")); + names.Add(_L("Negative Part")); + if (!volume->is_cut_connector()) { + names.Add(_L("Modifier")); + if (!volume->is_svg()) { + names.Add(_L("Support Blocker")); + names.Add(_L("Support Enforcer")); + } + } + + SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), names, int(type)); auto new_type = ModelVolumeType(dlg.GetSingleChoiceIndex()); if (new_type == type || new_type == ModelVolumeType::INVALID) @@ -5527,6 +5673,7 @@ void ObjectList::msw_rescale() const int em = wxGetApp().em_unit(); GetColumn(colName )->SetWidth(20 * em); + GetColumn(colHeight)->SetWidth(3 * em); GetColumn(colPrint )->SetWidth( 3 * em); GetColumn(colFilament)->SetWidth( 5 * em); // QDS @@ -5819,6 +5966,7 @@ void ObjectList::reload_all_plates(bool notify_partplate) wxGetApp().plater()->update(); // update printable states on canvas wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_objects(obj_idxs); + ppl.update_plate_trans(ppl.get_plate_count()); } void ObjectList::on_plate_selected(int plate_index) @@ -5943,6 +6091,26 @@ void ObjectList::toggle_printable_state() wxGetApp().plater()->reload_paint_after_background_process_apply(); } +void ObjectList::enable_layers_editing() +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.IsEmpty()) + return; + + wxDataViewItem frst_item = sels[0]; + + ItemType type = m_objects_model->GetItemType(frst_item); + if (!(type & itObject)) + return; + //take_snapshot(""); + + auto view3d = wxGetApp().plater()->get_view3D_canvas3D(); + if (view3d != nullptr && m_objects_model->IsVariableHeight(frst_item)){ + view3d->enable_layers_editing(true); + } +} + ModelObject* ObjectList::object(const int obj_idx) const { if (obj_idx < 0) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 57f1157..5a74fd4 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -167,6 +167,7 @@ private: ObjectDataViewModel *m_objects_model{ nullptr }; ModelConfig *m_config {nullptr}; std::vector *m_objects{ nullptr }; + size_t m_variable_layer_obj_num = 0; BitmapComboBox *m_extruder_editor { nullptr }; @@ -224,6 +225,8 @@ public: void update_filament_colors(); // show/hide "Extruder" column for Objects List void set_filament_column_hidden(const bool hide) const; + // show/hide variable height column for Objects List + void set_variable_height_column_hidden(const bool hide) const; // QDS void set_color_paint_hidden(const bool hide) const; void set_support_paint_hidden(const bool hide) const; @@ -288,6 +291,7 @@ public: // QDS void switch_to_object_process(); int load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp); + int add_text_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info,const Transform3d& text_in_object_tran, bool is_temp); bool del_object(const int obj_idx, bool refresh_immediately = true); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); @@ -416,6 +420,7 @@ public: void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); void update_object_list_by_printer_technology(); void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr, bool added_object = false); + void update_variable_layer_obj_num(ObjectDataViewModelNode* obj_node, size_t layer_data_count); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); void instances_to_separated_objects(const int obj_idx); @@ -440,6 +445,7 @@ public: //update printable state for item from objects model void update_printable_state(int obj_idx, int instance_idx); void toggle_printable_state(); + void enable_layers_editing(); //QDS: remove const qualifier void set_extruder_for_selected_items(const int extruder); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index f48eea4..918fcb0 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -387,7 +387,7 @@ void ObjectSettings::update_config_values(ModelConfig* config) //QDS: change local config to DynamicPrintConfig ConfigManipulation config_manipulation(load_config, toggle_field, nullptr, nullptr, &(config->get())); - // QDS: whether the preset is QIDI Lab printer + // QDS: whether the preset is QIDI Tech printer PresetBundle &preset_bundle = *wxGetApp().preset_bundle; bool is_QDT_printer = preset_bundle.printers.get_edited_preset().is_qdt_vendor_preset(&preset_bundle); config_manipulation.set_is_QDT_Printer(is_QDT_printer); diff --git a/src/slic3r/GUI/GUI_ObjectTableSettings.cpp b/src/slic3r/GUI/GUI_ObjectTableSettings.cpp index c2796e5..57b0975 100644 --- a/src/slic3r/GUI/GUI_ObjectTableSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectTableSettings.cpp @@ -298,7 +298,7 @@ bool ObjectTableSettings::update_settings_list(bool is_object, bool is_multiple_ if (line) line->toggle_visible = toggle; }; ConfigManipulation config_manipulation(nullptr, toggle_field, toggle_line, nullptr, &m_current_config); - // QDS: whether the preset is QIDI Lab printer + // QDS: whether the preset is QIDI Tech printer PresetBundle &preset_bundle = *wxGetApp().preset_bundle; bool is_QDT_printer = preset_bundle.printers.get_edited_preset().is_qdt_vendor_preset(&preset_bundle); config_manipulation.set_is_QDT_Printer(is_QDT_printer); @@ -403,7 +403,7 @@ void ObjectTableSettings::update_config_values(bool is_object, ModelObject* obje }; ConfigManipulation config_manipulation(nullptr, toggle_field, toggle_line, nullptr, &m_current_config); - // QDS: whether the preset is QIDI Lab printer + // QDS: whether the preset is QIDI Tech printer PresetBundle &preset_bundle = *wxGetApp().preset_bundle; bool is_QDT_printer = preset_bundle.printers.get_edited_preset().is_qdt_vendor_preset(&preset_bundle); config_manipulation.set_is_QDT_Printer(is_QDT_printer); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index e3afba6..b2c4a13 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -734,8 +734,8 @@ void Preview::load_print_as_fff(bool keep_z_range, bool only_gcode) //QDS show sliders show_moves_sliders(); - //QDS: turn off shells for preview - m_canvas->set_shells_on_previewing(false); + //QDS: keep shell preview on or not by app_config + m_canvas->set_shells_on_previewing(wxGetApp().app_config->get_bool("show_shells_in_preview")); Refresh(); zs = m_canvas->get_gcode_layers_zs(); //QDS: add m_loaded_print logic diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index 658d9b2..5151e50 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -12,6 +12,7 @@ #include #include "GLGizmosCommon.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/format.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/AppConfig.hpp" #include "../GUI/MsgDialog.hpp" @@ -479,6 +480,11 @@ void GLGizmoAdvancedCut::on_set_state() // Reset m_cut_z on gizmo activation if (get_state() == On) { + const Selection &selection = m_parent.get_selection(); + if (selection.is_empty()) {//check selection again + close(); + return; + } m_hover_id = -1; m_connectors_editing = false; @@ -515,6 +521,13 @@ void GLGizmoAdvancedCut::on_set_state() } } +void GLGizmoAdvancedCut::close() +{//close gizmo == open it again + auto &mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() == GLGizmosManager::Cut) + mng.open_gizmo(GLGizmosManager::Cut); +} + bool GLGizmoAdvancedCut::on_is_activable() const { const Selection &selection = m_parent.get_selection(); @@ -524,7 +537,7 @@ bool GLGizmoAdvancedCut::on_is_activable() const if (const ModelObject *mo = wxGetApp().plater()->model().objects[object_idx]; mo->is_cut() && mo->volumes.size() == 1) { const ModelVolume *volume = mo->volumes[0]; - if (volume->is_cut_connector() && volume->cut_info.connector_type == CutConnectorType::Dowel) + if (volume->is_cut_connector() && volume->cut_info.connector_type == CutConnectorType::Dowel) return false; } @@ -674,9 +687,10 @@ void GLGizmoAdvancedCut::on_render() if (m_part_selection) { if (!m_connectors_editing) { - if (m_is_dragging == false) { m_part_selection->part_render(nullptr); } - } else - m_part_selection->part_render(&m_plane_normal); + if (m_is_dragging == false) { m_part_selection->part_render(nullptr,nullptr); } + } else { + m_part_selection->part_render(&m_plane_center, &m_plane_normal); + } } if (!m_connectors_editing) { render_cut_plane_and_grabbers(); @@ -701,11 +715,6 @@ void GLGizmoAdvancedCut::on_render_for_picking() GLGizmoRotate3D::on_render_for_picking(); BoundingBoxf3 box = m_parent.get_selection().get_bounding_box(); -#if ENABLE_FIXED_GRABBER - float mean_size = (float) (GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float) ((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif // pick grabber { color = picking_color_component(0); @@ -713,14 +722,14 @@ void GLGizmoAdvancedCut::on_render_for_picking() m_move_z_grabber.color[1] = color[1]; m_move_z_grabber.color[2] = color[2]; m_move_z_grabber.color[3] = color[3]; - m_move_z_grabber.render_for_picking(mean_size); + m_move_z_grabber.render_for_picking(); if (m_cut_mode == CutMode::cutTongueAndGroove) { color = picking_color_component(1); m_move_x_grabber.color[0] = color[0]; m_move_x_grabber.color[1] = color[1]; m_move_x_grabber.color[2] = color[2]; m_move_x_grabber.color[3] = color[3]; - m_move_x_grabber.render_for_picking(mean_size); + m_move_x_grabber.render_for_picking(); } } @@ -837,6 +846,39 @@ void update_object_cut_id(CutObjectBase &cut_id, ModelObjectCutAttributes attrib } } +static void check_objects_after_cut(const ModelObjectPtrs &objects) +{ + std::vector err_objects_names; + std::vector err_objects_idxs; + int obj_idx{0}; + for (const ModelObject *object : objects) { + std::vector connectors_names; + connectors_names.reserve(object->volumes.size()); + for (const ModelVolume *vol : object->volumes) + if (vol->cut_info.is_connector) connectors_names.push_back(vol->name); + const size_t connectors_count = connectors_names.size(); + sort_remove_duplicates(connectors_names); + if (connectors_count != connectors_names.size()) err_objects_names.push_back(object->name); + + // check manifol/repairs + auto stats = object->get_object_stl_stats(); + if (!stats.manifold() || stats.repaired()) err_objects_idxs.push_back(obj_idx); + obj_idx++; + } + + auto plater = wxGetApp().plater(); + if (!err_objects_names.empty()) { + wxString names = from_u8(err_objects_names[0]); + for (size_t i = 1; i < err_objects_names.size(); i++) names += ", " + from_u8(err_objects_names[i]); + WarningDialog(plater, format_wxstr("Objects(%1%) have duplicated connectors. " + "Some connectors may be missing in slicing result.\n" + "Please report to QIDISudio team in which scenario this issue happened.\n" + "Thank you.", + names)) + .ShowModal(); + } +} + void synchronize_model_after_cut(Model &model, const CutObjectBase &cut_id) { for (ModelObject *obj : model.objects) @@ -902,6 +944,7 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) const ModelObjectPtrs &new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection->get_cut_parts(), dowels_count) : cut_with_groove ? cut.perform_with_groove(m_groove, m_rotate_matrix) : cut.perform_with_plane(); + check_objects_after_cut(new_objects);// Fix for #11487 - Cut Connectors Broken when assigning part to other side // fix_non_manifold_edges #ifdef HAS_WIN10SDK if (is_windows10()) { @@ -1161,8 +1204,8 @@ void GLGizmoAdvancedCut::render_cut_plane_and_grabbers() // QDS set to fixed size grabber // float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - float fullsize = 8.0f; - if (GLGizmoBase::INV_ZOOM > 0) { fullsize = m_move_z_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM; } + float fullsize = get_grabber_size(); + GLModel &cube_z = m_move_z_grabber.get_cube(); GLModel &cube_x = m_move_x_grabber.get_cube(); if (is_render_z_grabber) { @@ -1648,6 +1691,22 @@ void GLGizmoAdvancedCut::process_contours() toggle_model_objects_visibility(); } +void GLGizmoAdvancedCut::render_flip_plane_button(bool disable_pred /*=false*/) +{ + ImGui::SameLine(); + + if (m_hover_id == c_plate_move_id) + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); + + m_imgui->disabled_begin(disable_pred); + if (m_imgui->button(_L("Flip cut plane"))) + flip_cut_plane(); + m_imgui->disabled_end(); + + if (m_hover_id == c_plate_move_id) + ImGui::PopStyleColor(); +} + void GLGizmoAdvancedCut::toggle_model_objects_visibility(bool show_in_3d) { if (m_part_selection && m_part_selection->valid() && show_in_3d == false && (m_is_dragging == false || m_connectors_editing)) // QDT @@ -2168,6 +2227,8 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float reset_connectors(); m_imgui->disabled_end(); + render_flip_plane_button(m_connectors_editing && connectors.empty()); + m_imgui->text(_L("Type")); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.00f, 0.00f, 1.00f)); bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); @@ -2529,14 +2590,18 @@ PartSelection::PartSelection( const ModelVolumePtrs &volumes = model_object()->volumes; // split to parts - for (int id = int(volumes.size()) - 1; id >= 0; id--) - if (volumes[id]->is_splittable()) volumes[id]->split(1); + for (int id = int(volumes.size()) - 1; id >= 0; id--) { + auto look = volumes[id]->is_model_part(); + if (volumes[id]->is_splittable() && volumes[id]->is_model_part()) // we have to split just solid volumes + volumes[id]->split(1); + } const Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset(); int i = 0; m_cut_parts.resize(volumes.size()); for (const ModelVolume *volume : volumes) { assert(volume != nullptr); + m_cut_parts[i].is_modifier = !volume->is_model_part(); m_cut_parts[i].is_up_part = false; if (m_cut_parts[i].raycaster) { delete m_cut_parts[i].raycaster; } m_cut_parts[i].raycaster = new MeshRaycaster(volume->mesh()); @@ -2613,11 +2678,13 @@ PartSelection::PartSelection(const ModelObject *object, int instance_idx_in) : m m_cut_parts.resize(volumes.size()); for (const ModelVolume *volume : volumes) { assert(volume != nullptr); + m_cut_parts[i].is_modifier = !volume->is_model_part(); if (m_cut_parts[i].raycaster) { delete m_cut_parts[i].raycaster; } m_cut_parts[i].raycaster = new MeshRaycaster(volume->mesh()); m_cut_parts[i].glmodel.reset(); m_cut_parts[i].glmodel.init_from(volume->mesh_ptr()->its); m_cut_parts[i].trans = Geometry::translation_transform(inst_offset) * model_object()->volumes[i]->get_matrix(); + // Now check whether this part is below or above the plane. m_cut_parts[i].is_up_part = volume->is_from_upper(); i++; } @@ -2625,19 +2692,37 @@ PartSelection::PartSelection(const ModelObject *object, int instance_idx_in) : m m_valid = true; } -void PartSelection::part_render(const Vec3d *normal) +void PartSelection::part_render(const Vec3d *cut_center, const Vec3d *normal) { if (!valid()) return; const Camera &camera = wxGetApp().plater()->get_camera(); - const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05; glEnable(GL_DEPTH_TEST); for (size_t id = 0; id < m_cut_parts.size(); ++id) { // m_parts.size() test - if (normal && ((is_looking_forward && m_cut_parts[id].is_up_part) || (!is_looking_forward && !m_cut_parts[id].is_up_part))) - continue; - GLGizmoBase::render_glmodel(m_cut_parts[id].glmodel, m_cut_parts[id].is_up_part ? UPPER_PART_COLOR.get_data() : LOWER_PART_COLOR.get_data(), m_cut_parts[id].trans); + bool is_looking_forward = true; + auto is_at_normal_dir = false; + if (cut_center && normal) { + auto part_center =m_cut_parts[id].trans * m_cut_parts[id].glmodel.get_bounding_box().center(); + is_at_normal_dir=(*cut_center - part_center).normalized().dot(*normal) > 0 ; + Vec3d valid_normal = is_at_normal_dir ? *normal : -*normal; + is_looking_forward = camera.get_dir_forward().dot(valid_normal) < 0.05; + } + if (cut_center) { + if (!is_looking_forward) + continue; + } + if (m_cut_parts[id].is_modifier) { + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + } + GLGizmoBase::render_glmodel(m_cut_parts[id].glmodel, + m_cut_parts[id].is_modifier ? MODIFIER_COLOR.get_data() : + (m_cut_parts[id].is_up_part ? UPPER_PART_COLOR.get_data() : LOWER_PART_COLOR.get_data()), + m_cut_parts[id].trans); + if (m_cut_parts[id].is_modifier) + glsafe(::glDisable(GL_BLEND)); } } @@ -2662,13 +2747,13 @@ bool PartSelection::is_one_object() const // flawlessly. Because it is currently not always so for self-intersecting // objects, let's better check the parts itself: if (m_cut_parts.size() < 2) return true; - return std::all_of(m_cut_parts.begin(), m_cut_parts.end(), [this](const PartPara &part) { return part.is_up_part == m_cut_parts.front().is_up_part; }); + return std::all_of(m_cut_parts.begin(), m_cut_parts.end(), [this](const PartPara &part) { return part.is_modifier || part.is_up_part == m_cut_parts.front().is_up_part; }); } std::vector PartSelection::get_cut_parts() { std::vector parts; - for (const auto &part : m_cut_parts) parts.push_back({part.is_up_part, false}); + for (const auto &part : m_cut_parts) parts.push_back({part.is_up_part, part.is_modifier}); return parts; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp index 3a62d51..f8a58b5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp @@ -38,8 +38,9 @@ public: MeshRaycaster* raycaster; bool is_up_part; Transform3d trans; + bool is_modifier; }; - void part_render(const Vec3d *normal); + void part_render(const Vec3d *cut_center, const Vec3d *normal); void toggle_selection(const Vec2d &mouse_pos); void toggle_selection(int id); void turn_over_selection(); @@ -229,6 +230,7 @@ protected: virtual std::string on_get_name() const; virtual std::string on_get_name_str() override { return "Cut"; } virtual void on_set_state(); + void close(); virtual bool on_is_activable() const; virtual CommonGizmosDataID on_get_requirements() const override; virtual void on_start_dragging() override; @@ -312,6 +314,7 @@ private: bool has_valid_groove() const; bool has_valid_contour() const; void reset_cut_by_contours(); + void render_flip_plane_button(bool disable_pred = false); void process_contours(); void toggle_model_objects_visibility(bool show_in_3d = false); void deal_connector_pos_by_type(Vec3d &pos, float &height, CutConnectorType, CutConnectorStyle, bool looking_forward, bool is_edit, const Vec3d &clp_normal); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index c18b315..5d9f6d8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -18,6 +18,7 @@ const float GLGizmoBase::Grabber::SizeFactor = 0.05f; const float GLGizmoBase::Grabber::MinHalfSize = 4.0f; const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; const float GLGizmoBase::Grabber::FixedGrabberSize = 16.0f; +float GLGizmoBase::Grabber::GrabberSizeFactor = 1.0f; const float GLGizmoBase::Grabber::FixedRadiusSize = 80.0f; @@ -78,7 +79,7 @@ GLGizmoBase::Grabber::Grabber() hover_color = GRABBER_HOVER_COL; } -void GLGizmoBase::Grabber::render(bool hover, float size) const +void GLGizmoBase::Grabber::render(bool hover) const { std::array render_color; if (hover) { @@ -87,7 +88,7 @@ void GLGizmoBase::Grabber::render(bool hover, float size) const else render_color = color; - render(size, render_color, false); + render(render_color, false); } float GLGizmoBase::Grabber::get_half_size(float size) const @@ -113,7 +114,7 @@ GLModel& GLGizmoBase::Grabber::get_cube() return cube; } -void GLGizmoBase::Grabber::render(float size, const std::array& render_color, bool picking) const +void GLGizmoBase::Grabber::render(const std::array& render_color, bool picking) const { if (! cube_initialized) { // This cannot be done in constructor, OpenGL is not yet @@ -126,10 +127,7 @@ void GLGizmoBase::Grabber::render(float size, const std::array& render //QDS set to fixed size grabber //float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - float fullsize = 8.0f; - if (GLGizmoBase::INV_ZOOM > 0) { - fullsize = FixedGrabberSize * GLGizmoBase::INV_ZOOM; - } + float fullsize = get_grabber_size(); const_cast(&cube)->set_color(-1, render_color); @@ -219,6 +217,52 @@ bool GLGizmoBase::render_combo(const std::string &label, const std::vector 0) { + grabber_size = GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; + grabber_size = grabber_size * GLGizmoBase::Grabber::GrabberSizeFactor; + } + return grabber_size; +} + GLGizmoBase::GLGizmoBase(GLCanvas3D &parent, const std::string &icon_filename, unsigned int sprite_id) : m_parent(parent) , m_group_id(-1) @@ -398,13 +442,13 @@ std::array GLGizmoBase::picking_color_component(unsigned int id) const void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const { #if ENABLE_FIXED_GRABBER - render_grabbers((float)(GLGizmoBase::Grabber::FixedGrabberSize)); + render_grabbers(); #else render_grabbers((float)((box.size().x() + box.size().y() + box.size().z()) / 3.0)); #endif } -void GLGizmoBase::render_grabbers(float size) const +void GLGizmoBase::render_grabbers() const { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -413,24 +457,18 @@ void GLGizmoBase::render_grabbers(float size) const shader->set_uniform("emission_factor", 0.1f); for (int i = 0; i < (int)m_grabbers.size(); ++i) { if (m_grabbers[i].enabled) - m_grabbers[i].render(m_hover_id == i, size); + m_grabbers[i].render(m_hover_id == i); } shader->stop_using(); } void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const { -#if ENABLE_FIXED_GRABBER - float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif - for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { if (m_grabbers[i].enabled) { std::array color = picking_color_component(i); m_grabbers[i].color = color; - m_grabbers[i].render_for_picking(mean_size); + m_grabbers[i].render_for_picking(); } } } @@ -444,6 +482,83 @@ void GLGizmoBase::set_dirty() { m_dirty = true; } +bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) +{ + bool is_dragging_finished = false; + if (mouse_event.Moving()) { + // it should not happen but for sure + assert(!m_dragging); + if (m_dragging) + is_dragging_finished = true; + else + return false; + } + + if (mouse_event.LeftDown()) { + Selection &selection = m_parent.get_selection(); + if (!selection.is_empty() && m_hover_id != -1 /* && + (m_grabbers.empty() || m_hover_id < static_cast(m_grabbers.size()))*/) { + selection.setup_cache(); + + m_dragging = true; + for (auto &grabber : m_grabbers) grabber.dragging = false; + // if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) + // m_grabbers[m_hover_id].dragging = true; + + on_start_dragging(); + + // Let the plater know that the dragging started + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED)); + m_parent.set_as_dirty(); + return true; + } + } else if (m_dragging) { + // when mouse cursor leave window than finish actual dragging operation + bool is_leaving = mouse_event.Leaving(); + if (mouse_event.Dragging()) { + Point mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + auto ray = m_parent.mouse_ray(mouse_coord); + UpdateData data(ray, mouse_coord); + + update(data); + + wxGetApp().obj_manipul()->set_dirty(); + m_parent.set_as_dirty(); + return true; + } else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { + do_stop_dragging(is_leaving); + return true; + } + } + return false; +} + +void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) +{ + for (auto &grabber : m_grabbers) grabber.dragging = false; + m_dragging = false; + + // NOTE: This should be part of GLCanvas3D + // Reset hover_id when leave window + if (perform_mouse_cleanup) m_parent.mouse_up_cleanup(); + + on_stop_dragging(); + + // There is prediction that after draggign, data are changed + // Data are updated twice also by canvas3D::reload_scene. + // Should be fixed. + m_parent.get_gizmos_manager().update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + + // Let the plater know that the dragging finished, so a delayed + // refresh of the scene with the background processing data should + // be performed. + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + m_parent.refresh_camera_scene_box(); +} + void GLGizmoBase::render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 5122046..c89212e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -5,8 +5,7 @@ #include "libslic3r/Color.hpp" #include "slic3r/GUI/I18N.hpp" -#include "slic3r/GUI/GLModel.hpp" -#include "slic3r/GUI/MeshUtils.hpp" +#include "slic3r/GUI/3DScene.hpp" #include @@ -26,7 +25,8 @@ class Linef3; class ModelObject; namespace GUI { - +#define MAX_NUM 9999.99 +#define MAX_SIZE "9999.99" class ImGuiWrapper; @@ -61,13 +61,13 @@ public: static void update_render_colors(); static void load_render_colors(); -protected: struct Grabber { static const float SizeFactor; static const float MinHalfSize; static const float DraggingScaleFactor; static const float FixedGrabberSize; + static float GrabberSizeFactor; static const float FixedRadiusSize; Vec3d center; @@ -79,15 +79,15 @@ protected: Grabber(); - void render(bool hover, float size) const; - void render_for_picking(float size) const { render(size, color, true); } + void render(bool hover) const; + void render_for_picking() const { render(color, true); } float get_half_size(float size) const; float get_dragging_half_size(float size) const; GLModel& get_cube(); private: - void render(float size, const std::array& render_color, bool picking) const; + void render(const std::array& render_color, bool picking) const; GLModel cube; bool cube_initialized = false; @@ -172,6 +172,8 @@ protected: DoubleShowType show_type = DoubleShowType::Normal); bool render_combo(const std::string &label, const std::vector &lines, size_t &selection_idx, float label_width, float item_width); + void render_cross_mark(const Vec3f& target,bool is_single =false); + static float get_grabber_size(); public: GLGizmoBase(GLCanvas3D& parent, @@ -268,13 +270,29 @@ protected: // No check is made for clashing with other picking color (i.e. GLVolumes) std::array picking_color_component(unsigned int id) const; void render_grabbers(const BoundingBoxf3& box) const; - void render_grabbers(float size) const; + void render_grabbers() const; void render_grabbers_for_picking(const BoundingBoxf3& box) const; std::string format(float value, unsigned int decimals) const; // Mark gizmo as dirty to Re-Render when idle() void set_dirty(); + + /// + /// function which + /// Set up m_dragging and call functions + /// on_start_dragging / on_dragging / on_stop_dragging + /// + /// Keep information about mouse click + /// same as on_mouse + bool use_grabbers(const wxMouseEvent &mouse_event); + void do_stop_dragging(bool perform_mouse_cleanup); + template void limit_value(T &value, T _min, T _max) + { + if (value >= _max) { value = _max;} + if (value <= _min) { value = _min; } + } + private: // Flag for dirty visible state of Gizmo // When True then need new rendering diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp new file mode 100644 index 0000000..34ee57b --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp @@ -0,0 +1,1070 @@ +#include "GLGizmoBrimEars.hpp" +#include +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { namespace GUI { + +static const ColorRGBA DEF_COLOR = {0.7f, 0.7f, 0.7f, 1.f}; +static const ColorRGBA SELECTED_COLOR = {0.0f, 0.5f, 0.5f, 1.0f}; +static const ColorRGBA ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f}; +static const ColorRGBA HOVER_COLOR = {0.7f, 0.7f, 0.7f, 0.5f}; + +static ModelVolume *get_model_volume(const Selection &selection, Model &model) +{ + const Selection::IndicesList &idxs = selection.get_volume_idxs(); + // only one selected volume + if (idxs.size() != 1) return nullptr; + const GLVolume *selected_volume = selection.get_volume(*idxs.begin()); + if (selected_volume == nullptr) return nullptr; + + const GLVolume::CompositeID &cid = selected_volume->composite_id; + const ModelObjectPtrs &objs = model.objects; + if (cid.object_id < 0 || objs.size() <= static_cast(cid.object_id)) return nullptr; + const ModelObject *obj = objs[cid.object_id]; + if (cid.volume_id < 0 || obj->volumes.size() <= static_cast(cid.volume_id)) return nullptr; + return obj->volumes[cid.volume_id]; +} + +GLGizmoBrimEars::GLGizmoBrimEars(GLCanvas3D &parent, const std::string &icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) {} + +bool GLGizmoBrimEars::on_init() +{ + + m_new_point_head_diameter = get_brim_default_radius(); + + m_shortcut_key = WXK_CONTROL_L; + + m_desc["head_diameter"] = _L("Head diameter"); + m_desc["max_angle"] = _L("Max angle"); + m_desc["detection_radius"] = _L("Detection radius"); + m_desc["remove_selected"] = _L("Remove selected points"); + m_desc["remove_all"] = _L("Remove all"); + m_desc["auto_generate"] = _L("Auto-generate points"); + m_desc["section_view"] = _L("Section view"); + + m_desc["left_click_caption"] = _L("Left click"); + m_desc["left_click"] = _L("Add a brim ear"); + m_desc["right_click_caption"] = _L("Right click"); + m_desc["right_click"] = _L("Delete a brim ear"); + m_desc["ctrl_mouse_wheel_caption"] = _L("Ctrl+Mouse wheel"); + m_desc["ctrl_mouse_wheel"] = _L("Adjust section view"); + + return true; +} + +void GLGizmoBrimEars::set_brim_data(ModelObject *model_object, const Selection &selection) +{ + if (!m_c->selection_info()) return; + + ModelObject *mo = m_c->selection_info()->model_object(); + + if (m_state == On && mo && mo->id() != m_old_mo_id) { + reload_cache(); + m_old_mo_id = mo->id(); + } +} + +void GLGizmoBrimEars::on_render() +{ + ModelObject *mo = m_c->selection_info()->model_object(); + const Selection &selection = m_parent.get_selection(); + + // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off + if (m_state == On && (mo != selection.get_model()->objects[selection.get_object_idx()] || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) { + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); + return; + } + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + if (selection.is_from_single_instance()) render_points(selection, false); + + m_selection_rectangle.render(m_parent); + m_c->object_clipper()->render_cut(); + + glsafe(::glDisable(GL_BLEND)); +} + +void GLGizmoBrimEars::on_render_for_picking() +{ + const Selection &selection = m_parent.get_selection(); + // glsafe(::glEnable(GL_DEPTH_TEST)); + render_points(selection, true); +} + +void GLGizmoBrimEars::render_points(const Selection &selection, bool picking) const +{ + auto editing_cache = m_editing_cache; + if (render_hover_point != nullptr) { editing_cache.push_back(*render_hover_point); } + + size_t cache_size = editing_cache.size(); + + bool has_points = (cache_size != 0); + + if (!has_points) return; + + GLShaderProgram *shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) shader->start_using(); + ScopeGuard guard([shader]() { + if (shader != nullptr) shader->stop_using(); + }); + + const GLVolume *vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const Transform3d &instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + const Transform3d &instance_matrix = vol->get_instance_transformation().get_matrix(); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(instance_matrix.data())); + + ColorRGBA render_color; + for (size_t i = 0; i < cache_size; ++i) { + const BrimPoint &brim_point = editing_cache[i].brim_point; + const bool &point_selected = editing_cache[i].selected; + const bool &hover = editing_cache[i].is_hover; + const bool &error = editing_cache[i].is_error; + // keep show brim ear + // if (is_mesh_point_clipped(brim_point.pos.cast())) + // continue; + + // First decide about the color of the point. + if (hover) { + render_color = HOVER_COLOR; + } else { + if (picking) + render_color = picking_color_component(i); + else { + if (size_t(m_hover_id) == i) // ignore hover state unless editing mode is active + render_color = {0.f, 1.f, 1.f, 1.f}; + else { // neigher hover nor picking + if (point_selected) + render_color = SELECTED_COLOR; + else { + if (error) + render_color = ERR_COLOR; + else + render_color = DEF_COLOR; + } + } + } + } + + const_cast(&m_cylinder)->set_color(render_color); + if (shader && !picking) shader->set_uniform("emission_factor", 0.5f); + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(brim_point.pos(0), brim_point.pos(1), brim_point.pos(2))); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + + if (vol->is_left_handed()) glFrontFace(GL_CW); + + // Matrices set, we can render the point mark now. + // If in editing mode, we'll also render a cone pointing to the sphere. + if (editing_cache[i].normal == Vec3f::Zero()) m_c->raycaster()->raycaster()->get_closest_point(editing_cache[i].brim_point.pos, &editing_cache[i].normal); + + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * editing_cache[i].normal.cast()); + Eigen::AngleAxisd aa(q); + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); + + glsafe(::glPushMatrix()); + double radius = (double) brim_point.head_front_radius * RenderPointScale; + glsafe(::glScaled(radius, radius, .2)); + m_cylinder.render(); + glsafe(::glPopMatrix()); + + if (vol->is_left_handed()) glFrontFace(GL_CCW); + + glsafe(::glPopMatrix()); + } + + glsafe(::glPopMatrix()); +} + +bool GLGizmoBrimEars::is_mesh_point_clipped(const Vec3d &point) const +{ + if (m_c->object_clipper()->get_position() == 0.) return false; + + auto sel_info = m_c->selection_info(); + int active_inst = m_c->selection_info()->get_active_instance(); + const ModelInstance *mi = sel_info->model_object()->instances[active_inst]; + const Transform3d &trafo = mi->get_transformation().get_matrix(); + + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + +bool GLGizmoBrimEars::unproject_on_mesh2(const Vec2d &mouse_pos, std::pair &pos_and_normal) +{ + const Camera &camera = wxGetApp().plater()->get_camera(); + double clp_dist = m_c->object_clipper()->get_position(); + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + bool mouse_on_object = false; + Vec3f position_on_model; + Vec3f normal_on_model; + double closest_hit_distance = std::numeric_limits::max(); + + for (auto item : m_mesh_raycaster_map) { + auto &raycaster = item.second->mesh_raycaster; + auto &world_tran = item.second->world_tran; + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + if (raycaster->unproject_on_mesh(mouse_pos, world_tran.get_matrix(), camera, hit, normal, clp_dist != 0. ? clp : nullptr)) { + double hit_squared_distance = (camera.get_position() - world_tran.get_matrix() * hit.cast()).norm(); + if (hit_squared_distance < closest_hit_distance) { + closest_hit_distance = hit_squared_distance; + mouse_on_object = true; + m_last_hit_volume = item.first; + auto volum_trsf = m_last_hit_volume->get_volume_transformation().get_matrix(); + position_on_model = (m_last_hit_volume->get_volume_transformation().get_matrix() * hit.cast()).cast(); + normal_on_model = normal; + } + } + } + pos_and_normal = std::make_pair(position_on_model, normal_on_model); + return mouse_on_object; +} +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoBrimEars::unproject_on_mesh(const Vec2d &mouse_pos, std::pair &pos_and_normal) +{ + if (m_c->raycaster()->raycasters().size() != 1) return false; + if (!m_c->raycaster()->raycaster()) return false; + + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = volume->get_instance_transformation(); + // trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));//sla shift看起来可以删掉 + + double clp_dist = m_c->object_clipper()->get_position(); + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + + // The raycaster query + Vec3f hit; + Vec3f normal; + if (m_c->raycaster()->raycaster()->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, clp_dist != 0. ? clp : nullptr)) { + pos_and_normal = std::make_pair(hit, normal); + return true; + } + + return false; +} + +void GLGizmoBrimEars::data_changed(bool is_serializing) +{ + if (!m_c->selection_info()) return; + + ModelObject *mo = m_c->selection_info()->model_object(); + if (mo) { + reset_all_pick(); + register_single_mesh_pick(); + } +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + ModelObject *mo = m_c->selection_info()->model_object(); + int active_inst = m_c->selection_info()->get_active_instance(); + + if (action == SLAGizmoEventType::Moving) { + // First check that the mouse pointer is on an object. + const Selection &selection = m_parent.get_selection(); + const ModelInstance *mi = mo->instances[0]; + Plater *plater = wxGetApp().plater(); + if (!plater) return false; + const Camera &camera = wxGetApp().plater()->get_camera(); + const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3d inverse_trsf = volume->get_instance_transformation().get_matrix(true).inverse(); + std::pair pos_and_normal; + if (unproject_on_mesh2(mouse_position, pos_and_normal)) { + render_hover_point = new CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_diameter / 2.f), false, (inverse_trsf * m_world_normal).cast(), true); + } else { + delete render_hover_point; + render_hover_point = nullptr; + } + } else if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { + // left down with shift - show the selection rectangle: + if (m_hover_id == -1) { + if (shift_down || alt_down) { m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); } + } else { + if (m_editing_cache[m_hover_id].selected) + unselect_point(m_hover_id); + else { + if (!alt_down) select_point(m_hover_id); + } + } + + return true; + } + + // left down without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id != -1) return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + std::pair pos_and_normal; + if (unproject_on_mesh2(mouse_position, pos_and_normal)) { + // we got an intersection + const Selection &selection = m_parent.get_selection(); + const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3d trsf = volume->get_instance_transformation().get_matrix(); + Transform3d inverse_trsf = volume->get_instance_transformation().get_matrix(true).inverse(); + // QDS brim ear postion is placed on the bottom side + Vec3d world_pos = trsf * pos_and_normal.first.cast(); + world_pos[2] = -0.0001; + Vec3d object_pos = trsf.inverse() * world_pos; + // brim ear always face up + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add brim ear"); + add_point_to_cache(object_pos.cast(), m_new_point_head_diameter / 2.f, false, (inverse_trsf * m_world_normal).cast()); + m_parent.set_as_dirty(); + m_wait_for_up_event = true; + find_single(); + } else + return false; + } else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + + // First collect positions of all the points in world coordinates. + Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); + // trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + std::vector points; + for (unsigned int i = 0; i < m_editing_cache.size(); ++i) points.push_back(trafo.get_matrix() * m_editing_cache[i].brim_point.pos.cast()); + + // Now ask the rectangle which of the points are inside. + std::vector points_inside; + std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + for (size_t idx : points_idxs) points_inside.push_back(points[idx].cast()); + + // Only select/deselect points that are actually visible. We want to check not only + // the point itself, but also the center of base of its cone, so the points don't hide + // under every miniature irregularity on the model. Remember the actual number and + // append the cone bases. + size_t orig_pts_num = points_inside.size(); + for (size_t idx : points_idxs) + points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].brim_point.pos + m_editing_cache[idx].normal)).cast()); + + for (size_t idx : + m_c->raycaster()->raycaster()->get_unobscured_idxs(trafo, wxGetApp().plater()->get_camera(), points_inside, m_c->object_clipper()->get_clipping_plane())) { + if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to + idx -= orig_pts_num; + if (rectangle_status == GLSelectionRectangle::Deselect) + unselect_point(points_idxs[idx]); + else + select_point(points_idxs[idx]); + } + return true; + } + + // left up with no selection rectangle + if (action == SLAGizmoEventType::LeftUp) { + if (m_wait_for_up_event) { m_wait_for_up_event = false; } + return true; + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_wait_for_up_event) + return true; // point has been placed and the button not released yet + // this prevents GLCanvas from starting scene rotation + + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); + return true; + } + + return false; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + + // mouse wheel up + if (action == SLAGizmoEventType::MouseWheelUp && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, false, true); + return true; + } + + if (action == SLAGizmoEventType::MouseWheelDown && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::max(0., pos - 0.01); + m_c->object_clipper()->set_position(pos, false, true); + return true; + } + + // reset clipper position + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + return false; +} + +void GLGizmoBrimEars::delete_selected_points() +{ + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Delete brim ear"); + + for (unsigned int idx = 0; idx < m_editing_cache.size(); ++idx) { + if (m_editing_cache[idx].selected) { m_editing_cache.erase(m_editing_cache.begin() + (idx--)); } + } + + select_point(NoPoints); + find_single(); +} + +void GLGizmoBrimEars::on_update(const UpdateData &data) +{ + if (m_hover_id != -1) { + std::pair pos_and_normal; + if (!unproject_on_mesh2(data.mouse_pos.cast(), pos_and_normal)) return; + m_editing_cache[m_hover_id].brim_point.pos[0] = pos_and_normal.first.x(); + m_editing_cache[m_hover_id].brim_point.pos[1] = pos_and_normal.first.y(); + //m_editing_cache[m_hover_id].normal = pos_and_normal.second; + m_editing_cache[m_hover_id].normal = Vec3f(0, 0, 1); + find_single(); + } +} + +std::vector GLGizmoBrimEars::get_config_options(const std::vector &keys) const +{ + std::vector out; + const ModelObject *mo = m_c->selection_info()->model_object(); + + if (!mo) return out; + + const DynamicPrintConfig &object_cfg = mo->config.get(); + const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + std::unique_ptr default_cfg = nullptr; + + for (const std::string &key : keys) { + if (object_cfg.has(key)) + out.push_back(object_cfg.option(key)); + else if (print_cfg.has(key)) + out.push_back(print_cfg.option(key)); + else { // we must get it from defaults + if (default_cfg == nullptr) default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); + out.push_back(default_cfg->option(key)); + } + } + + return out; +} + +void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + ModelObject *mo = m_c->selection_info()->model_object(); + + if (!mo) return; + + const DynamicPrintConfig& obj_cfg = mo->config.get(); + const DynamicPrintConfig& glb_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); + + const float currt_scale = m_parent.get_scale(); + ImGuiWrapper::push_toolbar_style(currt_scale); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0 * currt_scale, 5.0 * currt_scale)); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 4.0f * currt_scale); + GizmoImguiBegin(get_name(), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + + float space_size = m_imgui->get_style_scaling() * 8; + std::vector text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"]}; + float widest_text = m_imgui->find_widest_text(text_list); + float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x; + float input_text_size = m_imgui->scaled(10.0f); + float button_size = ImGui::GetFrameHeight(); + + float list_width = input_text_size + ImGui::GetStyle().ScrollbarSize + 2 * currt_scale; + + const float slider_icon_width = m_imgui->get_slider_icon_size().x; + const float slider_width = list_width - space_size; + const float drag_left_width = caption_size + slider_width + space_size; + + // adjust window position to avoid overlap the view toolbar + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) last_h = win_h; + if (last_y != y) last_y = y; + } + + ImGui::AlignTextToFramePadding(); + + // Following is a nasty way to: + // - save the initial value of the slider before one starts messing with it + // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene + // - take correct undo/redo snapshot after the user is done with moving the slider + float initial_value = m_new_point_head_diameter; + m_imgui->text(m_desc["head_diameter"]); + ImGui::SameLine(caption_size); + ImGui::PushItemWidth(slider_width); + auto update_cache_radius = [this]() { + for (auto &cache_entry : m_editing_cache) + if (cache_entry.selected) { + cache_entry.brim_point.head_front_radius = m_new_point_head_diameter / 2.f; + find_single(); + } + }; + m_imgui->qdt_slider_float_style("##head_diameter", &m_new_point_head_diameter, 5, 20, "%.1f", 1.0f, true); + if (m_imgui->get_last_slider_status().clicked) { + if (m_old_point_head_diameter == 0.f) m_old_point_head_diameter = initial_value; + } + if (m_imgui->get_last_slider_status().edited) + update_cache_radius(); + if (m_imgui->get_last_slider_status().deactivated_after_edit) { + // momentarily restore the old value to take snapshot + for (auto &cache_entry : m_editing_cache) + if (cache_entry.selected) cache_entry.brim_point.head_front_radius = m_old_point_head_diameter / 2.f; + float backup = m_new_point_head_diameter; + m_new_point_head_diameter = m_old_point_head_diameter; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change point head diameter"); + m_new_point_head_diameter = backup; + update_cache_radius(); + m_old_point_head_diameter = 0.f; + } + ImGui::SameLine(drag_left_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + ImGui::QDTDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f"); + ImGui::AlignTextToFramePadding(); + + m_imgui->text(m_desc["max_angle"]); + ImGui::SameLine(caption_size); + ImGui::PushItemWidth(slider_width); + m_imgui->qdt_slider_float_style("##max_angle", &m_max_angle, 0, 180, "%.1f", 1.0f, true); + ImGui::SameLine(drag_left_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + ImGui::QDTDragFloat("##max_angle_input", &m_max_angle, 0.05f, 0.0f, 180.0f, "%.1f"); + ImGui::AlignTextToFramePadding(); + + m_imgui->text(m_desc["detection_radius"]); + ImGui::SameLine(caption_size); + ImGui::PushItemWidth(slider_width); + m_imgui->qdt_slider_float_style("##detection_radius", &m_detection_radius, 0, static_cast(m_detection_radius_max), "%.1f", 1.0f, true); + ImGui::SameLine(drag_left_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + ImGui::QDTDragFloat("##detection_radius_input", &m_detection_radius, 0.05f, 0.0f, static_cast(m_detection_radius_max), "%.1f"); + ImGui::Separator(); + + float clp_dist = float(m_c->object_clipper()->get_position()); + m_imgui->text(m_desc["section_view"]); + ImGui::SameLine(caption_size); + ImGui::PushItemWidth(slider_width); + bool slider_clp_dist = m_imgui->qdt_slider_float_style("##section_view", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); + ImGui::SameLine(drag_left_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + bool b_clp_dist_input = ImGui::QDTDragFloat("##section_view_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); + if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position(clp_dist, false, true); } + ImGui::Separator(); + + // ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + if (m_imgui->button(m_desc["auto_generate"])) { auto_generate(); } + + if (m_imgui->button(m_desc["remove_selected"])) { delete_selected_points(); } + float font_size = ImGui::GetFontSize(); + //ImGui::Dummy(ImVec2(font_size * 1, font_size * 1.3)); + ImGui::SameLine(); + if (m_imgui->button(m_desc["remove_all"])) { + if (m_editing_cache.size() > 0) { + select_point(AllPoints); + delete_selected_points(); + } + } + ImGui::PopStyleVar(1); + + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + show_tooltip_information(x, get_cur_y); + + if (glb_cfg.opt_enum("brim_type") != btBrimEars) { + ImGui::SameLine(); + auto link_text = [&]() { + ImColor HyperColor = m_link_text_hover ? ImColor(0, 240, 91).Value : ImColor(0, 174, 66).Value; + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING())); + float parent_width = ImGui::GetContentRegionAvail().x; + m_imgui->text_wrapped(_L("Warning: The brim type is not set to \"painted\",the brim ears will not take effect !"), parent_width); + ImGui::PopStyleColor(); + ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value); + ImGui::Dummy(ImVec2(font_size * 1.8, font_size * 1.3)); + ImGui::SameLine(); + m_imgui->bold_text(_u8L("Set the brim type to \"painted\"")); + ImGui::PopStyleColor(); + // 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::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)) { + m_link_text_hover = true; + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + DynamicPrintConfig new_conf = obj_cfg; + new_conf.set_key_value("brim_type", new ConfigOptionEnum(btBrimEars)); + mo->config.assign_config(new_conf); + } + }else { + m_link_text_hover = false; + } + }; + + if (obj_cfg.option("brim_type")) { + if (obj_cfg.opt_enum("brim_type") != btBrimEars){ + link_text(); + } + }else { + link_text(); + } + + } + + if (!m_single_brim.empty()) { + wxString out = _L("Warning") + ": " + std::to_string(m_single_brim.size()) + _L(" invalid brim ears"); + m_imgui->warning_text(out); + } + + GizmoImguiEnd(); + ImGui::PopStyleVar(2); + ImGuiWrapper::pop_toolbar_style(); +} + +void GLGizmoBrimEars::show_tooltip_information(float x, float y) +{ + std::array info_array = std::array{"left_click", "right_click", "ctrl_mouse_wheel"}; + float caption_max = 0.f; + for (const auto &t : info_array) { caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); } + + ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += m_imgui->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { + m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto &t : info_array) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +bool GLGizmoBrimEars::on_is_activable() const +{ + const Selection &selection = m_parent.get_selection(); + + if (!selection.is_single_full_instance()) return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + // const Selection::IndicesList &list = selection.get_volume_idxs(); + // for (const auto &idx : list) + // if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) return false; + + return true; +} + +std::string GLGizmoBrimEars::on_get_name() const +{ + if (!on_is_activable() && m_state == EState::Off) { + return _u8L("Brim Ears") + ":\n" + _u8L("Please select single object."); + } else { + return _u8L("Brim Ears"); + } +} + +CommonGizmosDataID GLGizmoBrimEars::on_get_requirements() const +{ + return CommonGizmosDataID(int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) | + int(CommonGizmosDataID::ObjectClipper)); +} + +void GLGizmoBrimEars::save_model() +{ + ModelObject* mo = m_c->selection_info()->model_object(); + if (mo) { + mo->brim_points.clear(); + for (const CacheEntry& ce : m_editing_cache) mo->brim_points.emplace_back(ce.brim_point); + wxGetApp().plater()->set_plater_dirty(true); + } +} + +// switch gizmos +void GLGizmoBrimEars::on_set_state() +{ + if (m_state == m_old_state) return; + + if (m_state == On && m_old_state != On) { + // the gizmo was just turned on + wxGetApp().plater()->enter_gizmos_stack(); + first_layer_slicer(); + } + if (m_state == Off && m_old_state != Off) { + // the gizmo was just turned Off + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Brim ears edit"); + save_model(); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().plater()->leave_gizmos_stack(); + // wxGetApp().mainframe->update_slice_print_status(MainFrame::SlicePrintEventType::eEventSliceUpdate, true, true); + } + m_old_state = m_state; +} + +void GLGizmoBrimEars::on_start_dragging() +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + m_point_before_drag = m_editing_cache[m_hover_id]; + } else + m_point_before_drag = CacheEntry(); +} + +void GLGizmoBrimEars::on_stop_dragging() +{ + if (m_hover_id != -1) { + CacheEntry backup = m_editing_cache[m_hover_id]; + + if (m_point_before_drag.brim_point.pos != Vec3f::Zero() // some point was touched + && backup.brim_point.pos != m_point_before_drag.brim_point.pos) // and it was moved, not just selected + { + m_editing_cache[m_hover_id] = m_point_before_drag; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Move support point"); + m_editing_cache[m_hover_id] = backup; + } + } + m_point_before_drag = CacheEntry(); +} + +void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); } + +void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); } + +void GLGizmoBrimEars::select_point(int i) +{ + if (i == AllPoints || i == NoPoints) { + for (auto &point_and_selection : m_editing_cache) point_and_selection.selected = (i == AllPoints); + m_selection_empty = (i == NoPoints); + + if (i == AllPoints) m_new_point_head_diameter = m_editing_cache[0].brim_point.head_front_radius * 2.f; + } else { + m_editing_cache[i].selected = true; + m_selection_empty = false; + m_new_point_head_diameter = m_editing_cache[i].brim_point.head_front_radius * 2.f; + } +} + +void GLGizmoBrimEars::unselect_point(int i) +{ + m_editing_cache[i].selected = false; + m_selection_empty = true; + for (const CacheEntry &ce : m_editing_cache) { + if (ce.selected) { + m_selection_empty = false; + break; + } + } +} + +void GLGizmoBrimEars::reload_cache() +{ + const ModelObject *mo = m_c->selection_info()->model_object(); + m_editing_cache.clear(); + for (const BrimPoint &point : mo->brim_points) m_editing_cache.emplace_back(point); +} + +Points GLGizmoBrimEars::generate_points(Polygon &obj_polygon, float ear_detection_length, float brim_ears_max_angle, bool is_outer) +{ + const coordf_t angle_threshold = (180 - brim_ears_max_angle) * PI / 180.0; + Points pt_ears; + if (ear_detection_length > 0) { + double detect_length = ear_detection_length / SCALING_FACTOR; + Points points = obj_polygon.points; + points.push_back(points.front()); + points = MultiPoint::_douglas_peucker(points, detect_length); + if (points.size() > 4) { + points.erase(points.end() - 1); + } + obj_polygon.points = points; + } + append(pt_ears, is_outer ? obj_polygon.convex_points(angle_threshold) : obj_polygon.concave_points(angle_threshold)); + return pt_ears; +} + +void GLGizmoBrimEars::first_layer_slicer() +{ + const Selection &selection = m_parent.get_selection(); + const Selection::IndicesList &idxs = selection.get_volume_idxs(); + if (idxs.size() <= 0) return; + std::vector slice_height(1, 0.1); + MeshSlicingParamsEx params; + params.mode = MeshSlicingParams::SlicingMode::Regular; + params.closing_radius = 0.1f; + params.extra_offset = 0.05f; + params.resolution = 0.01; + ExPolygons part_ex; + ExPolygons negative_ex; + for (auto idx : idxs) { + const GLVolume *volume = selection.get_volume(idx); + const ModelVolume *model_volume = get_model_volume(*volume, wxGetApp().model()); + if (model_volume == nullptr) continue; + if (model_volume->type() == ModelVolumeType::MODEL_PART || model_volume->type() == ModelVolumeType::NEGATIVE_VOLUME) { + indexed_triangle_set volume_its = model_volume->mesh().its; + if (volume_its.indices.size() <= 0) continue; + Transform3d trsf = volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(); + MeshSlicingParamsEx params_ex(params); + params_ex.trafo = params_ex.trafo * trsf; + if (params_ex.trafo.rotation().determinant() < 0.) its_flip_triangles(volume_its); + ExPolygons sliced_layer = slice_mesh_ex(volume_its, slice_height, params_ex).front(); + if (model_volume->type() == ModelVolumeType::MODEL_PART) { + part_ex = union_ex(part_ex, sliced_layer); + } else { + negative_ex = union_ex(negative_ex, sliced_layer); + } + } + } + m_first_layer = diff_ex(part_ex, negative_ex); + get_detection_radius_max(); +} + +void GLGizmoBrimEars::auto_generate() +{ + const Selection &selection = m_parent.get_selection(); + const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3d trsf = volume->get_instance_transformation().get_matrix(); + Vec3f normal = (volume->get_instance_transformation().get_matrix(true).inverse() * m_world_normal).cast(); + auto add_point = [this, &trsf, &normal](const Point &p) { + Vec3d world_pos = {float(p.x() * SCALING_FACTOR), float(p.y() * SCALING_FACTOR), -0.0001}; + Vec3d object_pos = trsf.inverse() * world_pos; + // m_editing_cache.emplace_back(BrimPoint(object_pos.cast(), m_new_point_head_diameter / 2), false, normal); + add_point_to_cache(object_pos.cast(), m_new_point_head_diameter / 2, false, normal); + }; + for (const ExPolygon &ex_poly : m_first_layer) { + Polygon out_poly = ex_poly.contour; + Polygons inner_poly = ex_poly.holes; + polygons_reverse(inner_poly); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Auto generate brim ear"); + Points out_points = generate_points(out_poly, m_detection_radius, m_max_angle, true); + for (Point &p : out_points) { add_point(p); } + for (Polygon &pl : inner_poly) { + Points inner_points = generate_points(pl, m_detection_radius, m_max_angle, false); + for (Point &p : inner_points) { add_point(p); } + } + } + find_single(); +} + +void GLGizmoBrimEars::get_detection_radius_max() +{ + double max_dist = 0.0; + int min_points_num = 0; + for (const ExPolygon &ex_poly : m_first_layer) { + Polygon out_poly = ex_poly.contour; + Polygons inner_poly = ex_poly.holes; + polygons_reverse(inner_poly); + + Points out_points = out_poly.points; + out_points.push_back(out_points.front()); + double tolerance = 0.0; + min_points_num = MultiPoint::_douglas_peucker(out_points, 0).size(); + int repeat = 0; + int loop_protect = 0; + for (;;) { + loop_protect++; + tolerance += 10; + int num = MultiPoint::_douglas_peucker(out_points, tolerance / SCALING_FACTOR).size(); + if (num == min_points_num) { + repeat++; + if (repeat > 1) + break; + } + min_points_num = num; + if (loop_protect > 100) break; + } + loop_protect = 0; + for (;;) { + loop_protect++; + tolerance -= 1; + int num = MultiPoint::_douglas_peucker(out_points, tolerance / SCALING_FACTOR).size(); + if (num <= min_points_num) { + min_points_num = num; + }else{ + break; + } + if (loop_protect > 100) break; + } + tolerance += 1; + if (tolerance > max_dist) + max_dist = tolerance; + } + if (max_dist > 100 || max_dist <= 0) { + m_detection_radius_max = 100; + } else { + m_detection_radius_max = max_dist; + } +} + +bool GLGizmoBrimEars::add_point_to_cache(Vec3f pos, float head_radius, bool selected, Vec3f normal) +{ + BrimPoint point(pos, head_radius); + for (int i = 0; i < m_editing_cache.size(); i++) { + if (m_editing_cache[i].brim_point == point) { return false; } + } + m_editing_cache.emplace_back(point, selected, normal); + return true; +} + +void GLGizmoBrimEars::register_single_mesh_pick() +{ + Selection &selection = m_parent.get_selection(); + const Selection::IndicesList &idxs = selection.get_volume_idxs(); + if (idxs.size() > 0) { + for (unsigned int idx : idxs) { + GLVolume *v = const_cast(selection.get_volume(idx)); + const ModelVolume* mv = get_model_volume(*v, wxGetApp().model()); + if (!mv->is_model_part()) continue; + auto world_tran = v->get_instance_transformation() * v->get_volume_transformation(); + auto mesh = const_cast(v->ori_mesh); + if (m_mesh_raycaster_map.find(v) != m_mesh_raycaster_map.end()) { + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + } else { + m_mesh_raycaster_map[v] = std::make_shared(mesh, -1); + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + } + } + } +} + +void GLGizmoBrimEars::update_single_mesh_pick(GLVolume *v) +{ + if (m_mesh_raycaster_map.find(v) != m_mesh_raycaster_map.end()) { + auto world_tran = v->get_instance_transformation() * v->get_volume_transformation(); + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + } +} + +void GLGizmoBrimEars::reset_all_pick() { std::map>().swap(m_mesh_raycaster_map); } + +float GLGizmoBrimEars::get_brim_default_radius() const +{ + const DynamicPrintConfig &pring_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; + return pring_cfg.get_abs_value("initial_layer_line_width") * 16.0f; +} + +ExPolygon GLGizmoBrimEars::make_polygon(BrimPoint point, const Geometry::Transformation &trsf) +{ + ExPolygon point_round; + Transform3d model_trsf = trsf.get_matrix(); + Vec3f world_pos = point.transform(trsf.get_matrix()); + coord_t size_ear = scale_(point.head_front_radius); + for (size_t i = 0; i < POLY_SIDE_COUNT; i++) { + double angle = (2.0 * PI * i) / POLY_SIDE_COUNT; + point_round.contour.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle)); + } + Vec3f pos = point.transform(model_trsf); + int32_t pt_x = scale_(pos.x()); + int32_t pt_y = scale_(pos.y()); + point_round.translate(Point(pt_x, pt_y)); + return point_round; +} + +void GLGizmoBrimEars::find_single() +{ + if (m_editing_cache.size() == 0) { + m_single_brim.clear(); + return; + } + const Selection &selection = m_parent.get_selection(); + const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trsf = volume->get_instance_transformation(); + ExPolygons model_pl = m_first_layer; + + m_single_brim.clear(); + for (int i = 0; i < m_editing_cache.size(); i++) + m_single_brim[i] = m_editing_cache[i]; + unsigned int index = 0; + bool cyc = true; + while (cyc) { + index++; + if (index > 99999999) break; // cycle protection + if (m_single_brim.empty()) { + break; + } + auto end = --m_single_brim.end(); + for (auto it = m_single_brim.begin(); it != m_single_brim.end(); ++it) { + ExPolygon point_pl = make_polygon(it->second.brim_point, trsf); + if (overlaps(model_pl, point_pl)) { + model_pl.emplace_back(point_pl); + model_pl = union_ex(model_pl); + it = m_single_brim.erase(it); + break; + } else { + if (it == end) cyc = false; + } + } + } + for (auto& it : m_editing_cache) { + it.is_error = false; + } + for (auto &it : m_single_brim) { + m_editing_cache[it.first].is_error = true; + } +} +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp new file mode 100644 index 0000000..ce54374 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp @@ -0,0 +1,178 @@ +#ifndef slic3r_GLGizmoBrimEars_hpp_ +#define slic3r_GLGizmoBrimEars_hpp_ + +#include "GLGizmoBase.hpp" +#include "slic3r/GUI/GLSelectionRectangle.hpp" +#include "libslic3r/BrimEarsPoint.hpp" +#include "libslic3r/ObjectID.hpp" + + +namespace Slic3r { + +class ConfigOption; + +namespace GUI { + +enum class SLAGizmoEventType : unsigned char; + +class GLGizmoBrimEars : public GLGizmoBase +{ +private: + + bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); + bool unproject_on_mesh2(const Vec2d& mouse_pos, std::pair& pos_and_normal); + + const float RenderPointScale = 1.f; + + class CacheEntry { + public: + CacheEntry() : + brim_point(BrimPoint()), + selected(false), + normal(Vec3f(0, 0, 1)), + is_hover(false), + is_error(false) + {} + + CacheEntry(const BrimPoint &point, bool sel = false, const Vec3f &norm = Vec3f(0, 0, 1), bool hover = false, bool error = false) + : brim_point(point), selected(sel), normal(norm), is_hover(hover), is_error(error) + {} + + bool operator==(const CacheEntry& rhs) const { + return (brim_point == rhs.brim_point); + } + + bool operator!=(const CacheEntry& rhs) const { + return ! ((*this) == rhs); + } + + inline bool pos_is_zero() { + return brim_point.pos.isZero(); + } + + void set_empty() { + brim_point = BrimPoint(); + selected = false; + normal.setZero(); + is_hover = false; + is_error = false; + } + + BrimPoint brim_point; + bool selected; // whether the point is selected + bool is_hover; // show mouse hover cylinder + bool is_error; + Vec3f normal; + + template + void serialize(Archive & ar) + { + ar(brim_point, selected, normal); + } + }; + +public: + GLGizmoBrimEars(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + virtual ~GLGizmoBrimEars() = default; + void data_changed(bool is_serializing) override; + void set_brim_data(ModelObject* model_object, const Selection& selection); + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + void delete_selected_points(); + void save_model(); + //ClippingPlane get_sla_clipping_plane() const; + + bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); } + + bool wants_enter_leave_snapshots() const override { return true; } + std::string get_gizmo_entering_text() const override { return "Entering Brim Ears"; } + std::string get_gizmo_leaving_text() const override { return "Leaving Brim Ears"; } + +private: + bool on_init() override; + void on_update(const UpdateData& data) override; + void on_render() override; + void on_render_for_picking() override; + + void render_points(const Selection& selection, bool picking = false) const; + + float m_new_point_head_diameter; // Size of a new point. + float m_max_angle = 125.f; + float m_detection_radius = 1.f; + double m_detection_radius_max = .0f; + CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited + float m_old_point_head_diameter = 0.; // the same + mutable std::vector m_editing_cache; // a support point and whether it is currently selectedchanges or undo/redo + std::map m_single_brim; + ObjectID m_old_mo_id; + const Vec3d m_world_normal = {0, 0, 1}; + std::map> m_mesh_raycaster_map; + GLVolume* m_last_hit_volume; + CacheEntry* render_hover_point = nullptr; + + bool m_link_text_hover = false; + + + // This map holds all translated description texts, so they can be easily referenced during layout calculations + // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. + std::map m_desc; + + GLSelectionRectangle m_selection_rectangle; + + ExPolygons m_first_layer; + + bool m_wait_for_up_event = false; + bool m_selection_empty = true; + EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) + + std::vector get_config_options(const std::vector& keys) const; + bool is_mesh_point_clipped(const Vec3d& point) const; + + // Methods that do the model_object and editing cache synchronization, + // editing mode selection, etc: + enum { + AllPoints = -2, + NoPoints, + }; + void select_point(int i); + void unselect_point(int i); + void reload_cache(); + Points generate_points(Polygon &obj_polygon, float ear_detection_length, float brim_ears_max_angle, bool is_outer); + void auto_generate(); + void first_layer_slicer(); + void get_detection_radius_max(); + +protected: + void on_set_state() override; + void on_set_hover_id() override + + { + if ((int)m_editing_cache.size() <= m_hover_id) + m_hover_id = -1; + } + void on_start_dragging() override; + void on_stop_dragging() override; + void on_render_input_window(float x, float y, float bottom_limit) override; + void show_tooltip_information(float x, float y); + + std::string on_get_name() const override; + std::string on_get_name_str() override { return "Brim Ears"; } + bool on_is_activable() const override; + //bool on_is_selectable() const override; + virtual CommonGizmosDataID on_get_requirements() const override; + void on_load(cereal::BinaryInputArchive& ar) override; + void on_save(cereal::BinaryOutputArchive& ar) const override; + void register_single_mesh_pick(); + void update_single_mesh_pick(GLVolume* v); + void reset_all_pick(); + bool add_point_to_cache(Vec3f pos, float head_radius, bool selected, Vec3f normal); + float get_brim_default_radius() const; + ExPolygon make_polygon(BrimPoint point, const Geometry::Transformation &trsf); + void find_single(); +}; + +wxDECLARE_EVENT(wxEVT_THREAD_DONE, wxCommandEvent); + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoBrimEars_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 6e3c7fe..4cdf99b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -83,8 +83,9 @@ bool GLGizmoFdmSupports::on_init() { // QDS m_shortcut_key = WXK_CONTROL_L; - - m_desc["clipping_of_view_caption"] = _L("Alt + Mouse wheel"); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + m_desc["clipping_of_view_caption"] = alt + _L("Mouse wheel"); m_desc["clipping_of_view"] = _L("Section view"); m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size_caption"] = _L("Ctrl + Mouse wheel"); @@ -99,10 +100,10 @@ bool GLGizmoFdmSupports::on_init() m_desc["highlight_by_angle"] = _L("Highlight overhang areas"); m_desc["gap_fill"] = _L("Gap fill"); m_desc["perform"] = _L("Perform"); - m_desc["gap_area_caption"] = _L("Ctrl + Mouse wheel"); + m_desc["gap_area_caption"] = ctrl +_L("Mouse wheel"); m_desc["gap_area"] = _L("Gap area"); m_desc["tool_type"] = _L("Tool type"); - m_desc["smart_fill_angle_caption"] = _L("Ctrl + Mouse wheel"); + m_desc["smart_fill_angle_caption"] = ctrl + _L("Mouse wheel"); m_desc["smart_fill_angle"] = _L("Smart fill angle"); m_desc["on_overhangs_only"] = _L("On overhangs only"); @@ -219,7 +220,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l init_print_instance(); if (! m_c->selection_info()->model_object()) return; - + m_imgui_start_pos[0] = x; + m_imgui_start_pos[1] = y; // QDS wchar_t old_tool = m_current_tool; @@ -493,7 +495,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_parent.set_as_dirty(); } ImGui::PopStyleVar(2); - + m_imgui_end_pos[0] = m_imgui_start_pos[0] + ImGui::GetWindowWidth(); + m_imgui_end_pos[1] = m_imgui_start_pos[1] + ImGui::GetWindowHeight(); GizmoImguiEnd(); // QDS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index bb0c399..7625508 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,13 +1,16 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoFlatten.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Model.hpp" -#include +#include +#include +#include #include namespace Slic3r { @@ -37,6 +40,34 @@ CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const return CommonGizmosDataID::SelectionInfo; } +void GLGizmoFlatten::on_render_input_window(float x, float y, float bottom_limit) { + if (!m_show_warning) { + return; + } + double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor(); + static float last_y = 0.0f; + static float last_h = 0.0f; + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) last_h = win_h; + if (last_y != y) last_y = y; + } + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + GizmoImguiBegin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + + if (m_show_warning) { + m_imgui->warning_text(_L("Warning: All triangle areas are too small,The current function is not working.")); + } + + GizmoImguiEnd(); + ImGuiWrapper::pop_toolbar_style(); +} + + std::string GLGizmoFlatten::on_get_name() const { if (!on_is_activable() && m_state == EState::Off) { @@ -143,7 +174,8 @@ void GLGizmoFlatten::update_planes() const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); // Following constants are used for discarding too small polygons. - const float minimal_area = 5.f; // in square mm (world coordinates) + const float experted_minimal_area = 5.0f; + const float minimal_area = 1.0f; // in square mm (world coordinates) const float minimal_side = 1.f; // mm const float minimal_angle = 1.f; // degree, initial value was 10, but cause bugs @@ -313,9 +345,38 @@ void GLGizmoFlatten::update_planes() // Transform back to 3D (and also back to mesh coordinates) polygon = transform(polygon, inst_matrix.inverse() * m.inverse()); } + if (m_planes.size() == 0) { + m_show_warning = true; + return; + } // We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations): std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; }); + + auto delte_index_to_end = [](int index, std::vector& planes) { + for (size_t i = planes.size() - 1; i >= index; i--) { + planes.pop_back(); + } + }; + const int plane_count = 10; + for (size_t i = 0; i < m_planes.size(); i++) { + if (m_planes[i].area < experted_minimal_area) { + if (i + 1 >= plane_count) { + delte_index_to_end(plane_count, m_planes); + break; + } + else {//= plane_count) { + delte_index_to_end(plane_count, m_planes); + break; + } + } + break; + } + } + } + m_planes.resize(std::min((int)m_planes.size(), 254)); // Planes are finished - let's save what we calculated it from: @@ -343,7 +404,7 @@ void GLGizmoFlatten::update_planes() plane.vertices.clear(); plane.vertices.shrink_to_fit(); } - + m_show_warning = false; m_planes_valid = true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index b7f341c..f07dade 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -58,6 +58,9 @@ protected: virtual void on_render_for_picking() override; virtual void on_set_state() override; virtual CommonGizmosDataID on_get_requirements() const override; + virtual void on_render_input_window(float x, float y, float bottom_limit) override; +private: + bool m_show_warning{false}; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 8d62c7d..9431400 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -472,6 +472,7 @@ void GLGizmoMeasure::on_set_state() m_mode = EMode::FeatureSelection; m_hover_id = -1; m_show_reset_first_tip = false; + m_only_select_plane = false; m_distance = Vec3d::Zero(); } } @@ -670,7 +671,8 @@ void GLGizmoMeasure::on_render() std::optional curr_feature = std::nullopt; if (m_curr_measuring) { curr_feature = wxGetMouseState().LeftIsDown() ? m_curr_feature : - mouse_on_object ? m_curr_measuring->get_feature(model_facet_idx, position_on_model, m_mesh_raycaster_map[m_last_hit_volume]->world_tran.get_matrix()) : + mouse_on_object ? m_curr_measuring->get_feature(model_facet_idx, position_on_model, + m_mesh_raycaster_map[m_last_hit_volume]->world_tran.get_matrix(), m_only_select_plane) : std::nullopt; } if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY) { @@ -1803,6 +1805,7 @@ void GLGizmoMeasure::show_selection_ui() const float feature_first_text_length = ImGui::CalcTextSize((_u8L(feature_first_text)).c_str()).x; ImGui::AlignTextToFramePadding(); if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY) { + m_only_select_plane = m_assembly_mode == AssemblyMode::FACE_FACE ? true : false; if (m_assembly_mode == AssemblyMode::FACE_FACE) { m_imgui->text(_u8L("Select 2 faces on objects and \n make objects assemble together.")); // tip } else if (m_assembly_mode == AssemblyMode::POINT_POINT) { @@ -2441,9 +2444,12 @@ void GLGizmoMeasure::set_distance(bool same_model_object, const Vec3d &displacem selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); m_pending_scale ++; if (same_model_object == false) { - selection->translate(v->object_idx(), v->instance_idx(), displacement); + auto object_displacement = v->get_instance_transformation().get_matrix_no_offset().inverse() * displacement; + v->set_instance_transformation(v->get_instance_transformation().get_matrix() * Geometry::translation_transform(object_displacement)); } else { - selection->translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement); + Geometry::Transformation tran(v->world_matrix()); + auto local_displacement = tran.get_matrix_no_offset().inverse() * displacement; + v->set_volume_transformation(v->get_volume_transformation().get_matrix() * Geometry::translation_transform(local_displacement)); } wxGetApp().plater()->canvas3D()->do_move(""); register_single_mesh_pick(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 5d51936..b8140cb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -297,6 +297,7 @@ protected: float m_space_size; float m_input_size_max; bool m_use_inches; + bool m_only_select_plane{false}; std::string m_units; mutable bool m_same_model_object; mutable unsigned int m_current_active_imgui_id; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp index dcd8a5e..985cafc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp @@ -26,6 +26,38 @@ GLGizmoMeshBoolean::~GLGizmoMeshBoolean() { } +void GLGizmoMeshBoolean::set_src_volume(ModelVolume* mv) +{ + m_src.mv = mv; + m_src.trafo = mv->get_matrix(); + m_src.volume_idx = -1; + const auto& volumes = mv->get_object()->volumes; + auto it = std::find(volumes.begin(), volumes.end(), mv); + assert(it != volumes.end()); + if (it != volumes.end()) + m_src.volume_idx = std::distance(volumes.begin(), it); + + if (m_src.mv == m_tool.mv) + m_tool.reset(); + + m_selecting_state = MeshBooleanSelectingState::SelectTool; +} + +void GLGizmoMeshBoolean::set_tool_volume(ModelVolume* mv) +{ + if (mv == m_src.mv) + return; + + m_tool.mv = mv; + m_tool.trafo = mv->get_matrix(); + m_tool.volume_idx = -1; + const auto& volumes = mv->get_object()->volumes; + auto it = std::find(volumes.begin(), volumes.end(), mv); + assert(it != volumes.end()); + if (it != volumes.end()) + m_tool.volume_idx = std::distance(volumes.begin(), it); +} + bool GLGizmoMeshBoolean::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (action == SLAGizmoEventType::LeftDown) { @@ -35,7 +67,7 @@ bool GLGizmoMeshBoolean::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const ModelInstance* mi = mo->instances[m_parent.get_selection().get_instance_idx()]; std::vector trafo_matrices; for (const ModelVolume* mv : mo->volumes) { - //if (mv->is_model_part()) { + //if (mv->is_model_part()) { trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); //} } @@ -69,16 +101,11 @@ bool GLGizmoMeshBoolean::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; if (get_selecting_state() == MeshBooleanSelectingState::SelectTool) { - m_tool.trafo = mo->volumes[closest_hit_mesh_id]->get_matrix(); - m_tool.volume_idx = closest_hit_mesh_id; set_tool_volume(mo->volumes[closest_hit_mesh_id]); return true; } if (get_selecting_state() == MeshBooleanSelectingState::SelectSource) { - m_src.trafo = mo->volumes[closest_hit_mesh_id]->get_matrix(); - m_src.volume_idx = closest_hit_mesh_id; set_src_volume(mo->volumes[closest_hit_mesh_id]); - m_selecting_state = MeshBooleanSelectingState::SelectTool; return true; } } @@ -94,10 +121,11 @@ bool GLGizmoMeshBoolean::on_init() std::string GLGizmoMeshBoolean::on_get_name() const { if (!on_is_activable() && m_state == EState::Off) { + if (m_parent.get_selection().get_volume_idxs().size() <= 1) { + return _u8L("Mesh Boolean") + ":\n" + _u8L("Please add at least one more object and select them together,\nthen right click to assembly these objects."); + } if (!m_parent.get_selection().is_single_full_instance()) { return _u8L("Mesh Boolean") + ":\n" + _u8L("Please right click to assembly these objects."); - } else if (m_parent.get_selection().get_volume_idxs().size() <= 1){ - return _u8L("Mesh Boolean") + ":\n" + _u8L("Please add at least one more object and select them together,\nthen right click to assembly these objects."); } } return _u8L("Mesh Boolean"); @@ -105,7 +133,14 @@ std::string GLGizmoMeshBoolean::on_get_name() const bool GLGizmoMeshBoolean::on_is_activable() const { - return m_parent.get_selection().is_single_full_instance() && m_parent.get_selection().get_volume_idxs().size() > 1; + auto& selection = m_parent.get_selection(); + + auto obj_idx = selection.get_object_idx(); + int volumes_size = 0; + if (obj_idx != -1) + volumes_size = selection.get_volume_idxs_from_object(obj_idx).size(); + + return !selection.is_empty() && !selection.is_multiple_full_object() && volumes_size > 1; } void GLGizmoMeshBoolean::on_render() @@ -128,8 +163,11 @@ void GLGizmoMeshBoolean::on_render() const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_parent.get_selection().get_instance_idx()]; const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int i : idxs) { + auto obj_idx = selection.get_object_idx(); + std::vector volume_ids; + if (obj_idx != -1) + volume_ids = selection.get_volume_idxs_from_object(obj_idx); + for (unsigned int i : volume_ids) { const GLVolume* volume = selection.get_volume(i); if(volume->volume_idx() == m_src.volume_idx) { src_bb = volume->transformed_convex_hull_bounding_box(); @@ -195,7 +233,7 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l const int max_cap_length = ImGui::GetStyle().WindowPadding.x + ImGui::GetStyle().ItemSpacing.x + std::max(ImGui::CalcTextSize(_u8L("Source Volume").c_str()).x, ImGui::CalcTextSize(_u8L("Tool Volume").c_str()).x); const int select_btn_length = 2 * ImGui::GetStyle().FramePadding.x + std::max(ImGui::CalcTextSize(("1 " + _u8L("selected")).c_str()).x, ImGui::CalcTextSize(_u8L("Select").c_str()).x); - auto selectable = [this](const wxString& label, bool selected, const ImVec2& size_arg) { + auto selectable = [this](const std::string& label, bool selected, const ImVec2& size_arg) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0,0 }); ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -269,8 +307,8 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l std::string cap_str1 = m_operation_mode != MeshBooleanOperation::Difference ? _u8L("Part 1") : _u8L("Subtract from"); m_imgui->text(cap_str1); ImGui::SameLine(max_cap_length); - wxString select_src_str = m_src.mv ? "1 " + _u8L("selected") : _u8L("Select"); - select_src_str << "##select_source_volume"; + std::string select_src_str = m_src.mv ? "1 " + _u8L("selected") : _u8L("Select"); + select_src_str += "##select_source_volume"; ImGui::PushItemWidth(select_btn_length); if (selectable(select_src_str, m_selecting_state == MeshBooleanSelectingState::SelectSource, ImVec2(select_btn_length, 0))) m_selecting_state = MeshBooleanSelectingState::SelectSource; @@ -289,6 +327,7 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l if (ImGui::Button((into_u8(ImGui::TextSearchCloseIcon) + "##src").c_str(), {18, 18})) { m_src.reset(); + m_selecting_state = MeshBooleanSelectingState::SelectSource; } ImGui::PopStyleColor(5); } @@ -297,8 +336,8 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l std::string cap_str2 = m_operation_mode != MeshBooleanOperation::Difference ? _u8L("Part 2") : _u8L("Subtract with"); m_imgui->text(cap_str2); ImGui::SameLine(max_cap_length); - wxString select_tool_str = m_tool.mv ? "1 " + _u8L("selected") : _u8L("Select"); - select_tool_str << "##select_tool_volume"; + std::string select_tool_str = m_tool.mv ? "1 " + _u8L("selected") : _u8L("Select"); + select_tool_str += "##select_tool_volume"; ImGui::PushItemWidth(select_btn_length); if (selectable(select_tool_str, m_selecting_state == MeshBooleanSelectingState::SelectTool, ImVec2(select_btn_length, 0))) m_selecting_state = MeshBooleanSelectingState::SelectTool; @@ -317,6 +356,7 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l if (ImGui::Button((into_u8(ImGui::TextSearchCloseIcon) + "tool").c_str(), {18, 18})) { m_tool.reset(); + m_selecting_state = (m_src.mv == nullptr) ? MeshBooleanSelectingState::SelectSource : MeshBooleanSelectingState::SelectTool; } ImGui::PopStyleColor(5); } @@ -339,6 +379,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l else { m_warning_texts[index] = warning_text_common; } + m_selecting_state = MeshBooleanSelectingState::SelectSource; + m_src.reset(); + m_tool.reset(); } } else if (m_operation_mode == MeshBooleanOperation::Difference) { @@ -357,6 +400,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l else { m_warning_texts[index] = warning_text_common; } + m_selecting_state = MeshBooleanSelectingState::SelectSource; + m_src.reset(); + m_tool.reset(); } } else if (m_operation_mode == MeshBooleanOperation::Intersection){ @@ -375,8 +421,10 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l else { m_warning_texts[index] = warning_text_intersection; } + m_selecting_state = MeshBooleanSelectingState::SelectSource; + m_src.reset(); + m_tool.reset(); } - } if (index >= 0 && index < m_warning_texts.size()) { render_input_window_warning(m_warning_texts[index]); @@ -428,20 +476,7 @@ void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMe // assign to new_volume from old_volume ModelVolume* old_volume = m_src.mv; - std::string suffix; - switch (m_operation_mode) - { - case MeshBooleanOperation::Union: - suffix = "union"; - break; - case MeshBooleanOperation::Difference: - suffix = "difference"; - break; - case MeshBooleanOperation::Intersection: - suffix = "intersection"; - break; - } - new_volume->name = old_volume->name + " - " + suffix; + new_volume->name = old_volume->name; new_volume->set_new_unique_id(); new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); @@ -457,9 +492,17 @@ void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMe std::swap(curr_model_object->volumes[m_src.volume_idx], curr_model_object->volumes.back()); curr_model_object->delete_volume(curr_model_object->volumes.size() - 1); + int obj_idx = m_parent.get_selection().get_object_idx(); + if (obj_idx == -1) { + auto& objects = *wxGetApp().obj_list()->objects(); + auto it = std::find(objects.begin(), objects.end(), curr_model_object); + if (it != objects.end()) + obj_idx = it - objects.begin(); + } + assert(obj_idx != -1); + if (delete_input) { std::vector items; - auto obj_idx = m_parent.get_selection().get_object_idx(); items.emplace_back(ItemType::itVolume, obj_idx, m_tool.volume_idx); wxGetApp().obj_list()->delete_from_model_and_list(items); } @@ -468,13 +511,11 @@ void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMe //curr_model_object->sort_volumes(true); wxGetApp().plater()->update(); - wxGetApp().obj_list()->select_item([this, new_volume]() { + wxGetApp().obj_list()->select_item([this, new_volume, obj_idx]() { wxDataViewItem sel_item; - - wxDataViewItemArray items = wxGetApp().obj_list()->reorder_volumes_and_get_selection(m_parent.get_selection().get_object_idx(), [new_volume](const ModelVolume* volume) { return volume == new_volume; }); + wxDataViewItemArray items = wxGetApp().obj_list()->reorder_volumes_and_get_selection(obj_idx, [new_volume](const ModelVolume* volume) { return volume == new_volume; }); if (!items.IsEmpty()) sel_item = items.front(); - return sel_item; }); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp index b6d305e..3fe71f3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp @@ -42,18 +42,10 @@ public: ~GLGizmoMeshBoolean(); void set_enable(bool enable) { m_enable = enable; } - bool get_enable() { return m_enable; } - MeshBooleanSelectingState get_selecting_state() { return m_selecting_state; } - void set_src_volume(ModelVolume* mv) { - m_src.mv = mv; - if (m_src.mv == m_tool.mv) - m_tool.reset(); - } - void set_tool_volume(ModelVolume* mv) { - m_tool.mv = mv; - if (m_tool.mv == m_src.mv) - m_src.reset(); - } + bool get_enable() const { return m_enable; } + MeshBooleanSelectingState get_selecting_state() const { return m_selecting_state; } + void set_src_volume(ModelVolume* mv); + void set_tool_volume(ModelVolume* mv); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 2d9a513..aed00b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -104,11 +104,12 @@ bool GLGizmoMmuSegmentation::on_init() { // QDS m_shortcut_key = WXK_CONTROL_N; - - m_desc["clipping_of_view_caption"] = _L("Alt + Mouse wheel"); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + m_desc["clipping_of_view_caption"] = alt+ _L("Mouse wheel"); m_desc["clipping_of_view"] = _L("Section view"); m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size_caption"] = _L("Ctrl + Mouse wheel"); + m_desc["cursor_size_caption"] = ctrl + _L("Mouse wheel"); m_desc["cursor_size"] = _L("Pen size"); m_desc["cursor_type"] = _L("Pen shape"); @@ -119,7 +120,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["shortcut_key_caption"] = _L("Key 1~9"); m_desc["shortcut_key"] = _L("Choose filament"); m_desc["edge_detection"] = _L("Edge detection"); - m_desc["gap_area_caption"] = _L("Ctrl + Mouse wheel"); + m_desc["gap_area_caption"] = ctrl + _L("Mouse wheel"); m_desc["gap_area"] = _L("Gap area"); m_desc["perform"] = _L("Perform"); @@ -134,15 +135,17 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["tool_smart_fill"] = _L("Smart fill"); m_desc["tool_bucket_fill"] = _L("Bucket fill"); - m_desc["smart_fill_angle_caption"] = _L("Ctrl + Mouse wheel"); + m_desc["smart_fill_angle_caption"] = ctrl + _L("Mouse wheel"); m_desc["smart_fill_angle"] = _L("Smart fill angle"); - m_desc["height_range_caption"] = _L("Ctrl + Mouse wheel"); + m_desc["height_range_caption"] = ctrl + _L("Mouse wheel"); m_desc["height_range"] = _L("Height range"); //add toggle wire frame hint - m_desc["toggle_wireframe_caption"] = _L("Alt + Shift + Enter"); - m_desc["toggle_wireframe"] = _L("Toggle Wireframe"); + m_desc["toggle_wireframe_caption"] = alt + _L("Shift + Enter"); + m_desc["toggle_wireframe"] = _L("Toggle Wireframe");//"show_wireframe" in shader + m_desc["toggle_non_manifold_edges_caption"] = ctrl + _L("Shift + L"); + m_desc["toggle_non_manifold_edges"] = _L("Toggle non-manifold edges"); init_extruders_data(); @@ -166,10 +169,56 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const m_c->object_clipper()->render_cut(); m_c->instances_hider()->render_cut(); render_cursor(); - + render_non_manifold_edges(); glsafe(::glDisable(GL_BLEND)); } +void GLGizmoMmuSegmentation::render_non_manifold_edges() const { + if (wxGetApp().plater()->is_show_non_manifold_edges()) { + if (!m_non_manifold_edges_model.is_initialized()) { + const Selection & selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + Line3floats non_manifold_edges; + int idx = -1; + for (ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { + ++idx; + auto &triangle_selector = m_triangle_selectors[idx]; + int max_orig_size_vertices = triangle_selector->get_orig_size_vertices(); + auto neighbors = triangle_selector->get_neighbors(); + auto vertices = triangle_selector->get_vertices(); + auto triangles = triangle_selector->get_triangles(); + auto world_tran = (mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix()).cast(); + for (size_t i = 0; i < neighbors.size(); i++) { + auto nei = neighbors[i]; + for (int j = 0; j < 3; j++) { + if (nei[j] < 0) { + auto jj = next_idx_modulo(j, 3); + auto v = world_tran * vertices[triangles[i].verts_idxs[j]].v; + auto next_v = world_tran * vertices[triangles[i].verts_idxs[jj]].v; + non_manifold_edges.emplace_back(Line3float(v, next_v)); + } + } + } + } + } + m_non_manifold_edges_model.init_model_from_lines(non_manifold_edges); + m_non_manifold_edges_model.set_color(ColorRGBA::RED()); + } + if (m_non_manifold_edges_model.is_initialized()) { + const Camera & camera = wxGetApp().plater()->get_camera(); + auto view_mat = camera.get_view_matrix(); + auto proj_mat = camera.get_projection_matrix(); + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + shader->start_using(); + shader->set_uniform("view_model_matrix", view_mat); + shader->set_uniform("projection_matrix", proj_mat); + m_non_manifold_edges_model.render_geometry(); + shader->stop_using(); + } + } +} + void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) { GLGizmoPainterBase::set_painter_gizmo_data(selection); @@ -363,17 +412,17 @@ void GLGizmoMmuSegmentation::show_tooltip_information(float caption_max, float x std::vector tip_items; switch (m_tool_type) { - case ToolType::BRUSH: - tip_items = {"paint", "erase", "cursor_size", "clipping_of_view", "toggle_wireframe"}; + case ToolType::BRUSH: + tip_items = {"paint", "erase", "cursor_size", "clipping_of_view", "toggle_wireframe", "toggle_non_manifold_edges"}; break; - case ToolType::BUCKET_FILL: - tip_items = {"paint", "erase", "smart_fill_angle", "clipping_of_view", "toggle_wireframe"}; + case ToolType::BUCKET_FILL: + tip_items = {"paint", "erase", "smart_fill_angle", "clipping_of_view", "toggle_wireframe", "toggle_non_manifold_edges"}; break; case ToolType::SMART_FILL: // TODO: break; case ToolType::GAP_FILL: - tip_items = {"gap_area", "toggle_wireframe"}; + tip_items = {"gap_area", "toggle_wireframe", "toggle_non_manifold_edges"}; break; default: break; @@ -391,7 +440,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float approx_height = m_imgui->scaled(22.0f); y = std::min(y, bottom_limit - approx_height); GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always); - + m_imgui_start_pos[0] = x; + m_imgui_start_pos[1] = y; wchar_t old_tool = m_current_tool; // QDS @@ -454,7 +504,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott float textbox_width = 1.5 * slider_icon_width; SliderInputLayout slider_input_layout = {clipping_slider_left, sliders_width, drag_left_width + circle_max_width, textbox_width}; - + if (wxGetApp().plater()->is_show_non_manifold_edges()) { + m_imgui->text(_L("hit face") + ":" + std::to_string(m_rr.facet)); + } { m_imgui->text(m_desc.at("filaments")); float text_offset = m_imgui->calc_text_size(m_desc.at("filaments")).x + m_imgui->scaled(1.5f); @@ -503,7 +555,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } // draw filament background - ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip; + ImGuiColorEditFlags flags = ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip; if (m_selected_extruder_idx != extruder_idx) flags |= ImGuiColorEditFlags_NoBorder; #ifdef __APPLE__ ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.27f, 0.47f, 0.98f, 1.00f)); @@ -529,10 +581,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott float gray = 0.299 * extruder_color[0] + 0.587 * extruder_color[1] + 0.114 * extruder_color[2]; ImGui::SameLine(button_offset + (button_size.x - label_size.x) / 2.f); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {10.0,15.0}); - if (gray * 255.f < 80.f) - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), item_text.c_str()); - else + if (abs(color_vec.w - 1) < 0.01) { + if (gray * 255.f < 80.f) + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), item_text.c_str()); + else + ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), item_text.c_str()); + } + else {//alpha ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), item_text.c_str()); + } ImGui::PopStyleVar(); } @@ -714,6 +771,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::QDTDragFloat("##cursor_height_input", &m_cursor_height, 0.05f, 0.0f, 0.0f, "%.2f"); + m_imgui->qdt_checkbox(_L("Place input box of bottom near mouse"), m_lock_x_for_height_bottom); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); @@ -774,8 +832,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_horizontal_only = horizontal_only; if (m_horizontal_only) { m_vertical_only = false; - m_is_front_view = true; - change_camera_view_angle(m_front_view_radian); } } @@ -784,12 +840,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_is_front_view != is_front_view) { m_is_front_view = is_front_view; if (m_is_front_view) { + update_front_view_radian(); change_camera_view_angle(m_front_view_radian); } } m_imgui->disabled_begin(!m_is_front_view); - if (render_slider_double_input_by_format(slider_input_layout, _u8L("Rotate horizontally"), m_front_view_radian, 0.f, 360.f, 0, DoubleShowType::DEGREE)) { + if (render_slider_double_input_by_format(slider_input_layout, _u8L("Rotate horizontally"), m_front_view_radian, -180.f, 180.f, 0, DoubleShowType::DEGREE)) { change_camera_view_angle(m_front_view_radian); } m_imgui->disabled_end(); @@ -836,6 +893,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_parent.set_as_dirty(); } ImGui::PopStyleVar(2); + m_imgui_end_pos[0] = m_imgui_start_pos[0] + ImGui::GetWindowWidth(); + m_imgui_end_pos[1] = m_imgui_start_pos[1] + ImGui::GetWindowHeight(); GizmoImguiEnd(); // QDS @@ -956,9 +1015,10 @@ void GLGizmoMmuSegmentation::on_set_state() GLGizmoPainterBase::on_set_state(); if (get_state() == On) { size_t n_extruder_colors = std::min((size_t) EnforcerBlockerType::ExtruderMax, m_extruders_colors.size()); - if (n_extruder_colors>=2) { + if (n_extruder_colors>=2) { m_selected_extruder_idx = 1; } + m_non_manifold_edges_model.reset(); } else if (get_state() == Off) { ModelObject* mo = m_c->selection_info()->model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 8afd847..2bbc218 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -68,7 +68,7 @@ public: ~GLGizmoMmuSegmentation() override = default; void render_painter_gizmo() const override; - + void render_non_manifold_edges() const; void set_painter_gizmo_data(const Selection& selection) override; void render_triangles(const Selection& selection) const override; @@ -139,6 +139,7 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + mutable GLModel m_non_manifold_edges_model; }; } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index d8798b3..2fe15ae 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -32,6 +32,12 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam , m_object_manipulation(obj_manipulation) { m_vbo_cone.init_from(its_make_cone(1., 1., 2*PI/36)); + try { + float value = std::stof(wxGetApp().app_config->get("grabber_size_factor")); + GLGizmoBase::Grabber::GrabberSizeFactor = value; + } catch (const std::invalid_argument &e) { + GLGizmoBase::Grabber::GrabberSizeFactor = 1.0f; + } } std::string GLGizmoMove3D::get_tooltip() const @@ -50,6 +56,11 @@ std::string GLGizmoMove3D::get_tooltip() const return ""; } +void GLGizmoMove3D::data_changed(bool is_serializing) +{ + change_cs_by_selection(); +} + bool GLGizmoMove3D::on_init() { for (int i = 0; i < 3; ++i) { @@ -78,7 +89,9 @@ bool GLGizmoMove3D::on_is_activable() const void GLGizmoMove3D::on_set_state() { if (get_state() == On) { - m_object_manipulation->set_coordinates_type(ECoordinatesType::World); + m_last_selected_obejct_idx = -1; + m_last_selected_volume_idx = -1; + change_cs_by_selection(); } } @@ -87,7 +100,7 @@ void GLGizmoMove3D::on_start_dragging() if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_drag_position = m_orient_matrix *m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); m_starting_box_bottom_center = box.center(); m_starting_box_bottom_center(2) = box.min(2); @@ -111,22 +124,27 @@ void GLGizmoMove3D::on_update(const UpdateData& data) void GLGizmoMove3D::on_render() { - const Selection& selection = m_parent.get_selection(); + Selection& selection = m_parent.get_selection(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = box.center(); + const auto &[box, box_trafo] = selection.get_bounding_box_in_current_reference_system(); + m_bounding_box = box; + m_center = box_trafo.translation(); + if (m_object_manipulation) { + m_object_manipulation->cs_center = box_trafo.translation(); + } + m_orient_matrix = box_trafo; float space_size = 20.f *INV_ZOOM; - + space_size *= GLGizmoBase::Grabber::GrabberSizeFactor; #if ENABLE_FIXED_GRABBER // x axis - m_grabbers[0].center = { box.max.x() + space_size, center.y(), center.z() }; + m_grabbers[0].center = {m_bounding_box.max.x() + space_size, 0, 0}; // y axis - m_grabbers[1].center = { center.x(), box.max.y() + space_size, center.z() }; + m_grabbers[1].center = {0, m_bounding_box.max.y() + space_size,0}; // z axis - m_grabbers[2].center = { center.x(), center.y(), box.max.z() + space_size }; + m_grabbers[2].center = {0,0, m_bounding_box.max.z() + space_size}; for (int i = 0; i < 3; ++i) { m_grabbers[i].color = AXES_COLOR[i]; @@ -147,7 +165,8 @@ void GLGizmoMove3D::on_render() #endif glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(Geometry::Transformation(m_orient_matrix).get_matrix().data())); // draw grabbers for (unsigned int i = 0; i < 3; ++i) { if (m_grabbers[i].enabled) render_grabber_extension((Axis) i, box, false); @@ -161,20 +180,34 @@ void GLGizmoMove3D::on_render() glLineStipple(1, 0x0FFF); glEnable(GL_LINE_STIPPLE); ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); + ::glVertex3dv(origin.data()); // use extension center ::glVertex3dv(m_grabbers[i].center.data()); glsafe(::glEnd()); glDisable(GL_LINE_STIPPLE); } } + glsafe(::glPopMatrix()); + + if (m_object_manipulation->is_instance_coordinates()) { + glsafe(::glPushMatrix()); + Geometry::Transformation cur_tran; + if (auto mi = m_parent.get_selection().get_selected_single_intance()) { + cur_tran = mi->get_transformation(); + } + else { + cur_tran = selection.get_first_volume()->get_instance_transformation(); + } + glsafe(::glMultMatrixd(cur_tran.get_matrix().data())); + render_cross_mark(Vec3f::Zero(), true); + glsafe(::glPopMatrix()); + } } void GLGizmoMove3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); //QDS donot render base grabber for picking //render_grabbers_for_picking(box); @@ -185,10 +218,12 @@ void GLGizmoMove3D::on_render_for_picking() m_grabbers[i].color = color; } } - - render_grabber_extension(X, box, true); - render_grabber_extension(Y, box, true); - render_grabber_extension(Z, box, true); + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(Geometry::Transformation(m_orient_matrix).get_matrix().data())); + render_grabber_extension(X, m_bounding_box, true); + render_grabber_extension(Y, m_bounding_box, true); + render_grabber_extension(Z, m_bounding_box, true); + glsafe(::glPopMatrix()); } //QDS: add input window for move @@ -227,13 +262,7 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const { -#if ENABLE_FIXED_GRABBER - float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif - - double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; + double size = get_grabber_size() * 0.75;//0.75 for arrow show std::array color = m_grabbers[axis].color; if (!picking && m_hover_id != -1) { @@ -268,7 +297,28 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box shader->stop_using(); } - +void GLGizmoMove3D::change_cs_by_selection() { + int obejct_idx, volume_idx; + ModelVolume *model_volume = m_parent.get_selection().get_selected_single_volume(obejct_idx, volume_idx); + if (m_last_selected_obejct_idx == obejct_idx && m_last_selected_volume_idx == volume_idx) { + return; + } + m_last_selected_obejct_idx = obejct_idx; + m_last_selected_volume_idx = volume_idx; + if (m_parent.get_selection().is_multiple_full_object()) { + m_object_manipulation->set_use_object_cs(false); + } + else if (model_volume) { + m_object_manipulation->set_use_object_cs(true); + } else { + m_object_manipulation->set_use_object_cs(false); + } + if (m_object_manipulation->get_use_object_cs()) { + m_object_manipulation->set_coordinates_type(ECoordinatesType::Instance); + } else { + m_object_manipulation->set_coordinates_type(ECoordinatesType::World); + } +} } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 1f070e0..69e8374 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -16,7 +16,10 @@ class GLGizmoMove3D : public GLGizmoBase static const double Offset; Vec3d m_displacement; - + Vec3d origin = Vec3d::Zero(); + Vec3d m_center{Vec3d::Zero()}; + BoundingBoxf3 m_bounding_box; + Transform3d m_orient_matrix{Transform3d::Identity()}; double m_snap_step; Vec3d m_starting_drag_position; @@ -40,6 +43,7 @@ public: const Vec3d& get_displacement() const { return m_displacement; } std::string get_tooltip() const override; + void data_changed(bool is_serializing) override; protected: virtual bool on_init() override; @@ -58,6 +62,9 @@ protected: private: double calc_projection(const UpdateData& data) const; void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; + void change_cs_by_selection(); //cs mean Coordinate System +private: + int m_last_selected_obejct_idx, m_last_selected_volume_idx; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 086edf1..3cc9f67 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -70,6 +70,11 @@ GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_pl return clp_data_out; } +void GLGizmoPainterBase::update_front_view_radian() +{ + wxGetApp().plater()->get_camera().calc_horizontal_rotate_rad(m_front_view_radian); +} + void GLGizmoPainterBase::render_triangles(const Selection& selection) const { auto* shader = wxGetApp().get_shader("gouraud"); @@ -151,9 +156,14 @@ void GLGizmoPainterBase::render_cursor() const else { m_rr.mouse_position = m_parent.get_local_mouse_position(); } - if (m_rr.mesh_id == -1) { + if (is_mouse_hit_in_imgui()) { + m_rr.mesh_id = -1; + return; + } + if (m_rr.mesh_id == -1) { m_is_cursor_in_imgui = false; - return; + m_x_for_height_input = -1; + return; } if (m_tool_type == ToolType::BRUSH) { @@ -276,7 +286,8 @@ bool GLGizmoPainterBase::is_valid_height_range_cursor(float min_z, float max_z) void GLGizmoPainterBase::render_cursor_height_range(const Transform3d& trafo) const { float buf_size= ImGui::CalcTextSize("-100.00").x + ImGui::GetStyle().FramePadding.x; - + m_height_range_input_all_size[0] = buf_size+ ImGui::CalcTextSize(_L("Bottom:").c_str()).x * 2 + ImGui::GetStyle().FramePadding.x; + m_height_range_input_all_size[1] = ImGui::CalcTextSize(_L("Bottom:").c_str()).y *2; const BoundingBoxf3 box = bounding_box(); Vec3d hit_world = trafo * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); float max_z = (float)box.max.z(); @@ -449,8 +460,23 @@ void GLGizmoPainterBase::update_contours(int i, const TriangleMesh &vol_mesh, fl if(screen_pos_sorts.size() >= 1){ m_height_start_pos = screen_pos_sorts[0].pos_screen; // make mouse to cover in a part of imgui - m_height_start_pos[0] -= 10; + if (m_lock_x_for_height_bottom) { + if (m_x_for_height_input == -1) { + m_x_for_height_input = m_rr.mouse_position.x() + 10; + } + m_height_start_pos[0] = m_x_for_height_input; + } else { + m_height_start_pos[0] -= 10; + } m_height_start_pos[1] -= 10; + const Camera &camera = wxGetApp().plater()->get_camera(); + auto viewport = camera.get_viewport(); + if (m_height_start_pos[0] + m_height_range_input_all_size[0] >= viewport[2]) { + m_height_start_pos[0] = viewport[2] - m_height_range_input_all_size[0]; + } + if (m_height_start_pos[1] + m_height_range_input_all_size[1] >= viewport[3]) { + m_height_start_pos[1] = viewport[3] - m_height_range_input_all_size[1]; + } } } m_cut_contours[i].contours.init_from(polys, static_cast(cursor_z)); @@ -667,6 +693,10 @@ std::vector GLGizmoPainterBase::get_pr bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { Vec2d _mouse_position = mouse_position; + if (is_mouse_hit_in_imgui()) { + m_rr.mesh_id = -1; + return false; + } if (action == SLAGizmoEventType::MouseWheelUp || action == SLAGizmoEventType::MouseWheelDown) { if (control_down) { @@ -1076,6 +1106,14 @@ CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const | int(CommonGizmosDataID::ObjectClipper)); } +bool GLGizmoPainterBase::is_mouse_hit_in_imgui() const +{ + if (m_rr.mouse_position[0] >= m_imgui_start_pos[0] && m_rr.mouse_position[1] >= m_imgui_start_pos[1]&& + m_rr.mouse_position[0] <= m_imgui_end_pos[0] && m_rr.mouse_position[1] <= m_imgui_end_pos[1]) { + return true; + } + return false; +} void GLGizmoPainterBase::on_set_state() { @@ -1292,9 +1330,8 @@ float TriangleSelectorPatch::gap_area = TriangleSelectorPatch::GapAreaMin; void TriangleSelectorPatch::render(ImGuiWrapper* imgui) { - static bool last_show_wireframe = false; - if (last_show_wireframe != wxGetApp().plater()->is_show_wireframe()) { - last_show_wireframe = wxGetApp().plater()->is_show_wireframe(); + if (m_cached_wireframe_mode != wxGetApp().plater()->is_show_wireframe()) { + m_cached_wireframe_mode = wxGetApp().plater()->is_show_wireframe(); m_update_render_data = true; m_paint_changed = true; } @@ -1643,8 +1680,8 @@ void TriangleSelectorPatch::render(int triangle_indices_idx, int position_id, bo glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); if ((this->m_triangle_indices_sizes[triangle_indices_idx] > 0)&&(position_id != -1)) glsafe(::glDisableVertexAttribArray(position_id)); - if ((this->m_triangle_indices_sizes[triangle_indices_idx] > 0)&&show_wireframe) { - glsafe(::glEnableClientState(GL_COLOR_ARRAY)); + if ((this->m_triangle_indices_sizes[triangle_indices_idx] > 0)&&show_wireframe) { + glsafe(::glDisableClientState(GL_COLOR_ARRAY)); } if (this->m_triangle_indices_sizes[triangle_indices_idx] > 0) glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 4d45365..d1bd782 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -198,6 +198,7 @@ protected: std::vector> m_ebt_colors; bool m_filter_state = false; + bool m_cached_wireframe_mode = false; private: void update_render_data(); @@ -235,6 +236,7 @@ public: virtual const float get_cursor_height_min() const { return CursorHeightMin; } virtual const float get_cursor_height_max() const { return CursorHeightMax; } virtual const float get_cursor_height_step() const { return CursorHeightStep; } + void update_front_view_radian(); protected: virtual void render_triangles(const Selection& selection) const; @@ -378,6 +380,9 @@ protected: mutable double m_height_start_z_in_imgui{0}; mutable bool m_is_set_height_start_z_by_imgui{false}; mutable Vec2i m_height_start_pos{0, 0}; + mutable float m_x_for_height_input{-1}; + mutable bool m_lock_x_for_height_bottom{false}; + mutable Vec2f m_height_range_input_all_size; mutable bool m_is_cursor_in_imgui{false}; BoundingBoxf3 bounding_box() const; void update_contours(int i, const TriangleMesh &vol_mesh, float cursor_z, float max_z, float min_z, bool update_height_start_pos) const; @@ -399,8 +404,10 @@ protected: bool wants_enter_leave_snapshots() const override { return true; } virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0; - + bool is_mouse_hit_in_imgui()const; friend class ::Slic3r::GUI::GLGizmoMmuSegmentation; + mutable Vec2i m_imgui_start_pos{0, 0}; + mutable Vec2i m_imgui_end_pos{0, 0}; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 8b5ef0c..8a23202 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -81,7 +81,7 @@ bool GLGizmoRotate::on_init() } void GLGizmoRotate::on_start_dragging() -{ +{ init_data_from_selection(m_parent.get_selection()); } @@ -314,9 +314,7 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const { - double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; - //float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); - //double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size); + double size = get_grabber_size() * 0.75;//0.75 for arrow show std::array color = m_grabbers[0].color; if (!picking && m_hover_id != -1) { @@ -361,7 +359,7 @@ void GLGizmoRotate::transform_to_local(const Selection &selection) const { glsafe(::glTranslated(m_center(0), m_center(1), m_center(2))); - if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) { + if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes() || m_force_local_coordinate) { glsafe(::glMultMatrixd(Geometry::Transformation(m_orient_matrix).get_matrix_no_offset().data())); } @@ -490,9 +488,31 @@ void GLGizmoRotate3D::on_set_state() g.set_state(m_state); if (get_state() == On && m_object_manipulation) { m_object_manipulation->set_coordinates_type(ECoordinatesType::World); + m_last_volume = nullptr; } } +void GLGizmoRotate3D::data_changed(bool is_serializing) { + const Selection &selection = m_parent.get_selection(); + const GLVolume * volume = selection.get_first_volume(); + if (volume == nullptr) { + m_last_volume = nullptr; + return; + } + if (m_last_volume != volume) { + m_last_volume = volume; + Geometry::Transformation tran; + if (selection.is_single_full_instance()) { + tran = volume->get_instance_transformation(); + } else { + tran = volume->get_volume_transformation(); + } + m_object_manipulation->set_init_rotation(tran); + } + for (GLGizmoRotate &g : m_gizmos) + g.init_data_from_selection(m_parent.get_selection()); +} + bool GLGizmoRotate3D::on_is_activable() const { const Selection &selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index d8dd230..ad881da 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -59,6 +59,7 @@ public: void set_center(const Vec3d &point) { m_custom_center = point; } void set_force_local_coordinate(bool use) { m_force_local_coordinate = use; } + void init_data_from_selection(const Selection &selection); protected: bool on_init() override; @@ -80,7 +81,6 @@ private: void transform_to_local(const Selection& selection) const; // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const; - void init_data_from_selection(const Selection &selection); }; class GLGizmoRotate3D : public GLGizmoBase @@ -137,6 +137,7 @@ protected: if (id < 3) m_gizmos[id].disable_grabber(0); } + void data_changed(bool is_serializing) override; bool on_is_activable() const override; void on_start_dragging() override; void on_stop_dragging() override; @@ -157,7 +158,7 @@ protected: void on_render_input_window(float x, float y, float bottom_limit) override; private: - + const GLVolume *m_last_volume; class RotoptimzeWindow { ImGuiWrapper *m_imgui = nullptr; public: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp new file mode 100644 index 0000000..eff2b99 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -0,0 +1,2198 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoSVG.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" + +#include "slic3r/GUI/Plater.hpp" +#include "libslic3r/AppConfig.hpp" + +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" +#include "slic3r/GUI/MainFrame.hpp" // to update title when add text +#include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/CameraUtils.hpp" + +#include "slic3r/Utils/UndoRedo.hpp" + +#include "libslic3r/Point.hpp" +#include "libslic3r/SVG.hpp" // debug store +#include "libslic3r/Geometry.hpp" // covex hull 2d +#include "libslic3r/Timer.hpp" // covex hull 2d + +#include "libslic3r/NSVGUtils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ClipperUtils.hpp" // union_ex + +#include "imgui/imgui_stdlib.h" // using std::string for inputs +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include "nanosvg/nanosvg.h" // load SVG file + +#include // detection of change DPI +#include + + +#include + +#include +#include +#include // measure enumeration of fonts +#include // save for svg +#include +#include + +#include "slic3r/GUI/BitmapCache.hpp" +#include "libslic3r/AABBTreeLines.hpp" // aabb lines for draw filled expolygon +#include +#include // use surface cuts +#include "slic3r/GUI/Jobs/Worker.hpp" +using namespace Slic3r; +using namespace Slic3r::Emboss; +using namespace Slic3r::GUI; + +namespace Slic3r { +namespace GUI { +const int c_move_cube_id = 1; +const std::array SVG_Move_GrabberColor = {1.0, 1.0, 0.0, 1.0}; +const std::array SVG_Move_GrabberHoverColor = {0.7, 0.7, 0.0, 1.0}; + +// TRN - Title in Undo/Redo stack after rotate with SVG around emboss axe +const std::string rotation_snapshot_name = "SVG rotate"; +// NOTE: Translation is made in "m_parent.do_rotate()" + +// TRN - Title in Undo/Redo stack after move with SVG along emboss axe - From surface +const std::string move_snapshot_name = "SVG move"; +// NOTE: Translation is made in "m_parent.do_translate()" + +// Variable keep limits for variables +const struct Limits +{ + MinMax depth{0.01, 1e4}; // in mm + MinMax size{0.01f, 1e4f}; // in mm (width + height) + MinMax ui_size{5.f, 100.f}; // in mm (width + height) - only slider values + MinMax ui_size_in{.1f, 4.f}; // in inches (width + height) - only slider values + MinMax relative_scale_ratio{1e-5, 1e4}; // change size + // distance text object from surface + MinMax angle{-180.f, 180.f}; // in degrees +} limits; + +wxString last_used_directory = wxEmptyString; +std::string choose_svg_file() +{ + wxWindow * parent = nullptr; + wxString message = _L("Choose SVG file for emboss:"); + wxString selected_file = wxEmptyString; + wxString wildcard = file_wildcards(FT_SVG); + long style = wxFD_OPEN | wxFD_FILE_MUST_EXIST; + wxFileDialog dialog(parent, message, last_used_directory, selected_file, wildcard, style); + if (dialog.ShowModal() != wxID_OK) { + BOOST_LOG_TRIVIAL(warning) << "SVG file for emboss was NOT selected."; + return {}; + } + + wxArrayString input_files; + dialog.GetPaths(input_files); + if (input_files.IsEmpty()) { + BOOST_LOG_TRIVIAL(warning) << "SVG file dialog result is empty."; + return {}; + } + + if (input_files.size() != 1) BOOST_LOG_TRIVIAL(warning) << "SVG file dialog result contain multiple files but only first is used."; + + std::string path = into_u8(input_files.front()); + if (!boost::filesystem::exists(path)) { + BOOST_LOG_TRIVIAL(warning) << "SVG file dialog return invalid path."; + return {}; + } + + if (!boost::algorithm::iends_with(path, ".svg")) { + BOOST_LOG_TRIVIAL(warning) << "SVG file dialog return path without '.svg' tail"; + return {}; + } + + last_used_directory = dialog.GetDirectory(); + return path; +} + + + + +double get_tesselation_tolerance(double scale) +{ + double tesselation_tolerance_in_mm = .1; // 8e-2; + double tesselation_tolerance_scaled = (tesselation_tolerance_in_mm * tesselation_tolerance_in_mm) / SCALING_FACTOR / SCALING_FACTOR; + return tesselation_tolerance_scaled / scale / scale; +} + +EmbossShape select_shape(std::string_view filepath, double tesselation_tolerance = get_tesselation_tolerance(1.)) +{ + EmbossShape shape; + shape.projection.depth = 10.; + shape.projection.use_surface = false; + + EmbossShape::SvgFile svg; + if (filepath.empty()) { + // When empty open file dialog + svg.path = choose_svg_file(); + if (svg.path.empty()) return {}; // file was not selected + } else { + svg.path = filepath; // copy + } + + boost::filesystem::path path(svg.path); + if (!boost::filesystem::exists(path)) { + show_error(nullptr, GUI::format(_u8L("File does NOT exist (%1%)."), svg.path)); + return {}; + } + + if (!boost::algorithm::iends_with(svg.path, ".svg")) { + show_error(nullptr, GUI::format(_u8L("Filename has to end with \".svg\" but you selected %1%"), svg.path)); + return {}; + } + + if (init_image(svg) == nullptr) { + show_error(nullptr, GUI::format(_u8L("Nano SVG parser can't load from file (%1%)."), svg.path)); + return {}; + } + + // Set default and unchanging scale + NSVGLineParams params{tesselation_tolerance}; + shape.shapes_with_ids = create_shape_with_ids(*svg.image, params); + + // Must contain some shapes !!! + if (shape.shapes_with_ids.empty()) { + show_error(nullptr, GUI::format(_u8L("SVG file does NOT contain a single path to be embossed (%1%)."), svg.path)); + return {}; + } + shape.svg_file = std::move(svg); + return shape; +} + +std::string get_file_name(const std::string &file_path) +{ + if (file_path.empty()) return file_path; + + size_t pos_last_delimiter = file_path.find_last_of("/\\"); + if (pos_last_delimiter == std::string::npos) { + // should not happend that in path is not delimiter + assert(false); + pos_last_delimiter = 0; + } + + size_t pos_point = file_path.find_last_of('.'); + if (pos_point == std::string::npos || pos_point < pos_last_delimiter // last point is inside of directory path + ) { + // there is no extension + assert(false); + pos_point = file_path.size(); + } + + size_t offset = pos_last_delimiter + 1; // result should not contain last delimiter ( +1 ) + size_t count = pos_point - pos_last_delimiter - 1; // result should not contain extension point ( -1 ) + return file_path.substr(offset, count); +} + +std::string volume_name(const EmbossShape &shape) +{ + std::string file_name = get_file_name(shape.svg_file->path); + if (!file_name.empty()) + return file_name; + return "SVG shape"; +} + + + + + +// QDS: GUI refactor: add obj manipulation +GLGizmoSVG::GLGizmoSVG(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, "toolbar_cut.svg", sprite_id) //"toolbar_cut.svg" no use + , m_gui_cfg(nullptr) + , m_rotate_gizmo(parent, GLGizmoRotate::Axis::Z) // grab id = 2 (Z axis) +{ + m_rotate_gizmo.set_group_id(0); + m_rotate_gizmo.set_force_local_coordinate(true); +} + +std::string GLGizmoSVG::get_tooltip() const +{ + return ""; +} + +void GLGizmoSVG::data_changed(bool is_serializing) +{ + set_volume_by_selection(); + if (!is_serializing && m_volume == nullptr) + close(); +} + +void GLGizmoSVG::on_enable_grabber(unsigned int id) { + m_rotate_gizmo.enable_grabber(0); +} + +void GLGizmoSVG::on_disable_grabber(unsigned int id) { + m_rotate_gizmo.disable_grabber(0); +} + +Emboss::DataBasePtr GLGizmoSVG::create_emboss_data_base(std::shared_ptr> &cancel, ModelVolumeType volume_type, std::string_view filepath) +{ + EmbossShape shape = select_shape(filepath); + + if (shape.shapes_with_ids.empty()) + // canceled selection of SVG file + return nullptr; + + // Cancel previous Job, when it is in process + // worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs + // Cancel only EmbossUpdateJob no others + if (cancel != nullptr) + cancel->store(true); + // create new shared ptr to cancel new job + cancel = std::make_shared>(false); + + std::string name = volume_name(shape); + + auto result = std::make_unique(name, cancel, std::move(shape)); + result->is_outside = volume_type == ModelVolumeType::MODEL_PART; + return result; +} + +Emboss::CreateVolumeParams GLGizmoSVG::create_input(GLCanvas3D &canvas, ModelVolumeType volume_type) +{ + auto gizmo = static_cast(GLGizmosManager::Svg); + const GLVolume *gl_volume = canvas.get_selection().get_first_volume();//modify by qds //get_first_hovered_gl_volume(canvas); + Plater * plater = wxGetApp().plater(); + auto register_mesh_pick = [this]() { register_single_mesh_pick(); }; +#ifndef EXECUTE_UPDATE_ON_MAIN_THREAD + return Emboss::CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), plater->get_ui_job_worker(), + register_mesh_pick, m_raycast_manager, m_raycast_condition,volume_type,gizmo,gl_volume}; +#else + return Emboss::CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), volume_type, gizmo, gl_volume}; + #endif +} + +enum class IconType : unsigned { + reset_value, + refresh, + change_file, + bake, + save, + exclamation, + lock, + unlock, + reflection_x, + reflection_y, + // automatic calc of icon's count + _count +}; +// This configs holds GUI layout size given by translated texts. +// etc. When language changes, GUI is recreated and this class constructed again, +// so the change takes effect. (info by GLGizmoFdmSupports.hpp) +struct GuiCfg +{ + // Detect invalid config values when change monitor DPI + double screen_scale = -1.; + bool dark_mode = false; + + // Define bigger size(width or height) + unsigned texture_max_size_px = 256; + + float input_width = 0.f; + float input_offset = 0.f; + + float icon_width = 0.f; + + float max_tooltip_width = 0.f; + + // offset for checbox for lock up vector + float lock_offset = 0.f; + // Only translations needed for calc GUI size + struct Translations + { + std::string depth; + std::string size; + std::string use_surface; + std::string rotation; + std::string distance; // from surface + std::string mirror; + }; + Translations translations; +}; + +GuiCfg create_gui_configuration() +{ + GuiCfg cfg; // initialize by default values; + + float line_height = ImGui::GetTextLineHeight(); + float line_height_with_spacing = ImGui::GetTextLineHeightWithSpacing(); + + float space = line_height_with_spacing - line_height; + + cfg.icon_width = std::max(std::round(line_height / 8) * 8, 8.f); + + GuiCfg::Translations &tr = cfg.translations; + + float lock_width = cfg.icon_width + 3 * space; + // TRN - Input label. Be short as possible + tr.depth = _u8L("Depth"); + // TRN - Input label. Be short as possible + tr.size = _u8L("Size"); + // TRN - Input label. Be short as possible + tr.use_surface = _u8L("Use surface"); + // TRN - Input label. Be short as possible + tr.distance = _u8L("From surface"); + // TRN - Input label. Be short as possible + tr.rotation = _u8L("Rotation"); + // TRN - Input label. Be short as possible + tr.mirror = _u8L("Mirror"); + float max_tr_width = std::max({ + ImGui::CalcTextSize(tr.depth.c_str()).x, + ImGui::CalcTextSize(tr.size.c_str()).x + lock_width, + ImGui::CalcTextSize(tr.use_surface.c_str()).x + space *2, + ImGui::CalcTextSize(tr.distance.c_str()).x + space, + ImGui::CalcTextSize(tr.rotation.c_str()).x + lock_width, + ImGui::CalcTextSize(tr.mirror.c_str()).x, + }) + space; + + const ImGuiStyle &style = ImGui::GetStyle(); + cfg.input_offset = style.WindowPadding.x + max_tr_width + space * 2+ cfg.icon_width; + cfg.lock_offset = cfg.input_offset - (cfg.icon_width + 2 * space); + + ImVec2 letter_m_size = ImGui::CalcTextSize("M"); + const float count_letter_M_in_input = 12.f; + cfg.input_width = letter_m_size.x * count_letter_M_in_input; + cfg.texture_max_size_px = std::round((cfg.input_width + cfg.input_offset + cfg.icon_width + space) / 8) * 8; + + cfg.max_tooltip_width = ImGui::GetFontSize() * 20.0f; + + return cfg; +} + +// use private definition +struct GLGizmoSVG::GuiCfg : public ::GuiCfg +{}; +// Define rendered version of icon +enum class IconState : unsigned { activable = 0, hovered /*1*/, disabled /*2*/ }; +// selector for icon by enum +const IconManager::Icon &get_icon(const IconManager::VIcons &icons, IconType type, IconState state) { return *icons[(unsigned) type][(unsigned) state]; } + +IconManager::VIcons init_icons(IconManager &mng, const GuiCfg &cfg) +{ + mng.release(); + + ImVec2 size(cfg.icon_width, cfg.icon_width); + // icon order has to match the enum IconType + std::vector filenames{ + "text_undo.svg", // reset_value + "text_refresh.svg", // refresh + "text_open.svg", // changhe_file + "text_bake.svg", // bake + "text_save.svg", // save + "text_obj_warning.svg", // exclamation // ORCA: use obj_warning instead exclamation. exclamation is not compatible with low res + "text_lock_closed.svg", // lock + "text_lock_open.svg", // unlock + "text_reflection_x.svg", // reflection_x + "text_reflection_y.svg", // reflection_y + }; + + assert(filenames.size() == static_cast(IconType::_count)); + std::string path = resources_dir() + "/images/"; + for (std::string &filename : filenames) + filename = path + filename; + + auto type = IconManager::RasterType::color_wite_gray; + return mng.init(filenames, size, type); +} + +bool draw_clickable(const IconManager::VIcons &icons, IconType type) { + return clickable(get_icon(icons, type, IconState::activable), get_icon(icons, type, IconState::hovered)); +} + +bool reset_button(const IconManager::VIcons &icons) +{ + float reset_offset = ImGui::GetStyle().WindowPadding.x; + ImGui::SameLine(reset_offset); + // from GLGizmoCut + // std::string label_id = "neco"; + // std::string btn_label; + // btn_label += ImGui::RevertButton; + // return ImGui::Button((btn_label + "##" + label_id).c_str()); + return draw_clickable(icons, IconType::reset_value); +} + +bool GLGizmoSVG::create_volume(ModelVolumeType volume_type) +{ + Emboss::CreateVolumeParams input = create_input(m_parent, volume_type); + Emboss::DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type); + if (!base) + return false; // Uninterpretable svg + return start_create_volume_without_position(input, std::move(base)); +} + +bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) +{ + Emboss::CreateVolumeParams input = create_input(m_parent, volume_type); + Emboss::DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type); + if (!base) + return false; // Uninterpretable svg + return start_create_volume(input, std::move(base), mouse_pos); +} + +bool GLGizmoSVG::create_volume(std::string_view svg_file, const Vec2d &mouse_pos, ModelVolumeType volume_type) +{//drag file//drag svg_file + wxBusyCursor wait; + Emboss::CreateVolumeParams input = create_input(m_parent, volume_type); + Emboss::DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type, svg_file); + if (!base) + return false; // Uninterpretable svg + return start_create_volume(input, std::move(base), mouse_pos); +} + +bool GLGizmoSVG::create_volume(std::string_view svg_file, ModelVolumeType volume_type) +{ + Emboss::CreateVolumeParams input = create_input(m_parent, volume_type); + Emboss::DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type, svg_file); + if (!base) + return false; // Uninterpretable svg + return start_create_volume_without_position(input, std::move(base)); +} + +bool GLGizmoSVG::is_svg_object(const ModelVolume &volume) +{ + if (!volume.emboss_shape.has_value()) return false; + if (volume.type() != ModelVolumeType::MODEL_PART) return false; + for (const ModelVolume *v : volume.get_object()->volumes) { + if (v->id() == volume.id()) continue; + if (v->type() == ModelVolumeType::MODEL_PART) return false; + } + return true; +} + +bool GLGizmoSVG::is_svg(const ModelVolume &volume) { + return volume.emboss_shape.has_value() && volume.emboss_shape->svg_file.has_value(); +} + +bool GLGizmoSVG::on_init() +{ + m_rotate_gizmo.init(); + ColorRGBA gray_color(.6f, .6f, .6f, .3f); + m_rotate_gizmo.set_highlight_color(gray_color.get_data()); + // Set rotation gizmo upwardrotate + m_rotate_gizmo.set_angle(PI / 2); + return true; +} + +std::string GLGizmoSVG::on_get_name() const +{ + return "SVG"; +} + +bool GLGizmoSVG::on_is_activable() const +{ + const Selection &selection = m_parent.get_selection(); + if (selection.is_empty()) { + return true; + } + return !selection.is_any_connector();//maybe is negitive volume or modifier volume +} + +void GLGizmoSVG::on_set_state() { + // enable / disable bed from picking + // Rotation gizmo must work through bed + // m_parent.set_raycaster_gizmos_on_top(GLGizmoBase::m_state == GLGizmoBase::On); + m_rotate_gizmo.set_state(GLGizmoBase::m_state); + + // Closing gizmo. e.g. selecting another one + if (GLGizmoBase::m_state == GLGizmoBase::Off) { + m_parent.enable_moving(true); // modify by qds + reset_volume(); + } else if (GLGizmoBase::m_state == GLGizmoBase::On) { + if (on_is_activable()) { + // Try(when exist) set text configuration by volume + set_volume_by_selection(); + if (m_volume) { + register_single_mesh_pick(); + } + } + } +} + +void GLGizmoSVG::on_start_dragging() +{ + if (m_hover_id < 0) { return;} + if (m_hover_id == c_move_cube_id) { + } + else { + m_rotate_gizmo.start_dragging(); + } +} + +void GLGizmoSVG::on_stop_dragging() +{ + if (m_hover_id == c_move_cube_id) { + } else { + m_rotate_gizmo.stop_dragging(); + // TODO: when start second rotatiton previous rotation rotate draggers + // This is fast fix for second try to rotate + // When fixing, move grabber above text (not on side) + m_rotate_gizmo.set_angle(PI / 2); + + // apply rotation + // TRN This is an item label in the undo-redo stack. + m_parent.do_rotate(rotation_snapshot_name); + m_rotate_start_angle.reset(); + volume_transformation_changed(); + // recalculate for surface cut + if (m_volume != nullptr && m_volume->emboss_shape.has_value() && m_volume->emboss_shape->projection.use_surface) + process_job(); + } +} + +bool GLGizmoSVG::on_mouse(const wxMouseEvent &mouse_event) +{ // not selected volume + if (m_volume == nullptr || get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || !m_volume->emboss_shape.has_value()) + return false; + if (m_hover_id != c_move_cube_id) { + if (on_mouse_for_rotation(mouse_event)) + return true; + } + if (m_hover_id == c_move_cube_id) { + if (mouse_event.Moving()) + return false; + bool used = use_grabbers(mouse_event); + if (on_mouse_for_translate(mouse_event)) + return true; + } + return false; +} + +void GLGizmoSVG::on_update(const UpdateData &data) { + if (m_hover_id == c_move_cube_id) { + } + else { + m_rotate_gizmo.update(data); + } +} + +void GLGizmoSVG::on_render() +{ + if (const Selection &selection = m_parent.get_selection(); selection.volumes_count() != 1 || // only one selected volume + m_volume == nullptr || // already selected volume in gizmo + get_model_volume(m_volume_id, selection.get_model()->objects) == nullptr) // still exist model + return; + if (!m_svg_volume) { + return; + } + bool is_surface_dragging = m_surface_drag.has_value(); + bool is_parent_dragging = m_parent.is_mouse_dragging(); + // Do NOT render rotation grabbers when dragging object + bool is_rotate_by_grabbers = m_dragging; + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + Geometry::Transformation tran(m_svg_volume->world_matrix()); + if (!m_volume->is_the_only_one_part()) { + m_parent.enable_moving(false); // modify by qds + bool hover = (m_hover_id == c_move_cube_id); + std::array render_color; + if (hover) { + render_color = SVG_Move_GrabberHoverColor; + } else + render_color = SVG_Move_GrabberColor; + float fullsize = get_grabber_size(); + m_move_grabber.center = tran.get_offset(); + Transform3d rotate_matrix = tran.get_rotation_matrix(); + Transform3d cube_mat = Geometry::translation_transform(m_move_grabber.center) * rotate_matrix * Geometry::scale_transform(fullsize); + render_glmodel(m_move_grabber.get_cube(), render_color, cube_mat); + } +#ifdef DEBUG_SVG + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(tran.get_matrix().data())); + render_cross_mark(Vec3f::Zero(), true); + glsafe(::glPopMatrix()); +#endif + if (is_rotate_by_grabbers || (!is_surface_dragging && !is_parent_dragging)) { + if (m_hover_id == c_move_cube_id && m_dragging) { + } + else { + m_rotate_gizmo.render(); + } + } +} + +void GLGizmoSVG::on_render_for_picking() +{ + glsafe(::glDisable(GL_DEPTH_TEST)); + m_rotate_gizmo.render_for_picking(); + + auto color = picking_color_component(c_move_cube_id); + m_move_grabber.color[0] = color[0]; + m_move_grabber.color[1] = color[1]; + m_move_grabber.color[2] = color[2]; + m_move_grabber.color[3] = color[3]; + m_move_grabber.render_for_picking(); +} + +//QDS: add input window for move +void GLGizmoSVG::on_render_input_window(float x, float y, float bottom_limit) +{ + set_volume_by_selection(); + double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor(); + // Configuration creation + if (m_gui_cfg == nullptr || // Exist configuration - first run + m_gui_cfg->screen_scale != screen_scale || // change of DPI + m_gui_cfg->dark_mode != m_is_dark_mode // change of dark mode + ) { + // Create cache for gui offsets + ::GuiCfg cfg = create_gui_configuration(); + cfg.screen_scale = screen_scale; + cfg.dark_mode = m_is_dark_mode; + + GuiCfg gui_cfg{std::move(cfg)}; + m_gui_cfg = std::make_unique(std::move(gui_cfg)); + + m_icons = init_icons(m_icon_manager, *m_gui_cfg); // need regeneration when change resolution(move between monitors) + } + + static float last_y = 0.0f; + static float last_h = 0.0f; + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) last_h = win_h; + if (last_y != y) last_y = y; + } + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + GizmoImguiBegin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + + draw_window(); + + GizmoImguiEnd(); + ImGuiWrapper::pop_toolbar_style(); +} + +void GLGizmoSVG::set_volume_by_selection() +{ + const Selection &selection = m_parent.get_selection(); + const GLVolume * gl_volume = get_selected_gl_volume(selection); + + if (gl_volume == nullptr) + return reset_volume(); + + const ModelObjectPtrs &objects = selection.get_model()->objects; + ModelVolume * volume = get_model_volume(*gl_volume, objects); + if (volume == nullptr) + return reset_volume(); + + // is same volume as actual selected? + if (volume->id() == m_volume_id) + return; + + // Do not use focused input value when switch volume(it must swith value) + if (m_volume != nullptr && m_volume != volume) // when update volume it changed id BUT not pointer + ImGuiWrapper::left_inputs(); + + // is valid svg volume? + if (!is_svg(*volume)) + return reset_volume(); + + // cancel previous job + if (m_job_cancel != nullptr) { + m_job_cancel->store(true); + m_job_cancel = nullptr; + } + + // calculate scale for height and depth inside of scaled object instance + calculate_scale(); // must be before calculation of tesselation + + // checking that exist is inside of function "is_svg" + EmbossShape & es = *volume->emboss_shape; + EmbossShape::SvgFile &svg_file = *es.svg_file; + if (svg_file.image == nullptr) { + if (init_image(svg_file) == nullptr) + return reset_volume(); + } + + assert(svg_file.image != nullptr); + assert(svg_file.image.get() != nullptr); + const NSVGimage & image = *svg_file.image; + ExPolygonsWithIds &shape_ids = es.shapes_with_ids; + if (shape_ids.empty()) { + NSVGLineParams params{get_tesselation_tolerance(get_scale_for_tolerance())}; + shape_ids = create_shape_with_ids(image, params); + } + reset_volume(); // clear cached data + + m_svg_volume = gl_volume; + m_volume = volume; + m_volume_id = volume->id(); + m_volume_shape = es; // copy + m_shape_warnings = create_shape_warnings(es, get_scale_for_tolerance()); + + // Calculate current angle of up vector + m_angle = calc_angle(selection); + m_distance = calc_distance(*gl_volume, m_raycast_manager, m_parent); + + m_shape_bb = get_extents(m_volume_shape.shapes_with_ids); + m_origin_shape_bb = m_shape_bb; +} + +void GLGizmoSVG::delete_texture(Emboss::Texture &texture) +{ + if (texture.id != 0) { + glsafe(::glDeleteTextures(1, &texture.id)); + texture.id = 0; + } +} + +void GLGizmoSVG::reset_volume() { + if (m_volume == nullptr) + return; // already reseted + m_svg_volume = nullptr; + m_volume = nullptr; + m_volume_id.id = 0; + m_volume_shape.shapes_with_ids.clear(); + m_filename_preview.clear(); + m_shape_warnings.clear(); + // delete texture after finish imgui draw + wxGetApp().plater()->CallAfter([this]() { + delete_texture(m_texture); + }); +} + +void GLGizmoSVG::calculate_scale() { + // be carefull m_volume is not set yet + const Selection &selection = m_parent.get_selection(); + const GLVolume * gl_volume = selection.get_first_volume(); + if (gl_volume == nullptr) return; + + Transform3d to_world = gl_volume->world_matrix(); + + const ModelVolume *volume_ptr = get_model_volume(*gl_volume, selection.get_model()->objects); + assert(volume_ptr != nullptr); + assert(volume_ptr->emboss_shape.has_value()); + // Fix for volume loaded from 3mf + if (volume_ptr != nullptr && volume_ptr->emboss_shape.has_value()) { + const std::optional &fix_tr = volume_ptr->emboss_shape->fix_3mf_tr; + if (fix_tr.has_value()) to_world = to_world * (fix_tr->inverse()); + } + + auto to_world_linear = to_world.linear(); + auto calc = [&to_world_linear](const Vec3d &axe, std::optional &scale) { + Vec3d axe_world = to_world_linear * axe; + double norm_sq = axe_world.squaredNorm(); + if (is_approx(norm_sq, 1.)) { + if (!scale.has_value()) return; + scale.reset(); + } else { + scale = sqrt(norm_sq); + } + }; + + calc(Vec3d::UnitX(), m_scale_width); + calc(Vec3d::UnitY(), m_scale_height); + calc(Vec3d::UnitZ(), m_scale_depth); +} + +// inspired by Xiaolin Wu's line algorithm - https://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm +// Draw inner part of polygon CCW line as full brightness(edge of expolygon) +void wu_draw_line_side(Linef line, const std::function &plot) +{ + auto ipart = [](float x) -> int { return static_cast(std::floor(x)); }; + auto round = [](float x) -> float { return std::round(x); }; + auto fpart = [](float x) -> float { return x - std::floor(x); }; + auto rfpart = [=](float x) -> float { return 1 - fpart(x); }; + + Vec2d d = line.b - line.a; + const bool steep = abs(d.y()) > abs(d.x()); + bool is_full; // identify full brightness pixel + if (steep) { + is_full = d.y() >= 0; + std::swap(line.a.x(), line.a.y()); + std::swap(line.b.x(), line.b.y()); + std::swap(d.x(), d.y()); + } else + is_full = d.x() < 0; // opposit direction of y + + if (line.a.x() > line.b.x()) { + std::swap(line.a.x(), line.b.x()); + std::swap(line.a.y(), line.b.y()); + d *= -1; + } + const float gradient = (d.x() == 0) ? 1. : d.y() / d.x(); + + int xpx11; + float intery; + { + const float xend = round(line.a.x()); + const float yend = line.a.y() + gradient * (xend - line.a.x()); + const float xgap = rfpart(line.a.x() + 0.5f); + xpx11 = int(xend); + const int ypx11 = ipart(yend); + if (steep) { + plot(ypx11, xpx11, is_full ? 1.f : (rfpart(yend) * xgap)); + plot(ypx11 + 1, xpx11, !is_full ? 1.f : (fpart(yend) * xgap)); + } else { + plot(xpx11, ypx11, is_full ? 1.f : (rfpart(yend) * xgap)); + plot(xpx11, ypx11 + 1, !is_full ? 1.f : (fpart(yend) * xgap)); + } + intery = yend + gradient; + } + + int xpx12; + { + const float xend = round(line.b.x()); + const float yend = line.b.y() + gradient * (xend - line.b.x()); + const float xgap = rfpart(line.b.x() + 0.5f); + xpx12 = int(xend); + const int ypx12 = ipart(yend); + if (steep) { + plot(ypx12, xpx12, is_full ? 1.f : (rfpart(yend) * xgap)); + plot(ypx12 + 1, xpx12, !is_full ? 1.f : (fpart(yend) * xgap)); + } else { + plot(xpx12, ypx12, is_full ? 1.f : (rfpart(yend) * xgap)); + plot(xpx12, ypx12 + 1, !is_full ? 1.f : (fpart(yend) * xgap)); + } + } + + if (steep) { + if (is_full) { + for (int x = xpx11 + 1; x < xpx12; x++) { + plot(ipart(intery), x, 1.f); + plot(ipart(intery) + 1, x, fpart(intery)); + intery += gradient; + } + } else { + for (int x = xpx11 + 1; x < xpx12; x++) { + plot(ipart(intery), x, rfpart(intery)); + plot(ipart(intery) + 1, x, 1.f); + intery += gradient; + } + } + } else { + if (is_full) { + for (int x = xpx11 + 1; x < xpx12; x++) { + plot(x, ipart(intery), 1.f); + plot(x, ipart(intery) + 1, fpart(intery)); + intery += gradient; + } + } else { + for (int x = xpx11 + 1; x < xpx12; x++) { + plot(x, ipart(intery), rfpart(intery)); + plot(x, ipart(intery) + 1, 1.f); + intery += gradient; + } + } + } +} + +template // N .. count of channels per pixel +void draw_side_outline(const ExPolygons &shape, const std::array &color, std::vector &data, size_t data_width, double scale) +{ + int count_lines = data.size() / (N * data_width); + size_t data_line = N * data_width; + auto get_offset = [count_lines, data_line](int x, int y) { + // NOTE: y has opposit direction in texture + return (count_lines - y - 1) * data_line + x * N; + }; + + // overlap color + auto draw = [&data, data_width, count_lines, get_offset, &color](int x, int y, float brightess) { + if (x < 0 || y < 0 || static_cast(x) >= data_width || y >= count_lines) return; // out of image + size_t offset = get_offset(x, y); + bool change_color = false; + for (size_t i = 0; i < N - 1; ++i) { + if (data[offset + i] != color[i]) { + data[offset + i] = color[i]; + change_color = true; + } + } + + unsigned char &alpha = data[offset + N - 1]; + if (alpha == 0 || change_color) { + alpha = static_cast(std::round(brightess * 255)); + } else if (alpha != 255) { + alpha = static_cast(std::min(255, int(alpha) + static_cast(std::round(brightess * 255)))); + } + }; + + BoundingBox bb_unscaled = get_extents(shape); + Linesf lines = to_linesf(shape); + BoundingBoxf bb(bb_unscaled.min.cast(), bb_unscaled.max.cast()); + + // scale lines to pixels + if (!is_approx(scale, 1.)) { + for (Linef &line : lines) { + line.a *= scale; + line.b *= scale; + } + bb.min *= scale; + bb.max *= scale; + } + + for (const Linef &line : lines) + wu_draw_line_side(line, draw); +} + +/// +/// Draw filled ExPolygon into data +/// line by line inspired by: http://alienryderflex.com/polygon_fill/ +/// +/// Count channels for one pixel(RGBA = 4) +/// Shape to draw +/// Color of shape contain count of channels(N) +/// Image(2d) stored in 1d array +/// Count of pixel on one line(size in data = N x data_width) +/// Shape scale for conversion to pixels +template // N .. count of channels per pixel +void draw_filled(const ExPolygons &shape, const std::array &color, std::vector &data, size_t data_width, double scale) +{ + assert(data.size() % N == 0); + assert(data.size() % data_width == 0); + assert((data.size() % (N * data_width)) == 0); + + BoundingBox bb_unscaled = get_extents(shape); + + Linesf lines = to_linesf(shape); + BoundingBoxf bb(bb_unscaled.min.cast(), bb_unscaled.max.cast()); + + // scale lines to pixels + if (!is_approx(scale, 1.)) { + for (Linef &line : lines) { + line.a *= scale; + line.b *= scale; + } + bb.min *= scale; + bb.max *= scale; + } + + int count_lines = data.size() / (N * data_width); + size_t data_line = N * data_width; + auto get_offset = [count_lines, data_line](int x, int y) { + // NOTE: y has opposit direction in texture + return (count_lines - y - 1) * data_line + x * N; + }; + auto set_color = [&data, &color, get_offset](int x, int y) { + size_t offset = get_offset(x, y); + if (data[offset + N - 1] != 0) return; // already setted by line + for (unsigned i = 0; i < N; ++i) data[offset + i] = color[i]; + }; + + // anti aliased drawing of lines + auto draw = [&data, width = static_cast(data_width), count_lines, get_offset, &color](int x, int y, float brightess) { + if (x < 0 || y < 0 || x >= width || y >= count_lines) return; // out of image + size_t offset = get_offset(x, y); + unsigned char &alpha = data[offset + N - 1]; + if (alpha == 0) { + alpha = static_cast(std::round(brightess * 255)); + for (size_t i = 0; i < N - 1; ++i) data[offset + i] = color[i]; + } else if (alpha != 255) { + alpha = static_cast(std::min(255, int(alpha) + static_cast(std::round(brightess * 255)))); + } + }; + + for (const Linef &line : lines) wu_draw_line_side(line, draw); + + auto tree = Slic3r::AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + + // range for intersection line + double x1 = bb.min.x() - 1.f; + double x2 = bb.max.x() + 1.f; + + int max_y = std::min(count_lines, static_cast(std::round(bb.max.y()))); + for (int y = std::max(0, static_cast(std::round(bb.min.y()))); y < max_y; ++y) { + double y_f = y + .5; // 0.5 ... intersection in center of pixel of pixel + Linef line(Vec2d(x1, y_f), Vec2d(x2, y_f)); + using Intersection = std::pair; + using Intersections = std::vector; + // sorted .. false + // + Intersections intersections = Slic3r::AABBTreeLines::get_intersections_with_line(lines, tree, line); + if (intersections.empty()) continue; + + assert((intersections.size() % 2) == 0); + + // sort intersections by x + std::sort(intersections.begin(), intersections.end(), [](const Intersection &i1, const Intersection &i2) { return i1.first.x() < i2.first.x(); }); + + // draw lines + for (size_t i = 0; i < intersections.size(); i += 2) { + const Vec2d &p2 = intersections[i + 1].first; + if (p2.x() < 0) continue; // out of data + + const Vec2d &p1 = intersections[i].first; + if (p1.x() > data_width) break; // out of data + + // clamp to data + int max_x = std::min(static_cast(data_width - 1), static_cast(std::round(p2.x()))); + for (int x = std::max(0, static_cast(std::round(p1.x()))); x <= max_x; ++x) set_color(x, y); + } + } +} + +/// Union shape defined by glyphs +ExPolygons union_ex(const ExPolygonsWithIds &shapes) +{ + // unify to one expolygon + ExPolygons result; + for (const ExPolygonsWithId &shape : shapes) { + if (shape.expoly.empty()) continue; + expolygons_append(result, shape.expoly); + } + return union_ex(result); +} + +// init texture by draw expolygons into texture +bool GLGizmoSVG::init_texture(Emboss::Texture &texture, const ExPolygonsWithIds &shapes_with_ids, unsigned max_size_px, const std::vector &shape_warnings) +{ + BoundingBox bb = get_extents(shapes_with_ids); + Point bb_size = bb.size(); + double bb_width = bb_size.x(); // [in mm] + double bb_height = bb_size.y(); // [in mm] + + bool is_widder = bb_size.x() > bb_size.y(); + double scale = 0.f; + if (is_widder) { + scale = max_size_px / bb_width; + texture.width = max_size_px; + texture.height = static_cast(std::ceil(bb_height * scale)); + } else { + scale = max_size_px / bb_height; + texture.width = static_cast(std::ceil(bb_width * scale)); + texture.height = max_size_px; + } + const int n_pixels = texture.width * texture.height; + if (n_pixels <= 0) return false; + + constexpr int channels_count = 4; + std::vector data(n_pixels * channels_count, {0}); + + // Union All shapes + ExPolygons shape = union_ex(shapes_with_ids); + + // align to texture + translate(shape, -bb.min); + size_t texture_width = static_cast(texture.width); + unsigned char alpha = 255; // without transparency + std::array color_shape{201, 201, 201, alpha}; // from degin by @JosefZachar + std::array color_error{237, 28, 36, alpha}; // from icon: resources/icons/flag_red.svg + std::array color_warning{237, 107, 33, alpha}; // icons orange + // draw unhealedable shape + for (const ExPolygonsWithId &shapes_with_id : shapes_with_ids) + if (!shapes_with_id.is_healed) { + ExPolygons bad_shape = shapes_with_id.expoly; // copy + translate(bad_shape, -bb.min); // align to texture + draw_side_outline<4>(bad_shape, color_error, data, texture_width, scale); + } + // Draw shape with warning + if (!shape_warnings.empty()) { + for (const ExPolygonsWithId &shapes_with_id : shapes_with_ids) { + assert(shapes_with_id.id < shape_warnings.size()); + if (shapes_with_id.id >= shape_warnings.size()) continue; + if (shape_warnings[shapes_with_id.id].empty()) continue; // no warnings for shape + ExPolygons warn_shape = shapes_with_id.expoly; // copy + translate(warn_shape, -bb.min); // align to texture + draw_side_outline<4>(warn_shape, color_warning, data, texture_width, scale); + } + } + + // Draw rest of shape + draw_filled<4>(shape, color_shape, data, texture_width, scale); + + // sends data to gpu + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + if (texture.id != 0) + glsafe(::glDeleteTextures(1, &texture.id)); + glsafe(::glGenTextures(1, &texture.id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, texture.id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) texture.width, (GLsizei) texture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void *) data.data())); + + 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)); + + GLuint NO_TEXTURE_ID = 0; + glsafe(::glBindTexture(GL_TEXTURE_2D, NO_TEXTURE_ID)); + return true; +} + +bool is_closed(NSVGpath *path) +{ + for (; path != NULL; path = path->next) + if (path->next == NULL && path->closed) return true; + return false; +} + +void add_comma_separated(std::string &result, const std::string &add) +{ + if (!result.empty()) result += ", "; + result += add; +} + +const float warning_preccission = 1e-4f; +std::string create_fill_warning(const NSVGshape &shape) +{ + if (!(shape.flags & NSVG_FLAGS_VISIBLE) || shape.fill.type == NSVG_PAINT_NONE) return {}; // not visible + + std::string warning; + if ((shape.opacity - 1.f + warning_preccission) <= 0.f) + add_comma_separated(warning, GUI::format(_L("Opacity (%1%)"), shape.opacity)); + + // if(shape->flags != NSVG_FLAGS_VISIBLE) add_warning(_u8L("Visibility flag")); + bool is_fill_gradient = shape.fillGradient[0] != '\0'; + if (is_fill_gradient) add_comma_separated(warning, GUI::format(_L("Color gradient (%1%)"), shape.fillGradient)); + + switch (shape.fill.type) { + case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined fill type")); break; + case NSVG_PAINT_LINEAR_GRADIENT: + if (!is_fill_gradient) add_comma_separated(warning, _u8L("Linear gradient")); + break; + case NSVG_PAINT_RADIAL_GRADIENT: + if (!is_fill_gradient) add_comma_separated(warning, _u8L("Radial gradient")); + break; + // case NSVG_PAINT_NONE: + // case NSVG_PAINT_COLOR: + // default: break; + } + + // Unfilled is only line which could be opened + if (shape.fill.type != NSVG_PAINT_NONE && !is_closed(shape.paths)) add_comma_separated(warning, _u8L("Open filled path")); + return warning; +} + +std::string create_stroke_warning(const NSVGshape &shape) +{ + std::string warning; + if (!(shape.flags & NSVG_FLAGS_VISIBLE) || shape.stroke.type == NSVG_PAINT_NONE || shape.strokeWidth <= 1e-5f) return {}; // not visible + + if ((shape.opacity - 1.f + warning_preccission) <= 0.f) add_comma_separated(warning, GUI::format(_L("Opacity (%1%)"), shape.opacity)); + + bool is_stroke_gradient = shape.strokeGradient[0] != '\0'; + if (is_stroke_gradient) add_comma_separated(warning, GUI::format(_L("Color gradient (%1%)"), shape.strokeGradient)); + + switch (shape.stroke.type) { + case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined stroke type")); break; + case NSVG_PAINT_LINEAR_GRADIENT: + if (!is_stroke_gradient) add_comma_separated(warning, _u8L("Linear gradient")); + break; + case NSVG_PAINT_RADIAL_GRADIENT: + if (!is_stroke_gradient) add_comma_separated(warning, _u8L("Radial gradient")); + break; + // case NSVG_PAINT_COLOR: + // case NSVG_PAINT_NONE: + // default: break; + } + + return warning; +} + +float GLGizmoSVG::get_scale_for_tolerance() +{ + return std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)); } + +/// +/// Create warnings about shape +/// +/// Input svg loaded to shapes +/// Vector of warnings with same size as EmbossShape::shapes_with_ids +/// or Empty when no warnings -> for fast checking that every thing is all right(more common case) +std::vector GLGizmoSVG::create_shape_warnings(const EmbossShape &shape, float scale) +{ + const std::shared_ptr &image_ptr = shape.svg_file->image; + assert(image_ptr != nullptr); + if (image_ptr == nullptr) return {std::string{"Uninitialized SVG image"}}; + + const NSVGimage & image = *image_ptr; + std::vector result; + auto add_warning = [&result, &image](size_t index, const std::string &message) { + if (result.empty()) result = std::vector(get_shapes_count(image) * 2); + std::string &res = result[index]; + if (res.empty()) + res = message; + else + res += '\n' + message; + }; + + if (!shape.final_shape.is_healed) { + for (const ExPolygonsWithId &i : shape.shapes_with_ids) + if (!i.is_healed) add_warning(i.id, _u8L("Path can't be healed from selfintersection and multiple points.")); + + // This waning is not connected to NSVGshape. It is about union of paths, but Zero index is shown first + size_t index = 0; + add_warning(index, _u8L("Final shape constains selfintersection or multiple points with same coordinate.")); + } + + size_t shape_index = 0; + for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next, ++shape_index) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) { + add_warning(shape_index * 2, GUI::format(_L("Shape is marked as invisible (%1%)."), shape->id)); + continue; + } + + std::string fill_warning = create_fill_warning(*shape); + if (!fill_warning.empty()) { + // TRN: The first placeholder is shape identifier, the second one is text describing the problem. + add_warning(shape_index * 2, GUI::format(_L("Fill of shape (%1%) contains unsupported: %2%."), shape->id, fill_warning)); + } + + float minimal_width_in_mm = 1e-3f; + if (shape->strokeWidth <= minimal_width_in_mm * scale) { + add_warning(shape_index * 2, GUI::format(_L("Stroke of shape (%1%) is too thin (minimal width is %2% mm)."), shape->id, minimal_width_in_mm)); + continue; + } + std::string stroke_warning = create_stroke_warning(*shape); + if (!stroke_warning.empty()) add_warning(shape_index * 2 + 1, GUI::format(_L("Stroke of shape (%1%) contains unsupported: %2%."), shape->id, stroke_warning)); + } + return result; +} + +bool GLGizmoSVG::process_job(bool make_snapshot) +{ // no volume is selected -> selection from right panel + assert(m_volume != nullptr); + if (m_volume == nullptr) return false; + + assert(m_volume->emboss_shape.has_value()); + if (!m_volume->emboss_shape.has_value()) return false; + + // Cancel previous Job, when it is in process + // worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs + // Cancel only EmbossUpdateJob no others + if (m_job_cancel != nullptr) + m_job_cancel->store(true); + // create new shared ptr to cancel new job + m_job_cancel = std::make_shared>(false); + + EmbossShape shape = m_volume_shape; // copy + auto base = std::make_unique(m_volume->name, m_job_cancel, std::move(shape)); + base->is_outside = m_volume->type() == ModelVolumeType::MODEL_PART; + Emboss::DataUpdate data{std::move(base), m_volume_id, make_snapshot}; + return start_update_volume(std::move(data), *m_volume, m_parent.get_selection(), m_raycast_manager); +} + +void GLGizmoSVG::close() +{ // close gizmo == open it again + auto &mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() == GLGizmosManager::Svg) + mng.open_gizmo(GLGizmosManager::Svg); + reset_volume(); +} + +void GLGizmoSVG::draw_window() +{ + assert(m_volume != nullptr); + assert(m_volume_id.valid()); + if (m_volume == nullptr || m_volume_id.invalid()) { + m_imgui->text(_L("Not valid state please report reproduction steps on github")); + return; + } + + assert(m_volume->emboss_shape.has_value()); + if (!m_volume->emboss_shape.has_value()) { + m_imgui->text(_L("No embossed file")); + return; + } + + assert(m_volume->emboss_shape->svg_file.has_value()); + if (!m_volume->emboss_shape->svg_file.has_value()) { + m_imgui->text(_L("Missing svg file in embossed shape")); + return; + } + + assert(m_volume->emboss_shape->svg_file->file_data != nullptr); + if (m_volume->emboss_shape->svg_file->file_data == nullptr) { + m_imgui->text(_L("Missing data of svg file")); + return; + } + draw_preview(); + + draw_filename(); + // Is SVG baked? + if (m_volume == nullptr) + return; + ImGui::Separator(); + + ImGui::Indent(m_gui_cfg->icon_width); + draw_depth(); + draw_size(); + + m_can_use_surface = (m_volume->emboss_shape->projection.use_surface) ? true : // already used surface must have option to uncheck + !m_volume->is_the_only_one_part(); + if (m_can_use_surface) { + draw_use_surface(); + draw_distance(); + } + draw_rotation(); + draw_mirroring(); + //draw_face_the_camera(); + + ImGui::Unindent(m_gui_cfg->icon_width); + + if (!m_volume->is_the_only_one_part()) { + ImGui::Separator(); + draw_model_type(); + } + if (!m_can_use_surface) { + m_imgui->text(_L("Tip:If you want to place svg file on another part surface,\nyou should select part first, and then drag svg file to the part surface.")); + } +} + +void GLGizmoSVG::draw_preview() +{// init texture when not initialized yet. + // drag&drop is out of rendering scope so texture must be created on this place + if (m_texture.id == 0) { + const ExPolygonsWithIds &shapes = m_volume->emboss_shape->shapes_with_ids; + init_texture(m_texture, shapes, m_gui_cfg->texture_max_size_px, m_shape_warnings); + } + + //::draw(m_volume_shape.shapes_with_ids, m_gui_cfg->texture_max_size_px); + + if (m_texture.id != 0) { + ImTextureID id = (void *) static_cast(m_texture.id); + ImVec2 s(m_texture.width, m_texture.height); + + std::optional spacing; + // is texture over full height? + if (m_texture.height != m_gui_cfg->texture_max_size_px) { + spacing = (m_gui_cfg->texture_max_size_px - m_texture.height) / 2.f; + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + *spacing); + } + // is texture over full width? + unsigned window_width = static_cast(ImGui::GetWindowSize().x - 2 * ImGui::GetStyle().WindowPadding.x); + if (window_width > m_texture.width) { + float space = (window_width - m_texture.width) / 2.f; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + space); + } + + ImGui::Image(id, s); + // if(ImGui::IsItemHovered()){ + // const EmbossShape &es = *m_volume->emboss_shape; + // size_t count_of_shapes = get_shapes_count(*es.svg_file.image); + // size_t count_of_expolygons = 0; + // size_t count_of_points = 0; + // for (const auto &shape : es.shapes_with_ids) { + // for (const ExPolygon &expoly : shape.expoly){ + // ++count_of_expolygons; + // count_of_points += count_points(expoly); + // } + // } + // // Do not translate it is only for debug + // std::string tooltip = GUI::format("%1% shapes, which create %2% polygons with %3% line segments", + // count_of_shapes, count_of_expolygons, count_of_points); + // ImGui::SetTooltip("%s", tooltip.c_str()); + //} + + if (spacing.has_value()) ImGui::SetCursorPosY(ImGui::GetCursorPosY() + *spacing); + } +} + +void GLGizmoSVG::draw_filename() +{ + const EmbossShape & es = *m_volume->emboss_shape; + const EmbossShape::SvgFile &svg = *es.svg_file; + if (m_filename_preview.empty()) { + // create filename preview + if (!svg.path.empty()) { + m_filename_preview = get_file_name(svg.path); + } else if (!svg.path_in_3mf.empty()) { + m_filename_preview = get_file_name(svg.path_in_3mf); + } + + if (m_filename_preview.empty()) + // TRN - Preview of filename after clear local filepath. + m_filename_preview = _u8L("Unknown filename"); + + m_filename_preview = ImGuiWrapper::trunc(m_filename_preview, m_gui_cfg->input_width); + } + + if (!m_shape_warnings.empty()) { + draw(get_icon(m_icons, IconType::exclamation, IconState::hovered)); + if (ImGui::IsItemHovered()) { + std::string tooltip; + for (const std::string &w : m_shape_warnings) { + if (w.empty()) continue; + if (!tooltip.empty()) tooltip += "\n"; + tooltip += w; + } + m_imgui->tooltip(tooltip, m_gui_cfg->max_tooltip_width); + } + ImGui::SameLine(); + } + + // Remove space between filename and gray suffix ".svg" + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", m_filename_preview.c_str()); + bool is_hovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + m_imgui->text_colored(ImGuiWrapper::COL_GREY_LIGHT, ".svg"); + ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing + + is_hovered |= ImGui::IsItemHovered(); + if (is_hovered) { + wxString tooltip = GUI::format_wxstr(_L("SVG file path is \"%1%\""), svg.path); + m_imgui->tooltip(tooltip, m_gui_cfg->max_tooltip_width); + } + + bool file_changed = false; + + // Re-Load button + bool can_reload = !m_volume_shape.svg_file->path.empty(); + if (can_reload) { + ImGui::SameLine(); + if (draw_clickable(m_icons, IconType::refresh)) { + if (!boost::filesystem::exists(m_volume_shape.svg_file->path)) { + m_volume_shape.svg_file->path.clear(); + } else { + file_changed = true; + } + } else if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Reload SVG file from disk."), m_gui_cfg->max_tooltip_width); + } + + wxString tooltip = ""; + ImGuiComboFlags flags = ImGuiComboFlags_PopupAlignLeft | ImGuiComboFlags_NoPreview; + ImGui::SameLine(); + ImGuiWrapper::push_combo_style(m_parent.get_scale()); + if (ImGui::BeginCombo("##file_options", nullptr, flags)) { + ScopeGuard combo_sg([]() { ImGui::EndCombo(); }); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {ImGui::GetStyle().FramePadding.x, 0}); + draw(get_icon(m_icons, IconType::change_file, IconState::hovered)); + ImGui::SameLine(); + if (ImGui::Selectable((_L("Change file") + dots).ToUTF8().data())) { + std::string new_path = choose_svg_file(); + if (!new_path.empty()) { + file_changed = true; + EmbossShape::SvgFile svg_file_new; + svg_file_new.path = new_path; + m_volume_shape.svg_file = svg_file_new; // clear data + } + } else if (ImGui::IsItemHovered()) { + tooltip = _L("Change to another .svg file"); + } + + //std::string forget_path = _u8L("Forget the file path"); + //if (m_volume->emboss_shape->svg_file->path.empty()) { + // draw(get_icon(m_icons, IconType::bake, IconState::disabled)); + // ImGui::SameLine(); + // m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, forget_path.c_str()); + //} else { + // draw(get_icon(m_icons, IconType::bake, IconState::hovered)); + // ImGui::SameLine(); + // if (ImGui::Selectable(forget_path.c_str())) { + // // set .svg_file.path_in_3mf to remember file name + // m_volume->emboss_shape->svg_file->path.clear(); + // m_volume_shape.svg_file->path.clear(); + // m_filename_preview.clear(); + // } else if (ImGui::IsItemHovered()) { + // tooltip = _L("Do NOT save local path to 3MF file.\n" + // "Also disables 'reload from disk' option."); + // } + //} + + draw(get_icon(m_icons, IconType::bake, IconState::hovered)); + ImGui::SameLine(); + // TRN: An menu option to convert the SVG into an unmodifiable model part. + if (ImGui::Selectable(_u8L("Bake to model").c_str())) { + m_volume->emboss_shape.reset(); + close(); + } else if (ImGui::IsItemHovered()) { + // TRN: Tooltip for the menu item. + tooltip = _L("Bake into model as uneditable part"); + } + + draw(get_icon(m_icons, IconType::save, IconState::activable)); + ImGui::SameLine(); + if (ImGui::Selectable((_L("Save as") + dots).ToUTF8().data())) { + wxWindow * parent = nullptr; + GUI::FileType file_type = FT_SVG; + wxString wildcard = file_wildcards(file_type); + wxString dlg_title = _L("Save SVG file"); + const EmbossShape::SvgFile &svg = *m_volume_shape.svg_file; + wxString dlg_file = from_u8(get_file_name(((!svg.path.empty()) ? svg.path : svg.path_in_3mf))) + ".svg"; + wxFileDialog dlg(parent, dlg_title, last_used_directory, dlg_file, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (dlg.ShowModal() == wxID_OK) { + last_used_directory = dlg.GetDirectory(); + wxString out_path = dlg.GetPath(); + std::string path{out_path.c_str()}; + // Slic3r::save(*m_volume_shape.svg_file.image, path); + + std::ofstream stream(path); + if (stream.is_open()) { + stream << *svg.file_data; + + // change source file + m_filename_preview.clear(); + m_volume_shape.svg_file->path = path; + m_volume_shape.svg_file->path_in_3mf.clear(); // possible change name + m_volume->emboss_shape->svg_file = m_volume_shape.svg_file; // copy - write changes into volume + } else { + BOOST_LOG_TRIVIAL(error) << "Opening file: \"" << path << "\" Failed"; + } + } + } else if (ImGui::IsItemHovered()) { + tooltip = _L("Save as '.svg' file"); + } + + // draw(get_icon(m_icons, IconType::save)); + // ImGui::SameLine(); + // if (ImGui::Selectable((_L("Save used as") + dots).ToUTF8().data())) { + // GUI::FileType file_type = FT_SVG; + // wxString wildcard = file_wildcards(file_type); + // wxString dlg_title = _L("Export SVG file:"); + // wxString dlg_dir = from_u8(wxGetApp().app_config->get_last_dir()); + // const EmbossShape::SvgFile& svg = m_volume_shape.svg_file; + // wxString dlg_file = from_u8(get_file_name(((!svg.path.empty()) ? svg.path : svg.path_in_3mf))) + ".svg"; + // wxFileDialog dlg(nullptr, dlg_title, dlg_dir, dlg_file, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + // if (dlg.ShowModal() == wxID_OK ){ + // wxString out_path = dlg.GetPath(); + // std::string path{out_path.c_str()}; + // Slic3r::save(*m_volume_shape.svg_file.image, path); + // } + //} else if (ImGui::IsItemHovered()) { + // ImGui::SetTooltip("%s", _u8L("Save only used path as '.svg' file").c_str()); + //} + ImGui::PopStyleVar(1); + } + ImGuiWrapper::pop_combo_style(); + if (!tooltip.empty()) m_imgui->tooltip(tooltip, m_gui_cfg->max_tooltip_width); + + if (file_changed) { + float scale = get_scale_for_tolerance(); + double tes_tol = get_tesselation_tolerance(scale); + EmbossShape es_ = select_shape(m_volume_shape.svg_file->path, tes_tol); + m_volume_shape.svg_file = std::move(es_.svg_file); + m_volume_shape.shapes_with_ids = std::move(es_.shapes_with_ids); + m_volume_shape.final_shape = {}; // clear cache + m_shape_warnings = create_shape_warnings(m_volume_shape, scale); + init_texture(m_texture, m_volume_shape.shapes_with_ids, m_gui_cfg->texture_max_size_px, m_shape_warnings); + process_job(); + } +} + +void GLGizmoSVG::draw_depth() +{ + ImGui::AlignTextToFramePadding(); + ImGuiWrapper::text(m_gui_cfg->translations.depth); + ImGui::SameLine(m_gui_cfg->input_offset); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); + + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + double & value = m_volume_shape.projection.depth; + constexpr double step = 1.; + constexpr double step_fast = 10.; + std::optional result_scale; + const char * size_format = "%.2f mm"; + double input = value; + if (use_inch) { + size_format = "%.2f in"; + // input in inches + input *= GizmoObjectManipulation::mm_to_in * m_scale_depth.value_or(1.f); + result_scale = GizmoObjectManipulation::in_to_mm / m_scale_depth.value_or(1.f); + } else if (m_scale_depth.has_value()) { + // scale input + input *= (*m_scale_depth); + result_scale = 1. / (*m_scale_depth); + } + + if (ImGui::InputDouble("##depth", &input, step, step_fast, size_format)) { + if (result_scale.has_value()) + input *= (*result_scale); + apply(input, limits.depth); + if (!is_approx(input, value, 1e-4)) { + value = input; + process_job(); + } + } else if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Size in emboss direction."), m_gui_cfg->max_tooltip_width); +} + +void GLGizmoSVG::draw_size() +{ + ImGui::AlignTextToFramePadding(); + ImGuiWrapper::text(m_gui_cfg->translations.size); + if (ImGui::IsItemHovered()) { + size_t count_points = 0; + for (const auto &s : m_volume_shape.shapes_with_ids) + count_points += Slic3r::count_points(s.expoly); + // TRN: The placeholder contains a number. + m_imgui->tooltip(GUI::format_wxstr(_L("Scale also changes amount of curve samples (%1%)"), count_points), + m_gui_cfg->max_tooltip_width); + } + + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + + Point size = m_shape_bb.size(); + double width = size.x() * m_volume_shape.scale * m_scale_width.value_or(1.f); + float ui_size_max = m_origin_shape_bb.size().x() * m_volume_shape.scale * 2; + if (use_inch) { + width *= GizmoObjectManipulation::mm_to_in; + //ui_size_max = 2 * width; + } + double height = size.y() * m_volume_shape.scale * m_scale_height.value_or(1.f); + if (use_inch) { height *= GizmoObjectManipulation::mm_to_in; } + + const auto is_valid_scale_ratio = [limit = &limits.relative_scale_ratio](double ratio) { + if (std::fabs(ratio - 1.) < limit->min) + return false; // too small ratio --> without effect + if (ratio > limit->max) + return false; + if (ratio < 1e-4) + return false; // negative scale is not allowed + return true; + }; + + std::optional new_relative_scale; + bool make_snap = false; + const MinMax &minmax = use_inch ? limits.ui_size_in : limits.ui_size; + if (m_keep_ratio) { + std::stringstream ss; + ss << _u8L("width:") << std::setprecision(2) << std::fixed << width << (use_inch ? "in" : "mm"); + std::stringstream ss_height; + ss_height << _u8L("height:") << std::setprecision(2) << std::fixed << height << (use_inch ? "in" : "mm"); + ImGui::SameLine(m_gui_cfg->input_offset); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); + float height_text_size_x = ImGui::CalcTextSize(ss_height.str().c_str()).x; + + // convert to float for slider + float width_f = static_cast(width); + if (m_imgui->slider_float("##width_size_slider", &width_f, minmax.min, ui_size_max, ss.str().c_str(), 1.f, false, _L("set width and height keep ratio with width"),false)) { + double width_ratio = width_f / width; + if (is_valid_scale_ratio(width_ratio)) { + m_scale_width = m_scale_width.value_or(1.f) * width_ratio; + m_scale_height = m_scale_height.value_or(1.f) * width_ratio; + new_relative_scale = Vec3d(width_ratio, width_ratio, 1.); + } + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(height_text_size_x); + m_imgui->text(ss_height.str()); + if (m_imgui->get_last_slider_status().deactivated_after_edit) + make_snap = true; // only last change of slider make snap + } else { + ImGuiInputTextFlags flags = 0; + + float space = m_gui_cfg->icon_width / 2; + float input_width = m_gui_cfg->input_width / 2 - space / 2; + float second_offset = m_gui_cfg->input_offset + input_width + space; + + const char *size_format = (use_inch) ? "%.2f in" : "%.1f mm"; + double step = -1.0; + double fast_step = -1.0; + + ImGui::SameLine(m_gui_cfg->input_offset); + ImGui::SetNextItemWidth(input_width); + double prev_width = width; + if (ImGui::InputDouble("##width", &width, step, fast_step, size_format, flags)) { + limit_value(width, (double) minmax.min, (double) MAX_NUM); + double width_ratio = width / prev_width; + if (is_valid_scale_ratio(width_ratio)) { + m_scale_width = m_scale_width.value_or(1.f) * width_ratio; + new_relative_scale = Vec3d(width_ratio, 1., 1.); + make_snap = true; + } + } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Width of SVG."), m_gui_cfg->max_tooltip_width); + + ImGui::SameLine(second_offset); + ImGui::SetNextItemWidth(input_width); + double prev_height = height; + if (ImGui::InputDouble("##height", &height, step, fast_step, size_format, flags)) { + limit_value(height, (double) minmax.min, (double) MAX_NUM); + double height_ratio = height / prev_height; + if (is_valid_scale_ratio(height_ratio)) { + m_scale_height = m_scale_height.value_or(1.f) * height_ratio; + new_relative_scale = Vec3d(1., height_ratio, 1.); + make_snap = true; + } + } + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Height of SVG."), m_gui_cfg->max_tooltip_width); + } + + // Lock on ratio m_keep_ratio + ImGui::SameLine(m_gui_cfg->lock_offset); + const IconManager::Icon &icon = get_icon(m_icons, m_keep_ratio ? IconType::lock : IconType::unlock, IconState::activable); + const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_ratio ? IconType::lock : IconType::unlock, IconState::hovered); + if (button(icon, icon_hover, icon)) + m_keep_ratio = !m_keep_ratio; + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Lock/unlock the aspect ratio of the SVG."), m_gui_cfg->max_tooltip_width); + + // reset button + bool can_reset = m_scale_width.has_value() || m_scale_height.has_value(); + if (can_reset) { + if (reset_button(m_icons)) { + new_relative_scale = Vec3d(1. / m_scale_width.value_or(1.f), 1. / m_scale_height.value_or(1.f), 1.); + make_snap = true; + } else if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Reset scale"), m_gui_cfg->max_tooltip_width); + } + + if (new_relative_scale.has_value()) { + Selection &selection = m_parent.get_selection(); + selection.setup_cache(); + + auto selection_scale_fnc = [&selection, rel_scale = *new_relative_scale]() { selection.scale(rel_scale, get_drag_transformation_type(selection)); }; + selection_transform(selection, selection_scale_fnc); + + std::string snap_name; // Empty mean do not store on undo/redo stack + m_parent.do_scale(snap_name); + wxGetApp().obj_manipul()->set_dirty(); + // should be the almost same + calculate_scale(); + + const NSVGimage *img = m_volume_shape.svg_file->image.get(); + assert(img != NULL); + if (img != NULL) { + NSVGLineParams params{get_tesselation_tolerance(get_scale_for_tolerance())}; + m_volume_shape.shapes_with_ids = create_shape_with_ids(*img, params); + m_volume_shape.final_shape = {}; // reset cache for final shape + if (!make_snap) // Be carefull: Last change may be without change of scale + process_job(false); + } + } + if (make_snap) + process_job(); // make undo/redo snap-shot} +} + +void GLGizmoSVG::draw_use_surface() +{ + m_imgui->disabled_begin(!m_can_use_surface); + ScopeGuard sc([imgui = m_imgui]() { imgui->disabled_end(); }); + + ImGui::AlignTextToFramePadding(); + ImGuiWrapper::text(m_gui_cfg->translations.use_surface); + ImGui::SameLine(m_gui_cfg->input_offset); + if (Emboss::UpdateSurfaceVolumeJob::is_use_surfae_error) { + Emboss::UpdateSurfaceVolumeJob::is_use_surfae_error = false; + m_volume_shape.projection.use_surface = false; + } + if (m_imgui->qdt_checkbox("##useSurface", m_volume_shape.projection.use_surface)) { + process_job(); + } +} + +void GLGizmoSVG::draw_distance() +{ + const EmbossProjection &projection = m_volume->emboss_shape->projection; + bool use_surface = projection.use_surface; + bool allowe_surface_distance = !use_surface && !m_volume->is_the_only_one_part(); + + float prev_distance = m_distance.value_or(.0f); + float min_distance = static_cast(-2 * projection.depth); + float max_distance = static_cast(2 * projection.depth); + + m_imgui->disabled_begin(!allowe_surface_distance); + ScopeGuard sg([imgui = m_imgui]() { imgui->disabled_end(); }); + + ImGui::AlignTextToFramePadding(); + ImGuiWrapper::text(m_gui_cfg->translations.distance); + ImGui::SameLine(m_gui_cfg->input_offset); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); + + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + const wxString move_tooltip = _L("Distance of the center of the SVG to the model surface."); + bool is_moved = false; + if (use_inch) { + std::optional distance_inch; + if (m_distance.has_value()) distance_inch = (*m_distance * GizmoObjectManipulation::mm_to_in); + min_distance = static_cast(min_distance * GizmoObjectManipulation::mm_to_in); + max_distance = static_cast(max_distance * GizmoObjectManipulation::mm_to_in); + if (m_imgui->slider_optional_float("##distance", m_distance, min_distance, max_distance, "%.3f in", 1.f, false, move_tooltip)) { + limit_value(*m_distance, min_distance, max_distance); + if (distance_inch.has_value()) { + m_distance = *distance_inch * GizmoObjectManipulation::in_to_mm; + } else { + m_distance.reset(); + } + is_moved = true; + } + } else { + if (m_imgui->slider_optional_float("##distance", m_distance, min_distance, max_distance, "%.2f mm", 1.f, false, move_tooltip)) { + limit_value(*m_distance, min_distance, max_distance); + is_moved = true; + } + } + bool is_stop_sliding = m_imgui->get_last_slider_status().deactivated_after_edit; + bool is_reseted = false; + if (m_distance.has_value()) { + if (reset_button(m_icons)) { + m_distance.reset(); + is_reseted = true; + } else if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Reset distance"), m_gui_cfg->max_tooltip_width); + } + + if (is_moved || is_reseted) + do_local_z_move(m_parent.get_selection(), m_distance.value_or(.0f) - prev_distance); + if (is_stop_sliding || is_reseted) + m_parent.do_move(move_snapshot_name); +} + +void GLGizmoSVG::draw_rotation() +{ + bool allow_rotation = true; + if (!m_volume->is_the_only_one_part()) { + const EmbossProjection &projection = m_volume->emboss_shape->projection; + bool use_surface = projection.use_surface; + allow_rotation = !use_surface; + } + m_imgui->disabled_begin(!allow_rotation); + ImGui::AlignTextToFramePadding(); + ImGuiWrapper::text(m_gui_cfg->translations.rotation); + ImGui::SameLine(m_gui_cfg->input_offset); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); + + // slider for Clock-wise angle in degress + // stored angle is optional CCW and in radians + // Convert stored value to degress + // minus create clock-wise roation from CCW + float angle = m_angle.value_or(0.f); + float angle_deg = static_cast(-angle * 180 / M_PI); + if (m_imgui->slider_float("##angle", &angle_deg, limits.angle.min, limits.angle.max, u8"%.2f °", 1.f, false, _L("Rotate text Clock-wise."))) { + // convert back to radians and CCW + double angle_rad = -angle_deg * M_PI / 180.0; + Geometry::to_range_pi_pi(angle_rad); + + double diff_angle = angle_rad - angle; + + do_local_z_rotate(m_parent.get_selection(), diff_angle); + + // calc angle after rotation + m_angle = calc_angle(m_parent.get_selection()); + + // recalculate for surface cut + if (m_volume->emboss_shape->projection.use_surface) + process_job(); + } + bool is_stop_sliding = m_imgui->get_last_slider_status().deactivated_after_edit; + + // Reset button + bool is_reseted = false; + if (m_angle.has_value()) { + if (reset_button(m_icons)) { + do_local_z_rotate(m_parent.get_selection(), -(*m_angle)); + m_angle.reset(); + + // recalculate for surface cut + if (m_volume->emboss_shape->projection.use_surface) + process_job(); + + is_reseted = true; + } else if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Reset rotation"), m_gui_cfg->max_tooltip_width); + } + + // Apply rotation on model (backend) + if (is_stop_sliding || is_reseted) m_parent.do_rotate(rotation_snapshot_name); + + // Keep up - lock button icon + if (!m_volume->is_the_only_one_part()) { + ImGui::SameLine(m_gui_cfg->lock_offset); + const IconManager::Icon &icon = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::activable); + const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::hovered); + if (button(icon, icon_hover, icon)) + m_keep_up = !m_keep_up; + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Lock/unlock rotation angle when dragging above the surface."), m_gui_cfg->max_tooltip_width); + } + m_imgui->disabled_end(); +} + +void GLGizmoSVG::draw_mirroring() +{ + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", m_gui_cfg->translations.mirror.c_str()); + ImGui::SameLine(m_gui_cfg->input_offset); + Axis axis = Axis::UNKNOWN_AXIS; + if (draw_clickable(m_icons, IconType::reflection_x)) { + axis = Axis::X; + } else if (ImGui::IsItemHovered()) { + m_imgui->tooltip(_L("Mirror vertically"), m_gui_cfg->max_tooltip_width); + } + + ImGui::SameLine(); + if (draw_clickable(m_icons, IconType::reflection_y)) { + axis = Axis::Y; + } else if (ImGui::IsItemHovered()) { + m_imgui->tooltip(_L("Mirror horizontally"), m_gui_cfg->max_tooltip_width); + } + + if (axis != Axis::UNKNOWN_AXIS) { + Selection &selection = m_parent.get_selection(); + selection.setup_cache(); + + auto selection_mirror_fnc = [&selection, &axis]() { selection.mirror(axis, get_drag_transformation_type(selection)); }; + selection_transform(selection, selection_mirror_fnc); + m_parent.do_mirror(L("Set Mirror")); + + // Mirror is ignoring keep up !! + if (m_keep_up) m_angle = calc_angle(selection); + + volume_transformation_changed(); + + if (m_volume_shape.projection.use_surface) + process_job(); + } +} + +void GLGizmoSVG::draw_face_the_camera() +{ + if (ImGui::Button(_u8L("Face the camera").c_str())) { + const Camera &cam = wxGetApp().plater()->get_camera(); + auto wanted_up_limit = (m_keep_up) ? std::optional(UP_LIMIT) : std::optional{}; + if (face_selected_volume_to_camera(cam, m_parent, wanted_up_limit)) + volume_transformation_changed(); + } +} + +void GLGizmoSVG::draw_model_type() +{ + ImGui::AlignTextToFramePadding(); + bool is_last_solid_part = m_volume->is_the_only_one_part(); + std::string title = _u8L("Operation"); + if (is_last_solid_part) { + ImVec4 color{.5f, .5f, .5f, 1.f}; + m_imgui->text_colored(color, title.c_str()); + } else { + ImGui::Text("%s", title.c_str()); + } + + std::optional new_type; + ModelVolumeType modifier = ModelVolumeType::PARAMETER_MODIFIER; + ModelVolumeType negative = ModelVolumeType::NEGATIVE_VOLUME; + ModelVolumeType part = ModelVolumeType::MODEL_PART; + ModelVolumeType type = m_volume->type(); + + // TRN EmbossOperation + ImGuiWrapper::push_radio_style(); + if (ImGui::RadioButton(_u8L("Join").c_str(), type == part)) + new_type = part; + else if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Click to change text into object part."), m_gui_cfg->max_tooltip_width); + ImGui::SameLine(); + + auto last_solid_part_hint = _L("You can't change a type of the last solid part of the object."); + if (ImGui::RadioButton(_CTX_utf8(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative)) + new_type = negative; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + m_imgui->tooltip(last_solid_part_hint, m_gui_cfg->max_tooltip_width); + else if (type != negative) + m_imgui->tooltip(_L("Click to change part type into negative volume."), m_gui_cfg->max_tooltip_width); + } + + // In simple mode are not modifiers + if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) { + ImGui::SameLine(); + if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier)) + new_type = modifier; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + m_imgui->tooltip(last_solid_part_hint, m_gui_cfg->max_tooltip_width); + else if (type != modifier) + m_imgui->tooltip(_L("Click to change part type into modifier."), m_gui_cfg->max_tooltip_width); + } + } + ImGuiWrapper::pop_radio_style(); + + if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) { + GUI_App &app = wxGetApp(); + Plater * plater = app.plater(); + // TRN: This is the name of the action that shows in undo/redo stack (changing part type from SVG to something else). + Plater::TakeSnapshot snapshot(plater, _u8L("Change SVG Type"), UndoRedo::SnapshotType::GizmoAction); + m_volume->set_type(*new_type); + + bool is_volume_move_inside = (type == part); + bool is_volume_move_outside = (*new_type == part); + // Update volume position when switch (from part) or (into part) + if ((is_volume_move_inside || is_volume_move_outside)) + process_job(); + + // inspiration in ObjectList::change_part_type() + // how to view correct side panel with objects + ObjectList * obj_list = app.obj_list(); + wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(obj_list->get_selected_obj_idx(), + [volume = m_volume](const ModelVolume *vol) { + return vol == volume; + }); + if (!sel.IsEmpty()) + obj_list->select_item(sel.front()); + + // NOTE: on linux, function reorder_volumes_and_get_selection call GLCanvas3D::reload_scene(refresh_immediately = false) + // which discard m_volume pointer and set it to nullptr also selection is cleared so gizmo is automaticaly closed + auto &mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() != GLGizmosManager::Svg) + mng.open_gizmo(GLGizmosManager::Svg); + // TODO: select volume back - Ask @Sasa + } +} + +void GLGizmoSVG::volume_transformation_changed() +{ + if (m_volume == nullptr || !m_volume->emboss_shape.has_value()) { + assert(false); + return; + } + + if (!m_keep_up) { + // update current style + m_angle = calc_angle(m_parent.get_selection()); + } else { + // angle should be the same + assert(is_approx(m_angle, calc_angle(m_parent.get_selection()))); + } + + // Update surface by new position + if (m_volume->emboss_shape->projection.use_surface) { + process_job(); + } else { + // inform slicing process that model changed + // SLA supports, processing + // ensure on bed + wxGetApp().plater()->changed_object(*m_volume->get_object()); + } + + // Show correct value of height & depth inside of inputs + calculate_scale(); +} + +bool GLGizmoSVG::on_mouse_for_rotation(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Moving()) + return false; + + bool used = use_grabbers(mouse_event); + if (!m_dragging) + return used; + + if (mouse_event.Dragging()) + dragging_rotate_gizmo(m_rotate_gizmo.get_angle(), m_angle, m_rotate_start_angle, m_parent.get_selection()); + + return used; +} + +bool GLGizmoSVG::on_mouse_for_translate(const wxMouseEvent &mouse_event) +{ // exist selected volume? + if (m_volume == nullptr) + return false; + + auto up_limit = m_keep_up ? std::optional(UP_LIMIT) : std::optional{}; + const Camera &camera = wxGetApp().plater()->get_camera(); + + bool was_dragging = m_surface_drag.has_value(); + bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); + bool is_dragging = m_surface_drag.has_value(); + + // End with surface dragging? + if (was_dragging && !is_dragging) { + // Update surface by new position + if (m_volume->emboss_shape->projection.use_surface) + process_job(); + + // TODO: Remove it when it will be stable + // Distance should not change during dragging + const GLVolume *gl_volume = m_parent.get_selection().get_first_volume(); + m_distance = calc_distance(*gl_volume, m_raycast_manager, m_parent); + + // Show correct value of height & depth inside of inputs + calculate_scale(); + } + // Start with dragging + else if (!was_dragging && is_dragging) { + // Cancel job to prevent interuption of dragging (duplicit result) + //if (m_job_cancel != nullptr) + // m_job_cancel->store(true); + } + // during drag + else if (was_dragging && is_dragging) { + // update scale of selected volume --> should be approx the same + calculate_scale(); + // Recalculate angle for GUI + if (!m_keep_up) + m_angle = calc_angle(m_parent.get_selection()); + } + return res; +} + +void GLGizmoSVG::register_single_mesh_pick() +{ + std::map>().swap(m_mesh_raycaster_map); + Selection & selection = m_parent.get_selection(); + int object_idx; + ModelObject * mo = selection.get_selected_single_object(object_idx); + std::vector volume_idxs = selection.get_volume_idxs_from_object(object_idx); + if (volume_idxs.empty()) { + m_raycast_condition.clear(); + m_raycast_manager.clear(); + return; + } + for (unsigned int idx : volume_idxs) { + GLVolume *v = const_cast(selection.get_volume(idx)); + if (v == m_svg_volume) { + continue; + } + auto world_tran = v->get_instance_transformation() * v->get_volume_transformation(); + auto mesh = const_cast(v->ori_mesh); + if (m_mesh_raycaster_map.find(v) != m_mesh_raycaster_map.end()) { + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + } else { + m_mesh_raycaster_map[v] = std::make_shared(mesh, -1); + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + } + } + + const ModelInstance * mi = mo->instances[selection.get_instance_idx()]; + if (m_svg_volume) { + const ModelVolume *svg_mv = get_model_volume(*m_svg_volume, *mo); + m_raycast_condition = create_condition(mo->volumes, svg_mv->id()); + } + else { + m_raycast_condition = create_condition(mo->volumes, -1); + } + + RaycastManager::Meshes meshes; // = create_meshes(input.canvas, cond); + meshes.reserve(m_mesh_raycaster_map.size()); + for (auto iter : m_mesh_raycaster_map) { + auto gl_volume = iter.first; + const ModelVolume *mv = get_model_volume(*gl_volume, *mo); + size_t id = mv->id().id; + auto mesh = std::make_unique(iter.second->mesh_raycaster->get_aabb_mesh()); + meshes.emplace_back(std::make_pair(id, std::move(mesh))); + } + m_raycast_manager.actualize(*mi, &m_raycast_condition, &meshes); // input.raycaster.actualize(*instance, &cond, &meshes); +} + +bool GLGizmoSVG::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (m_volume && m_volume->is_the_only_one_part()){ + return false; + } + const Selection & selection = m_parent.get_selection(); + if (action == SLAGizmoEventType::Moving) { + + } else if (action == SLAGizmoEventType::LeftDown) { + if (!selection.is_empty() && get_hover_id() != -1) { + //start_dragging(); + return true; + } + } + return true; +} + +void GLGizmoSVG::update_single_mesh_pick(GLVolume *v) +{ + if (m_mesh_raycaster_map.find(v) != m_mesh_raycaster_map.end()) { + auto world_tran = v->get_instance_transformation() * v->get_volume_transformation(); + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + } +} + +}} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp new file mode 100644 index 0000000..55280ba --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -0,0 +1,175 @@ +#ifndef slic3r_GLGizmoSVG_hpp_ +#define slic3r_GLGizmoSVG_hpp_ + +#include "GLGizmoRotate.hpp" +#include "slic3r/GUI/SurfaceDrag.hpp" +#include "slic3r/GUI/GLTexture.hpp" + +#include "slic3r/GUI/IconManager.hpp" +//QDS: add size adjust related +#include "libslic3r/Model.hpp" +#include "slic3r/GUI/Jobs/EmbossJob.hpp" +namespace Slic3r { +class ModelVolume; +enum class ModelVolumeType : int; +} // namespace Slic3r + +namespace Slic3r { +namespace GUI { +//#define DEBUG_SVG +struct Camera; +class Worker; +enum class SLAGizmoEventType : unsigned char; +class GLGizmoSVG : public GLGizmoBase +{ + Emboss::DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel, ModelVolumeType volume_type, std::string_view filepath = ""); + Emboss::CreateVolumeParams create_input(GLCanvas3D &canvas, ModelVolumeType volume_type); + + +public: + GLGizmoSVG(GLCanvas3D& parent, unsigned int sprite_id); + virtual ~GLGizmoSVG() = default; + + std::string get_tooltip() const override; + void data_changed(bool is_serializing) override; + void on_set_hover_id() override { m_rotate_gizmo.set_hover_id(m_hover_id); } + void on_enable_grabber(unsigned int id) override; + void on_disable_grabber(unsigned int id) override; + + /// + /// Create new text without given position + /// + /// Object part / Negative volume / Modifier + /// True on succesfull start creation otherwise False + bool create_volume(ModelVolumeType volume_type); // first open file dialog //by rigth menu + bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog //by rigth menu + + bool create_volume(std::string_view svg_file, const Vec2d &mouse_pos, ModelVolumeType volume_type = ModelVolumeType::MODEL_PART); + bool create_volume(std::string_view svg_file, ModelVolumeType volume_type = ModelVolumeType::MODEL_PART); + /// + /// Check whether volume is object containing only emboss volume + /// + /// Pointer to volume + /// True when object otherwise False + static bool is_svg_object(const ModelVolume &volume); + + /// + /// Check whether volume has emboss data + /// + /// Pointer to volume + /// True when constain emboss data otherwise False + static bool is_svg(const ModelVolume &volume); + + void register_single_mesh_pick(); + + void update_single_mesh_pick(GLVolume *v); + bool gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down); + +protected: + bool on_is_selectable() const override { return false; } + virtual bool on_init() override; + virtual std::string on_get_name() const override; + std::string on_get_name_str() override { return "Move"; } + virtual bool on_is_activable() const override; + virtual void on_set_state() override; + virtual void on_start_dragging() override; + virtual void on_stop_dragging() override; + /// + /// Rotate by text on dragging rotate grabers + /// + /// Information about mouse + /// Propagete normaly return false. + bool on_mouse(const wxMouseEvent &mouse_event) override; + virtual void on_update(const UpdateData& data) override; + virtual void on_render() override; + virtual void on_render_for_picking() override; + virtual void on_render_input_window(float x, float y, float bottom_limit); + +private: + void set_volume_by_selection(); + void delete_texture(Emboss::Texture &texture); + void reset_volume(); + void calculate_scale(); + float get_scale_for_tolerance(); + std::vector create_shape_warnings(const EmbossShape &shape, float scale); + + bool process_job(bool make_snapshot = true); // process(bool make_snapshot = true) + void close(); + void draw_window(); + bool init_texture(Emboss::Texture &texture, const ExPolygonsWithIds &shapes_with_ids, unsigned max_size_px, const std::vector &shape_warnings); + void draw_preview(); + void draw_filename(); + void draw_depth(); + void draw_size(); + void draw_use_surface(); + void draw_distance(); + void draw_rotation(); + void draw_mirroring(); + void draw_face_the_camera(); + void draw_model_type(); + + void volume_transformation_changed(); + + // process mouse event + bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); + bool on_mouse_for_translate(const wxMouseEvent &mouse_event); + +private: + struct GuiCfg; + std::unique_ptr m_gui_cfg; + // actual selected only one volume - with emboss data + ModelVolume *m_volume = nullptr; + const GLVolume * m_svg_volume{nullptr}; + // Is used to edit eboss and send changes to job + // Inside volume is current state of shape WRT Volume + EmbossShape m_volume_shape; // copy from m_volume for edit + // same index as volumes in + std::vector m_shape_warnings; + // cancel for previous update of volume to cancel finalize part + std::shared_ptr> m_job_cancel = nullptr; + // When work with undo redo stack there could be situation that + // m_volume point to unexisting volume so One need also objectID + ObjectID m_volume_id; + // move gizmo + Grabber m_move_grabber; + // Rotation gizmo + GLGizmoRotate m_rotate_gizmo; + std::optional m_angle; + std::optional m_distance; + + bool m_can_use_surface; + // Value is set only when dragging rotation to calculate actual angle + std::optional m_rotate_start_angle; + // TODO: it should be accessible by other gizmo too. + // May be move to plater? + RaycastManager m_raycast_manager; + RaycastManager::AllowVolumes m_raycast_condition; + std::map> m_mesh_raycaster_map;//for text + // When true keep up vector otherwise relative rotation + bool m_keep_up = true; + // Keep size aspect ratio when True. + bool m_keep_ratio = true; + // Keep data about dragging only during drag&drop + std::optional m_surface_drag; + // For volume on scaled objects + std::optional m_scale_width; + std::optional m_scale_height; + std::optional m_scale_depth; + + // keep SVG data rendered on GPU + Emboss::Texture m_texture; + // bounding box of shape + // Note: Scaled mm to int value by m_volume_shape.scale + BoundingBox m_shape_bb; + BoundingBox m_origin_shape_bb; + std::string m_filename_preview; + IconManager m_icon_manager; + IconManager::VIcons m_icons; +}; + + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMove_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index f02b030..bf52f64 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -5,7 +5,7 @@ #include -#include +#include namespace Slic3r { namespace GUI { @@ -29,7 +29,17 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen , m_snap_step(0.05) //QDS: GUI refactor: add obj manipulation , m_object_manipulation(obj_manipulation) +{} + +const Vec3d &GLGizmoScale3D::get_scale() { + if (m_object_manipulation) { + Vec3d cache_scale = m_object_manipulation->get_cache().scale.cwiseQuotient(Vec3d(100,100,100)); + Vec3d temp_scale = cache_scale.cwiseProduct(m_scale); + m_object_manipulation->limit_scaling_ratio(temp_scale); + m_scale = temp_scale.cwiseQuotient(cache_scale); + } + return m_scale; } std::string GLGizmoScale3D::get_tooltip() const @@ -51,7 +61,7 @@ std::string GLGizmoScale3D::get_tooltip() const return "Y: " + format(scale(1), 4) + "%"; else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging) return "Z: " + format(scale(2), 4) + "%"; - else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 || + else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) { std::string tooltip = "X: " + format(scale(0), 2) + "%\n"; @@ -63,6 +73,11 @@ std::string GLGizmoScale3D::get_tooltip() const return ""; } +void GLGizmoScale3D::data_changed(bool is_serializing) +{ + change_cs_by_selection(); +} + void GLGizmoScale3D::enable_ununiversal_scale(bool enable) { for (unsigned int i = 0; i < 6; ++i) @@ -78,14 +93,6 @@ bool GLGizmoScale3D::on_init() double half_pi = 0.5 * (double)PI; - // x axis - m_grabbers[0].angles(1) = half_pi; - m_grabbers[1].angles(1) = half_pi; - - // y axis - m_grabbers[2].angles(0) = half_pi; - m_grabbers[3].angles(0) = half_pi; - // QDS m_grabbers[4].enabled = false; m_shortcut_key = WXK_CONTROL_S; @@ -110,30 +117,52 @@ bool GLGizmoScale3D::on_is_activable() const void GLGizmoScale3D::on_set_state() { if (get_state() == On) { - m_object_manipulation->set_coordinates_type(ECoordinatesType::Local); + m_last_selected_obejct_idx = -1; + m_last_selected_volume_idx = -1; + change_cs_by_selection(); } } +static int constraint_id(int grabber_id) +{ + static const std::vector id_map = {1, 0, 3, 2, 5, 4, 8, 9, 6, 7}; + return (0 <= grabber_id && grabber_id < (int) id_map.size()) ? id_map[grabber_id] : -1; +} + void GLGizmoScale3D::on_start_dragging() { - if (m_hover_id != -1) - { - m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.plane_center = m_grabbers[4].center; - m_starting.plane_nromal = m_grabbers[5].center - m_grabbers[4].center; + if (m_hover_id != -1) { + auto grabbers_transform = m_grabbers_tran.get_matrix(); + m_starting.drag_position = grabbers_transform * m_grabbers[m_hover_id].center; + m_starting.plane_center = grabbers_transform * m_grabbers[4].center; // plane_center = bottom center + m_starting.plane_nromal = (grabbers_transform * m_grabbers[5].center - grabbers_transform * m_grabbers[4].center).normalized(); m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); - m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); + m_starting.box = m_bounding_box; - const Vec3d& center = m_starting.box.center(); - m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2)); - m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min(0), center(1), center(2)); - m_starting.pivots[2] = m_transform * Vec3d(center(0), m_starting.box.max(1), center(2)); - m_starting.pivots[3] = m_transform * Vec3d(center(0), m_starting.box.min(1), center(2)); - m_starting.pivots[4] = m_transform * Vec3d(center(0), center(1), m_starting.box.max(2)); - m_starting.pivots[5] = m_transform * Vec3d(center(0), center(1), m_starting.box.min(2)); + m_starting.center = m_center; + m_starting.instance_center = m_instance_center; + + const Vec3d box_half_size = 0.5 * m_bounding_box.size(); + + m_starting.local_pivots[0] = Vec3d(box_half_size.x(), 0.0, -box_half_size.z()); + m_starting.local_pivots[1] = Vec3d(-box_half_size.x(), 0.0, -box_half_size.z()); + m_starting.local_pivots[2] = Vec3d(0.0, box_half_size.y(), -box_half_size.z()); + m_starting.local_pivots[3] = Vec3d(0.0, -box_half_size.y(), -box_half_size.z()); + m_starting.local_pivots[4] = Vec3d(0.0, 0.0, box_half_size.z()); + m_starting.local_pivots[5] = Vec3d(0.0, 0.0, -box_half_size.z()); + for (size_t i = 0; i < 6; i++) { + m_starting.pivots[i] = grabbers_transform * m_starting.local_pivots[i]; // todo delete + } + m_starting.constraint_position = grabbers_transform * m_grabbers[constraint_id(m_hover_id)].center; + m_scale = m_starting.scale = Vec3d::Ones() ; + m_offset = Vec3d::Zero(); } } +void GLGizmoScale3D::on_stop_dragging() +{ +} + void GLGizmoScale3D::on_update(const UpdateData& data) { if ((m_hover_id == 0) || (m_hover_id == 1)) @@ -146,99 +175,84 @@ void GLGizmoScale3D::on_update(const UpdateData& data) do_scale_uniform(data); } -void GLGizmoScale3D::on_render() +void GLGizmoScale3D::update_grabbers_data() { - const Selection& selection = m_parent.get_selection(); + const Selection &selection = m_parent.get_selection(); + const auto &[box, box_trafo] = selection.get_bounding_box_in_current_reference_system(); + m_bounding_box = box; + m_center = box_trafo.translation(); + m_grabbers_tran.set_matrix(box_trafo); + m_instance_center = (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) ? selection.get_first_volume()->get_instance_offset() : m_center; + + const Vec3d box_half_size = 0.5 * m_bounding_box.size(); + bool ctrl_down = wxGetKeyState(WXK_CONTROL); bool single_instance = selection.is_single_full_instance(); - bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); + bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); + // x axis + m_grabbers[0].center = Vec3d(-(box_half_size.x()), 0.0, -box_half_size.z()); + m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + m_grabbers[1].center = Vec3d(box_half_size.x(), 0.0, -box_half_size.z()); + m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + + // y axis + m_grabbers[2].center = Vec3d(0.0, -(box_half_size.y()), -box_half_size.z()); + m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + m_grabbers[3].center = Vec3d(0.0, box_half_size.y(), -box_half_size.z()); + m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + + // z axis do not show 4 + m_grabbers[4].center = Vec3d(0.0, 0.0, -(box_half_size.z())); + m_grabbers[4].enabled = false; + + m_grabbers[5].center = Vec3d(0.0, 0.0, box_half_size.z()); + m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + + // uniform + m_grabbers[6].center = Vec3d(-box_half_size.x(), -box_half_size.y(), -box_half_size.z()); + m_grabbers[6].color = (ctrl_down && m_hover_id == 8) ? CONSTRAINED_COLOR : GRABBER_UNIFORM_COL; + m_grabbers[7].center = Vec3d(box_half_size.x(), -box_half_size.y(), -box_half_size.z()); + m_grabbers[7].color = (ctrl_down && m_hover_id == 9) ? CONSTRAINED_COLOR : GRABBER_UNIFORM_COL; + m_grabbers[8].center = Vec3d(box_half_size.x(), box_half_size.y(), -box_half_size.z()); + m_grabbers[8].color = (ctrl_down && m_hover_id == 6) ? CONSTRAINED_COLOR : GRABBER_UNIFORM_COL; + m_grabbers[9].center = Vec3d(-box_half_size.x(), box_half_size.y(), -box_half_size.z()); + m_grabbers[9].color = (ctrl_down && m_hover_id == 7) ? CONSTRAINED_COLOR : GRABBER_UNIFORM_COL; + + for (int i = 0; i < 6; ++i) { + //m_grabbers[i].color = AXES_COLOR[i / 2]; + m_grabbers[i].hover_color = AXES_HOVER_COLOR[i / 2]; + } + for (int i = 6; i < 10; ++i) { + //m_grabbers[i].color = GRABBER_UNIFORM_COL; + m_grabbers[i].hover_color = GRABBER_UNIFORM_HOVER_COL; + } +} + +void GLGizmoScale3D::change_cs_by_selection() { + int obejct_idx, volume_idx; + ModelVolume *model_volume = m_parent.get_selection().get_selected_single_volume(obejct_idx, volume_idx); + if (m_last_selected_obejct_idx == obejct_idx && m_last_selected_volume_idx == volume_idx) { return; } + m_last_selected_obejct_idx = obejct_idx; + m_last_selected_volume_idx = volume_idx; + if (m_parent.get_selection().is_multiple_full_object()) { + m_object_manipulation->set_coordinates_type(ECoordinatesType::World); + } else if (model_volume) { + m_object_manipulation->set_coordinates_type(ECoordinatesType::Local); + } +} + +void GLGizmoScale3D::on_render() +{ glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - m_box.reset(); - m_transform = Transform3d::Identity(); - // Transforms grabbers' offsets to world refefence system - Transform3d offsets_transform = Transform3d::Identity(); - m_offsets_transform = Transform3d::Identity(); - Vec3d angles = Vec3d::Zero(); - - if (single_instance) { - // calculate bounding box in instance local reference system - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int idx : idxs) { - const GLVolume* vol = selection.get_volume(idx); - m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix())); - } - - // gets transform from first selected volume - const GLVolume* v = selection.get_volume(*idxs.begin()); - m_transform = v->get_instance_transformation().get_matrix(); - // gets angles from first selected volume - angles = v->get_instance_rotation(); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - m_offsets_transform = offsets_transform; - } - else if (single_volume) { - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); - m_box = v->bounding_box(); - m_transform = v->world_matrix(); - angles = Geometry::extract_euler_angles(m_transform); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v->get_volume_rotation(), Vec3d::Ones(), v->get_volume_mirror()); - } - else - m_box = selection.get_bounding_box(); - - const Vec3d& center = m_box.center(); - Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); - Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); - Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); - - bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); - - // x axis - m_grabbers[0].center = m_transform * Vec3d(m_box.min(0), center(1), m_box.min(2)); - m_grabbers[1].center = m_transform * Vec3d(m_box.max(0), center(1), m_box.min(2)); - - // y axis - m_grabbers[2].center = m_transform * Vec3d(center(0), m_box.min(1), m_box.min(2)); - m_grabbers[3].center = m_transform * Vec3d(center(0), m_box.max(1), m_box.min(2)); - - // z axis do not show 4 - m_grabbers[4].center = m_transform * Vec3d(center(0), center(1), m_box.min(2)); - m_grabbers[4].enabled = false; - - m_grabbers[5].center = m_transform * Vec3d(center(0), center(1), m_box.max(2)); - - // uniform - m_grabbers[6].center = m_transform * Vec3d(m_box.min(0), m_box.min(1), m_box.min(2)); - m_grabbers[7].center = m_transform * Vec3d(m_box.max(0), m_box.min(1), m_box.min(2)); - m_grabbers[8].center = m_transform * Vec3d(m_box.max(0), m_box.max(1), m_box.min(2)); - m_grabbers[9].center = m_transform * Vec3d(m_box.min(0), m_box.max(1), m_box.min(2)); - - for (int i = 0; i < 6; ++i) { - m_grabbers[i].color = AXES_COLOR[i/2]; - m_grabbers[i].hover_color = AXES_HOVER_COLOR[i/2]; - } - - for (int i = 6; i < 10; ++i) { - m_grabbers[i].color = GRABBER_UNIFORM_COL; - m_grabbers[i].hover_color = GRABBER_UNIFORM_HOVER_COL; - } - - // sets grabbers orientation - for (int i = 0; i < 10; ++i) { - m_grabbers[i].angles = angles; - } + update_grabbers_data(); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - const BoundingBoxf3& selection_box = selection.get_bounding_box(); - - float grabber_mean_size = (float)((selection_box.size()(0) + selection_box.size()(1) + selection_box.size()(2)) / 3.0); + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(m_grabbers_tran.get_matrix().data())); //draw connections @@ -259,13 +273,17 @@ void GLGizmoScale3D::on_render() render_grabbers_connection(9, 6); // draw grabbers - render_grabbers(grabber_mean_size); + render_grabbers(); + glsafe(::glPopMatrix()); } void GLGizmoScale3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(m_grabbers_tran.get_matrix().data())); render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); + glsafe(::glPopMatrix()); } void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const @@ -296,22 +314,25 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; - if (m_starting.ctrl_down) - { + if (m_starting.ctrl_down && abs(ratio-1.0f)>0.001) { double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); - if (m_hover_id == 2 * axis) + if (m_hover_id == 2 * axis) { local_offset *= -1.0; - + } Vec3d local_offset_vec; switch (axis) { case X: { local_offset_vec = local_offset * Vec3d::UnitX(); break; } - case Y: { local_offset_vec = local_offset * Vec3d::UnitY(); break; } - case Z: { local_offset_vec = local_offset * Vec3d::UnitZ(); break; } + case Y: { local_offset_vec = local_offset * Vec3d::UnitY(); break;} + case Z: { local_offset_vec = local_offset * Vec3d::UnitZ(); break; + } default: break; } - - m_offset = m_offsets_transform * local_offset_vec; + if (m_object_manipulation->is_world_coordinates()) { + m_offset = local_offset_vec; + } else {//if (m_object_manipulation->is_instance_coordinates()) + m_offset = m_grabbers_tran.get_matrix_no_offset() * local_offset_vec; + } } else m_offset = Vec3d::Zero(); @@ -332,7 +353,7 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.plane_center; + Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.constraint_position : m_starting.plane_center; // plane_center = bottom center Vec3d starting_vec = m_starting.drag_position - pivot; double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) @@ -344,10 +365,16 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const Vec3d plane_vec = mouse_dir.cross(m_starting.plane_nromal); plane_normal = plane_vec.cross(m_starting.plane_nromal); } - + plane_normal = plane_normal.normalized(); // finds the intersection of the mouse ray with the plane that the drag point moves // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection - Vec3d inters = GetIntersectionOfRayAndPlane(data.mouse_ray.a, mouse_dir, m_starting.drag_position, plane_normal.normalized()); + auto dot_value = (plane_normal.dot(mouse_dir)); + auto angle = Geometry::rad2deg(acos(dot_value)); + auto big_than_min_angle = abs(angle) < 95 && abs(angle) > 85; + if (big_than_min_angle) { + return 1; + } + Vec3d inters = GetIntersectionOfRayAndPlane(data.mouse_ray.a, mouse_dir, m_starting.drag_position, plane_normal); Vec3d inters_vec = inters - m_starting.drag_position; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 1150762..609a086 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -19,19 +19,23 @@ class GLGizmoScale3D : public GLGizmoBase { Vec3d scale; Vec3d drag_position; + Vec3d constraint_position; + Vec3d center{Vec3d::Zero()};//sphere bounding box center + Vec3d instance_center{Vec3d::Zero()}; Vec3d plane_center; // keep the relative center position for scale in the bottom plane - Vec3d plane_nromal; // keep the bottom plane + Vec3d plane_nromal; // keep the bottom plane BoundingBoxf3 box; - Vec3d pivots[6]; + Vec3d pivots[6];// Vec3d constraint_position{Vec3d::Zero()}; + Vec3d local_pivots[6]; bool ctrl_down; StartingData() : scale(Vec3d::Ones()), drag_position(Vec3d::Zero()), ctrl_down(false) { for (int i = 0; i < 5; ++i) { pivots[i] = Vec3d::Zero(); } } }; - mutable BoundingBoxf3 m_box; - mutable Transform3d m_transform; - // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) - mutable Transform3d m_offsets_transform; + mutable BoundingBoxf3 m_bounding_box; + Geometry::Transformation m_grabbers_tran;//m_grabbers_transform + Vec3d m_center{Vec3d::Zero()}; + Vec3d m_instance_center{Vec3d::Zero()}; Vec3d m_scale; Vec3d m_offset; double m_snap_step; @@ -48,13 +52,13 @@ public: double get_snap_step(double step) const { return m_snap_step; } void set_snap_step(double step) { m_snap_step = step; } - const Vec3d& get_scale() const { return m_scale; } + const Vec3d &get_scale(); void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } const Vec3d& get_offset() const { return m_offset; } std::string get_tooltip() const override; - + void data_changed(bool is_serializing) override; void enable_ununiversal_scale(bool enable); protected: virtual bool on_init() override; @@ -63,12 +67,12 @@ protected: virtual bool on_is_activable() const override; virtual void on_set_state() override; virtual void on_start_dragging() override; + virtual void on_stop_dragging() override; virtual void on_update(const UpdateData& data) override; virtual void on_render() override; virtual void on_render_for_picking() override; //QDS: GUI refactor: add object manipulation virtual void on_render_input_window(float x, float y, float bottom_limit); - private: void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; @@ -76,6 +80,10 @@ private: void do_scale_uniform(const UpdateData& data); double calc_ratio(const UpdateData& data) const; + void update_grabbers_data(); + void change_cs_by_selection(); // cs mean Coordinate System +private: + int m_last_selected_obejct_idx, m_last_selected_volume_idx; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index aad91ee..bd265a7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -28,11 +28,12 @@ void GLGizmoSeam::on_shutdown() bool GLGizmoSeam::on_init() { m_shortcut_key = WXK_CONTROL_P; - - m_desc["clipping_of_view_caption"] = _L("Alt + Mouse wheel"); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + m_desc["clipping_of_view_caption"] = alt + _L("Mouse wheel"); m_desc["clipping_of_view"] = _L("Section view"); m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size_caption"] = _L("Ctrl + Mouse wheel"); + m_desc["cursor_size_caption"]= ctrl + _L("Mouse wheel"); m_desc["cursor_size"] = _L("Brush size"); m_desc["cursor_type"] = _L("Brush shape"); m_desc["enforce_caption"] = _L("Left mouse button"); @@ -186,7 +187,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) { if (! m_c->selection_info()->model_object()) return; - + m_imgui_start_pos[0] = x; + m_imgui_start_pos[1] = y; const float approx_height = m_imgui->scaled(12.5f); y = std::min(y, bottom_limit - approx_height); //QDS: GUI refactor: move gizmo to the right @@ -329,21 +331,18 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) auto vertical_only = m_vertical_only; if (m_imgui->qdt_checkbox(_L("Vertical"), vertical_only)) { m_vertical_only = vertical_only; - if (m_vertical_only) { - m_is_front_view = true; - change_camera_view_angle(m_front_view_radian); - } } auto is_front_view = m_is_front_view; m_imgui->qdt_checkbox(_L("View: keep horizontal"), is_front_view); - if (m_is_front_view != is_front_view) { + if (m_is_front_view != is_front_view){ m_is_front_view = is_front_view; - if (m_is_front_view) { + if (m_is_front_view) { + update_front_view_radian(); change_camera_view_angle(m_front_view_radian); } } m_imgui->disabled_begin(!m_is_front_view); - if (render_slider_double_input_by_format(slider_input_layout, _u8L("Rotate horizontally"), m_front_view_radian, 0.f, 360.f, 0, DoubleShowType::DEGREE)) { + if (render_slider_double_input_by_format(slider_input_layout, _u8L("Rotate horizontally"), m_front_view_radian, -180.f, 180.f, 0, DoubleShowType::DEGREE)) { change_camera_view_angle(m_front_view_radian); } m_imgui->disabled_end(); @@ -372,6 +371,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) m_parent.set_as_dirty(); } ImGui::PopStyleVar(2); + m_imgui_end_pos[0] = m_imgui_start_pos[0] + ImGui::GetWindowWidth(); + m_imgui_end_pos[1] = m_imgui_start_pos[1] + ImGui::GetWindowHeight(); GizmoImguiEnd(); //QDS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 35d97ad..73a41fe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -320,7 +320,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::PushItemWidth(slider_width + space_size); if (m_imgui->qdt_slider_float_style("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) { if (m_configuration.decimate_ratio < 0.f) - m_configuration.decimate_ratio = 0.01f; + m_configuration.decimate_ratio = 0.f; if (m_configuration.decimate_ratio > 100.f) m_configuration.decimate_ratio = 100.f; m_configuration.fix_count_by_ratio(orig_triangle_count); @@ -331,7 +331,14 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::PushItemWidth(ImGui::CalcTextSize("100.00%").x + space_size); - ImGui::QDTDragFloat("##decimate_ratio_input", &m_configuration.decimate_ratio, 0.05f, 0.0f, 0.0f, "%.2f%%"); + if (ImGui::QDTDragFloat("##decimate_ratio_input", &m_configuration.decimate_ratio, 0.05f, 0.0f, 0.0f, "%.2f%%")) { + if (m_configuration.decimate_ratio < 0.f) + m_configuration.decimate_ratio = 0.f; + if (m_configuration.decimate_ratio > 100.f) + m_configuration.decimate_ratio = 100.f; + m_configuration.fix_count_by_ratio(orig_triangle_count); + start_process = true; + } ImGui::NewLine(); ImGui::SameLine(bottom_left_width + space_size); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp index bda805f..1784d44 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp @@ -38,7 +38,16 @@ static const int FONT_SIZE = 12; static const float SELECTABLE_INNER_OFFSET = 8.0f; static const wxFontEncoding font_encoding = wxFontEncoding::wxFONTENCODING_SYSTEM; - +const std::array TEXT_GRABBER_COLOR = {1.0, 1.0, 0.0, 1.0}; +const std::array TEXT_GRABBER_HOVER_COLOR = {0.7, 0.7, 0.0, 1.0}; +#ifdef DEBUG_TEXT +std::string formatFloat(float val) +{ + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << val; + return ss.str(); +} +#endif std::vector init_face_names() { std::vector valid_font_names; @@ -72,7 +81,7 @@ std::vector init_face_names() return false; return true; - }; + }; std::sort(facenames.begin(), facenames.end()); for (const wxString &name : facenames) { @@ -216,7 +225,6 @@ bool GLGizmoText::on_init() m_desc["rotate_text_caption"] = _L("Shift + Mouse move up or down"); m_desc["rotate_text"] = _L("Rotate text"); - m_grabbers.push_back(Grabber()); return true; } @@ -285,15 +293,11 @@ bool GLGizmoText::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_posit if (text.empty()) return true; - const ModelObject * mo = m_c->selection_info()->model_object(); - if (m_is_modify) { - const Selection &selection = m_parent.get_selection(); - mo = selection.get_model()->objects[m_object_idx]; - } + const Selection &selection = m_parent.get_selection(); + auto mo = selection.get_model()->objects[m_object_idx]; if (mo == nullptr) return true; - const Selection & selection = m_parent.get_selection(); const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; const Camera & camera = wxGetApp().plater()->get_camera(); @@ -376,10 +380,10 @@ bool GLGizmoText::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_posit if (closest_hit == Vec3f::Zero() && closest_normal == Vec3f::Zero()) return true; - m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_normal}; + m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_normal};//left down - m_is_modify = true; generate_text_volume(false); + m_is_modify = true; plater->update(); } @@ -389,18 +393,14 @@ bool GLGizmoText::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_posit void GLGizmoText::on_set_state() { if (m_state == EState::On) { - if (m_parent.get_selection().is_single_volume() || m_parent.get_selection().is_single_modifier()) { - ModelVolume *model_volume = get_selected_single_volume(m_object_idx, m_volume_idx); - if (model_volume) { - TextInfo text_info = model_volume->get_text_info(); - if (!text_info.m_text.empty()) { - load_from_text_info(text_info); - m_is_modify = true; - } - } - } + m_last_text_mv = nullptr; + m_need_fix = false; + load_init_text(); } else if (m_state == EState::Off) { + m_show_warning = false; + m_show_text_normal_error = false; + m_edit_text_again = false; reset_text_info(); delete_temp_preview_text_volume(); m_parent.use_slope(false); @@ -408,6 +408,60 @@ void GLGizmoText::on_set_state() } } +void GLGizmoText::load_init_text() +{ + Plater *plater = wxGetApp().plater(); + if (m_parent.get_selection().is_single_volume() || m_parent.get_selection().is_single_modifier()) { + ModelVolume *model_volume = m_parent.get_selection().get_selected_single_volume(m_object_idx, m_volume_idx); + if (model_volume) { + TextInfo text_info = model_volume->get_text_info(); + if (!text_info.m_text.empty()) { + if (m_last_text_mv == model_volume) { + m_last_text_mv = model_volume; + return; + } + m_need_fix = false; + if (plater) { + plater->take_snapshot("enter Text"); + } + m_last_text_mv = model_volume; + load_from_text_info(text_info); + m_edit_text_again = true; + m_text_volume_tran = model_volume->get_matrix(); + m_text_tran_in_object.set_matrix(m_text_volume_tran); + int temp_object_idx; + auto mo = m_parent.get_selection().get_selected_single_object(temp_object_idx); + const ModelInstance *mi = mo->instances[m_parent.get_selection().get_instance_idx()]; + auto world_tran = mi->get_transformation().get_matrix() * m_text_volume_tran; + m_text_tran_in_world.set_matrix(world_tran); + m_text_position_in_world = m_text_tran_in_world.get_offset(); + m_text_normal_in_world = -m_text_tran_in_world.get_matrix().linear().col(1).cast(); + { + TriangleMesh text_attach_mesh(mo->volumes[m_rr.mesh_id]->mesh()); + text_attach_mesh.transform(mo->volumes[m_rr.mesh_id]->get_matrix()); + MeshRaycaster temp_ray_caster(text_attach_mesh); + Vec3f local_center = m_text_tran_in_object.get_offset().cast(); + Vec3f temp_normal; + Vec3f closest_pt = temp_ray_caster.get_closest_point(local_center, &temp_normal); + m_fix_text_position_in_world = mi->get_transformation().get_matrix() * closest_pt.cast(); + m_fix_text_normal_in_world = (mi->get_transformation().get_matrix_no_offset().cast() * temp_normal).normalized(); + if ((m_fix_text_position_in_world - m_text_position_in_world).norm() > 0.1) { + m_need_fix = true; + } + } + // m_rr.mesh_id + m_need_update_text = false; + m_is_modify = true; + } + } + } +} + +void GLGizmoText::data_changed(bool is_serializing) { + load_init_text(); + m_rr.normal = Vec3f::Zero(); +} + CommonGizmosDataID GLGizmoText::on_get_requirements() const { return CommonGizmosDataID( @@ -434,7 +488,7 @@ bool GLGizmoText::on_is_activable() const return true; int obejct_idx, volume_idx; - ModelVolume *model_volume = get_selected_single_volume(obejct_idx, volume_idx); + ModelVolume *model_volume = m_parent.get_selection().get_selected_single_volume(obejct_idx, volume_idx); if (model_volume) return !model_volume->get_text_info().m_text.empty(); @@ -453,31 +507,54 @@ void GLGizmoText::on_render() } ModelObject *mo = nullptr; - mo = m_c->selection_info()->model_object(); - - if (mo == nullptr) { - const Selection &selection = m_parent.get_selection(); - mo = selection.get_model()->objects[m_object_idx]; - } + const Selection &selection = m_parent.get_selection(); + mo = selection.get_model()->objects[m_object_idx]; if (mo == nullptr) { BOOST_LOG_TRIVIAL(info) << boost::format("Text: selected object is null"); return; } - // First check that the mouse pointer is on an object. - const Selection & selection = m_parent.get_selection(); - const ModelInstance *mi = mo->instances[0]; + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; Plater *plater = wxGetApp().plater(); if (!plater) return; +#ifdef DEBUG_TEXT + if (m_text_normal_in_world.norm() > 0.1) { // debug + Geometry::Transformation tran(m_text_volume_tran); + if (tran.get_offset().norm() > 1) { + auto text_volume_tran_world = mi->get_transformation().get_matrix() * m_text_volume_tran; + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(text_volume_tran_world.data())); + render_cross_mark(Vec3f::Zero(), true); + glsafe(::glPopMatrix()); + } - if (!m_is_modify || m_shift_down) { + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(m_text_tran_in_world.get_matrix().data())); + render_cross_mark(Vec3f::Zero(), true); + glsafe(::glPopMatrix()); + + glsafe(::glLineWidth(2.0f)); + ::glBegin(GL_LINES); + glsafe(::glColor3f(1.0f, 0.0f, 0.0f)); + + for (size_t i = 1; i < m_cut_points_in_world.size(); i++) {//draw points + auto target0 = m_cut_points_in_world[i - 1].cast(); + auto target1 = m_cut_points_in_world[i].cast(); + glsafe(::glVertex3f(target0(0), target0(1), target0(2))); + glsafe(::glVertex3f(target1(0), target1(1), target1(2))); + } + glsafe(::glEnd()); + } +#endif + if (!m_is_modify || m_shift_down) {//for temp text const Camera &camera = wxGetApp().plater()->get_camera(); // Precalculate transformations of individual meshes. std::vector trafo_matrices; for (const ModelVolume *mv : mo->volumes) { - if (mv->is_model_part()) trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + if (mv->is_model_part()) + trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); } // Raycast and return if there's no hit. Vec2d mouse_pos; @@ -500,27 +577,33 @@ void GLGizmoText::on_render() if (!position_changed && !m_need_update_text && !m_shift_down) return; + update_text_pos_normal(); } - if (m_is_modify && m_grabbers.size() == 1) { - std::vector trafo_matrices; - for (const ModelVolume *mv : mo->volumes) { - if (mv->is_model_part()) { - trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); - } + if (m_is_modify) { + update_text_pos_normal(); + Geometry::Transformation tran;//= m_text_tran_in_world; + { + double phi; + Vec3d rotation_axis; + Matrix3d rotation_matrix; + Geometry::rotation_from_two_vectors(Vec3d::UnitZ(), m_text_normal_in_world.cast(), rotation_axis, phi, &rotation_matrix); + tran.set_matrix((Transform3d) rotation_matrix); } - - m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); - - float mean_size = (float) (GLGizmoBase::Grabber::FixedGrabberSize); - - m_grabbers[0].center = m_mouse_position_world; - m_grabbers[0].enabled = true; - std::array color = picking_color_component(0); - m_grabbers[0].color = color; - m_grabbers[0].render_for_picking(mean_size); + tran.set_offset(m_text_position_in_world); + bool hover = (m_hover_id == m_move_cube_id); + std::array render_color; + if (hover) { + render_color = TEXT_GRABBER_HOVER_COLOR; + } else + render_color = TEXT_GRABBER_COLOR; + float fullsize = get_grabber_size(); + m_move_grabber.center = tran.get_offset(); + Transform3d rotate_matrix = tran.get_rotation_matrix(); + Transform3d cube_mat = Geometry::translation_transform(m_move_grabber.center) * rotate_matrix * Geometry::scale_transform(fullsize); + render_glmodel(m_move_grabber.get_cube(), render_color, cube_mat); } - + delete_temp_preview_text_volume(); if (m_is_modify && !m_need_update_text) @@ -535,50 +618,36 @@ void GLGizmoText::on_render_for_picking() glsafe(::glDisable(GL_DEPTH_TEST)); int obejct_idx, volume_idx; - ModelVolume *model_volume = get_selected_single_volume(obejct_idx, volume_idx); + ModelVolume *model_volume = m_parent.get_selection().get_selected_single_volume(obejct_idx, volume_idx); if (model_volume && !model_volume->get_text_info().m_text.empty()) { - if (m_grabbers.size() == 1) { - ModelObject *mo = m_c->selection_info()->model_object(); - if (m_is_modify) { - const Selection &selection = m_parent.get_selection(); - mo = selection.get_model()->objects[m_object_idx]; - } - if (mo == nullptr) return; - - const Selection & selection = m_parent.get_selection(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - for (const ModelVolume *mv : mo->volumes) { - if (mv->is_model_part()) { - trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); - } - } - - m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); - - float mean_size = (float) (GLGizmoBase::Grabber::FixedGrabberSize); - m_grabbers[0].center = m_mouse_position_world; - m_grabbers[0].enabled = true; - std::array color = picking_color_component(0); - m_grabbers[0].color = color; - m_grabbers[0].render_for_picking(mean_size); - } + const Selection &selection = m_parent.get_selection(); + auto mo = selection.get_model()->objects[m_object_idx]; + if (mo == nullptr) + return; + auto color = picking_color_component(m_move_cube_id); + m_move_grabber.color[0] = color[0]; + m_move_grabber.color[1] = color[1]; + m_move_grabber.color[2] = color[2]; + m_move_grabber.color[3] = color[3]; + m_move_grabber.render_for_picking(); } } +void GLGizmoText::on_start_dragging() +{ +} + +void GLGizmoText::on_stop_dragging() +{ +} + void GLGizmoText::on_update(const UpdateData &data) { Vec2d mouse_pos = Vec2d(data.mouse_pos.x(), data.mouse_pos.y()); - const ModelObject *mo = m_c->selection_info()->model_object(); - if (m_is_modify) { - const Selection &selection = m_parent.get_selection(); - mo = selection.get_model()->objects[m_object_idx]; - } - if (mo == nullptr) return; - - const Selection & selection = m_parent.get_selection(); + const Selection &selection = m_parent.get_selection(); + auto mo = selection.get_model()->objects[m_object_idx]; + if (mo == nullptr) + return; const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; const Camera & camera = wxGetApp().plater()->get_camera(); @@ -621,7 +690,7 @@ void GLGizmoText::on_update(const UpdateData &data) if (closest_hit == Vec3f::Zero() && closest_normal == Vec3f::Zero()) return; if (closest_hit_mesh_id != -1) { - m_rr = {mouse_pos, closest_hit_mesh_id, closest_hit, closest_normal}; + m_rr = {mouse_pos, closest_hit_mesh_id, closest_hit, closest_normal};//on drag m_need_update_text = true; } } @@ -654,7 +723,7 @@ void GLGizmoText::push_button_style(bool pressed) { ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(238 / 255.f, 238 / 255.f, 238 / 255.f, 1.f)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.f, 1.f, 1.f, 1.f)); } - + } } @@ -723,11 +792,12 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) } } else if (selection.is_single_volume() || selection.is_single_modifier()) { int object_idx, volume_idx; - ModelVolume *model_volume = get_selected_single_volume(object_idx, volume_idx); + ModelVolume *model_volume = m_parent.get_selection().get_selected_single_volume(object_idx, volume_idx); if ((object_idx != m_object_idx || (object_idx == m_object_idx && volume_idx != m_volume_idx)) && model_volume) { + m_last_text_mv = model_volume; TextInfo text_info = model_volume->get_text_info(); - load_from_text_info(text_info); + load_from_text_info(text_info);//mouse click down m_is_modify = true; m_volume_idx = volume_idx; m_object_idx = object_idx; @@ -746,7 +816,17 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0,5.0) * currt_scale); ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 4.0f * currt_scale); GizmoImguiBegin("Text", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); - +#ifdef DEBUG_TEXT + std::string world_hit = "world hit x:" + formatFloat(m_text_position_in_world[0]) + " y:" + formatFloat(m_text_position_in_world[1]) + + " z:" + formatFloat(m_text_position_in_world[2]); + std::string hit = "local hit x:" + formatFloat(m_rr.hit[0]) + " y:" + formatFloat(m_rr.hit[1]) + " z:" + formatFloat(m_rr.hit[2]); + std::string normal = "normal x:" + formatFloat(m_rr.normal[0]) + " y:" + formatFloat(m_rr.normal[1]) + " z:" + formatFloat(m_rr.normal[2]); + auto cut_dir = "cut_dir x:" + formatFloat(m_cut_plane_dir_in_world[0]) + " y:" + formatFloat(m_cut_plane_dir_in_world[1]) + " z:" + formatFloat(m_cut_plane_dir_in_world[2]); + m_imgui->text(world_hit); + m_imgui->text(hit); + m_imgui->text(normal); + m_imgui->text(cut_dir); +#endif float space_size = m_imgui->get_style_scaling() * 8; float font_cap = m_imgui->calc_text_size(_L("Font")).x; float size_cap = m_imgui->calc_text_size(_L("Size")).x; @@ -823,9 +903,10 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) m_imgui->text(_L("Size")); ImGui::SameLine(caption_size); ImGui::PushItemWidth(input_size); - if(ImGui::InputFloat("###font_size", &m_font_size, 0.0f, 0.0f, "%.2f")) + if (ImGui::InputFloat("###font_size", &m_font_size, 0.0f, 0.0f, "%.2f")) { + limit_value(m_font_size, m_font_size_min, m_font_size_max); m_need_update_text = true; - if (m_font_size < 3.0f)m_font_size = 3.0f; + } ImGui::SameLine(); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * currt_scale); @@ -887,9 +968,9 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(caption_size); ImGui::PushItemWidth(list_width); old_value = m_embeded_depth; - ImGui::InputFloat("###text_embeded_depth", &m_embeded_depth, 0.0f, 0.0f, "%.2f"); - if (m_embeded_depth < 0.f) - m_embeded_depth = 0.f; + if (ImGui::InputFloat("###text_embeded_depth", &m_embeded_depth, 0.0f, 0.0f, "%.2f")) { + limit_value(m_embeded_depth, 0.0f, m_embeded_depth_max); + } if (old_value != m_embeded_depth) m_need_update_text = true; @@ -900,7 +981,16 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) if(ImGui::InputText("", m_text, sizeof(m_text))) m_need_update_text = true; - + std::string text = std::string(m_text); + if (text.empty() && m_is_modify) { + m_imgui->warning_text(_L("Warning:Input cannot be empty!")); + } + if (m_show_warning) { + m_imgui->warning_text(_L("Warning:create text fail.")); + } + if (m_show_text_normal_error) { + m_imgui->warning_text(_L("Warning:text normal is error.")); + } ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); @@ -909,16 +999,20 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - + ImGui::SameLine(caption_size); ImGui::AlignTextToFramePadding(); - if (m_imgui->qdt_checkbox(_L("Surface"), m_is_surface_text)) + if (m_imgui->qdt_checkbox(_L("Surface"), m_is_surface_text)){ m_need_update_text = true; + } ImGui::SameLine(); ImGui::AlignTextToFramePadding(); - if (m_imgui->qdt_checkbox(_L("Horizontal text"), m_keep_horizontal)) + auto keep_horizontal = !m_is_surface_text; + if (m_imgui->qdt_checkbox(_L("Horizontal text"), keep_horizontal)) { m_need_update_text = true; + } + m_is_surface_text = !keep_horizontal; //ImGui::SameLine(); //ImGui::AlignTextToFramePadding(); @@ -974,20 +1068,6 @@ void GLGizmoText::show_tooltip_information(float x, float y) ImGui::PopStyleVar(2); } -ModelVolume *GLGizmoText::get_selected_single_volume(int &out_object_idx, int &out_volume_idx) const -{ - if (m_parent.get_selection().is_single_volume() || m_parent.get_selection().is_single_modifier()) { - const Selection &selection = m_parent.get_selection(); - const GLVolume * gl_volume = selection.get_volume(*selection.get_volume_idxs().begin()); - out_object_idx = gl_volume->object_idx(); - ModelObject *model_object = selection.get_model()->objects[out_object_idx]; - out_volume_idx = gl_volume->volume_idx(); - if (out_volume_idx < model_object->volumes.size()) - return model_object->volumes[out_volume_idx]; - } - return nullptr; -} - void GLGizmoText::reset_text_info() { m_font_name = ""; @@ -1001,12 +1081,40 @@ void GLGizmoText::reset_text_info() m_rotate_angle = 0; m_text_gap = 0.f; m_is_surface_text = true; - m_keep_horizontal = false; + m_rr = RaycastResult(); m_is_modify = false; } -void GLGizmoText::update_font_status() { +void GLGizmoText::update_text_pos_normal() { + if (m_rr.mesh_id < 0) { return; } + if (m_rr.normal.norm() < 0.1) { return; } + const Selection &selection = m_parent.get_selection(); + auto mo = selection.get_model()->objects[m_object_idx]; + if (mo == nullptr) { + BOOST_LOG_TRIVIAL(info) << boost::format("Text: selected object is null"); + return; + } + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + + std::vector w_matrices; + std::vector mv_trans; + for (const ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { + w_matrices.emplace_back(Geometry::Transformation(mi->get_transformation().get_matrix() * mv->get_matrix())); + mv_trans.emplace_back(Geometry::Transformation(mv->get_matrix())); + } + } +#ifdef DEBUG_TEXT_VALUE + m_rr.hit = Vec3f(-0.58, -1.70, -12.8); + m_rr.normal = Vec3f(0,0,-1);//just rotate cube +#endif + m_text_position_in_world = w_matrices[m_rr.mesh_id].get_matrix() * m_rr.hit.cast(); + m_text_normal_in_world = (w_matrices[m_rr.mesh_id].get_matrix_no_offset().cast() * m_rr.normal).normalized(); +} + +void GLGizmoText::update_font_status() +{ std::unique_lock lock(m_mutex); m_font_status.reserve(m_avail_font_names.size()); for (std::string font_name : m_avail_font_names) { @@ -1038,17 +1146,14 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) int text_num = texts.size(); m_position_points.clear(); m_normal_points.clear(); - ModelObject *mo = m_c->selection_info()->model_object(); - if (m_is_modify) { - const Selection &selection = m_parent.get_selection(); - mo = selection.get_model()->objects[m_object_idx]; - } + + const Selection &selection = m_parent.get_selection(); + auto mo = selection.get_model()->objects[m_object_idx]; if (mo == nullptr) return false; - const Selection & selection = m_parent.get_selection(); const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - + m_model_object_in_world_tran = mi->get_transformation(); // Precalculate transformations of individual meshes. std::vector trafo_matrices; std::vector rotate_trafo_matrices; @@ -1064,8 +1169,20 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) return false; } - m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); - m_mouse_normal_world = rotate_trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.normal(0), m_rr.normal(1), m_rr.normal(2)); + // mouse_position_world may is error after user modified + if (m_need_fix) { + m_need_fix = false; + m_text_position_in_world = m_fix_text_position_in_world; + m_text_normal_in_world = m_fix_text_normal_in_world; + } + if (m_text_normal_in_world.norm() < 0.1) { + m_show_text_normal_error = true; + BOOST_LOG_TRIVIAL(info) << "m_text_normal_in_object is error"; + return false; + } + m_show_text_normal_error = false; + auto mouse_position_world = m_text_position_in_world.cast(); + auto mouse_normal_world = m_text_normal_in_world.cast(); TriangleMesh slice_meshs; int mesh_index = 0; @@ -1089,34 +1206,40 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) ModelVolume* volume = mo->volumes[volume_index]; - Vec3d temp_position = m_mouse_position_world; - Vec3d temp_normal = m_mouse_normal_world; - - Vec3d cut_plane = Vec3d::UnitY(); + Vec3d temp_normal = m_text_normal_in_world.cast(); + Vec3d cut_plane_in_world = Vec3d::UnitY(); double epson = 1e-6; if (!(abs(temp_normal.x()) <= epson && abs(temp_normal.y()) <= epson && abs(temp_normal.z()) > epson)) { // temp_normal != Vec3d::UnitZ() Vec3d v_plane = temp_normal.cross(Vec3d::UnitZ()); - cut_plane = v_plane.cross(temp_normal); + cut_plane_in_world = v_plane.cross(temp_normal); } - Transform3d rotate_trans; - rotate_trans.setIdentity(); - rotate_trans.rotate(Eigen::AngleAxisd(Geometry::deg2rad(m_rotate_angle), temp_normal)); - cut_plane = rotate_trans * cut_plane; + m_cut_plane_dir_in_world = cut_plane_in_world; - m_cut_plane_dir = cut_plane; + auto y_dir = -m_text_normal_in_world.cast(); + Vec3d x_dir_world = y_dir.cross(m_cut_plane_dir_in_world); + m_text_tran_in_world = Geometry::generate_transform(x_dir_world, y_dir, m_cut_plane_dir_in_world.cast(), m_text_position_in_world); + Geometry::Transformation rotate_trans; + rotate_trans.set_rotation(Vec3d(0, Geometry::deg2rad(m_rotate_angle), 0)); // m_rotate_angle + m_text_tran_in_world.set_matrix(m_text_tran_in_world.get_matrix() * rotate_trans.get_matrix()); + m_cut_plane_dir_in_world = m_text_tran_in_world.get_matrix().linear().col(2); - if (m_keep_horizontal && m_mouse_normal_world != Vec3d::UnitZ()) - m_cut_plane_dir = Vec3d::UnitZ(); + // generate clip cs at click pos + auto text_position_in_object = mi->get_transformation().get_matrix().inverse() * m_text_position_in_world.cast(); + auto rotate_mat_inv = mi->get_transformation().get_matrix_no_offset().inverse(); + + auto text_tran_in_object = mi->get_transformation().get_matrix().inverse() * m_text_tran_in_world.get_matrix(); // Geometry::generate_transform(cs_x_dir, cs_y_dir, cs_z_dir, text_position_in_object); // todo modify by m_text_tran_in_world + m_text_tran_in_object.set_matrix(text_tran_in_object); + m_text_cs_to_world_tran = mi->get_transformation().get_matrix() * m_text_tran_in_object.get_matrix(); if (!m_is_surface_text) { m_position_points.resize(text_num); m_normal_points.resize(text_num); - Vec3d pos_dir = m_cut_plane_dir.cross(m_mouse_normal_world); + Vec3d pos_dir = m_cut_plane_dir_in_world.cross(mouse_normal_world); pos_dir.normalize(); if (text_num % 2 == 1) { - m_position_points[text_num / 2] = m_mouse_position_world; + m_position_points[text_num / 2] = mouse_position_world; for (int i = 0; i < text_num / 2; ++i) { double left_gap = text_lengths[text_num / 2 - i - 1] + m_text_gap + text_lengths[text_num / 2 - i]; if (left_gap < 0) @@ -1142,8 +1265,8 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) right_gap = 0; if (i == 0) { - m_position_points[text_num / 2 - 1 - i] = m_mouse_position_world - left_gap * pos_dir; - m_position_points[text_num / 2 + i] = m_mouse_position_world + right_gap * pos_dir; + m_position_points[text_num / 2 - 1 - i] = mouse_position_world - left_gap * pos_dir; + m_position_points[text_num / 2 + i] = mouse_position_world + right_gap * pos_dir; continue; } @@ -1153,50 +1276,21 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) } for (int i = 0; i < text_num; ++i) { - m_normal_points[i] = m_mouse_normal_world; + m_normal_points[i] = mouse_normal_world; } return true; } - double phi; - Vec3d rotation_axis; - Matrix3d rotation_matrix; - Geometry::rotation_from_two_vectors(m_cut_plane_dir, Vec3d::UnitZ(), rotation_axis, phi, &rotation_matrix); - if (abs(phi - PI) < 1e-6) { - Transform3d transform = Transform3d::Identity(); - transform.rotate(Eigen::AngleAxisd(phi, m_mouse_normal_world)); - rotation_matrix = transform.matrix().block<3, 3>(0, 0); - } - - Transform3d transfo1; - transfo1.setIdentity(); - transfo1.translate(-(mi->get_transformation().get_offset() + volume->get_transformation().get_offset())); - transfo1 = rotation_matrix * transfo1; - - Transform3d transfo2; - transfo2.setIdentity(); - transfo2.translate(mi->get_transformation().get_offset() + volume->get_transformation().get_offset()); - Transform3d transfo = transfo2 * transfo1; - - Vec3d click_point = transfo * temp_position; - MeshSlicingParams slicing_params; - slicing_params.trafo = transfo * mi->get_transformation().get_matrix() /** volume->get_transformation().get_matrix()*/; + slicing_params.trafo = m_text_tran_in_object.get_matrix().inverse(); // for debug // its_write_obj(slice_meshs.its, "D:/debug_files/mesh.obj"); - // generate polygons - const Polygons temp_polys = slice_mesh(slice_meshs.its, click_point.z(), slicing_params); - - m_mouse_position_world = click_point; - m_mouse_normal_world = transfo * temp_normal; - - m_mouse_position_world.x() *= 1e6; - m_mouse_position_world.y() *= 1e6; - + const Polygons temp_polys = slice_mesh(slice_meshs.its, 0, slicing_params); + Vec3d scale_click_pt(scale_(0), scale_(0), 0); // for debug - //export_regions_to_svg(Point(m_mouse_position_world.x(), m_mouse_position_world.y()), temp_polys); + // export_regions_to_svg(Point(scale_pt.x(), scale_pt.y()), temp_polys); Polygons polys = union_(temp_polys); @@ -1216,7 +1310,7 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) for (int i = 0; i < lines.size(); ++i) { Line line = lines[i]; double distance = min_distance; - if (point_in_line_rectange(line, Point(m_mouse_position_world.x(), m_mouse_position_world.y()), distance)) { + if (point_in_line_rectange(line, Point(scale_click_pt.x(), scale_click_pt.y()), distance)) { if (distance < min_distance) { min_distance = distance; index = i; @@ -1231,30 +1325,22 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) return false; } - auto make_trafo_for_slicing = [](const Transform3d &trafo) -> Transform3d { - auto t = trafo; - static constexpr const double s = 1. / SCALING_FACTOR; - t.prescale(Vec3d(s, s, 1.)); - return t.cast(); - }; - transfo = make_trafo_for_slicing(transfo); - Transform3d transfo_inv = transfo.inverse(); - std::vector new_points; + m_cut_points_in_world.clear(); + m_cut_points_in_world.reserve(hit_ploy.points.size()); for (int i = 0; i < hit_ploy.points.size(); ++i) { - new_points.emplace_back(transfo_inv * Vec3d(hit_ploy.points[i].x(), hit_ploy.points[i].y(), click_point.z())); + m_cut_points_in_world.emplace_back(m_text_cs_to_world_tran * Vec3d(unscale_(hit_ploy.points[i].x()), unscale_(hit_ploy.points[i].y()), 0)); } - m_mouse_position_world = transfo_inv * m_mouse_position_world; - Polygon_3D new_polygon(new_points); + Polygon_3D new_polygon(m_cut_points_in_world); m_position_points.resize(text_num); if (text_num % 2 == 1) { - m_position_points[text_num / 2] = Vec3d(m_mouse_position_world.x(), m_mouse_position_world.y(), m_mouse_position_world.z()); + m_position_points[text_num / 2] = Vec3d(mouse_position_world.x(), mouse_position_world.y(), mouse_position_world.z()); std::vector lines = new_polygon.get_lines(); Line_3D line = lines[index]; { int index1 = index; - double left_length = (m_mouse_position_world - line.a).cast().norm(); + double left_length = (mouse_position_world - line.a).cast().norm(); int left_num = text_num / 2; while (left_num > 0) { double gap_length = (text_lengths[left_num] + m_text_gap + text_lengths[left_num - 1]); @@ -1288,7 +1374,7 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) { int index2 = index; - double right_length = (line.b - m_mouse_position_world).cast().norm(); + double right_length = (line.b - mouse_position_world).cast().norm(); int right_num = text_num / 2; while (right_num > 0) { double gap_length = (text_lengths[text_num - right_num] + m_text_gap + text_lengths[text_num - right_num - 1]); @@ -1328,7 +1414,7 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) Line_3D line = lines[index]; { int index1 = index; - double left_length = (m_mouse_position_world - line.a).cast().norm(); + double left_length = (mouse_position_world - line.a).cast().norm(); int left_num = text_num / 2; for (int i = 0; i < text_num / 2; ++i) { double gap_length = 0; @@ -1368,7 +1454,7 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) { int index2 = index; - double right_length = (line.b - m_mouse_position_world).cast().norm(); + double right_length = (line.b - mouse_position_world).cast().norm(); int right_num = text_num / 2; double gap_length = 0; for (int i = 0; i < text_num / 2; ++i) { @@ -1427,6 +1513,7 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) return abs(s0 + s1 + s2 - s); }; + bool is_mirrored =m_model_object_in_world_tran.is_left_handed(); for (int i = 0; i < m_position_points.size(); ++i) { for (auto indice : mesh.its.indices) { stl_vertex stl_point0 = mesh.its.vertices[indice[0]]; @@ -1449,6 +1536,9 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) Vec3d s2 = point2 - point0; m_normal_points[i] = s1.cross(s2); m_normal_points[i].normalize(); + if(is_mirrored){ + m_normal_points[i] = -m_normal_points[i]; + } } } } @@ -1461,9 +1551,8 @@ TriangleMesh GLGizmoText::get_text_mesh(const char* text_str, const Vec3d &posit load_text_shape(text_str, m_font_name.c_str(), m_font_size, m_thickness + m_embeded_depth, m_bold, m_italic, text_result); TriangleMesh mesh = text_result.text_mesh; - auto center = mesh.bounding_box().center(); - double mesh_offset = center.z(); - mesh.translate(-text_result.text_width / 2, -m_font_size / 4, -center.z()); + + mesh.translate(-text_result.text_width / 2, -m_font_size / 4, 0); double phi; Vec3d rotation_axis; @@ -1486,15 +1575,11 @@ TriangleMesh GLGizmoText::get_text_mesh(const char* text_str, const Vec3d &posit mesh.rotate(phi, rotation_axis); - const Selection & selection = m_parent.get_selection(); - ModelObject * model_object = selection.get_model()->objects[m_object_idx]; - Geometry::Transformation instance_transformation = model_object->instances[0]->get_transformation(); - Vec3d offset = position - instance_transformation.get_offset(); - offset = offset + mesh_offset * normal; - offset = offset - m_embeded_depth * normal; + + Vec3d offset = position - m_embeded_depth * normal; mesh.translate(offset.x(), offset.y(), offset.z()); - return mesh; + return mesh;//mesh in object cs } bool GLGizmoText::update_raycast_cache(const Vec2d &mouse_position, const Camera &camera, const std::vector &trafo_matrices) @@ -1534,8 +1619,8 @@ bool GLGizmoText::update_raycast_cache(const Vec2d &mouse_position, const Camera } } } - - m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_nromal}; + + m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_nromal};//update_raycast_cache berfor click down return true; } @@ -1552,49 +1637,65 @@ void GLGizmoText::generate_text_volume(bool is_temp) alphas.push_back(str_cnv.to_bytes(w)); } - update_text_positions(alphas); + update_text_positions(alphas);//update m_model_object_in_world_tran if (m_position_points.size() == 0) return; - + auto inv_text_cs_in_object = (m_model_object_in_world_tran.get_matrix() * m_text_tran_in_object.get_matrix()).inverse(); + auto inv_text_cs_in_object_no_offset = (m_model_object_in_world_tran.get_matrix_no_offset() * m_text_tran_in_object.get_matrix_no_offset()).inverse(); TriangleMesh mesh; for (int i = 0; i < alphas.size(); ++i) { - TriangleMesh sub_mesh = get_text_mesh(alphas[i].c_str(), m_position_points[i], m_normal_points[i], m_cut_plane_dir); + auto position = inv_text_cs_in_object * m_position_points[i]; + auto normal = inv_text_cs_in_object_no_offset * m_normal_points[i]; + auto cut_plane_dir = inv_text_cs_in_object_no_offset * m_cut_plane_dir_in_world; + TriangleMesh sub_mesh = get_text_mesh(alphas[i].c_str(), position, normal, cut_plane_dir); mesh.merge(sub_mesh); } - if (mesh.empty()) return; - Plater *plater = wxGetApp().plater(); if (!plater) return; TextInfo text_info = get_text_info(); + const Selection &selection = m_parent.get_selection(); + ModelObject * model_object = selection.get_model()->objects[m_object_idx]; + int cur_volume_id; if (m_is_modify && m_need_update_text) { if (m_object_idx == -1 || m_volume_idx == -1) { BOOST_LOG_TRIVIAL(error) << boost::format("Text: selected object_idx = %1%, volume_idx = %2%") % m_object_idx % m_volume_idx; return; } - plater->take_snapshot("Modify Text"); - const Selection &selection = m_parent.get_selection(); - ModelObject * model_object = selection.get_model()->objects[m_object_idx]; + if (!is_temp) { + plater->take_snapshot("Modify Text"); + } ModelVolume * model_volume = model_object->volumes[m_volume_idx]; - ModelVolume * new_model_volume = model_object->add_volume(std::move(mesh)); + ModelVolume * new_model_volume = model_object->add_volume(std::move(mesh),false); + new_model_volume->set_transformation(m_text_tran_in_object.get_matrix()); new_model_volume->set_text_info(text_info); new_model_volume->name = model_volume->name; new_model_volume->set_type(model_volume->type()); new_model_volume->config.apply(model_volume->config); std::swap(model_object->volumes[m_volume_idx], model_object->volumes.back()); model_object->delete_volume(model_object->volumes.size() - 1); + model_object->invalidate_bounding_box(); plater->update(); + m_text_volume_tran = new_model_volume->get_matrix(); } else { - if (m_need_update_text) + if (!is_temp && m_need_update_text) plater->take_snapshot("Add Text"); ObjectList *obj_list = wxGetApp().obj_list(); - int volume_id = obj_list->load_mesh_part(mesh, "text_shape", text_info, is_temp); - m_preview_text_volume_id = is_temp ? volume_id : -1; + cur_volume_id = obj_list->add_text_part(mesh, "text_shape", text_info, m_text_tran_in_object.get_matrix(), is_temp); + m_preview_text_volume_id = is_temp ? cur_volume_id : -1; + if (cur_volume_id >= 0) { + m_show_warning = false; + ModelVolume *text_model_volume = model_object->volumes[cur_volume_id]; + m_text_volume_tran = text_model_volume->get_matrix(); + } + else { + m_show_warning = true; + } } m_need_update_text = false; } @@ -1627,12 +1728,11 @@ TextInfo GLGizmoText::get_text_info() text_info.m_italic = m_italic; text_info.m_thickness = m_thickness; text_info.m_text = m_text; - text_info.m_rr = m_rr; + text_info.m_rr.mesh_id = m_rr.mesh_id; text_info.m_embeded_depth = m_embeded_depth; text_info.m_rotate_angle = m_rotate_angle; text_info.m_text_gap = m_text_gap; text_info.m_is_surface_text = m_is_surface_text; - text_info.m_keep_horizontal = m_keep_horizontal; return text_info; } @@ -1651,12 +1751,11 @@ void GLGizmoText::load_from_text_info(const TextInfo &text_info) m_italic = text_info.m_italic; m_thickness = text_info.m_thickness; strcpy(m_text, text_info.m_text.c_str()); - m_rr = text_info.m_rr; + m_rr.mesh_id = text_info.m_rr.mesh_id; m_embeded_depth = text_info.m_embeded_depth; m_rotate_angle = text_info.m_rotate_angle; m_text_gap = text_info.m_text_gap; m_is_surface_text = text_info.m_is_surface_text; - m_keep_horizontal = text_info.m_keep_horizontal; } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp index 5e144d7..630613c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp @@ -13,6 +13,8 @@ enum class ModelVolumeType : int; class ModelVolume; namespace GUI { +//#define DEBUG_TEXT +//#define DEBUG_TEXT_VALUE enum class SLAGizmoEventType : unsigned char; class GLGizmoText : public GLGizmoBase @@ -22,17 +24,19 @@ private: char m_text[1024] = { 0 }; std::string m_font_name; float m_font_size = 16.f; + const float m_font_size_min = 3.f; + const float m_font_size_max = 1000.f; int m_curr_font_idx = 0; bool m_bold = true; bool m_italic = false; float m_thickness = 2.f; - float m_thickness_min = 0.01f; - float m_thickness_max = 999.99f; + const float m_thickness_min = 0.01f; + const float m_thickness_max = 1000.f; float m_embeded_depth = 0.f; + const float m_embeded_depth_max = 1000.f; float m_rotate_angle = 0; float m_text_gap = 0.f; bool m_is_surface_text = false; - bool m_keep_horizontal = false; mutable RaycastResult m_rr; float m_combo_height = 0.0f; @@ -62,25 +66,42 @@ private: std::mutex m_mutex; std::thread m_thread; + bool m_edit_text_again = false; bool m_is_modify = false; bool m_need_update_text = false; + bool m_show_warning = false; + bool m_show_text_normal_error = false; int m_object_idx = -1; int m_volume_idx = -1; int m_preview_text_volume_id = -1; + Vec3d m_fix_text_position_in_world = Vec3d::Zero(); + Vec3f m_fix_text_normal_in_world = Vec3f::Zero(); + bool m_need_fix; + Vec3d m_text_position_in_world = Vec3d::Zero(); + Vec3f m_text_normal_in_world = Vec3f::Zero(); + Geometry::Transformation m_text_tran_in_object; + Geometry::Transformation m_text_tran_in_world; + Geometry::Transformation m_model_object_in_world_tran; + Transform3d m_text_cs_to_world_tran; - Vec3d m_mouse_position_world = Vec3d::Zero(); - Vec3d m_mouse_normal_world = Vec3d::Zero(); - - Vec3d m_cut_plane_dir = Vec3d::UnitZ(); + Vec3d m_cut_plane_dir_in_world = Vec3d::UnitZ(); std::vector m_position_points; std::vector m_normal_points; - + std::vector m_cut_points_in_world; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + Transform3d m_text_volume_tran; + ModelVolume * m_last_text_mv; + // move gizmo + Grabber m_move_grabber; + const int m_move_cube_id = 1; + + // TRN - Title in Undo/Redo stack after move with SVG along emboss axe - From surface + const std::string move_snapshot_name = "Text move"; public: GLGizmoText(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -100,19 +121,23 @@ protected: virtual bool on_is_activable() const override; virtual void on_render() override; virtual void on_render_for_picking() override; + virtual void on_start_dragging() override; + virtual void on_stop_dragging() override; virtual void on_update(const UpdateData &data) override; void push_combo_style(const float scale); void pop_combo_style(); void push_button_style(bool pressed); void pop_button_style(); virtual void on_set_state() override; + virtual void data_changed(bool is_serializing) override; virtual CommonGizmosDataID on_get_requirements() const override; virtual void on_render_input_window(float x, float y, float bottom_limit); void show_tooltip_information(float x, float y); private: - ModelVolume *get_selected_single_volume(int& out_object_idx, int& out_volume_idx) const; + void load_init_text(); + void update_text_pos_normal(); void update_font_status(); void reset_text_info(); bool update_text_positions(const std::vector& texts); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index bccce4c..a871bdb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -414,7 +414,7 @@ void CommonGizmosDataObjects::ObjectClipper::render_cut(const std::vectorselection_info(); - //consider normal view or assemble view + //consider normal view or assemble view const ModelObject * mo = sel_info->model_object(); Geometry::Transformation inst_trafo; bool is_assem_cnv = get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView; @@ -463,7 +463,7 @@ void CommonGizmosDataObjects::ObjectClipper::set_behaviour(bool hide_clipped, bo clipper.first->set_behaviour(fill_cut, contour_width); } -void ObjectClipper::set_position(double pos, bool keep_normal) +void ObjectClipper::set_position(double pos, bool keep_normal, bool vertical_normal) { const ModelObject *mo = get_pool()->selection_info()->model_object(); int active_inst = get_pool()->selection_info()->get_active_instance(); @@ -471,7 +471,12 @@ void ObjectClipper::set_position(double pos, bool keep_normal) if (active_inst < 0) { return; } - Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward(); + Vec3d normal; + if(vertical_normal) { + normal = {0, 0, 1}; + }else { + normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward(); + } Vec3d center; if (get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) { const SelectionInfo *sel_info = get_pool()->selection_info(); @@ -491,6 +496,12 @@ void ObjectClipper::set_position(double pos, bool keep_normal) get_pool()->get_canvas()->set_as_dirty(); } +void ObjectClipper::set_position_to_init_layer() +{ + m_clp.reset(new ClippingPlane({0, 0, 1}, 0.1)); + get_pool()->get_canvas()->set_as_dirty(); +} + int CommonGizmosDataObjects::ObjectClipper::get_number_of_contours() const { int sum = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 1eabff3..8e5956c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -4,14 +4,11 @@ #include #include +#include "libslic3r/Model.hpp" #include "slic3r/GUI/MeshUtils.hpp" #include "libslic3r/SLA/Hollowing.hpp" namespace Slic3r { - -class ModelObject; - - namespace GUI { class GLCanvas3D; @@ -266,8 +263,9 @@ public: CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } #endif // NDEBUG - void set_position(double pos, bool keep_normal); + void set_position(double pos, bool keep_normal, bool vertical_normal=false); double get_position() const { return m_clp_ratio; } + void set_position_to_init_layer(); ClippingPlane* get_clipping_plane() const { return m_clp.get(); } void render_cut(const std::vector *ignore_idxs = nullptr) const; void set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index b415965..9547117 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -15,6 +15,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp" // QDS #include "slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp" @@ -23,6 +24,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" #include "slic3r/GUI/Gizmos/GLGizmoText.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp" #include "slic3r/GUI/Gizmos/GLGizmoAssembly.hpp" @@ -221,10 +223,12 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, m_is_dark ? "toolbar_support_dark.svg" : "toolbar_support.svg", EType::FdmSupports)); m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam)); m_gizmos.emplace_back(new GLGizmoText(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Text)); + m_gizmos.emplace_back(new GLGizmoSVG(m_parent, EType::Svg)); m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmuSegmentation)); m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure)); m_gizmos.emplace_back(new GLGizmoAssembly(m_parent, m_is_dark ? "toolbar_assembly_dark.svg" : "toolbar_assembly.svg", EType::Assembly)); m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify)); + m_gizmos.emplace_back(new GLGizmoBrimEars(m_parent, m_is_dark ? "toolbar_brimears_dark.svg" : "toolbar_brimears.svg", EType::BrimEars)); //m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++)); //m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++)); //m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", sprite_id++)); @@ -264,6 +268,16 @@ bool GLGizmosManager::init_icon_textures() else return false; + if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/toolbar_reset_zero.svg", 14, 14, texture_id)) + icon_list.insert(std::make_pair((int) IC_TOOLBAR_RESET_ZERO, texture_id)); + else + return false; + + if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/toolbar_reset_zero_hover.svg", 14, 14, texture_id)) + icon_list.insert(std::make_pair((int) IC_TOOLBAR_RESET_ZERO_HOVER, texture_id)); + else + return false; + if (IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/toolbar_tooltip.svg", 30, 22, texture_id)) icon_list.insert(std::make_pair((int)IC_TOOLBAR_TOOLTIP, texture_id)); else @@ -375,6 +389,10 @@ bool GLGizmosManager::open_gizmo(EType type) return false; } +bool GLGizmosManager::open_gizmo(unsigned char type) +{ + return open_gizmo((EType)type); +} bool GLGizmosManager::check_gizmos_closed_except(EType type) const { @@ -466,6 +484,7 @@ void GLGizmosManager::update_data() ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; set_flattening_data(model_object); set_sla_support_data(model_object); + set_brim_data(model_object); set_painter_gizmo_data(); } else if (selection.is_single_volume() || selection.is_single_modifier()) @@ -477,6 +496,7 @@ void GLGizmosManager::update_data() finish_cut_rotation(); set_flattening_data(nullptr); set_sla_support_data(nullptr); + set_brim_data(nullptr); set_painter_gizmo_data(); } else if (is_wipe_tower) @@ -486,6 +506,7 @@ void GLGizmosManager::update_data() set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(proj_cfg.option("wipe_tower_rotation_angle"))->value)); set_flattening_data(nullptr); set_sla_support_data(nullptr); + set_brim_data(nullptr); set_painter_gizmo_data(); } else @@ -494,12 +515,15 @@ void GLGizmosManager::update_data() set_rotation(Vec3d::Zero()); set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); + set_brim_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); set_painter_gizmo_data(); } //QDS: GUI refactor: add object manipulation in gizmo - m_object_manipulation.update_ui_from_settings(); - m_object_manipulation.UpdateAndShow(true); + if (!selection.is_empty()) { + m_object_manipulation.update_ui_from_settings(); + m_object_manipulation.UpdateAndShow(true); + } } bool GLGizmosManager::is_running() const @@ -607,6 +631,14 @@ void GLGizmosManager::finish_cut_rotation() dynamic_cast(m_gizmos[Cut].get())->finish_rotation(); } +void GLGizmosManager::update_paint_base_camera_rotate_rad() +{ + if (m_current == MmuSegmentation || m_current == Seam) { + auto paint_gizmo = dynamic_cast(m_gizmos[m_current].get()); + paint_gizmo->update_front_view_radian(); + } +} + Vec3d GLGizmosManager::get_flattening_normal() const { if (!m_enabled || m_gizmos.empty()) @@ -636,6 +668,14 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object) gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection()); } +void GLGizmosManager::set_brim_data(ModelObject* model_object) +{ + if (!m_enabled || m_gizmos.empty()) + return; + auto* gizmo_brim = dynamic_cast(m_gizmos[BrimEars].get()); + gizmo_brim->set_brim_data(model_object, m_parent.get_selection()); +} + void GLGizmosManager::set_painter_gizmo_data() { if (!m_enabled || m_gizmos.empty()) @@ -684,6 +724,19 @@ bool GLGizmosManager::is_show_only_active_plate() return false; } +bool GLGizmosManager::is_ban_move_glvolume() +{ + auto current_type = get_current_type(); + if (current_type == GLGizmosManager::EType::Undefined || + current_type == GLGizmosManager::EType::Move || + current_type == GLGizmosManager::EType::Rotate || + current_type == GLGizmosManager::EType::Scale) { + return false; + } + return true; +} + + //1.9.5 bool GLGizmosManager::get_gizmo_active_condition(GLGizmosManager::EType type) { if (auto cur_gizmo = get_gizmo(type)) { @@ -692,7 +745,8 @@ bool GLGizmosManager::get_gizmo_active_condition(GLGizmosManager::EType type) { return false; } -void GLGizmosManager::check_object_located_outside_plate() { +void GLGizmosManager::check_object_located_outside_plate(bool change_plate) +{ PartPlateList &plate_list = wxGetApp().plater()->get_partplate_list(); auto curr_plate_index = plate_list.get_curr_plate_index(); Selection & selection = m_parent.get_selection(); @@ -710,7 +764,7 @@ void GLGizmosManager::check_object_located_outside_plate() { ModelObjectPtrs objects = plate->get_objects_on_this_plate(); for (auto object : objects) { if (model_object == object) { - if (curr_plate_index != i) { // confirm selected model_object at corresponding plate + if (change_plate && curr_plate_index != i) { // confirm selected model_object at corresponding plate wxGetApp().plater()->get_partplate_list().select_plate(i); } find_object = true; @@ -742,6 +796,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Text) return dynamic_cast(m_gizmos[Text].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == Svg) + return dynamic_cast(m_gizmos[Svg].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Measure) return dynamic_cast(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Assembly) @@ -750,10 +806,28 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == MeshBoolean) return dynamic_cast(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == BrimEars) + return dynamic_cast(m_gizmos[BrimEars].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else return false; } +bool GLGizmosManager::is_paint_gizmo() +{ + return m_current == EType::FdmSupports || + m_current == EType::MmuSegmentation || + m_current == EType::Seam; +} + +bool GLGizmosManager::is_allow_select_all() { + if (m_current == Undefined || m_current == EType::Move|| + m_current == EType::Rotate || + m_current == EType::Scale) { + return true; + } + return false; +} + ClippingPlane GLGizmosManager::get_clipping_plane() const { if (! m_common_gizmos_data @@ -848,7 +922,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) { bool processed = false; - if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) { + if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == BrimEars) { float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown() // QDS @@ -886,7 +960,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // mouse anywhere if (evt.Moving()) { m_tooltip = update_hover_state(mouse_pos); - if (m_current == MmuSegmentation || m_current == FdmSupports || m_current == Text) + if (m_current == MmuSegmentation || m_current == FdmSupports || m_current == Text || m_current == BrimEars || m_current == Svg) // QDS gizmo_event(SLAGizmoEventType::Moving, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); } else if (evt.LeftUp()) { @@ -969,20 +1043,39 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) case Move: { // Apply new temporary offset - selection.translate(get_displacement()); + TransformationType trafo_type; + trafo_type.set_relative(); + switch (wxGetApp().obj_manipul()->get_coordinates_type()) { + case ECoordinatesType::Instance: { + trafo_type.set_instance(); + break; + } + case ECoordinatesType::Local: { + trafo_type.set_local(); + break; + } + default: { + break; + } + } + selection.translate(get_displacement(), trafo_type); // QDS //wxGetApp().obj_manipul()->set_dirty(); break; } - case Scale: - { + case Scale: { // Apply new temporary scale factors - TransformationType transformation_type(TransformationType::Local_Absolute_Joint); - //if (evt.AltDown()) - // transformation_type.set_independent(); - selection.scale(get_scale(), transformation_type); - if (control_down && m_gizmos[m_current].get()->get_hover_id() < 6) - selection.translate(get_scale_offset(), true); + TransformationType transformation_type; + if (wxGetApp().obj_manipul()->is_local_coordinates()) { + transformation_type.set_local(); + } else if (wxGetApp().obj_manipul()->is_instance_coordinates()) + transformation_type.set_instance(); + transformation_type.set_relative(); + + if (evt.AltDown()) + transformation_type.set_independent(); + selection.scale_and_translate(get_scale(), get_scale_offset(), transformation_type); + // QDS //wxGetApp().obj_manipul()->set_dirty(); break; @@ -1030,8 +1123,8 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_tooltip.clear(); if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || - m_current == Seam || m_current == MmuSegmentation || m_current == Text || m_current == Cut || m_current == MeshBoolean) + if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Svg || + m_current == Seam || m_current == MmuSegmentation || m_current == Text || m_current == Cut || m_current == MeshBoolean || m_current == BrimEars) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown())) // the gizmo got the event and took some action, there is no need to do anything more processed = true; @@ -1056,7 +1149,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) processed = true; } } - else if (evt.RightDown() && selected_object_idx != -1 && (m_current == SlaSupports || m_current == Hollow) + else if (evt.RightDown() && selected_object_idx != -1 && (m_current == SlaSupports || m_current == Hollow || m_current == BrimEars) && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) { // we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object pending_right_up = true; @@ -1070,12 +1163,12 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) processed = true; } else if (evt.Dragging() && m_parent.get_move_volume_id() != -1 - && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation)) + && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == BrimEars)) // don't allow dragging objects with the Sla gizmo on processed = true; //1.9.5 else if (evt.Dragging() && !control_down - && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) + && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut || m_current == BrimEars) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown())) { // the gizmo got the event and took some action, no need to do anything more here m_parent.set_as_dirty(); @@ -1089,12 +1182,16 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), true); } else if (evt.LeftUp() - && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) - && !m_parent.is_mouse_dragging() - && gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down)) { + && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut || m_current == BrimEars) + && gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down) + && !m_parent.is_mouse_dragging()) { // in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither // object moving or selecting is suppressed in that case processed = true; + } else if (evt.LeftUp() && m_current == Svg && m_gizmos[m_current]->get_hover_id() != -1) { + // QDS + // wxGetApp().obj_manipul()->set_dirty(); + processed = true; } else if (evt.LeftUp() && m_current == Flatten && m_gizmos[m_current]->get_hover_id() != -1) { // to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active @@ -1187,7 +1284,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) if (m_current != Undefined) { if ((m_current == Measure || m_current == Assembly) && gizmo_event(SLAGizmoEventType::Escape)) { // do nothing - } else if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) + } else if ((m_current != SlaSupports && m_current != BrimEars) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) reset_all_states(); processed = true; @@ -1223,7 +1320,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) //case WXK_BACK: case WXK_DELETE: { - if ((m_current == Cut || m_current == Measure || m_current == Assembly) && gizmo_event(SLAGizmoEventType::Delete)) + if ((m_current == Cut || m_current == Measure || m_current == Assembly || m_current == BrimEars) && gizmo_event(SLAGizmoEventType::Delete)) processed = true; break; } @@ -1296,7 +1393,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) if (evt.GetEventType() == wxEVT_KEY_UP) { - if (m_current == SlaSupports || m_current == Hollow) + if (m_current == SlaSupports || m_current == Hollow || m_current == BrimEars) { bool is_editing = true; bool is_rectangle_dragging = false; @@ -1305,6 +1402,9 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) GLGizmoSlaSupports* gizmo = dynamic_cast(get_current()); is_editing = gizmo->is_in_editing_mode(); is_rectangle_dragging = gizmo->is_selection_rectangle_dragging(); + } else if (m_current == BrimEars) { + GLGizmoBrimEars* gizmo = dynamic_cast(get_current()); + is_rectangle_dragging = gizmo->is_selection_rectangle_dragging(); } else { GLGizmoHollow* gizmo = dynamic_cast(get_current()); @@ -1347,6 +1447,10 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) // m_parent.set_cursor(GLCanvas3D::Cross); processed = true; } + else if ((m_current == BrimEars) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT))) + { + processed = true; + } else if (m_current == Cut) { // QDS @@ -1372,7 +1476,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) // QDS else if (m_current == MmuSegmentation) { GLGizmoMmuSegmentation* mmu_seg = dynamic_cast(get_current()); - if (mmu_seg != nullptr) { + if (mmu_seg != nullptr && evt.ControlDown() == false) { if (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_NUMPAD9) { keyCode = keyCode- WXK_NUMPAD0+'0'; } @@ -1647,13 +1751,14 @@ void GLGizmosManager::do_render_overlay() const GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); - if (idx == m_current) { + if (idx == m_current// Orca: Show Svg dialog at the same place as emboss gizmo + || (m_current == Svg && idx == Text)) { //QDS: GUI refactor: GLToolbar&&Gizmo adjust //render_input_window uses a different coordination(imgui) //1. no need to scale by camera zoom, set {0,0} at left-up corner for imgui #if QDS_TOOLBAR_ON_TOP //gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top); - gizmo->render_input_window(0.5 * cnv_w + zoomed_top_x * zoom, height, cnv_h); + m_gizmos[m_current]->render_input_window(0.5 * cnv_w + zoomed_top_x * zoom, height, cnv_h); is_render_current = true; #else @@ -1841,11 +1946,14 @@ bool GLGizmosManager::grabber_contains_mouse() const bool GLGizmosManager::is_in_editing_mode(bool error_notification) const { - if (m_current != SlaSupports || ! dynamic_cast(get_current())->is_in_editing_mode()) + if (m_current == SlaSupports && dynamic_cast(get_current())->is_in_editing_mode()) { + return true; + } else if (m_current == BrimEars) { + dynamic_cast(get_current())->save_model(); return false; - - // QDS: remove SLA editing notification - return true; + } else { + return false; + } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 0a7c121..1bc0540 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -77,10 +77,12 @@ public: Seam, // QDS Text, + Svg, MmuSegmentation, Measure, Assembly, Simplify, + BrimEars, SlaSupports, // QDS //FaceRecognition, @@ -161,6 +163,8 @@ public: enum MENU_ICON_NAME { IC_TOOLBAR_RESET = 0, IC_TOOLBAR_RESET_HOVER, + IC_TOOLBAR_RESET_ZERO, + IC_TOOLBAR_RESET_ZERO_HOVER, IC_TOOLBAR_TOOLTIP, IC_TOOLBAR_TOOLTIP_HOVER, IC_TEXT_B, @@ -228,6 +232,7 @@ public: void reset_all_states(); bool is_serializing() const { return m_serializing; } bool open_gizmo(EType type); + bool open_gizmo(unsigned char type); bool check_gizmos_closed_except(EType) const; void set_hover_id(int id); @@ -275,22 +280,26 @@ public: else return nullptr; } - + void update_paint_base_camera_rotate_rad(); Vec3d get_flattening_normal() const; void set_flattening_data(const ModelObject* model_object); void set_sla_support_data(ModelObject* model_object); + void set_brim_data(ModelObject* model_object); void set_painter_gizmo_data(); bool is_gizmo_activable_when_single_full_instance(); bool is_gizmo_click_empty_not_exit(); bool is_show_only_active_plate(); + bool is_ban_move_glvolume(); bool get_gizmo_active_condition(GLGizmosManager::EType type); //1.9.5 - void check_object_located_outside_plate(); + void check_object_located_outside_plate(bool change_plate =true); bool get_object_located_outside_plate() { return m_object_located_outside_plate; } bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); + bool is_paint_gizmo(); + bool is_allow_select_all(); ClippingPlane get_clipping_plane() const; ClippingPlane get_assemble_view_clipping_plane() const; bool wants_reslice_supports_on_undo() const; diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index 6a20419..16e5722 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -18,8 +18,6 @@ #include -#define MAX_NUM 9999.99 -#define MAX_SIZE "9999.99" namespace Slic3r { @@ -54,6 +52,24 @@ GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas) { m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; m_new_unit_string = m_imperial_units ? L("in") : L("mm"); + + const wxString shift = "Shift+"; + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + m_desc_move["part_selection_caption"] = alt + _L("Left mouse button"); + m_desc_move["part_selection"] = _L("Part selection"); + m_desc_move["snap_step_caption"] = shift + _L("Left mouse button"); + m_desc_move["snap_step"] = _L("Fixed step drag"); + + m_desc_rotate["part_selection_caption"] = alt + _L("Left mouse button"); + m_desc_rotate["part_selection"] = _L("Part selection"); + + m_desc_scale["part_selection_caption"] = alt + _L("Left mouse button"); + m_desc_scale["part_selection"] = _L("Part selection"); + m_desc_scale["snap_step_caption"] = shift + _L("Left mouse button"); + m_desc_scale["snap_step"] = _L("Fixed step drag"); + m_desc_scale["single_sided_caption"] = ctrl + _L("Left mouse button"); + m_desc_scale["single_sided"] = _L("Single sided scaling"); } void GizmoObjectManipulation::UpdateAndShow(const bool show) @@ -78,7 +94,8 @@ void GizmoObjectManipulation::update_ui_from_settings() void GizmoObjectManipulation::update_settings_value(const Selection& selection) { m_new_move_label_string = L("Position"); - m_new_rotate_label_string = L("Rotation"); + m_new_rotate_label_string = L("Rotate (relative)"); + m_new_rotation = Vec3d::Zero(); m_new_scale_label_string = L("Scale ratios"); ObjectList* obj_list = wxGetApp().obj_list(); @@ -88,15 +105,16 @@ void GizmoObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_instance_offset(); if (is_world_coordinates()) {//for move and rotate - m_new_rotate_label_string = L("Rotate"); - m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_size = selection.get_scaled_instance_bounding_box().size(); - m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_unscale_size = selection.get_unscaled_instance_bounding_box().size(); + m_new_scale = m_new_size.cwiseQuotient(m_unscale_size) * 100.0; } else {//if (is_local_coordinates()) {//for scale - m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); - m_new_scale = volume->get_instance_scaling_factor() * 100.; + auto tran = selection.get_first_volume()->get_instance_transformation(); + m_new_position = tran.get_matrix().inverse() * cs_center; + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_unscale_size = selection.get_full_unscaled_instance_local_bounding_box().size(); + m_new_scale = m_new_size.cwiseQuotient(m_unscale_size) * 100.0; } m_new_enabled = true; @@ -106,48 +124,45 @@ void GizmoObjectManipulation::update_settings_value(const Selection& selection) else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) { const BoundingBoxf3& box = selection.get_bounding_box(); m_new_position = box.center(); - m_new_rotation = Vec3d::Zero(); m_new_scale = Vec3d(100., 100., 100.); - m_new_size = box.size(); - m_new_rotate_label_string = L("Rotate"); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); m_new_scale_label_string = L("Scale"); m_new_enabled = true; m_new_title_string = L("Object Operations"); - } - else if (selection.is_single_modifier() || selection.is_single_volume()) { + } else if (selection.is_single_volume_or_modifier()) { const GLVolume *volume = selection.get_first_volume(); if (is_world_coordinates()) {//for move and rotate const Geometry::Transformation trafo(volume->world_matrix()); const Vec3d &offset = trafo.get_offset(); m_new_position = offset; - m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); - m_new_scale = volume->get_volume_scaling_factor() * 100.; + m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_unscale_size = selection.get_bounding_box_in_current_reference_system().first.size(); m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); } else if (is_local_coordinates()) {//for scale m_new_position = Vec3d::Zero(); - m_new_rotation = Vec3d::Zero(); m_new_scale = volume->get_volume_scaling_factor() * 100.0; - m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct( - volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size())); + m_unscale_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); } else { m_new_position = volume->get_volume_offset(); - m_new_rotate_label_string = L("Rotate (relative)"); - m_new_rotation = Vec3d::Zero(); m_new_scale_label_string = L("Scale"); m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_unscale_size = selection.get_bounding_box_in_current_reference_system().first.size(); m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); } m_new_enabled = true; m_new_title_string = L("Volume Operations"); - } - else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { + } else if (obj_list->is_connectors_item_selected() || obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { reset_settings_value(); m_new_move_label_string = L("Translate"); - m_new_rotate_label_string = L("Rotate"); m_new_scale_label_string = L("Scale"); - m_new_size = selection.get_bounding_box().size(); + m_unscale_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); m_new_enabled = true; m_new_title_string = L("Group Operations"); + } else if (selection.is_wipe_tower()) { + const BoundingBoxf3 &box = selection.get_bounding_box(); + m_new_position = box.center(); } else { // No selection, reset the cache. @@ -230,8 +245,9 @@ void GizmoObjectManipulation::update_reset_buttons_visibility() { const Selection& selection = m_glcanvas.get_selection(); - if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { + const GLVolume * volume = selection.get_first_volume(); + Vec3d rotation; Vec3d scale; double min_z = 0.; @@ -245,8 +261,9 @@ void GizmoObjectManipulation::update_reset_buttons_visibility() scale = volume->get_volume_scaling_factor(); min_z = get_volume_min_z(volume); } - m_show_clear_rotation = !rotation.isApprox(Vec3d::Zero()); - m_show_clear_scale = !scale.isApprox(Vec3d::Ones(), EPSILON); + m_show_clear_rotation = !rotation.isApprox(m_init_rotation); + m_show_reset_0_rotation = !rotation.isApprox(Vec3d::Zero()); + m_show_clear_scale = (m_cache.scale / 100.0f - Vec3d::Ones()).norm() > 0.001; m_show_drop_to_bed = (std::abs(min_z) > EPSILON); } } @@ -273,9 +290,24 @@ void GizmoObjectManipulation::change_position_value(int axis, double value) position(axis) = value; Selection& selection = m_glcanvas.get_selection(); - selection.start_dragging(); - selection.translate(position - m_cache.position, selection.requires_local_axes()); - wxGetApp().plater()->take_snapshot(_u8L("Set Position"), UndoRedo::SnapshotType::GizmoAction); + selection.setup_cache(); + TransformationType trafo_type; + trafo_type.set_relative(); + switch (m_coordinates_type) { + case ECoordinatesType::Instance: { + trafo_type.set_instance(); + break; + } + case ECoordinatesType::Local: { + trafo_type.set_local(); + break; + } + default: { + break; + } + } + selection.translate(position - m_cache.position, trafo_type); + wxGetApp().plater()->take_snapshot("Set Position", UndoRedo::SnapshotType::GizmoAction); m_glcanvas.do_move(""); m_cache.position = position; @@ -293,19 +325,17 @@ void GizmoObjectManipulation::change_rotation_value(int axis, double value) Selection& selection = m_glcanvas.get_selection(); - TransformationType transformation_type(TransformationType::World_Relative_Joint); - if (selection.is_single_full_instance() || selection.requires_local_axes()) - transformation_type.set_independent(); - if (selection.is_single_full_instance() && !is_world_coordinates()) { - //FIXME Selection::rotate() does not process absoulte rotations correctly: It does not recognize the axis index, which was changed. - // transformation_type.set_absolute(); - transformation_type.set_local(); - } + TransformationType transformation_type; + transformation_type.set_relative(); + if (selection.is_single_full_instance()) + transformation_type.set_independent(); + if (is_local_coordinates()) + transformation_type.set_local(); + if (is_instance_coordinates()) + transformation_type.set_instance(); - selection.start_dragging(); - selection.rotate( - (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), - transformation_type); + selection.setup_cache(); + selection.rotate((M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); wxGetApp().plater()->take_snapshot(_u8L("Set Orientation"), UndoRedo::SnapshotType::GizmoAction); m_glcanvas.do_rotate(""); @@ -318,18 +348,20 @@ void GizmoObjectManipulation::change_scale_value(int axis, double value) { if (value <= 0.0) return; - if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON) + if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON) { + m_show_clear_scale = (m_cache.scale / 100.0f - Vec3d::Ones()).norm() > 0.001; return; - - Vec3d scale = m_cache.scale; - if (scale[axis] != 0 && std::abs(m_cache.size[axis] * value / scale[axis]) > MAX_NUM) { - scale[axis] *= MAX_NUM / m_cache.size[axis]; } - else { - scale(axis) = value; - } - - this->do_scale(axis, scale); + Vec3d scale = m_cache.scale; + scale(axis) = value; + Vec3d ref_scale = m_cache.scale; + const Selection &selection = m_glcanvas.get_selection(); + if (selection.is_single_volume_or_modifier()) { + scale = scale.cwiseQuotient(ref_scale); // scale / ref_scale + ref_scale = Vec3d::Ones(); + } else if (selection.is_single_full_instance()) + ref_scale = 100 * Vec3d::Ones(); + this->do_scale(axis, scale.cwiseQuotient(ref_scale)); m_cache.scale = scale; m_cache.scale_rounded(axis) = DBL_MAX; @@ -350,17 +382,17 @@ void GizmoObjectManipulation::change_size_value(int axis, double value) const Selection& selection = m_glcanvas.get_selection(); Vec3d ref_size = m_cache.size; - if (selection.is_single_volume() || selection.is_single_modifier()) { - Vec3d instance_scale = wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->instances[0]->get_transformation().get_scaling_factor(); - ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); - ref_size = Vec3d(instance_scale[0] * ref_size[0], instance_scale[1] * ref_size[1], instance_scale[2] * ref_size[2]); + if (selection.is_single_volume_or_modifier()) { + size = size.cwiseQuotient(ref_size); + ref_size = Vec3d::Ones(); + } else if (selection.is_single_full_instance()) { + if (is_world_coordinates()) + ref_size = selection.get_full_unscaled_instance_bounding_box().size(); + else + ref_size = selection.get_full_unscaled_instance_local_bounding_box().size(); } - else if (selection.is_single_full_instance()) - ref_size = is_world_coordinates() ? - selection.get_unscaled_instance_bounding_box().size() : - wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); - this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); + this->do_scale(axis, size.cwiseQuotient(ref_size)); m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; @@ -370,25 +402,33 @@ void GizmoObjectManipulation::change_size_value(int axis, double value) void GizmoObjectManipulation::do_scale(int axis, const Vec3d &scale) const { Selection& selection = m_glcanvas.get_selection(); - Vec3d scaling_factor = scale; - TransformationType transformation_type(TransformationType::World_Relative_Joint); - if (selection.is_single_full_instance()) { - transformation_type.set_absolute(); - if (!is_world_coordinates()) - transformation_type.set_local(); - } + TransformationType transformation_type; + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + if (selection.is_single_volume_or_modifier() && !is_local_coordinates()) + transformation_type.set_relative(); - // QDS: when select multiple objects, uniform scale can be deselected - if (m_uniform_scale/* || selection.requires_uniform_scale()*/) - scaling_factor = scale(axis) * Vec3d::Ones(); + Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + limit_scaling_ratio(scaling_factor); selection.start_dragging(); - selection.scale(scaling_factor * 0.01, transformation_type); + selection.scale(scaling_factor, transformation_type); m_glcanvas.do_scale(L("Set Scale")); } -void GizmoObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) + +void GizmoObjectManipulation::limit_scaling_ratio(Vec3d &scaling_factor) const{ + for (size_t i = 0; i < scaling_factor.size(); i++) { // range protect //scaling_factor too big has problem + if (scaling_factor[i] * m_unscale_size[i] > MAX_NUM) { + scaling_factor[i] = MAX_NUM / m_unscale_size[i]; + } + } +} + +void GizmoObjectManipulation::on_change(const std::string &opt_key, int axis, double new_value) { if (!m_cache.is_valid()) return; @@ -406,6 +446,36 @@ void GizmoObjectManipulation::on_change(const std::string& opt_key, int axis, do change_size_value(axis, new_value); } +bool GizmoObjectManipulation::render_combo( + ImGuiWrapper *imgui_wrapper, const std::string &label, const std::vector &lines, size_t &selection_idx, float label_width, float item_width) +{ + ImGui::AlignTextToFramePadding(); + imgui_wrapper->text(label); + ImGui::SameLine(label_width); + ImGui::PushItemWidth(item_width); + + size_t selection_out = selection_idx; + + const char *selected_str = (selection_idx >= 0 && selection_idx < int(lines.size())) ? lines[selection_idx].c_str() : ""; + if (ImGui::QDTBeginCombo(("##" + label).c_str(), selected_str, 0)) { + for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { + ImGui::PushID(int(line_idx)); + if (ImGui::Selectable("", line_idx == selection_idx)) selection_out = line_idx; + + ImGui::SameLine(); + ImGui::Text("%s", lines[line_idx].c_str()); + ImGui::PopID(); + } + + ImGui::EndCombo(); + } + + bool is_changed = selection_idx != selection_out; + selection_idx = selection_out; + + return is_changed; +} + void GizmoObjectManipulation::reset_position_value() { Selection& selection = m_glcanvas.get_selection(); @@ -430,29 +500,43 @@ void GizmoObjectManipulation::reset_position_value() UpdateAndShow(true); } -void GizmoObjectManipulation::reset_rotation_value() +void GizmoObjectManipulation::reset_rotation_value(bool reset_relative) { - Selection& selection = m_glcanvas.get_selection(); - - if (selection.is_single_volume() || selection.is_single_modifier()) { - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - volume->set_volume_rotation(Vec3d::Zero()); - } - else if (selection.is_single_full_instance()) { - for (unsigned int idx : selection.get_volume_idxs()) { - GLVolume* volume = const_cast(selection.get_volume(idx)); - volume->set_instance_rotation(Vec3d::Zero()); + Selection &selection = m_glcanvas.get_selection(); + selection.setup_cache(); + if (selection.is_single_volume_or_modifier()) { + GLVolume * vol = const_cast(selection.get_first_volume()); + Geometry::Transformation trafo = vol->get_volume_transformation(); + if (reset_relative) { + auto offset = trafo.get_offset(); + trafo.set_from_transform(m_init_rotation_scale_tran); + trafo.set_offset(offset); } - } - else + else { + trafo.reset_rotation(); + } + vol->set_volume_transformation(trafo); + } else if (selection.is_single_full_instance()) { + Geometry::Transformation trafo = selection.get_first_volume()->get_instance_transformation(); + if (reset_relative) { + auto offset = trafo.get_offset(); + trafo.set_from_transform(m_init_rotation_scale_tran); + trafo.set_offset(offset); + } else { + trafo.reset_rotation(); + } + for (unsigned int idx : selection.get_volume_idxs()) { + const_cast(selection.get_volume(idx))->set_instance_transformation(trafo); + } + } else return; + // Synchronize instances/volumes. - // Update rotation at the GLVolumes. - selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_instances(Selection::SyncRotationType::RESET); selection.synchronize_unselected_volumes(); + // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - wxGetApp().plater()->take_snapshot(_u8L("Reset Rotation"), UndoRedo::SnapshotType::GizmoAction); - m_glcanvas.do_rotate(""); + m_glcanvas.do_rotate(L("Reset Rotation")); UpdateAndShow(true); } @@ -478,8 +562,8 @@ void GizmoObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) void GizmoObjectManipulation::set_coordinates_type(ECoordinatesType type) { - if (wxGetApp().get_mode() == comSimple) - type = ECoordinatesType::World; + /*if (wxGetApp().get_mode() == comSimple) + type = ECoordinatesType::World;*/ if (m_coordinates_type == type) return; @@ -520,6 +604,23 @@ bool GizmoObjectManipulation::reset_button(ImGuiWrapper *imgui_wrapper, float ca return pressed; } +bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size) +{ + bool pressed = false; + ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET_ZERO); + ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_RESET_ZERO_HOVER); + + float font_size = ImGui::GetFontSize() * 1.1; + ImVec2 button_size = ImVec2(font_size, font_size); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + + pressed = ImGui::ImageButton3(normal_id, hover_id, button_size); + + ImGui::PopStyleVar(1); + return pressed; +} + float GizmoObjectManipulation::max_unit_size(int number, Vec3d &vec1, Vec3d &vec2,std::string str) { if (number <= 1) return -1; @@ -543,7 +644,112 @@ bool GizmoObjectManipulation::reset_button(ImGuiWrapper *imgui_wrapper, float ca return unit_size + 8.0; } -void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit) + bool GizmoObjectManipulation::qdt_checkbox(const wxString &label, bool &value) +{ + bool result; + bool b_value = value; + if (b_value) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f)); + } + auto label_utf8 = into_u8(label); + result = ImGui::QDTCheckbox(label_utf8.c_str(), &value); + + if (b_value) { ImGui::PopStyleColor(3); } + return result; +} + +void GizmoObjectManipulation::show_move_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y) +{ + ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += imgui_wrapper->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &imgui_wrapper,& caption_max](const wxString &caption, const wxString &text) { + imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto &t : std::array{"part_selection", "snap_step"}) + draw_text_with_caption(m_desc_move.at(t + "_caption") + ": ", m_desc_move.at(t)); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +void GizmoObjectManipulation::show_rotate_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y) +{ + ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += imgui_wrapper->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &imgui_wrapper, &caption_max](const wxString &caption, const wxString &text) { + imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto &t : std::array{"part_selection"}) + draw_text_with_caption(m_desc_rotate.at(t + "_caption") + ": ", m_desc_rotate.at(t)); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +void GizmoObjectManipulation::show_scale_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y) +{ + ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += imgui_wrapper->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &imgui_wrapper, &caption_max](const wxString &caption, const wxString &text) { + imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto &t : std::array{"part_selection", "snap_step", "single_sided"}) + draw_text_with_caption(m_desc_scale.at(t + "_caption") + ": ", m_desc_scale.at(t)); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +void GizmoObjectManipulation::set_init_rotation(const Geometry::Transformation &value) { + m_init_rotation_scale_tran = value.get_matrix_no_offset(); + m_init_rotation = value.get_rotation(); +} + + void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit) { // QDS: GUI refactor: move gizmo to the right if (abs(last_move_input_window_width) > 0.01f) { @@ -581,8 +787,7 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, float space_size = imgui_wrapper->get_style_scaling() * 8; float position_size = imgui_wrapper->calc_text_size(_L("Position")).x + space_size; - float World_size = imgui_wrapper->calc_text_size(_L("World coordinates")).x + space_size; - float caption_max = std::max(position_size, World_size) + 2 * space_size; + float caption_max = imgui_wrapper->calc_text_size(_L("Object coordinates")).x + 2 * space_size; float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x; // position @@ -601,8 +806,28 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, ImGui::AlignTextToFramePadding(); unsigned int current_active_id = ImGui::GetActiveID(); - ImGui::PushItemWidth(caption_max); - imgui_wrapper->text(_L("World coordinates")); + + Selection & selection = m_glcanvas.get_selection(); + std::vector modes = {_u8L("World coordinates"), _u8L("Object coordinates")};//_u8L("Part coordinates") + if (selection.is_multiple_full_object() || selection.is_wipe_tower()) { + modes.pop_back(); + } + size_t selection_idx = (int) m_coordinates_type; + if (selection_idx >= modes.size()) { + set_coordinates_type(ECoordinatesType::World); + selection_idx = 0; + } + + float caption_cs_size = imgui_wrapper->calc_text_size("").x; + float caption_size = caption_cs_size + 2 * space_size; + float combox_content_size = imgui_wrapper->calc_text_size(_L("Object coordinates")).x * 1.2 + imgui_wrapper->calc_text_size("xxx").x + imgui_wrapper->scaled(3); + ImGuiWrapper::push_combo_style(m_glcanvas.get_scale()); + bool combox_changed = false; + if (render_combo(imgui_wrapper, "", modes, selection_idx, caption_size, combox_content_size)) { + combox_changed = true; + } + ImGuiWrapper::pop_combo_style(); + caption_max = combox_content_size - 4 * space_size; ImGui::SameLine(caption_max + index * space_size); ImGui::PushItemWidth(unit_size); ImGui::TextAlignCenter("X"); @@ -629,14 +854,22 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size); imgui_wrapper->text(this->m_new_unit_string); - for (int i = 0;i MAX_NUM)display_position[i] = MAX_NUM; - if (display_position[i] < -MAX_NUM)display_position[i] = -MAX_NUM; + bool is_avoid_one_update{false}; + if (combox_changed) { + combox_changed = false; + set_coordinates_type((ECoordinatesType) selection_idx); + UpdateAndShow(true); + is_avoid_one_update = true; // avoid update(current_active_id, "position", original_position } - m_buffered_position = display_position; - update(current_active_id, "position", original_position, m_buffered_position); + if (!is_avoid_one_update) { + for (int i = 0; i < display_position.size(); i++) { + if (display_position[i] > MAX_NUM) display_position[i] = MAX_NUM; + if (display_position[i] < -MAX_NUM) display_position[i] = -MAX_NUM; + } + m_buffered_position = display_position; + update(current_active_id, "position", original_position, m_buffered_position); + } // the init position values are not zero, won't add reset button // send focus to m_glcanvas @@ -650,7 +883,14 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, } } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + float tip_caption_max = 0.f; + float total_text_max = 0.f; + for (const auto &t : std::array{"part_selection", "snap_step"}) { + tip_caption_max = std::max(tip_caption_max, imgui_wrapper->calc_text_size(m_desc_move[t + "_caption"]).x); + total_text_max = std::max(total_text_max, imgui_wrapper->calc_text_size(m_desc_move[t]).x); + } + show_move_tooltip_information(imgui_wrapper, tip_caption_max, x, get_cur_y); m_last_active_item = current_active_id; last_move_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); @@ -695,7 +935,7 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe }; float space_size = imgui_wrapper->get_style_scaling() * 8; - float position_size = imgui_wrapper->calc_text_size(_L("Rotation")).x + space_size; + float position_size = imgui_wrapper->calc_text_size(_L("Rotate (relative)")).x + space_size; float World_size = imgui_wrapper->calc_text_size(_L("World coordinates")).x + space_size; float caption_max = std::max(position_size, World_size) + 2 * space_size; float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x; @@ -727,13 +967,20 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe ImGui::SameLine(caption_max + (++index_unit) * unit_size + (++index) * space_size); ImGui::PushItemWidth(unit_size); ImGui::TextAlignCenter("Z"); - + if (m_show_reset_0_rotation) { + ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size); + if (reset_zero_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size)) { reset_rotation_value(false); } + if (ImGui::IsItemHovered()) { + float tooltip_size = imgui_wrapper->calc_text_size(_L("Reset current rotation to real zeros.")).x + 3 * space_size; + imgui_wrapper->tooltip(_L("Reset current rotation to real zeros."), tooltip_size); + } + } index = 1; index_unit = 1; // ImGui::PushItemWidth(unit_size * 2); ImGui::AlignTextToFramePadding(); - imgui_wrapper->text(_L("Rotation")); + imgui_wrapper->text(_L("Rotate (relative)")); ImGui::SameLine(caption_max + index * space_size); ImGui::PushItemWidth(unit_size); ImGui::QDTInputDouble(label_values[1][0], &rotation[0], 0.0f, 0.0f, "%.2f"); @@ -750,7 +997,13 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe if (m_show_clear_rotation) { ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size); - if (reset_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size)) { reset_rotation_value(); } + if (reset_button(imgui_wrapper, caption_max, unit_size, space_size, end_text_size)) { + reset_rotation_value(true); + } + if (ImGui::IsItemHovered()) { + float tooltip_size = imgui_wrapper->calc_text_size(_L("Reset current rotation to the value when open the rotation tool.")).x + 3 * space_size; + imgui_wrapper->tooltip(_L("Reset current rotation to the value when open the rotation tool."), tooltip_size); + } } else { ImGui::SameLine(caption_max + 3 * unit_size + 5 * space_size + end_text_size); ImGui::InvisibleButton("", ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize())); @@ -767,7 +1020,14 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe } } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + float tip_caption_max = 0.f; + float total_text_max = 0.f; + for (const auto &t : std::array{"part_selection"}) { + tip_caption_max = std::max(tip_caption_max, imgui_wrapper->calc_text_size(m_desc_move[t + "_caption"]).x); + total_text_max = std::max(total_text_max, imgui_wrapper->calc_text_size(m_desc_move[t]).x); + } + show_rotate_tooltip_information(imgui_wrapper, tip_caption_max, x, get_cur_y); m_last_active_item = current_active_id; last_rotate_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); @@ -818,8 +1078,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w float space_size = imgui_wrapper->get_style_scaling() * 8; float scale_size = imgui_wrapper->calc_text_size(_L("Scale")).x + space_size; - float size_len = imgui_wrapper->calc_text_size(_L("Size")).x + space_size; - float caption_max = std::max(scale_size, size_len) + 2 * space_size; + float caption_max = imgui_wrapper->calc_text_size(_L("Object coordinates")).x + 2 * space_size; float end_text_size = imgui_wrapper->calc_text_size(this->m_new_unit_string).x; ImGui::AlignTextToFramePadding(); unsigned int current_active_id = ImGui::GetActiveID(); @@ -835,10 +1094,30 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w int index = 2; int index_unit = 1; - ImGui::PushItemWidth(caption_max); - ImGui::Dummy(ImVec2(caption_max, -1)); - //imgui_wrapper->text(_L(" ")); - //ImGui::PushItemWidth(unit_size * 1.5); + Selection & selection = m_glcanvas.get_selection(); + std::vector modes = {_u8L("World coordinates"), _u8L("Object coordinates"), _u8L("Part coordinates")}; + if (selection.is_single_full_object()) { modes.pop_back(); } + if (selection.is_multiple_full_object()) { + modes.pop_back(); + modes.pop_back(); + } + size_t selection_idx = (int) m_coordinates_type; + if (selection_idx >= modes.size()) { + set_coordinates_type(ECoordinatesType::World); + selection_idx = 0; + } + + float caption_cs_size = imgui_wrapper->calc_text_size("").x; + float caption_size = caption_cs_size + 2 * space_size; + float combox_content_size = imgui_wrapper->calc_text_size(_L("Object coordinates")).x * 1.2 + imgui_wrapper->calc_text_size("xxx").x + imgui_wrapper->scaled(3); + ImGuiWrapper::push_combo_style(m_glcanvas.get_scale()); + bool combox_changed = false; + if (render_combo(imgui_wrapper, "", modes, selection_idx, caption_size, combox_content_size)) { + combox_changed = true; + } + ImGuiWrapper::pop_combo_style(); + caption_max = combox_content_size - 4 * space_size; + //ImGui::Dummy(ImVec2(caption_max, -1)); ImGui::SameLine(caption_max + space_size); ImGui::PushItemWidth(unit_size); ImGui::TextAlignCenter("X"); @@ -866,7 +1145,9 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w ImGui::QDTInputDouble(label_scale_values[0][2], &scale[2], 0.0f, 0.0f, "%.2f"); ImGui::SameLine(caption_max + (++index_unit) *unit_size + (++index) * space_size); imgui_wrapper->text(_L("%")); - m_buffered_scale = scale; + if (scale.x() > 0 && scale.y() > 0 && scale.z() > 0) { + m_buffered_scale = scale; + } if (m_show_clear_scale) { ImGui::SameLine(caption_max + 3 * unit_size + 4 * space_size + end_text_size); @@ -900,17 +1181,31 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w ImGui::QDTInputDouble(label_scale_values[1][2], &display_size[2], 0.0f, 0.0f, "%.2f"); ImGui::SameLine(caption_max + (++index_unit) *unit_size + (++index) * space_size); imgui_wrapper->text(this->m_new_unit_string); - - for (int i = 0;i MAX_NUM) display_size[i] = MAX_NUM; + for (int i = 0; i < display_size.size(); i++) { + if (std::abs(display_size[i]) > MAX_NUM) { + display_size[i] = MAX_NUM; + } + } + if (display_size.x() > 0 && display_size.y() > 0 && display_size.z() > 0) { + m_buffered_size = display_size; } - m_buffered_size = display_size; - int size_sel = update(current_active_id, "size", original_size, m_buffered_size); - ImGui::PopStyleVar(1); - ImGui::Separator(); + ImGui::AlignTextToFramePadding(); + bool is_avoid_one_update{false}; + if (combox_changed) { + combox_changed = false; + set_coordinates_type((ECoordinatesType) selection_idx); + UpdateAndShow(true); + is_avoid_one_update = true; + } + auto uniform_scale_size =imgui_wrapper->calc_text_size(_L("uniform scale")).x; + ImGui::PushItemWidth(uniform_scale_size); + int size_sel{-1}; + if (!is_avoid_one_update) { + size_sel = update(current_active_id, "size", original_size, m_buffered_size); + } + ImGui::PopStyleVar(1); bool uniform_scale = this->m_uniform_scale; // QDS: when select multiple objects, uniform scale can be deselected @@ -951,9 +1246,6 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w } } - - - //send focus to m_glcanvas bool focued_on_text = false; for (int i = 0; i < 2; i++) @@ -969,7 +1261,14 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + float tip_caption_max = 0.f; + float total_text_max = 0.f; + for (const auto &t : std::array{"part_selection", "snap_step", "single_sided"}) { + tip_caption_max = std::max(tip_caption_max, imgui_wrapper->calc_text_size(m_desc_scale[t + "_caption"]).x); + total_text_max = std::max(total_text_max, imgui_wrapper->calc_text_size(m_desc_scale[t]).x); + } + show_scale_tooltip_information(imgui_wrapper, tip_caption_max, x, get_cur_y); m_last_active_item = current_active_id; last_scale_input_window_width = ImGui::GetWindowWidth(); diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp index 603b10e..7aa2be1 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp @@ -53,7 +53,7 @@ public: Cache m_cache; bool m_imperial_units { false }; - + bool m_use_object_cs{false}; // Mirroring buttons and their current state //enum MirrorButtonState { // mbHidden, @@ -74,15 +74,18 @@ public: Vec3d m_new_rotation; Vec3d m_new_scale; Vec3d m_new_size; + Vec3d m_unscale_size; Vec3d m_buffered_position; Vec3d m_buffered_rotation; Vec3d m_buffered_scale; Vec3d m_buffered_size; + Vec3d cs_center; bool m_new_enabled {true}; bool m_uniform_scale {true}; // Does the object manipulation panel work in World or Local coordinates? ECoordinatesType m_coordinates_type{ECoordinatesType::World}; + bool m_show_reset_0_rotation{false}; bool m_show_clear_rotation { false }; bool m_show_clear_scale { false }; bool m_show_drop_to_bed { false }; @@ -106,6 +109,8 @@ public: void set_uniform_scaling(const bool uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } + void set_use_object_cs(bool flag){ if (m_use_object_cs != flag) m_use_object_cs = flag; } + bool get_use_object_cs() { return m_use_object_cs; } // Does the object manipulation panel work in World or Local coordinates? void set_coordinates_type(ECoordinatesType type); ECoordinatesType get_coordinates_type() const { return m_coordinates_type; } @@ -113,15 +118,26 @@ public: bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; } bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; } + const Cache& get_cache() {return m_cache; } void reset_cache() { m_cache.reset(); } + void limit_scaling_ratio(Vec3d &scaling_factor) const; void on_change(const std::string& opt_key, int axis, double new_value); + bool render_combo(ImGuiWrapper *imgui_wrapper, const std::string &label, const std::vector &lines, size_t &selection_idx, float label_width, float item_width); void do_render_move_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit); void do_render_rotate_window(ImGuiWrapper *imgui_wrapper, std::string window_name, float x, float y, float bottom_limit); void do_render_scale_input_window(ImGuiWrapper* imgui_wrapper, std::string window_name, float x, float y, float bottom_limit); float max_unit_size(int number, Vec3d &vec1, Vec3d &vec2,std::string str); bool reset_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size); + bool reset_zero_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size); + bool qdt_checkbox(const wxString &label, bool &value); + + void show_move_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y); + void show_rotate_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y); + void show_scale_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y); + void set_init_rotation(const Geometry::Transformation &value); + private: void reset_settings_value(); void update_settings_value(const Selection& selection); @@ -132,18 +148,23 @@ private: //Show or hide mirror buttons //void update_mirror_buttons_visibility(); - // change values + // change values void change_position_value(int axis, double value); void change_rotation_value(int axis, double value); void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; void reset_position_value(); - void reset_rotation_value(); + void reset_rotation_value(bool reset_relative); void reset_scale_value(); GLCanvas3D& m_glcanvas; unsigned int m_last_active_item { 0 }; + std::map m_desc_move; + std::map m_desc_rotate; + std::map m_desc_scale; + Vec3d m_init_rotation; + Transform3d m_init_rotation_scale_tran; }; }} diff --git a/src/slic3r/GUI/HMS.cpp b/src/slic3r/GUI/HMS.cpp index 6b6f0ea..92fee47 100644 --- a/src/slic3r/GUI/HMS.cpp +++ b/src/slic3r/GUI/HMS.cpp @@ -247,14 +247,15 @@ wxString HMSQuery::_query_hms_msg(std::string long_error_code, std::string lang_ return wxEmptyString; } -wxString HMSQuery::_query_error_msg(std::string error_code, std::string lang_code) +bool HMSQuery::_query_error_msg(wxString &error_msg, std::string error_code, std::string lang_code) { if (m_hms_info_json.contains("device_error")) { if (m_hms_info_json["device_error"].contains(lang_code)) { for (auto item = m_hms_info_json["device_error"][lang_code].begin(); item != m_hms_info_json["device_error"][lang_code].end(); item++) { if (item->contains("ecode") && boost::to_upper_copy((*item)["ecode"].get()) == error_code) { if (item->contains("intro")) { - return wxString::FromUTF8((*item)["intro"].get()); + error_msg = wxString::FromUTF8((*item)["intro"].get()); + return true; } } } @@ -267,7 +268,8 @@ wxString HMSQuery::_query_error_msg(std::string error_code, std::string lang_cod for (auto item = lang.begin(); item != lang.end(); item++) { if (item->contains("ecode") && boost::to_upper_copy((*item)["ecode"].get()) == error_code) { if (item->contains("intro")) { - return wxString::FromUTF8((*item)["intro"].get()); + error_msg = wxString::FromUTF8((*item)["intro"].get()); + return true; } } } @@ -277,9 +279,11 @@ wxString HMSQuery::_query_error_msg(std::string error_code, std::string lang_cod } else { BOOST_LOG_TRIVIAL(info) << "device_error is not exists"; - return wxEmptyString; + error_msg = wxEmptyString; + return false; } - return wxEmptyString; + error_msg = wxEmptyString; + return false; } wxString HMSQuery::_query_error_url_action(std::string long_error_code, std::string dev_id, std::vector& button_action) @@ -309,12 +313,12 @@ wxString HMSQuery::_query_error_url_action(std::string long_error_code, std::str } -wxString HMSQuery::query_print_error_msg(int print_error) +bool HMSQuery::query_print_error_msg(int print_error, wxString &error_msg) { char buf[32]; ::sprintf(buf, "%08X", print_error); std::string lang_code = HMSQuery::hms_language_code(); - return _query_error_msg(std::string(buf), lang_code); + return _query_error_msg(error_msg, std::string(buf), lang_code); } wxString HMSQuery::query_print_error_url_action(int print_error, std::string dev_id, std::vector& button_action) diff --git a/src/slic3r/GUI/HMS.hpp b/src/slic3r/GUI/HMS.hpp index 01df19e..f2455f3 100644 --- a/src/slic3r/GUI/HMS.hpp +++ b/src/slic3r/GUI/HMS.hpp @@ -28,13 +28,13 @@ protected: int save_to_local(std::string lang, std::string hms_type,json save_json); std::string get_hms_file(std::string hms_type, std::string lang = std::string("en")); wxString _query_hms_msg(std::string long_error_code, std::string lang_code = std::string("en")); - wxString _query_error_msg(std::string long_error_code, std::string lang_code = std::string("en")); + bool _query_error_msg(wxString &error_msg, std::string long_error_code, std::string lang_code = std::string("en")); wxString _query_error_url_action(std::string long_error_code, std::string dev_id, std::vector& button_action); public: HMSQuery() {} int check_hms_info(); wxString query_hms_msg(std::string long_error_code); - wxString query_print_error_msg(int print_error); + bool query_print_error_msg(int print_error, wxString &error_msg); wxString query_print_error_url_action(int print_error, std::string dev_id, std::vector& button_action); static std::string hms_language_code(); static std::string build_query_params(std::string& lang); diff --git a/src/slic3r/GUI/IMSlider.cpp b/src/slic3r/GUI/IMSlider.cpp index 4d447bc..d7c7e36 100644 --- a/src/slic3r/GUI/IMSlider.cpp +++ b/src/slic3r/GUI/IMSlider.cpp @@ -1223,7 +1223,7 @@ void IMSlider::render_go_to_layer_dialog() | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; - if (ImGui::BeginPopupModal((_u8L("Jump to layer")).c_str(), NULL, windows_flag)) + if (ImGui::BeginPopupModal((_u8L("Jump to Layer")).c_str(), NULL, windows_flag)) { imgui.text(_u8L("Please enter the layer number") + " (" + std::to_string(m_min_value + 1) + " - " + std::to_string(m_max_value + 1) + "):"); if (ImGui::IsMouseClicked(0)) { diff --git a/src/slic3r/GUI/IconManager.cpp b/src/slic3r/GUI/IconManager.cpp new file mode 100644 index 0000000..15d242d --- /dev/null +++ b/src/slic3r/GUI/IconManager.cpp @@ -0,0 +1,413 @@ +#include "IconManager.hpp" +#include +#include +#include +#include +#include +#include "nanosvg/nanosvg.h" +#include "nanosvg/nanosvgrast.h" +#include "libslic3r/Utils.hpp" // ScopeGuard + +#include "3DScene.hpp" // glsafe +#include "GL/glew.h" + +#define STB_RECT_PACK_IMPLEMENTATION +#include "imgui/imstb_rectpack.h" // distribute rectangles + +using namespace Slic3r::GUI; + +namespace priv { +// set shared pointer to point on bad texture +static void clear(IconManager::Icons &icons); +static const std::vector>& get_states(IconManager::RasterType type); +static void draw_transparent_icon(const IconManager::Icon &icon); // only help function +} + +IconManager::~IconManager() { + priv::clear(m_icons); + // release opengl texture is made in ~GLTexture() + + if (m_id != 0) + glsafe(::glDeleteTextures(1, &m_id)); +} + +namespace { +NSVGimage *parse_file(const char * filepath) { + FILE *fp = boost::nowide::fopen(filepath, "rb"); + assert(fp != nullptr); + if (fp == nullptr) + return nullptr; + + Slic3r::ScopeGuard sg([fp]() { fclose(fp); }); + + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + // Note: +1 is for null termination + auto data_ptr = std::make_unique(size+1); + data_ptr[size] = '\0'; // Must be null terminated. + + size_t readed_size = fread(data_ptr.get(), 1, size, fp); + assert(readed_size == size); + if (readed_size != size) + return nullptr; + + return nsvgParse(data_ptr.get(), "px", 96.0f); +} + +void subdata(unsigned char *data, size_t data_stride, const std::vector &data2, size_t data2_row) { + assert(data_stride >= data2_row); + for (size_t data2_offset = 0, data_offset = 0; + data2_offset < data2.size(); + data2_offset += data2_row, data_offset += data_stride) + ::memcpy((void *)(data + data_offset), (const void *)(data2.data() + data2_offset), data2_row); +} +} + +IconManager::Icons IconManager::init(const InitTypes &input) +{ + assert(!input.empty()); + if (input.empty()) + return {}; + + // TODO: remove in future + if (m_id != 0) { + glsafe(::glDeleteTextures(1, &m_id)); + m_id = 0; + } + + int total_surface = 0; + for (const InitType &i : input) + total_surface += i.size.x * i.size.y; + const int surface_sqrt = (int)sqrt((float)total_surface) + 1; + + // Start packing + // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). + const int TEX_HEIGHT_MAX = 1024 * 32; + int width = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; + + int num_nodes = width; + std::vector nodes(num_nodes); + stbrp_context context; + stbrp_init_target(&context, width, TEX_HEIGHT_MAX, nodes.data(), num_nodes); + + ImVector pack_rects; + pack_rects.resize(input.size()); + memset(pack_rects.Data, 0, (size_t) pack_rects.size_in_bytes()); + for (size_t i = 0; i < input.size(); i++) { + const ImVec2 &size = input[i].size; + assert(size.x > 1); + assert(size.y > 1); + pack_rects[i].w = size.x; + pack_rects[i].h = size.y; + } + int pack_rects_res = stbrp_pack_rects(&context, &pack_rects[0], pack_rects.Size); + assert(pack_rects_res == 1); + if (pack_rects_res != 1) + return {}; + + ImVec2 tex_size(width, width); + for (const stbrp_rect &rect : pack_rects) { + float x = rect.x + rect.w; + float y = rect.y + rect.h; + if(x > tex_size.x) tex_size.x = x; + if(y > tex_size.y) tex_size.y = y; + } + + Icons result(input.size()); + for (int i = 0; i < pack_rects.Size; i++) { + const stbrp_rect &rect = pack_rects[i]; + assert(rect.was_packed); + if (!rect.was_packed) + return {}; + + ImVec2 tl(rect.x / tex_size.x, rect.y / tex_size.y); + ImVec2 br((rect.x + rect.w) / tex_size.x, (rect.y + rect.h) / tex_size.y); + + assert(input[i].size.x == rect.w); + assert(input[i].size.y == rect.h); + Icon icon = {input[i].size, tl, br}; + result[i] = std::make_shared(std::move(icon)); + } + + NSVGrasterizer *rast = nsvgCreateRasterizer(); + assert(rast != nullptr); + if (rast == nullptr) + return {}; + ScopeGuard sg_rast([rast]() { ::nsvgDeleteRasterizer(rast); }); + + int channels = 4; + int n_pixels = tex_size.x * tex_size.y; + // store data for whole texture + std::vector data(n_pixels * channels, {0}); + + // initialize original index locations + std::vector idx(input.size()); + std::iota(idx.begin(), idx.end(), 0); + + // Group same filename by sort inputs + // sort indexes based on comparing values in input + std::sort(idx.begin(), idx.end(), [&input](size_t i1, size_t i2) { return input[i1].filepath < input[i2].filepath; }); + for (size_t j: idx) { + const InitType &i = input[j]; + if (i.filepath.empty()) + continue; // no file path only reservation of space for texture + assert(boost::filesystem::exists(i.filepath)); + if (!boost::filesystem::exists(i.filepath)) + continue; + assert(boost::algorithm::iends_with(i.filepath, ".svg")); + if (!boost::algorithm::iends_with(i.filepath, ".svg")) + continue; + + NSVGimage *image = parse_file(i.filepath.c_str()); + assert(image != nullptr); + if (image == nullptr) + return {}; + + ScopeGuard sg_image([image]() { ::nsvgDelete(image); }); + + float svg_scale = i.size.y / image->height; + // scale should be same in both directions + assert(is_approx(svg_scale, i.size.y / image->width)); + + const stbrp_rect &rect = pack_rects[j]; + int n_pixels = rect.w * rect.h; + std::vector icon_data(n_pixels * channels, {0}); + ::nsvgRasterize(rast, image, 0, 0, svg_scale, icon_data.data(), i.size.x, i.size.y, i.size.x * channels); + + // makes white or gray only data in icon + if (i.type == RasterType::white_only_data || + i.type == RasterType::gray_only_data) { + unsigned char value = (i.type == RasterType::white_only_data) ? 255 : 127; + for (size_t k = 0; k < icon_data.size(); k += channels) + if (icon_data[k] != 0 || icon_data[k + 1] != 0 || icon_data[k + 2] != 0) { + icon_data[k] = value; + icon_data[k + 1] = value; + icon_data[k + 2] = value; + } + } + + int start_offset = (rect.y*tex_size.x + rect.x) * channels; + int data_stride = tex_size.x * channels; + subdata(data.data() + start_offset, data_stride, icon_data, rect.w * channels); + } + + if (m_id != 0) + glsafe(::glDeleteTextures(1, &m_id)); + + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glGenTextures(1, &m_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint) m_id)); + 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(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) tex_size.x, (GLsizei) tex_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*) data.data())); + + // bind no texture + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + for (const auto &i : result) + i->tex_id = m_id; + return result; +} + +std::vector IconManager::init(const std::vector &file_paths, const ImVec2 &size, RasterType type) +{ + assert(!file_paths.empty()); + assert(size.x >= 1); + assert(size.x < 256*16); + + // TODO: remove in future + if (!m_icons.empty()) { + // not first initialization + priv::clear(m_icons); + m_icons.clear(); + m_icons_texture.reset(); + } + + // only rectangle are supported + assert(size.x == size.y); + // no subpixel supported + unsigned int width = static_cast(std::abs(std::round(size.x))); + assert(size.x == static_cast(width)); + + // state order has to match the enum IconState + const auto& states = priv::get_states(type); + + bool compress = false; + bool is_loaded = m_icons_texture.load_from_svg_files_as_sprites_array(file_paths, states, width, compress); + if (!is_loaded || (size_t) m_icons_texture.get_width() < (states.size() * width) || + (size_t) m_icons_texture.get_height() < (file_paths.size() * width)) { + // bad load of icons, but all usage of m_icons_texture check that texture is initialized + assert(false); + m_icons_texture.reset(); + return {}; + } + + unsigned count_files = file_paths.size(); + // count icons per file + unsigned count = states.size(); + // create result + std::vector result; + result.reserve(count_files); + + Icon def_icon; + def_icon.tex_id = m_icons_texture.get_id(); + def_icon.size = size; + + // float beacouse of dividing + float tex_height = static_cast(m_icons_texture.get_height()); + float tex_width = static_cast(m_icons_texture.get_width()); + + //for (const auto &f: file_paths) { + for (unsigned f = 0; f < count_files; ++f) { + // NOTE: there are space between icons + unsigned start_y = static_cast(f) * (width + 1) + 1; + float y1 = start_y / tex_height; + float y2 = (start_y + width) / tex_height; + Icons file_icons; + file_icons.reserve(count); + //for (const auto &s : states) { + for (unsigned j = 0; j < count; ++j) { + auto icon = std::make_shared(def_icon); + // NOTE: there are space between icons + unsigned start_x = static_cast(j) * (width + 1) + 1; + float x1 = start_x / tex_width; + float x2 = (start_x + width) / tex_width; + icon->tl = ImVec2(x1, y1); + icon->br = ImVec2(x2, y2); + file_icons.push_back(icon); + m_icons.push_back(std::move(icon)); + } + result.emplace_back(std::move(file_icons)); + } + return result; +} + +void IconManager::release() { + BOOST_LOG_TRIVIAL(error) << "Not implemented yet"; +} + +void priv::clear(IconManager::Icons &icons) { + std::string message; + for (auto &icon : icons) { + // Exist more than this instance of shared ptr? + long count = icon.use_count(); + if (count != 1) { + // in existing icon change texture to non existing one + icon->tex_id = 0; + + std::string descr = + ((count > 2) ? (std::to_string(count - 1) + "x") : "") + // count + std::to_string(icon->size.x) + "x" + std::to_string(icon->size.y); // resolution + if (message.empty()) + message = descr; + else + message += ", " + descr; + } + } + + if (!message.empty()) + BOOST_LOG_TRIVIAL(warning) << "There is still used icons(" << message << ")."; +} + +const std::vector> &priv::get_states(IconManager::RasterType type) { + static std::vector> color = {std::make_pair(0, false)}; + static std::vector> white = {std::make_pair(1, false)}; + static std::vector> gray = {std::make_pair(2, false)}; + static std::vector> color_wite_gray = { + std::make_pair(1, false), // Activable + std::make_pair(0, false), // Hovered + std::make_pair(2, false) // Disabled + }; + + switch (type) { + case IconManager::RasterType::color: return color; + case IconManager::RasterType::white_only_data: return white; + case IconManager::RasterType::gray_only_data: return gray; + case IconManager::RasterType::color_wite_gray: return color_wite_gray; + default: return color; + } +} + +void priv::draw_transparent_icon(const IconManager::Icon &icon) +{ + // Check input + if (!icon.is_valid()) { + assert(false); + BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon."; + ImGui::Text("?"); + return; + } + + // size UV texture coors [in texture ratio] + ImVec2 size_uv(icon.br.x - icon.tl.x, icon.br.y - icon.tl.y); + ImVec2 one_px(size_uv.x / icon.size.x, size_uv.y / icon.size.y); + + // use top left corner of first icon + IconManager::Icon icon_px = icon; // copy + // reduce uv coors to one pixel + icon_px.tl = ImVec2(0, 0); + icon_px.br = one_px; + draw(icon_px); +} + +#include "imgui/imgui_internal.h" //ImGuiWindow +namespace Slic3r::GUI { + +void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_col, const ImVec4 &border_col) +{ + // Check input + if (!icon.is_valid()) { + assert(false); + BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon."; + ImGui::Text("?"); + return; + } + ImTextureID id = (void *)static_cast(icon.tex_id); + const ImVec2 &s = (size.x < 1 || size.y < 1) ? icon.size : size; + + // Orca: Align icon center vertically + ImGuiWindow *window = ImGui::GetCurrentWindow(); + ImGuiContext &g = *GImGui; + float cursor_y = window->DC.CursorPos.y; + float line_height = ImGui::GetTextLineHeight() + g.Style.FramePadding.y * 2; + int offset_y = (line_height - s.y) / 2; // Make sure its int otherwise it will be pixelated + window->DC.CursorPos.y += offset_y; + + ImGui::Image(id, s, icon.tl, icon.br, tint_col, border_col); + + // Reset offset + window->DC.CursorPosPrevLine.y = cursor_y; +} + +bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover) +{ + // check of hover + ImGuiWindow *window = ImGui::GetCurrentWindow(); + float cursor_x = ImGui::GetCursorPosX() + - window->DC.GroupOffset.x + - window->DC.ColumnsOffset.x; + priv::draw_transparent_icon(icon); + ImGui::SameLine(cursor_x); + if (ImGui::IsItemHovered()) { + // redraw image + draw(icon_hover); + } else { + // redraw normal image + draw(icon); + } + return ImGui::IsItemClicked(); +} + +bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled) +{ + if (disabled) { + draw(disable); + return false; + } + return clickable(activ, hover); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/IconManager.hpp b/src/slic3r/GUI/IconManager.hpp new file mode 100644 index 0000000..0b30a84 --- /dev/null +++ b/src/slic3r/GUI/IconManager.hpp @@ -0,0 +1,134 @@ +#ifndef slic3r_IconManager_hpp_ +#define slic3r_IconManager_hpp_ + +#include +#include +#include "imgui/imgui.h" // ImVec2 +#include "slic3r/GUI/GLTexture.hpp" // texture storage + +namespace Slic3r::GUI { + +/// +/// Keep texture with icons for UI +/// Manage texture live -> create and destruct texture +/// by live of icon shared pointers. +/// +class IconManager +{ +public: + /// + /// Release texture + /// Set shared pointers to invalid texture + /// + ~IconManager(); + + /// + /// Define way to convert svg data to raster + /// + enum class RasterType: int{ + color = 1 << 1, + white_only_data = 1 << 2, + gray_only_data = 1 << 3, + color_wite_gray = color | white_only_data | gray_only_data + // TODO: add type with backgrounds + }; + + struct InitType { + // path to file with image .. svg + std::string filepath; + + // resolution of stored rasterized icon + ImVec2 size; // float will be rounded + + // could contain more than one type + RasterType type = RasterType::color; + // together color, white and gray = color | white_only_data | gray_only_data + }; + using InitTypes = std::vector; + + /// + /// Data for render texture with icon + /// + struct Icon { + // stored texture size + ImVec2 size = ImVec2(-1, -1); // [in px] --> unsigned int values stored as float + + // SubTexture UV coordinate in range from 0. to 1. + ImVec2 tl; // top left -> uv0 + ImVec2 br; // bottom right -> uv1 + + // OpenGL texture id + unsigned int tex_id = 0; + bool is_valid() const { return tex_id != 0;} + // && size.x > 0 && size.y > 0 && tl.x != br.x && tl.y != br.y; + }; + using Icons = std::vector >; + // Vector of icons, each vector contain multiple use of a SVG render + using VIcons = std::vector; + + /// + /// Initialize raster texture on GPU with given images + /// NOTE: Have to be called after OpenGL initialization + /// + /// Define files and its size with rasterization + /// Rasterized icons stored on GPU, + /// Same size and order as input, each item of vector is set of texture in order by RasterType + Icons init(const InitTypes &input); + + /// + /// Initialize multiple icons with same settings for size and type + /// NOTE: Have to be called after OpenGL initialization + /// + /// Define files with icon + /// Size of stored texture[in px], float will be rounded + /// Define way to rasterize icon, + /// together color, white and gray = RasterType::color | RasterType::white_only_data | RasterType::gray_only_data + /// Rasterized icons stored on GPU, + /// Same size and order as file_paths, each item of vector is set of texture in order by RasterType + VIcons init(const std::vector &file_paths, const ImVec2 &size, RasterType type = RasterType::color); + + /// + /// Release icons which are hold only by this manager + /// May change texture and position of icons. + /// + void release(); + +private: + // keep data stored on GPU + GLTexture m_icons_texture; + + unsigned int m_id{ 0 }; + Icons m_icons; +}; + +/// +/// Draw imgui image with icon +/// +/// Place in texture +/// [optional]Size of image, wen zero than use same size as stored texture +/// viz ImGui::Image +/// viz ImGui::Image +void draw(const IconManager::Icon &icon, + const ImVec2 &size = ImVec2(0, 0), + const ImVec4 &tint_col = ImVec4(1, 1, 1, 1), + const ImVec4 &border_col = ImVec4(0, 0, 0, 0)); + +/// +/// Draw icon which change on hover +/// +/// Draw when no hover +/// Draw when hover +/// True when click, otherwise False +bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover); + +/// +/// Use icon as button with 3 states activ hover and disabled +/// +/// Not disabled not hovered image +/// Hovered image +/// Disabled image +/// True when click on enabled, otherwise False +bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled = false); + +} // namespace Slic3r::GUI +#endif // slic3r_IconManager_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 1b02976..4d80737 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -519,7 +519,7 @@ void ImGuiWrapper::render() m_new_frame_open = false; } -ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) const +ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) { auto text_utf8 = into_u8(text); ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width); @@ -532,6 +532,15 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) cons return size; } +float ImGuiWrapper::find_widest_text(std::vector &text_list) +{ + float width = .0f; + for(const wxString &text : text_list) { + width = std::max(width, this->calc_text_size(text).x); + } + return width; +} + ImVec2 ImGuiWrapper::calc_button_size(const wxString &text, const ImVec2 &button_size) const { const ImVec2 text_size = this->calc_text_size(text); @@ -982,13 +991,13 @@ void ImGuiWrapper::text(const char *label) void ImGuiWrapper::text(const std::string &label) { - this->text(label.c_str()); + ImGuiWrapper::text(label.c_str()); } void ImGuiWrapper::text(const wxString &label) { auto label_utf8 = into_u8(label); - this->text(label_utf8.c_str()); + ImGuiWrapper::text(label_utf8.c_str()); } void ImGuiWrapper::warning_text(const char *label) @@ -1564,9 +1573,9 @@ bool begin_menu(const char *label, bool enabled) return menu_is_open; } -void end_menu() -{ - ImGui::EndMenu(); +void end_menu() +{ + ImGui::EndMenu(); } bool menu_item_with_icon(const char *label, const char *shortcut, ImVec2 icon_size /* = ImVec2(0, 0)*/, ImU32 icon_color /* = 0*/, bool selected /* = false*/, bool enabled /* = true*/, bool* hovered/* = nullptr*/) @@ -1903,6 +1912,162 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } +template +static bool input_optional(std::optional &v, Func &f, std::function is_default, const T &def_val) +{ + if (v.has_value()) { + if (f(*v)) { + if (is_default(*v)) v.reset(); + return true; + } + } else { + T val = def_val; + if (f(val)) { + if (!is_default(val)) v = val; + return true; + } + } + return false; +} + +bool ImGuiWrapper::input_optional_int(const char *label, std::optional &v, int step, int step_fast, ImGuiInputTextFlags flags, int def_val) +{ + auto func = [&](int &value) { return ImGui::InputInt(label, &value, step, step_fast, flags); }; + std::function is_default = [def_val](const int &value) -> bool { return value == def_val; }; + return input_optional(v, func, is_default, def_val); +} + +bool ImGuiWrapper::input_optional_float(const char *label, std::optional &v, float step, float step_fast, const char *format, ImGuiInputTextFlags flags, float def_val) +{ + auto func = [&](float &value) { return ImGui::InputFloat(label, &value, step, step_fast, format, flags); }; + std::function is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) <= std::numeric_limits::epsilon(); }; + return input_optional(v, func, is_default, def_val); +} + +bool ImGuiWrapper::drag_optional_float(const char *label, std::optional &v, float v_speed, float v_min, float v_max, const char *format, float power, float def_val) +{ + auto func = [&](float &value) { return ImGui::DragFloat(label, &value, v_speed, v_min, v_max, format, power); }; + std::function is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) <= std::numeric_limits::epsilon(); }; + return input_optional(v, func, is_default, def_val); +} + +bool ImGuiWrapper::slider_optional_float( + const char *label, std::optional &v, float v_min, float v_max, const char *format, float power, bool clamp, const wxString &tooltip, bool show_edit_btn, float def_val) +{ + auto func = [&](float &value) { return slider_float(label, &value, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); }; + std::function is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) <= std::numeric_limits::epsilon(); }; + return input_optional(v, func, is_default, def_val); +} + +bool ImGuiWrapper::slider_optional_int( + const char *label, std::optional &v, int v_min, int v_max, const char *format, float power, bool clamp, const wxString &tooltip, bool show_edit_btn, int def_val) +{ + std::optional val; + if (v.has_value()) val = static_cast(*v); + auto func = [&](float &value) { return slider_float(label, &value, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); }; + std::function is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) < 0.9f; }; + + float default_value = static_cast(def_val); + if (input_optional(val, func, is_default, default_value)) { + if (val.has_value()) + v = static_cast(std::round(*val)); + else + v.reset(); + return true; + } else + return false; +} + +std::optional ImGuiWrapper::change_window_position(const char *window_name, bool try_to_fix) +{ + ImGuiWindow *window = ImGui::FindWindowByName(window_name); + // is window just created + if (window == NULL) return {}; + + // position of window on screen + ImVec2 position = window->Pos; + ImVec2 size = window->SizeFull; + + // screen size + ImVec2 screen = ImGui::GetMainViewport()->Size; + + std::optional output_window_offset; + if (position.x < 0) { + if (position.y < 0) + // top left + output_window_offset = ImVec2(0, 0); + else + // only left + output_window_offset = ImVec2(0, position.y); + } else if (position.y < 0) { + // only top + output_window_offset = ImVec2(position.x, 0); + } else if (screen.x < (position.x + size.x)) { + if (screen.y < (position.y + size.y)) + // right bottom + output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y); + else + // only right + output_window_offset = ImVec2(screen.x - size.x, position.y); + } else if (screen.y < (position.y + size.y)) { + // only bottom + output_window_offset = ImVec2(position.x, screen.y - size.y); + } + + if (!try_to_fix && output_window_offset.has_value()) output_window_offset = ImVec2(-1, -1); // Put on default position + + return output_window_offset; +} + +void ImGuiWrapper::left_inputs() { ImGui::ClearActiveID(); } + +std::string ImGuiWrapper::trunc(const std::string &text, float width, const char *tail) +{ + float text_width = ImGui::CalcTextSize(text.c_str()).x; + if (text_width < width) return text; + float tail_width = ImGui::CalcTextSize(tail).x; + assert(width > tail_width); + if (width <= tail_width) return "Error: Can't add tail and not be under wanted width."; + float allowed_width = width - tail_width; + + // guess approx count of letter + wxString temp{"n"}; + float average_letter_width = calc_text_size(temp).x; // average letter width + unsigned count_letter = static_cast(allowed_width / average_letter_width); + + //std::string_view text_ = text; + //std::string_view result_text = text_.substr(0, count_letter); + wxString result_text(text.substr(0, count_letter)); + text_width = calc_text_size(result_text).x; + if (text_width < allowed_width) { + // increase letter count + while (count_letter < text.length()) { + ++count_letter; + wxString act_text(text.substr(0, count_letter)); + text_width = calc_text_size(act_text).x; + if (text_width > allowed_width) break; + result_text = act_text; + } + } else { + // decrease letter count + while (count_letter > 1) { + --count_letter; + result_text = text.substr(0, count_letter); + text_width = calc_text_size(result_text).x; + if (text_width < allowed_width) break; + } + } + return result_text .ToStdString()+ tail; +} + +void ImGuiWrapper::escape_double_hash(std::string &text) +{ // add space between hashes + const std::string search = "##"; + const std::string replace = "# #"; + size_t pos = 0; + while ((pos = text.find(search, pos)) != std::string::npos) text.replace(pos, search.length(), replace); +} + void ImGuiWrapper::disable_background_fadeout_animation() { GImGui->DimBgRatio = 1.0f; @@ -2197,6 +2362,19 @@ void ImGuiWrapper::pop_combo_style() ImGui::PopStyleColor(7); } +void ImGuiWrapper::push_radio_style() +{ + if (m_is_dark_mode) { + ImGui::PushStyleColor(ImGuiCol_CheckMark, to_ImVec4(decode_color_to_float_array("#00675b"))); // ORCA use orca color for radio buttons + } else { + ImGui::PushStyleColor(ImGuiCol_CheckMark, to_ImVec4(decode_color_to_float_array("#009688"))); // ORCA use orca color for radio buttons + } +} + +void ImGuiWrapper::pop_radio_style() { + ImGui::PopStyleColor(1); +} + void ImGuiWrapper::init_font(bool compress) { destroy_font(); @@ -2223,7 +2401,7 @@ void ImGuiWrapper::init_font(bool compress) //FIXME replace with io.Fonts->AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, m_font_size, nullptr, ranges.Data); //https://github.com/ocornut/imgui/issues/220 if (m_is_korean) - default_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "NotoSansKR-Regular.ttf").c_str(), m_font_size, &cfg, ranges.Data); + default_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "NanumGothic-Regular.ttf").c_str(), m_font_size, &cfg, ranges.Data); else default_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "HarmonyOS_Sans_SC_Regular.ttf").c_str(), m_font_size, &cfg, ranges.Data); if (default_font == nullptr) { @@ -2234,7 +2412,7 @@ void ImGuiWrapper::init_font(bool compress) } if (m_is_korean) - bold_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "NotoSansKR-Bold.ttf").c_str(), m_font_size, &cfg, ranges.Data); + bold_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "NanumGothic-Bold.ttf").c_str(), m_font_size, &cfg, ranges.Data); else bold_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "HarmonyOS_Sans_SC_Bold.ttf").c_str(), m_font_size, &cfg, ranges.Data); if (bold_font == nullptr) { diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 70006e6..7afed83 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -90,9 +90,9 @@ public: float scaled(float x) const { return x * m_font_size; } ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } - ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f) const; + static ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f); ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const; - + float find_widest_text(std::vector &text_list); ImVec2 get_item_spacing() const; float get_slider_float_height() const; const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; } @@ -140,9 +140,9 @@ public: bool qdt_checkbox(const wxString &label, bool &value); bool qdt_radio_button(const char *label, bool active); bool qdt_sliderin(const char *label, int *v, int v_min, int v_max, const char *format = "%d", ImGuiSliderFlags flags = 0); - void text(const char *label); - void text(const std::string &label); - void text(const wxString &label); + static void text(const char *label); + static void text(const std::string &label); + static void text(const wxString &label); void warning_text(const char *all_text); void warning_text(const wxString &all_text); void text_colored(const ImVec4& color, const char* label); @@ -195,6 +195,71 @@ public: bool want_text_input() const; bool want_any_input() const; + // Optional inputs are used for set up value inside of an optional, with default value + // + // Extended function ImGui::InputInt to work with std::optional, when value == def_val optional is released. + static bool input_optional_int(const char *label, std::optional &v, int step = 1, int step_fast = 100, ImGuiInputTextFlags flags = 0, int def_val = 0); + // Extended function ImGui::InputFloat to work with std::optional value near def_val cause release of optional + static bool input_optional_float( + const char *label, std::optional &v, float step = 0.0f, float step_fast = 0.0f, const char *format = "%.3f", ImGuiInputTextFlags flags = 0, float def_val = .0f); + // Extended function ImGui::DragFloat to work with std::optional value near def_val cause release of optional + static bool drag_optional_float(const char *label, std::optional &v, float v_speed, float v_min, float v_max, const char *format, float power, float def_val = .0f); + // Extended function ImGuiWrapper::slider_float to work with std::optional value near def_val cause release of optional + bool slider_optional_float(const char * label, + std::optional &v, + float v_min, + float v_max, + const char * format = "%.3f", + float power = 1.0f, + bool clamp = true, + const wxString & tooltip = {}, + bool show_edit_btn = true, + float def_val = .0f); + // Extended function ImGuiWrapper::slider_float to work with std::optional, when value == def_val than optional release its value + bool slider_optional_int(const char * label, + std::optional &v, + int v_min, + int v_max, + const char * format = "%.3f", + float power = 1.0f, + bool clamp = true, + const wxString & tooltip = {}, + bool show_edit_btn = true, + int def_val = 0); + + /// + /// Change position of imgui window + /// + /// ImGui identifier of window + /// [output] optional + /// When True Only move to be full visible otherwise reset position + /// New offset of window for function ImGui::SetNextWindowPos + static std::optional change_window_position(const char *window_name, bool try_to_fix); + + /// + /// Use ImGui internals to unactivate (lose focus) in input. + /// When input is activ it can't change value by application. + /// + static void left_inputs(); + + /// + /// Truncate text by ImGui draw function to specific width + /// NOTE 1: ImGui must be initialized + /// NOTE 2: Calculation for actual acive imgui font + /// + /// Text to be truncated + /// Maximal width before truncate + /// String puted on end of text to be visible truncation + /// Truncated text + static std::string trunc(const std::string &text, float width, const char *tail = " .."); + + /// + /// Escape ## in data by add space between hashes + /// Needed when user written text is visualized by ImGui. + /// + /// In/Out text to be escaped + static void escape_double_hash(std::string &text); + #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT bool requires_extra_frame() const { return m_requires_extra_frame; } void set_requires_extra_frame() { m_requires_extra_frame = true; } @@ -241,6 +306,8 @@ public: static void pop_button_disable_style(); static void push_combo_style(const float scale); static void pop_combo_style(); + static void push_radio_style(); + static void pop_radio_style(); //QDS static int TOOLBAR_WINDOW_FLAGS; diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index 76f63d4..0a8ebbc 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -236,10 +236,10 @@ namespace instance_check_internal dbus_uint32_t serial = 0; const char* sigval = message_text.c_str(); //std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck"; - std::string interface_name = "com.qidilab.QIDIStudio.InstanceCheck.Object" + version; + std::string interface_name = "com.qiditech.QIDIStudio.InstanceCheck.Object" + version; std::string method_name = "AnotherInstance"; //std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck"; - std::string object_name = "/com/QIDILab/QIDIStudio/InstanceCheck/Object" + version; + std::string object_name = "/com/QIDITech/QIDIStudio/InstanceCheck/Object" + version; // initialise the error value @@ -538,7 +538,7 @@ namespace MessageHandlerDBusInternal " " " " " " - " " + " " " " " " " " @@ -576,7 +576,7 @@ namespace MessageHandlerDBusInternal { const char* interface_name = dbus_message_get_interface(message); const char* member_name = dbus_message_get_member(message); - std::string our_interface = "com.QIDILab.QIDIStudio.InstanceCheck.Object" + wxGetApp().get_instance_hash_string(); + std::string our_interface = "com.QIDITech.QIDIStudio.InstanceCheck.Object" + wxGetApp().get_instance_hash_string(); BOOST_LOG_TRIVIAL(trace) << "DBus message received: interface: " << interface_name << ", member: " << member_name; if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) { respond_to_introspect(connection, message); @@ -596,8 +596,8 @@ void OtherInstanceMessageHandler::listen() int name_req_val; DBusObjectPathVTable vtable; std::string instance_hash = wxGetApp().get_instance_hash_string(); - std::string interface_name = "com.QIDILab.QIDIStudio.InstanceCheck.Object" + instance_hash; - std::string object_name = "/com/QIDILab/QIDIStudio/InstanceCheck/Object" + instance_hash; + std::string interface_name = "com.QIDITech.QIDIStudio.InstanceCheck.Object" + instance_hash; + std::string object_name = "/com/QIDITech/QIDIStudio/InstanceCheck/Object" + instance_hash; //BOOST_LOG_TRIVIAL(debug) << "init dbus listen " << interface_name << " " << object_name; dbus_error_init(&err); diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index fcd797a..cd828cd 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -1,9 +1,6 @@ #include "ArrangeJob.hpp" -#include "libslic3r/BuildVolume.hpp" #include "libslic3r/SVG.hpp" -#include "libslic3r/MTUtils.hpp" -#include "libslic3r/PresetBundle.hpp" #include "libslic3r/ModelArrange.hpp" #include "slic3r/GUI/PartPlate.hpp" @@ -12,11 +9,11 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/format.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" #include "libnest2d/common.hpp" #define SAVE_ARRANGE_POLY 0 +#define ARRANGE_LOG(level) BOOST_LOG_TRIVIAL(level) << "arrange: " namespace Slic3r { namespace GUI { using ArrangePolygon = arrangement::ArrangePolygon; @@ -57,7 +54,7 @@ public: ret.is_wipe_tower = true; ++ret.priority; - BOOST_LOG_TRIVIAL(debug) << " arrange: wipe tower info:" << m_bb << ", m_pos: " << m_pos.transpose(); + ARRANGE_LOG(debug) << " wipe tower info:" << m_bb << ", m_pos: " << m_pos.transpose(); return ret; } @@ -158,7 +155,7 @@ void ArrangeJob::prepare_selected() { m_locked.emplace_back(std::move(ap)); if (inst_sel[i]) selected_is_locked = true; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%, name %3%") % oidx % i % mo->name; + ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%, name %3%") % oidx % i % mo->name; } } } @@ -226,14 +223,12 @@ void ArrangeJob::prepare_all() { ap.itemid = m_locked.size(); m_locked.emplace_back(std::move(ap)); selected_is_locked = true; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % i; + ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % i; } } } - // If the selection was empty arrange everything - //if (m_selected.empty()) m_selected.swap(m_unselected); if (m_selected.empty()) { if (!selected_is_locked) { m_plater->get_notification_manager()->push_notification(NotificationType::QDTPlateInfo, @@ -244,6 +239,13 @@ void ArrangeJob::prepare_all() { NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("All the selected objects are on the locked plate,\nWe can not do auto-arrange on these objects."))); } } + if (!m_uncompatible_plates.empty()) { + auto msg = _L("The following plates are skipped due to different arranging settings from global:"); + for (int i : m_uncompatible_plates) { msg += "\n"+_L("Plate") + " " + std::to_string(i + 1); + } + m_plater->get_notification_manager()->push_notification(NotificationType::QDTPlateInfo, + NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(msg)); + } prepare_wipe_tower(); @@ -298,7 +300,7 @@ void ArrangeJob::prepare_wipe_tower() obj_extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end()); if (obj_extruders.size() > 1) { need_wipe_tower = true; - BOOST_LOG_TRIVIAL(info) << "arrange: need wipe tower because object " << item.name << " has multiple extruders (has paint-on colors)"; + ARRANGE_LOG(info) << "need wipe tower because object " << item.name << " has multiple extruders (has paint-on colors)"; break; } } @@ -312,12 +314,12 @@ void ArrangeJob::prepare_wipe_tower() for (const auto& be : bedTemp2extruderIds) { if (be.second.size() > 1) { need_wipe_tower = true; - BOOST_LOG_TRIVIAL(info) << "arrange: need wipe tower because allow_multi_materials_on_same_plate=true and we have multiple extruders of same type"; + ARRANGE_LOG(info) << "need wipe tower because allow_multi_materials_on_same_plate=true and we have multiple extruders of same type"; break; } } } - BOOST_LOG_TRIVIAL(info) << "arrange: need_wipe_tower=" << need_wipe_tower; + ARRANGE_LOG(info) << "need_wipe_tower=" << need_wipe_tower; ArrangePolygon wipe_tower_ap; @@ -372,7 +374,7 @@ void ArrangeJob::prepare_partplate() { if (plate->empty()) { //no instances on this plate - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": no instances in current plate!"); + ARRANGE_LOG(info) << __FUNCTION__ << boost::format(": no instances in current plate!"); return; } @@ -423,6 +425,75 @@ void ArrangeJob::prepare_partplate() { plate_list.preprocess_exclude_areas(m_unselected, current_plate_index + 1); } +void ArrangeJob::prepare_outside_plate() { + clear_input(); + + std::set> all_inside_objects; + std::set> all_outside_objects; + + Model &model = m_plater->model(); + PartPlateList &plate_list = m_plater->get_partplate_list(); + //collect all the objects outside + for (int plate_idx = 0; plate_idx < plate_list.get_plate_count(); plate_idx++) { + PartPlate *plate = plate_list.get_plate(plate_idx); + assert(plate != nullptr); + std::set>& plate_objects = plate->get_obj_and_inst_set(); + std::set>& plate_outside_objects = plate->get_obj_and_inst_outside_set(); + if (plate_objects.empty()) { + // no instances on this plate + ARRANGE_LOG(info) << __FUNCTION__ << format(": no instances in plate %d!", plate_idx); + continue; + } + + all_inside_objects.insert(plate_objects.begin(), plate_objects.end()); + if (!plate_outside_objects.empty()) + all_outside_objects.insert(plate_outside_objects.begin(), plate_outside_objects.end()); + + if (plate->is_locked()) { + ARRANGE_LOG(info) << __FUNCTION__ << format(": skip locked plate %d!", plate_idx); + continue; + } + + // if there are objects inside the plate, lock the plate and don't put new objects in it + if (plate_objects.size() > plate_outside_objects.size()) { + plate->lock(true); + m_uncompatible_plates.push_back(plate_idx); + ARRANGE_LOG(info) << __FUNCTION__ << format(": lock plate %d because there are objects inside!", plate_idx); + } + } + + for (int obj_idx = 0; obj_idx < model.objects.size(); obj_idx++) { + ModelObject *object = model.objects[obj_idx]; + for (size_t inst_idx = 0; inst_idx < object->instances.size(); ++inst_idx) { + ModelInstance * instance = object->instances[inst_idx]; + std::set>::iterator iter1, iter2; + + iter1 = all_inside_objects.find(std::pair(obj_idx, inst_idx)); + iter2 = all_outside_objects.find(std::pair(obj_idx, inst_idx)); + bool outside_plate = false; + if (iter1 == all_inside_objects.end()) { + //skip + continue; + } + if (iter2 != all_outside_objects.end()) { + outside_plate = true; + } + ArrangePolygon&& ap = prepare_arrange_polygon(instance); + ArrangePolygons &cont = instance->printable ? (outside_plate ? m_selected : m_locked) : m_unprintable; + ap.itemid = cont.size(); + if (!outside_plate) { + plate_list.preprocess_arrange_polygon(obj_idx, inst_idx, ap, true); + } + cont.emplace_back(std::move(ap)); + } + } + + prepare_wipe_tower(); + + // add the virtual object into unselect list if has + plate_list.preprocess_exclude_areas(m_unselected, current_plate_index + 1); +} + //QDS: add partplate logic void ArrangeJob::prepare() { @@ -449,6 +520,9 @@ void ArrangeJob::prepare() else if (state == Job::JobPrepareState::PREPARE_STATE_MENU) { only_on_partplate = true; // only arrange items on current plate prepare_partplate(); + } else if (state == Job::JobPrepareState::PREPARE_STATE_OUTSIDE_BED) { + only_on_partplate = false; + prepare_outside_plate(); } @@ -456,8 +530,7 @@ void ArrangeJob::prepare() if (1) { // subtract excluded region and get a polygon bed auto& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); - auto print_config = print.config(); - bed_poly.points = get_bed_shape(*m_plater->config()); + bed_poly.points = get_bed_shape(config); Pointfs excluse_area_points = print_config.bed_exclude_area.values; Polygons exclude_polys; Polygon exclude_poly; @@ -540,12 +613,11 @@ void ArrangeJob::process() if (params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer")) partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES); - update_arrange_params(params, m_plater->config(), m_selected); - update_selected_items_inflation(m_selected, m_plater->config(), params); - update_unselected_items_inflation(m_unselected, m_plater->config(), params); - update_selected_items_axis_align(m_selected, m_plater->config(), params); + update_arrange_params(params, global_config, m_selected); + update_selected_items_inflation(m_selected, global_config, params); + update_unselected_items_inflation(m_unselected, global_config, params); - Points bedpts = get_shrink_bedpts(m_plater->config(),params); + Points bedpts = get_shrink_bedpts(global_config,params); partplate_list.preprocess_exclude_areas(params.excluded_regions, 1, scale_(1)); @@ -611,15 +683,16 @@ static std::string concat_strings(const std::set &strings, }); } -void ArrangeJob::finalize() { +void ArrangeJob::finalize() +{ + // QDS: partplate + PartPlateList &plate_list = m_plater->get_partplate_list(); // Ignore the arrange result if aborted. if (!was_canceled()) { // Unprintable items go to the last virtual bed int beds = 0; - //QDS: partplate - PartPlateList& plate_list = m_plater->get_partplate_list(); //clear all the relations before apply the arrangement results if (only_on_partplate) { plate_list.clear(false, false, true, current_plate_index); diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 4228c66..4249dcd 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -38,6 +38,9 @@ class ArrangeJob : public PlaterJob //QDS:prepare the items from current selected partplate void prepare_partplate(); + + void prepare_outside_plate(); + void prepare_wipe_tower(); ArrangePolygon prepare_arrange_polygon(void* instance); diff --git a/src/slic3r/GUI/Jobs/BindJob.cpp b/src/slic3r/GUI/Jobs/BindJob.cpp index 9686984..cc2ca89 100644 --- a/src/slic3r/GUI/Jobs/BindJob.cpp +++ b/src/slic3r/GUI/Jobs/BindJob.cpp @@ -116,7 +116,9 @@ void BindJob::process() try { error_code = stoi(result_info); - result_info = wxGetApp().get_hms_query()->query_print_error_msg(error_code).ToStdString(); + wxString error_msg; + wxGetApp().get_hms_query()->query_print_error_msg(error_code, error_msg); + result_info = error_msg.ToStdString(); } catch (...) { ; diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp new file mode 100644 index 0000000..3a22354 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp @@ -0,0 +1,182 @@ +#include + +#include "BoostThreadWorker.hpp" + +namespace Slic3r { namespace GUI { + +void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner) +{ + switch(MsgType(get_type())) { + case Empty: break; + case Status: { + auto info = boost::get(m_data); + if (runner.get_pri()) { + runner.get_pri()->set_progress(info.status); + runner.get_pri()->set_status_text(info.msg.c_str()); + } + break; + } + case Finalize: { + auto& entry = boost::get(m_data); + entry.job->finalize(entry.canceled, entry.eptr); + + // Unhandled exceptions are rethrown without mercy. + if (entry.eptr) + std::rethrow_exception(entry.eptr); + + break; + } + case MainThreadCall: { + auto &calldata = boost::get(m_data); + calldata.fn(); + calldata.promise.set_value(); + + break; + } + } +} + +void BoostThreadWorker::run() +{ + bool stop = false; + while (!stop) { + m_input_queue + .consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) { + if (!e.job) + stop = true; + else { + m_canceled.store(false); + + try { + e.job->process(*this); + } catch (...) { + e.eptr = std::current_exception(); + } + + e.canceled = m_canceled.load(); + m_output_queue.push(std::move(e)); // finalization message + } + m_running.store(false); + }); + }; +} + +void BoostThreadWorker::update_status(int st, const std::string &msg) +{ + m_output_queue.push(st, msg); +} + +std::future BoostThreadWorker::call_on_main_thread(std::function fn) +{ + MainThreadCallData cbdata{std::move(fn), {}}; + std::future future = cbdata.promise.get_future(); + + m_output_queue.push(std::move(cbdata)); + + return future; +} + +BoostThreadWorker::BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes &attribs, + const char * name) + : m_progress(std::move(pri)), m_name{name} +{ + if (m_progress) + m_progress->set_cancel_callback([this](){ cancel(); }); + + m_thread = create_thread(attribs, [this] { this->run(); }); + + std::string nm{name}; + if (!nm.empty()) set_thread_name(m_thread, name); +} + +constexpr int ABORT_WAIT_MAX_MS = 10000; + +BoostThreadWorker::~BoostThreadWorker() +{ + bool joined = false; + try { + cancel_all(); + wait_for_idle(ABORT_WAIT_MAX_MS); + m_input_queue.push(JobEntry{nullptr}); + joined = join(ABORT_WAIT_MAX_MS); + } catch(...) {} + + if (!joined) + BOOST_LOG_TRIVIAL(error) + << "Could not join worker thread '" << m_name << "'"; +} + +bool BoostThreadWorker::join(int timeout_ms) +{ + if (!m_thread.joinable()) + return true; + + if (timeout_ms <= 0) { + m_thread.join(); + } + else if (m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) { + return true; + } + else + return false; + + return true; +} + +void BoostThreadWorker::process_events() +{ + while (m_output_queue.consume_one([this](WorkerMessage &msg) { + msg.deliver(*this); + })); +} + +bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms) +{ + bool ret = true; + + if (!is_idle()) { + bool was_finish = false; + bool timeout_reached = false; + while (!timeout_reached && !was_finish) { + timeout_reached = + !m_output_queue.consume_one(BlockingWait{timeout_ms}, + [this, &was_finish]( + WorkerMessage &msg) { + msg.deliver(*this); + if (msg.get_type() == + WorkerMessage::Finalize) + was_finish = true; + }); + } + + ret = !timeout_reached; + } + + return ret; +} + +bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms) +{ + bool timeout_reached = false; + while (!timeout_reached && !is_idle()) { + timeout_reached = !m_output_queue + .consume_one(BlockingWait{timeout_ms}, + [this](WorkerMessage &msg) { + msg.deliver(*this); + }); + } + + return !timeout_reached; +} + +bool BoostThreadWorker::push(std::unique_ptr job) +{ + if (!job) + return false; + + m_input_queue.push(JobEntry{std::move(job)}); + return true; +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp new file mode 100644 index 0000000..9083668 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp @@ -0,0 +1,155 @@ +#ifndef BOOSTTHREADWORKER_HPP +#define BOOSTTHREADWORKER_HPP + +#include + +#include "Worker.hpp" + +#include +#include + +#include "ThreadSafeQueue.hpp" +#include "slic3r/GUI/GUI.hpp" + +namespace Slic3r { namespace GUI { + +// An implementation of the Worker interface which uses the boost::thread +// API and two thread safe message queues to communicate with the main thread +// back and forth. The queue from the main thread to the worker thread holds the +// job entries that will be performed on the worker. The other queue holds messages +// from the worker to the main thread. These messages include status updates, +// finishing operation and arbitrary functiors that need to be performed +// on the main thread during the jobs execution, like displaying intermediate +// results. +class BoostThreadWorker : public Worker, private JobNew::Ctl +{ + struct JobEntry // Goes into worker and also out of worker as a finalize msg + { + std::unique_ptr job; + bool canceled = false; + std::exception_ptr eptr = nullptr; + }; + + // A message data for status updates. Only goes from worker to main thread. + struct StatusInfo { int status; std::string msg; }; + + // An arbitrary callback to be called on the main thread. Only from worker + // to main thread. + struct MainThreadCallData + { + std::function fn; + std::promise promise; + }; + + struct EmptyMessage {}; + + class WorkerMessage + { + public: + enum MsgType { Empty, Status, Finalize, MainThreadCall }; + + private: + boost::variant m_data; + + public: + WorkerMessage() = default; + WorkerMessage(int s, std::string txt) + : m_data{StatusInfo{s, std::move(txt)}} + {} + WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {} + WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {} + + int get_type () const { return m_data.which(); } + + void deliver(BoostThreadWorker &runner); + }; + + using JobQueue = ThreadSafeQueueSPSC; + using MessageQueue = ThreadSafeQueueSPSC; + + boost::thread m_thread; + std::atomic m_running{false}, m_canceled{false}; + std::shared_ptr m_progress; + JobQueue m_input_queue; // from main thread to worker + MessageQueue m_output_queue; // form worker to main thread + std::string m_name; + + void run(); + + bool join(int timeout_ms = 0); + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override; + + bool was_canceled() const override { return m_canceled.load(); } + + std::future call_on_main_thread(std::function fn) override; + +public: + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes & attr, + const char * name = ""); + + explicit BoostThreadWorker(std::shared_ptr pri, + boost::thread::attributes && attr, + const char * name = "") + : BoostThreadWorker{std::move(pri), attr, name} + {} + + explicit BoostThreadWorker(std::shared_ptr pri, + const char * name = "") + : BoostThreadWorker{std::move(pri), {}, name} + {} + + ~BoostThreadWorker(); + + BoostThreadWorker(const BoostThreadWorker &) = delete; + BoostThreadWorker(BoostThreadWorker &&) = delete; + BoostThreadWorker &operator=(const BoostThreadWorker &) = delete; + BoostThreadWorker &operator=(BoostThreadWorker &&) = delete; + + bool push(std::unique_ptr job) override; + + bool is_idle() const override + { + // The assumption is that jobs can only be queued from a single main + // thread from which this method is also called. And the output + // messages are also processed only in this calling thread. In that + // case, if the input queue is empty, it will remain so during this + // function call. If the worker thread is also not running and the + // output queue is already processed, we can safely say that the + // worker is dormant. + return m_input_queue.empty() && !m_running.load() && m_output_queue.empty(); + } + + void cancel() override { m_canceled.store(true); } + void cancel_all() override { m_input_queue.clear(); cancel(); } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } + + void clear_percent() override + { + if (m_progress) { + m_progress->clear_percent(); + } + } + + void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) override + { + if (m_progress) { + m_progress->show_error_info(from_u8(msg), code, from_u8(description), from_u8(extra)); + } + } + + void process_events() override; + bool wait_for_current_job(unsigned timeout_ms = 0) override; + bool wait_for_idle(unsigned timeout_ms = 0) override; + +}; + +}} // namespace Slic3r::GUI + +#endif // BOOSTTHREADWORKER_HPP diff --git a/src/slic3r/GUI/Jobs/BusyCursorJob.hpp b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp new file mode 100644 index 0000000..77ae456 --- /dev/null +++ b/src/slic3r/GUI/Jobs/BusyCursorJob.hpp @@ -0,0 +1,54 @@ +#ifndef BUSYCURSORJOB_HPP +#define BUSYCURSORJOB_HPP + +#include "JobNew.hpp" + +#include +#include + +namespace Slic3r { namespace GUI { + +struct CursorSetterRAII +{ + JobNew::Ctl &ctl; + CursorSetterRAII(JobNew::Ctl &c) : ctl{c} + { + ctl.call_on_main_thread([] { wxBeginBusyCursor(); }); + } + ~CursorSetterRAII() + { + try { + ctl.call_on_main_thread([] { wxEndBusyCursor(); }); + } catch(...) { + BOOST_LOG_TRIVIAL(error) << "Can't revert cursor from busy to normal"; + } + } +}; + +template +class BusyCursored : public JobNew +{ + JobSubclass m_job; + +public: + template + BusyCursored(Args &&...args) : m_job{std::forward(args)...} + {} + + void process(Ctl &ctl) override + { + CursorSetterRAII cursor_setter{ctl}; + m_job.process(ctl); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + m_job.finalize(canceled, eptr); + } +}; + + +} +} + +#endif // BUSYCURSORJOB_HPP diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp new file mode 100644 index 0000000..75a481c --- /dev/null +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -0,0 +1,1153 @@ +#include "EmbossJob.hpp" +#include +#include +#include +// +#include +#include // load_obj for default mesh +#include // use surface cuts +#include // create object +#include + +#include "libslic3r/libslic3r.h" +#include "slic3r/GUI/Plater.hpp" +////#include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/SurfaceDrag.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +//#include "slic3r/GUI/MainFrame.hpp" +//#include "slic3r/GUI/GUI.hpp" +//#include "slic3r/GUI/GUI_App.hpp" +//#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp" +#include "slic3r/GUI/Selection.hpp" +#include "slic3r/GUI/CameraUtils.hpp" +#include "slic3r/GUI/format.hpp" +//#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/Jobs/Worker.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +// #define EXECUTE_UPDATE_ON_MAIN_THREAD // debug execution on main thread +using namespace Slic3r::Emboss; +namespace Slic3r { +namespace GUI { +namespace Emboss { +// Offset of clossed side to model +const float SAFE_SURFACE_OFFSET = 0.015f; // [in mm] +// create sure that emboss object is bigger than source object [in mm] +constexpr float safe_extension = 1.0f; +void create_message(const std::string &message) { show_error(nullptr, message.c_str()); } +// jobs +class JobException : public std::runtime_error +{ +public: + using std::runtime_error::runtime_error; +}; + +bool was_canceled(const JobNew::Ctl &ctl, const DataBase &base) +{ + if (base.cancel->load()) return true; + return ctl.was_canceled(); +} + +bool _finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input) +{ + // doesn't care about exception when process was canceled by user + if (canceled || input.cancel->load()) { + eptr = nullptr; + return false; + } + return !exception_process(eptr); +} + +bool exception_process(std::exception_ptr &eptr) +{ + if (!eptr) return false; + try { + std::rethrow_exception(eptr); + } catch (JobException &e) { + create_message(e.what()); + eptr = nullptr; + } + return true; +} + +void update_name_in_list(const ObjectList &object_list, const ModelVolume &volume) +{ + const ModelObjectPtrs *objects_ptr = object_list.objects(); + if (objects_ptr == nullptr) return; + + const ModelObjectPtrs &objects = *objects_ptr; + const ModelObject * object = volume.get_object(); + const ObjectID & object_id = object->id(); + + // search for index of object + int object_index = -1; + for (size_t i = 0; i < objects.size(); ++i) + if (objects[i]->id() == object_id) { + object_index = static_cast(i); + break; + } + + const ModelVolumePtrs volumes = object->volumes; + const ObjectID & volume_id = volume.id(); + + // search for index of volume + int volume_index = -1; + for (size_t i = 0; i < volumes.size(); ++i) + if (volumes[i]->id() == volume_id) { + volume_index = static_cast(i); + break; + } + + if (object_index < 0 || volume_index < 0) return; + + object_list.update_name_in_list(object_index, volume_index); +} + +ExPolygons create_shape(DataBase &input) +{ + EmbossShape &es = input.create_shape(); + // TODO: improve to use real size of volume + // ... need world matrix for volume + // ... printer resolution will be fine too + return union_with_delta(es, UNION_DELTA, UNION_MAX_ITERATIN); +} + +void _update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr) +{ + // for sure that some object will be created + if (mesh.its.empty()) return create_message("Empty mesh can't be created."); + + Plater *plater = wxGetApp().plater(); + // Check gizmo is still open otherwise job should be canceled + assert(plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Svg); + + if (data.make_snapshot) { + // TRN: This is the title of the action appearing in undo/redo stack. + // It is same for Text and SVG. + std::string snap_name = _u8L("Emboss attribute change"); + Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction); + } + + ModelVolume *volume = get_model_volume(data.volume_id, plater->model().objects); + + // could appear when user delete edited volume + if (volume == nullptr) return; + + if (tr) { + volume->set_transformation(*tr); + } else { + // apply fix matrix made by store to .3mf + const std::optional &emboss_shape = volume->emboss_shape; + assert(emboss_shape.has_value()); + if (emboss_shape.has_value() && emboss_shape->fix_3mf_tr.has_value()) volume->set_transformation(volume->get_matrix() * emboss_shape->fix_3mf_tr->inverse()); + } + + UpdateJob::update_volume(volume, std::move(mesh), *data.base); +} + +std::vector create_line_bounds(const ExPolygonsWithIds &shapes, size_t count_lines = 0) +{ + if (count_lines == 0) count_lines = get_count_lines(shapes); + assert(count_lines == get_count_lines(shapes)); + + std::vector result(count_lines); + size_t text_line_index = 0; + // s_i .. shape index + for (const ExPolygonsWithId &shape_id : shapes) { + const ExPolygons &shape = shape_id.expoly; + BoundingBox bb; + if (!shape.empty()) { bb = get_extents(shape); } + BoundingBoxes &line_qds = result[text_line_index]; + line_qds.push_back(bb); + if (shape_id.id == ENTER_UNICODE) { + // skip enters on beginig and tail + ++text_line_index; + } + } + return result; +} + +bool is_valid(ModelVolumeType volume_type) +{ + assert(volume_type != ModelVolumeType::INVALID); + assert(volume_type == ModelVolumeType::MODEL_PART || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); + if (volume_type == ModelVolumeType::MODEL_PART || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER) return true; + + BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type; + return false; +} + +bool check(unsigned char gizmo_type) { return gizmo_type == (unsigned char) GLGizmosManager::Svg; } + +bool check(const CreateVolumeParams &input) +{ + bool res = is_valid(input.volume_type); + auto gizmo_type = static_cast(input.gizmo_type); + res &= check(gizmo_type); + return res; +} + +bool check(const DataBase &input, bool check_fontfile, bool use_surface) +{ + bool res = true; + // if (check_fontfile) { + // assert(input.font_file.has_value()); + // res &= input.font_file.has_value(); + // } + // assert(!input.text_configuration.fix_3mf_tr.has_value()); + // res &= !input.text_configuration.fix_3mf_tr.has_value(); + // assert(!input.text_configuration.text.empty()); + // res &= !input.text_configuration.text.empty(); + assert(!input.volume_name.empty()); + res &= !input.volume_name.empty(); + // const FontProp& prop = input.text_configuration.style.prop; + // assert(prop.per_glyph == !input.text_lines.empty()); + // res &= prop.per_glyph == !input.text_lines.empty(); + // if (prop.per_glyph) { + // assert(get_count_lines(input.text_configuration.text) == input.text_lines.size()); + // res &= get_count_lines(input.text_configuration.text) == input.text_lines.size(); + //} + return res; +} + +bool check(const DataCreateObject &input) +{ + bool check_fontfile = false; + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, check_fontfile); + assert(input.screen_coor.x() >= 0.); + res &= input.screen_coor.x() >= 0.; + assert(input.screen_coor.y() >= 0.); + res &= input.screen_coor.y() >= 0.; + assert(input.bed_shape.size() >= 3); // at least triangle + res &= input.bed_shape.size() >= 3; + res &= check(input.gizmo_type); + assert(!input.base->shape.projection.use_surface); + res &= !input.base->shape.projection.use_surface; + return res; +} + +bool check(const DataUpdate &input, bool is_main_thread, bool use_surface) +{ + bool check_fontfile = true; + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, check_fontfile, use_surface); + if (is_main_thread) assert(get_model_volume(input.volume_id, wxGetApp().model().objects) != nullptr); + // assert(input.base->cancel != nullptr); + if (is_main_thread) assert(!input.base->cancel->load()); + assert(!input.base->shape.projection.use_surface); + res &= !input.base->shape.projection.use_surface; + return res; +} + +bool check(const CreateSurfaceVolumeData &input, bool is_main_thread) +{ + bool use_surface = true; + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, is_main_thread, use_surface); + assert(!input.sources.empty()); + res &= !input.sources.empty(); + res &= check(input.gizmo_type); + assert(input.base->shape.projection.use_surface); + res &= input.base->shape.projection.use_surface; + return res; +} + +bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread) +{ + bool use_surface = true; + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, is_main_thread, use_surface); + assert(!input.sources.empty()); + res &= !input.sources.empty(); + assert(input.base->shape.projection.use_surface); + res &= input.base->shape.projection.use_surface; + return res; +} + +bool check(const DataCreateVolume &input, bool is_main_thread) +{ + bool check_fontfile = false; + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, check_fontfile); + res &= is_valid(input.volume_type); + res &= check(input.gizmo_type); + assert(!input.base->shape.projection.use_surface); + res &= !input.base->shape.projection.use_surface; + return res; +} + +void DataBase::write(ModelVolume &volume) const +{ + volume.name = volume_name; + volume.emboss_shape = shape; + volume.emboss_shape->fix_3mf_tr.reset(); +} + +UpdateSurfaceVolumeJob::UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input) : m_input(std::move(input)) { +} + + +void UpdateSurfaceVolumeJob::process(Ctl &ctl) +{ + if (!check(m_input)) + throw JobException("Bad input data for UseSurfaceJob."); + m_result = cut_surface(*m_input.base, m_input); //, was_canceled(ctl, *m_input.base) +} +bool UpdateSurfaceVolumeJob::is_use_surfae_error =false; + +void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) +{ + if (!_finalize(canceled, eptr, *m_input.base)) + return; + // when start using surface it is wanted to move text origin on surface of model + // also when repeteadly move above surface result position should match + _update_volume(std::move(m_result), m_input, &m_input.transform); +} + +UpdateJob::UpdateJob(DataUpdate &&input) : m_input(std::move(input)) {} + +void UpdateJob::process(Ctl &ctl) +{ + if (!check(m_input)) + throw JobException("Bad input data for EmbossUpdateJob."); + + m_result = try_create_mesh(*m_input.base); + if (was_canceled(ctl, *m_input.base)) + return; + if (m_result.its.empty()) + throw JobException("Created text volume is empty. Change text or font."); +} + +void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr) +{ + if (!_finalize(canceled, eptr, *m_input.base)) return; + _update_volume(std::move(m_result), m_input); +} + +void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base) +{ // check inputs + bool is_valid_input = volume != nullptr && !mesh.empty() && !base.volume_name.empty(); + assert(is_valid_input); + if (!is_valid_input) return; + + // update volume + volume->set_mesh(std::move(mesh)); + volume->set_new_unique_id(); + volume->calculate_convex_hull(); + + // write data from base into volume + base.write(*volume); + + GUI_App &app = wxGetApp(); // may be move to input + if (volume->name != base.volume_name) { + volume->name = base.volume_name; + + const ObjectList *obj_list = app.obj_list(); + if (obj_list != nullptr) + update_name_in_list(*obj_list, *volume); + } + + ModelObject *object = volume->get_object(); + if (object == nullptr) + return; + + Plater *plater = app.plater(); + if (plater->printer_technology() == ptSLA) + sla::reproject_points_and_holes(object); + plater->changed_object(*object); +} + +CreateObjectJob::CreateObjectJob(DataCreateObject &&input) : m_input(std::move(input)) {} +void CreateObjectJob::process(Ctl &ctl) +{ + if (!check(m_input)) + throw JobException("Bad input data for EmbossCreateObjectJob."); + + // can't create new object with using surface + if (m_input.base->shape.projection.use_surface) m_input.base->shape.projection.use_surface = false; + + // auto was_canceled = ::was_canceled(ctl, *m_input.base); + m_result = create_mesh(*m_input.base); + + // Create new object + // calculate X,Y offset position for lay on platter in place of + // mouse click + Vec2d bed_coor = CameraUtils::get_z0_position(m_input.camera, m_input.screen_coor); + + // check point is on build plate: + Points bed_shape_; + bed_shape_.reserve(m_input.bed_shape.size()); + for (const Vec2d &p : m_input.bed_shape) bed_shape_.emplace_back(p.cast()); + Slic3r::Polygon bed(bed_shape_); + if (!bed.contains(bed_coor.cast())) + // mouse pose is out of build plate so create object in center of plate + bed_coor = bed.centroid().cast(); + + double z = m_input.base->shape.projection.depth / 2; + Vec3d offset(bed_coor.x(), bed_coor.y(), z); + offset -= m_result.center(); + Transform3d::TranslationType tt(offset.x(), offset.y(), offset.z()); + m_transformation = Transform3d(tt); + + // rotate around Z by style settings + if (m_input.angle.has_value()) { + std::optional distance; // new object ignore surface distance from style settings + apply_transformation(m_input.angle, distance, m_transformation); + } +} + +void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) +{ + if (!_finalize(canceled, eptr, *m_input.base)) return; + // only for sure + if (m_result.empty()) { + create_message("Can't create empty object."); + return; + } + GUI_App &app = wxGetApp(); + Plater * plater = app.plater(); + plater->take_snapshot(_u8L("Add Emboss text object")); + + Model &model = plater->model(); +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + { + // INFO: inspiration for create object is from ObjectList::load_mesh_object() + ModelObject *new_object = model.add_object(); + new_object->name = m_input.base->volume_name; + new_object->add_instance(); // each object should have at list one instance + + ModelVolume *new_volume = new_object->add_volume(std::move(m_result)); + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + // write emboss data into volume + m_input.base->write(*new_volume); + + // set transformation + Slic3r::Geometry::Transformation tr(m_transformation); + new_object->instances.front()->set_transformation(tr); + new_object->ensure_on_bed(); + + // Actualize right panel and set inside of selection + app.obj_list()->paste_objects_into_list({model.objects.size() - 1}); + } +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + // When add new object selection is empty. + // When cursor move and no one object is selected than + // Manager::reset_all() So Gizmo could be closed before end of creation object + GLCanvas3D * canvas = plater->get_view3D_canvas3D(); + GLGizmosManager &manager = canvas->get_gizmos_manager(); + if (manager.get_current_type() != m_input.gizmo_type) // GLGizmosManager::EType::svg + manager.open_gizmo(m_input.gizmo_type); + + // redraw scene + canvas->reload_scene(true); +} + +CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input) : m_input(std::move(input)) {} +void CreateSurfaceVolumeJob::process(Ctl &ctl) +{ + if (!check(m_input)) + throw JobException("Bad input data for CreateSurfaceVolumeJob."); + m_result = cut_surface(*m_input.base, m_input); // was_canceled(ctl, *m_input.base) +} +void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) +{ + if (!_finalize(canceled, eptr, *m_input.base)) + return; + create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.transform, *m_input.base, m_input.gizmo_type); +} + + +CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input) : m_input(std::move(input)) {} + +void CreateVolumeJob::process(Ctl &ctl) +{ + if (!check(m_input)) + throw std::runtime_error("Bad input data for EmbossCreateVolumeJob."); + m_result = create_mesh(*m_input.base); +} + +void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) +{ + if (!_finalize(canceled, eptr, *m_input.base)) + return; + if (m_result.its.empty()) return create_message("Can't create empty volume."); + create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base, m_input.gizmo_type); +} +/// Update Volume +TriangleMesh create_mesh_per_glyph(DataBase &input) +{ + // method use square of coord stored into int64_t + // static_assert(std::is_same()); + const EmbossShape &shape = input.create_shape(); + if (shape.shapes_with_ids.empty()) return {}; + + // Precalculate bounding boxes of glyphs + // Separate lines of text to vector of Bounds + assert(get_count_lines(shape.shapes_with_ids) == input.text_lines.size()); + size_t count_lines = input.text_lines.size(); + std::vector qds = create_line_bounds(shape.shapes_with_ids, count_lines); + + double depth = shape.projection.depth / shape.scale; + auto scale_tr = Eigen::Scaling(shape.scale); + + // half of font em size for direction of letter emboss + // double em_2_mm = prop.size_in_mm / 2.; // TODO: fix it + double em_2_mm = 5.; + coord_t em_2_polygon = static_cast(std::round(scale_(em_2_mm))); + + size_t s_i_offset = 0; // shape index offset(for next lines) + indexed_triangle_set result; + for (size_t text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) { + const BoundingBoxes &line_qds = qds[text_line_index]; + const TextLine & line = input.text_lines[text_line_index]; + PolygonPoints samples = sample_slice(line, line_qds, shape.scale); + std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + + for (size_t i = 0; i < line_qds.size(); ++i) { + const BoundingBox &letter_bb = line_qds[i]; + if (!letter_bb.defined) continue; + + Vec2d to_zero_vec = letter_bb.center().cast() * shape.scale; // [in mm] + float surface_offset = input.is_outside ? -SAFE_SURFACE_OFFSET : (-shape.projection.depth + SAFE_SURFACE_OFFSET); + + if (input.from_surface.has_value()) surface_offset += *input.from_surface; + + Eigen::Translation to_zero(-to_zero_vec.x(), 0., static_cast(surface_offset)); + + const double & angle = angles[i]; + Eigen::AngleAxisd rotate(angle + M_PI_2, Vec3d::UnitY()); + + const PolygonPoint & sample = samples[i]; + Vec2d offset_vec = unscale(sample.point); // [in mm] + Eigen::Translation offset_tr(offset_vec.x(), 0., -offset_vec.y()); + Transform3d tr = offset_tr * rotate * to_zero * scale_tr; + + const ExPolygons &letter_shape = shape.shapes_with_ids[s_i_offset + i].expoly; + assert(get_extents(letter_shape) == letter_bb); + auto projectZ = std::make_unique(depth); + ProjectTransform project(std::move(projectZ), tr); + indexed_triangle_set glyph_its = polygons2model(letter_shape, project); + its_merge(result, std::move(glyph_its)); + + if (((s_i_offset + i) % 15)) return {}; + } + s_i_offset += line_qds.size(); + +#ifdef STORE_SAMPLING + { // Debug store polygon + // std::string stl_filepath = "C:/data/temp/line" + std::to_string(text_line_index) + "_model.stl"; + // bool suc = its_write_stl_ascii(stl_filepath.c_str(), "label", result); + + BoundingBox bbox = get_extents(line.polygon); + std::string file_path = "C:/data/temp/line" + std::to_string(text_line_index) + "_letter_position.svg"; + SVG svg(file_path, bbox); + svg.draw(line.polygon); + int32_t radius = bbox.size().x() / 300; + for (size_t i = 0; i < samples.size(); i++) { + const PolygonPoint &pp = samples[i]; + const Point & p = pp.point; + svg.draw(p, "green", radius); + std::string label = std::string(" ") + tc.text[i]; + svg.draw_text(p, label.c_str(), "black"); + + double a = angles[i]; + double length = 3.0 * radius; + Point n(length * std::cos(a), length * std::sin(a)); + svg.draw(Slic3r::Line(p - n, p + n), "Lime"); + } + } +#endif // STORE_SAMPLING + } + return TriangleMesh(std::move(result)); +} + + + +TriangleMesh try_create_mesh(DataBase &input) +{ + if (!input.text_lines.empty()) { + TriangleMesh tm = create_mesh_per_glyph(input); + if (!tm.empty()) return tm; + } + + ExPolygons shapes = create_shape(input); + if (shapes.empty()) return {}; + + // NOTE: SHAPE_SCALE is applied in ProjectZ + double scale = input.shape.scale; + double depth = input.shape.projection.depth / scale; + auto projectZ = std::make_unique(depth); + float offset = input.is_outside ? -SAFE_SURFACE_OFFSET : (SAFE_SURFACE_OFFSET - input.shape.projection.depth); + if (input.from_surface.has_value()) offset += *input.from_surface; + Transform3d tr = Eigen::Translation(0., 0., static_cast(offset)) * Eigen::Scaling(scale); + ProjectTransform project(std::move(projectZ), tr); + + return TriangleMesh(polygons2model(shapes, project)); +} + +TriangleMesh create_default_mesh() +{ + // When cant load any font use default object loaded from file + std::string path = Slic3r::resources_dir() + "/data/embossed_text.obj"; + TriangleMesh triangle_mesh; + std::string message; + ObjInfo obj_info; + if (!load_obj(path.c_str(), &triangle_mesh, obj_info, message)) { + // when can't load mesh use cube + return TriangleMesh(its_make_cube(36., 4., 2.5)); + } + return triangle_mesh; +} + +TriangleMesh create_mesh(DataBase &input) +{ + // It is neccessary to create some shape + // Emboss text window is opened by creation new emboss text object + TriangleMesh result = try_create_mesh(input); + + if (result.its.empty()) { + result = create_default_mesh(); + create_message("It is used default volume for embossed text, try to change text or font to fix it."); + // only info + /*ctl.call_on_main_thread([]() { + create_message("It is used default volume for embossed text, try to change text or font to fix it."); + });*/ + } + + assert(!result.its.empty()); + return result; +} + +void create_volume( + TriangleMesh &&mesh, const ObjectID &object_id, const ModelVolumeType type, const std::optional &trmat, const DataBase &data, unsigned char gizmo_type) +{ + GUI_App & app = wxGetApp(); + Plater * plater = app.plater(); + ObjectList * obj_list = app.obj_list(); + GLCanvas3D * canvas = plater->get_view3D_canvas3D(); + ModelObjectPtrs &objects = plater->model().objects; + + ModelObject *obj = nullptr; + size_t object_idx = 0; + for (; object_idx < objects.size(); ++object_idx) { + ModelObject *o = objects[object_idx]; + if (o->id() == object_id) { + obj = o; + break; + } + } + + // Parent object for text volume was propably removed. + // Assumption: User know what he does, so text volume is no more needed. + if (obj == nullptr) return create_message("Bad object to create volume."); + + if (mesh.its.empty()) return create_message("Can't create empty volume."); + + plater->take_snapshot(_u8L("Add Emboss text Volume")); + + BoundingBoxf3 instance_bb; + if (!trmat.has_value()) { + // used for align to instance + size_t instance_index = 0; // must exist + instance_bb = obj->instance_bounding_box(instance_index); + } + + // NOTE: be carefull add volume also center mesh !!! + // So first add simple shape(convex hull is also calculated) + ModelVolume *volume = obj->add_volume(make_cube(1., 1., 1.), type); + + // TODO: Refactor to create better way to not set cube at begining + // Revert mesh centering by set mesh after add cube + volume->set_mesh(std::move(mesh)); + volume->calculate_convex_hull(); + + // set a default extruder value, since user can't add it manually + volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + // do not allow model reload from disk + volume->source.is_from_builtin_objects = true; + + volume->name = data.volume_name; // copy + + if (trmat.has_value()) { + volume->set_transformation(*trmat); + } else { + assert(!data.shape.projection.use_surface); + // Create transformation for volume near from object(defined by glVolume) + // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject + Vec3d volume_size = volume->mesh().bounding_box().size(); + // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. + Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created + -instance_bb.size().y() / 2 - volume_size.y() / 2, // under + volume_size.z() / 2 - instance_bb.size().z() / 2); // lay on bed + // use same instance as for calculation of instance_bounding_box + Transform3d tr = obj->instances.front()->get_transformation().get_matrix_no_offset().inverse(); + Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); + volume->set_transformation(volume_trmat); + } + + data.write(*volume); + + // update printable state on canvas + if (type == ModelVolumeType::MODEL_PART) { + volume->get_object()->ensure_on_bed(); + canvas->update_instance_printable_state_for_object(object_idx); + } + + // update volume name in object list + // updata selection after new volume added + // change name of volume in right panel + // select only actual volume + // when new volume is created change selection to this volume + auto add_to_selection = [volume](const ModelVolume *vol) { return vol == volume; }; + wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection); + if (!sel.IsEmpty()) obj_list->select_item(sel.front()); + + obj_list->selection_changed(); + + // Now is valid text volume selected open emboss gizmo + GLGizmosManager &manager = canvas->get_gizmos_manager(); + if (manager.get_current_type() != gizmo_type) manager.open_gizmo(gizmo_type); + + // update model and redraw scene + // canvas->reload_scene(true); + plater->update(); +} + +OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const std::pair &z_range) +{ + double min_z = z_range.first - safe_extension; + double max_z = z_range.second + safe_extension; + assert(min_z < max_z); + // range between min and max value + double projection_size = max_z - min_z; + Matrix3d transformation_for_vector = tr.linear(); + // Projection must be negative value. + // System of text coordinate + // X .. from left to right + // Y .. from bottom to top + // Z .. from text to eye + Vec3d untransformed_direction(0., 0., projection_size); + Vec3d project_direction = transformation_for_vector * untransformed_direction; + + // Projection is in direction from far plane + tr.translate(Vec3d(0., 0., min_z)); + tr.scale(shape_scale); + return OrthoProject(tr, project_direction); +} + +OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut) +{ + float front_move = (is_outside) ? emboss : SAFE_SURFACE_OFFSET, back_move = -((is_outside) ? SAFE_SURFACE_OFFSET : emboss); + its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move))); + Vec3d from_front_to_back(0., 0., back_move - front_move); + return OrthoProject3d(from_front_to_back); +} + +indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input) +{ + assert(!sources.empty()); + BoundingBox bb = get_extents(shapes); + double shape_scale = input.shape.scale; + + const SurfaceVolumeData::ModelSource *biggest = &sources.front(); + + size_t biggest_count = 0; + // convert index from (s)ources to (i)ndexed (t)riangle (s)ets + std::vector s_to_itss(sources.size(), std::numeric_limits::max()); + std::vector itss; + itss.reserve(sources.size()); + for (const SurfaceVolumeData::ModelSource &s : sources) { + Transform3d mesh_tr_inv = s.tr.inverse(); + Transform3d cut_projection_tr = mesh_tr_inv * tr; + std::pair z_range{0., 1.}; + OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range); + // copy only part of source model + indexed_triangle_set its = Slic3r::its_cut_AoI(s.mesh->its, bb, cut_projection); + if (its.indices.empty()) continue; + if (biggest_count < its.vertices.size()) { + biggest_count = its.vertices.size(); + biggest = &s; + } + size_t source_index = &s - &sources.front(); + size_t its_index = itss.size(); + s_to_itss[source_index] = its_index; + itss.emplace_back(std::move(its)); + } + if (itss.empty()) return {}; + + Transform3d tr_inv = biggest->tr.inverse(); + Transform3d cut_projection_tr = tr_inv * tr; + + size_t itss_index = s_to_itss[biggest - &sources.front()]; + BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]); + for (const SurfaceVolumeData::ModelSource &s : sources) { + itss_index = s_to_itss[&s - &sources.front()]; + if (itss_index == std::numeric_limits::max()) continue; + if (&s == biggest) continue; + + Transform3d tr = s.tr * tr_inv; + bool fix_reflected = true; + indexed_triangle_set &its = itss[itss_index]; + its_transform(its, tr, fix_reflected); + BoundingBoxf3 its_bb = bounding_box(its); + mesh_bb.merge(its_bb); + } + + // tr_inv = transformation of mesh inverted + Transform3d emboss_tr = cut_projection_tr.inverse(); + BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); + std::pair z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()}; + OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range); + float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension); + + ExPolygons shapes_data; // is used only when text is reflected to reverse polygon points order + const ExPolygons *shapes_ptr = &shapes; + bool is_text_reflected = Slic3r::has_reflection(tr); + if (is_text_reflected) { + // revert order of points in expolygons + // CW --> CCW + shapes_data = shapes; // copy + for (ExPolygon &shape : shapes_data) { + shape.contour.reverse(); + for (Slic3r::Polygon &hole : shape.holes) hole.reverse(); + } + shapes_ptr = &shapes_data; + } + + // Use CGAL to cut surface from triangle mesh + SurfaceCut cut = Slic3r::cut_surface(*shapes_ptr, itss, cut_projection, projection_ratio); + + if (is_text_reflected) { + for (SurfaceCut::Contour &c : cut.contours) std::reverse(c.begin(), c.end()); + for (Vec3i32 &t : cut.indices) std::swap(t[0], t[1]); + } + + if (cut.empty()) return {}; // There is no valid surface for text projection. + // if (was_canceled()) return {}; + + // !! Projection needs to transform cut + OrthoProject3d projection = create_emboss_projection(input.is_outside, input.shape.projection.depth, emboss_tr, cut); + return cut2model(cut, projection); +} + +TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2) +{ + // Precalculate bounding boxes of glyphs + // Separate lines of text to vector of Bounds + const EmbossShape &es = input1.create_shape(); + // if (was_canceled()) return {}; + if (es.shapes_with_ids.empty()) { + throw JobException(_u8L("Font doesn't have any shape for given text.").c_str()); + } + assert(get_count_lines(es.shapes_with_ids) == input1.text_lines.size()); + size_t count_lines = input1.text_lines.size(); + std::vector qds = create_line_bounds(es.shapes_with_ids, count_lines); + + // half of font em size for direction of letter emboss + double em_2_mm = 5.; // TODO: fix it + int32_t em_2_polygon = static_cast(std::round(scale_(em_2_mm))); + + size_t s_i_offset = 0; // shape index offset(for next lines) + indexed_triangle_set result; + for (size_t text_line_index = 0; text_line_index < input1.text_lines.size(); ++text_line_index) { + const BoundingBoxes &line_qds = qds[text_line_index]; + const TextLine & line = input1.text_lines[text_line_index]; + PolygonPoints samples = sample_slice(line, line_qds, es.scale); + std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + + for (size_t i = 0; i < line_qds.size(); ++i) { + const BoundingBox &glyph_bb = line_qds[i]; + if (!glyph_bb.defined) continue; + + const double &angle = angles[i]; + auto rotate = Eigen::AngleAxisd(angle + M_PI_2, Vec3d::UnitY()); + + const PolygonPoint &sample = samples[i]; + Vec2d offset_vec = unscale(sample.point); // [in mm] + auto offset_tr = Eigen::Translation(offset_vec.x(), 0., -offset_vec.y()); + + ExPolygons glyph_shape = es.shapes_with_ids[s_i_offset + i].expoly; + assert(get_extents(glyph_shape) == glyph_bb); + + Point offset(-glyph_bb.center().x(), 0); + for (ExPolygon &s : glyph_shape) s.translate(offset); + + Transform3d modify = offset_tr * rotate; + Transform3d tr = input2.transform * modify; + indexed_triangle_set glyph_its = cut_surface_to_its(glyph_shape, tr, input2.sources, input1); + // move letter in volume on the right position + its_transform(glyph_its, modify); + + // Improve: union instead of merge + its_merge(result, std::move(glyph_its)); + + if (((s_i_offset + i) % 15)) return {}; + } + s_i_offset += line_qds.size(); + } + + // if (was_canceled()) return {}; + if (result.empty()) { + UpdateSurfaceVolumeJob::is_use_surfae_error = true; + throw JobException(_u8L("There is no valid surface for text projection.").c_str()); + } + return TriangleMesh(std::move(result)); +} + +// input can't be const - cache of font +TriangleMesh cut_surface(DataBase &input1, const SurfaceVolumeData &input2) +{ + if (!input1.text_lines.empty()) + return cut_per_glyph_surface(input1, input2); + + ExPolygons shapes = create_shape(input1); + // if (was_canceled()) return {}; + if (shapes.empty()) { + throw JobException(_u8L("Font doesn't have any shape for given text.").c_str()); + } + indexed_triangle_set its = cut_surface_to_its(shapes, input2.transform, input2.sources, input1); + // if (was_canceled()) return {}; + if (its.empty()) { + UpdateSurfaceVolumeJob::is_use_surfae_error = true; + throw JobException(_u8L("There is no valid surface for text projection.").c_str()); + } + return TriangleMesh(std::move(its)); +} + +bool start_update_volume(DataUpdate &&data, const ModelVolume &volume, const Selection &selection, RaycastManager &raycaster) +{ + assert(data.volume_id == volume.id()); + + // check cutting from source mesh + bool &use_surface = data.base->shape.projection.use_surface; + if (use_surface && volume.is_the_only_one_part()) use_surface = false; + + std::unique_ptr job = nullptr; + if (use_surface) { + // Model to cut surface from. + SurfaceVolumeData::ModelSources sources = create_volume_sources(volume); + if (sources.empty()) return false; + + Transform3d volume_tr = volume.get_matrix(); + const std::optional &fix_3mf = volume.emboss_shape->fix_3mf_tr; + if (fix_3mf.has_value()) volume_tr = volume_tr * fix_3mf->inverse(); + + // when it is new applying of use surface than move origin onto surfaca + if (!volume.emboss_shape->projection.use_surface) { + auto offset = calc_surface_offset(selection, raycaster); + if (offset.has_value()) volume_tr *= Eigen::Translation(*offset); + } + + UpdateSurfaceVolumeData surface_data{std::move(data), {volume_tr, std::move(sources)}}; + job = std::make_unique(std::move(surface_data)); + } else { + job = std::make_unique(std::move(data)); + } +#ifndef EXECUTE_UPDATE_ON_MAIN_THREAD + auto &worker = wxGetApp().plater()->get_ui_job_worker(); + auto is_idle = worker.is_idle(); + return queue_job(worker, std::move(job)); +#else + // Run Job on main thread (blocking) - ONLY DEBUG + return execute_job(std::move(job)); +#endif // EXECUTE_UPDATE_ON_MAIN_THREAD +} + +bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor) +{ + const Pointfs & bed_shape = input.build_volume.printable_area(); + DataCreateObject m_input{std::move(emboss_data), coor, input.camera, bed_shape, input.gizmo_type, input.angle}; + + //// Fix: adding text on print bed with style containing use_surface + if (m_input.base->shape.projection.use_surface) + // // Til the print bed is flat using surface for Object is useless + m_input.base->shape.projection.use_surface = false; + + auto job = std::make_unique(std::move(m_input)); + return queue_job(input.worker, std::move(job)); +} + +const GLVolume *find_closest(const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center) +{ + assert(closest_center != nullptr); + const GLVolume * closest = nullptr; + const Selection::IndicesList &indices = selection.get_volume_idxs(); + assert(!indices.empty()); // no selected volume + if (indices.empty()) return closest; + + double center_sq_distance = std::numeric_limits::max(); + for (unsigned int id : indices) { + const GLVolume *gl_volume = selection.get_volume(id); + if (const ModelVolume *volume = get_model_volume(*gl_volume, objects); volume == nullptr || !volume->is_model_part()) continue; + Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); + Vec2d c = hull.centroid().cast(); + Vec2d d = c - screen_center; + bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); + if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance)) continue; + + double distance = d.squaredNorm(); + if (center_sq_distance < distance) continue; + center_sq_distance = distance; + + *closest_center = c; + closest = gl_volume; + } + return closest; +} + +bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data) +{ + assert(data != nullptr); + if (data == nullptr) return false; + if (!check(input)) return false; + + // select position by camera position and view direction + const Selection &selection = input.canvas.get_selection(); + int object_idx = selection.get_object_idx(); + + Size s = input.canvas.get_canvas_size(); + Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); + const ModelObjectPtrs &objects = selection.get_model()->objects; + + // No selected object so create new object + if (selection.is_empty() || object_idx < 0 || static_cast(object_idx) >= objects.size()) + // create Object on center of screen + // when ray throw center of screen not hit bed it create object on center of bed + return start_create_object_job(input, std::move(data), screen_center); + + // create volume inside of selected object + Vec2d coor; + const Camera &camera = wxGetApp().plater()->get_camera(); + input.gl_volume = find_closest(selection, screen_center, camera, objects, &coor); + if (input.gl_volume == nullptr) + return start_create_object_job(input, std::move(data), screen_center); + else { + return start_create_volume_on_surface_job(input, std::move(data), coor); + } +} + +bool start_create_volume_job( + Worker &worker, const ModelObject &object, const std::optional &volume_tr, DataBasePtr data, ModelVolumeType volume_type, unsigned char gizmo_type) +{ + bool & use_surface = data->shape.projection.use_surface; + std::unique_ptr job; + if (use_surface) { + // Model to cut surface from. + SurfaceVolumeData::ModelSources sources = create_sources(object.volumes); + if (sources.empty() || !volume_tr.has_value()) { + use_surface = false; + } else { + SurfaceVolumeData sfvd{*volume_tr, std::move(sources)}; + CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id(), gizmo_type}; + job = std::make_unique(std::move(surface_data)); + } + } + if (!use_surface) { + // create volume + DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr, gizmo_type}; + job = std::make_unique(std::move(create_volume_data)); + } + return queue_job(worker, std::move(job)); +} + +bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos) +{ + auto on_bad_state = [&input](DataBasePtr data_, const ModelObject *object = nullptr) { + // In centroid of convex hull is not hit with object. e.g. torid + // soo create transfomation on border of object + + // there is no point on surface so no use of surface will be applied + if (data_->shape.projection.use_surface) data_->shape.projection.use_surface = false; + + auto gizmo_type = static_cast(input.gizmo_type); + return start_create_volume_job(input.worker, *object, {}, std::move(data_), input.volume_type, gizmo_type); + }; + const Model * model = input.canvas.get_model(); + const ModelObjectPtrs &objects = model->objects; + const ModelVolume * mv = get_model_volume(*input.gl_volume, objects); + if (mv == nullptr) + return false; + const ModelInstance *instance = get_model_instance(*input.gl_volume, objects); + assert(instance != nullptr); + if (instance == nullptr) + return false; + const ModelObject *object = mv->get_object(); + if (object == nullptr) + return false; + + input.on_register_mesh_pick(); // modify by qds + + std::optional hit = ray_from_camera(input.raycaster, mouse_pos, input.camera, &input.raycast_condition); + // context menu for add text could be open only by right click on an + // object. After right click, object is selected and object_idx is set + // also hit must exist. But there is options to add text by object list + if (!hit.has_value()) { // modify by qds + return start_create_object_job(input, std::move(data), mouse_pos); // return on_bad_state(std::move(data), object); + } + + // Create result volume transformation + Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, UP_LIMIT); + apply_transformation(input.angle, input.distance, surface_trmat); + Transform3d transform = instance->get_matrix().inverse() * surface_trmat; + auto gizmo_type = static_cast(input.gizmo_type); + // Try to cast ray into scene and find object for add volume + return start_create_volume_job(input.worker, *object, transform, std::move(data), input.volume_type, gizmo_type); +} + +bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos) +{ + if (data == nullptr) return false; + if (!check(input)) return false; + + if (input.gl_volume == nullptr || !input.gl_volume->selected) + // object is not under mouse position soo create object on plater + return start_create_object_job(input, std::move(data), mouse_pos); + else { // modify by qds + return start_create_volume_on_surface_job(input, std::move(data), mouse_pos); + } +} + + +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume) +{ + const ModelVolumePtrs &volumes = volume.get_object()->volumes; + // no other volume in object + if (volumes.size() <= 1) return {}; + return create_sources(volumes, volume.id().id); +} + + +SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) +{ + SurfaceVolumeData::ModelSources result; + result.reserve(volumes.size() - 1); + for (const ModelVolume *v : volumes) { + if (text_volume_id.has_value() && v->id().id == *text_volume_id) continue; + // skip modifiers and negative volumes, ... + if (!v->is_model_part()) continue; + const TriangleMesh &tm = v->mesh(); + if (tm.empty()) continue; + if (tm.its.empty()) continue; + result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()}); + } + return result; +} + +} +} // namespace Slic3r::GUI +} // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp new file mode 100644 index 0000000..bb346d3 --- /dev/null +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -0,0 +1,351 @@ +#ifndef slic3r_EmbossJob_hpp_ +#define slic3r_EmbossJob_hpp_ + +#include +#include +#include +#include +#include +#include // ExPolygonsWithIds +#include "libslic3r/Point.hpp" // Transform3d +#include "libslic3r/ObjectID.hpp" + +#include "slic3r/GUI/Camera.hpp" +//#include "slic3r/GUI/TextLines.hpp" +#include "slic3r/Utils/RaycastManager.hpp" +#include "JobNew.hpp" + +namespace Slic3r { +class TriangleMesh; +class ModelVolume; +class ModelObject; +enum class ModelVolumeType : int; +class BuildVolume; +namespace GUI { +//class Plater; +class GLCanvas3D; +class Worker; +class Selection; +namespace Emboss { +class DataBase +{ +public: + DataBase(const std::string &volume_name, std::shared_ptr> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {} + DataBase(const std::string &volume_name, std::shared_ptr> cancel, EmbossShape &&shape) + : volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape)) + {} + DataBase(DataBase &&) = default; + virtual ~DataBase() = default; + + /// + /// Create shape + /// e.g. Text extract glyphs from font + /// Not 'const' function because it could modify shape + /// + virtual EmbossShape &create_shape() { return shape; }; + + /// + /// Write data how to reconstruct shape to volume + /// + /// Data object for store emboss params + virtual void write(ModelVolume &volume) const; + + // Define projection move + // True (raised) .. move outside from surface (MODEL_PART) + // False (engraved).. move into object (NEGATIVE_VOLUME) + bool is_outside = true; + + // Define per letter projection on one text line + // [optional] It is not used when empty + Slic3r::Emboss::TextLines text_lines = {}; + // [optional] Define distance for surface + // It is used only for flat surface (not cutted) + // Position of Zero(not set value) differ for MODEL_PART and NEGATIVE_VOLUME + std::optional from_surface; + // new volume name + std::string volume_name; + // flag that job is canceled + // for time after process. + std::shared_ptr> cancel; + // shape to emboss + EmbossShape shape; +}; + +struct DataCreateVolumeUtil : public DataBase // modfiy bu qds //struct DataCreateVolume : public DataBase +{ + // define embossed volume type + ModelVolumeType volume_type; + + // parent ModelObject index where to create volume + ObjectID object_id; + + // new created volume transformation + Transform3d trmat; +}; +using DataBasePtr = std::unique_ptr; +/// +/// Hold neccessary data to update embossed text object in job +/// +struct DataUpdate +{ + // Hold data about shape + DataBasePtr base; + + // unique identifier of volume to change + ObjectID volume_id; + + // Used for prevent flooding Undo/Redo stack on slider. + bool make_snapshot; +}; + + struct CreateVolumeParams +{ + GLCanvas3D &canvas; + // Direction of ray into scene + const Camera &camera; + // To put new object on the build volume + const BuildVolume &build_volume; + // used to emplace job for execution + Worker &worker; + // Contain AABB trees from scene + typedef std::function register_mesh_pick; + register_mesh_pick on_register_mesh_pick{nullptr}; + RaycastManager &raycaster; + RaycastManager::AllowVolumes& raycast_condition; + // New created volume type + ModelVolumeType volume_type; + // Define which gizmo open on the success + unsigned char gizmo_type; // GLGizmosManager::EType + // Volume define object to add new volume + const GLVolume *gl_volume; + // Contain AABB trees from scene + // RaycastManager &raycaster; + // Wanted additionl move in Z(emboss) direction of new created volume + std::optional distance = {}; + // Wanted additionl rotation around Z of new created volume + std::optional angle = {}; +}; +struct DataCreateObject +{ + // Hold data about shape + DataBasePtr base; + // define position on screen where to create object + Vec2d screen_coor; + // projection property + const Camera &camera; + // shape of bed in case of create volume on bed + std::vector bed_shape; + // Define which gizmo open on the success + unsigned char gizmo_type; + // additionl rotation around Z axe, given by style settings + std::optional angle = {}; +}; + + /// +/// Hold neccessary data to create ModelVolume in job +/// Volume is created on the surface of existing volume in object. +/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! +/// +struct DataCreateVolume +{ + // Hold data about shape + DataBasePtr base; + + // define embossed volume type + ModelVolumeType volume_type; + + // parent ModelObject index where to create volume + ObjectID object_id; + + // new created volume transformation + std::optional trmat; + + // Define which gizmo open on the success + unsigned char gizmo_type; +}; + +struct SurfaceVolumeData +{ + // Transformation of volume inside of object + Transform3d transform; + + struct ModelSource + { + // source volumes + std::shared_ptr mesh; + // Transformation of volume inside of object + Transform3d tr; + }; + using ModelSources = std::vector; + ModelSources sources; +}; + + + /// +/// Hold neccessary data to update embossed text object in job +/// +struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData +{}; + +static bool was_canceled(const JobNew::Ctl &ctl, const DataBase &base); +static bool exception_process(std::exception_ptr &eptr); +static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input); +/// +/// Update text volume to use surface from object +/// +class UpdateSurfaceVolumeJob : public JobNew +{ + UpdateSurfaceVolumeData m_input; + TriangleMesh m_result; + +public: + // move params to private variable + explicit UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; + static bool is_use_surfae_error; +}; + +/// +/// Update text shape in existing text volume +/// Predict that there is only one runnig(not canceled) instance of it +/// +class UpdateJob : public JobNew +{ + DataUpdate m_input; + TriangleMesh m_result; + +public: + // move params to private variable + explicit UpdateJob(DataUpdate &&input); + + /// + /// Create new embossed volume by m_input data and store to m_result + /// + /// Control containing cancel flag + void process(Ctl &ctl) override; + + /// + /// Update volume - change object_id + /// + /// Was process canceled. + /// NOTE: Be carefull it doesn't care about + /// time between finished process and started finalize part. + /// unused + void finalize(bool canceled, std::exception_ptr &eptr) override; + + /// + /// Update text volume + /// + /// Volume to be updated + /// New Triangle mesh for volume + /// Data to write into volume + static void update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base); +}; + +/// +/// Create new TextObject on the platter +/// Should not be stopped +/// +class CreateObjectJob : public JobNew +{ + DataCreateObject m_input; + TriangleMesh m_result; + Transform3d m_transformation; + +public: + explicit CreateObjectJob(DataCreateObject &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; + +struct Texture +{ + unsigned id{0}; + unsigned width{0}; + unsigned height{0}; +}; + +/// +/// Hold neccessary data to create(cut) volume from surface object in job +/// +struct CreateSurfaceVolumeData : public SurfaceVolumeData +{ + // Hold data about shape + DataBasePtr base; + // define embossed volume type + ModelVolumeType volume_type; + // parent ModelObject index where to create volume + ObjectID object_id; + // Define which gizmo open on the success + unsigned char gizmo_type; +}; +class CreateSurfaceVolumeJob : public JobNew +{ + CreateSurfaceVolumeData m_input; + TriangleMesh m_result; + +public: + explicit CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; + +class CreateVolumeJob : public JobNew +{ + DataCreateVolume m_input; + TriangleMesh m_result; + +public: + explicit CreateVolumeJob(DataCreateVolume &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; +static bool check(unsigned char gizmo_type); +static bool check(const DataBase &input, bool check_fontfile, bool use_surface = false); +static bool check(const CreateVolumeParams &input); +static bool check(const DataCreateObject &input); +static bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false); +static bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); +static bool check(const DataCreateVolume &input, bool is_main_thread = false); +static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); +bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor); +bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data); +bool start_create_volume_job( Worker &worker, const ModelObject &object, const std::optional &volume_tr, DataBasePtr data, ModelVolumeType volume_type, unsigned char gizmo_type); +bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos); +bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos); +static ExPolygons create_shape(DataBase &input); +static TriangleMesh create_mesh_per_glyph(DataBase &input); +static TriangleMesh try_create_mesh(DataBase &input); +static TriangleMesh create_mesh(DataBase &input); +static indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input); +static TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2); +static TriangleMesh cut_surface(DataBase &input1, const SurfaceVolumeData &input2); +static void _update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr); +static void create_volume( + TriangleMesh &&mesh, const ObjectID &object_id, const ModelVolumeType type, const std::optional &trmat, const DataBase &data, unsigned char gizmo_type); +/// Update text volume +/// +/// Volume to be updated +/// New Triangle mesh for volume +/// Data to write into volume +bool start_update_volume(DataUpdate &&data, const ModelVolume &volume, const Selection &selection, RaycastManager &raycaster); + +/// +/// Copied triangles from object to be able create mesh for cut surface from +/// +/// Define embossed volume +/// Source data for cut surface from +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume); +/// +/// Copied triangles from object to be able create mesh for cut surface from +/// +/// Source object volumes for cut surface from +/// Source volume id +/// Source data for cut surface from +SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id = {}); +} +} // namespace Slic3r::GUI +} // namespace Slic3r + +#endif // slic3r_EmbossJob_hpp_ diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 3da1c33..d6db52d 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -125,7 +125,7 @@ void FillBedJob::prepare() plate_list.preprocess_exclude_areas(params.excluded_regions, 1, scaled_exclusion_gap); plate_list.preprocess_exclude_areas(m_unselected); - m_bedpts = get_bed_shape(*m_plater->config()); + m_bedpts = get_bed_shape(global_config); auto &objects = m_plater->model().objects; /*BoundingBox bedbb = get_extents(m_bedpts); @@ -200,18 +200,17 @@ void FillBedJob::prepare() void FillBedJob::process() { if (m_object_idx == -1 || m_selected.empty()) return; + const Slic3r::DynamicPrintConfig &global_config = wxGetApp().preset_bundle->full_config(); - update_arrange_params(params, m_plater->config(), m_selected); - m_bedpts = get_shrink_bedpts(m_plater->config(), params); + update_arrange_params(params, global_config, m_selected); + m_bedpts = get_shrink_bedpts(global_config, params); auto &partplate_list = m_plater->get_partplate_list(); - auto &print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); - const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config(); if (params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer")) partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES); - update_selected_items_inflation(m_selected, m_plater->config(), params); - update_unselected_items_inflation(m_unselected, m_plater->config(), params); + update_selected_items_inflation(m_selected, global_config, params); + update_unselected_items_inflation(m_unselected, global_config, params); bool do_stop = false; params.stopcondition = [this, &do_stop]() { diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp index 19c9364..b3adf7e 100644 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ b/src/slic3r/GUI/Jobs/Job.cpp @@ -62,7 +62,6 @@ GUI::Job::Job(std::shared_ptr pri) // to make sure they close, fade out, whathever m_progress->set_progress(m_range); m_progress->set_cancel_callback(); - wxEndBusyCursor(); if (m_worker_error) { m_finalized = true; @@ -86,7 +85,7 @@ GUI::Job::Job(std::shared_ptr pri) finalize(); } - + wxEndBusyCursor(); // dont do finalization again for the same process m_finalized = true; } @@ -96,6 +95,8 @@ GUI::Job::Job(std::shared_ptr pri) void GUI::Job::start() { // Start the job. No effect if the job is already running if (!m_running.load()) { + // Changing cursor to busy + wxBeginBusyCursor(); prepare(); // Save the current status indicatior range and push the new one @@ -110,9 +111,6 @@ void GUI::Job::start() m_finalized = false; m_finalizing = false; - // Changing cursor to busy - wxBeginBusyCursor(); - try { // Execute the job m_worker_error = nullptr; m_thread = create_thread([this] { this->run(m_worker_error); }); diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index a92dfbf..005a807 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -71,7 +71,8 @@ protected: public: enum JobPrepareState { PREPARE_STATE_DEFAULT = 0, - PREPARE_STATE_MENU = 1, + PREPARE_STATE_MENU = 1, + PREPARE_STATE_OUTSIDE_BED = 2, }; Job(std::shared_ptr pri); diff --git a/src/slic3r/GUI/Jobs/JobNew.hpp b/src/slic3r/GUI/Jobs/JobNew.hpp new file mode 100644 index 0000000..5d671a8 --- /dev/null +++ b/src/slic3r/GUI/Jobs/JobNew.hpp @@ -0,0 +1,68 @@ +#ifndef JOBNEW_HPP +#define JOBNEW_HPP + +#include +#include +#include + +#include + +#include "libslic3r/libslic3r.h" +#include "ProgressIndicator.hpp" + +namespace Slic3r { namespace GUI { + +// A class representing a job that is to be run in the background, not blocking +// the main thread. Running it is up to a Worker object (see Worker interface) +class JobNew { +public: + + enum JobPrepareState { + PREPARE_STATE_DEFAULT = 0, + PREPARE_STATE_MENU = 1, + }; + + // A controller interface that informs the job about cancellation and + // makes it possible for the job to advertise its status. + class Ctl { + public: + virtual ~Ctl() = default; + + // status update, to be used from the work thread (process() method) + virtual void update_status(int st, const std::string &msg = "") = 0; + + // Returns true if the job was asked to cancel itself. + virtual bool was_canceled() const = 0; + + // Orca: + virtual void clear_percent() = 0; + virtual void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) = 0; + + // Execute a functor on the main thread. Note that the exact time of + // execution is hard to determine. This can be used to make modifications + // on the UI, like displaying some intermediate results or modify the + // cursor. + // This function returns a std::future object which enables the + // caller to optionally wait for the main thread to finish the function call. + virtual std::future call_on_main_thread(std::function fn) = 0; + }; + + virtual ~JobNew() = default; + + // The method where the actual work of the job should be defined. This is + // run on the worker thread. + virtual void process(Ctl &ctl) = 0; + + // Launched when the job is finished on the UI thread. + // If the job was cancelled, the first parameter will have a true value. + // Exceptions occuring in process() are redirected from the worker thread + // into the main (UI) thread. This method receives the exception and can + // handle it properly. Assign nullptr to this second argument before + // function return to prevent further action. Leaving it with a non-null + // value will result in rethrowing by the worker. + virtual void finalize(bool /*canceled*/, std::exception_ptr &) {} +}; + +}} // namespace Slic3r::GUI + +#endif // JOB_HPP diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp index 13470f7..17249d4 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp @@ -22,11 +22,14 @@ void NotificationProgressIndicator::set_range(int range) void NotificationProgressIndicator::set_cancel_callback(CancelFn fn) { - m_nm->progress_indicator_set_cancel_callback(std::move(fn)); + m_cancelfn = std::move(fn); + m_nm->progress_indicator_set_cancel_callback(m_cancelfn); } void NotificationProgressIndicator::set_progress(int pr) { + if (!pr) + set_cancel_callback(m_cancelfn); m_nm->progress_indicator_set_progress(pr); } diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp index ba0e899..f3150ed 100644 --- a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp @@ -9,6 +9,7 @@ class NotificationManager; class NotificationProgressIndicator: public ProgressIndicator { NotificationManager *m_nm = nullptr; + CancelFn m_cancelfn; public: diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp new file mode 100644 index 0000000..8e344e6 --- /dev/null +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -0,0 +1,155 @@ +#ifndef PLATERWORKER_HPP +#define PLATERWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "BusyCursorJob.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" + +namespace Slic3r { namespace GUI { + +template +class PlaterWorker: public Worker { + WorkerSubclass m_w; + wxWindow *m_plater; + + class PlaterJob : public JobNew { + std::unique_ptr m_job; + wxWindow *m_plater; + long long m_process_duration; // [ms] + + public: + void process(Ctl &c) override + { + // Ensure that wxWidgets processing wakes up to handle outgoing + // messages in plater's wxIdle handler. Otherwise it might happen + // that the message will only be processed when an event like mouse + // move comes along which might be too late. + struct WakeUpCtl: Ctl { + Ctl &ctl; + WakeUpCtl(Ctl &c) : ctl{c} {} + + void update_status(int st, const std::string &msg = "") override + { + ctl.update_status(st, msg); + wxWakeUpIdle(); + } + + bool was_canceled() const override { return ctl.was_canceled(); } + + std::future call_on_main_thread(std::function fn) override + { + auto ftr = ctl.call_on_main_thread(std::move(fn)); + wxWakeUpIdle(); + + return ftr; + } + + void clear_percent() override { + ctl.clear_percent(); + wxWakeUpIdle(); + } + + void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) override + { + ctl.show_error_info(msg, code, description, extra); + wxWakeUpIdle(); + } + + } wctl{c}; + + CursorSetterRAII busycursor{wctl}; + + using namespace std::chrono; + steady_clock::time_point process_start = steady_clock::now(); + m_job->process(wctl); + steady_clock::time_point process_end = steady_clock::now(); + m_process_duration = duration_cast(process_end - process_start).count(); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + using namespace std::chrono; + steady_clock::time_point finalize_start = steady_clock::now(); + m_job->finalize(canceled, eptr); + steady_clock::time_point finalize_end = steady_clock::now(); + long long finalize_duration = duration_cast(finalize_end - finalize_start).count(); + + BOOST_LOG_TRIVIAL(info) + << std::fixed // do not use scientific notations + << "Job '" << typeid(*m_job).name() << "' " + << "spend " << m_process_duration + finalize_duration << "ms " + << "(process " << m_process_duration << "ms + finalize " << finalize_duration << "ms)"; + + if (eptr) try { + std::rethrow_exception(eptr); + } catch (std::exception &e) { + show_error(m_plater, _L("An unexpected error occured") + ": " + e.what()); + eptr = nullptr; + } + } + + PlaterJob(wxWindow *p, std::unique_ptr j) + : m_job{std::move(j)}, m_plater{p} + { + // TODO: decide if disabling slice button during UI job is what we + // want. + // if (m_plater) + // m_plater->sidebar().enable_buttons(false); + } + + ~PlaterJob() override + { + // TODO: decide if disabling slice button during UI job is what we want. + + // Reload scene ensures that the slice button gets properly + // enabled or disabled after the job finishes, depending on the + // state of slicing. This might be an overkill but works for now. + // if (m_plater) + // m_plater->canvas3D()->reload_scene(false); + } + }; + + EventGuard on_idle_evt; + EventGuard on_paint_evt; + +public: + + template + PlaterWorker(wxWindow *plater, WorkerArgs &&...args) + : m_w{std::forward(args)...} + , m_plater{plater} + // Ensure that messages from the worker thread to the UI thread are + // processed continuously. + , on_idle_evt(plater, wxEVT_IDLE, [this](wxIdleEvent&) { process_events(); }) + , on_paint_evt(plater, wxEVT_PAINT, [this](wxPaintEvent&) { process_events(); }) + { + } + + // Always package the job argument into a PlaterJob + bool push(std::unique_ptr job) override + { + return m_w.push(std::make_unique(m_plater, std::move(job))); + } + + bool is_idle() const override { return m_w.is_idle(); } + void cancel() override { m_w.cancel(); } + void cancel_all() override { m_w.cancel_all(); } + void process_events() override { m_w.process_events(); } + bool wait_for_current_job(unsigned timeout_ms = 0) override + { + return m_w.wait_for_current_job(timeout_ms); + } + bool wait_for_idle(unsigned timeout_ms = 0) override + { + return m_w.wait_for_idle(timeout_ms); + } +}; + +}} // namespace Slic3r::GUI + +#endif // PLATERJOB_HPP diff --git a/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp new file mode 100644 index 0000000..3ee1672 --- /dev/null +++ b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp @@ -0,0 +1,124 @@ +#ifndef THREADSAFEQUEUE_HPP +#define THREADSAFEQUEUE_HPP + +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +// Helper structure for overloads of ThreadSafeQueueSPSC::consume_one() +// to block if the queue is empty. +struct BlockingWait +{ + // Timeout to wait for the arrival of new element into the queue. + unsigned timeout_ms = 0; + + // An optional atomic flag to set true if an incoming element gets + // consumed. The flag will be atomically set to true when popping the + // front of the queue. + std::atomic *pop_flag = nullptr; +}; + +// A thread safe queue for one producer and one consumer. +template class Container = std::deque, + class... ContainerArgs> +class ThreadSafeQueueSPSC +{ + std::queue> m_queue; + mutable std::mutex m_mutex; + std::condition_variable m_cond_var; +public: + + // Consume one element, block if the queue is empty. + template bool consume_one(const BlockingWait &blkw, Fn &&fn) + { + static_assert(!std::is_reference_v, ""); + static_assert(std::is_default_constructible_v, ""); + static_assert(std::is_move_assignable_v || std::is_copy_assignable_v, ""); + + T el; + { + std::unique_lock lk{m_mutex}; + + auto pred = [this]{ return !m_queue.empty(); }; + if (blkw.timeout_ms > 0) { + auto timeout = std::chrono::milliseconds(blkw.timeout_ms); + if (!m_cond_var.wait_for(lk, timeout, pred)) + return false; + } + else + m_cond_var.wait(lk, pred); + + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + + if (blkw.pop_flag) + // The optional flag is set before the lock us unlocked. + blkw.pop_flag->store(true); + } + + fn(el); + return true; + } + + // Consume one element, return true if consumed, false if queue was empty. + template bool consume_one(Fn &&fn) + { + T el; + { + std::unique_lock lk{m_mutex}; + if (!m_queue.empty()) { + if constexpr (std::is_move_assignable_v) + el = std::move(m_queue.front()); + else + el = m_queue.front(); + + m_queue.pop(); + } else + return false; + } + + fn(el); + + return true; + } + + // Push element into the queue. + template void push(TArgs&&...el) + { + std::lock_guard lk{m_mutex}; + m_queue.emplace(std::forward(el)...); + m_cond_var.notify_one(); + } + + bool empty() const + { + std::lock_guard lk{m_mutex}; + return m_queue.empty(); + } + + size_t size() const + { + std::lock_guard lk{m_mutex}; + return m_queue.size(); + } + + void clear() + { + std::lock_guard lk{m_mutex}; + while (!m_queue.empty()) + m_queue.pop(); + } +}; + +}} // namespace Slic3r::GUI + +#endif // THREADSAFEQUEUE_HPP diff --git a/src/slic3r/GUI/Jobs/Worker.hpp b/src/slic3r/GUI/Jobs/Worker.hpp new file mode 100644 index 0000000..dd2f80c --- /dev/null +++ b/src/slic3r/GUI/Jobs/Worker.hpp @@ -0,0 +1,140 @@ +#ifndef PRUSALSICER_WORKER_HPP +#define PRUSALSICER_WORKER_HPP + +#include + +#include "JobNew.hpp" + +namespace Slic3r { namespace GUI { +// #define EXECUTE_UPDATE_ON_MAIN_THREAD // debug execution on main thread +// An interface of a worker that runs jobs on a dedicated worker thread, one +// after the other. It is assumed that every method of this class is called +// from the same main thread. + + #ifdef EXECUTE_UPDATE_ON_MAIN_THREAD +namespace { +// Run Job on main thread (blocking) - ONLY DEBUG +static inline bool execute_job(std::shared_ptr j) +{ + struct MyCtl : public JobNew::Ctl + { + void update_status(int st, const std::string &msg = "") override{}; + bool was_canceled() const override { return false; } + std::future call_on_main_thread(std::function fn) override { return std::future{}; } + } ctl; + j->process(ctl); + wxGetApp().plater()->CallAfter([j]() { + std::exception_ptr e_ptr = nullptr; + j->finalize(false, e_ptr); + }); + return true; +} +} // namespace +#endif +class Worker { +public: + // Queue up a new job after the current one. This call does not block. + // Returns false if the job gets discarded. + virtual bool push(std::unique_ptr job) = 0; + + // Returns true if no job is running, the job queue is empty and no job + // message is left to be processed. This means that nothing is left to + // finalize or take care of in the main thread. + virtual bool is_idle() const = 0; + + // Ask the current job gracefully to cancel. This call is not blocking and + // the job may or may not cancel eventually, depending on its + // implementation. Note that it is not trivial to kill a thread forcefully + // and we don't need that. + virtual void cancel() = 0; + + // This method will delete the queued jobs and cancel the current one. + virtual void cancel_all() = 0; + + // Needs to be called continuously to process events (like status update + // or finalizing of jobs) in the main thread. This can be done e.g. in a + // wxIdle handler. + virtual void process_events() = 0; + + // Wait until the current job finishes. Timeout will only be considered + // if not zero. Returns false if timeout is reached but the job has not + // finished. + virtual bool wait_for_current_job(unsigned timeout_ms = 0) = 0; + + // Wait until the whole job queue finishes. Timeout will only be considered + // if not zero. Returns false only if timeout is reached but the worker has + // not reached the idle state. + virtual bool wait_for_idle(unsigned timeout_ms = 0) = 0; + + // The destructor shall properly close the worker thread. + virtual ~Worker() = default; +}; + +template constexpr bool IsProcessFn = std::is_invocable_v; +template constexpr bool IsFinishFn = std::is_invocable_v; + +// Helper function to use the worker with arbitrary functors. +template>, + class = std::enable_if_t> > +bool queue_job(Worker &w, ProcessFn fn, FinishFn finishfn) +{ + struct LambdaJob: JobNew { + ProcessFn fn; + FinishFn finishfn; + + LambdaJob(ProcessFn pfn, FinishFn ffn) + : fn{std::move(pfn)}, finishfn{std::move(ffn)} + {} + + void process(Ctl &ctl) override { fn(ctl); } + void finalize(bool canceled, std::exception_ptr &eptr) override + { + finishfn(canceled, eptr); + } + }; + + auto j = std::make_unique(std::move(fn), std::move(finishfn)); + return w.push(std::move(j)); +} + +template>> +bool queue_job(Worker &w, ProcessFn fn) +{ + return queue_job(w, std::move(fn), [](bool, std::exception_ptr &) {}); +} + +inline bool queue_job(Worker &w, std::unique_ptr j) +{ + return w.push(std::move(j)); +} + +// Replace the current job queue with a new job. The signature is the same +// as for queue_job(). This cancels all jobs and +// will not wait. The new job will begin after the queue cancels properly. +// Note that this can be called from the UI thread and will not block it if +// the jobs take longer to cancel. +template bool replace_job(Worker &w, Args&& ...args) +{ + w.cancel_all(); + return queue_job(w, std::forward(args)...); +} + +// Cancel the current job and wait for it to actually be stopped. +inline bool stop_current_job(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel(); + return w.wait_for_current_job(timeout_ms); +} + +// Cancel all pending jobs including current one and wait until the worker +// becomes idle. +inline bool stop_queue(Worker &w, unsigned timeout_ms = 0) +{ + w.cancel_all(); + return w.wait_for_idle(timeout_ms); +} + +}} // namespace Slic3r::GUI + +#endif // WORKER_HPP diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 614f9f3..79f2f35 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -195,12 +195,12 @@ void KBShortcutsDialog::fill_shortcuts() #else { ctrl + "P", L("Preferences") }, #endif - //3D control, for Apple, use Cmd-Shift-M instead of Ctrl/Cmd-M due - #ifndef __APPLE__ - { ctrl + "Shift+M", L("Show/Hide 3Dconnexion devices settings dialog") }, - #else - { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") }, - #endif + //3Dconnexion control +#ifndef __APPLE__ + {ctrl + "Shift+M", L("Show/Hide 3Dconnexion devices settings dialog")}, +#else + {ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog")}, +#endif // Switch table page #ifndef __APPLE__ @@ -213,7 +213,11 @@ void KBShortcutsDialog::fill_shortcuts() {L("Del"), L("Delete selected")}, #endif // Help - { "?", L("Show keyboard shortcuts list") } +#ifdef __WINDOWS__ + { "Shift+Alt+?", L("Show keyboard shortcuts list") } +#else + {"Shift+?", L("Show keyboard shortcuts list")} +#endif }; m_full_shortcuts.push_back({{_L("Global shortcuts"), ""}, global_shortcuts}); @@ -264,14 +268,8 @@ void KBShortcutsDialog::fill_shortcuts() Shortcuts gizmos_shortcuts = { {"Esc", L("Deselect all")}, - {L("Shift+"), L("Move: press to snap by 1mm")}, - #ifdef __APPLE__ - {L("⌘+Mouse wheel"), L("Support/Color Painting: adjust pen radius")}, - {L("⌥+Mouse wheel"), L("Support/Color Painting: adjust section position")}, - #else - {L("Ctrl+Mouse wheel"), L("Support/Color Painting: adjust pen radius")}, - {L("Alt+Mouse wheel"), L("Support/Color Painting: adjust section position")}, - #endif + {ctrl + L("Mouse wheel"), L("Support/Color Painting: adjust pen radius")}, + {alt + L("Mouse wheel"), L("Support/Color Painting: adjust section position")}, }; m_full_shortcuts.push_back({{_L("Gizmo"), ""}, gizmos_shortcuts}); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ec3f678..25faa01 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -67,7 +67,7 @@ #endif // _WIN32 #include - +//y #include "../QIDI/QIDINetwork.hpp" namespace Slic3r { @@ -199,6 +199,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ //reset log level auto loglevel = wxGetApp().app_config->get("severity_level"); Slic3r::set_logging_level(Slic3r::level_string_to_boost(loglevel)); + std::map wx_log_levels{{"fatal", wxLOG_FatalError}, {"error", wxLOG_FatalError}, {"warning", wxLOG_Warning}, + {"info", wxLOG_Info}, {"debug", wxLOG_Debug}, {"trace", wxLOG_Trace}}; + wxLog::SetLogLevel(wx_log_levels[loglevel]); // QDS m_recent_projects.SetMenuPathStyle(wxFH_PATH_SHOW_ALWAYS); @@ -550,11 +553,13 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ MarkdownTip::ExitTip(); m_plater->reset(); - + + //y m_printer_view->StopStatusThread(); this->shutdown(); // propagate event + wxGetApp().remove_mall_system_dialog(); event.Skip(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": mainframe finished process close_widow event"; @@ -578,7 +583,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); event.Skip(); }); -#endif +#endif update_ui_from_settings(); // FIXME (?) @@ -641,12 +646,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ return; } else if (evt.CmdDown() && evt.GetKeyCode() == 'G') { if (can_export_gcode()) { wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_SLICED_FILE)); } evt.Skip(); return; } - if (evt.CmdDown() && evt.GetKeyCode() == 'J') { m_printhost_queue_dlg->Show(); return; } + if (evt.CmdDown() && evt.GetKeyCode() == 'J') { m_printhost_queue_dlg->Show(); return; } if (evt.CmdDown() && evt.GetKeyCode() == 'N') { m_plater->new_project(); return;} if (evt.CmdDown() && evt.GetKeyCode() == 'O') { m_plater->load_project(); return;} if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;} else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;} - if (evt.CmdDown() && evt.GetKeyCode() == 'F') { + if (evt.CmdDown() && evt.GetKeyCode() == 'F') { if (m_plater && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview)) { m_plater->sidebar().can_search(); } @@ -821,6 +826,10 @@ void MainFrame::update_layout() if (!preview_only_hint()) return; } + //y + //else if (evt.GetId() == tpCalibration) { + // m_calibration->update_all(); + //} evt.Skip(); }); @@ -884,6 +893,7 @@ void MainFrame::shutdown() #endif // _WIN32 if (m_plater != nullptr) { + m_plater->get_ui_job_worker().cancel_all(); m_plater->stop_jobs(); // Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC, @@ -952,13 +962,6 @@ void MainFrame::update_title() return; } -void MainFrame::show_publish_button(bool show) -{ - //B - // m_publish_btn->Show(show); - Layout(); -} - void MainFrame::show_calibration_button(bool show) { #ifdef __APPLE__ @@ -1040,8 +1043,25 @@ void MainFrame::init_tabpanel() wxCommandEvent* evt = new wxCommandEvent(EVT_INSTALL_PLUGIN_HINT); wxQueueEvent(m_plater, evt); } + if (m_confirm_download_plugin_dlg == nullptr){ + m_confirm_download_plugin_dlg = new SecondaryCheckDialog(this, wxID_ANY, _L("Install network plug-in"), SecondaryCheckDialog::ButtonStyle::ONLY_CONFIRM); + m_confirm_download_plugin_dlg->SetSize(wxSize(FromDIP(270), FromDIP(158))); + m_confirm_download_plugin_dlg->update_text(_L("Please Install network plug-in before log in.")); + m_confirm_download_plugin_dlg->update_btn_label(_L("Install Network Plug-in"), _L("")); + + m_confirm_download_plugin_dlg->Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) { + this->m_confirm_download_plugin_dlg->Close(); + wxGetApp().ShowDownNetPluginDlg(); + return; + }); + } + int xPos = GetRect().GetX() + (GetSize().x - m_confirm_download_plugin_dlg->GetSize().x) / 2; + int yPos = GetRect().GetY() + (GetSize().y - m_confirm_download_plugin_dlg->GetSize().y) / 2; + m_confirm_download_plugin_dlg->SetPosition(wxPoint(xPos, yPos)); + m_confirm_download_plugin_dlg->on_show(); } } + //y else if (new_sel == tpMonitor && wxGetApp().preset_bundle != nullptr) { // auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; //wxString url; @@ -1515,6 +1535,7 @@ bool MainFrame::can_send_gcode() const { if (m_plater && !m_plater->model().objects.empty()) { + // y //auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; //if (const auto *print_host_opt = cfg.option("print_host"); print_host_opt) // return !print_host_opt->value.empty(); @@ -1605,17 +1626,14 @@ wxBoxSizer* MainFrame::create_side_tools() m_slice_select = eSlicePlate; m_print_select = ePrintPlate; - // m_publish_btn = new Button(this, _L("Upload"), "bar_publish", 0, FromDIP(16)); m_slice_btn = new SideButton(this, _L("Slice plate"), ""); m_slice_option_btn = new SideButton(this, "", "sidebutton_dropdown", 0, FromDIP(14)); m_print_btn = new SideButton(this, _L("Print plate"), ""); m_print_option_btn = new SideButton(this, "", "sidebutton_dropdown", 0, FromDIP(14)); update_side_button_style(); - // m_publish_btn->Hide(); m_slice_option_btn->Enable(); m_print_option_btn->Enable(); - // sizer->Add(m_publish_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(1)); sizer->Add(FromDIP(15), 0, 0, 0, 0); sizer->Add(m_slice_option_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(1)); sizer->Add(m_slice_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(1)); @@ -1626,23 +1644,6 @@ wxBoxSizer* MainFrame::create_side_tools() sizer->Layout(); - // m_publish_btn->Bind(wxEVT_BUTTON, [this](auto& e) { - // CallAfter([this] { - // wxGetApp().open_publish_page_dialog(); - - // if (!wxGetApp().getAgent()) { - // BOOST_LOG_TRIVIAL(info) << "publish: no agent"; - // return; - // } - - // // record - // json j; - // NetworkAgent* agent = GUI::wxGetApp().getAgent(); - // if (agent) - // agent->track_event("enter_model_mall", j.dump()); - // }); - // }); - m_slice_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { //this->m_plater->select_view_3D("Preview"); @@ -1787,6 +1788,8 @@ wxBoxSizer* MainFrame::create_side_tools() SideButton* export_all_sliced_file_btn = new SideButton(p, _L("Export all sliced file"), ""); export_all_sliced_file_btn->SetCornerRadius(0); + //y + export_all_sliced_file_btn->Hide(); print_plate_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) { m_print_btn->SetLabel(_L("Print plate")); @@ -1867,7 +1870,6 @@ wxBoxSizer* MainFrame::create_side_tools() }); p->append_button(print_multi_machine_btn); } - } p->Popup(m_print_btn); @@ -1961,11 +1963,11 @@ bool MainFrame::get_enable_print_status() } else if (m_print_select == eExportGcode) { - if (!current_plate->is_slice_result_valid()) - { - enable = false; - } - enable = enable && !is_all_plates; + if (!current_plate->is_slice_result_valid()) + { + enable = false; + } + enable = enable && !is_all_plates; } else if (m_print_select == eSendGcode) { @@ -1978,19 +1980,19 @@ bool MainFrame::get_enable_print_status() // y16 else if (m_print_select == eUploadGcode) { - if (!current_plate->is_slice_result_valid()) - enable = false; - if (!can_send_gcode()) - enable = false; - enable = enable && !is_all_plates; + if (!current_plate->is_slice_result_valid()) + enable = false; + if (!can_send_gcode()) + enable = false; + enable = enable && !is_all_plates; } - else if (m_print_select == eExportSlicedFile) - { - if (!current_plate->is_slice_result_ready_for_export()) - { - enable = false; - } - enable = enable && !is_all_plates; + else if (m_print_select == eExportSlicedFile) + { + if (!current_plate->is_slice_result_ready_for_export()) + { + enable = false; + } + enable = enable && !is_all_plates; } else if (m_print_select == eSendToPrinter) { @@ -1998,30 +2000,30 @@ bool MainFrame::get_enable_print_status() { enable = false; } - enable = enable && !is_all_plates; + enable = enable && !is_all_plates; } - else if (m_print_select == eSendToPrinterAll) - { - if (!part_plate_list.is_all_slice_results_ready_for_print()) - { - enable = false; - } - } - else if (m_print_select == eExportAllSlicedFile) - { - if (!part_plate_list.is_all_slice_result_ready_for_export()) - { - enable = false; - } - } - else if (m_print_select == ePrintMultiMachine) - { - if (!current_plate->is_slice_result_ready_for_print()) - { - enable = false; - } - enable = enable && !is_all_plates; - } + else if (m_print_select == eSendToPrinterAll) + { + if (!part_plate_list.is_all_slice_results_ready_for_print()) + { + enable = false; + } + } + else if (m_print_select == eExportAllSlicedFile) + { + if (!part_plate_list.is_all_slice_result_ready_for_export()) + { + enable = false; + } + } + else if (m_print_select == ePrintMultiMachine) + { + if (!current_plate->is_slice_result_ready_for_print()) + { + enable = false; + } + enable = enable && !is_all_plates; + } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": m_print_select %1%, enable= %2% ")%m_print_select %enable; @@ -2045,13 +2047,6 @@ void MainFrame::update_side_button_style() std::pair(wxColour(68, 121, 251), StateColor::Normal) // y96 ); - // m_publish_btn->SetMinSize(wxSize(FromDIP(125), FromDIP(24))); - // m_publish_btn->SetCornerRadius(FromDIP(12)); - // m_publish_btn->SetBackgroundColor(m_btn_bg_enable); - // m_publish_btn->SetBorderColor(m_btn_bg_enable); - // m_publish_btn->SetBackgroundColour(wxColour(59,68,70)); - // m_publish_btn->SetTextColor(StateColor::darkModeColorFor("#FFFFFE")); - m_slice_btn->SetTextLayout(SideButton::EHorizontalOrientation::HO_Left, FromDIP(15)); m_slice_btn->SetCornerRadius(FromDIP(12)); m_slice_btn->SetExtraSize(wxSize(FromDIP(38), FromDIP(10))); @@ -2238,10 +2233,15 @@ static const wxString sep_space = ""; static wxMenu* generate_help_menu() { wxMenu* helpMenu = new wxMenu(); - +#ifdef __WINDOWS__ // shortcut key - append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"), + auto alt = GUI::shortkey_alt_prefix(); + append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "& Shift+" + alt +"?", _L("Show the list of the keyboard shortcuts"), [](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); +#else + append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "& Shift+?", _L("Show the list of the keyboard shortcuts"), + [](wxCommandEvent &) { wxGetApp().keyboard_shortcuts(); }); +#endif // Show Beginner's Tutorial append_menu_item(helpMenu, wxID_ANY, _L("Setup Wizard"), _L("Setup Wizard"), [](wxCommandEvent &) {wxGetApp().ShowUserGuide();}); @@ -2273,7 +2273,11 @@ static wxMenu* generate_help_menu() return true; }); //B - // append_menu_item(helpMenu, wxID_ANY, _L("Open Network Test"), _L("Open Network Test"), [](wxCommandEvent&) { + // append_menu_item(helpMenu, wxID_ANY, _L("Check for Presets Update"), _L("Check for Presets Update"), [](wxCommandEvent &) { + // wxGetApp().check_config_updates_from_menu(); + // }); + + // append_menu_item(helpMenu, wxID_ANY, _L("Open Network Test"), _L("Open Network Test"), [](wxCommandEvent&) { // NetworkTestDialog dlg(wxGetApp().mainframe); // dlg.ShowModal(); // }); @@ -2473,7 +2477,29 @@ void MainFrame::init_menubar_as_editor() append_submenu(fileMenu, export_menu, wxID_ANY, _L("Export"), ""); - fileMenu->AppendSeparator(); + //y + // // Publish to MakerWorld + // append_menu_item( + // fileMenu, wxID_ANY, _L("Publish to MakerWorld"), _L("Publish to MakerWorld"), + // [this](wxCommandEvent &) { + // CallAfter([this] { + // wxGetApp().open_publish_page_dialog(); + + // if (!wxGetApp().getAgent()) { + // BOOST_LOG_TRIVIAL(info) << "publish: no agent"; + // return; + // } + + // // record + // json j; + // NetworkAgent *agent = GUI::wxGetApp().getAgent(); + // if (agent) agent->track_event("enter_model_mall", j.dump()); + // }); + // }, + // "", nullptr, + // [this](){ return wxGetApp().has_model_mall(); }, this); + + // fileMenu->AppendSeparator(); #ifndef __APPLE__ append_menu_item(fileMenu, wxID_EXIT, _L("Quit"), wxString::Format(_L("Quit")), @@ -2715,7 +2741,7 @@ void MainFrame::init_menubar_as_editor() viewMenu->Check(wxID_CAMERA_ORTHOGONAL + camera_id_base, true); viewMenu->AppendSeparator(); - append_menu_check_item(viewMenu, wxID_ANY, _L("Show &Labels") + "\t" + ctrl + "E", _L("Show object labels in 3D scene"), + append_menu_check_item(viewMenu, wxID_ANY, _L("Show Labels") + "\t" + ctrl + "E", _L("Show object labels in 3D scene"), [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); @@ -2725,6 +2751,25 @@ void MainFrame::init_menubar_as_editor() m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->is_view3D_overhang_shown(); }, this); + viewMenu->AppendSeparator(); + append_menu_item( + viewMenu, wxID_ANY, _L("Set 3DConnexion"), _L("Set 3DConnexion mouse"), + [this](wxCommandEvent &) { +#ifdef _WIN32 + if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "true") { +#endif //_WIN32 + Mouse3DController &controller = wxGetApp().plater()->get_mouse3d_controller(); + controller.show_settings_dialog(!controller.is_settings_dialog_shown()); +#ifdef _WIN32 + } +#endif //_WIN32 + }, "", nullptr, [this]() { + Mouse3DController &controller = wxGetApp().plater()->get_mouse3d_controller(); + auto tab_index = (MainFrame::TabPosition) dynamic_cast(wxGetApp().tab_panel())->GetSelection(); + auto is_3d_view = tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview; + return is_3d_view && controller.connected(); + }, + this); /*viewMenu->AppendSeparator(); append_menu_check_item(viewMenu, wxID_ANY, _L("Show &Wireframe") + "\tCtrl+Shift+Enter", _L("Show wireframes in 3D scene"), [this](wxCommandEvent&) { m_plater->toggle_show_wireframe(); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this, @@ -2977,7 +3022,7 @@ void MainFrame::init_menubar_as_editor() NetworkAgent *agent = GUI::wxGetApp().getAgent(); if (agent) agent->track_event("third_cali", js.dump()); } catch (...) {} - wxLaunchDefaultBrowser("https://wiki.qidilab.com/en/qidi-studio/Calibration", wxBROWSER_NEW_WINDOW); + wxLaunchDefaultBrowser("https://wiki.qidi3d.com/en/qidi-studio/Calibration", wxBROWSER_NEW_WINDOW); }, "", nullptr, [this]() {return m_plater->is_view3D_shown();; }, this); @@ -3107,14 +3152,14 @@ void MainFrame::init_menubar_as_editor() NetworkAgent *agent = GUI::wxGetApp().getAgent(); if (agent) agent->track_event("third_cali", js.dump()); } catch (...) {} - wxLaunchDefaultBrowser("https://wiki.qidilab.com/en/qidi-studio/Calibration", wxBROWSER_NEW_WINDOW); + wxLaunchDefaultBrowser("https://wiki.qidi3d.com/en/qidi-studio/Calibration", wxBROWSER_NEW_WINDOW); }, "", nullptr, [this]() { return m_plater->is_view3D_shown(); ; }, this); - + m_menubar->Append(new wxMenu(), L("Window")); std::string window_items[] = { L("Minimize"), @@ -3287,7 +3332,7 @@ void MainFrame::export_config() { ExportConfigsDialog export_configs_dlg(nullptr); export_configs_dlg.ShowModal(); - return; + return; // Generate a cummulative configuration for the selected print, filaments and printer. wxDirDialog dlg(this, _L("Choose a directory"), @@ -3807,7 +3852,7 @@ void MainFrame::load_printer_url() PresetBundle &preset_bundle = *wxGetApp().preset_bundle; if (preset_bundle.printers.get_edited_preset().is_qdt_vendor_preset(&preset_bundle)) return; - + auto cfg = preset_bundle.printers.get_edited_preset().config; wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui"); @@ -3834,9 +3879,9 @@ void MainFrame::RunScript(wxString js) m_webview->RunScript(js); } -void MainFrame::RunScriptLeft(wxString js) +void MainFrame::RunScriptLeft(wxString js) { - if (m_webview != nullptr) + if (m_webview != nullptr) m_webview->RunScriptLeft(js); } diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 19b5d6b..4f67837 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -24,7 +24,6 @@ #include "Auxiliary.hpp" #include "Project.hpp" #include "CalibrationPanel.hpp" -#include "Filament_web.hpp" #include "UnsavedChangesDialog.hpp" #include "Widgets/SideButton.hpp" #include "Widgets/SideMenuPopup.hpp" @@ -35,6 +34,9 @@ #include "calib_dlg.hpp" #include "MultiMachinePage.hpp" +//y +#include "Filament_web.hpp" + #define ENABEL_PRINT_ALL 0 class Notebook; @@ -209,6 +211,7 @@ public: //QDS GUI refactor enum TabPosition { + //y //tpHome = 0, //tp3DEditor = 1, //tpPreview = 2, @@ -269,7 +272,6 @@ public: void update_title(); void set_max_recent_count(int max); - void show_publish_button(bool show); void show_calibration_button(bool show); void update_title_colour_after_set_title(); @@ -366,6 +368,7 @@ public: MaxVolumetricSpeed_Test_Dlg *m_vol_test_dlg{nullptr}; VFA_Test_Dlg * m_vfa_test_dlg{nullptr}; Retraction_Test_Dlg * m_retraction_calib_dlg{nullptr}; + SecondaryCheckDialog* m_confirm_download_plugin_dlg{ nullptr }; // QDS. Replace title bar and menu bar with top bar. QDTTopbar* m_topbar{ nullptr }; @@ -379,6 +382,7 @@ public: ProjectPanel* m_project{ nullptr }; CalibrationPanel* m_calibration{ nullptr }; + //w FilamentPanel* m_filament{nullptr}; WebViewPanel* m_webview { nullptr }; PrinterWebView* m_printer_view{nullptr}; @@ -398,7 +402,6 @@ public: // QDS mutable int m_print_select{ ePrintAll }; mutable int m_slice_select{ eSliceAll }; - Button* m_publish_btn{ nullptr }; SideButton* m_slice_btn{ nullptr }; SideButton* m_slice_option_btn{ nullptr }; SideButton* m_print_btn{ nullptr }; diff --git a/src/slic3r/GUI/MediaFilePanel.cpp b/src/slic3r/GUI/MediaFilePanel.cpp index 78ac8b7..fa8456e 100644 --- a/src/slic3r/GUI/MediaFilePanel.cpp +++ b/src/slic3r/GUI/MediaFilePanel.cpp @@ -9,6 +9,7 @@ #include "Printer/PrinterFileSystem.h" #include "MsgDialog.hpp" #include "Widgets/ProgressDialog.hpp" +#include #include #include @@ -219,10 +220,10 @@ void MediaFilePanel::SetMachineObject(MachineObject* obj) m_lan_ip = obj->dev_ip; m_lan_passwd = obj->get_access_code(); m_dev_ver = obj->get_ota_version(); - m_device_busy = obj->is_camera_busy_off(); + m_device_busy = obj->is_camera_busy_off(); m_sdcard_exist = obj->has_sdcard(); - m_local_support = obj->file_local; - m_remote_support = obj->file_remote; + m_local_proto = obj->file_local; + m_remote_proto = obj->file_remote; m_model_download_support = obj->file_model_download; } else { m_lan_mode = false; @@ -231,13 +232,13 @@ void MediaFilePanel::SetMachineObject(MachineObject* obj) m_dev_ver.clear(); m_sdcard_exist = false; m_device_busy = false; - m_local_support = false; - m_remote_support = false; + m_local_proto = 0; + m_remote_proto = 0; m_model_download_support = false; } Enable(obj && obj->is_connected() && obj->m_push_count > 0); if (machine == m_machine) { - if ((m_waiting_enable && IsEnabled()) || (m_waiting_support && (m_local_support || m_remote_support))) { + if ((m_waiting_enable && IsEnabled()) || (m_waiting_support && (m_local_proto || m_remote_proto))) { auto fs = m_image_grid->GetFileSystem(); if (fs) fs->Retry(); } @@ -440,6 +441,7 @@ void MediaFilePanel::modeChanged(wxCommandEvent& e1) } extern wxString hide_passwd(wxString url, std::vector const &passwords); +extern void refresh_agora_url(char const *device, char const *dev_ver, char const *channel, void *context, void (*callback)(void *context, char const *url)); void MediaFilePanel::fetchUrl(boost::weak_ptr wfs) { @@ -452,7 +454,7 @@ void MediaFilePanel::fetchUrl(boost::weak_ptr wfs) return; } m_waiting_enable = false; - if (!m_local_support && !m_remote_support) { + if (!m_local_proto && !m_remote_proto) { m_waiting_support = true; m_image_grid->SetStatus(m_bmp_failed, _L("Browsing file in SD card is not supported in current firmware. Please update the printer firmware.")); fs->SetUrl("0"); @@ -471,7 +473,7 @@ void MediaFilePanel::fetchUrl(boost::weak_ptr wfs) m_waiting_support = false; NetworkAgent *agent = wxGetApp().getAgent(); std::string agent_version = agent ? agent->get_version() : ""; - if ((m_lan_mode || !m_remote_support) && m_local_support && !m_lan_ip.empty()) { + if ((m_lan_mode || !m_remote_proto) && m_local_proto && !m_lan_ip.empty()) { std::string url = "qidi:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; url += "&device=" + m_machine; url += "&net_ver=" + agent_version; @@ -481,7 +483,7 @@ void MediaFilePanel::fetchUrl(boost::weak_ptr wfs) fs->SetUrl(url); return; } - if (!m_remote_support && m_local_support) { // not support tutk + if (!m_remote_proto && m_local_proto) { // not support tutk m_image_grid->SetStatus(m_bmp_failed, _L("Please enter the IP of printer to connect.")); fs->SetUrl("0"); fs.reset(); @@ -497,12 +499,14 @@ void MediaFilePanel::fetchUrl(boost::weak_ptr wfs) return; } if (agent) { - agent->get_camera_url(m_machine, + std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; + agent->get_camera_url(m_machine + "|" + m_dev_ver + "|" + protocols[m_remote_proto], [this, wfs, m = m_machine, v = agent->get_version(), dv = m_dev_ver](std::string url) { if (boost::algorithm::starts_with(url, "qidi:///")) { url += "&device=" + m; url += "&net_ver=" + v; url += "&dev_ver=" + dv; + url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); url += "&cli_ver=" + std::string(SLIC3R_VERSION); } diff --git a/src/slic3r/GUI/MediaFilePanel.h b/src/slic3r/GUI/MediaFilePanel.h index 2ab2bdd..270542c 100644 --- a/src/slic3r/GUI/MediaFilePanel.h +++ b/src/slic3r/GUI/MediaFilePanel.h @@ -85,8 +85,8 @@ private: std::string m_dev_ver; bool m_lan_mode = false; bool m_sdcard_exist = false; - bool m_local_support = false; - bool m_remote_support = false; + int m_local_proto = false; + int m_remote_proto = false; bool m_model_download_support = false; bool m_device_busy = false; bool m_waiting_enable = false; diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index b31c892..dab55b7 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -41,13 +41,14 @@ namespace Slic3r { namespace GUI { //1.9.5 static int SecondsSinceLastInput(); -MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos, const wxSize &size) +MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size) : wxPanel(parent, wxID_ANY, pos, size) , m_media_ctrl(media_ctrl) { SetLabel("MediaPlayCtrl"); SetBackgroundColour(*wxWHITE); m_media_ctrl->Bind(wxEVT_MEDIA_STATECHANGED, &MediaPlayCtrl::onStateChanged, this); + m_media_ctrl->SetIdleImage(from_u8(resources_dir() + "/images/live_stream_default.png")); m_button_play = new Button(this, "", "media_play", wxBORDER_NONE); m_button_play->SetCanFocus(false); @@ -92,7 +93,7 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const w m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); }); m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_media_ctrl->Play(); }); m_label_status->Bind(wxEVT_LEFT_UP, [this](auto &e) { - auto url = wxString::Format(L"https://wiki.qidilab.com/%s/software/qidi-studio/faq/live-view", L"en"); + auto url = wxString::Format(L"https://wiki.qidi3d.com/%s/software/qidi-studio/faq/live-view", L"en"); wxLaunchDefaultBrowser(url); }); @@ -156,7 +157,7 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj) m_dev_ver = obj->get_ota_version(); m_lan_mode = obj->is_lan_mode_printer(); m_lan_proto = obj->liveview_local; - m_remote_support = obj->liveview_remote; + m_remote_proto = obj->liveview_remote; m_lan_ip = obj->dev_ip; m_lan_passwd = obj->get_access_code(); m_device_busy = obj->is_camera_busy_off(); @@ -169,21 +170,13 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj) m_lan_passwd.clear(); m_dev_ver.clear(); m_tutk_state.clear(); - m_remote_support = false; + m_remote_proto = 0; m_device_busy = false; } Enable(obj && obj->is_connected() && obj->m_push_count > 0); if (machine == m_machine) { if (m_last_state == MEDIASTATE_IDLE && IsEnabled()) Play(); - else if (m_last_state == MEDIASTATE_IDLE && m_tutk_state == "disable" - && m_last_user_play + wxTimeSpan::Seconds(3) < wxDateTime::Now()) { - // resend ttcode to printer - if (auto agent = wxGetApp().getAgent()) - agent->get_camera_url(machine, [](auto) {}); - m_last_user_play = wxDateTime::Now(); - } - //1.9.7.52 if (m_last_state == wxMediaState::wxMEDIASTATE_PLAYING) { auto now = std::chrono::system_clock::now(); if (m_play_timer <= now) { @@ -261,6 +254,18 @@ wxString hide_passwd(wxString url, std::vector const &passwords) return url; } +void refresh_agora_url(char const* device, char const* dev_ver, char const* channel, void* context, void (*callback)(void* context, char const* url)) +{ + std::string device2 =device; + device2 += "|"; + device2 += dev_ver; + device2 += "|\"agora\"|"; + device2 += channel; + wxGetApp().getAgent()->get_camera_url(device2, [context, callback](std::string url) { + callback(context, url.c_str()); + }); +} + void MediaPlayCtrl::Play() { if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry) @@ -289,11 +294,12 @@ void MediaPlayCtrl::Play() return; } - m_button_play->SetIcon("media_stop"); + m_play_timer = std::chrono::system_clock::now(); + NetworkAgent *agent = wxGetApp().getAgent(); std::string agent_version = agent ? agent->get_version() : ""; - if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_support) && !m_disable_lan && !m_lan_ip.empty()) { - m_disable_lan = m_remote_support && !m_lan_mode; // try remote next time + if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { + m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time std::string url; if (m_lan_proto == MachineObject::LVL_Local) url = "qidi:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; @@ -309,19 +315,20 @@ void MediaPlayCtrl::Play() BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(hide_id_middle_string(url, url.find(m_lan_ip), m_lan_ip.length()), {m_lan_passwd}); m_url = url; load(); + m_button_play->SetIcon("media_stop"); return; } // m_lan_mode && m_lan_proto > LVL_Disable (use local tunnel) // m_lan_mode && m_lan_proto == LVL_Disable (*) // m_lan_mode && m_lan_proto == LVL_None (x) - // !m_lan_mode && m_remote_support (go on) - // !m_lan_mode && !m_remote_support && m_lan_proto > LVL_None (use local tunnel) - // !m_lan_mode && !m_remote_support && m_lan_proto == LVL_Disable (*) - // !m_lan_mode && !m_remote_support && m_lan_proto == LVL_None (x) + // !m_lan_mode && m_remote_proto (go on) + // !m_lan_mode && !m_remote_proto && m_lan_proto > LVL_None (use local tunnel) + // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*) + // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x) - if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_support)) { - Stop(m_lan_proto == MachineObject::LVL_None + if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) { + Stop(m_lan_proto == MachineObject::LVL_None ? _L("Problem occured. Please update the printer firmware and try again.") : _L("LAN Only Liveview is off. Please turn on the liveview on printer screen.")); return; @@ -333,7 +340,7 @@ void MediaPlayCtrl::Play() //1.9.5 m_button_play->SetIcon("media_stop"); - if (!m_remote_support) { // not support tutk + if (!m_remote_proto) { // not support tutk m_failed_code = -1; m_url = "qidi:///local/"; Stop(_L("Please enter the IP of printer to connect.")); @@ -342,19 +349,20 @@ void MediaPlayCtrl::Play() m_label_stat->SetLabel({}); SetStatus(_L("Initializing...")); - m_play_timer = std::chrono::system_clock::now(); if (agent) { - agent->get_camera_url(m_machine, + std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; + agent->get_camera_url(m_machine + "|" + m_dev_ver + "|" + protocols[m_remote_proto], [this, m = m_machine, v = agent_version, dv = m_dev_ver](std::string url) { if (boost::algorithm::starts_with(url, "qidi:///")) { url += "&device=" + into_u8(m); url += "&net_ver=" + v; url += "&dev_ver=" + dv; + url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); url += "&cli_ver=" + std::string(SLIC3R_VERSION); } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url, + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url, {"?uid=", "authkey=", "passwd=", "license=", "token="}); CallAfter([this, m, url] { if (m != m_machine) { @@ -364,7 +372,7 @@ void MediaPlayCtrl::Play() if (m_last_state == MEDIASTATE_IDLE) { if (url.empty() || !boost::algorithm::starts_with(url, "qidi:///")) { m_failed_code = 3; - Stop(_L("Connection Failed. Please check the network and try again")); + Stop(_L("Connection Failed. Please check the network and try again"), from_u8(url)); } else { m_url = url; load(); @@ -379,7 +387,7 @@ void MediaPlayCtrl::Play() void start_ping_test(); -void MediaPlayCtrl::Stop(wxString const &msg) +void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2) { int last_state = m_last_state; @@ -422,9 +430,9 @@ void MediaPlayCtrl::Stop(wxString const &msg) } auto tunnel = m_url.empty() ? "" : into_u8(wxURI(m_url).GetPath()).substr(1); - if (auto n = tunnel.find_first_of('/_'); n != std::string::npos) + if (auto n = tunnel.find_first_of("/_"); n != std::string::npos) tunnel = tunnel.substr(0, n); - if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0 + if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0 && m_last_failed_codes.find(m_failed_code) == m_last_failed_codes.end() && (m_user_triggered || m_failed_retry > 3)) { json j; @@ -442,6 +450,8 @@ void MediaPlayCtrl::Stop(wxString const &msg) j["tutk_state"] = m_tutk_state; } j["msg"] = into_u8(msg); + if (!msg2.IsEmpty()) + j["msg2"] = into_u8(msg2); NetworkAgent *agent = wxGetApp().getAgent(); if (agent) agent->track_event("start_liveview", j.dump()); @@ -560,7 +570,7 @@ void MediaPlayCtrl::ToggleStream() wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1"); if (res == wxID_CANCEL) return; } - if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_support) && !m_disable_lan && !m_lan_ip.empty()) { + if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { std::string url; if (m_lan_proto == MachineObject::LVL_Local) url = "qidi:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; @@ -586,10 +596,11 @@ void MediaPlayCtrl::ToggleStream() url += "&device=" + m; url += "&net_ver=" + v; url += "&dev_ver=" + dv; + url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); url += "&cli_ver=" + std::string(SLIC3R_VERSION); } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url, + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url, {"?uid=", "authkey=", "passwd=", "license=", "token="}); CallAfter([this, m, url] { if (m != m_machine) return; @@ -661,7 +672,7 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event) j["result"] = "success"; j["code"] = 0; auto tunnel = into_u8(wxURI(m_url).GetPath()).substr(1); - if (auto n = tunnel.find_first_of('/_'); n != std::string::npos) + if (auto n = tunnel.find_first_of("/_"); n != std::string::npos) tunnel = tunnel.substr(0, n); j["tunnel"] = tunnel; if (tunnel == "tutk") { @@ -695,6 +706,7 @@ void MediaPlayCtrl::SetStatus(wxString const &msg2, bool hyperlink) int state2 = m_last_state >= MEDIASTATE_IDLE ? m_last_state - MEDIASTATE_IDLE : m_last_state + MEDIASTATE_IDLE - MEDIASTATE_IDLE; msg += wxString::Format(" [%d:%d]", state2, m_failed_code); + msg += wxDateTime::Now().Format(_T(" <%m-%d %H:%M>")); } BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetStatus: " << msg.ToUTF8().data() << " tutk_state: " << m_tutk_state; #ifdef __WXMSW__ @@ -771,7 +783,9 @@ void MediaPlayCtrl::media_proc() break; } else if (url == "") { + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play"; m_media_ctrl->Play(); + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play"; } else { BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load"; @@ -901,25 +915,19 @@ static int SecondsSinceLastInput() }} -void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags) +void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height) { -#ifdef __WXMAC__ - wxWindow::DoSetSize(x, y, width, height, sizeFlags); -#else - wxMediaCtrl::DoSetSize(x, y, width, height, sizeFlags); -#endif - if (sizeFlags & wxSIZE_USE_EXISTING) return; - wxSize size = m_video_size; + wxSize size = videoSize; + if (!size.IsFullySpecified()) size = {16, 9}; int maxHeight = (width * size.GetHeight() + size.GetHeight() - 1) / size.GetWidth(); - if (maxHeight != GetMaxHeight()) { - // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2::DoSetSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight; - SetMaxSize({-1, maxHeight}); - CallAfter([this] { - if (auto p = GetParent()) { + if (maxHeight != ctrl->GetMaxHeight()) { + // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl_OnSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight; + ctrl->SetMaxSize({-1, maxHeight}); + ctrl->CallAfter([ctrl] { + if (auto p = ctrl->GetParent()) { p->Layout(); p->Refresh(); } }); } } - diff --git a/src/slic3r/GUI/MediaPlayCtrl.h b/src/slic3r/GUI/MediaPlayCtrl.h index b431ef4..8a338a4 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.h +++ b/src/slic3r/GUI/MediaPlayCtrl.h @@ -8,7 +8,14 @@ #ifndef MediaPlayCtrl_h #define MediaPlayCtrl_h +#define USE_WX_MEDIA_CTRL_2 0 + +#if USE_WX_MEDIA_CTRL_2 #include "wxMediaCtrl2.h" +#define wxMediaCtrl3 wxMediaCtrl2 +#else +#include "wxMediaCtrl3.h" +#endif #include @@ -31,7 +38,7 @@ namespace GUI { class MediaPlayCtrl : public wxPanel { public: - MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize); + MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize); ~MediaPlayCtrl(); @@ -50,7 +57,7 @@ protected: void Play(); - void Stop(wxString const &msg = {}); + void Stop(wxString const &msg = {}, wxString const &msg2 = {}); void TogglePlay(); @@ -74,7 +81,7 @@ private: // static constexpr wxMediaState MEDIASTATE_LOADING = (wxMediaState) 5; // static constexpr wxMediaState MEDIASTATE_BUFFERING = (wxMediaState) 6; - wxMediaCtrl2 * m_media_ctrl; + wxMediaCtrl3 * m_media_ctrl; wxMediaState m_last_state = MEDIASTATE_IDLE; std::string m_machine; int m_lan_proto = 0; @@ -85,7 +92,7 @@ private: std::string m_tutk_state; bool m_camera_exists = false; bool m_lan_mode = false; - bool m_remote_support = false; + int m_remote_proto = 0; bool m_device_busy = false; bool m_disable_lan = false; wxString m_url; diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 236abd0..80b5e17 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -7,7 +7,7 @@ #include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" -#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/GLModel.hpp" #include diff --git a/src/slic3r/GUI/Monitor.cpp b/src/slic3r/GUI/Monitor.cpp index d5d6acc..9d438f8 100644 --- a/src/slic3r/GUI/Monitor.cpp +++ b/src/slic3r/GUI/Monitor.cpp @@ -284,21 +284,11 @@ void MonitorPanel::select_machine(std::string machine_sn) wxQueueEvent(this, event); } -void MonitorPanel::on_update_all(wxMouseEvent &event) -{ - if (update_flag) { - update_all(); - Layout(); - Refresh(); - } -} - void MonitorPanel::on_timer(wxTimerEvent& event) { if (update_flag) { update_all(); Layout(); - Refresh(); } } @@ -314,15 +304,18 @@ void MonitorPanel::on_update_all(wxMouseEvent &event) if (!dev->set_selected_machine(event.GetString().ToStdString())) return; + m_status_info_panel->reset_ams_group_show_flag(); + set_default(); update_all(); + m_status_info_panel->last_cali_version.reset(); + MachineObject *obj_ = dev->get_selected_machine(); if (obj_) GUI::wxGetApp().sidebar().load_ams_list(obj_->dev_id, obj_); Layout(); - Refresh(); } void MonitorPanel::on_printer_clicked(wxMouseEvent &event) @@ -348,8 +341,8 @@ void MonitorPanel::on_printer_clicked(wxMouseEvent &event) void MonitorPanel::on_size(wxSizeEvent &event) { - Layout(); - Refresh(); + // Layout(); + // Refresh(); } void MonitorPanel::update_all() @@ -481,6 +474,13 @@ bool MonitorPanel::Show(bool show) stop_update(); m_refresh_timer->Stop(); } + + if (obj && !obj->dev_id.empty()) { + select_machine(obj->dev_id); + } else { + select_machine(""); + } + return wxPanel::Show(show); } diff --git a/src/slic3r/GUI/Monitor.hpp b/src/slic3r/GUI/Monitor.hpp index 8da56dd..935ee33 100644 --- a/src/slic3r/GUI/Monitor.hpp +++ b/src/slic3r/GUI/Monitor.hpp @@ -132,7 +132,6 @@ public: StatusPanel* get_status_panel() {return m_status_info_panel;}; void select_machine(std::string machine_sn); - void on_update_all(wxMouseEvent &event); void on_timer(wxTimerEvent& event); void on_select_printer(wxCommandEvent& event); void on_printer_clicked(wxMouseEvent &event); diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index e2f3e90..ded765e 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -303,10 +303,12 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin page_size = wxSize(68 * em, page_height); } else { +#ifdef __WINDOWS__ Label* wrapped_text = new Label(html, msg); wrapped_text->Wrap(68 * em); msg = wrapped_text->GetLabel(); wrapped_text->Destroy(); +#endif //__WINDOWS__ wxClientDC dc(parent); wxSize msg_sz = dc.GetMultiLineTextExtent(msg); @@ -381,25 +383,27 @@ RichMessageDialog::RichMessageDialog(wxWindow* parent, { add_msg_content(this, content_sizer, message); - m_checkBox = new wxCheckBox(this, wxID_ANY, m_checkBoxText); - wxGetApp().UpdateDarkUI(m_checkBox); - m_checkBox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_checkBoxValue = m_checkBox->GetValue(); }); - - btn_sizer->Insert(0, m_checkBox, wxALIGN_CENTER_VERTICAL); - finalize(); } int RichMessageDialog::ShowModal() { - if (m_checkBoxText.IsEmpty()) - m_checkBox->Hide(); - else - m_checkBox->SetLabelText(m_checkBoxText); + if (!m_checkBoxText.IsEmpty()) { + show_dsa_button(m_checkBoxText); + m_checkbox_dsa->SetValue(m_checkBoxValue); + } Layout(); return wxDialog::ShowModal(); } + +bool RichMessageDialog::IsCheckBoxChecked() const +{ + if (m_checkbox_dsa) + return m_checkbox_dsa->GetValue(); + + return m_checkBoxValue; +} #endif // InfoDialog @@ -529,7 +533,7 @@ wxBoxSizer *Newer3mfVersionDialog::get_msg_sizer() if (file_version_newer) { text1 = new wxStaticText(this, wxID_ANY, _L("The 3mf file version is in Beta and it is newer than the current QIDI Studio version.")); wxStaticText * text2 = new wxStaticText(this, wxID_ANY, _L("If you would like to try QIDI Studio Beta, you may click to")); - wxHyperlinkCtrl *github_link = new wxHyperlinkCtrl(this, wxID_ANY, _L("Download Beta Version"), "https://github.com/qidilab/QIDIStudio/releases"); + wxHyperlinkCtrl *github_link = new wxHyperlinkCtrl(this, wxID_ANY, _L("Download Beta Version"), "https://github.com/qidi3d/QIDIStudio/releases"); horizontal_sizer->Add(text2, 0, wxEXPAND, 0); horizontal_sizer->Add(github_link, 0, wxEXPAND | wxLEFT, 5); @@ -610,6 +614,109 @@ wxBoxSizer *Newer3mfVersionDialog::get_btn_sizer() return horizontal_sizer; } +NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent) + : DPIDialog(parent ? parent : nullptr, wxID_ANY, _L("Server Exception"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + 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)); + + wxBoxSizer* 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)); + + wxBoxSizer* sizer_bacis_text = new wxBoxSizer(wxVERTICAL); + + m_text_basic = new Label(this, _L("The server is unable to respond. Please click the link below to check the server status.")); + m_text_basic->SetForegroundColour(0x323A3C); + m_text_basic->SetMinSize(wxSize(FromDIP(470), -1)); + m_text_basic->SetMaxSize(wxSize(FromDIP(470), -1)); + m_text_basic->Wrap(FromDIP(470)); + m_text_basic->SetFont(::Label::Body_14); + sizer_bacis_text->Add(m_text_basic, 0, wxALL, 0); + + + wxBoxSizer* sizer_link = new wxBoxSizer(wxVERTICAL); + + m_link_server_state = new wxHyperlinkCtrl(this, wxID_ANY, _L("Check the status of current system services"), ""); + m_link_server_state->SetFont(::Label::Body_13); + m_link_server_state->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {wxGetApp().link_to_network_check(); }); + m_link_server_state->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); }); + m_link_server_state->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); + + sizer_link->Add(m_link_server_state, 0, wxALL, 0); + + + wxBoxSizer* sizer_help = new wxBoxSizer(wxVERTICAL); + + m_text_proposal = new Label(this, _L("If the server is in a fault state, you can temporarily use offline printing or local network printing.")); + m_text_proposal->SetMinSize(wxSize(FromDIP(470), -1)); + m_text_proposal->SetMaxSize(wxSize(FromDIP(470), -1)); + m_text_proposal->Wrap(FromDIP(470)); + m_text_proposal->SetFont(::Label::Body_14); + m_text_proposal->SetForegroundColour(0x323A3C); + + m_text_wiki = new wxHyperlinkCtrl(this, wxID_ANY, _L("How to use LAN only mode"), ""); + m_text_wiki->SetFont(::Label::Body_13); + m_text_wiki->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {wxGetApp().link_to_lan_only_wiki(); }); + m_text_wiki->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); }); + m_text_wiki->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); + + sizer_help->Add(m_text_proposal, 0, wxEXPAND, 0); + sizer_help->Add(m_text_wiki, 0, wxALL, 0); + + wxBoxSizer* sizer_button = new wxBoxSizer(wxHORIZONTAL); + + /*dont show again*/ + auto checkbox = new ::CheckBox(this); + checkbox->SetValue(false); + + + auto checkbox_title = new Label(this, _L("Don't show this dialog again")); + checkbox_title->SetForegroundColour(0x323A3C); + checkbox_title->SetFont(::Label::Body_14); + checkbox_title->Wrap(-1); + + checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox](wxCommandEvent &e) { + m_show_again = checkbox->GetValue(); + e.Skip(); + }); + + auto bt_enable = StateColor(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + m_button_confirm = new Button(this, _L("Confirm")); + m_button_confirm->SetBackgroundColor(bt_enable); + m_button_confirm->SetBorderColor(bt_enable); + m_button_confirm->SetTextColor(StateColor::darkModeColorFor("#FFFFFE")); + m_button_confirm->SetMinSize(wxSize(FromDIP(68), FromDIP(23))); + m_button_confirm->SetMinSize(wxSize(FromDIP(68), FromDIP(23))); + m_button_confirm->SetCornerRadius(12); + m_button_confirm->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {EndModal(wxCLOSE);}); + + sizer_button->Add(checkbox, 0, wxALL, 5); + sizer_button->Add(checkbox_title, 0, wxALL, 5); + sizer_button->Add(0, 0, 1, wxEXPAND, 5); + sizer_button->Add(m_button_confirm, 0, wxALL, 5); + + sizer_main->Add(m_line_top, 0, wxEXPAND, 0); + sizer_main->Add(0, 0, 0, wxTOP, 20); + sizer_main->Add(sizer_bacis_text, 0, wxEXPAND | wxLEFT | wxRIGHT, 15); + sizer_main->Add(0, 0, 0, wxTOP, 6); + sizer_main->Add(sizer_link, 0, wxLEFT | wxRIGHT, 15); + sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, FromDIP(20)); + sizer_main->Add(sizer_help, 1, wxLEFT | wxRIGHT, 15); + sizer_main->Add(0, 0, 0, wxEXPAND | wxTOP, FromDIP(20)); + sizer_main->Add(sizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT, 15); + sizer_main->Add(0, 0, 0, wxTOP, 18); + + SetSizer(sizer_main); + Layout(); + sizer_main->Fit(this); + Centre(wxBOTH); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index 0156b61..a2158bc 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -191,7 +191,7 @@ public: } wxString GetCheckBoxText() const { return m_checkBoxText; } - bool IsCheckBoxChecked() const { return m_checkBoxValue; } + bool IsCheckBoxChecked() const; // This part o fcode isported from the "wx\msgdlg.h" using wxMD = wxMessageDialogBase; @@ -410,6 +410,25 @@ private: wxStaticText *m_msg_text = nullptr; }; + +class NetworkErrorDialog : public DPIDialog +{ +public: + NetworkErrorDialog(wxWindow* parent); + ~NetworkErrorDialog() {}; + virtual void on_dpi_changed(const wxRect& suggested_rect) {}; + +private: + Label* m_text_basic; + wxHyperlinkCtrl* m_link_server_state; + Label* m_text_proposal; + wxHyperlinkCtrl* m_text_wiki; + Button * m_button_confirm; + +public: + bool m_show_again{false}; +}; + } } diff --git a/src/slic3r/GUI/NetworkTestDialog.cpp b/src/slic3r/GUI/NetworkTestDialog.cpp index 69d24d3..c7c9829 100644 --- a/src/slic3r/GUI/NetworkTestDialog.cpp +++ b/src/slic3r/GUI/NetworkTestDialog.cpp @@ -140,11 +140,11 @@ wxBoxSizer* NetworkTestDialog::create_content_sizer(wxWindow* parent) grid_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); StateColor btn_bg(std::pair(wxColour(95, 82, 253), StateColor::Pressed),std::pair(wxColour(129, 150, 255), StateColor::Hovered), std::pair(wxColour(255,255,255), StateColor::Enabled)); - btn_link = new Button(this, _L("Test QIDILab")); + btn_link = new Button(this, _L("Test QIDITech")); btn_link->SetBackgroundColor(btn_bg); grid_sizer->Add(btn_link, 0, wxEXPAND | wxALL, 5); - text_link_title = new wxStaticText(this, wxID_ANY, _L("Test QIDILab:"), wxDefaultPosition, wxDefaultSize, 0); + text_link_title = new wxStaticText(this, wxID_ANY, _L("Test QIDITech:"), wxDefaultPosition, wxDefaultSize, 0); text_link_title->Wrap(-1); grid_sizer->Add(text_link_title, 0, wxALIGN_RIGHT | wxALL, 5); @@ -245,7 +245,7 @@ wxBoxSizer* NetworkTestDialog::create_content_sizer(wxWindow* parent) sizer->Add(grid_sizer, 1, wxEXPAND, 5); btn_link->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { - start_test_qidilab_thread(); + start_test_qiditech_thread(); }); btn_bing->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { @@ -294,7 +294,7 @@ NetworkTestDialog::~NetworkTestDialog() void NetworkTestDialog::init_bind() { Bind(EVT_UPDATE_RESULT, [this](wxCommandEvent& evt) { - if (evt.GetInt() == TEST_QIDILAB_JOB) { + if (evt.GetInt() == TEST_QIDITECH_JOB) { text_link_val->SetLabelText(evt.GetString()); } else if (evt.GetInt() == TEST_BING_JOB) { text_bing_val->SetLabelText(evt.GetString()); @@ -351,7 +351,7 @@ wxString NetworkTestDialog::get_dns_info() void NetworkTestDialog::start_all_job() { - start_test_qidilab_thread(); + start_test_qiditech_thread(); start_test_bing_thread(); start_test_iot_thread(); @@ -368,7 +368,7 @@ void NetworkTestDialog::start_all_job_sequence() update_status(-1, "start_test_sequence"); start_test_bing(); if (m_closing) return; - start_test_qidilab(); + start_test_qiditech(); if (m_closing) return; start_test_oss(); if (m_closing) return; @@ -417,10 +417,10 @@ void NetworkTestDialog::start_test_bing() m_in_testing[TEST_BING_JOB] = false; } -void NetworkTestDialog::start_test_qidilab() +void NetworkTestDialog::start_test_qiditech() { - m_in_testing[TEST_QIDILAB_JOB] = true; - update_status(TEST_QIDILAB_JOB, "test qidilab start..."); + m_in_testing[TEST_QIDITECH_JOB] = true; + update_status(TEST_QIDITECH_JOB, "test qiditech start..."); std::string platform = "windows"; @@ -441,7 +441,7 @@ void NetworkTestDialog::start_test_qidilab() AppConfig* app_config = wxGetApp().app_config; std::string url = wxGetApp().get_http_url(app_config->get_country_code()) + query_params; Slic3r::Http http = Slic3r::Http::get(url); - update_status(-1, "[test_qidilab]: url=" + url); + update_status(-1, "[test_qiditech]: url=" + url); int result = -1; http.header("accept", "application/json") .timeout_max(10) @@ -456,18 +456,18 @@ void NetworkTestDialog::start_test_qidilab() } }) .on_ip_resolve([this](std::string ip) { - wxString ip_report = wxString::Format("test qidilab ip resolved = %s", ip); - update_status(TEST_QIDILAB_JOB, ip_report); + wxString ip_report = wxString::Format("test qiditech ip resolved = %s", ip); + update_status(TEST_QIDITECH_JOB, ip_report); }) .on_error([this](std::string body, std::string error, unsigned int status) { wxString info = wxString::Format("status=%u, body=%s, error=%s", status, body, error); - this->update_status(TEST_QIDILAB_JOB, "test qidilab failed"); + this->update_status(TEST_QIDITECH_JOB, "test qiditech failed"); this->update_status(-1, info); }).perform_sync(); if (result == 0) { - update_status(TEST_QIDILAB_JOB, "test qidilab ok"); + update_status(TEST_QIDITECH_JOB, "test qiditech ok"); } - m_in_testing[TEST_QIDILAB_JOB] = false; + m_in_testing[TEST_QIDITECH_JOB] = false; } void NetworkTestDialog::start_test_iot() @@ -501,12 +501,12 @@ void NetworkTestDialog::start_test_oss() m_in_testing[TEST_OSS_JOB] = true; update_status(TEST_OSS_JOB, "test storage start..."); - std::string url = "http://upload-file.qidilab.com"; + std::string url = "http://upload-file.qiditech.com"; AppConfig* config = wxGetApp().app_config; if (config) { if (config->get_country_code() == "CN") - url = "http://upload-file.qidilab.cn"; + url = "http://upload-file.qiditech.cn"; } Slic3r::Http http = Slic3r::Http::get(url); @@ -548,12 +548,12 @@ void NetworkTestDialog::start_test_oss_upgrade() m_in_testing[TEST_OSS_UPGRADE_JOB] = true; update_status(TEST_OSS_UPGRADE_JOB, "test storage upgrade start..."); - std::string url = "http://upgrade-file.qidilab.com"; + std::string url = "http://upgrade-file.qiditech.com"; AppConfig* config = wxGetApp().app_config; if (config) { if (config->get_country_code() == "CN") - url = "http://upgrade-file.qidilab.cn"; + url = "http://upgrade-file.qiditech.cn"; } Slic3r::Http http = Slic3r::Http::get(url); @@ -891,11 +891,11 @@ void NetworkTestDialog::start_test_bing_thread() }); } -void NetworkTestDialog::start_test_qidilab_thread() +void NetworkTestDialog::start_test_qiditech_thread() { - if (m_in_testing[TEST_QIDILAB_JOB]) return; - test_job[TEST_QIDILAB_JOB] = new boost::thread([this] { - start_test_qidilab(); + if (m_in_testing[TEST_QIDITECH_JOB]) return; + test_job[TEST_QIDITECH_JOB] = new boost::thread([this] { + start_test_qiditech(); }); } diff --git a/src/slic3r/GUI/NetworkTestDialog.hpp b/src/slic3r/GUI/NetworkTestDialog.hpp index 229cdfc..d863a99 100644 --- a/src/slic3r/GUI/NetworkTestDialog.hpp +++ b/src/slic3r/GUI/NetworkTestDialog.hpp @@ -35,7 +35,7 @@ namespace GUI { enum TestJob { TEST_BING_JOB = 0, - TEST_QIDILAB_JOB = 1, + TEST_QIDITECH_JOB = 1, TEST_IOT_JOB = 2, TEST_OSS_JOB = 3, TEST_OSS_UPGRADE_JOB = 4, @@ -116,7 +116,7 @@ public: void start_all_job(); void start_all_job_sequence(); void start_test_bing_thread(); - void start_test_qidilab_thread(); + void start_test_qiditech_thread(); void start_test_iot_thread(); void start_test_oss_thread(); void start_test_oss_upgrade_thread(); @@ -126,7 +126,7 @@ public: void start_test_plugin_download_thread(); void start_test_bing(); - void start_test_qidilab(); + void start_test_qiditech(); void start_test_iot(); void start_test_oss(); void start_test_oss_upgrade(); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index af99dba..777b842 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -147,8 +147,6 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, , m_evt_handler (evt_handler) , m_notification_start (GLCanvas3D::timestamp_now()) { - m_is_dark = wxGetApp().plater()->get_current_canvas3D()->get_dark_mode_status(); - // y96 m_ErrorColor = ImVec4(0.9, 0.36, 0.36, 1); m_WarnColor = ImVec4(0.99, 0.69, 0.455, 1); @@ -159,17 +157,30 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, m_WindowBkgColor = ImVec4(1, 1, 1, 1); m_TextColor = ImVec4(.2f, .2f, .2f, 1.0f); m_HyperTextColor = ImVec4(0.3, 0.18, 0.91, 1); +} - m_WindowRadius = 4.0f * wxGetApp().plater()->get_current_canvas3D()->get_scale(); +// We cannot call plater()->get_current_canvas3D() from constructor, so we do it here +void NotificationManager::PopNotification::ensure_ui_inited() +{ + if (!m_is_dark_inited) { + m_is_dark = wxGetApp().plater()->get_current_canvas3D()->get_dark_mode_status(); + m_is_dark_inited = true; + } + if (!m_WindowRadius_inited) { + m_WindowRadius = 4.0f * wxGetApp().plater()->get_current_canvas3D()->get_scale(); + m_WindowRadius_inited = true; + } } void NotificationManager::PopNotification::on_change_color_mode(bool is_dark) { + m_is_dark_inited = true; m_is_dark = is_dark; } void NotificationManager::PopNotification::use_qdt_theme() { + ensure_ui_inited(); ImGuiStyle &OldStyle = ImGui::GetStyle(); m_DefaultTheme.mWindowPadding = OldStyle.WindowPadding; @@ -862,6 +873,7 @@ void NotificationManager::PopNotification::qdt_render_block_notif_left_sign(ImGu void NotificationManager::PopNotification::qdt_render_left_sign(ImGuiWrapper &imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { + ensure_ui_inited(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); ImVec2 round_rect_pos = ImVec2(win_pos_x - win_size_x + ImGui::GetStyle().WindowBorderSize, win_pos_y + ImGui::GetStyle().WindowBorderSize); ImVec2 round_rect_size = ImVec2(m_WindowRadius * 2, win_size_y - 2 * ImGui::GetStyle().WindowBorderSize); @@ -893,6 +905,7 @@ void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) } void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) { + ensure_ui_inited(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); @@ -1076,6 +1089,7 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& void NotificationManager::ExportFinishedNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { + ensure_ui_inited(); PopNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); if (m_to_removable && !m_eject_pending) render_eject_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); @@ -2710,6 +2724,25 @@ void NotificationManager::qdt_close_gcode_overlap_notification() if (notification->get_type() == NotificationType::QDTGcodeOverlap) { notification->close(); } } +void NotificationManager::qdt_show_bed_filament_incompatible_notification(const std::string& text) +{ + auto callback = [](wxEvtHandler*) { + std::string language = wxGetApp().app_config->get("language"); + wxString region = L"en"; + if (language.find("zh") == 0) + region = L"zh"; + const wxString bed_filament_compatibility_wiki = wxString::Format(L"https://wiki.qiditech.com/%s/PLA/PETG-with-qidi-bool-plate-supertack", region); + wxGetApp().open_browser_with_warning_dialog(bed_filament_compatibility_wiki); + return false; + }; + push_notification_data({ NotificationType::QDTBedFilamentIncompatible,NotificationLevel::ErrorNotificationLevel,0,_u8L("Error:") + "\n" + text,_u8L("Click Wiki for help."),callback }, 0); +} + +void NotificationManager::qdt_close_bed_filament_incompatible_notification() +{ + close_notification_of_type(NotificationType::QDTBedFilamentIncompatible); +} + void NotificationManager::qdt_show_sole_text_notification(NotificationType sType, const std::string &text, bool bOverride, int level, bool autohide) { NotificationLevel nlevel; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index a5071f5..f547801 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -145,6 +145,7 @@ enum class NotificationType QDTPreviewOnlyMode, QDTPrinterConfigUpdateAvailable, QDTUserPresetExceedLimit, + QDTBedFilamentIncompatible }; class NotificationManager @@ -337,6 +338,10 @@ public: void qdt_show_gcode_overlap_notification(); void qdt_close_gcode_overlap_notification(); + //QDS--bed filament match + void qdt_show_bed_filament_incompatible_notification(const std::string& text); + void qdt_close_bed_filament_incompatible_notification(); + //QDS--sole notification void qdt_show_sole_text_notification(NotificationType sType,const std::string &text, bool bOverride, int level, bool autohide); void qdt_chose_sole_text_notification(NotificationType sType); @@ -469,9 +474,9 @@ private: virtual bool push_background_color(); // used this function instead of reading directly m_data.duration. Some notifications might need to return changing value. virtual int get_duration() { return m_data.duration; } - + void ensure_ui_inited(); bool m_is_dark = false; - + bool m_is_dark_inited = false; const NotificationData m_data; // For reusing ImGUI windows. NotificationIDProvider &m_id_provider; @@ -500,7 +505,7 @@ private: ImVec4 m_CurrentColor; float m_WindowRadius; - + bool m_WindowRadius_inited = false; void use_qdt_theme(); void restore_default_theme(); @@ -903,7 +908,7 @@ private: _u8L("The number of user presets cached in the cloud has exceeded the upper limit, newly created user presets can only be used locally."), _u8L("Wiki"), [](wxEvtHandler* evnthndlr) { - wxLaunchDefaultBrowser("https://wiki.qidilab.com/en/software/qidi-studio/3rd-party-printer-profile#cloud-user-presets-limit"); + wxLaunchDefaultBrowser("https://wiki.qiditech.com/en/software/qidi-studio/3rd-party-printer-profile#cloud-user-presets-limit"); return false; }}, diff --git a/src/slic3r/GUI/ObjColorDialog.cpp b/src/slic3r/GUI/ObjColorDialog.cpp index 7ec42f8..d1435e7 100644 --- a/src/slic3r/GUI/ObjColorDialog.cpp +++ b/src/slic3r/GUI/ObjColorDialog.cpp @@ -376,7 +376,7 @@ bool ObjColorPanel::is_ok() { void ObjColorPanel::update_filament_ids() { if (m_is_add_filament) { - for (auto c:m_new_add_colors) { + for (auto c : m_new_add_final_colors) { /*auto evt = new ColorEvent(EVT_ADD_CUSTOM_FILAMENT, c); wxQueueEvent(wxGetApp().plater(), evt);*/ wxGetApp().sidebar().add_custom_filament(c); @@ -452,7 +452,7 @@ wxBoxSizer *ObjColorPanel::create_reset_btn_sizer(wxWindow *parent) StateColor calc_btn_text(std::pair(wxColour(255, 255, 254), StateColor::Normal)); // create btn m_quick_reset_btn = new Button(parent, _L("Reset")); - m_quick_add_btn->SetToolTip(_L("Reset mapped extruders.")); + m_quick_reset_btn->SetToolTip(_L("Reset mapped extruders.")); auto cur_btn = m_quick_reset_btn; cur_btn->SetFont(Label::Body_13); cur_btn->SetMinSize(wxSize(FromDIP(60), FromDIP(20))); @@ -521,7 +521,13 @@ ComboBox *ObjColorPanel::CreateEditorCtrl(wxWindow *parent, int id) // wxRect la c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { auto *com_box = static_cast(evt.GetEventObject()); int i = atoi(com_box->GetName().c_str()); - if (i < m_cluster_map_filaments.size()) { m_cluster_map_filaments[i] = com_box->GetSelection(); } + if (i < m_cluster_map_filaments.size()) { + m_cluster_map_filaments[i] = com_box->GetSelection(); + if (m_cluster_map_filaments[i] > m_max_filament_index) { + m_max_filament_index = m_cluster_map_filaments[i]; + update_new_add_final_colors(); + } + } evt.StopPropagation(); }); return c_editor; @@ -549,7 +555,11 @@ void ObjColorPanel::deal_approximate_match_btn() auto new_index= color_dists[0].id; m_result_icon_list[i]->bitmap_combox->SetSelection(new_index); m_cluster_map_filaments[i] = new_index; + if (new_index > m_max_filament_index) { + m_max_filament_index = new_index; + } } + update_new_add_final_colors(); } void ObjColorPanel::show_sizer(wxSizer *sizer, bool show) @@ -663,6 +673,16 @@ void ObjColorPanel::draw_table() m_scrolledWindow->SetScrollRate(20, 20); } +void ObjColorPanel::update_new_add_final_colors() +{ + m_new_add_final_colors = m_new_add_colors; + if (m_max_filament_index <= m_colours.size()) { // Fix 20240904 + m_new_add_final_colors.clear(); + } else { + m_new_add_final_colors.resize(m_max_filament_index - m_colours.size()); + } +} + void ObjColorPanel::deal_algo(char cluster_number, bool redraw_ui) { if (m_last_cluster_number == cluster_number) { @@ -751,6 +771,7 @@ void ObjColorPanel::deal_reset_btn() } m_is_add_filament = false; m_new_add_colors.clear(); + m_new_add_final_colors.clear(); m_warning_text->SetLabelText(""); } diff --git a/src/slic3r/GUI/ObjColorDialog.hpp b/src/slic3r/GUI/ObjColorDialog.hpp index bc1909b..f35f415 100644 --- a/src/slic3r/GUI/ObjColorDialog.hpp +++ b/src/slic3r/GUI/ObjColorDialog.hpp @@ -41,6 +41,7 @@ private: void update_color_icon_and_rgba_sizer(int id, const wxColour &color); ComboBox* CreateEditorCtrl(wxWindow *parent,int id); void draw_table(); + void update_new_add_final_colors(); void show_sizer(wxSizer *sizer, bool show); void redraw_part_table(); void deal_approximate_match_btn(); @@ -78,9 +79,11 @@ private: int m_input_colors_size{0}; std::vector m_colours;//from project and show right std::vector m_cluster_map_filaments;//show middle + int m_max_filament_index = 0; std::vector m_cluster_colours;//from_algo and show left bool m_can_add_filament{true}; std::vector m_new_add_colors; + std::vector m_new_add_final_colors; //algo result std::vector m_cluster_colors_from_algo; std::vector m_cluster_labels_from_algo; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 747a99f..6846b74 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -72,6 +72,7 @@ const std::map INFO_ITEMS{ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, + const bool is_svg_volume, const wxBitmap& bmp, const wxString& extruder, const int idx/* = -1*/, @@ -80,6 +81,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare m_name(sub_obj_name), m_type(itVolume), m_volume_type(type), + m_is_svg_volume(is_svg_volume), m_idx(idx), m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : ""), m_warning_icon_name(warning_icon_name) @@ -203,6 +205,13 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) create_scaled_bitmap(m_printable == piPrintable ? "check_on" : "check_off_focused"); } +void ObjectDataViewModelNode::set_variable_height_icon(VaryHeightIndicator vari_height) { + if (m_variable_height == vari_height) + return; + m_variable_height = vari_height; + m_variable_height_icon = m_variable_height == hiUnVariable ? m_empty_bmp : create_scaled_bitmap("toolbar_variable_layer_height", nullptr, 20); +} + void ObjectDataViewModelNode::set_action_icon(bool enable) { if (m_action_enable == enable) @@ -300,6 +309,8 @@ void ObjectDataViewModelNode::msw_rescale() if (m_printable != piUndef) m_printable_icon = create_scaled_bitmap(m_printable == piPrintable ? "obj_printable" : "obj_unprintable"); + m_variable_height_icon = m_variable_height == hiUnVariable ? m_empty_bmp : create_scaled_bitmap("toolbar_variable_layer_height", nullptr, 20); + if (!m_opt_categories.empty()) update_settings_digest_bitmaps(); @@ -313,6 +324,9 @@ bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) case colPrint: m_printable_icon << variant; return true; + case colHeight: + m_variable_height_icon << variant; + return true; case colName: { DataViewBitmapText data; data << variant; @@ -438,6 +452,8 @@ ObjectDataViewModel::ObjectDataViewModel() m_bitmap_cache = new Slic3r::GUI::BitmapCache; m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps(); + m_svg_volume_bmps = MenuFactory::get_svg_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); m_lock_bmp = create_scaled_bitmap(LockIcon); @@ -530,7 +546,10 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node) is_volume_node &= (vol_type >= int(ModelVolumeType::MODEL_PART) && vol_type <= int(ModelVolumeType::SUPPORT_ENFORCER)); if (!node->has_warning_icon() && !node->has_lock()) { - node->SetBitmap(is_volume_node ? m_volume_bmps.at(vol_type) : m_empty_bmp); + node->SetBitmap(is_volume_node ? (node->is_text_volume() ? m_text_volume_bmps.at(vol_type) : + node->is_svg_volume() ? m_svg_volume_bmps.at(vol_type) : + m_volume_bmps.at(vol_type)) : + m_empty_bmp); return; } @@ -549,16 +568,22 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node) bmps.emplace_back(node->warning_icon_name() == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp); if (node->has_lock()) bmps.emplace_back(m_lock_bmp); - if (is_volume_node) - bmps.emplace_back(m_volume_bmps[vol_type]); + if (is_volume_node) { + if (!bmps.empty()) // ORCA: Add spacing between icons if there are multiple + bmps.emplace_back(create_scaled_bitmap("dot", nullptr, int(wxGetApp().em_unit() / 10) * 4)); + bmps.emplace_back(node->is_text_volume() ? m_text_volume_bmps[vol_type] : + node->is_svg_volume() ? m_svg_volume_bmps[vol_type] : + m_volume_bmps[vol_type]); + } bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } node->SetBitmap(*bmp); } -void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node, bool has_lock) +void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node, const std::string &warning_icon_name, bool has_lock) { + node->SetWarningIconName(warning_icon_name); node->SetLock(has_lock); UpdateBitmapForNode(node); } @@ -585,7 +610,7 @@ wxDataViewItem ObjectDataViewModel::AddObject(ModelObject *model_object, std::st const wxString extruder_str = wxString::Format("%d", extruder); auto obj_node = new ObjectDataViewModelNode(name, extruder_str, plate_idx, model_object); obj_node->SetWarningBitmap(GetWarningBitmap(warning_bitmap), warning_bitmap); - UpdateBitmapForNode(obj_node, has_lock); + UpdateBitmapForNode(obj_node, warning_bitmap, has_lock); if (plate_node != nullptr) { obj_node->m_parent = plate_node; @@ -611,6 +636,7 @@ wxDataViewItem ObjectDataViewModel::AddObject(ModelObject *model_object, std::st wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, + const bool is_svg_volume, const std::string& warning_icon_name/* = std::string()*/, const int extruder/* = 0*/, const bool create_frst_child/* = true*/) @@ -626,8 +652,9 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent if (create_frst_child && root->m_volumes_cnt == 0) { const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; - const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, root->m_warning_icon_name), root->m_extruder, 0, root->m_warning_icon_name); - + const auto node = new ObjectDataViewModelNode(root, root->m_name, type, is_svg_volume, GetVolumeIcon(type, root->m_warning_icon_name), root->m_extruder, + 0, root->m_warning_icon_name); + UpdateBitmapForNode(node, root->m_warning_icon_name, root->has_lock()); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control const wxDataViewItem child((void*)node); @@ -649,15 +676,16 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent extruder_str = wxString::Format("%d", extruder); } - const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, warning_icon_name), + const auto node = new ObjectDataViewModelNode(root, name, volume_type, is_svg_volume, GetVolumeIcon(volume_type, warning_icon_name), extruder_str, root->m_volumes_cnt, warning_icon_name); + UpdateBitmapForNode(node, warning_icon_name, root->has_lock() && volume_type < ModelVolumeType::PARAMETER_MODIFIER); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // if part with errors is added, but object wasn't marked, then mark it - if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name && - (root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon) ) + if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name && (root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon)) { root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); - + UpdateBitmapForNode(root); + } // notify control const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); @@ -849,6 +877,14 @@ bool ObjectDataViewModel::IsPrintable(const wxDataViewItem& item) const return node->IsPrintable() == piPrintable; } +bool ObjectDataViewModel::IsVariableHeight(const wxDataViewItem& item) const { + ObjectDataViewModelNode* node = static_cast(item.GetID()); + if (!node) + return false; + + return node->IsVaribaleHeight() == hiVariable; +} + wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) { return AddRoot(parent_item, itLayerRoot); @@ -1729,6 +1765,9 @@ void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &ite case colPrint: variant << node->m_printable_icon; break; + case colHeight: + variant << node->m_variable_height_icon; + break; case colName: variant << DataViewBitmapText(node->m_name, node->m_bmp); break; @@ -2257,6 +2296,18 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( return obj_item; } +// is the height is variable? +wxDataViewItem ObjectDataViewModel::SetObjectVariableHeightState(VaryHeightIndicator vari_height, wxDataViewItem obj_item) { + + ObjectDataViewModelNode* node = static_cast(obj_item.GetID()); + if (!node) + return wxDataViewItem(0); + node->set_variable_height_icon(vari_height); + ItemChanged(obj_item); + + return obj_item; +} + bool ObjectDataViewModel::IsColorPainted(wxDataViewItem& item) const { ObjectDataViewModelNode* node = static_cast(item.GetID()); @@ -2315,6 +2366,8 @@ void ObjectDataViewModel::SetSinkState(const bool painted, wxDataViewItem obj_it void ObjectDataViewModel::Rescale() { m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps(); + m_svg_volume_bmps = MenuFactory::get_svg_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); m_lock_bmp = create_scaled_bitmap(LockIcon); @@ -2336,10 +2389,8 @@ void ObjectDataViewModel::Rescale() switch (node->m_type) { case itObject: - if (node->m_bmp.IsOk()) node->m_bmp = GetWarningBitmap(node->m_warning_icon_name); - break; case itVolume: - node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name); + UpdateBitmapForNode(node); break; case itLayerRoot: node->m_bmp = create_scaled_bitmap(LayerRootIcon); @@ -2385,13 +2436,14 @@ void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std:: ObjectDataViewModelNode *node = static_cast(item.GetID()); if (node->GetType() & itObject) { - node->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); + UpdateBitmapForNode(node, warning_icon_name, node->has_lock()); return; } if (node->GetType() & itVolume) { - node->SetWarningBitmap(GetVolumeIcon(node->GetVolumeType(), warning_icon_name), warning_icon_name); - node->GetParent()->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); + UpdateBitmapForNode(node, warning_icon_name, node->has_lock()); + if (ObjectDataViewModelNode *parent = node->GetParent()) + UpdateBitmapForNode(parent, warning_icon_name, parent->has_lock()); return; } } @@ -2412,6 +2464,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo } node->SetWarningBitmap(wxNullBitmap, ""); + UpdateBitmapForNode(node); if (unmark_object) { wxDataViewItemArray children; diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index b247997..c348b84 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -39,6 +39,7 @@ enum ItemType { enum ColumnNumber { colName = 0, // item name + colHeight , // variable height colPrint , // printable property colFilament , // extruder selection // QDS @@ -56,6 +57,12 @@ enum PrintIndicator piUnprintable , // unprintable }; +enum VaryHeightIndicator +{ + hiUnVariable, // unvariable height + hiVariable, // variable height +}; + enum class InfoItemType { Undef, @@ -94,11 +101,16 @@ class ObjectDataViewModelNode wxBitmap m_sinking_icon; PrintIndicator m_printable {piUndef}; wxBitmap m_printable_icon; + + VaryHeightIndicator m_variable_height{ hiUnVariable }; + wxBitmap m_variable_height_icon; std::string m_warning_icon_name{ "" }; bool m_has_lock{false}; // for cut object icon std::string m_action_icon_name = ""; ModelVolumeType m_volume_type = ModelVolumeType(-1); + bool m_is_text_volume{false}; + bool m_is_svg_volume{false}; InfoItemType m_info_item_type {InfoItemType::Undef}; bool m_action_enable = false; // can undo all settings // QDS @@ -129,6 +141,7 @@ public: ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, + const bool is_svg_volume, const wxBitmap& bmp, const wxString& extruder, const int idx = -1, @@ -227,6 +240,7 @@ public: void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } void SetExtruder(const wxString &extruder) { m_extruder = extruder; } + void SetWarningIconName(const std::string &warning_icon_name) { m_warning_icon_name = warning_icon_name; } void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } void SetLock(bool has_lock) { m_has_lock = has_lock; } const wxBitmap& GetBitmap() const { return m_bmp; } @@ -242,6 +256,7 @@ public: t_layer_height_range GetLayerRange() const { return m_layer_range; } wxString GetExtruder() { return m_extruder; } PrintIndicator IsPrintable() const { return m_printable; } + VaryHeightIndicator IsVaribaleHeight() const { return m_variable_height; } // QDS bool HasColorPainting() const { return m_color_enable; } bool HasSupportPainting() const { return m_support_enable; } @@ -283,6 +298,7 @@ public: void set_extruder_icon(); // Set printable icon for node void set_printable_icon(PrintIndicator printable); + void set_variable_height_icon(VaryHeightIndicator vari_height); void set_action_icon(bool enable); // QDS void set_color_icon(bool enable); @@ -295,6 +311,8 @@ public: void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); int volume_type() const { return int(m_volume_type); } + bool is_text_volume() const { return m_is_text_volume; } + bool is_svg_volume() const { return m_is_svg_volume; } void msw_rescale(); #ifndef NDEBUG @@ -323,6 +341,8 @@ class ObjectDataViewModel :public wxDataViewModel std::vector m_plates; std::vector m_objects; std::vector m_volume_bmps; + std::vector m_text_volume_bmps; + std::vector m_svg_volume_bmps; std::map m_info_bmps; wxBitmap m_empty_bmp; wxBitmap m_warning_bmp; @@ -363,6 +383,7 @@ public: wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, + const bool is_svg_volume, const std::string& warning_icon_name = std::string(), const int extruder = 0, const bool create_frst_child = true); @@ -471,12 +492,15 @@ public: bool IsPrintable(const wxDataViewItem &item) const; void UpdateObjectPrintable(wxDataViewItem parent_item); void UpdateInstancesPrintable(wxDataViewItem parent_item); + bool IsVariableHeight(const wxDataViewItem& item) const; + void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); ModelVolumeType GetVolumeType(const wxDataViewItem &item); wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, int subobj_idx = -1, ItemType subobj_type = itInstance); wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item); + wxDataViewItem SetObjectVariableHeightState(VaryHeightIndicator vari_height, wxDataViewItem obj_item); // QDS bool IsColorPainted(wxDataViewItem& item) const; bool IsSupportPainted(wxDataViewItem &item) const; @@ -525,7 +549,7 @@ private: wxDataViewItem AddOutsidePlate(bool refresh = true); void UpdateBitmapForNode(ObjectDataViewModelNode *node); - void UpdateBitmapForNode(ObjectDataViewModelNode *node, bool has_lock); + void UpdateBitmapForNode(ObjectDataViewModelNode *node, const std::string &warning_icon_name, bool has_lock); }; diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 5c9850d..7f18115 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -236,6 +236,7 @@ OpenGLManager::~OpenGLManager() bool OpenGLManager::init_gl(bool popup_error) { if (!m_gl_initialized) { + glewExperimental = GL_TRUE; GLenum result = glewInit(); if (result != GLEW_OK) { BOOST_LOG_TRIVIAL(error) << "Unable to init glew library"; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 3d04c89..88c1253 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -57,6 +57,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co default: switch (opt.type) { case coFloatOrPercent: + case coFloatsOrPercents: case coFloat: case coFloats: case coPercent: @@ -996,7 +997,16 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = text_value; break; } - case coPercent:{ + case coFloatsOrPercents: { + const auto &value = config.option(opt_key)->get_at(idx); + + text_value = double_to_string(value.value); + if (value.percent) text_value += "%"; + + ret = text_value; + break; + } + case coPercent: { double val = config.option(opt_key)->value; text_value = wxString::Format(_T("%i"), int(val)); ret = text_value;// += "%"; diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 75a3927..5171b7b 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -194,6 +194,8 @@ public: bool is_activated() { return sizer != nullptr; } + //y47 + void set_label_width(int width) { label_width = width; } protected: std::map m_options; wxWindow* m_parent {nullptr}; diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index a748a01..b791731 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -71,8 +71,6 @@ std::array PlateTextureForeground = {0x44, 0x79, 0xfb, 0xff}; namespace Slic3r { namespace GUI { -class Bed3D; - std::array PartPlate::SELECT_COLOR = { 0.2666f, 0.2784f, 0.2784f, 1.0f }; //{ 0.4196f, 0.4235f, 0.4235f, 1.0f }; std::array PartPlate::UNSELECT_COLOR = { 0.82f, 0.82f, 0.82f, 1.0f }; std::array PartPlate::UNSELECT_DARK_COLOR = { 0.384f, 0.384f, 0.412f, 1.0f }; @@ -349,59 +347,6 @@ void PartPlate::calc_bounding_boxes() const { } } -void PartPlate::calc_triangles(const ExPolygon& poly) { - if (!m_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create plate triangles\n"; -} - -void PartPlate::calc_exclude_triangles(const ExPolygon& poly) { - if (!m_exclude_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create exclude triangles\n"; -} - -void PartPlate::calc_gridlines(const ExPolygon& poly, const BoundingBox& pp_bbox) { - Polylines axes_lines, axes_lines_bolder; - int count = 0; - for (coord_t x = pp_bbox.min(0); x <= pp_bbox.max(0); x += scale_(10.0)) { - Polyline line; - line.append(Point(x, pp_bbox.min(1))); - line.append(Point(x, pp_bbox.max(1))); - - if ( (count % 5) == 0 ) - axes_lines_bolder.push_back(line); - else - axes_lines.push_back(line); - count ++; - } - count = 0; - for (coord_t y = pp_bbox.min(1); y <= pp_bbox.max(1); y += scale_(10.0)) { - Polyline line; - line.append(Point(pp_bbox.min(0), y)); - line.append(Point(pp_bbox.max(0), y)); - axes_lines.push_back(line); - - if ( (count % 5) == 0 ) - axes_lines_bolder.push_back(line); - else - axes_lines.push_back(line); - count ++; - } - - // 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))); - Lines gridlines_bolder = to_lines(intersection_pl(axes_lines_bolder, 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) << __FUNCTION__ << "Unable to create bed grid lines\n"; - - if (!m_gridlines_bolder.set_from_lines(gridlines_bolder, GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create bed grid lines\n"; -} - void PartPlate::calc_height_limit() { Lines3 bottom_h_lines, top_lines, top_h_lines, common_lines; int shape_count = m_shape.size(); @@ -433,47 +378,22 @@ void PartPlate::calc_height_limit() { } //std::copy(bottom_lines.begin(), bottom_lines.end(), std::back_inserter(bottom_h_lines)); std::copy(top_lines.begin(), top_lines.end(), std::back_inserter(top_h_lines)); - - if (!m_height_limit_common.set_from_3d_Lines(common_lines)) + m_height_limit_common.reset(); + if (!m_height_limit_common.init_model_from_lines(common_lines)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create height limit bottom lines\n"; - - if (!m_height_limit_bottom.set_from_3d_Lines(bottom_h_lines)) + m_height_limit_bottom.reset(); + if (!m_height_limit_bottom.init_model_from_lines(bottom_h_lines)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create height limit bottom lines\n"; - - if (!m_height_limit_top.set_from_3d_Lines(top_h_lines)) + m_height_limit_top.reset(); + if (!m_height_limit_top.init_model_from_lines(top_h_lines)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create height limit top lines\n"; } -void PartPlate::calc_vertex_for_number(int index, bool one_number, GeometryBuffer &buffer) -{ - ExPolygon poly; -#if 0 //in the up area - Vec2d& p = m_shape[2]; - float offset_x = one_number?PARTPLATE_TEXT_OFFSET_X1: PARTPLATE_TEXT_OFFSET_X2; - - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP) - PARTPLATE_ICON_GAP - PARTPLATE_ICON_SIZE + PARTPLATE_TEXT_OFFSET_Y) }); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP)- PARTPLATE_ICON_GAP - PARTPLATE_ICON_SIZE + PARTPLATE_TEXT_OFFSET_Y) }); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP)- PARTPLATE_ICON_GAP - PARTPLATE_TEXT_OFFSET_Y)}); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP)- PARTPLATE_ICON_GAP - PARTPLATE_TEXT_OFFSET_Y) }); -#else //in the bottom - Vec2d& p = m_shape[1]; - float offset_x = one_number?PARTPLATE_TEXT_OFFSET_X1: PARTPLATE_TEXT_OFFSET_X2; - - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) + PARTPLATE_TEXT_OFFSET_Y) }); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) + PARTPLATE_TEXT_OFFSET_Y) }); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) + PARTPLATE_ICON_SIZE - PARTPLATE_TEXT_OFFSET_Y)}); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) + PARTPLATE_ICON_SIZE - PARTPLATE_TEXT_OFFSET_Y) }); -#endif - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; -} - -void PartPlate::calc_vertex_for_plate_name(GLTexture &texture, GeometryBuffer &buffer) +void PartPlate::calc_vertex_for_plate_name(GLTexture &texture, GLModel &gl_model) { if (texture.get_width() > 0 && texture.get_height()) { wxCoord w, h; - auto bed_ext = get_extents(m_shape); + auto bed_ext = get_extents(m_partplate_list->m_shape); auto factor = bed_ext.size()(1) / 200.0; ExPolygon poly; float offset_x = 1; @@ -486,13 +406,15 @@ void PartPlate::calc_vertex_for_plate_name(GLTexture &texture, GeometryBuffer &b poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) )}); auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; - + gl_model.reset(); + if (!gl_model.init_model_from_poly(triangles, GROUND_Z)) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; } } -void PartPlate::calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int index, GeometryBuffer &buffer) { - auto bed_ext = get_extents(m_shape); +void PartPlate::calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int index, GLModel &gl_model) +{ + auto bed_ext = get_extents(m_partplate_list->m_shape); auto factor = bed_ext.size()(1) / 200.0; wxCoord w, h; h = int(factor * 16); @@ -501,6 +423,7 @@ void PartPlate::calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int ind float offset_x = 1; h = PARTPLATE_EDIT_PLATE_NAME_ICON_SIZE; p += Vec2d(0, PARTPLATE_PLATENAME_OFFSET_Y + h); + std::vector triangles; if (texture && texture->get_width() > 0 && texture->get_height()) { w = int(factor * (texture->get_original_width() * 16) / texture->get_height()) + 1; @@ -509,9 +432,7 @@ void PartPlate::calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int ind poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + w + PARTPLATE_EDIT_PLATE_NAME_ICON_SIZE), scale_(p(1))}); poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + w), scale_(p(1) )}); - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; + triangles = triangulate_expolygon_2f(poly, NORMALS_UP); } else { poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x ), scale_(p(1) - h )}); @@ -519,31 +440,17 @@ void PartPlate::calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int ind poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x + PARTPLATE_EDIT_PLATE_NAME_ICON_SIZE), scale_(p(1))}); poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) )}); - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; + triangles = triangulate_expolygon_2f(poly, NORMALS_UP); } -} - -void PartPlate::calc_vertex_for_icons(int index, GeometryBuffer &buffer) -{ - ExPolygon poly; - Vec2d& p = m_shape[2]; - - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y) - PARTPLATE_ICON_GAP_TOP - PARTPLATE_ICON_SIZE) }); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y)- PARTPLATE_ICON_GAP_TOP - PARTPLATE_ICON_SIZE) }); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y)- PARTPLATE_ICON_GAP_TOP)}); - poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y)- PARTPLATE_ICON_GAP_TOP) }); - - auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); - if (!buffer.set_from_triangles(triangles, GROUND_Z)) + gl_model.reset(); + if (!gl_model.init_model_from_poly(triangles, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; } void PartPlate::calc_vertex_for_icons_background(int icon_count, GeometryBuffer &buffer) { ExPolygon poly; - Vec2d& p = m_shape[2]; + Vec2d & p = m_partplate_list->m_shape[2]; poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT), scale_(p(1) - icon_count * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y) - PARTPLATE_ICON_GAP_TOP) }); poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE), scale_(p(1) - icon_count * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y)- PARTPLATE_ICON_GAP_TOP) }); @@ -555,33 +462,24 @@ void PartPlate::calc_vertex_for_icons_background(int icon_count, GeometryBuffer BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; } -void PartPlate::render_background(bool force_default_color) const { - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - - //return directly for current plate - if (m_selected && !force_default_color) return; - - // draw background - glsafe(::glDepthMask(GL_FALSE)); - - if (!force_default_color) { - if (m_selected) { - glsafe(::glColor4fv(PartPlate::SELECT_COLOR.data())); - } - else { - glsafe(m_partplate_list->m_is_dark ? ::glColor4fv(PartPlate::UNSELECT_DARK_COLOR.data()) : ::glColor4fv(PartPlate::UNSELECT_COLOR.data())); - } - } - else { - glsafe(::glColor4fv(PartPlate::DEFAULT_COLOR.data())); - } - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - glsafe(::glDepthMask(GL_TRUE)); +bool PartPlate::calc_bed_3d_boundingbox(BoundingBoxf3 &box_in_plate_origin) { + if (m_partplate_list && m_partplate_list->m_bed3d && !m_partplate_list->m_bed3d->get_model_filename().empty()) { + auto cur_bed = m_partplate_list->m_bed3d; + auto cur_box = cur_bed->get_cur_bed_model_box(); + if (cur_box.size().x() > 1.0f) { + Vec3d min_ = cur_box.min - m_origin; + Vec3d max_ = cur_box.max - m_origin; + cur_box.reset(); + cur_box.merge(min_); + cur_box.merge(max_); + box_in_plate_origin = cur_box; + return true; + } + } + return false; } -void PartPlate::render_logo_texture(GLTexture &logo_texture, const GeometryBuffer& logo_buffer, bool bottom, unsigned int vbo_id) const +void PartPlate::render_logo_texture(GLTexture &logo_texture, GLModel &logo_buffer, bool bottom) { //check valid if (logo_texture.unsent_compressed_data_available()) { @@ -589,137 +487,81 @@ void PartPlate::render_logo_texture(GLTexture &logo_texture, const GeometryBuffe logo_texture.send_compressed_data_to_gpu(); } - if (logo_buffer.get_vertices_count() > 0) { - GLShaderProgram* shader = wxGetApp().get_shader("printbed"); - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("transparent_background", 0); - shader->set_uniform("svg_source", 0); - - //glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDepthMask(GL_FALSE)); + if (logo_buffer.is_initialized()) { if (bottom) glsafe(::glFrontFace(GL_CW)); - unsigned int stride = logo_buffer.get_vertex_data_size(); - - GLint position_id = shader->get_attrib_location("v_position"); - GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); - if (position_id != -1) { - glsafe(::glEnableVertexAttribArray(position_id)); - } - if (tex_coords_id != -1) { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - } - // show the temporary texture while no compressed data is available GLuint tex_id = (GLuint)logo_texture.get_id(); - glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id)); - - if (position_id != -1) - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)logo_buffer.get_position_offset())); - if (tex_coords_id != -1) - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)logo_buffer.get_tex_coords_offset())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)logo_buffer.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)); + logo_buffer.render_geometry(); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); if (bottom) glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glDepthMask(GL_TRUE)); - - shader->stop_using(); - } } } -void PartPlate::render_logo(bool bottom, bool render_cali) const +void PartPlate::render_logo(bool bottom, bool render_cali) { - if (!m_partplate_list->render_bedtype_logo) { - // render third-party printer texture logo - if (m_partplate_list->m_logo_texture_filename.empty()) { - m_partplate_list->m_logo_texture.reset(); + // render printer custom texture logo + if (m_partplate_list->m_logo_texture_filename.empty()) { + m_partplate_list->m_logo_texture.reset(); + } else { + if (m_partplate_list->m_logo_texture.get_id() == 0 || m_partplate_list->m_logo_texture.get_source() != m_partplate_list->m_logo_texture_filename) { + m_partplate_list->m_logo_texture.reset(); + + if (boost::algorithm::iends_with(m_partplate_list->m_logo_texture_filename, ".svg")) { + // starts generating the main texture, compression will run asynchronously + GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); + GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048; + if (!m_partplate_list->m_logo_texture.load_from_svg_file(m_partplate_list->m_logo_texture_filename, true, false, false, logo_tex_size)) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename; + return; + } + } else if (boost::algorithm::iends_with(m_partplate_list->m_logo_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_logo_texture_filename) { + if (!temp_texture->load_from_file(m_logo_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 (!m_partplate_list->m_logo_texture.load_from_file(m_partplate_list->m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename; + return; + } + } else { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ + << boost::format(": can not load logo texture from %1%, unsupported format") % m_partplate_list->m_logo_texture_filename; + return; + } + } else if (m_partplate_list->m_logo_texture.unsent_compressed_data_available()) { + // sends to gpu the already available compressed levels of the main texture + m_partplate_list->m_logo_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(); + } + BoundingBoxf3 box_in_plate_origin; + if (calc_bed_3d_boundingbox(box_in_plate_origin)) { + if ((m_cur_bed_boundingbox.center() - box_in_plate_origin.center()).norm() > 1.0f) { + set_logo_box_by_bed(box_in_plate_origin); + } + } + if (m_logo_triangles.is_initialized()) { + render_logo_texture(m_partplate_list->m_logo_texture, m_logo_triangles, bottom); + } + if (!m_partplate_list->render_bedtype_logo) { return; } - - //GLTexture* temp_texture = const_cast(&m_temp_texture); - - if (m_partplate_list->m_logo_texture.get_id() == 0 || m_partplate_list->m_logo_texture.get_source() != m_partplate_list->m_logo_texture_filename) { - m_partplate_list->m_logo_texture.reset(); - - if (boost::algorithm::iends_with(m_partplate_list->m_logo_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 - GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048; - if (!m_partplate_list->m_logo_texture.load_from_svg_file(m_partplate_list->m_logo_texture_filename, true, false, false, logo_tex_size)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename; - return; - } - } - else if (boost::algorithm::iends_with(m_partplate_list->m_logo_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_logo_texture_filename) { - if (!temp_texture->load_from_file(m_logo_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 (!m_partplate_list->m_logo_texture.load_from_file(m_partplate_list->m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename; - return; - } - } - else { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not load logo texture from %1%, unsupported format") % m_partplate_list->m_logo_texture_filename; - return; - } - } - else if (m_partplate_list->m_logo_texture.unsent_compressed_data_available()) { - // sends to gpu the already available compressed levels of the main texture - m_partplate_list->m_logo_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_vbo_id == 0) { - unsigned int* vbo_id_ptr = const_cast(&m_vbo_id); - glsafe(::glGenBuffers(1, vbo_id_ptr)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id_ptr)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_logo_triangles.get_vertices_data_size(), (const GLvoid*)m_logo_triangles.get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - if (m_vbo_id != 0 && m_logo_triangles.get_vertices_count() > 0) - render_logo_texture(m_partplate_list->m_logo_texture, m_logo_triangles, bottom, m_vbo_id); - return; - } + } m_partplate_list->load_bedtype_textures(); m_partplate_list->load_cali_textures(); @@ -735,17 +577,16 @@ void PartPlate::render_logo(bool bottom, bool render_cali) const // render bed textures for (auto &part : m_partplate_list->bed_texture_info[bed_type_idx].parts) { if (part.texture) { - if (part.buffer && part.buffer->get_vertices_count() > 0 + if (part.buffer && part.buffer->is_initialized() //&& part.vbo_id != 0 ) { if (part.offset.x() != m_origin.x() || part.offset.y() != m_origin.y()) { part.offset = Vec2d(m_origin.x(), m_origin.y()); - part.update_buffer(); + //part.update_buffer(); } render_logo_texture(*(part.texture), *(part.buffer), - bottom, - part.vbo_id); + bottom); } } } @@ -754,322 +595,130 @@ void PartPlate::render_logo(bool bottom, bool render_cali) const if (render_cali) { for (auto& part : m_partplate_list->cali_texture_info.parts) { if (part.texture) { - if (part.buffer && part.buffer->get_vertices_count() > 0) { + if (part.buffer && part.buffer->is_initialized()) { if (part.offset.x() != m_origin.x() || part.offset.y() != m_origin.y()) { part.offset = Vec2d(m_origin.x(), m_origin.y()); - part.update_buffer(); + //part.update_buffer(); } render_logo_texture(*(part.texture), *(part.buffer), - bottom, - part.vbo_id); + bottom); } } } } } -void PartPlate::render_exclude_area(bool force_default_color) const { - if (force_default_color) //for thumbnail case - return; - - unsigned int triangles_vcount = m_exclude_triangles.get_vertices_count(); - std::array select_color{ 0.765f, 0.7686f, 0.7686f, 1.0f }; - std::array unselect_color{ 0.9f, 0.9f, 0.9f, 1.0f }; - std::array default_color{ 0.9f, 0.9f, 0.9f, 1.0f }; - - // draw exclude area - glsafe(::glDepthMask(GL_FALSE)); - - if (m_selected) { - glsafe(::glColor4fv(select_color.data())); - } - else { - glsafe(::glColor4fv(unselect_color.data())); - } - - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_exclude_triangles.get_vertex_data_size(), (GLvoid*)m_exclude_triangles.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - glsafe(::glDepthMask(GL_TRUE)); -} - - -/*void PartPlate::render_background_for_picking(const float* render_color) const -{ - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - - glsafe(::glDepthMask(GL_FALSE)); - glsafe(::glColor4fv(render_color)); - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - glsafe(::glDepthMask(GL_TRUE)); -}*/ - -void PartPlate::render_grid(bool bottom) const { - //glsafe(::glEnable(GL_MULTISAMPLE)); - // draw grid - glsafe(::glLineWidth(1.0f * m_scale_factor)); - if (bottom) - glsafe(::glColor4fv(LINE_BOTTOM_COLOR.data())); - else { - if (m_selected) - glsafe(m_partplate_list->m_is_dark ? ::glColor4fv(LINE_TOP_SEL_DARK_COLOR.data()) : ::glColor4fv(LINE_TOP_SEL_COLOR.data())); - else - glsafe(m_partplate_list->m_is_dark ? ::glColor4fv(LINE_TOP_DARK_COLOR.data()) : ::glColor4fv(LINE_TOP_COLOR.data())); - } - glsafe(::glVertexPointer(3, GL_FLOAT, m_gridlines.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); - - glsafe(::glLineWidth(2.0f * m_scale_factor)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_gridlines_bolder.get_vertex_data_size(), (GLvoid*)m_gridlines_bolder.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines_bolder.get_vertices_count())); -} - -void PartPlate::render_height_limit(PartPlate::HeightLimitMode mode) const +void PartPlate::render_height_limit(PartPlate::HeightLimitMode mode) { if (m_print && m_print->config().print_sequence == PrintSequence::ByObject && mode != HEIGHT_LIMIT_NONE) { // draw lower limit glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4fv(HEIGHT_LIMIT_BOTTOM_COLOR.data())); - glsafe(::glVertexPointer(3, GL_FLOAT, m_height_limit_common.get_vertex_data_size(), (GLvoid*)m_height_limit_common.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_height_limit_common.get_vertices_count())); + m_height_limit_common.set_color(HEIGHT_LIMIT_BOTTOM_COLOR); + m_height_limit_common.render_geometry(); if ((mode == HEIGHT_LIMIT_BOTTOM) || (mode == HEIGHT_LIMIT_BOTH)) { - glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4fv(HEIGHT_LIMIT_BOTTOM_COLOR.data())); - glsafe(::glVertexPointer(3, GL_FLOAT, m_height_limit_bottom.get_vertex_data_size(), (GLvoid*)m_height_limit_bottom.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_height_limit_bottom.get_vertices_count())); - } - + glsafe(::glLineWidth(3.0f * m_scale_factor)); + m_height_limit_bottom.set_color(HEIGHT_LIMIT_BOTTOM_COLOR); + m_height_limit_bottom.render_geometry(); + } // draw upper limit if ((mode == HEIGHT_LIMIT_TOP) || (mode == HEIGHT_LIMIT_BOTH)){ glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4fv(HEIGHT_LIMIT_TOP_COLOR.data())); - glsafe(::glVertexPointer(3, GL_FLOAT, m_height_limit_top.get_vertex_data_size(), (GLvoid*)m_height_limit_top.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_height_limit_top.get_vertices_count())); + m_height_limit_top.set_color(HEIGHT_LIMIT_TOP_COLOR); + m_height_limit_top.render_geometry(); } } } -void PartPlate::render_icon_texture(int position_id, int tex_coords_id, const GeometryBuffer &buffer, GLTexture &texture, unsigned int &vbo_id) const +void PartPlate::render_icon_texture(GLModel &icon, GLTexture &texture) { - if (vbo_id == 0) { - glsafe(::glGenBuffers(1, &vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)buffer.get_vertices_data_size(), (const GLvoid*)buffer.get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - unsigned int stride = buffer.get_vertex_data_size(); - GLuint 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(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)buffer.get_position_offset())); - if (tex_coords_id != -1) - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)buffer.get_tex_coords_offset())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)buffer.get_vertices_count())); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -} - -void PartPlate::render_plate_name_texture(int position_id, int tex_coords_id) -{ - if (m_name_change) { - m_name_change = false; - if (m_plate_name_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_name_vbo_id)); - m_plate_name_vbo_id = 0; - } - if (m_plate_name_edit_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_name_edit_vbo_id)); - m_plate_name_edit_vbo_id = 0; - } - } - if (m_plate_name_vbo_id==0) { - if (generate_plate_name_texture()) { - calc_vertex_for_plate_name(m_name_texture, m_plate_name_icon); - if (m_plate_name_edit_vbo_id > 0) { //for redo - glsafe(::glDeleteBuffers(1, &m_plate_name_edit_vbo_id)); - m_plate_name_edit_vbo_id = 0; - } - calc_vertex_for_plate_name_edit_icon(&m_name_texture, 0, m_plate_name_edit_icon); - } - else { - if (m_plate_name_edit_vbo_id==0) { - calc_vertex_for_plate_name_edit_icon(nullptr, 0, m_plate_name_edit_icon); - } - return; - } - } - - if (m_plate_name_vbo_id == 0 && m_plate_name_icon.get_vertices_data_size() > 0) { - glsafe(::glGenBuffers(1, &m_plate_name_vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_plate_name_vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) m_plate_name_icon.get_vertices_data_size(), (const GLvoid *) m_plate_name_icon.get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - unsigned int stride = m_plate_name_icon.get_vertex_data_size(); - GLuint tex_id = (GLuint) m_name_texture.get_id(); + GLuint tex_id = (GLuint) texture.get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_plate_name_vbo_id)); - if (position_id != -1) glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid *) (intptr_t) m_plate_name_icon.get_position_offset())); - if (tex_coords_id != -1) glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid *) (intptr_t) m_plate_name_icon.get_tex_coords_offset())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei) m_plate_name_icon.get_vertices_count())); + icon.render_geometry(); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +} - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +void PartPlate::render_plate_name_texture() +{ + if (m_name_change) { + m_name_change = false; + generate_plate_name_texture(); + } + if (m_name_texture.get_id() == 0) + generate_plate_name_texture(); + if (!m_plate_name_icon.is_initialized()) { + return; + } + GLuint tex_id = (GLuint) m_name_texture.get_id(); + glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); + m_plate_name_icon.render_geometry(); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); } void PartPlate::render_icons(bool bottom, bool only_body, int hover_id) { - 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_partplate_list->m_del_texture.get_source(), ".svg")); - shader->set_uniform("svg_source", 0); + if (!only_body) { + if (hover_id == 1) + render_icon_texture(m_partplate_list->m_del_icon, m_partplate_list->m_del_hovered_texture); + else + render_icon_texture(m_partplate_list->m_del_icon, m_partplate_list->m_del_texture); - //if (bottom) - // glsafe(::glFrontFace(GL_CW)); - glsafe(::glDepthMask(GL_FALSE)); + if (hover_id == 2) + render_icon_texture(m_partplate_list->m_orient_icon, m_partplate_list->m_orient_hovered_texture); + else + render_icon_texture(m_partplate_list->m_orient_icon, m_partplate_list->m_orient_texture); - GLint position_id = shader->get_attrib_location("v_position"); - GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); - if (position_id != -1) { - glsafe(::glEnableVertexAttribArray(position_id)); + if (hover_id == 3) + render_icon_texture(m_partplate_list->m_arrange_icon, m_partplate_list->m_arrange_hovered_texture); + else + render_icon_texture(m_partplate_list->m_arrange_icon, m_partplate_list->m_arrange_texture); + + if (hover_id == 4) { + if (this->is_locked()) + render_icon_texture(m_partplate_list->m_lock_icon, m_partplate_list->m_locked_hovered_texture); + else + render_icon_texture(m_partplate_list->m_lock_icon, m_partplate_list->m_lockopen_hovered_texture); + } else { + if (this->is_locked()) + render_icon_texture(m_partplate_list->m_lock_icon, m_partplate_list->m_locked_texture); + else + render_icon_texture(m_partplate_list->m_lock_icon, m_partplate_list->m_lockopen_texture); } - if (tex_coords_id != -1) { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - } - render_plate_name_texture(position_id, tex_coords_id); - if (!only_body) { - if (hover_id == 1) - render_icon_texture(position_id, tex_coords_id, m_del_icon, m_partplate_list->m_del_hovered_texture, m_del_vbo_id); - else - render_icon_texture(position_id, tex_coords_id, m_del_icon, m_partplate_list->m_del_texture, m_del_vbo_id); - if (hover_id == 2) - render_icon_texture(position_id, tex_coords_id, m_orient_icon, m_partplate_list->m_orient_hovered_texture, m_orient_vbo_id); - else - render_icon_texture(position_id, tex_coords_id, m_orient_icon, m_partplate_list->m_orient_texture, m_orient_vbo_id); + if (hover_id == PLATE_NAME_ID) + render_icon_texture(m_plate_name_edit_icon, m_partplate_list->m_plate_name_edit_hovered_texture); + else + render_icon_texture(m_plate_name_edit_icon, m_partplate_list->m_plate_name_edit_texture); - if (hover_id == 3) - render_icon_texture(position_id, tex_coords_id, m_arrange_icon, m_partplate_list->m_arrange_hovered_texture, m_arrange_vbo_id); - else - render_icon_texture(position_id, tex_coords_id, m_arrange_icon, m_partplate_list->m_arrange_texture, m_arrange_vbo_id); - - if (hover_id == 4) { - if (this->is_locked()) - render_icon_texture(position_id, tex_coords_id, m_lock_icon, m_partplate_list->m_locked_hovered_texture, m_lock_vbo_id); + if (m_partplate_list->render_plate_settings) { + bool has_plate_settings = get_bed_type() != BedType::btDefault || get_print_seq() != PrintSequence::ByDefault || !get_first_layer_print_sequence().empty() || + !get_other_layers_print_sequence().empty() || has_spiral_mode_config(); + if (hover_id == 5) { + if (!has_plate_settings) + render_icon_texture(m_partplate_list->m_plate_settings_icon, m_partplate_list->m_plate_settings_hovered_texture); else - render_icon_texture(position_id, tex_coords_id, m_lock_icon, m_partplate_list->m_lockopen_hovered_texture, m_lock_vbo_id); + render_icon_texture(m_partplate_list->m_plate_settings_icon, m_partplate_list->m_plate_settings_changed_hovered_texture); } else { - if (this->is_locked()) - render_icon_texture(position_id, tex_coords_id, m_lock_icon, m_partplate_list->m_locked_texture, m_lock_vbo_id); + if (!has_plate_settings) + render_icon_texture(m_partplate_list->m_plate_settings_icon, m_partplate_list->m_plate_settings_texture); else - render_icon_texture(position_id, tex_coords_id, m_lock_icon, m_partplate_list->m_lockopen_texture, m_lock_vbo_id); - } - - if (hover_id == 6) - render_icon_texture(position_id, tex_coords_id, m_plate_name_edit_icon, m_partplate_list->m_plate_name_edit_hovered_texture, m_plate_name_edit_vbo_id); - else - render_icon_texture(position_id, tex_coords_id, m_plate_name_edit_icon, m_partplate_list->m_plate_name_edit_texture, m_plate_name_edit_vbo_id); - - if (m_partplate_list->render_plate_settings) { - bool has_plate_settings = get_bed_type() != BedType::btDefault || get_print_seq() != PrintSequence::ByDefault || !get_first_layer_print_sequence().empty() || !get_other_layers_print_sequence().empty() || has_spiral_mode_config(); - if (hover_id == 5) { - if (!has_plate_settings) - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_hovered_texture, m_plate_settings_vbo_id); - else - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_changed_hovered_texture, - m_plate_settings_vbo_id); - } else { - if (!has_plate_settings) - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_texture, m_plate_settings_vbo_id); - else - render_icon_texture(position_id, tex_coords_id, m_plate_settings_icon, m_partplate_list->m_plate_settings_changed_texture, m_plate_settings_vbo_id); - } - } - - if (m_plate_index >= 0 && m_plate_index < MAX_PLATE_COUNT) { - render_icon_texture(position_id, tex_coords_id, m_plate_idx_icon, m_partplate_list->m_idx_textures[m_plate_index], m_plate_idx_vbo_id); + render_icon_texture(m_partplate_list->m_plate_settings_icon, m_partplate_list->m_plate_settings_changed_texture); } } - if (tex_coords_id != -1) - glsafe(::glDisableVertexAttribArray(tex_coords_id)); - - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - - //if (bottom) - // glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glDepthMask(GL_TRUE)); - shader->stop_using(); } + render_plate_name_texture(); } -void PartPlate::render_only_numbers(bool bottom) const +void PartPlate::render_numbers(bool bottom) { - 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_partplate_list->m_del_texture.get_source(), ".svg")); - shader->set_uniform("svg_source", 0); - - //if (bottom) - // glsafe(::glFrontFace(GL_CW)); - glsafe(::glDepthMask(GL_FALSE)); - - GLint position_id = shader->get_attrib_location("v_position"); - GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); - if (position_id != -1) { - glsafe(::glEnableVertexAttribArray(position_id)); - } - if (tex_coords_id != -1) { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - } - - if (m_plate_index >=0 && m_plate_index < MAX_PLATE_COUNT) { - render_icon_texture(position_id, tex_coords_id, m_plate_idx_icon, m_partplate_list->m_idx_textures[m_plate_index], m_plate_idx_vbo_id); - } - - if (tex_coords_id != -1) - glsafe(::glDisableVertexAttribArray(tex_coords_id)); - - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - - //if (bottom) - // glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glDepthMask(GL_TRUE)); - shader->stop_using(); + if (m_plate_index >=0 && m_plate_index < MAX_PLATE_COUNT) { + render_icon_texture(m_partplate_list->m_plate_idx_icon, m_partplate_list->m_idx_textures[m_plate_index]); } } -void PartPlate::render_rectangle_for_picking(const GeometryBuffer &buffer, const float* render_color) const -{ - unsigned int triangles_vcount = buffer.get_vertices_count(); - - //glsafe(::glDepthMask(GL_FALSE)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glColor4fv(render_color)); - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, buffer.get_vertex_data_size(), (GLvoid*)buffer.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - //glsafe(::glDepthMask(GL_TRUE)); -} - void PartPlate::render_label(GLCanvas3D& canvas) const { std::string label = (boost::format("Plate %1%") % (m_plate_index + 1)).str(); const Camera& camera = wxGetApp().plater()->get_camera(); @@ -1286,62 +935,28 @@ void PartPlate::render_right_arrow(const float* render_color, bool use_lighting) #endif } -void PartPlate::on_render_for_picking() const { +void PartPlate::on_render_for_picking() { //glsafe(::glDisable(GL_DEPTH_TEST)); - int hover_id = 0; - std::array color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - //render_grabber(m_grabber_color, false); - render_rectangle_for_picking(m_triangles, m_grabber_color); - hover_id = 1; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - //render_left_arrow(m_grabber_color, false); - render_rectangle_for_picking(m_del_icon, m_grabber_color); - hover_id = 2; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - render_rectangle_for_picking(m_orient_icon, m_grabber_color); - hover_id = 3; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - render_rectangle_for_picking(m_arrange_icon, m_grabber_color); - hover_id = 4; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - //render_right_arrow(m_grabber_color, false); - render_rectangle_for_picking(m_lock_icon, m_grabber_color); - hover_id = 5; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - if (m_partplate_list->render_plate_settings) - render_rectangle_for_picking(m_plate_settings_icon, m_grabber_color); - hover_id = 6; - color = picking_color_component(hover_id); - m_grabber_color[0] = color[0]; - m_grabber_color[1] = color[1]; - m_grabber_color[2] = color[2]; - m_grabber_color[3] = color[3]; - // render_left_arrow(m_grabber_color, false); - render_rectangle_for_picking(m_plate_name_edit_icon, m_grabber_color); + const Camera &camera = wxGetApp().plater()->get_camera(); + auto view_mat = camera.get_view_matrix(); + auto proj_mat = camera.get_projection_matrix(); + + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + shader->start_using(); + auto model_mat = m_partplate_list->m_plate_trans[m_plate_index].get_matrix(); + shader->set_uniform("view_model_matrix", view_mat * model_mat); + shader->set_uniform("projection_matrix", proj_mat); + + std::vector gl_models = {&m_partplate_list->m_triangles, &m_partplate_list->m_del_icon, &m_partplate_list->m_orient_icon, &m_partplate_list->m_arrange_icon, + &m_partplate_list->m_lock_icon, &m_partplate_list->m_plate_settings_icon, + &m_plate_name_edit_icon}; + for (size_t i = 0; i < gl_models.size(); i++) { + int hover_id = i; + std::array color = picking_color_component(hover_id); + gl_models[i]->set_color(-1, color); + gl_models[i]->render_geometry(); + } + shader->stop_using(); } std::array PartPlate::picking_color_component(int idx) const @@ -1358,42 +973,6 @@ std::array PartPlate::picking_color_component(int idx) const void PartPlate::release_opengl_resource() { - if (m_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_vbo_id)); - m_vbo_id = 0; - } - if (m_del_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_del_vbo_id)); - m_del_vbo_id = 0; - } - if (m_orient_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_orient_vbo_id)); - m_orient_vbo_id = 0; - } - if (m_arrange_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_arrange_vbo_id)); - m_arrange_vbo_id = 0; - } - if (m_lock_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_lock_vbo_id)); - m_lock_vbo_id = 0; - } - if (m_plate_settings_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_settings_vbo_id)); - m_plate_settings_vbo_id = 0; - } - if (m_plate_idx_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_idx_vbo_id)); - m_plate_idx_vbo_id = 0; - } - if (m_plate_name_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_name_vbo_id)); - m_plate_name_vbo_id = 0; - } - if (m_plate_name_edit_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_plate_name_edit_vbo_id)); - m_plate_name_edit_vbo_id = 0; - } } std::vector PartPlate::get_extruders(bool conside_custom_gcode) const @@ -1890,7 +1469,12 @@ bool PartPlate::generate_plate_name_texture() } limitTextWidth = wxControl::Ellipsize(cur_plate_name, dc, wxELLIPSIZE_END, bed_width); } - if (limitTextWidth.Length()==0) { + if (limitTextWidth.Length() == 0) { + if (m_name_texture.get_width() > 0) { + m_name_texture.reset(); + m_plate_name_icon.reset(); + calc_vertex_for_plate_name_edit_icon(&m_name_texture, 0, m_plate_name_edit_icon); + } return false; } // generate m_name_texture texture from m_name with generate_from_text_string @@ -1901,6 +1485,8 @@ bool PartPlate::generate_plate_name_texture() BOOST_LOG_TRIVIAL(error) << "PartPlate::generate_plate_name_texture(): generate_from_text_string() failed"; return false; } + calc_vertex_for_plate_name(m_name_texture, m_plate_name_icon); + calc_vertex_for_plate_name_edit_icon(&m_name_texture, 0, m_plate_name_edit_icon); return true; } @@ -2500,27 +2086,61 @@ void PartPlate::move_instances_to(PartPlate& left_plate, PartPlate& right_plate, void PartPlate::generate_logo_polygon(ExPolygon &logo_polygon) { - if (m_shape.size() == 4) - { - //rectangle case - for (int i = 0; i < 4; i++) - { - const Vec2d& p = m_shape[i]; + auto &cur_shape = m_partplate_list->m_shape; + if (cur_shape.size() == 4) { // rectangle case + for (int i = 0; i < 4; i++){ + const Vec2d &p = cur_shape[i]; if ((i == 0) || (i == 1)) { - logo_polygon.contour.append({ scale_(p(0)), scale_(p(1) - 12.f) }); + logo_polygon.contour.append({ scale_(p(0)), scale_(p(1) - 10.f) }); } else { - logo_polygon.contour.append({ scale_(p(0)), scale_(p(1)) }); + logo_polygon.contour.append({scale_(p(0)), scale_(p(1) + 10.f)}); } } } else { - for (const Vec2d& p : m_shape) { + for (const Vec2d &p : cur_shape) { logo_polygon.contour.append({ scale_(p(0)), scale_(p(1)) }); } } } +void PartPlate::generate_logo_polygon(ExPolygon &logo_polygon, const BoundingBoxf3 &box) { + if (box.defined) { + { + Vec2d p(box.min.x(), box.min.y()); + logo_polygon.contour.append({scale_(p(0)), scale_(p(1))}); + } + { + Vec2d p(box.max.x(), box.min.y()); + logo_polygon.contour.append({scale_(p(0)), scale_(p(1))}); + } + { + Vec2d p(box.max.x(), box.max.y()); + logo_polygon.contour.append({scale_(p(0)), scale_(p(1))}); + } + { + Vec2d p(box.min.x(), box.max.y()); + logo_polygon.contour.append({scale_(p(0)), scale_(p(1))}); + } + } +} + +void PartPlate::set_logo_box_by_bed(const BoundingBoxf3& box) +{ + if (box.defined) { + m_cur_bed_boundingbox = box; + ExPolygon logo_poly; + generate_logo_polygon(logo_poly, box); + auto triangles = triangulate_expolygon_2f(logo_poly, NORMALS_UP); + m_logo_triangles.reset(); + if (!m_logo_triangles.init_model_from_poly(triangles, GROUND_Z + 0.01f)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":error :Unable to create logo triangles in set_logo_box_by_bed\n"; + return; + } + } +} + void PartPlate::generate_print_polygon(ExPolygon &print_polygon) { auto compute_points = [&print_polygon](Vec2d& center, double radius, double start_angle, double stop_angle, int count) @@ -2686,38 +2306,20 @@ bool PartPlate::set_shape(const Pointfs& shape, const Pointfs& exclude_areas, Ve ExPolygon logo_poly; generate_logo_polygon(logo_poly); - if (!m_logo_triangles.set_from_triangles(triangulate_expolygon_2f(logo_poly, NORMALS_UP), GROUND_Z+0.02f)) + auto triangles = triangulate_expolygon_2f(logo_poly, NORMALS_UP); + m_logo_triangles.reset(); + if (!m_logo_triangles.init_model_from_poly(triangles, GROUND_Z + 0.01f)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create logo triangles\n"; else { ; } + BoundingBoxf3 box_in_plate_origin; + if (calc_bed_3d_boundingbox(box_in_plate_origin)) { + if ((m_cur_bed_boundingbox.center() - box_in_plate_origin.center()).norm() > 1.0f) { + set_logo_box_by_bed(box_in_plate_origin); + } + } - ExPolygon poly; - /*for (const Vec2d& p : m_shape) { - poly.contour.append({ scale_(p(0)), scale_(p(1)) }); - }*/ - generate_print_polygon(poly); - calc_triangles(poly); - - ExPolygon exclude_poly; - /*for (const Vec2d& p : m_exclude_area) { - exclude_poly.contour.append({ scale_(p(0)), scale_(p(1)) }); - }*/ - generate_exclude_polygon(exclude_poly); - calc_exclude_triangles(exclude_poly); - - const BoundingBox& pp_bbox = poly.contour.bounding_box(); - calc_gridlines(poly, pp_bbox); - - //calc_vertex_for_icons_background(5, m_del_and_background_icon); - //calc_vertex_for_icons(4, m_del_icon); - calc_vertex_for_icons(0, m_del_icon); - calc_vertex_for_icons(1, m_orient_icon); - calc_vertex_for_icons(2, m_arrange_icon); - calc_vertex_for_icons(3, m_lock_icon); - calc_vertex_for_icons(4, m_plate_settings_icon); - //calc_vertex_for_number(0, (m_plate_index < 9), m_plate_idx_icon); - calc_vertex_for_number(0, false, m_plate_idx_icon); calc_vertex_for_plate_name(m_name_texture, m_plate_name_icon);//if (generate_plate_name_texture()) calc_vertex_for_plate_name_edit_icon(&m_name_texture, 0, m_plate_name_edit_icon); } @@ -2771,40 +2373,41 @@ bool PartPlate::intersects(const BoundingBoxf3& bb) const void PartPlate::render(bool bottom, bool only_body, bool force_background_color, HeightLimitMode mode, int hover_id, bool render_cali) { - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + const Camera &camera = wxGetApp().plater()->get_camera(); + auto view_mat = camera.get_view_matrix(); + auto proj_mat = camera.get_projection_matrix(); + { + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + shader->start_using(); + shader->set_uniform("view_model_matrix", view_mat); + shader->set_uniform("projection_matrix", proj_mat); - if (!bottom) { - // draw background - render_background(force_background_color); - - render_exclude_area(force_background_color); - } - - render_grid(bottom); - - if (!bottom && m_selected && !force_background_color) { - if (m_partplate_list) - render_logo(bottom, m_partplate_list->render_cali_logo && render_cali); - else - render_logo(bottom); - } - - render_height_limit(mode); - - render_icons(bottom, only_body, hover_id); - if (!force_background_color){ - render_only_numbers(bottom); - } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisable(GL_BLEND)); - - //if (with_label) { - // render_label(canvas); - //} - glsafe(::glDisable(GL_DEPTH_TEST)); + render_height_limit(mode); + shader->stop_using(); + } + { + GLShaderProgram *shader = wxGetApp().get_shader("printbed"); + shader->start_using(); + auto model_mat = m_partplate_list->m_plate_trans[m_plate_index].get_matrix(); + shader->set_uniform("view_model_matrix", view_mat * model_mat); + shader->set_uniform("projection_matrix", proj_mat); + shader->set_uniform("svg_source", 0); + shader->set_uniform("transparent_background", 0); + if (!bottom && m_selected && !force_background_color) {//bed all icon + if (m_partplate_list) + render_logo(bottom, m_partplate_list->render_cali_logo && render_cali); + else + render_logo(bottom); + } + { + shader->set_uniform("transparent_background", bottom); + render_icons(bottom, only_body, hover_id); + if (!force_background_color) { + render_numbers(bottom); + } + } + shader->stop_using(); + } } void PartPlate::set_selected() { @@ -3204,6 +2807,7 @@ void PartPlateList::init() first_plate = new PartPlate(this, Vec3d(0.0, 0.0, 0.0), m_plate_width, m_plate_depth, m_plate_height, m_plater, m_model, true, printer_technology); assert(first_plate != NULL); m_plate_list.push_back(first_plate); + update_plate_trans(1); m_print_index = 0; if (printer_technology == ptFFF) @@ -3232,6 +2836,255 @@ void PartPlateList::init() m_intialized = true; } +void PartPlateList::update_plate_trans(int count) +{ + m_update_plate_mats_vbo = true; + m_plate_trans.resize(count); + int cols = compute_colum_count(count); + for (size_t i = 0; i < count; i++) { + Vec2d pos = compute_shape_position(i, cols); + Vec3d plate_origin= Vec3d(pos.x(), pos.y(), 0); + m_plate_trans[i].set_offset(plate_origin); + } + update_unselected_plate_trans(count); +} + +void PartPlateList::update_unselected_plate_trans(int count) { + if (count == 1) { + m_unselected_plate_trans.clear(); + return; + } + m_update_unselected_plate_mats_vbo = true; + m_unselected_plate_trans.resize(count - 1); + int cols = compute_colum_count(count); + int index = 0; + for (size_t i = 0; i < count; i++) { + if (i == m_current_plate) { continue; } + Vec2d pos = compute_shape_position(i, cols); + Vec3d plate_origin = Vec3d(pos.x(), pos.y(), 0); + m_unselected_plate_trans[index].set_offset(plate_origin); + index++; + } +} + +void PartPlateList::generate_print_polygon(ExPolygon &print_polygon) +{ + auto compute_points = [&print_polygon](Vec2d ¢er, double radius, double start_angle, double stop_angle, int count) { + double angle_steps; + angle_steps = (stop_angle - start_angle) / (count - 1); + for (int j = 0; j < count; j++) { + double angle = start_angle + j * angle_steps; + double x = center(0) + ::cos(angle) * radius; + double y = center(1) + ::sin(angle) * radius; + print_polygon.contour.append({scale_(x), scale_(y)}); + } + }; + + int points_count = 8; + if (m_shape.size() == 4) { + // rectangle case + for (int i = 0; i < 4; i++) { + const Vec2d &p = m_shape[i]; + Vec2d center; + double start_angle, stop_angle, radius_x, radius_y, radius; + switch (i) { + case 0: + radius = 5.f; + center(0) = p(0) + radius; + center(1) = p(1) + radius; + start_angle = PI; + stop_angle = 1.5 * PI; + compute_points(center, radius, start_angle, stop_angle, points_count); + break; + case 1: print_polygon.contour.append({scale_(p(0)), scale_(p(1))}); break; + case 2: + radius_x = (int) (p(0)) % 10; + radius_y = (int) (p(1)) % 10; + radius = (radius_x > radius_y) ? radius_y : radius_x; + if (radius < 5.0) radius = 5.f; + center(0) = p(0) - radius; + center(1) = p(1) - radius; + start_angle = 0; + stop_angle = 0.5 * PI; + compute_points(center, radius, start_angle, stop_angle, points_count); + break; + case 3: + radius_x = (int) (p(0)) % 10; + radius_y = (int) (p(1)) % 10; + radius = (radius_x > radius_y) ? radius_y : radius_x; + if (radius < 5.0) radius = 5.f; + center(0) = p(0) + radius; + center(1) = p(1) - radius; + start_angle = 0.5 * PI; + stop_angle = PI; + compute_points(center, radius, start_angle, stop_angle, points_count); + break; + } + } + } else { + for (const Vec2d &p : m_shape) { + print_polygon.contour.append({scale_(p(0)), scale_(p(1))}); + } + } +} + +void PartPlateList::generate_exclude_polygon(ExPolygon &exclude_polygon) +{ + auto compute_exclude_points = [&exclude_polygon](Vec2d ¢er, double radius, double start_angle, double stop_angle, int count) { + double angle_steps; + angle_steps = (stop_angle - start_angle) / (count - 1); + for (int j = 0; j < count; j++) { + double angle = start_angle + j * angle_steps; + double x = center(0) + ::cos(angle) * radius; + double y = center(1) + ::sin(angle) * radius; + exclude_polygon.contour.append({scale_(x), scale_(y)}); + } + }; + + int points_count = 8; + if (m_exclude_areas.size() == 4) { + // rectangle case + for (int i = 0; i < 4; i++) { + const Vec2d &p = m_exclude_areas[i]; + Vec2d center; + double start_angle, stop_angle, radius; + switch (i) { + case 0: + radius = 5.f; + center(0) = p(0) + radius; + center(1) = p(1) + radius; + start_angle = PI; + stop_angle = 1.5 * PI; + compute_exclude_points(center, radius, start_angle, stop_angle, points_count); + break; + case 1: exclude_polygon.contour.append({scale_(p(0)), scale_(p(1))}); break; + case 2: + radius = 3.f; + center(0) = p(0) - radius; + center(1) = p(1) - radius; + start_angle = 0; + stop_angle = 0.5 * PI; + compute_exclude_points(center, radius, start_angle, stop_angle, points_count); + break; + case 3: exclude_polygon.contour.append({scale_(p(0)), scale_(p(1))}); break; + } + } + } else { + for (const Vec2d &p : m_exclude_areas) { + exclude_polygon.contour.append({scale_(p(0)), scale_(p(1))}); + } + } +} + +void PartPlateList::calc_triangles(const ExPolygon &poly) +{ + auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); + m_triangles.reset(); + if (!m_triangles.init_model_from_poly(triangles, GROUND_Z)) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create plate triangles\n"; +} + +void PartPlateList::calc_exclude_triangles(const ExPolygon &poly) +{ + if (poly.empty()) { + m_exclude_triangles.reset(); + return; + } + auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); + m_exclude_triangles.reset(); + if (!m_exclude_triangles.init_model_from_poly(triangles, GROUND_Z)) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create plate triangles\n"; +} + +void PartPlateList::calc_gridlines(const ExPolygon &poly, const BoundingBox &pp_bbox) +{ + Polylines axes_lines, axes_lines_bolder; + int count = 0; + for (coord_t x = pp_bbox.min(0); x <= pp_bbox.max(0); x += scale_(10.0)) { + Polyline line; + line.append(Point(x, pp_bbox.min(1))); + line.append(Point(x, pp_bbox.max(1))); + + if ((count % 5) == 0) + axes_lines_bolder.push_back(line); + else + axes_lines.push_back(line); + count++; + } + count = 0; + for (coord_t y = pp_bbox.min(1); y <= pp_bbox.max(1); y += scale_(10.0)) { + Polyline line; + line.append(Point(pp_bbox.min(0), y)); + line.append(Point(pp_bbox.max(0), y)); + axes_lines.push_back(line); + + if ((count % 5) == 0) + axes_lines_bolder.push_back(line); + else + axes_lines.push_back(line); + count++; + } + + // 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))); + Lines gridlines_bolder = to_lines(intersection_pl(axes_lines_bolder, 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)); + + m_gridlines.reset(); + if (!m_gridlines.init_model_from_lines(gridlines, GROUND_Z)) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create bed grid lines\n"; + m_gridlines_bolder.reset(); + if (!m_gridlines_bolder.init_model_from_lines(gridlines_bolder, GROUND_Z)) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to create bed grid lines\n"; +} + +void PartPlateList::calc_vertex_for_number(int index, bool one_number, GLModel &gl_model) +{ + ExPolygon poly; +#if 0 // in the up area + Vec2d& p = m_shape[2]; + float offset_x = one_number?PARTPLATE_TEXT_OFFSET_X1: PARTPLATE_TEXT_OFFSET_X2; + + poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP) - PARTPLATE_ICON_GAP - PARTPLATE_ICON_SIZE + PARTPLATE_TEXT_OFFSET_Y) }); + poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP)- PARTPLATE_ICON_GAP - PARTPLATE_ICON_SIZE + PARTPLATE_TEXT_OFFSET_Y) }); + poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP)- PARTPLATE_ICON_GAP - PARTPLATE_TEXT_OFFSET_Y)}); + poly.contour.append({ scale_(p(0) + PARTPLATE_ICON_GAP + offset_x), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP)- PARTPLATE_ICON_GAP - PARTPLATE_TEXT_OFFSET_Y) }); +#else // in the bottom + Vec2d &p = m_shape[1]; + float offset_x = one_number ? PARTPLATE_TEXT_OFFSET_X1 : PARTPLATE_TEXT_OFFSET_X2; + + poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) + PARTPLATE_TEXT_OFFSET_Y)}); + poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) + PARTPLATE_TEXT_OFFSET_Y)}); + poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE - offset_x), scale_(p(1) + PARTPLATE_ICON_SIZE - PARTPLATE_TEXT_OFFSET_Y)}); + poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + offset_x), scale_(p(1) + PARTPLATE_ICON_SIZE - PARTPLATE_TEXT_OFFSET_Y)}); +#endif + auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); + gl_model.reset(); + if (!gl_model.init_model_from_poly(triangles, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create plate triangles\n"; +} + +void PartPlateList::calc_vertex_for_icons(int index, GLModel &gl_model) +{ + ExPolygon poly; + Vec2d & p = m_shape[2]; + + poly.contour.append( + {scale_(p(0) + PARTPLATE_ICON_GAP_LEFT), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y) - PARTPLATE_ICON_GAP_TOP - PARTPLATE_ICON_SIZE)}); + poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE), + scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y) - PARTPLATE_ICON_GAP_TOP - PARTPLATE_ICON_SIZE)}); + poly.contour.append( + {scale_(p(0) + PARTPLATE_ICON_GAP_LEFT + PARTPLATE_ICON_SIZE), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y) - PARTPLATE_ICON_GAP_TOP)}); + poly.contour.append({scale_(p(0) + PARTPLATE_ICON_GAP_LEFT), scale_(p(1) - index * (PARTPLATE_ICON_SIZE + PARTPLATE_ICON_GAP_Y) - PARTPLATE_ICON_GAP_TOP)}); + + auto triangles = triangulate_expolygon_2f(poly, NORMALS_UP); + gl_model.reset(); + if (!gl_model.init_model_from_poly(triangles, GROUND_Z)) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Unable to generate geometry buffers for icons\n"; +} + //compute the origin for printable plate with index i Vec3d PartPlateList::compute_origin(int i, int cols) { @@ -3630,6 +3483,10 @@ void PartPlateList::reinit() return; } +void PartPlateList::set_bed3d(Bed3D *_bed3d) { + m_bed3d = _bed3d; +} + /*basic plate operations*/ //create an empty plate, and return its index //these model instances which are not in any plates should not be affected also @@ -3743,6 +3600,14 @@ int PartPlateList::destroy_print(int print_index) return result; } +void PartPlateList::add_plate() { + if (m_plater) m_plater->take_snapshot("add partplate"); + create_plate(); + int new_plate = get_plate_count() - 1; + select_plate(new_plate); + update_plate_trans(get_plate_count()); +} + //delete a plate by index //keep its instance at origin position and add them into next plate if have //update the plate index and position after it @@ -3850,7 +3715,7 @@ int PartPlateList::delete_plate(int index) destroy_print(print_index); delete plate; - + update_plate_trans(get_plate_count()); // FIX: context of BackgroundSliceProcess and gcode preview need to be updated before ObjectList::reload_all_plates(). #if 0 if (m_plater != nullptr) { @@ -3947,6 +3812,7 @@ int PartPlateList::select_plate(int index) m_current_plate = index; m_plate_list[m_current_plate]->set_selected(); + update_plate_trans(get_plate_count()); //QDS if(m_model) m_model->curr_plate_index = index; @@ -4050,7 +3916,7 @@ void PartPlateList::update_all_plates_pos_and_size(bool adjust_position, bool wi plate->set_pos_and_size(origin1, m_plate_width, m_plate_depth, m_plate_height, adjust_position, do_clear); // set default wipe pos when switch plate - if (switch_plate_type && m_plater && plate->get_used_extruders().size() <= 0) { + if (switch_plate_type && m_plater/* && plate->get_used_extruders().size() <= 0*/) { set_default_wipe_tower_pos_for_plate(i); } } @@ -4817,18 +4683,199 @@ void PartPlateList::postprocess_arrange_polygon(arrangement::ArrangePolygon& arr } /*rendering related functions*/ +void PartPlateList::render_instance(bool bottom, bool only_current, bool only_body, bool force_background_color, int hover_id, bool show_grid, bool enable_multi_instance) +{ + if (enable_multi_instance) { + if (!only_current) { + if (m_update_plate_mats_vbo) { + m_update_plate_mats_vbo = false; + GLModel::create_or_update_mats_vbo(m_plate_mats_vbo, m_plate_trans); + } + if (m_update_unselected_plate_mats_vbo) { + m_update_unselected_plate_mats_vbo = false; + GLModel::create_or_update_mats_vbo(m_unselected_plate_mats_vbo, m_unselected_plate_trans); + } + } + } + + const Camera &camera = wxGetApp().plater()->get_camera(); + auto view_mat = camera.get_view_matrix(); + auto proj_mat = camera.get_projection_matrix(); + { + auto cur_shader = wxGetApp().get_current_shader(); + if (cur_shader) { + cur_shader->stop_using(); + } + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + {//for selected + shader->start_using(); + shader->set_uniform("view_model_matrix", view_mat * m_plate_trans[m_current_plate].get_matrix()); + shader->set_uniform("projection_matrix", proj_mat); + if (!bottom) { // draw background + render_exclude_area(force_background_color); // for selected_plate + } + if (show_grid) + render_grid(bottom); // for selected_plate + } + if (enable_multi_instance) { + shader->stop_using(); + } + if (!only_current) { + if (enable_multi_instance) { + GLShaderProgram *shader = wxGetApp().get_shader("flat_instance"); + shader->start_using(); + auto res = shader->set_uniform("view_matrix", view_mat); + res = shader->set_uniform("projection_matrix", proj_mat); + if (!bottom) { // draw background + render_instance_background(force_background_color); // for unselected_plate + render_instance_exclude_area(force_background_color); // for unselected_plate + } + render_instance_grid(bottom); // for unselected_plate + + shader->stop_using(); + } + else { + for (size_t i = 0; i < m_unselected_plate_trans.size(); i++) { + shader->set_uniform("view_model_matrix", view_mat * m_unselected_plate_trans[i].get_matrix()); + if (!bottom) { // draw background + render_unselected_background(force_background_color); // for unselected_plate + render_unselected_exclude_area(force_background_color); // for unselected_plate + } + render_unselected_grid(bottom); // for unselected_plate + } + } + } + if (!enable_multi_instance) { + shader->stop_using(); + } + } + +} + +void PartPlateList::render_grid(bool bottom) +{ + // glsafe(::glEnable(GL_MULTISAMPLE)); + // draw grid + glsafe(::glLineWidth(1.0f * m_scale_factor)); + ColorRGBA color; + if (bottom) + color = PartPlate::LINE_BOTTOM_COLOR; + else { + color = m_is_dark ? PartPlate::LINE_TOP_SEL_DARK_COLOR : PartPlate::LINE_TOP_SEL_COLOR; + } + m_gridlines.set_color(color); + m_gridlines.render_geometry(); + + glsafe(::glLineWidth(2.0f * m_scale_factor)); + m_gridlines_bolder.set_color(color); + m_gridlines_bolder.render_geometry(); +} + +void PartPlateList::render_instance_grid(bool bottom) +{ + // draw grid + if (m_unselected_plate_trans.size() == 0) { return; } + glsafe(::glLineWidth(1.0f * m_scale_factor)); + ColorRGBA color; + if (bottom) + color = PartPlate::LINE_BOTTOM_COLOR; + else { + color = m_is_dark ? PartPlate::LINE_TOP_DARK_COLOR : PartPlate::LINE_TOP_COLOR; + } + m_gridlines.set_color(color); + m_gridlines.render_geometry_instance(m_unselected_plate_mats_vbo, m_unselected_plate_trans.size()); + glsafe(::glLineWidth(2.0f * m_scale_factor)); + m_gridlines_bolder.set_color(color); + m_gridlines_bolder.render_geometry_instance(m_unselected_plate_mats_vbo, m_unselected_plate_trans.size()); +} + +void PartPlateList::render_unselected_grid(bool bottom) +{ + glsafe(::glLineWidth(1.0f * m_scale_factor)); + ColorRGBA color; + if (bottom) + color = PartPlate::LINE_BOTTOM_COLOR; + else { + color = m_is_dark ? PartPlate::LINE_TOP_DARK_COLOR : PartPlate::LINE_TOP_COLOR; + } + m_gridlines.set_color(color); + m_gridlines.render_geometry(); + glsafe(::glLineWidth(2.0f * m_scale_factor)); + m_gridlines_bolder.set_color(color); + m_gridlines_bolder.render_geometry(); +} + +void PartPlateList::render_instance_background(bool force_default_color) +{ + if (m_unselected_plate_trans.size() == 0) { return; } + // draw background + ColorRGBA color; + if (!force_default_color) { + color = m_is_dark ? PartPlate::UNSELECT_DARK_COLOR : PartPlate::UNSELECT_COLOR; + } else { + color = PartPlate::DEFAULT_COLOR; + } + m_triangles.set_color(color); + m_triangles.render_geometry_instance(m_unselected_plate_mats_vbo, m_unselected_plate_trans.size()); +} + +void PartPlateList::render_unselected_background(bool force_default_color) +{ + // draw background + ColorRGBA color; + if (!force_default_color) { + color = m_is_dark ? PartPlate::UNSELECT_DARK_COLOR : PartPlate::UNSELECT_COLOR; + } else { + color = PartPlate::DEFAULT_COLOR; + } + m_triangles.set_color(color); + m_triangles.render_geometry(); +} + + +void PartPlateList::render_exclude_area(bool force_default_color) +{ + if (force_default_color || !m_exclude_triangles.is_initialized()) // for thumbnail case + return; + ColorRGBA select_color{0.765f, 0.7686f, 0.7686f, 1.0f}; + // draw exclude area + m_exclude_triangles.set_color(select_color); + m_exclude_triangles.render_geometry(); +} + +void PartPlateList::render_instance_exclude_area(bool force_default_color) +{ + if (force_default_color || !m_exclude_triangles.is_initialized()) // for thumbnail case + return; + if (m_unselected_plate_trans.size() == 0) { return; } + ColorRGBA unselect_color{0.9f, 0.9f, 0.9f, 1.0f}; + // draw exclude area + m_exclude_triangles.set_color(unselect_color); + m_exclude_triangles.render_geometry_instance(m_unselected_plate_mats_vbo, m_unselected_plate_trans.size()); +} + +void PartPlateList::render_unselected_exclude_area(bool force_default_color) +{ + if (force_default_color || !m_exclude_triangles.is_initialized()) // for thumbnail case + return; + ColorRGBA unselect_color{0.9f, 0.9f, 0.9f, 1.0f}; + // draw exclude area + m_exclude_triangles.set_color(unselect_color); + m_exclude_triangles.render_geometry(); +} + //render -void PartPlateList::render(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) +void PartPlateList::render(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid, bool enable_multi_instance) { const std::lock_guard local_lock(m_plates_mutex); std::vector::iterator it = m_plate_list.begin(); - int plate_hover_index = -1; - int plate_hover_action = -1; - if (hover_id != -1) { - plate_hover_index = hover_id / PartPlate::GRABBER_COUNT; - plate_hover_action = hover_id % PartPlate::GRABBER_COUNT; - } + m_plate_hover_index = -1; + m_plate_hover_action = -1; + if (hover_id != -1) { + m_plate_hover_index = hover_id / PartPlate::GRABBER_COUNT; + m_plate_hover_action = hover_id % PartPlate::GRABBER_COUNT; + } static bool last_dark_mode_status = m_is_dark; if (m_is_dark != last_dark_mode_status) { @@ -4836,24 +4883,35 @@ void PartPlateList::render(bool bottom, bool only_current, bool only_body, int h generate_icon_textures(); }else if(m_del_texture.get_id() == 0) generate_icon_textures(); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + glsafe(::glDepthMask(GL_FALSE)); + + render_instance(bottom, only_current, only_body, false, m_plate_hover_action, show_grid, enable_multi_instance); + for (it = m_plate_list.begin(); it != m_plate_list.end(); it++) { int current_index = (*it)->get_index(); if (only_current && (current_index != m_current_plate)) continue; if (current_index == m_current_plate) { PartPlate::HeightLimitMode height_mode = (only_current)?PartPlate::HEIGHT_LIMIT_NONE:m_height_limit_mode; - if (plate_hover_index == current_index) - (*it)->render(bottom, only_body, false, height_mode, plate_hover_action, render_cali); + if (m_plate_hover_index == current_index) + (*it)->render(bottom, only_body, false, height_mode, m_plate_hover_action, render_cali); else (*it)->render(bottom, only_body, false, height_mode, -1, render_cali); } else { - if (plate_hover_index == current_index) - (*it)->render(bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, plate_hover_action, render_cali); + if (m_plate_hover_index == current_index) + (*it)->render(bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, m_plate_hover_action, render_cali); else (*it)->render(bottom, only_body, false, PartPlate::HEIGHT_LIMIT_NONE, -1, render_cali); } } + glsafe(::glDepthMask(GL_TRUE)); + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDisable(GL_DEPTH_TEST)); } void PartPlateList::render_for_picking_pass() @@ -4861,7 +4919,7 @@ void PartPlateList::render_for_picking_pass() const std::lock_guard local_lock(m_plates_mutex); std::vector::iterator it = m_plate_list.begin(); for (it = m_plate_list.begin(); it != m_plate_list.end(); it++) { - (*it)->render_for_picking(); + (*it)->on_render_for_picking(); } } @@ -4960,7 +5018,29 @@ bool PartPlateList::set_shapes(const Pointfs& shape, const Pointfs& exclude_area calc_bounding_boxes(); update_logo_texture_filename(texture_filename); + update_plate_trans(get_plate_count()); + { // prepare render data + ExPolygon poly; + generate_print_polygon(poly); + calc_triangles(poly); + + ExPolygon exclude_poly; + generate_exclude_polygon(exclude_poly); + calc_exclude_triangles(exclude_poly); + + const BoundingBox &pp_bbox = poly.contour.bounding_box(); + calc_gridlines(poly, pp_bbox); + + // calc_vertex_for_icons(4, m_del_icon); + calc_vertex_for_icons(0, m_del_icon); + calc_vertex_for_icons(1, m_orient_icon); + calc_vertex_for_icons(2, m_arrange_icon); + calc_vertex_for_icons(3, m_lock_icon); + calc_vertex_for_icons(4, m_plate_settings_icon); + calc_vertex_for_icons(5, m_plate_filament_map_icon); + calc_vertex_for_number(0, false, m_plate_idx_icon); + } return true; } @@ -4972,8 +5052,10 @@ void PartPlateList::update_logo_texture_filename(const std::string &texture_file }; if (!texture_filename.empty() && !check_texture(texture_filename)) { BOOST_LOG_TRIVIAL(error) << "Unable to load bed texture: " << texture_filename; - } else + } else { m_logo_texture_filename = texture_filename; + std::replace(m_logo_texture_filename.begin(), m_logo_texture_filename.end(), '\\', '/'); + } } /*slice related functions*/ @@ -5125,12 +5207,13 @@ int PartPlateList::rebuild_plates_after_deserialize(std::vector& previous_ BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": plates count %1%") % m_plate_list.size(); update_plate_cols(); update_all_plates_pos_and_size(false, false, false, false); + for (unsigned int i = 0; i < (unsigned int) m_plate_list.size(); ++i) { + m_plate_list[i]->m_partplate_list = this; + }//set_shapes api: every plate use m_partplate_list set_shapes(m_shape, m_exclude_areas, m_logo_texture_filename, m_height_to_lid, m_height_to_rod); - for (unsigned int i = 0; i < (unsigned int)m_plate_list.size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_plate_list.size(); ++i){ bool need_reset_print = false; m_plate_list[i]->m_plater = this->m_plater; - m_plate_list[i]->m_partplate_list = this; m_plate_list[i]->m_model = this->m_model; m_plate_list[i]->printer_technology = this->printer_technology; //check the previous sliced result @@ -5511,17 +5594,9 @@ void PartPlateList::BedTextureInfo::TexturePart::update_buffer() } } - if (!buffer) - buffer = new GeometryBuffer(); - - if (buffer->set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z + 0.02f)) { - release_vbo(); - unsigned int* vbo_id_ptr = const_cast(&vbo_id); - glsafe(::glGenBuffers(1, vbo_id_ptr)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id_ptr)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)buffer->get_vertices_data_size(), (const GLvoid*)buffer->get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } else { + if (!buffer) buffer = new GLModel(); + buffer->reset(); + if (!buffer->init_model_from_poly(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z + 0.02f)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create buffer triangles\n"; } } @@ -5554,18 +5629,23 @@ void PartPlateList::BedTextureInfo::reset() void PartPlateList::init_bed_type_info() { - BedTextureInfo::TexturePart pc_part1(10, 130, 10, 110, "qdt_bed_pc_left.svg"); - BedTextureInfo::TexturePart pc_part2(74, -10, 148, 12, "qdt_bed_pc_bottom.svg"); - BedTextureInfo::TexturePart ep_part1(7.5, 90, 12.5, 150, "qdt_bed_ep_left.svg"); - BedTextureInfo::TexturePart ep_part2(74, -10, 148, 12, "qdt_bed_ep_bottom.svg"); - BedTextureInfo::TexturePart pei_part1(7.5, 50, 12.5, 190, "qdt_bed_pei_left.svg"); - BedTextureInfo::TexturePart pei_part2(74, -10, 148, 12, "qdt_bed_pei_bottom.svg"); - BedTextureInfo::TexturePart pte_part1(10, 80, 10, 160, "qdt_bed_pte_left.svg"); - BedTextureInfo::TexturePart pte_part2(74, -10, 148, 12, "qdt_bed_pte_bottom.svg"); + BedTextureInfo::TexturePart st_part1(10, 52, 8.393f, 192, "qdt_bed_st_left.svg"); + BedTextureInfo::TexturePart st_part2(74, -10, 148, 12, "qdt_bed_st_bottom.svg"); + BedTextureInfo::TexturePart pc_part1(10, 52, 8.393f, 192, "qdt_bed_pc_left.svg"); + BedTextureInfo::TexturePart pc_part2(74, -10, 148, 12, "qdt_bed_pc_bottom.svg"); + BedTextureInfo::TexturePart ep_part1(10, 52, 8.393f, 192, "qdt_bed_ep_left.svg"); + BedTextureInfo::TexturePart ep_part2(74, -10, 148, 12, "qdt_bed_ep_bottom.svg"); + BedTextureInfo::TexturePart pei_part1(10, 52, 8.393f, 192, "qdt_bed_pei_left.svg"); + BedTextureInfo::TexturePart pei_part2(74, -10, 148, 12, "qdt_bed_pei_bottom.svg"); + BedTextureInfo::TexturePart pte_part1(10, 52, 8.393f, 192, "qdt_bed_pte_left.svg"); + BedTextureInfo::TexturePart pte_part2(74, -10, 148, 12, "qdt_bed_pte_bottom.svg"); + for (size_t i = 0; i < btCount; i++) { bed_texture_info[i].reset(); bed_texture_info[i].parts.clear(); } + bed_texture_info[btSuperTack].parts.push_back(st_part1); + bed_texture_info[btSuperTack].parts.push_back(st_part2); bed_texture_info[btPC].parts.push_back(pc_part1); bed_texture_info[btPC].parts.push_back(pc_part2); bed_texture_info[btEP].parts.push_back(ep_part1); @@ -5584,8 +5664,13 @@ void PartPlateList::init_bed_type_info() float y_rate = bed_height / base_height; for (int i = 0; i < btCount; i++) { for (int j = 0; j < bed_texture_info[i].parts.size(); j++) { - bed_texture_info[i].parts[j].x *= x_rate; - bed_texture_info[i].parts[j].y *= y_rate; + if (j == 0 && (bed_width == 180 && bed_height == 180)) { + bed_texture_info[i].parts[j].x = 10; + bed_texture_info[i].parts[j].y = 35; + } else { + bed_texture_info[i].parts[j].x *= x_rate; + bed_texture_info[i].parts[j].y *= y_rate; + } bed_texture_info[i].parts[j].w *= x_rate; bed_texture_info[i].parts[j].h *= y_rate; bed_texture_info[i].parts[j].update_buffer(); diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 257b4d3..917dea6 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -70,7 +70,7 @@ class Plater; class GLCanvas3D; struct Camera; class PartPlateList; - +class Bed3D; using GCodeResult = GCodeProcessorResult; class PartPlate : public ObjectBase @@ -121,38 +121,17 @@ private: Pointfs m_exclude_area; BoundingBoxf3 m_bounding_box; BoundingBoxf3 m_extended_bounding_box; + BoundingBoxf3 m_cur_bed_boundingbox; mutable std::vector m_exclude_bounding_box; mutable BoundingBoxf3 m_grabber_box; Transform3d m_grabber_trans_matrix; - Slic3r::Geometry::Transformation position; - std::vector positions; - unsigned int m_vbo_id{ 0 }; - GeometryBuffer m_triangles; - GeometryBuffer m_exclude_triangles; - GeometryBuffer m_logo_triangles; - GeometryBuffer m_gridlines; - GeometryBuffer m_gridlines_bolder; - GeometryBuffer m_height_limit_common; - GeometryBuffer m_height_limit_bottom; - GeometryBuffer m_height_limit_top; - GeometryBuffer m_del_icon; - GeometryBuffer m_plate_name_edit_icon; - //GeometryBuffer m_del_and_background_icon; - mutable unsigned int m_del_vbo_id{ 0 }; - GeometryBuffer m_arrange_icon; - mutable unsigned int m_arrange_vbo_id{ 0 }; - GeometryBuffer m_orient_icon; - mutable unsigned int m_orient_vbo_id{ 0 }; - GeometryBuffer m_lock_icon; - mutable unsigned int m_lock_vbo_id{ 0 }; - GeometryBuffer m_plate_settings_icon; - mutable unsigned int m_plate_settings_vbo_id{ 0 }; - GeometryBuffer m_plate_idx_icon; - mutable unsigned int m_plate_idx_vbo_id{ 0 }; - mutable unsigned int m_plate_name_edit_vbo_id{0}; - GLTexture m_texture; - mutable float m_grabber_color[4]; + GLModel m_logo_triangles; + GLModel m_height_limit_common; + GLModel m_height_limit_bottom; + GLModel m_height_limit_top; + GLModel m_plate_name_edit_icon; + float m_scale_factor{ 1.0f }; GLUquadricObject* m_quadric; int m_hover_id; @@ -165,7 +144,7 @@ private: // part plate name std::string m_name; // utf8 string bool m_name_change = false; - GeometryBuffer m_plate_name_icon; + GLModel m_plate_name_icon; mutable unsigned int m_plate_name_vbo_id{0}; GLTexture m_name_texture; @@ -174,42 +153,39 @@ private: void generate_print_polygon(ExPolygon &print_polygon); void generate_exclude_polygon(ExPolygon &exclude_polygon); void generate_logo_polygon(ExPolygon &logo_polygon); + void generate_logo_polygon(ExPolygon &logo_polygon,const BoundingBoxf3& box); void calc_bounding_boxes() const; - void calc_triangles(const ExPolygon& poly); - void calc_exclude_triangles(const ExPolygon& poly); - void calc_gridlines(const ExPolygon& poly, const BoundingBox& pp_bbox); void calc_height_limit(); - void calc_vertex_for_number(int index, bool one_number, GeometryBuffer &buffer); - void calc_vertex_for_plate_name(GLTexture& texture, GeometryBuffer &buffer); - void calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int index, GeometryBuffer &buffer); - void calc_vertex_for_icons(int index, GeometryBuffer &buffer); + + void calc_vertex_for_plate_name(GLTexture &texture, GLModel &buffer); + void calc_vertex_for_plate_name_edit_icon(GLTexture *texture, int index, GLModel &buffer); void calc_vertex_for_icons_background(int icon_count, GeometryBuffer &buffer); - void render_background(bool force_default_color = false) const; - void render_logo(bool bottom, bool render_cali = true) const; - void render_logo_texture(GLTexture& logo_texture, const GeometryBuffer& logo_buffer, bool bottom, unsigned int vbo_id) const; - void render_exclude_area(bool force_default_color) const; - //void render_background_for_picking(const float* render_color) const; - void render_grid(bool bottom) const; - void render_height_limit(PartPlate::HeightLimitMode mode = HEIGHT_LIMIT_BOTH) const; + bool calc_bed_3d_boundingbox(BoundingBoxf3 & box_in_plate_origin); + void render_logo(bool bottom, bool render_cali = true); + void render_logo_texture(GLTexture &logo_texture, GLModel &logo_buffer, bool bottom); + + void render_height_limit(PartPlate::HeightLimitMode mode = HEIGHT_LIMIT_BOTH); void render_label(GLCanvas3D& canvas) const; void render_grabber(const float* render_color, bool use_lighting) const; void render_face(float x_size, float y_size) const; void render_arrows(const float* render_color, bool use_lighting) const; void render_left_arrow(const float* render_color, bool use_lighting) const; void render_right_arrow(const float* render_color, bool use_lighting) const; - void render_icon_texture(int position_id, int tex_coords_id, const GeometryBuffer &buffer, GLTexture &texture, unsigned int &vbo_id) const; - void render_plate_name_texture(int position_id, int tex_coords_id); + + void render_icon_texture(GLModel &buffer, GLTexture &texture); + void render_plate_name_texture(); void render_icons(bool bottom, bool only_body = false, int hover_id = -1); - void render_only_numbers(bool bottom) const; - void render_rectangle_for_picking(const GeometryBuffer &buffer, const float* render_color) const; - void on_render_for_picking() const; + + void render_numbers(bool bottom); + void on_render_for_picking(); std::array picking_color_component(int idx) const; void release_opengl_resource(); public: static const unsigned int PLATE_BASE_ID = 255 * 255 * 253; - static const unsigned int PLATE_NAME_HOVER_ID = 6; + //static const unsigned int PLATE_FILAMENT_MAP_ID = 6;//todo static const unsigned int GRABBER_COUNT = 7; + static const unsigned int PLATE_NAME_ID = GRABBER_COUNT-1; static std::array SELECT_COLOR; static std::array UNSELECT_COLOR; @@ -240,6 +216,7 @@ public: void reset_bed_type(); DynamicPrintConfig* config() { return &m_config; } + void set_logo_box_by_bed(const BoundingBoxf3& box); // set print sequence per plate //bool print_seq_same_global = true; void set_print_seq(PrintSequence print_seq = PrintSequence::ByDefault); @@ -303,6 +280,8 @@ public: Vec2d get_size() const { return Vec2d(m_width, m_depth); } ModelObjectPtrs get_objects() { return m_model->objects; } ModelObjectPtrs get_objects_on_this_plate(); + std::set>& get_obj_and_inst_set() { return obj_to_instance_set; } + std::set>& get_obj_and_inst_outside_set() { return instance_outside_set; } ModelInstance* get_instance(int obj_id, int instance_id); BoundingBoxf3 get_objects_bounding_box(); @@ -372,7 +351,6 @@ public: bool intersects(const BoundingBoxf3& bb) const; void render(bool bottom, bool only_body = false, bool force_background_color = false, HeightLimitMode mode = HEIGHT_LIMIT_NONE, int hover_id = -1, bool render_cali = false); - void render_for_picking() const { on_render_for_picking(); } void set_selected(); void set_unselected(); void set_hover_id(int id) { m_hover_id = id; } @@ -439,7 +417,9 @@ public: { bool result = m_slice_result_valid; if (result) - result = m_gcode_result ? (!m_gcode_result->toolpath_outside) : false;// && !m_gcode_result->conflict_result.has_value() gcode conflict can also print + result = m_gcode_result ? + (!m_gcode_result->toolpath_outside && !m_gcode_result->filament_printable_reuslt.has_value()) : + false;// && !m_gcode_result->conflict_result.has_value() gcode conflict can also print return result; } @@ -534,6 +514,7 @@ class PartPlateList : public ObjectBase { Plater* m_plater; //Plater reference, not own it Model* m_model; //Model reference, not own it + Bed3D * m_bed3d{nullptr}; PrinterTechnology printer_technology; std::vector m_plate_list; @@ -600,6 +581,39 @@ class PartPlateList : public ObjectBase friend class cereal::access; friend class UndoRedo::StackImpl; friend class PartPlate; +//render plate repetitive object and so on +private: + void generate_print_polygon(ExPolygon &print_polygon); + void generate_exclude_polygon(ExPolygon &exclude_polygon); + void calc_triangles(const ExPolygon &poly); + void calc_vertex_for_icons(int index, GLModel &gl_model); + void calc_exclude_triangles(const ExPolygon &poly); + void calc_gridlines(const ExPolygon &poly, const BoundingBox &pp_bbox); + void calc_vertex_for_number(int index, bool one_number, GLModel &gl_model); +private: + int m_plate_hover_index{-1}; + int m_plate_hover_action{-1}; + + std::vector m_plate_trans; // MAX_PLATE_COUNT + unsigned int m_plate_mats_vbo{0}; + bool m_update_plate_mats_vbo{true}; + + std::vector m_unselected_plate_trans; + unsigned int m_unselected_plate_mats_vbo{0}; + bool m_update_unselected_plate_mats_vbo{true}; + + GLModel m_triangles; + GLModel m_exclude_triangles; + GLModel m_gridlines; + GLModel m_gridlines_bolder; + GLModel m_del_icon; + GLModel m_arrange_icon; + GLModel m_orient_icon; + GLModel m_lock_icon; + GLModel m_plate_settings_icon; + GLModel m_plate_filament_map_icon; + GLModel m_plate_idx_icon; + float m_scale_factor{1.0f}; public: class BedTextureInfo { @@ -615,7 +629,7 @@ public: std::string filename; GLTexture* texture { nullptr }; Vec2d offset; - GeometryBuffer* buffer { nullptr }; + GLModel* buffer{nullptr}; TexturePart(float xx, float yy, float ww, float hh, std::string file){ x = xx; y = yy; w = ww; h = hh; @@ -665,9 +679,12 @@ public: //compute the origin for printable plate with index i using new width Vec3d compute_origin_using_new_size(int i, int new_width, int new_depth); + void update_plate_trans(int count); + void update_unselected_plate_trans(int count); //reset partplate to init states void reinit(); - + void set_bed3d(Bed3D* _bed3d); + Bed3D *get_bed3d() { return m_bed3d; } //get the plate stride double plate_stride_x(); double plate_stride_y(); @@ -684,6 +701,7 @@ public: //destroy print which has the index of print_index int destroy_print(int print_index); + void add_plate(); //delete a plate by index int delete_plate(int index); @@ -793,8 +811,24 @@ public: void postprocess_arrange_polygon(arrangement::ArrangePolygon& arrange_polygon, bool selected); /*rendering related functions*/ + void render_instance(bool bottom, + bool only_current = false, + bool only_body = false, + bool force_background_color = false, + int hover_id = -1, + bool show_grid = true, + bool enable_multi_instance = true); + void render_instance_grid(bool bottom); + void render_unselected_grid(bool bottom); + void render_instance_background(bool force_default_color = false); + void render_unselected_background(bool force_default_color); + void render_grid(bool bottom); + void render_exclude_area(bool force_default_color); + void render_instance_exclude_area(bool force_default_color); + void render_unselected_exclude_area(bool force_default_color); + void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; } - void render(bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false); + void render(bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true, bool enable_multi_instance = true); void render_for_picking_pass(); void set_render_option(bool bedtype_texture, bool plate_settings); void set_render_cali(bool value = true) { render_cali_logo = value; } diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 11000f5..210c364 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -132,6 +132,8 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_ m_config->set_key_value("printhost_apikey", new ConfigOptionString("")); } m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); + //y47 + m_optgroup->set_label_width(15); auto button_sizer = new wxBoxSizer(wxHORIZONTAL); @@ -250,17 +252,17 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr // Set a wider width for a better alignment Option option = m_optgroup->get_option("print_host"); // option.opt.width = Field::def_width_wider(); - // y21 - option.opt.width = 15; + // 47 + option.opt.width = 20; Line host_line = m_optgroup->create_single_option_line(option); host_line.append_widget(printhost_browse); host_line.append_widget(print_host_test); m_optgroup->append_line(host_line); - //y40 + //y47 option = m_optgroup->get_option("printhost_apikey"); - option.opt.width = 15; + option.opt.width = 20; m_optgroup->append_single_option_line(option); m_optgroup->activate(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 73bb8be..1a85946 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -58,7 +58,6 @@ #include "libslic3r/PresetBundle.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ObjColorUtils.hpp" - // For stl export #include "libslic3r/CSGMesh/ModelToCSGMesh.hpp" #include "libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp" @@ -89,6 +88,8 @@ #include "Jobs/SLAImportJob.hpp" #include "Jobs/PrintJob.hpp" #include "Jobs/NotificationProgressIndicator.hpp" +#include "Jobs/PlaterWorker.hpp" +#include "Jobs/BoostThreadWorker.hpp" #include "BackgroundSlicingProcess.hpp" #include "SelectMachine.hpp" #include "SendMultiMachinePage.hpp" @@ -108,6 +109,7 @@ #include "MsgDialog.hpp" #include "ProjectDirtyStateManager.hpp" #include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification +#include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file // QDS #include "Widgets/ProgressDialog.hpp" @@ -140,6 +142,7 @@ #include "PlateSettingsDialog.hpp" #include "DailyTips.hpp" #include "CreatePresetsDialog.hpp" +#include "StepMeshDialog.hpp" using boost::optional; namespace fs = boost::filesystem; @@ -200,13 +203,18 @@ bool Plater::has_illegal_filename_characters(const std::string& name) for (size_t i = 0; i < std::strlen(illegal_characters); i++) if (name.find_first_of(illegal_characters[i]) != std::string::npos) return true; - + std::array escape_characters = {"<", ">", "&", """, "'"}; + for (auto escape : escape_characters) { + if (boost::contains(name, escape)) { + return true; + } + } return false; } void Plater::show_illegal_characters_warning(wxWindow* parent) { - show_error(parent, _L("Invalid name, the following characters are not allowed:") + " <>:/\\|?*\""); + show_error(parent, _L("Invalid name, the following characters are not allowed:") + " <>:/\\|?*\"" +_L("(Including its escape characters)")); } enum SlicedInfoIdx @@ -463,7 +471,6 @@ void Sidebar::priv::show_rich_tip(const wxString& tooltip, wxButton* btn) } } - void Sidebar::priv::hide_rich_tip(wxButton* btn) { if (wxRichToolTipPopup* popup = get_rtt_popup(btn)) @@ -728,6 +735,7 @@ Sidebar::Sidebar(Plater *parent) // Bed type selection wxBoxSizer* bed_type_sizer = new wxBoxSizer(wxHORIZONTAL); wxStaticText* bed_type_title = new wxStaticText(p->m_panel_printer_content, wxID_ANY, _L("Bed type")); + bed_type_title->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#000000"))); //bed_type_title->SetBackgroundColour(); bed_type_title->Wrap(-1); bed_type_title->SetFont(Label::Body_14); @@ -754,7 +762,7 @@ Sidebar::Sidebar(Plater *parent) // SetCursor(wxCURSOR_ARROW); // }); // bed_type_title->Bind(wxEVT_LEFT_UP, [bed_type_title, this](wxMouseEvent &e) { - // wxLaunchDefaultBrowser("https://wiki.qidilab.com/en/x1/manual/compatibility-and-parameter-settings-of-filaments"); + // wxLaunchDefaultBrowser("https://wiki.qiditech.com/en/x1/manual/compatibility-and-parameter-settings-of-filaments"); // }); AppConfig *app_config = wxGetApp().app_config; @@ -773,7 +781,6 @@ Sidebar::Sidebar(Plater *parent) vsizer_printer->Add(bed_type_sizer, 0, wxEXPAND | wxTOP, FromDIP(5)); vsizer_printer->AddSpacer(FromDIP(16)); - auto& project_config = wxGetApp().preset_bundle->project_config; /*const t_config_enum_values* keys_map = print_config_def.get("curr_bed_type")->enum_keys_map; BedType bed_type = btCount; @@ -1045,6 +1052,7 @@ Sidebar::Sidebar(Plater *parent) p->m_search_bar->ShowSearchButton(true); p->m_search_bar->ShowCancelButton(true); p->m_search_bar->SetDescriptiveText(_L("Search plate, object and part.")); + p->m_search_bar->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#000000"))); p->m_search_bar->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent&) { this->p->on_search_update(); @@ -1709,7 +1717,7 @@ void Sidebar::add_filament() { void Sidebar::delete_filament() { if (p->combos_filament.size() <= 1) return; - + wxBusyCursor busy; size_t filament_count = p->combos_filament.size() - 1; if (wxGetApp().preset_bundle->is_the_only_edited_filament(filament_count) || (filament_count == 1)) { wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", true); @@ -1808,6 +1816,11 @@ void Sidebar::load_ams_list(std::string const &device, MachineObject* obj) void Sidebar::sync_ams_list() { + // Force load ams list + auto obj = wxGetApp().getDeviceManager()->get_selected_machine(); + if (obj) + GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj); + auto & list = wxGetApp().preset_bundle->filament_ams_list; if (list.empty()) { MessageDialog dlg(this, @@ -1833,7 +1846,6 @@ void Sidebar::sync_ams_list() add_button(wxID_OK, true, _L("Sync")); add_button(wxID_YES, false, _L("Resync")); } - //1.9.7.52 add_button(wxID_CANCEL, false, _L("No")); } } dlg(this, ams_filament_ids.empty()); @@ -2309,7 +2321,9 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; - + // UIThreadWorker can be used as a replacement for BoostThreadWorker if + // no additional worker threads are desired (useful for debugging or profiling) + PlaterWorker m_worker; // Jobs defined inside the group class will be managed so that only one can // run at a time. Also, the background process will be stopped if a job is // started. It is up the the plater to ensure that the background slicing @@ -2388,7 +2402,7 @@ struct Plater::priv bool show_render_statistic_dialog{ false }; bool show_wireframe{ false }; bool wireframe_enabled{ true }; - + bool show_non_manifold_edges{false}; static const std::regex pattern_bundle; static const std::regex pattern_3mf; static const std::regex pattern_zip_amf; @@ -2407,6 +2421,7 @@ struct Plater::priv void set_plater_dirty(bool is_dirty) { dirty_state.set_plater_dirty(is_dirty); } bool is_project_dirty() const { return dirty_state.is_dirty(); } bool is_presets_dirty() const { return dirty_state.is_presets_dirty(); } + //y void resetUploadCount(); void update_project_dirty_from_presets() { @@ -2657,6 +2672,7 @@ struct Plater::priv void on_add_custom_filament(ColorEvent &); void on_object_select(SimpleEvent&); + void show_right_click_menu(Vec2d mouse_position, wxMenu *menu); void on_plate_name_change(SimpleEvent &); void on_right_click(RBtnEvent&); //QDS: add model repair @@ -2712,7 +2728,7 @@ struct Plater::priv bool can_decrease_instances() const; bool can_split_to_objects() const; bool can_split_to_volumes() const; - bool can_arrange() const; + bool can_do_ui_job() const; bool can_layers_editing() const; bool can_fix_through_netfabb() const; bool can_simplify() const; @@ -2771,7 +2787,6 @@ struct Plater::priv bool PopupObjectTable(int object_id, int volume_id, const wxPoint& position); void on_action_send_to_printer(bool isall = false); void on_action_send_to_multi_machine(SimpleEvent&); - int update_print_required_data(Slic3r::DynamicPrintConfig config, Slic3r::Model model, Slic3r::PlateDataPtrs plate_data_list, std::string file_name, std::string file_path); private: bool layers_height_allowed() const; @@ -2809,7 +2824,7 @@ private: // vector of all warnings generated by last slicing std::vector> current_warnings; bool show_warning_dialog { false }; - + //y std::chrono::system_clock::time_point m_time_p; int UploadCount = 0; int max_send_number = 1; @@ -2831,7 +2846,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) //QDS: add bed_exclude_area , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "printable_area", "bed_exclude_area", "bed_custom_texture", "bed_custom_model", "print_sequence", - "extruder_clearance_radius", "extruder_clearance_max_radius", + "extruder_clearance_dist_to_rod", "extruder_clearance_max_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", "nozzle_height", "skirt_loops", "skirt_distance", "brim_width", "brim_object_gap", "brim_type", "nozzle_diameter", "single_extruder_multi_material", @@ -2846,6 +2861,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , notification_manager(std::make_unique(q)) + , m_worker{q, std::make_unique(notification_manager.get()), "ui_worker"} , m_ui_jobs(this) , m_job_prepare_state(Job::JobPrepareState::PREPARE_STATE_DEFAULT) , delayed_scene_refresh(false) @@ -2897,6 +2913,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_DEL_FILAMENT, &priv::on_delete_filament, this); this->q->Bind(EVT_ADD_CUSTOM_FILAMENT, &priv::on_add_custom_filament, this); view3D = new View3D(q, bed, &model, config, &background_process); + partplate_list.set_bed3d(&bed); //QDS: use partplater's gcode preview = new Preview(q, bed, &model, config, &background_process, partplate_list.get_current_slice_result(), [this]() { schedule_background_process(); }); @@ -3463,6 +3480,10 @@ void Plater::priv::reset_all_gizmos() view3D->get_canvas3d()->reset_all_gizmos(); } +Worker &Plater::get_ui_job_worker() { return p->m_worker; } + +const Worker &Plater::get_ui_job_worker() const { return p->m_worker; } + // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void Plater::priv::update_ui_from_settings() @@ -3500,7 +3521,8 @@ BoundingBox Plater::priv::scaled_bed_shape_bb() const } -void read_binary_stl(const std::string& filename, std::string& model_id, std::string& code) { +void read_binary_stl(const std::string& filename, std::string& model_id, std::string& code, + std::string& ml_name, std::string& ml_region, std::string& ml_id) { std::ifstream file( encode_path(filename.c_str()), std::ios::binary); if (!file) { return; @@ -3520,42 +3542,60 @@ void read_binary_stl(const std::string& filename, std::string& model_id, std::st return; } - char magic[2] = { data[0], data[1] }; - if (magic[0] != 'M' || magic[1] != 'W') { - file.close(); - return; + /*include ml info*/ + std::string ext_content(data); + std::string ml_content; + std::string mw_content; + + size_t pos = ext_content.find('&'); + if (pos != std::string::npos) { + ml_content = ext_content.substr(0, pos); + mw_content = ext_content.substr(pos + 1); } - if (data[2] != ' ') { - file.close(); - return; + if (ml_content.empty() && ext_content.find("ML") != std::string::npos) { + ml_content = ext_content; } - char protocol_version[3] = { data[3], data[4], data[5] }; - - //version - if (protocol_version[0] != '1' || protocol_version[1] != '.' || protocol_version[2] != '0') { - file.close(); - return; + if (mw_content.empty() && ext_content.find("MW") != std::string::npos) { + mw_content = ext_content; } - std::vector tokens; - std::istringstream iss(data); - std::string token; - while (std::getline(iss, token, ' ')) { - char* tokenPtr = new char[token.length() + 1]; - std::strcpy(tokenPtr, token.c_str()); - tokens.push_back(tokenPtr); + /*parse ml info*/ + if (!ml_content.empty()) { + std::istringstream iss(ml_content); + std::string token; + std::vector result; + while (iss >> token) { + if (token.find(' ') == std::string::npos) { + result.push_back(token); + } + } + + if (result.size() == 4 && result[0] == "ML") { + ml_region = result[1]; + ml_name = result[2]; + ml_id = result[3]; + } } - //model id - if (tokens.size() < 4) { - file.close(); - return; + /*parse mw info*/ + if (!mw_content.empty()) { + std::istringstream iss(mw_content); + std::string token; + std::vector result; + while (iss >> token) { + if (token.find(' ') == std::string::npos) { + result.push_back(token); + } + } + + if (result.size() == 4 && result[0] == "MW") { + model_id = result[2]; + code = result[3]; + } } - model_id = tokens[2]; - code = tokens[3]; file.close(); } catch (...) { @@ -3602,6 +3642,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ std::string designer_model_id; std::string designer_country_code; + std::string makerlab_region; + std::string makerlab_name; + std::string makerlab_id; int answer_convert_from_meters = wxOK_DEFAULT; int answer_convert_from_imperial_units = wxOK_DEFAULT; @@ -3636,7 +3679,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ const float INIT_MODEL_RATIO = 0.75; const float CENTER_AROUND_ORIGIN_RATIO = 0.8; const float LOAD_MODEL_RATIO = 0.9; - + bool import_obj_or_stl = false; for (size_t i = 0; i < input_files.size(); ++i) { int file_percent = 0; @@ -3741,7 +3784,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ // do not reset the model config load_config = false; if(load_type != LoadType::LoadGeometry) - show_info(q, _L("The 3mf is not from QIDI Lab, load geometry data only."), _L("Load 3mf")); + show_info(q, _L("The 3mf is not from QIDI Tech, load geometry data only."), _L("Load 3mf")); } //w18 ////else if (load_config && (file_version.maj() != app_version.maj())) { @@ -3762,7 +3805,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ //// if (en_3mf_file_type == En3mfType::From_QDS) //// show_info(q, _L("Due to the lower version of QIDI Studio, this 3mf file cannot be fully loaded. Please update QIDI Studio to the latest version"), _L("Load 3mf")); //// else - //// show_info(q, _L("The 3mf is not from QIDI Lab, load geometry data only."), _L("Load 3mf")); + //// show_info(q, _L("The 3mf is not from QIDI Tech, load geometry data only."), _L("Load 3mf")); //// } //// for (ModelObject *model_object : model.objects) { //// model_object->config.reset(); @@ -4064,23 +4107,70 @@ std::vector Plater::priv::load_files(const std::vector& input_ std::vector project_presets; bool is_xxx; Semver file_version; - //ObjImportColorFn obj_color_fun=nullptr; - auto obj_color_fun = [this, &path](std::vector &input_colors, bool is_single_color, std::vector &filament_ids, - unsigned char &first_extruder_id) { - if (!boost::iends_with(path.string(), ".obj")) { return; } + auto obj_color_fun = [this, &path, &makerlab_region, &makerlab_name, &makerlab_id](std::vector &input_colors, bool is_single_color, std::vector &filament_ids, + unsigned char& first_extruder_id, std::string ml_origin, std::string ml_name, std::string ml_id) { + + makerlab_region = ml_origin; + makerlab_name = ml_name; + makerlab_id = ml_id; + + if (!boost::iends_with(path.string(), ".obj")) { return; } const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); ObjColorDialog color_dlg(nullptr, input_colors, is_single_color, extruder_colours, filament_ids, first_extruder_id); if (color_dlg.ShowModal() != wxID_OK) { filament_ids.clear(); } }; - model = Slic3r::Model::read_from_file( + if (boost::iends_with(path.string(), ".stp") || + boost::iends_with(path.string(), ".step")) { + double linear = string_to_double_decimal_point(wxGetApp().app_config->get("linear_defletion")); + if (linear <= 0) linear = 0.003; + double angle = string_to_double_decimal_point(wxGetApp().app_config->get("angle_defletion")); + if (angle <= 0) angle = 0.5; + model = Slic3r::Model:: read_from_step(path.string(), strategy, + [this, &dlg, real_filename, &progress_percent, &file_percent, step_percent, INPUT_FILES_RATIO, total_files, i](int load_stage, int current, int total, bool &cancel) + { + bool cont = true; + float percent_float = (100.0f * (float)i / (float)total_files) + INPUT_FILES_RATIO * ((float)step_percent[load_stage] + (float)current * (float)(step_percent[load_stage + 1] - step_percent[load_stage]) / (float)total) / (float)total_files; + BOOST_LOG_TRIVIAL(trace) << "load_step_file: percent(float)=" << percent_float << ", stage = " << load_stage << ", curr = " << current << ", total = " << total; + progress_percent = (int)percent_float; + wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename)); + cont = dlg.Update(progress_percent, msg); + cancel = !cont; + }, + [](int isUtf8StepFile) { + if (!isUtf8StepFile) + Slic3r::GUI::show_info(nullptr, _L("Name of components inside step file is not UTF8 format!") + "\n\n" + _L("The name may show garbage characters!"), + _L("Attention!")); + }, + [this, &path, &is_user_cancel, &linear, &angle](Slic3r::Step& file, double& linear_value, double& angle_value)-> int { + if (wxGetApp().app_config->get_bool("enable_step_mesh_setting")) { + StepMeshDialog mesh_dlg(nullptr, file, linear, angle); + if (mesh_dlg.ShowModal() == wxID_OK) { + linear_value = mesh_dlg.get_linear_defletion(); + angle_value = mesh_dlg.get_angle_defletion(); + return 1; + } + }else { + linear_value = linear; + angle_value = angle; + return 1; + } + is_user_cancel = true; + return -1; + }, linear, angle); + }else { + model = Slic3r::Model:: read_from_file( path.string(), nullptr, nullptr, strategy, &plate_data, &project_presets, &is_xxx, &file_version, nullptr, - [this, &dlg, real_filename, &progress_percent, &file_percent, INPUT_FILES_RATIO, total_files, i, &designer_model_id, &designer_country_code](int current, int total, bool &cancel, std::string &mode_id, std::string &code) + [this, &dlg, real_filename, &progress_percent, &file_percent, INPUT_FILES_RATIO, total_files, i, &designer_model_id, &designer_country_code, &makerlab_region, &makerlab_name, &makerlab_id](int current, int total, bool &cancel, + std::string &mode_id, std::string &code, std::string &ml_region, std::string &ml_name, std::string &ml_id) { - designer_model_id = mode_id; - designer_country_code = code; + designer_model_id = mode_id; + designer_country_code = code; + makerlab_region = ml_region; + makerlab_name = ml_name; + makerlab_id = ml_id; bool cont = true; float percent_float = (100.0f * (float)i / (float)total_files) + INPUT_FILES_RATIO * 100.0f * ((float)current / (float)total) / (float)total_files; @@ -4089,27 +4179,12 @@ std::vector Plater::priv::load_files(const std::vector& input_ wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename)); cont = dlg.Update(progress_percent, msg); cancel = !cont; - }, - [this, &dlg, real_filename, &progress_percent, &file_percent, step_percent, INPUT_FILES_RATIO, total_files, i](int load_stage, int current, int total, bool &cancel) - { - bool cont = true; - float percent_float = (100.0f * (float)i / (float)total_files) + INPUT_FILES_RATIO * ((float)step_percent[load_stage] + (float)current * (float)(step_percent[load_stage + 1] - step_percent[load_stage]) / (float)total) / (float)total_files; - BOOST_LOG_TRIVIAL(trace) << "load_step_file: percent(float)=" << percent_float << ", stage = " << load_stage << ", curr = " << current << ", total = " << total; - progress_percent = (int)percent_float; - wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename)); - cont = dlg.Update(progress_percent, msg); - cancel = !cont; }, - [](int isUtf8StepFile) { - if (!isUtf8StepFile) - Slic3r::GUI::show_info(nullptr, _L("Name of components inside step file is not UTF8 format!") + "\n\n" + _L("The name may show garbage characters!"), - _L("Attention!")); - }, nullptr, 0, obj_color_fun); - + } if (designer_model_id.empty() && boost::algorithm::iends_with(path.string(), ".stl")) { - read_binary_stl(path.string(), designer_model_id, designer_country_code); + read_binary_stl(path.string(), designer_model_id, designer_country_code, makerlab_name, makerlab_region, makerlab_id); } if (type_any_amf && is_xxx) imperial_units = true; @@ -4262,7 +4337,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ q->skip_thumbnail_invalid = false; return empty_result; } - + if (boost::algorithm::iends_with(path.string(), ".stl") || boost::algorithm::iends_with(path.string(), ".obj")) { + import_obj_or_stl = true; + } if (one_by_one) { // QDS: add load_old_project logic if (type_3mf && !is_project_file && !load_old_project) @@ -4277,7 +4354,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before load_model_objects, count %1%")%model.objects.size(); auto loaded_idxs = load_model_objects(model.objects, is_project_file); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); - + if (import_obj_or_stl) { + for (int i = 0; i < loaded_idxs.size(); i++) { + q->model().set_assembly_pos(q->model().objects[q->model().objects.size() - 1 - i]); + } + } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", finished load_model_objects"); wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename)); dlg_cont = dlg.Update(progress_percent, msg); @@ -4312,6 +4393,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ auto loaded_idxs = load_model_objects(new_model->objects); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); + if (import_obj_or_stl) { + for (int i = 0; i < loaded_idxs.size(); i++) { + q->model().set_assembly_pos(q->model().objects[q->model().objects.size() - 1 - i]); + } + } } if (new_model) delete new_model; @@ -4409,6 +4495,10 @@ std::vector Plater::priv::load_files(const std::vector& input_ //set designer_model_id q->model().stl_design_id = designer_model_id; q->model().stl_design_country = designer_country_code; + q->model().makerlab_region = makerlab_region; + q->model().makerlab_name = makerlab_name; + q->model().makerlab_id = makerlab_id; + //if (!designer_model_id.empty() && q->model().stl_design_id.empty() && !designer_country_code.empty()) { // q->model().stl_design_id = designer_model_id; // q->model().stl_design_country = designer_country_code; @@ -4650,6 +4740,17 @@ fs::path Plater::priv::get_export_file_path(GUI::FileType file_type) } return output_file; } +bool delete_file_name_redundant_suffix(fs::path &path, const std::wstring &suffix) { + auto temp_str = path.filename().wstring(); + boost::ireplace_last(temp_str, suffix, ""); + if (boost::icontains(temp_str, suffix)) { + boost::ireplace_all(temp_str, suffix, ""); + std::wstring temp = L"/"; + path = (path.parent_path().wstring() + temp + temp_str + suffix); + return true; + } + return false; +} wxString Plater::priv::get_export_file(GUI::FileType file_type) { @@ -4714,9 +4815,8 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) wxString out_path = dlg.GetPath(); fs::path path(into_path(out_path)); #ifdef __WXMSW__ - //1.9.5 if (boost::iequals(path.extension().string(), output_file.extension().string()) == false) { - out_path += output_file.extension().string(); + out_path += output_file.extension().wstring(); boost::system::error_code ec; if (boost::filesystem::exists(into_u8(out_path), ec)) { auto result = MessageBox(q->GetHandle(), @@ -4728,6 +4828,9 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) } } #endif + if (delete_file_name_redundant_suffix(path, output_file.extension().wstring())) { + out_path = path.wstring(); + } wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); return out_path; @@ -5025,6 +5128,11 @@ void Plater::priv::split_object() ModelObject* current_model_object = new_model.objects[obj_idx]; wxBusyCursor wait; + if (current_model_object->volumes.size() == 1) { + split_volume();//keep color + new_model = model; + current_model_object = new_model.objects[obj_idx]; + } ModelObjectPtrs new_objects; current_model_object->split(&new_objects); if (new_objects.size() == 1) @@ -5822,15 +5930,17 @@ void Plater::priv::reload_from_disk() // load one file at a time for (size_t i = 0; i < input_paths.size(); ++i) { const auto& path = input_paths[i].string(); - auto obj_color_fun = [this, &path](std::vector &input_colors, bool is_single_color, std::vector &filament_ids, unsigned char &first_extruder_id) { + auto obj_color_fun = [this, &path](std::vector &input_colors, bool is_single_color, std::vector &filament_ids, unsigned char &first_extruder_id, + std::string ml_origin, std::string ml_name, std::string ml_id) { if (!boost::iends_with(path, ".obj")) { return; } const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); ObjColorDialog color_dlg(nullptr, input_colors, is_single_color, extruder_colours, filament_ids, first_extruder_id); if (color_dlg.ShowModal() != wxID_OK) { filament_ids.clear(); } }; wxBusyCursor wait; - wxBusyInfo info(_L("Reload from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); - + if (!boost::iends_with(path, ".obj")) { + wxBusyInfo info(_L("Reload from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas()); + } Model new_model; try { @@ -5840,8 +5950,16 @@ void Plater::priv::reload_from_disk() std::vector project_presets; // QDS: backup - new_model = Model::read_from_file(path, nullptr, nullptr, LoadStrategy::AddDefaultInstances | LoadStrategy::LoadModel, &plate_data, &project_presets, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0, obj_color_fun); + if (boost::iends_with(path, ".stp") || + boost::iends_with(path, ".step")) { + double linear = string_to_double_decimal_point(wxGetApp().app_config->get("linear_defletion")); + double angle = string_to_double_decimal_point(wxGetApp().app_config->get("angle_defletion")); + new_model = Model::read_from_step(path, LoadStrategy::AddDefaultInstances | LoadStrategy::LoadModel, nullptr, nullptr, nullptr, linear, angle); + }else { + new_model = Model::read_from_file(path, nullptr, nullptr, LoadStrategy::AddDefaultInstances | LoadStrategy::LoadModel, &plate_data, &project_presets, nullptr, nullptr, nullptr, nullptr, nullptr, 0, obj_color_fun); + } + + for (ModelObject* model_object : new_model.objects) { model_object->center_around_origin(); @@ -5903,6 +6021,11 @@ void Plater::priv::reload_from_disk() } } if (found) break; + // QDS: step model,object loaded as a volume. GUI_ObfectList.cpp load_modifier() + if (obj->name == old_volume->name) { + new_object_idx = (int) o; + break; + } } } @@ -5911,22 +6034,30 @@ void Plater::priv::reload_from_disk() continue; } ModelObject *new_model_object = new_model.objects[new_object_idx]; - if (new_volume_idx < 0 || int(new_model_object->volumes.size()) <= new_volume_idx) { + if (int(new_model_object->volumes.size()) <= new_volume_idx) { fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); continue; } - old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); - ModelVolume *new_volume = old_model_object->volumes.back(); + ModelVolume *new_volume = nullptr; + // QDS: step model + if (new_volume_idx < 0 && new_object_idx >= 0) { + TriangleMesh mesh = new_model_object->mesh(); + new_volume = old_model_object->add_volume(std::move(mesh)); + new_volume->name = new_model_object->name; + new_volume->source.input_file = new_model_object->input_file; + }else { + new_volume = old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); + // new_volume = old_model_object->volumes.back(); + } + new_volume->set_new_unique_id(); new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); - Transform3d transform = Transform3d::Identity(); - transform.translate(new_volume->source.mesh_offset - old_volume->source.mesh_offset); - new_volume->set_transformation(old_volume->get_transformation().get_matrix() * old_volume->source.transform.get_matrix(true) * - transform * new_volume->source.transform.get_matrix(true).inverse()); + new_volume->source.mesh_offset = old_volume->source.mesh_offset; + new_volume->set_transformation(old_volume->get_transformation()); new_volume->source.object_idx = old_volume->source.object_idx; new_volume->source.volume_idx = old_volume->source.volume_idx; @@ -6339,7 +6470,7 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice) assemble_view->get_canvas3d()->bind_event_handlers(); assemble_view->reload_scene(true); - + assemble_view->get_canvas3d()->set_ignore_left_up(); if (old_panel == view3D) { GLCanvas3D* view3D_canvas = view3D->get_canvas3d(); Selection::IndicesList select_idxs = view3D_canvas->get_selection().get_volume_idxs(); @@ -6463,18 +6594,60 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) PartPlateList& old_plate_list = this->partplate_list; PartPlate* old_plate = old_plate_list.get_selected_plate(); Vec3d old_plate_pos = old_plate->get_center_origin(); + Vec3d old_plate_size = old_plate->get_plate_box().size(); // QDS: Save the model in the current platelist - std::vector > plate_object; + std::vector> plate_object; + std::set all_plate_object; for (size_t i = 0; i < old_plate_list.get_plate_count(); ++i) { PartPlate* plate = old_plate_list.get_plate(i); + std::set> obj_set = plate->get_obj_and_inst_set(); + std::vector obj_idxs; - for (int obj_idx = 0; obj_idx < model.objects.size(); obj_idx++) { - if (plate && plate->contain_instance(obj_idx, 0)) { - obj_idxs.emplace_back(obj_idx); - } + for (auto& p: obj_set) { + obj_idxs.push_back(p.first); + all_plate_object.emplace(p.first); + } + plate_object.emplace_back(std::move(obj_idxs)); + } + + BoundingBoxf3 platelist_bbox = old_plate_list.get_bounding_box(); + std::map outside_plate_object; + for (int i = 0; i < model.objects.size(); ++i) + { + ModelObject* object = model.objects[i]; + ModelInstance* obj_inst = object->instances[0]; + + if (all_plate_object.find(i) == all_plate_object.end()) + { + int position_type = 0; + BoundingBoxf3 instance_bbox = object->instance_convex_hull_bounding_box(obj_inst); + /* 1 | 2 | 3 + * -------------------------------- + * 4 | 5 | 6 + * -------------------------------- + * 7 | 8 | 9 + */ + if ((platelist_bbox.min.x() >= instance_bbox.max.x()) && (platelist_bbox.max.y() <= instance_bbox.min.y())) + position_type = 1; + else if ((platelist_bbox.min.x() >= instance_bbox.max.x()) && (platelist_bbox.min.y() >= instance_bbox.max.y())) + position_type = 7; + else if (platelist_bbox.min.x() >= instance_bbox.max.x()) + position_type = 4; + else if ((platelist_bbox.max.x() <= instance_bbox.min.x()) && (platelist_bbox.max.y() <= instance_bbox.min.y())) + position_type = 3; + else if ((platelist_bbox.max.x() <= instance_bbox.min.x()) && (platelist_bbox.min.y() >= instance_bbox.max.y())) + position_type = 9; + else if (platelist_bbox.max.x() <= instance_bbox.min.x()) + position_type = 6; + else if (platelist_bbox.max.y() <= instance_bbox.min.y()) + position_type = 2; + else if (platelist_bbox.min.y() >= instance_bbox.max.y()) + position_type = 8; + else + position_type = 5; + outside_plate_object.emplace(i, position_type); } - plate_object.emplace_back(obj_idxs); } bool flag = is_support_filament(idx); @@ -6532,14 +6705,66 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) PartPlateList& cur_plate_list = this->partplate_list; PartPlate* cur_plate = cur_plate_list.get_curr_plate(); Vec3d cur_plate_pos = cur_plate->get_center_origin(); + Vec3d cur_plate_size = cur_plate->get_bounding_box().size(); + bool cur_plate_is_smaller = cur_plate_size.x() + 1.0 < old_plate_size.x() || cur_plate_size.y() + 1.0 < old_plate_size.y(); + BOOST_LOG_TRIVIAL(info) << format("change bed pos from (%.0f,%.0f) to (%.0f,%.0f)", old_plate_pos.x(), old_plate_pos.y(), cur_plate_pos.x(), cur_plate_pos.y()); - if (old_plate_pos.x() != cur_plate_pos.x() || old_plate_pos.y() != cur_plate_pos.y()) { + if (old_plate_pos.x() != cur_plate_pos.x() || old_plate_pos.y() != cur_plate_pos.y() || cur_plate_is_smaller) { for (int i = 0; i < plate_object.size(); ++i) { view3D->select_object_from_idx(plate_object[i]); this->sidebar->obj_list()->update_selections(); view3D->center_selected_plate(i); } + const BoundingBoxf3& cur_platelist_bbox = cur_plate_list.get_bounding_box(); + const BoundingBoxf3 last_plate_bbox = cur_plate_list.get_plate(cur_plate_list.get_plate_count() - 1)->get_bounding_box(); + int cur_plate_w, cur_plate_d, cur_plate_h; + cur_plate_list.get_plate_size(cur_plate_w, cur_plate_d, cur_plate_h); + for (auto& iter: outside_plate_object) + { + ModelObject *object = model.objects[iter.first]; + BoundingBoxf3 instance_bbox = object->instance_convex_hull_bounding_box(size_t(0), false); + Vec3d offset = Vec3d::Zero(); + switch(iter.second) { + case 1: + case 2: + offset(1) = cur_platelist_bbox.max.y() - platelist_bbox.max.y(); + break; + case 7: + case 8: + offset(1) = cur_platelist_bbox.min.y() - platelist_bbox.min.y(); + break; + case 3: + offset(0) = cur_platelist_bbox.max.x() - platelist_bbox.max.x(); + offset(1) = cur_platelist_bbox.max.y() - platelist_bbox.max.y(); + break; + case 6: + offset(0) = cur_platelist_bbox.max.x() - platelist_bbox.max.x(); + break; + case 9: + offset(0) = cur_platelist_bbox.max.x() - platelist_bbox.max.x(); + offset(1) = cur_platelist_bbox.min.y() - platelist_bbox.min.y(); + break; + case 5: + offset(0) = last_plate_bbox.center().x() + 1.2f * cur_plate_w - instance_bbox.center().x(); + offset(1) = last_plate_bbox.center().y() - instance_bbox.center().y(); + break; + default: + break; + } + + object->translate_instance(0, offset); + cur_plate_list.notify_instance_update(iter.first, 0); + } + + BOOST_LOG_TRIVIAL(info) << format("change bed size from (%.0f,%.0f) to (%.0f,%.0f)", old_plate_size.x(), old_plate_size.y(), cur_plate_size.x(), cur_plate_size.y()); + if (cur_plate_is_smaller && + std::any_of(plate_object.begin(), plate_object.end(), [](const std::vector &obj_idxs) { return !obj_idxs.empty(); })) { + take_snapshot("Arrange after bed size changes"); + q->set_prepare_state(Job::PREPARE_STATE_OUTSIDE_BED); + q->arrange(); + } + view3D->deselect_all(); } #if 0 // do not toggle auto calc when change printer @@ -6995,10 +7220,7 @@ void Plater::priv::on_action_add(SimpleEvent&) void Plater::priv::on_action_add_plate(SimpleEvent&) { if (q != nullptr) { - take_snapshot("add partplate"); - this->partplate_list.create_plate(); - int new_plate = this->partplate_list.get_plate_count() - 1; - this->partplate_list.select_plate(new_plate); + partplate_list.add_plate(); update(); // QDS set default view @@ -7538,6 +7760,41 @@ void Plater::priv::on_change_color_mode(SimpleEvent& evt) { if (m_send_to_sdcard_dlg) m_send_to_sdcard_dlg->on_change_color_mode(); } +static void get_position(wxWindowBase *child, wxWindowBase *until_parent, int &x, int &y) +{ + int res_x = 0, res_y = 0; + + while (child != until_parent && child != nullptr) { + int _x, _y; + child->GetPosition(&_x, &_y); + res_x += _x; + res_y += _y; + + child = child->GetParent(); + } + + x = res_x; + y = res_y; +} + +void Plater::priv::show_right_click_menu(Vec2d mouse_position, wxMenu *menu) +{ + // QDS: GUI refactor: move sidebar to the left + int x, y; + get_position(current_panel, wxGetApp().mainframe, x, y); + wxPoint position(static_cast(mouse_position.x() + x), static_cast(mouse_position.y() + y)); +#ifdef __linux__ + // For some reason on Linux the menu isn't displayed if position is + // specified (even though the position is sane). + position = wxDefaultPosition; +#endif + GLCanvas3D &canvas = *q->canvas3D(); + canvas.apply_retina_scale(mouse_position); + canvas.set_popup_menu_position(mouse_position); + q->PopupMenu(menu, position); + canvas.clear_popup_menu_position(); +} + void Plater::priv::on_right_click(RBtnEvent& evt) { int obj_idx = get_selected_object_idx(); @@ -7585,24 +7842,33 @@ void Plater::priv::on_right_click(RBtnEvent& evt) menu = is_some_full_instances ? menus.assemble_object_menu() : is_part ? menus.assemble_part_menu() : menus.assemble_multi_selection_menu(); } else { - menu = is_some_full_instances ? menus.object_menu() : - is_part ? menus.part_menu() : menus.multi_selection_menu(); + if (is_some_full_instances) + menu = printer_technology == ptSLA ? menus.sla_object_menu() : menus.object_menu(); + else if (is_part) { + const GLVolume * gl_volume = selection.get_first_volume(); + const ModelVolume *model_volume = get_model_volume(*gl_volume, selection.get_model()->objects); + if (model_volume != nullptr) { + if (model_volume->is_svg()) { + menu = menus.svg_part_menu(); + } else if (model_volume->is_cut_connector()) { + menu = menus.cut_connector_menu(); + } else { + menu = menus.part_menu(); + } + } + } else { + if (selection.is_any_cut_volume()) { + menu = menus.cut_connector_menu(); + } else { + menu = menus.multi_selection_menu(); + } + } } } } if (q != nullptr && menu) { -#ifdef __linux__ - // For some reason on Linux the menu isn't displayed if position is specified - // (even though the position is sane). - q->PopupMenu(menu); -#else - //QDS: GUI refactor: move sidebar to the left - int x, y; - current_panel->GetPosition(&x, &y); - q->PopupMenu(menu, (int)evt.data.first.x() + x, (int)evt.data.first.y()); - //q->PopupMenu(menu); -#endif + show_right_click_menu(evt.data.first, menu); } } @@ -7610,16 +7876,7 @@ void Plater::priv::on_right_click(RBtnEvent& evt) void Plater::priv::on_plate_right_click(RBtnPlateEvent& evt) { wxMenu* menu = menus.plate_menu(); - -#ifdef __linux__ - q->PopupMenu(menu); -#else - //QDS: GUI refactor: move sidebar to the left - int x, y; - current_panel->GetPosition(&x, &y); - q->PopupMenu(menu, (int)evt.data.first.x() + x, (int)evt.data.first.y()); - //q->PopupMenu(menu); -#endif + show_right_click_menu(evt.data.first, menu); } void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) @@ -8309,9 +8566,9 @@ bool Plater::priv::can_split_to_volumes() const return (printer_technology != ptSLA) && q->can_split(false); } -bool Plater::priv::can_arrange() const +bool Plater::priv::can_do_ui_job() const { - return !model.objects.empty() && !m_ui_jobs.is_any_running(); + return !model.objects.empty() && !m_ui_jobs.is_any_running() && !q->is_background_process_slicing(); } bool Plater::priv::layers_height_allowed() const @@ -8345,7 +8602,6 @@ void Plater::priv::on_create_filament(SimpleEvent &) CreatePresetSuccessfulDialog success_dlg(wxGetApp().mainframe, SuccessType::FILAMENT); int res = success_dlg.ShowModal(); } - //1.9.7.52 wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_FILAMENTS); } @@ -8375,7 +8631,7 @@ void Plater::priv::on_modify_filament(SimpleEvent &evt) // when some preset have modified, if the printer is not need_edit_preset_name compatible printer, the preset will jump to other preset, need select again if (!need_edit_preset->is_compatible) tab->select_preset(need_edit_preset->name); } else - wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_FILAMENTS); //1.9.7.52 + wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_FILAMENTS); } @@ -8940,6 +9196,7 @@ void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_ int Plater::save_project_if_dirty(const wxString& reason) { return p->save_project_if_dirty(reason); } void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); } void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); } +//y void Plater::resetUploadCount(){ p->resetUploadCount(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } @@ -9038,7 +9295,6 @@ void Plater::load_project(wxString const& filename2, wxString const& originfile) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "filename is: " << filename2 << "and originfile is: " << originfile; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__; auto filename = filename2; auto check = [&filename, this] (bool yes_or_no) { if (!yes_or_no && !wxGetApp().check_and_save_current_preset_changes(_L("Load project"), @@ -10648,7 +10904,6 @@ void Plater::load_gcode(const wxString& filename) //QDS: zoom to bed 0 for gcode preview //p->preview->get_canvas3d()->zoom_to_gcode(); p->preview->get_canvas3d()->zoom_to_plate(0); - //1.9.5 p->partplate_list.get_curr_plate()->update_slice_result_valid_state(true); current_print.apply(this->model(), wxGetApp().preset_bundle->full_config()); @@ -11075,7 +11330,81 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } -//QDS: remove GCodeViewer as seperate APP logic +bool Plater::emboss_svg(const wxString &svg_file, bool from_toolbar_or_file_menu) +{ + std::string svg_file_str = into_u8(svg_file); + GLCanvas3D *canvas = canvas3D(); + if (canvas == nullptr) + return false; + auto base_svg = canvas->get_gizmos_manager().get_gizmo(GLGizmosManager::Svg); + if (base_svg == nullptr) + return false; + GLGizmoSVG *svg = dynamic_cast(base_svg); + if (svg == nullptr) + return false; + // Refresh hover state to find surface point under mouse + if (from_toolbar_or_file_menu) { + return svg->create_volume(svg_file_str, ModelVolumeType::MODEL_PART); + } else { + wxMouseEvent evt(wxEVT_MOTION); + auto mouse_drop_position = canvas->get_local_mouse_position(); + evt.SetPosition(wxPoint(mouse_drop_position.x(), mouse_drop_position.y())); + canvas->on_mouse(evt); // call render where is call GLCanvas3D::_picking_pass() + return svg->create_volume(svg_file_str, mouse_drop_position, ModelVolumeType::MODEL_PART); + } +} + +bool Plater::load_svg(const wxArrayString &filenames, bool from_toolbar_or_file_menu) +{ + // When only one .svg file is dropped on scene + if (filenames.size() == 1) { + const wxString &filename = filenames.Last(); + const wxString file_extension = filename.substr(filename.length() - 4); + if (file_extension.CmpNoCase(".svg") == 0) { + return emboss_svg(filename, from_toolbar_or_file_menu); + } + } + else { + const auto loading = _L("Loading") + dots; + ProgressDialog dlg(loading, "", 100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_APP_MODAL); + wxBusyCursor busy; + for (size_t i = 0; i < filenames.size(); i++) { + if (i > 0) { + deselect_all(); + } + wxArrayString temp_filenames; + if (!boost::iends_with(filenames[i].ToStdString(), ".svg")) { + return false; + } + temp_filenames.push_back(filenames[i]); + const auto dlg_info = _L("Loading file") + ": " + filenames[i]; + int progress_percent = static_cast(100.0f * static_cast(i) / static_cast(filenames.size())); + dlg.Update(progress_percent, dlg_info); + load_svg(temp_filenames,true); + get_ui_job_worker().wait_for_current_job(); + } + return true; + } + return false; +} + +bool Plater::load_same_type_files(const wxArrayString &filenames) { + if (filenames.size() <= 1) { return true; } + else { + const wxString &filename = filenames.front(); + boost::filesystem::path path(filename.ToStdWstring()); + auto extension =path.extension(); + for (size_t i = 1; i < filenames.size(); i++) { + boost::filesystem::path temp(filenames[i].ToStdWstring()); + auto temp_extension = temp.extension(); + if (extension != temp_extension) { + return false; + } + } + return true; + } +} + //QDS: remove GCodeViewer as seperate APP logic bool Plater::load_files(const wxArrayString& filenames) { const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg)", std::regex::icase); @@ -11083,6 +11412,15 @@ bool Plater::load_files(const wxArrayString& filenames) std::vector normal_paths; std::vector gcode_paths; + if (!load_same_type_files(filenames)) { + MessageDialog msg(wxGetApp().mainframe, _L("Please import multiple files with the same suffix."), _L("Warning"), wxYES | wxICON_WARNING); + if (msg.ShowModal() == wxID_YES) { + return true; + } + } + if (load_svg(filenames)) { + return true; + } for (const auto& filename : filenames) { fs::path path(into_path(filename)); @@ -11196,7 +11534,6 @@ bool Plater::load_files(const wxArrayString& filenames) case LoadFilesType::Multiple3MFOther: for (const auto &path : normal_paths) { - //1.9.5 if (boost::iends_with(path.filename().string(), ".3mf")){ if (first_file.size() <= 0) first_file.push_back(path); @@ -11274,7 +11611,6 @@ int Plater::get_3mf_file_count(std::vector paths) { auto count = 0; for (const auto &path : paths) { - //1.9.5 if (boost::iends_with(path.filename().string(), ".3mf")) { count++; } @@ -11325,6 +11661,9 @@ void Plater::add_file() case LoadFilesType::SingleOther: { Plater::TakeSnapshot snapshot(this, snapshot_label); + if (load_svg(input_files,true)) { + return; + } if (!load_files(paths, LoadStrategy::LoadModel, false).empty()) { if (get_project_name() == _L("Untitled") && paths.size() > 0) { p->set_project_filename(wxString::FromUTF8(paths[0].string())); @@ -11346,6 +11685,19 @@ void Plater::add_file() case LoadFilesType::MultipleOther: { Plater::TakeSnapshot snapshot(this, snapshot_label); + wxArrayString filenames; + for (auto path : paths) { + filenames.push_back(path.wstring()); + } + if (!load_same_type_files(filenames)) { + MessageDialog msg(wxGetApp().mainframe, _L("Please import multiple files with the same suffix."), _L("Warning"), wxYES | wxICON_WARNING); + if (msg.ShowModal() == wxID_YES) { + return; + } + } + if (boost::iends_with(paths[0].string(), ".svg")&& load_svg(filenames)) { + return; + } if (!load_files(paths, LoadStrategy::LoadModel, true).empty()) { if (get_project_name() == _L("Untitled") && paths.size() > 0) { p->set_project_filename(wxString::FromUTF8(paths[0].string())); @@ -11356,7 +11708,6 @@ void Plater::add_file() } case LoadFilesType::Multiple3MFOther: for (const auto &path : paths) { - //1.9.5 if (boost::iends_with(path.filename().string(), ".3mf")) { if (first_file.size() <= 0) first_file.push_back(path); @@ -11505,6 +11856,7 @@ void Plater::set_selected_visible(bool visible) return; Plater::TakeSnapshot snapshot(this, "Set Selected Objects Visible in AssembleView"); + get_ui_job_worker().cancel_all(); p->m_ui_jobs.cancel_all(); p->get_current_canvas3D()->set_selected_visible(visible); @@ -11523,6 +11875,7 @@ void Plater::remove_selected() return; Plater::TakeSnapshot snapshot(this, "Delete Selected Objects"); + get_ui_job_worker().cancel_all(); p->m_ui_jobs.cancel_all(); //QDS delete current selected @@ -11919,7 +12272,6 @@ void Plater::export_gcode_3mf(bool export_all) fs::path default_output_file; AppConfig& appconfig = *wxGetApp().app_config; std::string start_dir; - //1.9.5 default_output_file = into_path(get_export_gcode_filename(".gcode.3mf", false, export_all)); if (default_output_file.empty()) { try { @@ -11927,8 +12279,7 @@ void Plater::export_gcode_3mf(bool export_all) wxString filename = get_export_gcode_filename(".gcode.3mf", true, export_all); std::string full_filename = start_dir + "/" + filename.utf8_string(); default_output_file = boost::filesystem::path(full_filename); - } - catch (...) { + } catch(...) { ; } } @@ -11940,16 +12291,21 @@ void Plater::export_gcode_3mf(bool export_all) { std::string ext = default_output_file.extension().string(); wxFileDialog dlg(this, _L("Save Sliced file as:"), - start_dir, from_path(default_output_file.filename()), GUI::file_wildcards(FT_GCODE_3MF, ""), //1.9.5 + start_dir, from_path(default_output_file.filename()), GUI::file_wildcards(FT_GCODE_3MF, ""), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if (dlg.ShowModal() == wxID_OK) { output_path = into_path(dlg.GetPath()); - if (boost::iends_with(output_path.string(), ".gcode")) { //1.9.5 - std::string path = output_path.string(); - //1.9.5 - path = path.substr(0, path.size() - 6); - output_path = path + ".gcode.3mf"; + delete_file_name_redundant_suffix(output_path, L".gcode.3mf"); + if (boost::iends_with(output_path.string(), ".gcode")) { + std::wstring temp_path = output_path.wstring(); + temp_path = temp_path.substr(0, temp_path.size() - 6); + output_path = temp_path + L".gcode.3mf"; + } + else if (boost::iends_with(output_path.string(), ".gcode.gcode.3mf")) {//for mac + std::wstring temp_path = output_path.wstring(); + temp_path = temp_path.substr(0, temp_path.size() - 16); + output_path = temp_path + L".gcode.3mf"; } else if (!boost::iends_with(output_path.string(), ".gcode.3mf")) { output_path = output_path.replace_extension(".gcode.3mf"); @@ -12487,6 +12843,101 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) ; // store failed } }*/ +namespace { +std::string get_file_name(const std::string &file_path) +{ + size_t pos_last_delimiter = file_path.find_last_of("/\\"); + size_t pos_point = file_path.find_last_of('.'); + size_t offset = pos_last_delimiter + 1; + size_t count = pos_point - pos_last_delimiter - 1; + return file_path.substr(offset, count); +} +using SvgFile = EmbossShape::SvgFile; +using SvgFiles = std::vector; +std::string create_unique_3mf_filepath(const std::string &file, const SvgFiles svgs) +{ + // const std::string MODEL_FOLDER = "3D/"; // copy from file 3mf.cpp + std::string path_in_3mf = "3D/" + file + ".svg"; + size_t suffix_number = 0; + bool is_unique = false; + do { + is_unique = true; + path_in_3mf = "3D/" + file + ((suffix_number++) ? ("_" + std::to_string(suffix_number)) : "") + ".svg"; + for (SvgFile *svgfile : svgs) { + if (svgfile->path_in_3mf.empty()) continue; + if (svgfile->path_in_3mf.compare(path_in_3mf) == 0) { + is_unique = false; + break; + } + } + } while (!is_unique); + return path_in_3mf; +} + +bool set_by_local_path(SvgFile &svg, const SvgFiles &svgs) +{ + // Try to find already used svg file + for (SvgFile *svg_ : svgs) { + if (svg_->path_in_3mf.empty()) continue; + if (svg.path.compare(svg_->path) == 0) { + svg.path_in_3mf = svg_->path_in_3mf; + return true; + } + } + return false; +} + +/// +/// Function to secure private data before store to 3mf +/// +/// Data(also private) to clean before publishing +void publish(Model &model, SaveStrategy strategy) +{ + // SVG file publishing + bool exist_new = false; + SvgFiles svgfiles; + for (ModelObject *object : model.objects) { + for (ModelVolume *volume : object->volumes) { + if (!volume->emboss_shape.has_value()) continue; + + assert(volume->emboss_shape->svg_file.has_value()); + if (!volume->emboss_shape->svg_file.has_value()) continue; + + SvgFile *svg = &(*volume->emboss_shape->svg_file); + if (svg->path_in_3mf.empty()) exist_new = true; + svgfiles.push_back(svg); + } + } + + // Orca: don't show this in silence mode + if (exist_new && !(strategy & SaveStrategy::Silence)) { + MessageDialog dialog(nullptr, + _L("Are you sure you want to store original SVGs with their local paths into the 3MF file?\n" + "If you hit 'NO', all SVGs in the project will not be editable any more."), + _L("Private protection"), wxYES_NO | wxICON_QUESTION); + if (dialog.ShowModal() == wxID_NO) { + for (ModelObject *object : model.objects) + for (ModelVolume *volume : object->volumes) + if (volume->emboss_shape.has_value()) volume->emboss_shape.reset(); + } + } + + for (SvgFile *svgfile : svgfiles) { + if (!svgfile->path_in_3mf.empty()) + continue; // already suggested path (previous save) + + // create unique name for svgs, when local path differ + std::string filename = "unknown"; + if (!svgfile->path.empty()) { + if (set_by_local_path(*svgfile, svgfiles)) + continue; + // check whether original filename is already in: + filename = get_file_name(svgfile->path); + } + svgfile->path_in_3mf = create_unique_3mf_filepath(filename, svgfiles); + } +} +} // namespace // QDS: backup int Plater::export_3mf(const boost::filesystem::path& output_path, SaveStrategy strategy, int export_plate_idx, Export3mfProgressFn proFn) @@ -12506,6 +12957,9 @@ int Plater::export_3mf(const boost::filesystem::path& output_path, SaveStrategy if (!path.Lower().EndsWith(".3mf")) return -1; + // take care about private data stored into .3mf + // modify model + publish(p->model, strategy); DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); @@ -13140,7 +13594,7 @@ int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn) return -1; } - SaveStrategy strategy = SaveStrategy::Silence | SaveStrategy::SkipModel | SaveStrategy::WithGcode | SaveStrategy::SkipAuxiliary; //1.9.7.52 + SaveStrategy strategy = SaveStrategy::Silence | SaveStrategy::SkipModel | SaveStrategy::WithGcode | SaveStrategy::SkipAuxiliary; #if !QDT_RELEASE_TO_PUBLIC //only save model in QA environment std::string sel = get_app_config()->get("iot_environment"); @@ -13473,14 +13927,30 @@ void Plater::set_bed_shape() const auto bundle = wxGetApp().preset_bundle; if (bundle != nullptr) { const Preset* curr = &bundle->printers.get_selected_preset(); - if (curr->is_system) + if (curr->is_system) { texture_filename = PresetUtils::system_printer_bed_texture(*curr); - else { - auto *printer_model = curr->config.opt("printer_model"); - if (printer_model != nullptr && ! printer_model->value.empty()) { - texture_filename = bundle->get_texture_for_printer_model(printer_model->value); + bool is_configed_by_QDT = PresetUtils::system_printer_bed_model(*curr).size() > 0; + if (is_configed_by_QDT) { + bool is_qdt_preset = bundle->printers.get_selected_preset().is_qdt_vendor_preset(bundle); + if (wxGetApp().app_config->has_section("user_qdt_svg_list")) { + auto cur_preset_name = bundle->printers.get_edited_preset().name; + auto user_qdt_svg_list = wxGetApp().app_config->get_section("user_qdt_svg_list"); + if (user_qdt_svg_list.size() > 0 && user_qdt_svg_list[cur_preset_name].size() > 0) { + texture_filename = user_qdt_svg_list[cur_preset_name]; + } else { + if (is_qdt_preset) { + texture_filename = ""; + } + } + } else if (is_qdt_preset) { + texture_filename = ""; + } } } + else { + auto *bed_custom_texture = curr->config.opt("bed_custom_texture"); + texture_filename = bed_custom_texture->value; + } } set_bed_shape(p->config->option("printable_area")->values, //QDS: add bed exclude areas @@ -13760,6 +14230,28 @@ void Plater::changed_mesh(int obj_idx) p->schedule_background_process(); } +void Plater::changed_object(ModelObject &object) +{ + assert(object.get_model() == &p->model); // is object from same model? + object.invalidate_bounding_box(); + + // recenter and re - align to Z = 0 + object.ensure_on_bed(p->printer_technology != ptSLA); + + if (p->printer_technology == ptSLA) { + // Update the SLAPrint from the current Model, so that the reload_scene() + // pulls the correct data, update the 3D scene. + p->update_restart_background_process(true, false); + } else + p->view3D->reload_scene(false); + + // update print + p->schedule_background_process(); + + // Check outside bed + get_current_canvas3D()->requires_check_outside_state(); +} + void Plater::changed_object(int obj_idx) { if (obj_idx < 0) @@ -14330,7 +14822,7 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi int action, plate_index; plate_index = hover_id / PartPlate::GRABBER_COUNT; - action = isModidyPlateName ? PartPlate::PLATE_NAME_HOVER_ID : hover_id % PartPlate::GRABBER_COUNT; + action = isModidyPlateName ? PartPlate::PLATE_NAME_ID : hover_id % PartPlate::GRABBER_COUNT; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": enter, hover_id %1%, plate_index %2%, action %3%")%hover_id % plate_index %action; if (action == 0) @@ -14420,36 +14912,42 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi } else if ((action == 2)&&(!right_click)) { - //arrange the plate - //take_snapshot("select_orient partplate"); - ret = select_plate(plate_index); - if (!ret) + if (p->can_do_ui_job()) { - set_prepare_state(Job::PREPARE_STATE_MENU); - orient(); - } - else - { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "can not select plate %1%" << plate_index; - ret = -1; + //arrange the plate + //take_snapshot("select_orient partplate"); + ret = select_plate(plate_index); + if (!ret) + { + set_prepare_state(Job::PREPARE_STATE_MENU); + orient(); + } + else + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "can not select plate %1%" << plate_index; + ret = -1; + } } } else if ((action == 3)&&(!right_click)) { - //arrange the plate - //take_snapshot("select_arrange partplate"); - ret = select_plate(plate_index); - if (!ret) + if (p->can_do_ui_job()) { - if (last_arrange_job_is_finished()) { - set_prepare_state(Job::PREPARE_STATE_MENU); - arrange(); + //arrange the plate + //take_snapshot("select_arrange partplate"); + ret = select_plate(plate_index); + if (!ret) + { + if (last_arrange_job_is_finished()) { + set_prepare_state(Job::PREPARE_STATE_MENU); + arrange(); + } + } + else + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "can not select plate %1%" << plate_index; + ret = -1; } - } - else - { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "can not select plate %1%" << plate_index; - ret = -1; } } else if ((action == 4)&&(!right_click)) @@ -14559,6 +15057,16 @@ void Plater::show_object_info() int obj_idx = selection.get_object_idx(); std::string info_text; + if (selection.is_wipe_tower()) { + info_text += _u8L("Prime Tower:"); + info_text += "\n"; + info_text += _u8L("A cube printed during a filament change to purge old color and ensure smooth color transition."); + notify_manager->qdt_show_objectsinfo_notification(info_text, false, !(p->current_panel == p->view3D), _u8L("View Wiki for more information"), [](wxEvtHandler*) { + wxGetApp().open_browser_with_warning_dialog("https://wiki.qiditech.com/en/software/qidi-studio/multi-color-printing"); + return false; + }); + return; + } if (selCount > 1 && !selection.is_single_full_object()) { notify_manager->qdt_close_objectsinfo_notification(); if (selection.get_mode() == Selection::EMode::Volume) { @@ -14570,7 +15078,7 @@ void Plater::show_object_info() notify_manager->qdt_show_objectsinfo_notification(info_text, false, !(p->current_panel == p->view3D)); return; } - else if (objects.empty() || (obj_idx < 0) || (obj_idx >= objects.size()) || + if (objects.empty() || (obj_idx < 0) || (obj_idx >= objects.size()) || objects[obj_idx]->volumes.empty() ||// hack to avoid crash when deleting the last object on the bed (selection.is_single_full_object() && objects[obj_idx]->instances.size()> 1) || !(selection.is_single_full_instance() || selection.is_single_volume())) @@ -14639,7 +15147,6 @@ void Plater::show_object_info() wxString info_manifold; int non_manifold_edges = 0; auto mesh_errors = p->sidebar->obj_list()->get_mesh_errors_info(&info_manifold, &non_manifold_edges); - //1.9.7.52 bool warning = non_manifold_edges > 0; wxString hyper_text; std::function callback; @@ -14654,7 +15161,6 @@ void Plater::show_object_info() #ifndef __WINDOWS__ if (non_manifold_edges > 0) { - //1.9.7.52 info_manifold += into_u8("\n" + _L("Tips:") + "\n" +_L("\"Fix Model\" feature is currently only on Windows. Please use a third-party tool to repair the model before importing it into QIDI Studio, such as ")); } if (warning) { @@ -14669,7 +15175,6 @@ void Plater::show_object_info() info_manifold = "" + info_manifold + ""; info_text += into_u8(info_manifold); - //1.9.7.52 notify_manager->qdt_show_objectsinfo_notification(info_text, warning, !(p->current_panel == p->view3D), into_u8(hyper_text), callback); } @@ -14826,7 +15331,7 @@ bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb bool Plater::can_simplify() const { return p->can_simplify(); } bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } -bool Plater::can_arrange() const { return p->can_arrange(); } +bool Plater::can_do_ui_job() const { return p->can_do_ui_job(); } bool Plater::can_layers_editing() const { return p->can_layers_editing(); } bool Plater::can_paste_from_clipboard() const { @@ -14903,8 +15408,14 @@ bool Plater::is_render_statistic_dialog_visible() const return p->show_render_statistic_dialog; } -void Plater::toggle_show_wireframe() -{ +void Plater::toggle_non_manifold_edges() { + p->show_non_manifold_edges = !p->show_non_manifold_edges; } + +bool Plater::is_show_non_manifold_edges() { + return p->show_non_manifold_edges; +} + +void Plater::toggle_show_wireframe() { p->show_wireframe = !p->show_wireframe; } @@ -14982,7 +15493,9 @@ bool Plater::PopupObjectTableBySelection() wxMenu* Plater::plate_menu() { return p->menus.plate_menu(); } wxMenu* Plater::object_menu() { return p->menus.object_menu(); } -wxMenu* Plater::part_menu() { return p->menus.part_menu(); } +wxMenu *Plater::part_menu() { return p->menus.part_menu(); } +wxMenu *Plater::svg_part_menu() { return p->menus.svg_part_menu(); } +wxMenu* Plater::cut_connector_menu() { return p->menus.cut_connector_menu(); } wxMenu* Plater::sla_object_menu() { return p->menus.sla_object_menu(); } wxMenu* Plater::default_menu() { return p->menus.default_menu(); } wxMenu* Plater::instance_menu() { return p->menus.instance_menu(); } @@ -15002,57 +15515,4 @@ SuppressBackgroundProcessingUpdate::~SuppressBackgroundProcessingUpdate() wxGetApp().plater()->schedule_background_process(m_was_scheduled); } -static wxString check_binary_vs_ascii_gcode_extension(PrinterTechnology pt, const std::string& ext, bool binary_output) -{ - wxString err_out; - if (pt == ptFFF) { - const bool binary_extension = (ext == ".bgcode" || ext == ".bgc"); - const bool ascii_extension = (ext == ".gcode" || ext == ".g" || ext == ".gco"); - if (binary_output && ascii_extension) { - // TRN The placeholder %1% is the file extension the user has selected. - err_out = format_wxstr(_L("Cannot save binary G-code with %1% extension.\n\n" - "Use a different extension or disable binary G-code export " - "in Printer Settings."), ext, "binary_gcode;printer"); - } - if (!binary_output && binary_extension) { - // TRN The placeholder %1% is the file extension the user has selected. - err_out = format_wxstr(_L("Cannot save ASCII G-code with %1% extension.\n\n" - "Use a different extension or enable binary G-code export " - "in Printer Settings."), ext, "binary_gcode;printer"); - } - } - return err_out; -} - -static void alert_when_exporting_binary_gcode(bool binary_output, const std::string& printer_notes) -{ - if (binary_output - && (boost::algorithm::contains(printer_notes, "PRINTER_MODEL_XL") - || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MINI") - || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK4") - || boost::algorithm::contains(printer_notes, "PRINTER_MODEL_MK3.9"))) - { - AppConfig* app_config = wxGetApp().app_config; - wxWindow* parent = wxGetApp().mainframe; - const std::string option_key = "dont_warn_about_firmware_version_when_exporting_binary_gcode"; - - if (app_config->get(option_key) != "1") { - const wxString url = "https://qidi.io/binary-gcode"; - //HtmlCapableRichMessageDialog dialog(parent, - // format_wxstr(_L("You are exporting binary G-code for a QIDI printer. " - // "Binary G-code enables significantly faster uploads. " - // "Ensure that your printer is running firmware version 5.1.0 or newer, as older versions do not support binary G-codes.\n\n" - // "To learn more about binary G-code, visit %1%."), - // url), - // _L("Warning"), wxOK, - // [&url](const std::string&) { wxGetApp().open_browser_with_warning_dialog(url); }); - //dialog.ShowCheckBox(_L("Don't show again")); - //if (dialog.ShowModal() == wxID_OK && dialog.IsCheckBoxChecked()) - // app_config->set(option_key, "1"); - } - } -} - - -} -} // namespace Slic3r::GUI +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a058dab..8198cbb 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -16,6 +16,7 @@ #include "libslic3r/BoundingBox.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "Jobs/Job.hpp" +#include "Jobs/Worker.hpp" #include "Search.hpp" #include "PartPlate.hpp" #include "GUI_App.hpp" @@ -302,12 +303,16 @@ public: // To be called when providing a list of files to the GUI slic3r on command line. std::vector load_files(const std::vector& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false); // to be called on drag and drop + bool emboss_svg(const wxString &svg_file, bool from_toolbar_or_file_menu = false); + bool load_svg(const wxArrayString &filenames, bool from_toolbar_or_file_menu = false); + bool load_same_type_files(const wxArrayString &filenames); bool load_files(const wxArrayString& filenames); - const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; } void update(bool conside_update_flag = false, bool force_background_processing_update = false); //QDS + Worker & get_ui_job_worker(); + const Worker &get_ui_job_worker() const; void object_list_changed(); void stop_jobs(); bool is_any_job_running() const; @@ -376,8 +381,10 @@ public: void export_gcode(bool prefer_removable); void export_gcode_3mf(bool export_all = false); void send_gcode_finish(wxString name); + //y void send_gcode_to_printer(); - void send_gcode_to_printers(); // y11 + void send_gcode_to_printers(); + void export_core_3mf(); static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function notify_func = {}); void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false); @@ -404,6 +411,7 @@ public: void clear_before_change_mesh(int obj_idx); void changed_mesh(int obj_idx); + void changed_object(ModelObject &object); void changed_object(int obj_idx); void changed_objects(const std::vector& object_idxs); void schedule_background_process(bool schedule = true); @@ -535,7 +543,7 @@ public: bool can_simplify() const; bool can_split_to_objects() const; bool can_split_to_volumes() const; - bool can_arrange() const; + bool can_do_ui_job() const; //QDS bool can_cut_to_clipboard() const; bool can_layers_editing() const; @@ -721,6 +729,8 @@ public: void toggle_render_statistic_dialog(); bool is_render_statistic_dialog_visible() const; + void toggle_non_manifold_edges(); + bool is_show_non_manifold_edges(); void toggle_show_wireframe(); bool is_show_wireframe() const; void enable_wireframe(bool status); @@ -739,6 +749,8 @@ public: wxMenu* plate_menu(); wxMenu* object_menu(); wxMenu* part_menu(); + wxMenu* svg_part_menu(); + wxMenu* cut_connector_menu(); wxMenu* sla_object_menu(); wxMenu* default_menu(); wxMenu* instance_menu(); @@ -759,7 +771,7 @@ public: return m_arrange_running.compare_exchange_strong(prevRunning, true); }; std::atomic m_arrange_running{false}; - + //y void resetUploadCount(); private: @@ -796,7 +808,7 @@ private: void _calib_pa_select_added_objects(); friend class SuppressBackgroundProcessingUpdate; - + //y std::thread m_sendThread; std::chrono::system_clock::time_point m_time_p; //w29 diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 18ffada..91ac7c0 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -16,7 +16,7 @@ #include "Widgets/TextInput.hpp" #include #include - +#include "Gizmos/GLGizmoBase.hpp" #ifdef __WINDOWS__ #ifdef _MSW_DARK_MODE #include "dark_mode.hpp" @@ -156,10 +156,19 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox( language_name = wxString::FromUTF8("\xD0\xA0\xD1\x83\xD1\x81\xD1\x81\xD0\xBA\xD0\xB8\xD0\xB9"); } else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_CZECH)) { - language_name = wxString::FromUTF8("\xC4\x8D\x65\xC5\xA1\x74\x69\x6E\x61"); + if (wxGetApp().app_config->get("language") == "ja_JP") { + language_name = wxString::FromUTF8("\x43\x7A\x65\x63\x68"); + } + else{ + language_name = wxString::FromUTF8("\xC4\x8D\x65\xC5\xA1\x74\x69\x6E\x61"); + } } else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_UKRAINIAN)) { - language_name = wxString::FromUTF8("\xD0\xA3\xD0\xBA\xD1\x80\xD0\xB0\xD1\x97\xD0\xBD\xD1\x81\xD1\x8C\xD0\xBA\xD0\xB0"); + if (wxGetApp().app_config->get("language") == "ja_JP") { + language_name = wxString::FromUTF8("\x55\x6B\x72\x61\x69\x6E\x69\x61\x6E"); + } else { + language_name = wxString::FromUTF8("\xD0\xA3\xD0\xBA\xD1\x80\xD0\xB0\xD1\x97\xD0\xBD\xD1\x81\xD1\x8C\xD0\xBA\xD0\xB0"); + } } else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_PORTUGUESE_BRAZILIAN)) { language_name = wxString::FromUTF8("\x50\x6F\x72\x74\x75\x67\x75\xC3\xAA\x73\x20\x28\x42\x72\x61\x73\x69\x6C\x29"); @@ -240,6 +249,7 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox( Close(); // Reparent(nullptr); GetParent()->RemoveChild(this); + Label::initSysFont(app_config->get_language_code()); wxGetApp().recreate_GUI(_L("Changing application language")); } } @@ -473,6 +483,64 @@ wxBoxSizer *PreferencesDialog::create_item_input(wxString title, wxString title2 return sizer_input; } +wxBoxSizer *PreferencesDialog::create_item_range_input( + wxString title, wxWindow *parent, wxString tooltip, std::string param, float range_min, float range_max, int keep_digital, std::function onchange) +{ + wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); + auto input_title = new wxStaticText(parent, wxID_ANY, title); + input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); + input_title->SetFont(::Label::Body_13); + input_title->SetToolTip(tooltip); + input_title->Wrap(-1); + + auto float_value = std::atof(app_config->get(param).c_str()); + if (float_value < range_min || float_value > range_max) { + float_value = range_min; + app_config->set(param, std::to_string(range_min)); + app_config->save(); + } + auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); + StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); + input->SetBackgroundColor(input_bg); + input->GetTextCtrl()->SetValue(app_config->get(param)); + wxTextValidator validator(wxFILTER_NUMERIC); + input->GetTextCtrl()->SetValidator(validator); + + sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); + sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); + sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL, 0); + auto format_str=[](int keep_digital,float val){ + std::stringstream ss; + ss << std::fixed << std::setprecision(keep_digital) << val; + return ss.str(); + }; + auto set_value_to_app = [this, param, onchange, input, range_min, range_max, format_str, keep_digital](float value, bool update_slider) { + if (value < range_min) { value = range_min; } + if (value > range_max) { value = range_max; } + auto str = format_str(keep_digital, value); + app_config->set(param, str); + app_config->save(); + if (onchange) { + onchange(str); + } + input->GetTextCtrl()->SetValue(str); + }; + input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, set_value_to_app, input](wxCommandEvent &e) { + auto value = std::atof(input->GetTextCtrl()->GetValue().c_str()); + set_value_to_app(value,true); + e.Skip(); + }); + + input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, set_value_to_app, input](wxFocusEvent &e) { + auto value = std::atof(input->GetTextCtrl()->GetValue().c_str()); + set_value_to_app(value, true); + e.Skip(); + }); + + return sizer_input; +} + + wxBoxSizer *PreferencesDialog::create_item_backup_input(wxString title, wxWindow *parent, wxString tooltip, std::string param) { wxBoxSizer *m_sizer_input = new wxBoxSizer(wxHORIZONTAL); @@ -753,6 +821,57 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxWindow *pa Slic3r::GUI::wxGetApp().update_internal_development(); } } + if (param == "show_print_history") { + auto show_history = app_config->get_bool("show_print_history"); + if (show_history == true) { + if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) { wxGetApp().mainframe->m_webview->ShowUserPrintTask(true); } + } else { + if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) { wxGetApp().mainframe->m_webview->ShowUserPrintTask(false); } + } + } + + if (param == "enable_lod") { + if (wxGetApp().plater()->is_project_dirty()) { + auto result = MessageDialog(static_cast(this), _L("The current project has unsaved changes, save it before continuing?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Save"), wxYES_NO | wxYES_DEFAULT | wxCENTRE) + .ShowModal(); + if (result == wxID_YES) { + wxGetApp().plater()->save_project(); + } + } + MessageDialog msg_wingow(nullptr, _L("Please note that the model show will undergo certain changes at small pixels case.\nEnabled LOD requires application restart.") + "\n" + _L("Do you want to continue?"), _L("Enable LOD"), + wxYES| wxYES_DEFAULT | wxCANCEL | wxCENTRE); + if (msg_wingow.ShowModal() == wxID_YES) { + Close(); + GetParent()->RemoveChild(this); + wxGetApp().recreate_GUI(_L("Enable LOD")); + } else { + checkbox->SetValue(!checkbox->GetValue()); + app_config->set_bool(param, checkbox->GetValue()); + app_config->save(); + } + } + if (param == "enable_opengl_multi_instance") { + if (wxGetApp().plater()->is_project_dirty()) { + auto result = MessageDialog(static_cast(this), _L("The current project has unsaved changes, save it before continuing?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Save"), wxYES_NO | wxYES_DEFAULT | wxCENTRE) + .ShowModal(); + if (result == wxID_YES) { wxGetApp().plater()->save_project(); } + } + MessageDialog msg_wingow(nullptr, + _L("Change opengl multi instance rendering requires application restart.") + "\n" + + _L("Do you want to continue?"), + _L("Enable opengl multi instance rendering"), wxYES | wxYES_DEFAULT | wxCANCEL | wxCENTRE); + if (msg_wingow.ShowModal() == wxID_YES) { + Close(); + GetParent()->RemoveChild(this); + wxGetApp().recreate_GUI(_L("Enable opengl multi instance rendering")); + } else { + checkbox->SetValue(!checkbox->GetValue()); + app_config->set_bool(param, checkbox->GetValue()); + app_config->save(); + } + } e.Skip(); }); @@ -1045,7 +1164,6 @@ wxWindow* PreferencesDialog::create_general_page() #endif 50, "single_instance"); - auto item_mouse_zoom_settings = create_item_checkbox(_L("Zoom to mouse position"), page, _L("Zoom in towards the mouse pointer's position in the 3D view, rather than the 2D window center."), 50, "zoom_to_mouse"); auto item_bed_type_follow_preset = create_item_checkbox(_L("Auto plate type"), page, _L("Studio will remember build plate selected last time for certain printer model."), 50, "user_bed_type"); @@ -1054,10 +1172,35 @@ wxWindow* PreferencesDialog::create_general_page() auto item_calc_in_long_retract = create_item_checkbox(_L("Flushing volumes: Auto-calculate every time when the filament is changed."), page, _L("If enabled, auto-calculate every time when filament is changed"), 50, "auto_calculate_when_filament_change"); // y96 Hide Multi-device Management auto item_multi_machine = create_item_checkbox(_L("Multi-device Management(Take effect after restarting Studio)."), page, _L("With this option enabled, you can send a task to multiple devices at the same time and manage multiple devices."), 50, "enable_multi_machine"); + auto item_step_mesh_setting = create_item_checkbox(_L("Show the step mesh parameter setting dialog."), page, _L("If enabled,a parameter settings dialog will appear during STEP file import."), 50, "enable_step_mesh_setting"); + auto item_beta_version_update = create_item_checkbox(_L("Support beta version update."), page, _L("With this option enabled, you can receive beta version updates."), 50, "enable_beta_version_update"); + auto _3d_settings = create_item_title(_L("3D Settings"), page, _L("3D Settings")); + auto item_mouse_zoom_settings = create_item_checkbox(_L("Zoom to mouse position"), page, + _L("Zoom in towards the mouse pointer's position in the 3D view, rather than the 2D window center."), 50, + "zoom_to_mouse"); + auto item_show_shells_in_preview_settings = create_item_checkbox(_L("Always show shells in preview"), page, + _L("Always show shells or not in preview view tab.If change value,you should reslice."), 50, + "show_shells_in_preview"); + auto enable_lod_settings = create_item_checkbox(_L("Improve rendering performance by lod"), page, + _L("Improved rendering performance under the scene of multiple plates and many models."), 50, + "enable_lod"); + auto enable_opengl_multi_instance_rendering = create_item_checkbox(_L("enable multi instance rendering by opengl"), page, + _L("If enabled, it can improve certain rendering performance. But for some graphics cards, it may not be applicable, please turn it off."), 50, "enable_opengl_multi_instance"); + float range_min = 1.0, range_max = 2.5; + auto item_grabber_size_settings = create_item_range_input(_L("Grabber scale"), page, + _L("Set grabber size for move,rotate,scale tool.") + _L("Value range") + ":[" + std::to_string(range_min) + "," + + std::to_string(range_max) + + "]","grabber_size_factor", range_min, range_max, 1, + [](wxString value) { + double d_value = 0; + if (value.ToDouble(&d_value)) { + GLGizmoBase::Grabber::GrabberSizeFactor = d_value; + } + }); auto title_presets = create_item_title(_L("Presets"), page, _L("Presets")); // y9 - // auto item_user_sync = create_item_checkbox(_L("Auto sync user presets(Printer/Filament/Process)"), page, _L("User Sync"), 50, "sync_user_preset"); - auto item_system_sync = create_item_checkbox(_L("Update built-in Presets automatically."), page, _L("System Sync"), 50, "sync_system_preset"); + // auto item_user_sync = create_item_checkbox(_L("Auto sync user presets(Printer/Filament/Process)"), page, _L("If enabled, auto sync user presets with cloud after QIDI Studio startup or presets modified."), 50, "sync_user_preset"); + auto item_system_sync = create_item_checkbox(_L("Auto check for system presets updates"), page, _L("If enabled, auto check whether there are system presets updates after QIDI Studio startup."), 50, "sync_system_preset"); auto item_save_presets = create_item_button(_L("Clear my choice on the unsaved presets."), _L("Clear"), page, _L("Clear my choice on the unsaved presets."), []() { wxGetApp().app_config->set("save_preset_choise", ""); }); @@ -1077,6 +1220,7 @@ wxWindow* PreferencesDialog::create_general_page() //auto title_modelmall = create_item_title(_L("Online Models"), page, _L("Online Models")); // auto item_backup = create_item_switch(_L("Backup switch"), page, _L("Backup switch"), "units"); //auto item_modelmall = create_item_checkbox(_L("Show online staff-picked models on the home page"), page, _L("Show online staff-picked models on the home page"), 50, "staff_pick_switch"); + // auto item_show_history = create_item_checkbox(_L("Show history on the home page"), page, _L("Show history on the home page"), 50, "show_print_history"); auto title_project = create_item_title(_L("Project"), page, ""); auto item_max_recent_count = create_item_input(_L("Maximum recent projects"), "", page, _L("Maximum count of recent projects"), "max_recent_count", [](wxString value) { @@ -1105,7 +1249,7 @@ wxWindow* PreferencesDialog::create_general_page() //ZY2 /*auto title_user_experience = create_item_title(_L("User Experience"), page, _L("User Experience")); auto item_priv_policy = create_item_checkbox(_L("Join Customer Experience Improvement Program."), page, "", 50, "privacyuse"); - wxHyperlinkCtrl* hyperlink = new wxHyperlinkCtrl(page, wxID_ANY, _L("What data would be collected?"), "https://qidilab.com/en/policies/privacy"); + wxHyperlinkCtrl* hyperlink = new wxHyperlinkCtrl(page, wxID_ANY, _L("What data would be collected?"), "https://qiditech.com/en/policies/privacy"); hyperlink->SetFont(Label::Head_13); item_priv_policy->Add(hyperlink, 0, wxALIGN_CENTER, 0);*/ @@ -1119,13 +1263,20 @@ wxWindow* PreferencesDialog::create_general_page() sizer_page->Add(item_region, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_currency, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_single_instance, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_mouse_zoom_settings, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_bed_type_follow_preset, 0, wxTOP, FromDIP(3)); //sizer_page->Add(item_hints, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_calc_mode, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_calc_in_long_retract, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_multi_machine, 0, wxTOP, FromDIP(3)); + sizer_page->Add(item_step_mesh_setting, 0, wxTOP, FromDIP(3)); + sizer_page->Add(item_beta_version_update, 0, wxTOP, FromDIP(3)); + sizer_page->Add(_3d_settings, 0, wxTOP | wxEXPAND, FromDIP(20)); + sizer_page->Add(item_mouse_zoom_settings, 0, wxTOP, FromDIP(3)); + sizer_page->Add(item_show_shells_in_preview_settings, 0, wxTOP, FromDIP(3)); + sizer_page->Add(enable_opengl_multi_instance_rendering, 0, wxTOP, FromDIP(3)); + sizer_page->Add(enable_lod_settings, 0, wxTOP, FromDIP(3)); + sizer_page->Add(item_grabber_size_settings, 0, wxTOP, FromDIP(3)); sizer_page->Add(title_presets, 0, wxTOP | wxEXPAND, FromDIP(20)); // y9 // sizer_page->Add(item_user_sync, 0, wxTOP, FromDIP(3)); @@ -1138,12 +1289,14 @@ wxWindow* PreferencesDialog::create_general_page() sizer_page->Add(item_associate_step, 0, wxTOP, FromDIP(3)); #endif // _WIN32 //ZY1 - /*auto item_title_modelmall = sizer_page->Add(title_modelmall, 0, wxTOP | wxEXPAND, FromDIP(20)); - auto item_item_modelmall = sizer_page->Add(item_modelmall, 0, wxTOP, FromDIP(3)); - auto update_modelmall = [this, item_title_modelmall, item_item_modelmall] (wxEvent & e) { + /*auto item_title_modelmall = sizer_page->Add(title_modelmall, 0, wxTOP | wxEXPAND, FromDIP(20)); + auto item_item_modelmall = sizer_page->Add(item_modelmall, 0, wxTOP, FromDIP(3)); + auto item_item_show_history = sizer_page->Add(item_show_history, 0, wxTOP, FromDIP(3)); + auto update_modelmall = [this, item_title_modelmall, item_item_modelmall, item_item_show_history](wxEvent &e) { bool has_model_mall = wxGetApp().has_model_mall(); item_title_modelmall->Show(has_model_mall); item_item_modelmall->Show(has_model_mall); + item_item_show_history->Show(has_model_mall); Layout(); Fit(); }; diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index c12d82b..0caf108 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -114,6 +114,8 @@ public: wxBoxSizer *create_item_button(wxString title, wxString title2, wxWindow *parent, wxString tooltip, std::function onclick); wxWindow* create_item_downloads(wxWindow* parent, int padding_left, std::string param); wxBoxSizer *create_item_input(wxString title, wxString title2, wxWindow *parent, wxString tooltip, std::string param, std::function onchange = {}); + wxBoxSizer *create_item_range_input( + wxString title, wxWindow *parent, wxString tooltip, std::string param, float range_min, float range_max, int keep_digital,std::function onchange = {}); wxBoxSizer *create_item_backup_input(wxString title, wxWindow *parent, wxString tooltip, std::string param); wxBoxSizer *create_item_multiple_combobox( wxString title, wxWindow *parent, wxString tooltip, int padding_left, std::string parama, std::vector vlista, std::vector vlistb); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 80d37c5..46d3fe6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -959,6 +959,8 @@ void PlaterPresetComboBox::update() std::map project_embedded_presets; std::map system_presets; std::map preset_descriptions; + std::map preset_filament_vendors; + std::map preset_filament_types; //QDS: move system to the end wxString selected_system_preset; @@ -983,6 +985,7 @@ void PlaterPresetComboBox::update() continue; bool single_bar = false; + const wxString name = get_preset_name(preset); if (m_type == Preset::TYPE_FILAMENT) { #if 0 @@ -994,12 +997,13 @@ void PlaterPresetComboBox::update() bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb; #endif + preset_filament_vendors[name] = preset.config.option("filament_vendor")->values.at(0); + preset_filament_types[name] = preset.config.option("filament_type")->values.at(0); } wxBitmap* bmp = get_bmp(preset); assert(bmp); - const wxString name = get_preset_name(preset); preset_descriptions.emplace(name, _L(preset.description)); if (preset.is_default || preset.is_system) { @@ -1041,32 +1045,59 @@ void PlaterPresetComboBox::update() if (m_type == Preset::TYPE_FILAMENT) add_ams_filaments(into_u8(selected_user_preset.empty() ? selected_system_preset : selected_user_preset), true); + //y + //std::vector filament_orders = {"QIDI PLA Basic", "QIDI PLA Matte", "QIDI PETG HF", "QIDI ABS", "QIDI PLA Silk", "QIDI PLA-CF", + // "QIDI PLA Galaxy", "QIDI PLA Metal", "QIDI PLA Marble", "QIDI PETG-CF", "QIDI PETG Translucent", "QIDI ABS-GF"}; + std::vector filament_orders = {}; + //w38 + std::vector first_vendors = {"QIDI", "Generic","Bambu Lab","HATCHBOX","Overture","Polymaker"}; + std::vector first_types = {"PLA", "PETG", "ABS", "TPU"}; + auto add_presets = [this, &preset_descriptions, &filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &first_types] + (std::map const &presets, wxString const &selected, std::string const &group) { + if (!presets.empty()) { + set_label_marker(Append(separator(group), wxNullBitmap)); + if (m_type == Preset::TYPE_FILAMENT) { + std::vector::value_type const*> list(presets.size(), nullptr); + std::transform(presets.begin(), presets.end(), list.begin(), [](auto & pair) { return &pair; }); + std::sort(list.begin(), list.end(), [&filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &first_types](auto *l, auto *r) { + { // Compare order + auto iter1 = std::find(filament_orders.begin(), filament_orders.end(), l->first); + auto iter2 = std::find(filament_orders.begin(), filament_orders.end(), r->first); + if (iter1 != iter2) + return iter1 < iter2; + } + { // Compare vendor + auto iter1 = std::find(first_vendors.begin(), first_vendors.end(), preset_filament_vendors[l->first]); + auto iter2 = std::find(first_vendors.begin(), first_vendors.end(), preset_filament_vendors[r->first]); + if (iter1 != iter2) + return iter1 < iter2; + } + { // Compare type + auto iter1 = std::find(first_types.begin(), first_types.end(), preset_filament_types[l->first]); + auto iter2 = std::find(first_types.begin(), first_types.end(), preset_filament_types[r->first]); + if (iter1 != iter2) + return iter1 < iter2; + } + return l->first < r->first; + }); + for (auto it : list) { + SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); + validate_selection(it->first == selected); + } + } else { + for (std::map::const_iterator it = presets.begin(); it != presets.end(); ++it) { + SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); + validate_selection(it->first == selected); + } + } + } + }; + //QDS: add project embedded preset logic - if (!project_embedded_presets.empty()) - { - set_label_marker(Append(separator(L("Project-inside presets")), wxNullBitmap)); - for (std::map::iterator it = project_embedded_presets.begin(); it != project_embedded_presets.end(); ++it) { - SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); - validate_selection(it->first == selected_user_preset); - } - } - if (!nonsys_presets.empty()) - { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); - validate_selection(it->first == selected_user_preset); - } - } - //QDS: move system to the end - if (!system_presets.empty()) - { - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); - for (std::map::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { - SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); - validate_selection(it->first == selected_system_preset); - } - } + add_presets(project_embedded_presets, selected_user_preset, L("Project-inside presets")); + add_presets(nonsys_presets, selected_user_preset, L("User presets")); + // QDS: move system to the end + add_presets(system_presets, selected_system_preset, L("System presets")); //QDS: remove unused pysical printer logic /*if (m_type == Preset::TYPE_PRINTER) diff --git a/src/slic3r/GUI/PrintOptionsDialog.cpp b/src/slic3r/GUI/PrintOptionsDialog.cpp index be74356..98bf543 100644 --- a/src/slic3r/GUI/PrintOptionsDialog.cpp +++ b/src/slic3r/GUI/PrintOptionsDialog.cpp @@ -441,6 +441,8 @@ PrinterPartsDialog::PrinterPartsDialog(wxWindow* parent) { nozzle_type_map[0] = "hardened_steel"; nozzle_type_map[1] = "stainless_steel"; + nozzle_type_map_tr[0] = _L("Hardened Steel"); + nozzle_type_map_tr[1] = _L("Stainless Steel"); nozzle_stainless_diameter_map[0] = 0.2; nozzle_stainless_diameter_map[1] = 0.4; @@ -467,8 +469,8 @@ PrinterPartsDialog::PrinterPartsDialog(wxWindow* parent) nozzle_type->Wrap(-1); nozzle_type_checkbox = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(140), -1), 0, NULL, wxCB_READONLY); - nozzle_type_checkbox->Append(_L("Stainless Steel")); - nozzle_type_checkbox->Append(_L("Hardened Steel")); + nozzle_type_checkbox->Append(nozzle_type_map_tr[0]); + nozzle_type_checkbox->Append(nozzle_type_map_tr[1]); nozzle_type_checkbox->SetSelection(0); @@ -605,7 +607,7 @@ bool PrinterPartsDialog::Show(bool show) for (int i=0; i < nozzle_type_map.size(); i++) { - nozzle_type_checkbox->Append( nozzle_type_map[i] ); + nozzle_type_checkbox->Append(nozzle_type_map_tr[i]); if (nozzle_type_map[i] == type) { nozzle_type_checkbox->SetSelection(i); } diff --git a/src/slic3r/GUI/PrintOptionsDialog.hpp b/src/slic3r/GUI/PrintOptionsDialog.hpp index db55d12..38f22ac 100644 --- a/src/slic3r/GUI/PrintOptionsDialog.hpp +++ b/src/slic3r/GUI/PrintOptionsDialog.hpp @@ -26,6 +26,7 @@ protected: ComboBox* nozzle_diameter_checkbox; std::string last_nozzle_type; std::map nozzle_type_map; + std::map nozzle_type_map_tr; std::map nozzle_stainless_diameter_map; std::map nozzle_hard_diameter_map; public: diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp index 6ea6735..4f10bb4 100644 --- a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp +++ b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp @@ -26,9 +26,9 @@ std::string last_system_error() { return Slic3r::decode_path(std::error_code( #ifdef _WIN32 - GetLastError(), + GetLastError(), #else - errno, + errno, #endif std::system_category()).message().c_str()); } @@ -54,18 +54,25 @@ static std::map error_messages = { }; struct StaticQIDILib : QIDILib { - static StaticQIDILib & get(); + static StaticQIDILib &get(QIDILib * copy = nullptr); static int Fake_QIDI_Create(QIDI_Tunnel*, char const*) { return -2; } + static void reset(); +private: + std::vector copies_; }; PrinterFileSystem::PrinterFileSystem() - : QIDILib(StaticQIDILib::get()) + : QIDILib(StaticQIDILib::get(this)) { if (!default_thumbnail.IsOk()) { - default_thumbnail = *Slic3r::GUI::BitmapCache().load_svg("printer_file", 0, 0); + Slic3r::GUI::BitmapCache c; + auto thumbnail = c.load_svg("printer_file", 0, 0); + if (thumbnail && thumbnail->IsOk()) { + default_thumbnail = *thumbnail; #ifdef __APPLE__ default_thumbnail = wxBitmap(default_thumbnail.ConvertToImage(), -1, 1); #endif + } } m_session.owner = this; #ifdef PRINTER_FILE_SYSTEM_TEST @@ -360,7 +367,7 @@ void PrinterFileSystem::FetchModel(size_t index, std::functionsecond.c_str()); else file_data->clear(); @@ -754,7 +761,7 @@ void PrinterFileSystem::UpdateFocusThumbnail() if (names.empty() && paths.empty()) return; m_task_flags |= FF_THUMNAIL; - UpdateFocusThumbnail2(std::make_shared>(paths.empty() ? names : paths), + UpdateFocusThumbnail2(std::make_shared>(paths.empty() ? names : paths), paths.empty() ? OldThumbnail : m_file_type == F_MODEL ? ModelMetadata : VideoThumbnail); } @@ -838,7 +845,7 @@ void PrinterFileSystem::UpdateFocusThumbnail2(std::shared_ptr> if (!thumbnail.empty()) { arr.push_back(file.path + "#" + thumbnail); file.flags &= ~FF_THUMNAIL; - file.local_path.clear(); + file.local_path.clear(); } } } @@ -944,7 +951,7 @@ std::pair PrinterFileSystem::FindFile(std m_file_list : m_file_list_cache[type]; if (index >= file_list.size() || (by_path ? file_list[index].path : file_list[index].name) != name) { - auto iter = std::find_if(m_file_list.begin(), file_list.end(), + auto iter = std::find_if(m_file_list.begin(), file_list.end(), [name, by_path](File &f) { return (by_path ? f.path : f.name) == name; }); if (iter == m_file_list.end()) return {file_list, -1}; index = std::distance(m_file_list.begin(), iter); @@ -1013,7 +1020,7 @@ void PrinterFileSystem::SendChangedEvent(wxEventType type, size_t index, std::st event.SetInt(index); if (!str.empty()) event.SetString(wxString::FromUTF8(str.c_str())); - else if (auto iter = error_messages.find(extra); iter != error_messages.end()) + else if (auto iter = error_messages.find(extra); iter != error_messages.end()) event.SetString(_L(iter->second.c_str())); else if (extra > CONTINUE && extra != ERROR_CANCEL) event.SetString(wxString::Format(_L("Error code: %d"), int(extra))); @@ -1277,7 +1284,7 @@ void PrinterFileSystem::Reconnect(boost::unique_lock &l, int resul } if (ret == 0) do { - ret = QIDI_StartStreamEx + ret = QIDI_StartStreamEx ? QIDI_StartStreamEx(tunnel, CTRL_TYPE) : QIDI_StartStream(tunnel, false); } while (ret == QIDI_would_block); @@ -1345,12 +1352,12 @@ static void* get_function(const char* name) #define GET_FUNC(x) lib.x = reinterpret_cast(get_function(#x)) -StaticQIDILib &StaticQIDILib::get() +StaticQIDILib &StaticQIDILib::get(QIDILib *copy) { static StaticQIDILib lib; // first load the library - if (lib.QIDI_Open) + if (lib.QIDI_Create) return lib; if (!module) { @@ -1374,11 +1381,22 @@ StaticQIDILib &StaticQIDILib::get() GET_FUNC(QIDI_SetLogger); GET_FUNC(QIDI_FreeLogMsg); - if (!lib.QIDI_Open) + if (!lib.QIDI_Create) { lib.QIDI_Create = Fake_QIDI_Create; + if (copy) + lib.copies_.push_back(copy); + } return lib; } +void StaticQIDILib::reset() +{ + get().QIDI_Create = nullptr; + auto &lib = get(); + for (auto c : lib.copies_) + *c = lib; +} + extern "C" QIDILib *qidilib_get() { return &StaticQIDILib::get(); } diff --git a/src/slic3r/GUI/Printer/gstQIDIsrc.c b/src/slic3r/GUI/Printer/gstQIDIsrc.c index fc0f8e8..40b2db4 100644 --- a/src/slic3r/GUI/Printer/gstQIDIsrc.c +++ b/src/slic3r/GUI/Printer/gstQIDIsrc.c @@ -1,5 +1,5 @@ /* qidisrc for gstreamer - * integration with proprietary QIDI Lab blob for getting raw h.264 video + * integration with proprietary QIDI Tech blob for getting raw h.264 video * * Copyright (C) 2023 Joshua Wise * @@ -136,14 +136,14 @@ gst_qidisrc_class_init (GstQIDISrcClass * klass) g_object_class_install_property (gobject_class, PROP_LOCATION, g_param_spec_string ("location", "Location", - "URI to pass to QIDI Lab blobs", "", + "URI to pass to QIDI Tech blobs", "", (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); gst_element_class_add_static_pad_template (gstelement_class, &srctemplate); - gst_element_class_set_static_metadata (gstelement_class, "QIDI Lab source", + gst_element_class_set_static_metadata (gstelement_class, "QIDI Tech source", "Source/Network", - "Receive data as a client over the network using the proprietary QIDI Lab blobs", + "Receive data as a client over the network using the proprietary QIDI Tech blobs", "Joshua Wise "); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qidisrc_change_state); @@ -160,7 +160,7 @@ gst_qidisrc_class_init (GstQIDISrcClass * klass) gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_qidisrc_create); GST_DEBUG_CATEGORY_INIT (gst_qidisrc_debug, "qidisrc", 0, - "QIDI Lab src"); + "QIDI Tech src"); } static void @@ -587,7 +587,7 @@ void gstqidisrc_register() return; did_register = 1; - gst_plugin_register_static(GST_VERSION_MAJOR, GST_VERSION_MINOR, "qidisrc", "QIDI Lab source", gstqidisrc_init, "0.0.1", "GPL", "QIDIStudio", "QIDIStudio", "https://github.com/qidilab/QIDIStudio"); + gst_plugin_register_static(GST_VERSION_MAJOR, GST_VERSION_MINOR, "qidisrc", "QIDI Tech source", gstqidisrc_init, "0.0.1", "GPL", "QIDIStudio", "QIDIStudio", "https://github.com/qiditech/QIDIStudio"); } #else @@ -596,6 +596,6 @@ void gstqidisrc_register() #define PACKAGE "qidisrc" #endif -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, qidisrc, "QIDI Lab source", gstqidisrc_init, "0.0.1", "GPL", "QIDIStudio", "https://github.com/qidilab/QIDIStudio") +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, qidisrc, "QIDI Tech source", gstqidisrc_init, "0.0.1", "GPL", "QIDIStudio", "https://github.com/qiditech/QIDIStudio") #endif diff --git a/src/slic3r/GUI/Printer/gstQIDIsrc.h b/src/slic3r/GUI/Printer/gstQIDIsrc.h index e1fabab..27caff8 100644 --- a/src/slic3r/GUI/Printer/gstQIDIsrc.h +++ b/src/slic3r/GUI/Printer/gstQIDIsrc.h @@ -1,5 +1,5 @@ /* qidisrc for gstreamer - * integration with proprietary QIDI Lab blob for getting raw h.264 video + * integration with proprietary QIDI Tech blob for getting raw h.264 video * * Copyright (C) 2023 Joshua Wise * diff --git a/src/slic3r/GUI/PrinterWebView.cpp b/src/slic3r/GUI/PrinterWebView.cpp index 84ebf8c..0055105 100644 --- a/src/slic3r/GUI/PrinterWebView.cpp +++ b/src/slic3r/GUI/PrinterWebView.cpp @@ -516,7 +516,10 @@ wxBoxSizer *PrinterWebView::init_menu_bar(wxPanel *Panel) std::pair(wxColour(76, 76, 80), StateColor::Hovered), std::pair(wxColour(67, 67, 71), StateColor::Normal)); - DeviceButton *machine_button = new DeviceButton(leftScrolledWindow, fullname, Machine_Name, wxBU_LEFT, wxSize(80, 80), device_name, ip, apikey); + //y48 + wxString machine_icon_path = from_u8(Slic3r::resources_dir() + "/" + "profiles" + "/" + "thumbnail" + "/") + Machine_Name; + + DeviceButton *machine_button = new DeviceButton(leftScrolledWindow, fullname, machine_icon_path, wxBU_LEFT, wxSize(80, 80), device_name, ip, apikey); machine_button->SetBackgroundColor(mac_btn_bg); machine_button->SetBorderColor(wxColour(57, 51, 55)); machine_button->SetCanFocus(false); @@ -595,9 +598,11 @@ wxBoxSizer *PrinterWebView::init_menu_bar(wxPanel *Panel) std::pair(wxColour(67, 67, 71), StateColor::Normal)); QIDINetwork m_qidinetwork; + //y48 + wxString machine_icon_path = from_u8(Slic3r::resources_dir() + "/" + "profiles" + "/" + "thumbnail" + "/") + Machine_Name; // device_name = m_qidinetwork.UTF8ToGBK(device_name.c_str()); - DeviceButton *machine_button = new DeviceButton(leftScrolledWindow, device.device_name, Machine_Name, wxBU_LEFT, wxSize(80, 80), + DeviceButton *machine_button = new DeviceButton(leftScrolledWindow, device.device_name, machine_icon_path, wxBU_LEFT, wxSize(80, 80), device_name, device.local_ip); machine_button->SetBackgroundColor(mac_btn_bg); diff --git a/src/slic3r/GUI/Project.cpp b/src/slic3r/GUI/Project.cpp index 928104d..4f4bde1 100644 --- a/src/slic3r/GUI/Project.cpp +++ b/src/slic3r/GUI/Project.cpp @@ -29,6 +29,8 @@ #include "MainFrame.hpp" #include +#include + namespace Slic3r { namespace GUI { wxDEFINE_EVENT(EVT_PROJECT_RELOAD, wxCommandEvent); @@ -61,7 +63,7 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this); m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this, m_browser->GetId()); - + Bind(wxEVT_WEBVIEW_NEWWINDOW, &ProjectPanel::OnNewWindow, this); Bind(EVT_PROJECT_RELOAD, &ProjectPanel::on_reload, this); SetSizer(main_sizer); @@ -71,6 +73,42 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, ProjectPanel::~ProjectPanel() {} +std::string trim(const std::string &str, const std::string &charsToTrim) +{ + std::regex pattern("^[" + charsToTrim + "]+|[" + charsToTrim + "]+$"); + return std::regex_replace(str, pattern, ""); +} + +/** + * On new window, we veto to stop extra windows appearing + */ +void ProjectPanel::OnNewWindow(wxWebViewEvent &evt) +{ + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << ": " << evt.GetURL().ToUTF8().data(); + wxString flag = " (other)"; + + if (evt.GetNavigationAction() == wxWEBVIEW_NAV_ACTION_USER) { flag = " (user)"; } + + if (wxGetApp().get_mode() == comDevelop) + wxLogMessage("%s", "New window; url='" + evt.GetURL() + "'" + flag); + + // If we handle new window events then just load them in local browser + wxString tmpUrl = evt.GetURL(); + + if (tmpUrl.StartsWith("File://") || tmpUrl.StartsWith("file://")) + { + std::regex pattern("%22http.+%22"); + std::smatch matches; + std::string UrlTmp = tmpUrl.ToStdString(); + if (std::regex_search(UrlTmp, matches, pattern)) { tmpUrl = trim(matches[0].str(), "%22"); } + } + + if (boost::starts_with(tmpUrl, "http://") || boost::starts_with(tmpUrl, "https://")) { + m_browser->Stop(); + evt.Veto(); + wxLaunchDefaultApplication(tmpUrl); + } +} void ProjectPanel::onWebNavigating(wxWebViewEvent& evt) { @@ -109,8 +147,6 @@ void ProjectPanel::on_reload(wxCommandEvent& evt) description = model.model_info->description; update_type = model.model_info->origin; - - if (model.design_info && !model.design_info->DesignerId.empty()) { if (m_model_id_map.count(model.design_info->DesignerId) > 0) { @@ -121,7 +157,6 @@ void ProjectPanel::on_reload(wxCommandEvent& evt) m_model_id_map[model.design_info->DesignerId] = model_id; } } - try { if (!model.model_info->copyright.empty()) { @@ -277,8 +312,6 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt) } } } - - } else if (strCmd == "debug_info") { //wxString msg = j["msg"]; diff --git a/src/slic3r/GUI/Project.hpp b/src/slic3r/GUI/Project.hpp index 65d332c..b0f1b88 100644 --- a/src/slic3r/GUI/Project.hpp +++ b/src/slic3r/GUI/Project.hpp @@ -67,7 +67,6 @@ private: wxString m_root_dir; std::map m_model_id_map; static inline int m_sequence_id = 8000; - public: ProjectPanel(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL); @@ -78,7 +77,8 @@ public: void on_reload(wxCommandEvent& evt); void on_size(wxSizeEvent& event); void on_navigated(wxWebViewEvent& event); - + void OnNewWindow(wxWebViewEvent &evt); + void msw_rescale(); void update_model_data(); void clear_model_info(); diff --git a/src/slic3r/GUI/ReleaseNote.cpp b/src/slic3r/GUI/ReleaseNote.cpp index 5bd5634..334edf3 100644 --- a/src/slic3r/GUI/ReleaseNote.cpp +++ b/src/slic3r/GUI/ReleaseNote.cpp @@ -33,11 +33,13 @@ wxDEFINE_EVENT(EVT_CHECKBOX_CHANGE, wxCommandEvent); wxDEFINE_EVENT(EVT_ENTER_IP_ADDRESS, wxCommandEvent); wxDEFINE_EVENT(EVT_CLOSE_IPADDRESS_DLG, wxCommandEvent); wxDEFINE_EVENT(EVT_CHECK_IP_ADDRESS_FAILED, wxCommandEvent); +wxDEFINE_EVENT(EVT_CHECK_IP_ADDRESS_LAYOUT, wxCommandEvent); wxDEFINE_EVENT(EVT_SECONDARY_CHECK_RETRY, wxCommandEvent); wxDEFINE_EVENT(EVT_PRINT_ERROR_STOP, wxCommandEvent); wxDEFINE_EVENT(EVT_UPDATE_NOZZLE, wxCommandEvent); wxDEFINE_EVENT(EVT_JUMP_TO_HMS, wxCommandEvent); wxDEFINE_EVENT(EVT_JUMP_TO_LIVEVIEW, wxCommandEvent); +wxDEFINE_EVENT(EVT_UPDATE_TEXT_MSG, wxCommandEvent); ReleaseNoteDialog::ReleaseNoteDialog(Plater *plater /*= nullptr*/) : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, _L("Release Note"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) @@ -880,7 +882,7 @@ PrintErrorDialog::PrintErrorDialog(wxWindow* parent, wxWindowID id, const wxStri bottom_sizer->Add(m_sizer_button, 0, wxEXPAND | wxRIGHT | wxLEFT, 0); - m_sizer_right->Add(bottom_sizer, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15)); + m_sizer_right->Add(bottom_sizer, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(20)); m_sizer_right->Add(0, 0, 0, wxTOP, FromDIP(10)); m_sizer_main->Add(m_sizer_right, 0, wxBOTTOM | wxEXPAND, FromDIP(5)); @@ -936,20 +938,34 @@ void PrintErrorDialog::on_webrequest_state(wxWebRequestEvent& evt) } } -void PrintErrorDialog::update_text_image(wxString text, wxString image_url) +void PrintErrorDialog::update_text_image(const wxString& text, const wxString& error_code, const wxString& image_url) { //if (!m_sizer_text_release_note) { // m_sizer_text_release_note = new wxBoxSizer(wxVERTICAL); //} wxBoxSizer* sizer_text_release_note = new wxBoxSizer(wxVERTICAL); - + wxString error_code_msg = error_code; + if (!error_code.IsEmpty()) { + wxDateTime now = wxDateTime::Now(); + wxString show_time = now.Format("%H%M%d"); + error_code_msg = wxString::Format("[%S %S]", error_code, show_time); + } + if (!m_staticText_release_note) { m_staticText_release_note = new Label(m_vebview_release_note, text, LB_AUTO_WRAP); sizer_text_release_note->Add(m_error_prompt_pic_static, 0, wxALIGN_CENTER, FromDIP(5)); + sizer_text_release_note->AddSpacer(10); sizer_text_release_note->Add(m_staticText_release_note, 0, wxALIGN_CENTER , FromDIP(5)); - m_vebview_release_note->SetSizer(sizer_text_release_note); } + if (!m_staticText_error_code) { + m_staticText_error_code = new Label(m_vebview_release_note, error_code_msg, LB_AUTO_WRAP); + sizer_text_release_note->AddSpacer(5); + sizer_text_release_note->Add(m_staticText_error_code, 0, wxALIGN_CENTER, FromDIP(5)); + } + + m_vebview_release_note->SetSizer(sizer_text_release_note); + if (!image_url.empty()) { web_request = wxWebSession::GetDefault().CreateRequest(this, image_url); BOOST_LOG_TRIVIAL(trace) << "monitor: create new webrequest, state = " << web_request.GetState() << ", url = " << image_url; @@ -966,6 +982,9 @@ void PrintErrorDialog::update_text_image(wxString text, wxString image_url) m_staticText_release_note->SetMaxSize(wxSize(FromDIP(300), -1)); m_staticText_release_note->SetMinSize(wxSize(FromDIP(300), -1)); m_staticText_release_note->SetLabelText(text); + m_staticText_error_code->SetMaxSize(wxSize(FromDIP(300), -1)); + m_staticText_error_code->SetMinSize(wxSize(FromDIP(300), -1)); + m_staticText_error_code->SetLabelText(error_code_msg); m_vebview_release_note->Layout(); auto text_size = m_staticText_release_note->GetBestSize(); @@ -974,7 +993,7 @@ void PrintErrorDialog::update_text_image(wxString text, wxString image_url) m_vebview_release_note->SetMinSize(wxSize(FromDIP(320), text_size.y + FromDIP(220))); } else { - m_vebview_release_note->SetMinSize(wxSize(FromDIP(320), text_size.y + FromDIP(25))); + m_vebview_release_note->SetMinSize(wxSize(FromDIP(320), text_size.y + FromDIP(50))); } else { m_vebview_release_note->SetMinSize(wxSize(FromDIP(320), FromDIP(340))); @@ -1032,7 +1051,8 @@ void PrintErrorDialog::update_title_style(wxString title, std::vector butto } -void PrintErrorDialog::init_button(PrintErrorButton style,wxString buton_text) { +void PrintErrorDialog::init_button(PrintErrorButton style,wxString buton_text) +{ Button* print_error_button = new Button(this, buton_text); print_error_button->SetBackgroundColor(btn_bg_white); print_error_button->SetBorderColor(wxColour(38, 46, 48)); @@ -1046,8 +1066,8 @@ void PrintErrorDialog::init_button(PrintErrorButton style,wxString buton_text) { } -void PrintErrorDialog::init_button_list() { - +void PrintErrorDialog::init_button_list() +{ init_button(RESUME_PRINTING, _L("Resume Printing")); m_button_list[RESUME_PRINTING]->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) { post_event(wxCommandEvent(EVT_SECONDARY_CHECK_RESUME)); @@ -1421,24 +1441,30 @@ void ConfirmBeforeSendDialog::rescale() m_button_cancel->Rescale(); } -InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) - :DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, _L("LAN Connection Failed (Sending print file)"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +InputIpAddressDialog::InputIpAddressDialog(wxWindow *parent) + : DPIDialog(static_cast(wxGetApp().mainframe), + wxID_ANY, + _L("Connect the printer using IP and access code"), + wxDefaultPosition, + wxDefaultSize, + wxCAPTION | wxCLOSE_BOX) { 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); - m_result = -1; - wxBoxSizer* m_sizer_body = new wxBoxSizer(wxVERTICAL); - wxBoxSizer* m_sizer_main = new wxBoxSizer(wxHORIZONTAL); - wxBoxSizer* m_sizer_main_left = new wxBoxSizer(wxVERTICAL); - wxBoxSizer* m_sizer_main_right = new wxBoxSizer(wxVERTICAL); - wxBoxSizer* m_sizer_msg = new wxBoxSizer(wxHORIZONTAL); - auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); + m_result = -1; + wxBoxSizer *m_sizer_body = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *m_sizer_main = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *m_sizer_main_left = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *m_sizer_main_right = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *m_sizer_msg = new wxBoxSizer(wxHORIZONTAL); + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); m_line_top->SetBackgroundColour(wxColour(166, 169, 170)); - comfirm_before_enter_text = _L("Step 1, please confirm QIDI Studio and your printer are in the same LAN."); - comfirm_after_enter_text = _L("Step 2, if the IP and Access Code below are different from the actual values on your printer, please correct them."); + comfirm_before_enter_text = _L("Step 1. Please confirm QIDI Studio and your printer are in the same LAN."); + comfirm_after_enter_text = _L("Step 2. If the IP and Access Code below are different from the actual values on your printer, please correct them."); + comfirm_last_enter_text = _L("Step 3. Please obtain the device SN from the printer side; it is usually found in the device information on the printer screen."); m_tip1 = new Label(this, ::Label::Body_13, comfirm_before_enter_text, LB_AUTO_WRAP); @@ -1446,30 +1472,42 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_tip1->SetMaxSize(wxSize(FromDIP(352), -1)); m_tip1->Wrap(FromDIP(352)); - auto m_line_tips = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); - m_line_tips->SetBackgroundColour(wxColour(0xEEEEEE)); - m_tip2 = new Label(this, ::Label::Body_13, comfirm_after_enter_text, LB_AUTO_WRAP); m_tip2->SetMinSize(wxSize(FromDIP(352), -1)); m_tip2->SetMaxSize(wxSize(FromDIP(352), -1)); - auto m_input_tip_area = new wxBoxSizer(wxHORIZONTAL); - auto m_input_area = new wxBoxSizer(wxHORIZONTAL); + m_tip3 = new Label(this, ::Label::Body_13, comfirm_last_enter_text, LB_AUTO_WRAP); + m_tip3->SetMinSize(wxSize(FromDIP(352), -1)); + m_tip3->SetMaxSize(wxSize(FromDIP(352), -1)); - m_tips_ip = new Label(this, "IP"); + ip_input_top_panel = new wxPanel(this); + ip_input_bot_panel = new wxPanel(this); + + ip_input_top_panel->SetBackgroundColour(*wxWHITE); + ip_input_bot_panel->SetBackgroundColour(*wxWHITE); + + auto m_input_top_sizer = new wxBoxSizer(wxVERTICAL); + auto m_input_bot_sizer = new wxBoxSizer(wxVERTICAL); + + /*top input*/ + + auto m_input_tip_area = new wxBoxSizer(wxHORIZONTAL); + auto m_input_area = new wxBoxSizer(wxHORIZONTAL); + + m_tips_ip = new Label(ip_input_top_panel, "IP"); m_tips_ip->SetMinSize(wxSize(FromDIP(168), -1)); m_tips_ip->SetMaxSize(wxSize(FromDIP(168), -1)); - m_input_ip = new TextInput(this, wxEmptyString, wxEmptyString); + m_input_ip = new TextInput(ip_input_top_panel, wxEmptyString, wxEmptyString); m_input_ip->Bind(wxEVT_TEXT, &InputIpAddressDialog::on_text, this); m_input_ip->SetMinSize(wxSize(FromDIP(168), FromDIP(28))); m_input_ip->SetMaxSize(wxSize(FromDIP(168), FromDIP(28))); - m_tips_access_code = new Label(this, _L("Access Code")); - m_tips_access_code->SetMinSize(wxSize(FromDIP(168),-1)); - m_tips_access_code->SetMaxSize(wxSize(FromDIP(168),-1)); + m_tips_access_code = new Label(ip_input_top_panel, _L("Access Code")); + m_tips_access_code->SetMinSize(wxSize(FromDIP(168), -1)); + m_tips_access_code->SetMaxSize(wxSize(FromDIP(168), -1)); - m_input_access_code = new TextInput(this, wxEmptyString, wxEmptyString); + m_input_access_code = new TextInput(ip_input_top_panel, wxEmptyString, wxEmptyString); m_input_access_code->Bind(wxEVT_TEXT, &InputIpAddressDialog::on_text, this); m_input_access_code->SetMinSize(wxSize(FromDIP(168), FromDIP(28))); m_input_access_code->SetMaxSize(wxSize(FromDIP(168), FromDIP(28))); @@ -1477,11 +1515,65 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_input_tip_area->Add(m_tips_ip, 0, wxALIGN_CENTER, 0); m_input_tip_area->Add(0, 0, 0, wxLEFT, FromDIP(16)); m_input_tip_area->Add(m_tips_access_code, 0, wxALIGN_CENTER, 0); - + m_input_area->Add(m_input_ip, 0, wxALIGN_CENTER, 0); m_input_area->Add(0, 0, 0, wxLEFT, FromDIP(16)); m_input_area->Add(m_input_access_code, 0, wxALIGN_CENTER, 0); + m_input_top_sizer->Add(m_input_tip_area, 0, wxRIGHT | wxEXPAND, FromDIP(18)); + m_input_top_sizer->Add(0, 0, 0, wxTOP, FromDIP(4)); + m_input_top_sizer->Add(m_input_area, 0, wxRIGHT | wxEXPAND, FromDIP(18)); + + ip_input_top_panel->SetSizer(m_input_top_sizer); + ip_input_top_panel->Layout(); + ip_input_top_panel->Fit(); + + /*bom input*/ + auto m_input_sn_area = new wxBoxSizer(wxHORIZONTAL); + auto m_input_modelID_area = new wxBoxSizer(wxHORIZONTAL); + + m_tips_sn = new Label(ip_input_bot_panel, "SN"); + m_tips_sn->SetMinSize(wxSize(FromDIP(168), -1)); + m_tips_sn->SetMaxSize(wxSize(FromDIP(168), -1)); + + m_input_sn = new TextInput(ip_input_bot_panel, wxEmptyString, wxEmptyString); + m_input_sn->Bind(wxEVT_TEXT, &InputIpAddressDialog::on_text, this); + m_input_sn->SetMinSize(wxSize(FromDIP(168), FromDIP(28))); + m_input_sn->SetMaxSize(wxSize(FromDIP(168), FromDIP(28))); + + m_tips_modelID = new Label(ip_input_bot_panel, _L("Printer model")); + m_tips_modelID->SetMinSize(wxSize(FromDIP(168), -1)); + m_tips_modelID->SetMaxSize(wxSize(FromDIP(168), -1)); + + m_input_modelID = new ComboBox(ip_input_bot_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(168), FromDIP(28)), 0, nullptr, wxCB_READONLY); + // m_input_modelID->Bind(wxEVT_TEXT, &InputIpAddressDialog::on_text, this); + m_input_modelID->SetMinSize(wxSize(FromDIP(168), FromDIP(28))); + m_input_modelID->SetMaxSize(wxSize(FromDIP(168), FromDIP(28))); + + m_models_map = DeviceManager::get_all_model_id_with_name(); + for (auto it = m_models_map.begin(); it != m_models_map.end(); ++it) { + m_input_modelID->Append(it->right); + m_input_modelID->SetSelection(0); + } + + m_input_sn_area->Add(m_tips_sn, 0, wxALIGN_CENTER, 0); + m_input_sn_area->Add(0, 0, 0, wxLEFT, FromDIP(16)); + m_input_sn_area->Add(m_tips_modelID, 0, wxALIGN_CENTER, 0); + + m_input_modelID_area->Add(m_input_sn, 0, wxALIGN_CENTER, 0); + m_input_modelID_area->Add(0, 0, 0, wxLEFT, FromDIP(16)); + m_input_modelID_area->Add(m_input_modelID, 0, wxALIGN_CENTER, 0); + + m_input_bot_sizer->Add(m_input_sn_area, 0, wxRIGHT | wxEXPAND, FromDIP(18)); + m_input_bot_sizer->Add(0, 0, 0, wxTOP, FromDIP(4)); + m_input_bot_sizer->Add(m_input_modelID_area, 0, wxRIGHT | wxEXPAND, FromDIP(18)); + + ip_input_bot_panel->SetSizer(m_input_bot_sizer); + ip_input_bot_panel->Layout(); + ip_input_bot_panel->Fit(); + + /*other*/ + m_test_right_msg = new Label(this, Label::Body_13, wxEmptyString, LB_AUTO_WRAP); m_test_right_msg->SetForegroundColour(wxColour(129, 150, 255)); m_test_right_msg->Hide(); @@ -1491,11 +1583,7 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_test_wrong_msg->SetForegroundColour(wxColour(208, 27, 27)); m_test_wrong_msg->Hide(); - m_tip3 = new Label(this, Label::Body_12, _L("Where to find your printer's IP and Access Code?"), LB_AUTO_WRAP); - m_tip3->SetMinSize(wxSize(FromDIP(352), -1)); - m_tip3->SetMaxSize(wxSize(FromDIP(352), -1)); - - m_tip4 = new Label(this, ::Label::Body_13, _L("Step 3: Ping the IP address to check for packet loss and latency."), LB_AUTO_WRAP); + m_tip4 = new Label(this, Label::Body_12, _L("Where to find your printer's IP and Access Code?"), LB_AUTO_WRAP); m_tip4->SetMinSize(wxSize(FromDIP(352), -1)); m_tip4->SetMaxSize(wxSize(FromDIP(352), -1)); @@ -1520,11 +1608,12 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_button_ok->SetSize(wxSize(FromDIP(58), FromDIP(24))); m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); m_button_ok->SetCornerRadius(FromDIP(12)); - - m_button_ok->Bind(wxEVT_LEFT_DOWN, &InputIpAddressDialog::on_ok, this); + m_button_ok->Enable(false); + m_button_ok->SetBackgroundColor(wxColour(0x90, 0x90, 0x90)); + m_button_ok->SetBorderColor(wxColour(0x90, 0x90, 0x90)); - auto m_button_cancel = new Button(this, _L("Close")); + /*auto m_button_cancel = new Button(this, _L("Close")); m_button_cancel->SetBackgroundColor(btn_bg_white); m_button_cancel->SetBorderColor(wxColour(38, 46, 48)); m_button_cancel->SetFont(Label::Body_12); @@ -1534,39 +1623,34 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) { on_cancel(); - }); + });*/ m_sizer_button->AddStretchSpacer(); - //m_sizer_button->Add(m_button_ok, 0, wxALL, FromDIP(5)); - m_sizer_button->Add(m_button_cancel, 0, wxALL, FromDIP(5)); + m_sizer_button->Add(m_button_ok, 0, wxALL, FromDIP(5)); + // m_sizer_button->Add(m_button_cancel, 0, wxALL, FromDIP(5)); m_sizer_button->Layout(); - m_status_bar = std::make_shared(this); + m_status_bar = std::make_shared(this); m_status_bar->get_panel()->Hide(); - auto m_step_icon_panel1 = new wxWindow(this, wxID_ANY); auto m_step_icon_panel2 = new wxWindow(this, wxID_ANY); - + m_step_icon_panel3 = new wxWindow(this, wxID_ANY); + m_step_icon_panel1->SetBackgroundColour(*wxWHITE); m_step_icon_panel2->SetBackgroundColour(*wxWHITE); - + m_step_icon_panel3->SetBackgroundColour(*wxWHITE); auto m_sizer_step_icon_panel1 = new wxBoxSizer(wxVERTICAL); auto m_sizer_step_icon_panel2 = new wxBoxSizer(wxVERTICAL); - + auto m_sizer_step_icon_panel3 = new wxBoxSizer(wxVERTICAL); m_img_step1 = new wxStaticBitmap(m_step_icon_panel1, wxID_ANY, create_scaled_bitmap("ip_address_step", this, 6), wxDefaultPosition, wxSize(FromDIP(6), FromDIP(6)), 0); - auto m_line_tips_left = new wxPanel(m_step_icon_panel1, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); - m_line_tips_left->SetBackgroundColour(wxColour(0xEEEEEE)); m_img_step2 = new wxStaticBitmap(m_step_icon_panel2, wxID_ANY, create_scaled_bitmap("ip_address_step", this, 6), wxDefaultPosition, wxSize(FromDIP(6), FromDIP(6)), 0); - m_img_step3 = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("ip_address_step", this, 6), wxDefaultPosition, wxSize(FromDIP(6), FromDIP(6)), 0); - - m_sizer_step_icon_panel1->Add(m_img_step1, 0, wxALIGN_CENTER|wxALL, FromDIP(5)); - + m_img_step3 = new wxStaticBitmap(m_step_icon_panel3, wxID_ANY, create_scaled_bitmap("ip_address_step", this, 6), wxDefaultPosition, wxSize(FromDIP(6), FromDIP(6)), 0); m_step_icon_panel1->SetSizer(m_sizer_step_icon_panel1); m_step_icon_panel1->Layout(); @@ -1576,52 +1660,52 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_step_icon_panel2->Layout(); m_step_icon_panel2->Fit(); + m_step_icon_panel3->SetSizer(m_sizer_step_icon_panel3); + m_step_icon_panel3->Layout(); + m_step_icon_panel3->Fit(); - - m_sizer_step_icon_panel2->Add(m_img_step2, 0, wxALIGN_CENTER|wxALL, FromDIP(5)); - //m_sizer_step_icon_panel3->Add(m_img_step3, 0, wxALIGN_CENTER|wxALL, FromDIP(5)); + m_sizer_step_icon_panel1->Add(m_img_step1, 0, wxALIGN_CENTER | wxALL, FromDIP(5)); + m_sizer_step_icon_panel2->Add(m_img_step2, 0, wxALIGN_CENTER | wxALL, FromDIP(5)); + m_sizer_step_icon_panel3->Add(m_img_step3, 0, wxALIGN_CENTER | wxALL, FromDIP(5)); m_step_icon_panel1->SetMinSize(wxSize(-1, m_tip1->GetBestSize().y)); m_step_icon_panel1->SetMaxSize(wxSize(-1, m_tip1->GetBestSize().y)); - m_sizer_msg->Add(0, 0, 0, wxALIGN_CENTER, FromDIP(20)); - m_sizer_msg->Add(m_img_step3, 0, wxALL, FromDIP(5)); - m_sizer_msg->Add(0, 0, 0, wxALIGN_CENTER, FromDIP(8)); - m_sizer_msg->Add(m_tip4, 0, wxALIGN_CENTER | wxEXPAND | wxLEFT, FromDIP(5)); - m_img_step3->Hide(); - m_tip4->Hide(); + m_step_icon_panel2->SetMinSize(wxSize(-1, m_tip2->GetBestSize().y)); + m_step_icon_panel2->SetMaxSize(wxSize(-1, m_tip2->GetBestSize().y)); + + m_sizer_msg->Layout(); m_sizer_main_left->Add(m_step_icon_panel1, 0, wxEXPAND, 0); m_sizer_main_left->Add(0, 0, 0, wxTOP, FromDIP(20)); - m_sizer_main_left->Add(m_line_tips_left, 1, wxEXPAND, 0); - m_sizer_main_left->Add(0, 0, 0, wxTOP, FromDIP(20)); m_sizer_main_left->Add(m_step_icon_panel2, 0, wxEXPAND, 0); + m_sizer_main_left->Add(0, 0, 0, wxTOP, FromDIP(20)); + m_sizer_main_left->Add(m_step_icon_panel3, 0, wxEXPAND, 0); m_sizer_main_left->Layout(); m_trouble_shoot->Hide(); - - m_sizer_main_right->Add(m_tip1, 0, wxRIGHT|wxEXPAND, FromDIP(18)); + + m_sizer_main_right->Add(m_tip1, 0, wxRIGHT | wxEXPAND, FromDIP(18)); m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(20)); - m_sizer_main_right->Add(m_line_tips, 0, wxRIGHT|wxEXPAND, FromDIP(18)); - m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(20)); - m_sizer_main_right->Add(m_tip2, 0, wxRIGHT|wxEXPAND, FromDIP(18)); + m_sizer_main_right->Add(m_tip2, 0, wxRIGHT | wxEXPAND, FromDIP(18)); + m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(2)); + m_sizer_main_right->Add(m_tip3, 0, wxTOP|wxRIGHT|wxEXPAND, FromDIP(18)); m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(12)); - m_sizer_main_right->Add(m_tip3, 0, wxRIGHT | wxEXPAND, FromDIP(18)); - m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(4)); + m_sizer_main_right->Add(m_tip4, 0, wxRIGHT | wxEXPAND, FromDIP(18)); + m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(3)); m_sizer_main_right->Add(m_img_help, 0, 0, 0); m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(12)); - m_sizer_main_right->Add(m_input_tip_area, 0, wxRIGHT|wxEXPAND, FromDIP(18)); + m_sizer_main_right->Add(ip_input_top_panel, 0, wxRIGHT|wxEXPAND, FromDIP(18)); + m_sizer_main_right->Add(ip_input_bot_panel, 0, wxRIGHT|wxEXPAND, FromDIP(18)); m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(4)); - m_sizer_main_right->Add(m_input_area, 0, wxRIGHT|wxEXPAND, FromDIP(18)); - m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(4)); - m_sizer_main_right->Add(m_button_ok, 0, wxRIGHT, FromDIP(18)); + //m_sizer_main_right->Add(m_button_ok, 0, wxRIGHT, FromDIP(18)); m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(4)); m_sizer_main_right->Add(m_test_right_msg, 0, wxRIGHT|wxEXPAND, FromDIP(18)); m_sizer_main_right->Add(m_test_wrong_msg, 0, wxRIGHT|wxEXPAND, FromDIP(18)); - m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(16)); + m_sizer_main_right->Add(0, 0, 0, wxTOP, FromDIP(4)); m_sizer_main_right->Add(m_status_bar->get_panel(), 0,wxRIGHT|wxEXPAND, FromDIP(18)); m_sizer_main_right->Layout(); @@ -1630,17 +1714,19 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) m_sizer_main->Layout(); m_sizer_body->Add(m_line_top, 0, wxEXPAND, 0); - m_sizer_body->Add(0, 0, 0, wxTOP, FromDIP(20)); - m_sizer_body->Add(m_sizer_main, 0, wxEXPAND, 0); + m_sizer_body->Add(0, 0, 0, wxTOP, FromDIP(10)); + m_sizer_body->Add(m_sizer_main, 0, wxRIGHT, FromDIP(10)); m_sizer_body->Add(0, 0, 0, wxTOP, FromDIP(4)); m_sizer_body->Add(m_sizer_msg, 0, wxLEFT|wxEXPAND, FromDIP(18)); m_sizer_body->Add(0, 0, 0, wxTOP, FromDIP(4)); m_sizer_body->Add(m_trouble_shoot, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(40)); - m_sizer_body->Add(0, 0, 0, wxTOP, FromDIP(8)); m_sizer_body->Add(m_sizer_button, 0, wxRIGHT | wxEXPAND, FromDIP(25)); + m_sizer_body->Add(0, 0, 0, wxTOP, FromDIP(10)); m_sizer_body->Layout(); + switch_input_panel(0); + SetSizer(m_sizer_body); Layout(); Fit(); @@ -1649,29 +1735,60 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow* parent) Move(wxPoint(GetScreenPosition().x, GetScreenPosition().y - FromDIP(50))); wxGetApp().UpdateDlgDarkUI(this); - Bind(EVT_CHECK_IP_ADDRESS_FAILED, &InputIpAddressDialog::on_check_ip_address_failed, this); + closeTimer = new wxTimer(); + closeTimer->SetOwner(this); + Bind(wxEVT_TIMER, &InputIpAddressDialog::OnTimer, this); + + //Bind(EVT_CHECK_IP_ADDRESS_FAILED, &InputIpAddressDialog::on_check_ip_address_failed, this); Bind(EVT_CLOSE_IPADDRESS_DLG, [this](auto& e) { m_status_bar->reset(); EndModal(wxID_YES); }); - Bind(wxEVT_CLOSE_WINDOW, [this](auto& e) {on_cancel();}); + Bind(wxEVT_CLOSE_WINDOW, [this](auto& e) { + on_cancel(); + closeTimer->Stop(); + }); + Bind(EVT_UPDATE_TEXT_MSG, &InputIpAddressDialog::update_test_msg_event, this); + Bind(EVT_CHECK_IP_ADDRESS_LAYOUT, [this](auto& e) { + int mode = e.GetInt(); + switch_input_panel(mode); + Layout(); + Fit(); + }); +} + +void InputIpAddressDialog::switch_input_panel(int index) +{ + if (index == 0) { + ip_input_top_panel->Show(); + ip_input_bot_panel->Hide(); + m_step_icon_panel3->Hide(); + m_tip3->Hide(); + } else { + ip_input_top_panel->Hide(); + ip_input_bot_panel->Show(); + m_step_icon_panel3->Show(); + m_tip3->Show(); + + m_button_ok->Enable(false); + m_button_ok->SetBackgroundColor(wxColour(0x90, 0x90, 0x90)); + m_button_ok->SetBorderColor(wxColour(0x90, 0x90, 0x90)); + } + current_input_index = index; } void InputIpAddressDialog::on_cancel() { - if (m_send_job) { - if (m_send_job->is_running()) { - m_send_job->cancel(); - m_send_job->join(); - } - } - if (m_result == 0){ - this->EndModal(wxID_YES); - }else { - this->EndModal(wxID_CANCEL); + if (m_thread) { + m_thread->interrupt(); + m_thread->detach(); + delete m_thread; + m_thread = nullptr; } + + EndModal(wxID_CANCEL); } @@ -1757,87 +1874,126 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt) { m_test_right_msg->Hide(); m_test_wrong_msg->Hide(); - m_img_step3->Hide(); - m_tip4->Hide(); m_trouble_shoot->Hide(); - Layout(); - Fit(); - wxString ip = m_input_ip->GetTextCtrl()->GetValue(); - wxString str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); + std::string str_ip = m_input_ip->GetTextCtrl()->GetValue().ToStdString(); + std::string str_access_code = m_input_access_code->GetTextCtrl()->GetValue().ToStdString(); + std::string str_sn = m_input_sn->GetTextCtrl()->GetValue().ToStdString(); + std::string str_model_id = ""; - //check support function - if (!m_obj) return; - if (!m_obj->is_support_send_to_sdcard) { - wxString input_str = wxString::Format("%s|%s", ip, str_access_code); - auto event = wxCommandEvent(EVT_ENTER_IP_ADDRESS); - event.SetString(input_str); - event.SetEventObject(this); - wxPostEvent(this, event); - - auto event_close = wxCommandEvent(EVT_CLOSE_IPADDRESS_DLG); - event_close.SetEventObject(this); - wxPostEvent(this, event_close); - return; + auto it = m_models_map.right.find(m_input_modelID->GetStringSelection().ToStdString()); + if (it != m_models_map.right.end()) { + str_model_id = it->get_left(); } m_button_ok->Enable(false); m_button_ok->SetBackgroundColor(wxColour(0x90, 0x90, 0x90)); m_button_ok->SetBorderColor(wxColour(0x90, 0x90, 0x90)); - if (m_send_job) { - m_send_job->join(); - } + Refresh(); + Layout(); + Fit(); + m_thread = new boost::thread(boost::bind(&InputIpAddressDialog::workerThreadFunc, this, str_ip, str_access_code, str_sn, str_model_id)); +} - m_status_bar->reset(); - m_status_bar->set_prog_block(); - m_status_bar->set_cancel_callback_fina([this]() { - BOOST_LOG_TRIVIAL(info) << "print_job: enter canceled"; - if (m_send_job) { - if (m_send_job->is_running()) { - BOOST_LOG_TRIVIAL(info) << "send_job: canceled"; - m_send_job->cancel(); - } - m_send_job->join(); - } - }); +void InputIpAddressDialog::update_test_msg_event(wxCommandEvent& evt) +{ + wxString text = evt.GetString(); + bool beconnect = evt.GetInt(); + update_test_msg(text, beconnect); + Layout(); + Fit(); +} +void InputIpAddressDialog::post_update_test_msg(wxString text, bool beconnect) +{ + wxCommandEvent event(EVT_UPDATE_TEXT_MSG); + event.SetEventObject(this); + event.SetString(text); + event.SetInt(beconnect); + wxPostEvent(this, event); +} - m_send_job = std::make_shared(m_status_bar, wxGetApp().plater(), m_obj->dev_id); - m_send_job->m_dev_ip = ip.ToStdString(); - m_send_job->m_access_code = str_access_code.ToStdString(); +void InputIpAddressDialog::workerThreadFunc(std::string str_ip, std::string str_access_code, std::string sn, std::string model_id) +{ + post_update_test_msg(_L("connecting..."), true); + detectResult detectData; + auto result = -1; + if (current_input_index == 0) { -#if !QDT_RELEASE_TO_PUBLIC - m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; - m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; +#ifdef __APPLE__ + result = -3; #else - m_send_job->m_local_use_ssl_for_mqtt = m_obj->local_use_ssl_for_mqtt; - m_send_job->m_local_use_ssl_for_ftp = m_obj->local_use_ssl_for_ftp; + result = wxGetApp().getAgent()->bind_detect(str_ip, "secure", detectData); #endif - m_send_job->connection_type = m_obj->connection_type(); - m_send_job->cloud_print_only = true; - m_send_job->has_sdcard = m_obj->has_sdcard(); - m_send_job->set_check_mode(); - m_send_job->set_project_name("verify_job"); + } else { + result = 0; + detectData.dev_name = sn; + detectData.dev_id = sn; + detectData.connect_type = "lan"; + detectData.connect_type = "free"; + } - m_send_job->on_check_ip_address_fail([this](int result) { - this->check_ip_address_failed(result); - }); + if (result < 0) { + post_update_test_msg(wxEmptyString, true); + if (result == -1) { + post_update_test_msg(_L("Failed to connect to printer."), false); + } + else if (result == -2) { + post_update_test_msg(_L("Failed to publish login request."), false); + } + else if (result == -3) { + wxCommandEvent event(EVT_CHECK_IP_ADDRESS_LAYOUT); + event.SetEventObject(this); + event.SetInt(1); + wxPostEvent(this, event); + } + return; + } - m_send_job->on_check_ip_address_success([this, ip, str_access_code]() { - wxString input_str = wxString::Format("%s|%s", ip, str_access_code); - auto event = wxCommandEvent(EVT_ENTER_IP_ADDRESS); - event.SetString(input_str); - event.SetEventObject(this); - wxPostEvent(this, event); - m_result = 0; - - update_test_msg(_L("IP and Access Code Verified! You may close the window"), true); - - }); + if (detectData.bind_state == "occupied") { + post_update_test_msg(wxEmptyString, true); + post_update_test_msg(_L("The printer has already been bound."), false); + return; + } - m_send_job->start(); + if (detectData.connect_type == "cloud") { + post_update_test_msg(wxEmptyString, true); + post_update_test_msg(_L("The printer mode is incorrect, please switch to LAN Only."), false); + return; + } + + DeviceManager* dev = wxGetApp().getDeviceManager(); + m_obj = dev->insert_local_device(detectData.dev_name, detectData.dev_id, str_ip, detectData.connect_type, detectData.bind_state, detectData.version, str_access_code); + + + if (m_obj) { + m_obj->set_user_access_code(str_access_code); + wxGetApp().getDeviceManager()->set_selected_machine(m_obj->dev_id); + } + + closeCount = 1; + + post_update_test_msg(wxEmptyString, true); + post_update_test_msg(wxString::Format(_L("Connecting to printer... The dialog will close later"), closeCount), true); + +#ifdef __APPLE__ + wxCommandEvent event(EVT_CLOSE_IPADDRESS_DLG); + wxPostEvent(this, event); +#else + closeTimer->Start(1000); +#endif +} + +void InputIpAddressDialog::OnTimer(wxTimerEvent& event) { + if (closeCount > 0) { + closeCount--; + } + else { + closeTimer->Stop(); + EndModal(wxID_CLOSE); + } } void InputIpAddressDialog::check_ip_address_failed(int result) @@ -1855,9 +2011,6 @@ void InputIpAddressDialog::on_check_ip_address_failed(wxCommandEvent& evt) } else { update_test_msg(_L("Connection failed! If your IP and Access Code is correct, \nplease move to step 3 for troubleshooting network issues"), false); - m_img_step3->Show(); - m_tip4->Show(); - //m_trouble_shoot->Show(); Layout(); Fit(); } @@ -1869,12 +2022,21 @@ void InputIpAddressDialog::on_check_ip_address_failed(wxCommandEvent& evt) m_button_ok->SetBackgroundColor(btn_bg_green); } -void InputIpAddressDialog::on_text(wxCommandEvent& evt) +void InputIpAddressDialog::on_text(wxCommandEvent &evt) { - auto str_ip = m_input_ip->GetTextCtrl()->GetValue(); - auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); + auto str_ip = m_input_ip->GetTextCtrl()->GetValue(); + auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); + auto str_sn = m_input_sn->GetTextCtrl()->GetValue(); + bool invalid_access_code = true; - if (isIp(str_ip.ToStdString()) && str_access_code.Length() == 8) { + for (char c : str_access_code) { + if (!('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z')) { + invalid_access_code = false; + return; + } + } + + if (isIp(str_ip.ToStdString()) && str_access_code.Length() == 8 && invalid_access_code) { m_button_ok->Enable(true); StateColor btn_bg_green(std::pair(wxColour(129, 150, 255), StateColor::Pressed), std::pair(wxColour(129, 150, 255), StateColor::Hovered), std::pair(AMS_CONTROL_BRAND_COLOUR, StateColor::Normal)); @@ -1886,6 +2048,20 @@ void InputIpAddressDialog::on_text(wxCommandEvent& evt) m_button_ok->SetBackgroundColor(wxColour(0x90, 0x90, 0x90)); m_button_ok->SetBorderColor(wxColour(0x90, 0x90, 0x90)); } + + if (current_input_index == 1){ + if (str_sn.length() == 15) { + m_button_ok->Enable(true); + StateColor btn_bg_green(std::pair(wxColour(61, 203, 115), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(AMS_CONTROL_BRAND_COLOUR, StateColor::Normal)); + m_button_ok->SetTextColor(StateColor::darkModeColorFor("#FFFFFE")); + m_button_ok->SetBackgroundColor(btn_bg_green); + } else { + m_button_ok->Enable(false); + m_button_ok->SetBackgroundColor(wxColour(0x90, 0x90, 0x90)); + m_button_ok->SetBorderColor(wxColour(0x90, 0x90, 0x90)); + } + } } InputIpAddressDialog::~InputIpAddressDialog() diff --git a/src/slic3r/GUI/ReleaseNote.hpp b/src/slic3r/GUI/ReleaseNote.hpp index 458c8bf..879c562 100644 --- a/src/slic3r/GUI/ReleaseNote.hpp +++ b/src/slic3r/GUI/ReleaseNote.hpp @@ -50,6 +50,7 @@ wxDECLARE_EVENT(EVT_UPDATE_NOZZLE, wxCommandEvent); wxDECLARE_EVENT(EVT_LOAD_VAMS_TRAY, wxCommandEvent); wxDECLARE_EVENT(EVT_JUMP_TO_HMS, wxCommandEvent); wxDECLARE_EVENT(EVT_JUMP_TO_LIVEVIEW, wxCommandEvent); +wxDECLARE_EVENT(EVT_UPDATE_TEXT_MSG, wxCommandEvent); class ReleaseNoteDialog : public DPIDialog { @@ -189,7 +190,7 @@ public: const wxSize& size = wxDefaultSize, long style = wxCLOSE_BOX | wxCAPTION ); - void update_text_image(wxString text, wxString image_url); + void update_text_image(const wxString& text, const wxString& error_code,const wxString& image_url); void on_show(); void on_hide(); void update_title_style(wxString title, std::vector style, wxWindow* parent = nullptr); @@ -206,6 +207,7 @@ public: wxWebRequest web_request; wxStaticBitmap* m_error_prompt_pic_static; Label* m_staticText_release_note{ nullptr }; + Label* m_staticText_error_code{ nullptr }; wxBoxSizer* m_sizer_main; wxBoxSizer* m_sizer_button; wxScrolledWindow* m_vebview_release_note{ nullptr }; @@ -274,8 +276,12 @@ class InputIpAddressDialog : public DPIDialog public: wxString comfirm_before_enter_text; wxString comfirm_after_enter_text; + wxString comfirm_last_enter_text; + + boost::thread* m_thread{nullptr}; std::string m_ip; + wxWindow* m_step_icon_panel3{ nullptr }; Label* m_tip1{ nullptr }; Label* m_tip2{ nullptr }; Label* m_tip3{ nullptr }; @@ -284,23 +290,34 @@ public: ~InputIpAddressDialog(); MachineObject* m_obj{nullptr}; + wxPanel * ip_input_top_panel{ nullptr }; + wxPanel * ip_input_bot_panel{ nullptr }; Button* m_button_ok{ nullptr }; Label* m_tips_ip{ nullptr }; Label* m_tips_access_code{ nullptr }; + Label* m_tips_sn{nullptr}; + Label* m_tips_modelID{nullptr}; Label* m_test_right_msg{ nullptr }; Label* m_test_wrong_msg{ nullptr }; TextInput* m_input_ip{ nullptr }; TextInput* m_input_access_code{ nullptr }; + TextInput* m_input_sn{ nullptr }; + ComboBox* m_input_modelID{ nullptr }; wxStaticBitmap* m_img_help{ nullptr }; wxStaticBitmap* m_img_step1{ nullptr }; wxStaticBitmap* m_img_step2{ nullptr }; wxStaticBitmap* m_img_step3{ nullptr }; wxHyperlinkCtrl* m_trouble_shoot{ nullptr }; + wxTimer* closeTimer{ nullptr }; + int closeCount{3}; bool m_show_access_code{ false }; int m_result; + int current_input_index {0}; std::shared_ptr m_send_job{nullptr}; std::shared_ptr m_status_bar; + boost::bimaps::bimap m_models_map; + void switch_input_panel(int index); void on_cancel(); void update_title(wxString title); void set_machine_obj(MachineObject* obj); @@ -309,6 +326,10 @@ public: void check_ip_address_failed(int result); void on_check_ip_address_failed(wxCommandEvent& evt); void on_ok(wxMouseEvent& evt); + void update_test_msg_event(wxCommandEvent &evt); + void post_update_test_msg(wxString text, bool beconnect); + void workerThreadFunc(std::string str_ip, std::string str_access_code, std::string sn, std::string model_id); + void OnTimer(wxTimerEvent& event); void on_text(wxCommandEvent& evt); void on_dpi_changed(const wxRect& suggested_rect) override; }; @@ -318,6 +339,7 @@ wxDECLARE_EVENT(EVT_CLOSE_IPADDRESS_DLG, wxCommandEvent); wxDECLARE_EVENT(EVT_CHECKBOX_CHANGE, wxCommandEvent); wxDECLARE_EVENT(EVT_ENTER_IP_ADDRESS, wxCommandEvent); wxDECLARE_EVENT(EVT_CHECK_IP_ADDRESS_FAILED, wxCommandEvent); +wxDECLARE_EVENT(EVT_CHECK_IP_ADDRESS_LAYOUT, wxCommandEvent); }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index 504220c..1a5c02b 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -396,11 +396,13 @@ SelectMachinePopup::SelectMachinePopup(wxWindow *parent) m_sizer_other_devices = new wxBoxSizer(wxVERTICAL); - m_panel_ping_code = new PinCodePanel(m_scrolledWindow, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_ITEM_SIZE); + m_panel_ping_code = new PinCodePanel(m_scrolledWindow, 0, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_ITEM_SIZE); + m_panel_direct_connection = new PinCodePanel(m_scrolledWindow, 1, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_ITEM_SIZE); m_sizxer_scrolledWindow->Add(own_title, 0, wxEXPAND | wxLEFT, FromDIP(15)); m_sizxer_scrolledWindow->Add(m_sizer_my_devices, 0, wxEXPAND, 0); m_sizxer_scrolledWindow->Add(m_panel_ping_code, 0, wxEXPAND, 0); + m_sizxer_scrolledWindow->Add(m_panel_direct_connection, 0, wxEXPAND, 0); m_sizxer_scrolledWindow->Add(other_title, 0, wxEXPAND | wxLEFT, FromDIP(15)); m_sizxer_scrolledWindow->Add(m_sizer_other_devices, 0, wxEXPAND, 0); @@ -877,14 +879,16 @@ void SelectMachinePopup::OnLeftUp(wxMouseEvent &event) //pin code auto pc_rect = m_panel_ping_code->ClientToScreen(wxPoint(0, 0)); if (mouse_pos.x > pc_rect.x && mouse_pos.y > pc_rect.y && mouse_pos.x < (pc_rect.x + m_panel_ping_code->GetSize().x) && mouse_pos.y < (pc_rect.y + m_panel_ping_code->GetSize().y)) { - /*wxMouseEvent event(wxEVT_LEFT_UP); - auto tag_pos = m_panel_ping_code->ScreenToClient(mouse_pos); - event.SetPosition(tag_pos); - event.SetEventObject(m_panel_ping_code); - wxPostEvent(m_panel_ping_code, event);*/ wxGetApp().popup_ping_bind_dialog(); } + //bind with access code + auto dc_rect = m_panel_direct_connection->ClientToScreen(wxPoint(0, 0)); + if (mouse_pos.x > dc_rect.x && mouse_pos.y > dc_rect.y && mouse_pos.x < (dc_rect.x + m_panel_direct_connection->GetSize().x) && mouse_pos.y < (dc_rect.y + m_panel_direct_connection->GetSize().y)) { + InputIpAddressDialog dlgo; + dlgo.ShowModal(); + } + //hyper link auto h_rect = m_hyperlink->ClientToScreen(wxPoint(0, 0)); if (mouse_pos.x > h_rect.x && mouse_pos.y > h_rect.y && mouse_pos.x < (h_rect.x + m_hyperlink->GetSize().x) && mouse_pos.y < (h_rect.y + m_hyperlink->GetSize().y)) { @@ -897,10 +901,12 @@ void SelectMachinePopup::OnLeftUp(wxMouseEvent &event) static wxString MACHINE_BED_TYPE_STRING[BED_TYPE_COUNT] = { //_L("Auto"), // y7 - _L("QIDI Cool Plate") + " / " + _L("PLA Plate"), + _L("QIDI Cool Plate"), _L("QIDI Engineering Plate"), _L("QIDI Smooth PEI Plate") + "/" + _L("High temperature Plate"), - _L("QIDI Textured PEI Plate")}; + _L("Bamabu Textured PEI Plate"), + _L("QIDI Cool Plate SuperTack") +}; static std::string MachineBedTypeString[BED_TYPE_COUNT] = { //"auto", @@ -908,6 +914,7 @@ static std::string MachineBedTypeString[BED_TYPE_COUNT] = { "pe", "pei", "pte", + "suprtack" }; void SelectMachineDialog::stripWhiteSpace(std::string& str) @@ -1542,30 +1549,6 @@ void SelectMachineDialog::init_bind() m_comboBox_printer->SetValue(obj->dev_name + "(LAN)"); } } - /*else if (e.GetInt() == 1 && (m_print_type == PrintFromType::FROM_SDCARD_VIEW)) { - on_send_print(); - } - else if (e.GetInt() == -2 && (m_print_type == PrintFromType::FROM_SDCARD_VIEW)) { - show_status(PrintDialogStatus::PrintStatusInit); - prepare_mode(); - MessageDialog msg_wingow(nullptr, _L("Printer local connection failed, please try again."), "", wxAPPLY | wxOK); - msg_wingow.ShowModal(); - } - else if (e.GetInt() == 5 && (m_print_type == PrintFromType::FROM_SDCARD_VIEW)) { - show_status(PrintDialogStatus::PrintStatusInit); - prepare_mode(); - - DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); - if (!dev) return; - ConnectPrinterDialog dlg(wxGetApp().mainframe, wxID_ANY, _L("Input access code")); - dlg.go_connect_printer(false); - if (dev->get_selected_machine()) { - dlg.set_machine_object(dev->get_selected_machine()); - if (dlg.ShowModal() == wxID_OK) { - this->connect_printer_mqtt(); - } - } - }*/ }); m_bitmap_last_plate->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { @@ -1994,14 +1977,7 @@ bool SelectMachineDialog::get_ams_mapping_result(std::string &mapping_array_str, } else { json j = json::array(); json mapping_info_json = json::array(); - //1.9.7.52 - BOOST_LOG_TRIVIAL(info) << "filaments size = " << m_filaments.size(); - int mapping_size = wxGetApp().preset_bundle->filament_presets.size(); - for (size_t i = 0; i < m_ams_mapping_result.size(); i++) { - mapping_size = std::max(mapping_size, m_ams_mapping_result[i].id); - } - mapping_size = std::min(mapping_size, 16); - for (int i = 0; i <= mapping_size; i++) { + for (int i = 0; i < wxGetApp().preset_bundle->filament_presets.size(); i++) { int tray_id = -1; json mapping_item; mapping_item["ams"] = tray_id; @@ -2014,12 +1990,9 @@ bool SelectMachineDialog::get_ams_mapping_result(std::string &mapping_array_str, tray_id = m_ams_mapping_result[k].tray_id; mapping_item["ams"] = tray_id; mapping_item["filamentType"] = m_filaments[k].type; - //1.9.7.52 - if (i >= 0 && i < wxGetApp().preset_bundle->filament_presets.size()) { - auto it = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]); - if (it != nullptr) { - mapping_item["filamentId"] = it->filament_id; - } + auto it = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]); + if (it != nullptr) { + mapping_item["filamentId"] = it->filament_id; } //convert #RRGGBB to RRGGBBAA mapping_item["sourceColor"] = m_filaments[k].color; @@ -2278,7 +2251,7 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vectorget_selected_machine(); if (obj_ == nullptr) return; auto sourcet_print_name = obj_->get_printer_type_display_str(); - sourcet_print_name.Replace(wxT("QIDI Lab "), wxEmptyString); + sourcet_print_name.Replace(wxT("QIDI Tech "), wxEmptyString); //target print std::string target_model_id; @@ -2293,7 +2266,7 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vectorget_preset_printer_model_name(target_model_id)); - target_print_name.Replace(wxT("QIDI Lab "), wxEmptyString); + target_print_name.Replace(wxT("QIDI Tech "), wxEmptyString); msg_text = wxString::Format(_L("The selected printer (%s) is incompatible with the chosen printer profile in the slicer (%s)."), sourcet_print_name, target_print_name); update_print_status_msg(msg_text, true, true); @@ -2745,7 +2718,7 @@ void SelectMachineDialog::on_ok_btn(wxCommandEvent &event) // is_printing_block = true; // nozzle_diameter = wxString::Format("%.1f", obj_->nozzle_diameter).ToStdString(); - // wxString nozzle_in_preset = wxString::Format(_L("Printing high temperature material(%s material) with %s may cause nozzle damage"), filament_type, format_steel_name(obj_->nozzle_type)); + // wxString nozzle_in_preset = wxString::Format(_L("Printing %1s material with %2s nozzle may cause nozzle damage."), filament_type,format_steel_name(obj_->nozzle_type)); // confirm_text.push_back(ConfirmBeforeSendInfo(nozzle_in_preset, ConfirmBeforeSendInfo::InfoLevel::Warning)); // } //} @@ -4231,8 +4204,6 @@ void SelectMachineDialog::reset_and_sync_ams_list() m_materialList.clear(); m_filaments.clear(); - BOOST_LOG_TRIVIAL(info) << "extruders = " << extruders.size(); - for (auto i = 0; i < extruders.size(); i++) { auto extruder = extruders[i] - 1; auto colour = wxGetApp().preset_bundle->project_config.opt_string("filament_colour", (unsigned int) extruder); @@ -4813,16 +4784,21 @@ void SelectMachineDialog::sys_color_changed() bool SelectMachineDialog::Show(bool show) { - show_status(PrintDialogStatus::PrintStatusInit); - // set default value when show this dialog if (show) { + m_refresh_timer->Start(LIST_REFRESH_INTERVAL); + show_status(PrintDialogStatus::PrintStatusInit); wxGetApp().UpdateDlgDarkUI(this); wxGetApp().reset_to_active(); set_default(); update_user_machine_list(); + + Layout(); + Fit(); + CenterOnParent(); } else { + m_refresh_timer->Stop(); DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (dev) { MachineObject* obj_ = dev->get_selected_machine(); @@ -4834,15 +4810,6 @@ bool SelectMachineDialog::Show(bool show) } } } - - if (show) { - m_refresh_timer->Start(LIST_REFRESH_INTERVAL); - } else { - m_refresh_timer->Stop(); - } - Layout(); - Fit(); - if (show) { CenterOnParent(); } return DPIDialog::Show(show); } @@ -5100,7 +5067,7 @@ void EditDevNameDialog::on_edit_name(wxCommandEvent &e) ThumbnailPanel::~ThumbnailPanel() {} - PinCodePanel::PinCodePanel(wxWindow* parent, wxWindowID winid /*= wxID_ANY*/, const wxPoint& pos /*= wxDefaultPosition*/, const wxSize& size /*= wxDefaultSize*/) + PinCodePanel::PinCodePanel(wxWindow* parent, int type, wxWindowID winid /*= wxID_ANY*/, const wxPoint& pos /*= wxDefaultPosition*/, const wxSize& size /*= wxDefaultSize*/) { wxPanel::Create(parent, winid, pos, SELECT_MACHINE_ITEM_SIZE); Bind(wxEVT_PAINT, &PinCodePanel::OnPaint, this); @@ -5108,8 +5075,7 @@ void EditDevNameDialog::on_edit_name(wxCommandEvent &e) SetMaxSize(SELECT_MACHINE_ITEM_SIZE); SetMinSize(SELECT_MACHINE_ITEM_SIZE); - m_bitmap = ScalableBitmap(this, "bind_device_ping_code",10); - + m_type = type; this->Bind(wxEVT_ENTER_WINDOW, &PinCodePanel::on_mouse_enter, this); this->Bind(wxEVT_LEAVE_WINDOW, &PinCodePanel::on_mouse_leave, this); this->Bind(wxEVT_LEFT_UP, &PinCodePanel::on_mouse_left_up, this); @@ -5145,12 +5111,20 @@ void EditDevNameDialog::on_edit_name(wxCommandEvent &e) void PinCodePanel::doRender(wxDC& dc) { auto size = GetSize(); - dc.DrawBitmap(m_bitmap.bmp(), wxPoint(FromDIP(20), (size.y - m_bitmap.GetBmpSize().y) / 2)); + + //m_bitmap = ScalableBitmap(this, "bind_device_ping_code",10); + + m_bitmap = ScalableBitmap(this, wxGetApp().dark_mode() ? "bind_device_ping_code_dark" : "bind_device_ping_code_light", 10); + + dc.DrawBitmap(m_bitmap.bmp(), wxPoint(FromDIP(12), (size.y - m_bitmap.GetBmpSize().y) / 2)); dc.SetFont(::Label::Head_13); - dc.SetTextForeground(wxColour(38, 46, 48)); - wxString txt = _L("Bind with Pin Code"); + dc.SetTextForeground(StateColor::darkModeColorFor(SELECT_MACHINE_GREY900)); + wxString txt; + if (m_type == 0) { txt = _L("Bind with Pin Code"); } + else if (m_type == 1) { txt = _L("Bind with Access Code"); } + auto txt_size = dc.GetTextExtent(txt); - dc.DrawText(txt, wxPoint(FromDIP(40), (size.y - txt_size.y) / 2)); + dc.DrawText(txt, wxPoint(FromDIP(28), (size.y - txt_size.y) / 2)); if (m_hover) { dc.SetPen(SELECT_MACHINE_BRAND); @@ -5173,7 +5147,27 @@ void EditDevNameDialog::on_edit_name(wxCommandEvent &e) void PinCodePanel::on_mouse_left_up(wxMouseEvent& evt) { - wxGetApp().popup_ping_bind_dialog(); + if (m_type == 0) { + if (wxGetApp().getAgent() && wxGetApp().getAgent()->is_user_login()){ + wxGetApp().popup_ping_bind_dialog(); + } + else{ + auto m_confirm_login_dlg = new SecondaryCheckDialog(nullptr, wxID_ANY, _L("Bind with Pin Code"), SecondaryCheckDialog::ButtonStyle::ONLY_CONFIRM, wxDefaultPosition); + m_confirm_login_dlg->SetSize(wxSize(FromDIP(270), FromDIP(158))); + m_confirm_login_dlg->update_text(_L("Please log in before binding your device with a PIN code.\nAlternatively, you can use LAN mode to bind your device. Learn about LAN mode.")); + m_confirm_login_dlg->update_btn_label(_L("Go to Login"), _L("")); + m_confirm_login_dlg->Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) { + //m_confirm_login_dlg->on_hide(); + wxGetApp().request_login(); + return; + }); + m_confirm_login_dlg->on_show(); + } + } + else if (m_type == 1) { + InputIpAddressDialog dlgo; + dlgo.ShowModal(); + } } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SelectMachine.hpp b/src/slic3r/GUI/SelectMachine.hpp index bd274b1..15f5dca 100644 --- a/src/slic3r/GUI/SelectMachine.hpp +++ b/src/slic3r/GUI/SelectMachine.hpp @@ -205,7 +205,7 @@ protected: #define SELECT_MACHINE_POPUP_SIZE wxSize(FromDIP(216), FromDIP(364)) #define SELECT_MACHINE_LIST_SIZE wxSize(FromDIP(212), FromDIP(360)) -#define SELECT_MACHINE_ITEM_SIZE wxSize(FromDIP(182), FromDIP(35)) +#define SELECT_MACHINE_ITEM_SIZE wxSize(FromDIP(190), FromDIP(35)) #define SELECT_MACHINE_GREY900 wxColour(38, 46, 48) #define SELECT_MACHINE_GREY600 wxColour(144,144,144) #define SELECT_MACHINE_GREY400 wxColour(206, 206, 206) @@ -224,6 +224,7 @@ class PinCodePanel : public wxPanel { public: PinCodePanel(wxWindow* parent, + int type, wxWindowID winid = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); @@ -231,6 +232,7 @@ public: ScalableBitmap m_bitmap; bool m_hover{false}; + int m_type{0}; void OnPaint(wxPaintEvent& event); void render(wxDC& dc); @@ -264,6 +266,7 @@ private: int m_my_devices_count{0}; int m_other_devices_count{0}; PinCodePanel* m_panel_ping_code{nullptr}; + PinCodePanel* m_panel_direct_connection{nullptr}; wxWindow* m_placeholder_panel{nullptr}; wxHyperlinkCtrl* m_hyperlink{nullptr}; Label* m_ping_code_text{nullptr}; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index fba2d64..3f72d96 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -149,6 +149,10 @@ void Selection::set_model(Model* model) update_valid(); } +void Selection::set_mode(EMode mode) { + m_mode = mode; +} + int Selection::query_real_volume_idx_from_other_view(unsigned int object_idx, unsigned int instance_idx, unsigned int model_volume_idx) { for (int i = 0; i < m_volumes->size(); i++) { @@ -407,6 +411,55 @@ void Selection::remove_volumes(EMode mode, const std::vector& volu this->set_bounding_boxes_dirty(); } +ModelVolume *Selection::get_selected_single_volume(int &out_object_idx, int &out_volume_idx) +{ + if (is_single_volume() || is_single_modifier()) { + const GLVolume *gl_volume = get_volume(*get_volume_idxs().begin()); + out_object_idx = gl_volume->object_idx(); + ModelObject *model_object = get_model()->objects[out_object_idx]; + out_volume_idx = gl_volume->volume_idx(); + if (out_volume_idx < model_object->volumes.size()) + return model_object->volumes[out_volume_idx]; + } + return nullptr; +} + +ModelObject *Selection::get_selected_single_object(int &out_object_idx) +{ + if (is_single_volume() || is_single_modifier() || is_single_full_object()) { + const GLVolume *gl_volume = get_volume(*get_volume_idxs().begin()); + out_object_idx = gl_volume->object_idx(); + return get_model()->objects[out_object_idx]; + } + return nullptr; +} + +const std::vector &Selection::get_all_tran_of_selected_volumes() +{ + m_trafo_matrices.clear(); + int object_idx; + auto mo = get_selected_single_object(object_idx); + if (mo) { + const ModelInstance *mi = mo->instances[get_instance_idx()]; + for (const ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { + m_trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + } + } + } + return m_trafo_matrices; +} + +const ModelInstance *Selection::get_selected_single_intance() +{ + int object_idx; + auto mo = get_selected_single_object(object_idx); + if (mo) { + return mo->instances[get_instance_idx()]; + } + return nullptr; +} + void Selection::add_curr_plate() { if (!m_valid) @@ -827,6 +880,10 @@ const GLVolume* Selection::get_volume(unsigned int volume_idx) const return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; } +GLVolume *Selection::get_volume(unsigned int volume_idx) { + return (m_valid && (volume_idx < (unsigned int) m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; +} + const BoundingBoxf3& Selection::get_bounding_box() const { if (!m_bounding_box.has_value()) { @@ -879,6 +936,63 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } +const BoundingBoxf3 &Selection::get_full_unscaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_bounding_box.has_value()) { + std::optional *bbox = const_cast *>(&m_full_unscaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume &volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_bounding_box; +} + +const BoundingBoxf3 &Selection::get_full_scaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_scaled_instance_bounding_box.has_value()) { + std::optional *bbox = const_cast *>(&m_full_scaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume &volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_scaled_instance_bounding_box; +} + +const BoundingBoxf3 &Selection::get_full_unscaled_instance_local_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_local_bounding_box.has_value()) { + std::optional *bbox = const_cast *>(&m_full_unscaled_instance_local_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume &volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_local_bounding_box; +} + const std::pair &Selection::get_bounding_box_in_current_reference_system() const { static int last_coordinates_type = -1; @@ -937,7 +1051,8 @@ std::pair Selection::get_bounding_box_in_reference_s const GLVolume & vol = *get_volume(id); const Transform3d vol_world_rafo = vol.world_matrix(); const TriangleMesh *mesh = vol.convex_hull(); - if (mesh == nullptr) mesh = &m_model->objects[vol.object_idx()]->volumes[vol.volume_idx()]->mesh(); + if (mesh == nullptr) + mesh = &m_model->objects[vol.object_idx()]->volumes[vol.volume_idx()]->mesh(); assert(mesh != nullptr); for (const stl_vertex &v : mesh->its.vertices) { const Vec3d world_v = vol_world_rafo * v.cast(); @@ -1057,66 +1172,65 @@ void Selection::setup_cache() set_caches(); } -void Selection::translate(const Vec3d &displacement, bool local) +void Selection::translate(const Vec3d &displacement, TransformationType transformation_type) { - if (!m_valid) - return; - - EMode translation_type = m_mode; - //BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, displacement {%2%, %3%, %4%}") % __LINE__ % displacement(X) % displacement(Y) % displacement(Z); + if (!m_valid) return; for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (v.is_wipe_tower) { - int plate_idx = v.object_idx() - 1000; - BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_bounding_box(); - Vec3d tower_size = v.bounding_box().size(); - Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position(); - Vec3d actual_displacement = displacement; - const double margin = WIPE_TOWER_MARGIN; + GLVolume & v = *(*m_volumes)[i]; + const VolumeCache &volume_data = m_cache.volumes_data[i]; + if (m_mode == Instance && !is_wipe_tower()) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.instance()) { + const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform(); + v.set_instance_offset(inst_trafo.get_offset() + inst_trafo.get_rotation_matrix() * displacement); + } else + transform_instance_relative(v, volume_data, transformation_type, Geometry::translation_transform(displacement), m_cache.dragging_center); + } else { + if (v.is_wipe_tower) {//in world cs + int plate_idx = v.object_idx() - 1000; + BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_bounding_box(); + Vec3d tower_size = v.bounding_box().size(); + Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position(); + Vec3d actual_displacement = displacement; + const double margin = WIPE_TOWER_MARGIN; - if (!local) - actual_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + actual_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * + m_cache.volumes_data[i].get_instance_mirror_matrix()) + .inverse() * + displacement; + if (tower_origin(0) + actual_displacement(0) - margin < plate_bbox.min(0)) { + actual_displacement(0) = plate_bbox.min(0) - tower_origin(0) + margin; + } else if (tower_origin(0) + actual_displacement(0) + tower_size(0) + margin > plate_bbox.max(0)) { + actual_displacement(0) = plate_bbox.max(0) - tower_origin(0) - tower_size(0) - margin; + } - if (tower_origin(0) + actual_displacement(0) - margin < plate_bbox.min(0)) { - actual_displacement(0) = plate_bbox.min(0) - tower_origin(0) + margin; - } - else if (tower_origin(0) + actual_displacement(0) + tower_size(0) + margin > plate_bbox.max(0)) { - actual_displacement(0) = plate_bbox.max(0) - tower_origin(0) - tower_size(0) - margin; - } + if (tower_origin(1) + actual_displacement(1) - margin < plate_bbox.min(1)) { + actual_displacement(1) = plate_bbox.min(1) - tower_origin(1) + margin; + } else if (tower_origin(1) + actual_displacement(1) + tower_size(1) + margin > plate_bbox.max(1)) { + actual_displacement(1) = plate_bbox.max(1) - tower_origin(1) - tower_size(1) - margin; + } - if (tower_origin(1) + actual_displacement(1) - margin < plate_bbox.min(1)) { - actual_displacement(1) = plate_bbox.min(1) - tower_origin(1) + margin; - } - else if (tower_origin(1) + actual_displacement(1) + tower_size(1) + margin > plate_bbox.max(1)) { - actual_displacement(1) = plate_bbox.max(1) - tower_origin(1) - tower_size(1) - margin; + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + actual_displacement); } + else if (transformation_type.local() && transformation_type.absolute()) { + const Geometry::Transformation &vol_trafo = volume_data.get_volume_transform(); + const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform(); + v.set_volume_offset(vol_trafo.get_offset() + inst_trafo.get_scaling_factor_matrix().inverse() * vol_trafo.get_rotation_matrix() * displacement); + } else { + Vec3d relative_disp = displacement; + if (transformation_type.world() && transformation_type.instance()) + relative_disp = volume_data.get_instance_transform().get_scaling_factor_matrix().inverse() * relative_disp; - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + actual_displacement); - } - else if (m_mode == Volume || v.is_wipe_tower) { - if (local) - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); - else { - const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); - } - } - else if (m_mode == Instance) { - if (is_from_fully_selected_instance(i)) - v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - else { - const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); - translation_type = Volume; + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(relative_disp), m_cache.dragging_center); } } } #if !DISABLE_INSTANCES_SYNCH - if (translation_type == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (translation_type == Volume) + if (m_mode == Instance) + synchronize_unselected_instances(SyncRotationType::NONE); + else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { @@ -1127,7 +1241,6 @@ void Selection::translate(const Vec3d &displacement, bool local) wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } } - // Rotate an object around one of the axes. Only one rotation component is expected to be changing. void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) { @@ -1221,7 +1334,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ else { - if (transformation_type.instance()) {//in object Coordinate System + if (transformation_type.instance()) {//in object Coordinate System // ensure that the volume rotates as a rigid body const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix(); rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix; @@ -1249,7 +1362,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); + synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL); else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1307,7 +1420,7 @@ void Selection::flattening_rotate(const Vec3d& normal) // Apply the same transformation also to other instances, // but respect their possibly diffrent z-rotation. if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_GENERAL); + synchronize_unselected_instances(SyncRotationType::GENERAL); #endif // !DISABLE_INSTANCES_SYNCH this->set_bounding_boxes_dirty(); @@ -1315,77 +1428,7 @@ void Selection::flattening_rotate(const Vec3d& normal) void Selection::scale(const Vec3d& scale, TransformationType transformation_type) { - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume &v = *(*m_volumes)[i]; - if (is_single_full_instance()) { - if (transformation_type.relative()) { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - v.set_instance_scaling_factor(new_scale); - } - else { - if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { - // Non-uniform scaling. Transform the scaling factors into the local coordinate system. - // This is only possible, if the instance rotation is mulitples of ninety degrees. - assert(Geometry::is_rotation_ninety_degrees(v.get_instance_rotation())); - v.set_instance_scaling_factor((v.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); - } - else - v.set_instance_scaling_factor(scale); - } - - // update the instance assemble transform - ModelObject* object = m_model->objects[v.object_idx()]; - Geometry::Transformation assemble_transform = object->instances[v.instance_idx()]->get_assemble_transformation(); - assemble_transform.set_scaling_factor(v.get_instance_scaling_factor()); - object->instances[v.instance_idx()]->set_assemble_transformation(assemble_transform); - } - else if (is_single_volume() || is_single_modifier()) - v.set_volume_scaling_factor(scale); - else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - if (m_mode == Instance) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - v.set_instance_scaling_factor(new_scale); - } - else if (m_mode == Volume) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) { - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); - } - v.set_volume_scaling_factor(new_scale); - } - } - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - ensure_on_bed(); - set_bounding_boxes_dirty(); - if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); - } + scale_and_translate(scale, Vec3d::Zero(), transformation_type); } #if ENABLE_ENHANCED_PRINT_VOLUME_FIT @@ -1410,7 +1453,9 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume) // center selection on print bed start_dragging(); offset.z() = -get_bounding_box().min.z(); - translate(offset); + TransformationType trafo_type; + trafo_type.set_relative(); + translate(offset, trafo_type); wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot // QDS @@ -1523,27 +1568,93 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) } #endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT -void Selection::mirror(Axis axis) +void Selection::scale_and_translate(const Vec3d &scale, const Vec3d &world_translation, TransformationType transformation_type) { - if (!m_valid) - return; + if (!m_valid) return; + + Vec3d relative_scale = scale; + if (transformation_type.absolute()) { + // converts to relative scale + if (m_mode == Instance) { + if (is_single_full_instance()) { + BoundingBoxf3 current_box = get_bounding_box_in_current_reference_system().first; + BoundingBoxf3 original_box; + if (transformation_type.world()) + original_box = get_full_unscaled_instance_bounding_box(); + else + original_box = get_full_unscaled_instance_local_bounding_box(); + + relative_scale = original_box.size().cwiseProduct(scale).cwiseQuotient(current_box.size()); + } + } + transformation_type.set_relative(); + } for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (is_single_full_instance()) - v.set_instance_mirror(axis, -v.get_instance_mirror(axis)); - else if (m_mode == Volume) - v.set_volume_mirror(axis, -v.get_volume_mirror(axis)); + GLVolume & v = *(*m_volumes)[i]; + const VolumeCache & volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform(); + auto old_rotate = inst_trafo.get_rotation(); + if (m_mode == Instance) { + if (transformation_type.instance()) { + const Vec3d world_inst_pivot = m_cache.dragging_center - inst_trafo.get_offset(); + const Vec3d local_inst_pivot = inst_trafo.get_matrix_no_offset().inverse() * world_inst_pivot; + Matrix3d inst_rotation, inst_scale; + inst_trafo.get_matrix().computeRotationScaling(&inst_rotation, &inst_scale); + const Transform3d offset_trafo = Geometry::translation_transform(inst_trafo.get_offset() + world_translation); + const Transform3d scale_trafo = Transform3d(inst_scale) * Geometry::scale_transform(relative_scale); + v.set_instance_transformation(Geometry::translation_transform(world_inst_pivot) * offset_trafo * Transform3d(inst_rotation) * scale_trafo * + Geometry::translation_transform(-local_inst_pivot)); + } else + transform_instance_relative(v, volume_data, transformation_type, Geometry::translation_transform(world_translation) * Geometry::scale_transform(relative_scale), + m_cache.dragging_center); + // update the instance assemble transform + ModelObject * object = m_model->objects[v.object_idx()]; + Geometry::Transformation assemble_transform = object->instances[v.instance_idx()]->get_assemble_transformation(); + assemble_transform.set_scaling_factor(v.get_instance_scaling_factor()); + object->instances[v.instance_idx()]->set_assemble_transformation(assemble_transform); + } else { + if (!is_single_volume_or_modifier()) { + assert(transformation_type.world()); + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(world_translation) * Geometry::scale_transform(scale), + m_cache.dragging_center); + } else { + transformation_type.set_independent(); + Vec3d translation; + if (transformation_type.local()) { + translation = volume_data.get_volume_transform().get_matrix_no_offset().inverse() * inst_trafo.get_matrix_no_offset().inverse() * world_translation; + } + else if (transformation_type.instance()) + translation = inst_trafo.get_matrix_no_offset().inverse() * world_translation; + else + translation = world_translation; + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale), + m_cache.dragging_center); + } + } } #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); + // even if there is no rotation, we pass SyncRotationType::GENERAL to force + // synchronize_unselected_instances() to apply the scale to the other instances + synchronize_unselected_instances(SyncRotationType::GENERAL); else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH + ensure_on_bed(); set_bounding_boxes_dirty(); + if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); + } +} + +void Selection::mirror(Axis axis, TransformationType transformation_type) +{ + const Vec3d mirror((axis == X) ? -1.0 : 1.0, (axis == Y) ? -1.0 : 1.0, (axis == Z) ? -1.0 : 1.0); + scale_and_translate(mirror, Vec3d::Zero(), transformation_type); + } void Selection::translate(unsigned int object_idx, const Vec3d& displacement) @@ -1703,7 +1814,8 @@ void Selection::notify_instance_update(int object_idx, int instance_idx) if (object_idx == -1) { std::set> notify_set; - for (unsigned int i : m_list) + auto list = m_list; + for (unsigned int i : list) { int obj_index = (*m_volumes)[i]->object_idx(); //-1 means all the instance in this object @@ -1901,42 +2013,33 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, bool unif glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); - + Transform3d orient_matrix = Transform3d::Identity(); if (!boost::starts_with(sidebar_field, "layer")) { - const Vec3d& center = get_bounding_box().center(); + Vec3d center = get_bounding_box().center(); + const auto &[box, box_trafo] = get_bounding_box_in_current_reference_system(); // QDS - if (is_single_full_instance()/* && !wxGetApp().obj_manipul()->get_world_coordinates()*/) { + if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { + center = box_trafo.translation(); glsafe(::glTranslated(center(0), center(1), center(2))); - if (!boost::starts_with(sidebar_field, "position")) { - Transform3d orient_matrix = Transform3d::Identity(); - if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::starts_with(sidebar_field, "rotation")) { - if (boost::ends_with(sidebar_field, "x")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::ends_with(sidebar_field, "y")) { - const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); - if (rotation(0) == 0.0) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else - orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); - } + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); + glsafe(::glMultMatrixd(orient_matrix.data())); + } else if (is_single_volume_or_modifier()) { + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + if (wxGetApp().obj_manipul()->is_local_coordinates()) { + orient_matrix = get_bounding_box_in_current_reference_system().second; + orient_matrix.translation() = Vec3d::Zero(); + } else { + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); + center = box_trafo.translation(); } - - glsafe(::glMultMatrixd(orient_matrix.data())); } - } else if (is_single_volume() || is_single_modifier()) { glsafe(::glTranslated(center(0), center(1), center(2))); - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - if (!boost::starts_with(sidebar_field, "position")) - orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); } else { glsafe(::glTranslated(center(0), center(1), center(2))); if (requires_local_axes()) { - const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); glsafe(::glMultMatrixd(orient_matrix.data())); } } @@ -1993,6 +2096,7 @@ void Selection::copy_to_clipboard() dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; dst_object->sla_drain_holes = src_object->sla_drain_holes; + dst_object->brim_points = src_object->brim_points; dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment dst_object->layer_height_profile.assign(src_object->layer_height_profile); dst_object->origin_translation = src_object->origin_translation; @@ -2613,7 +2717,9 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, boo void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const { static const double Margin = 10.0; - + if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) { + return; + } std::string field = sidebar_field; // extract max_z @@ -2669,6 +2775,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co ::glBegin(GL_QUADS); if ((camera_on_top && type == 2) || (!camera_on_top && type == 1)) + //y ::glColor4f(68.0f/255.0f , 121.0f / 255.0f, 251.0f / 255.0f, 1.0f); else ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); @@ -2752,7 +2859,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: { + case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); @@ -2760,7 +2867,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ v->set_instance_offset(Z, volume->get_instance_offset().z()); break; } - case SYNC_ROTATION_GENERAL: + case SyncRotationType::GENERAL: // generic rotation -> update instance z with the delta of the rotation. const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); @@ -2923,8 +3030,8 @@ void Selection::paste_volumes_from_clipboard() { ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); - Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); - Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); + Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix_no_offset(); + Transform3d dst_matrix = dst_instance->get_transformation().get_matrix_no_offset(); bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); // used to keep relative position of multivolume selections when pasting from another object @@ -3008,14 +3115,14 @@ void Selection::paste_objects_from_clipboard() Vec3d displacement; bool in_current = plate->intersects(bbox); auto start_point = in_current ? bbox.center() : plate->get_build_volume().center(); + auto start_offset = in_current ? src_object->instances.front()->get_offset() : plate->get_build_volume().center(); if (shift_all(0) != 0 || shift_all(1) != 0) { // QDS: if multiple objects are selected, move them as a whole after copy if (i == 0) empty_cell_all = wxGetApp().plater()->canvas3D()->get_nearest_empty_cell({start_point(0), start_point(1)}, {bbox.size()(0)+1,bbox.size()(1)+1}); auto instance_shift = src_object->instances.front()->get_offset() - src_objects[0]->instances.front()->get_offset(); - displacement = {shift_all.x() + empty_cell_all.x()+instance_shift.x(), shift_all.y() + empty_cell_all.y()+instance_shift.y(), start_point(2)}; + displacement = {shift_all.x() + empty_cell_all.x() + instance_shift.x(), shift_all.y() + empty_cell_all.y() + instance_shift.y(), start_offset(2)}; } else { // QDS: if only one object is copied, find an empty cell to put it - auto start_offset = in_current ? src_object->instances.front()->get_offset() : plate->get_build_volume().center(); auto point_offset = start_offset - start_point; auto empty_cell = wxGetApp().plater()->canvas3D()->get_nearest_empty_cell({start_point(0), start_point(1)}, {bbox.size()(0)+1, bbox.size()(1)+1}); displacement = {empty_cell.x() + point_offset.x(), empty_cell.y() + point_offset.y(), start_offset(2)}; @@ -3082,5 +3189,52 @@ void Selection::transform_volume_relative( assert(false); } +ModelVolume *get_selected_volume(const Selection &selection) +{ + const GLVolume *gl_volume = get_selected_gl_volume(selection); + if (gl_volume == nullptr) return nullptr; + const ModelObjectPtrs &objects = selection.get_model()->objects; + return get_model_volume(*gl_volume, objects); +} + +const GLVolume *get_selected_gl_volume(const Selection &selection) +{ + int object_idx = selection.get_object_idx(); + // is more object selected? + if (object_idx == -1) return nullptr; + + const auto &list = selection.get_volume_idxs(); + // is more volumes selected? + if (list.size() != 1) return nullptr; + + unsigned int volume_idx = *list.begin(); + return selection.get_volume(volume_idx); +} + +ModelVolume *get_selected_volume(const ObjectID &volume_id, const Selection &selection) +{ + const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); + const ModelObjectPtrs & model_objects = selection.get_model()->objects; + for (auto id : volume_ids) { + const GLVolume * selected_volume = selection.get_volume(id); + const GLVolume::CompositeID &cid = selected_volume->composite_id; + ModelObject * obj = model_objects[cid.object_id]; + ModelVolume * volume = obj->volumes[cid.volume_id]; + if (volume_id == volume->id()) return volume; + } + return nullptr; +} + +ModelVolume *get_volume(const ObjectID &volume_id, const Selection &selection) +{ + const ModelObjectPtrs &objects = selection.get_model()->objects; + for (const ModelObject *object : objects) { + for (ModelVolume *volume : object->volumes) { + if (volume->id() == volume_id) return volume; + } + } + return nullptr; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 94d401d..a000ae3 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -12,6 +12,9 @@ namespace Slic3r { class Shader; class Model; class ModelObject; +class ModelVolume; +class ModelInstance; +class ObjectID; class GLVolume; class GLArrow; class GLCurvedArrow; @@ -27,7 +30,7 @@ using ModelObjectPtrs = std::vector; namespace GUI { enum ECoordinatesType : unsigned char { - World, + World = 0, Instance, Local }; @@ -179,7 +182,7 @@ private: const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } const Transform3d &get_instance_full_matrix() const { return m_instance.full_tran.get_matrix(); } - + const Geometry::Transformation &get_volume_transform() const { return m_volume.full_tran; } const Geometry::Transformation &get_instance_transform() const { return m_instance.full_tran; } }; @@ -247,7 +250,15 @@ private: // is useful for absolute scaling of tilted objects in world coordinate space. std::optional m_unscaled_instance_bounding_box; std::optional m_scaled_instance_bounding_box; - + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_bounding_box; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + std::optional m_full_scaled_instance_bounding_box; + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_local_bounding_box; // Bounding box aligned to the axis of the currently selected reference system (World/Object/Part) // and transform to place and orient it in world coordinates std::optional> m_bounding_box_in_current_reference_system; @@ -266,6 +277,7 @@ private: // QDS EMode m_volume_selection_mode{ Instance }; bool m_volume_selection_locked { false }; + std::vector m_trafo_matrices; public: Selection(); @@ -280,7 +292,7 @@ public: void set_model(Model* model); EMode get_mode() const { return m_mode; } - void set_mode(EMode mode) { m_mode = mode; } + void set_mode(EMode mode); int query_real_volume_idx_from_other_view(unsigned int object_idx, unsigned int instance_idx, unsigned int model_volume_idx); void add(unsigned int volume_idx, bool as_single_selection = true, bool check_for_already_contained = false); @@ -299,6 +311,10 @@ public: void remove_volumes(EMode mode, const std::vector& volume_idxs); //QDS + ModelVolume * get_selected_single_volume(int &out_object_idx, int &out_volume_idx); + ModelObject * get_selected_single_object(int &out_object_idx); + const ModelInstance * get_selected_single_intance(); + const std::vector &get_all_tran_of_selected_volumes(); void add_curr_plate(); void add_object_from_idx(std::vector& object_idxs); void remove_curr_plate(); @@ -361,6 +377,7 @@ public: const IndicesList& get_volume_idxs() const { return m_list; } const GLVolume* get_volume(unsigned int volume_idx) const; + GLVolume* get_volume(unsigned int volume_idx); const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); } const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } @@ -370,7 +387,15 @@ public: // is useful for absolute scaling of tilted objects in world coordinate space. const BoundingBoxf3& get_unscaled_instance_bounding_box() const; const BoundingBoxf3& get_scaled_instance_bounding_box() const; - + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3 &get_full_unscaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + const BoundingBoxf3 &get_full_scaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3 &get_full_unscaled_instance_local_bounding_box() const; // Returns the bounding box aligned to the axes of the currently selected reference system (World/Object/Part) // and the transform to place and orient it in world coordinates const std::pair &get_bounding_box_in_current_reference_system() const; @@ -385,7 +410,7 @@ public: const std::pair get_bounding_sphere() const; void setup_cache(); - void translate(const Vec3d& displacement, bool local = false); + void translate(const Vec3d &displacement, TransformationType transformation_type);//new void move_to_center(const Vec3d& displacement, bool local = false); void rotate(const Vec3d& rotation, TransformationType transformation_type); void flattening_rotate(const Vec3d& normal); @@ -395,7 +420,8 @@ public: #else void scale_to_fit_print_volume(const DynamicPrintConfig& config); #endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT - void mirror(Axis axis); + void scale_and_translate(const Vec3d &scale, const Vec3d &world_translation, TransformationType transformation_type); + void mirror(Axis axis, TransformationType transformation_type); void translate(unsigned int object_idx, const Vec3d& displacement); void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); @@ -406,6 +432,7 @@ public: //QDS: add partplate related logic void notify_instance_update(int object_idx, int instance_idx); // QDS + EMode get_volume_selection_mode(){ return m_volume_selection_mode;} void set_volume_selection_mode(EMode mode) { if (!m_volume_selection_locked) m_volume_selection_mode = mode; } void lock_volume_selection_mode() { m_volume_selection_locked = true; } void unlock_volume_selection_mode() { m_volume_selection_locked = false; } @@ -460,6 +487,9 @@ private: void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_bounding_box.reset(); + m_full_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_local_bounding_box.reset(); m_bounding_box_in_current_reference_system.reset(); m_bounding_sphere.reset(); } @@ -473,11 +503,13 @@ private: void render_sidebar_layers_hints(const std::string& sidebar_field) const; public: - enum SyncRotationType { + enum class SyncRotationType { // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. - SYNC_ROTATION_NONE = 0, + NONE = 0, // Synchronize after rotation by an axis not parallel with Z. - SYNC_ROTATION_GENERAL = 1, + GENERAL = 1, + // Synchronize after rotation reset. + RESET = 2 }; void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_volumes(); @@ -495,7 +527,11 @@ private: void transform_volume_relative( GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot); }; +ModelVolume * get_selected_volume(const Selection &selection); +const GLVolume *get_selected_gl_volume(const Selection &selection); +ModelVolume *get_selected_volume(const ObjectID &volume_id, const Selection &selection); +ModelVolume *get_volume(const ObjectID &volume_id, const Selection &selection); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index 09204d2..2e184ff 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -320,11 +320,14 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) wxBoxSizer *bSizer_buttons = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *bSizer_text = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *bSizer_finish_time = new wxBoxSizer(wxHORIZONTAL); wxPanel* penel_bottons = new wxPanel(parent); wxPanel* penel_text = new wxPanel(penel_bottons); + wxPanel* penel_finish_time = new wxPanel(parent); penel_text->SetBackgroundColour(*wxWHITE); penel_bottons->SetBackgroundColour(*wxWHITE); + penel_finish_time->SetBackgroundColour(*wxWHITE); wxBoxSizer *sizer_percent = new wxBoxSizer(wxVERTICAL); sizer_percent->Add(0, 0, 1, wxEXPAND, 0); @@ -383,6 +386,23 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) penel_text->SetSizer(bSizer_text); penel_text->Layout(); + m_staticText_finish_time = new wxStaticText(penel_finish_time, wxID_ANY, _L("Finish Time: N/A")); + m_staticText_finish_time->Wrap(-1); + m_staticText_finish_time->SetFont(wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("HarmonyOS Sans SC"))); + m_staticText_finish_time->SetForegroundColour(wxColour(146, 146, 146)); + m_staticText_finish_time->SetToolTip(_L("The estimated printing time for \nmulti-color models may be inaccurate.")); + m_staticText_finish_day = new RectTextPanel(penel_finish_time); + m_staticText_finish_day->Hide(); + bSizer_finish_time->Add(0, 0, 1, wxEXPAND, 0); + bSizer_finish_time->Add(0, 0, 0, wxLEFT, FromDIP(20)); + bSizer_finish_time->Add(m_staticText_finish_time, 0, wxALIGN_CENTER | wxALL, 0); + bSizer_finish_time->Add(m_staticText_finish_day, 0,wxLEFT | wxRIGHT , FromDIP(10)); + // bSizer_finish_time->Add(0, 0, 0, wxLEFT, FromDIP(20)); + bSizer_finish_time->Add(panel_button_block, 0, wxALIGN_CENTER | wxALL, 0); + penel_finish_time->SetMaxSize(wxSize(FromDIP(600), -1)); + penel_finish_time->SetSizer(bSizer_finish_time); + penel_finish_time->Layout(); + bSizer_buttons->Add(penel_text, 1, wxEXPAND | wxALL, 0); bSizer_buttons->Add(panel_button_block, 0, wxALIGN_CENTER | wxALL, 0); @@ -395,7 +415,7 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) bSizer_subtask_info->Add(m_printing_stage_value, 0, wxEXPAND | wxTOP, FromDIP(5)); bSizer_subtask_info->Add(penel_bottons, 0, wxEXPAND | wxTOP, FromDIP(10)); bSizer_subtask_info->Add(m_panel_progress, 0, wxEXPAND|wxRIGHT, FromDIP(25)); - + bSizer_subtask_info->Add(penel_finish_time, 0, wxEXPAND, FromDIP(10)); m_printing_sizer = new wxBoxSizer(wxHORIZONTAL); m_printing_sizer->SetMinSize(wxSize(PAGE_MIN_WIDTH, -1)); @@ -403,7 +423,6 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) m_printing_sizer->Add(FromDIP(8), 0, 0, wxEXPAND, 0); m_printing_sizer->Add(bSizer_subtask_info, 1, wxALL | wxEXPAND, 0); - m_staticline = new wxPanel( parent, wxID_ANY); m_staticline->SetBackgroundColour(wxColour(238,238,238)); m_staticline->Layout(); @@ -688,14 +707,44 @@ void PrintingTaskPanel::update_left_time(wxString time) m_staticText_progress_left->SetLabelText(time); } +void PrintingTaskPanel::update_finish_time(wxString finish_time) +{ + static wxString finish_day = ""; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " finish time: " << finish_time << " finish day: " << finish_day; + if (finish_time == "Finished") { + m_staticText_finish_time->SetLabelText(_L("Finished")); + finish_day = ""; + if (m_staticText_finish_day->IsShown()) m_staticText_finish_day->Hide(); + } + else { + if (!finish_time.Contains('+')) { + finish_day = ""; + if (m_staticText_finish_day->IsShown()) m_staticText_finish_day->Hide(); + } else { + int index = finish_time.find_last_of('+'); + wxString day = finish_time.Mid(index); + finish_time = finish_time.Mid(0, index); + if (finish_day != day) { + m_staticText_finish_day->setText(day); + finish_day = day; + if (!m_staticText_finish_day->IsShown()) m_staticText_finish_day->Show(); + } + } + m_staticText_finish_time->SetLabelText(_L("Finish Time: ") + finish_time); + } +} + + void PrintingTaskPanel::update_left_time(int mc_left_time) { // update gcode progress std::string left_time; + std::string right_time; wxString left_time_text = NA_STR; try { left_time = get_qdt_monitor_time_dhm(mc_left_time); + right_time = get_qdt_finish_time_dhm(mc_left_time); } catch (...) { ; @@ -703,6 +752,7 @@ void PrintingTaskPanel::update_left_time(int mc_left_time) if (!left_time.empty()) left_time_text = wxString::Format("-%s", left_time); update_left_time(left_time_text); + update_finish_time(right_time); } void PrintingTaskPanel::update_layers_num(bool show, wxString num) @@ -776,6 +826,10 @@ void PrintingTaskPanel::market_scoring_show() m_score_subtask_info->Show(); } +bool PrintingTaskPanel::is_market_scoring_show() { + return m_score_staticline->IsShown() && m_score_subtask_info->IsShown(); } + + void PrintingTaskPanel::market_scoring_hide() { m_score_staticline->Hide(); @@ -988,7 +1042,7 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() // media_ctrl_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); // media_ctrl_panel->SetBackgroundColour(*wxBLACK); // wxBoxSizer *bSizer_monitoring = new wxBoxSizer(wxVERTICAL); - m_media_ctrl = new wxMediaCtrl2(this); + m_media_ctrl = new wxMediaCtrl3(this); m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288))); m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40))); @@ -1528,13 +1582,18 @@ wxBoxSizer *StatusBasePanel::create_ams_group(wxWindow *parent) void StatusBasePanel::show_ams_group(bool show) { - m_ams_control->Show(true); - m_ams_control_box->Show(true); - m_ams_control->show_noams_mode(); - if (m_show_ams_group != show) { + if (m_show_ams_group != show || m_show_ams_group_reset) { + m_ams_control->Show(true); + m_ams_control_box->Show(true); + m_ams_control->show_noams_mode(); + if (m_show_ams_group != show) { Fit(); } + m_show_ams_group = show; + m_show_ams_group_reset = false; + m_ams_control->Layout(); + m_ams_control->Fit(); + Layout(); Fit(); } - m_show_ams_group = show; } void StatusPanel::update_camera_state(MachineObject* obj) @@ -1882,7 +1941,16 @@ void StatusPanel::on_subtask_abort(wxCommandEvent &event) } }); } - abort_dlg->update_text(_L("Are you sure you want to cancel this print?")); + abort_dlg->update_text(_L("Are you sure to stop printing?")); + abort_dlg->update_btn_label(_L("Yes"), _L("No")); + //y + // abort_dlg->m_button_cancel->SetBackgroundColor(abort_dlg->btn_bg_green); + // abort_dlg->m_button_cancel->SetBorderColor(*wxWHITE); + // abort_dlg->m_button_cancel->SetTextColor(wxColor("#FFFFFE")); + + // abort_dlg->m_button_ok->SetBackgroundColor(abort_dlg->btn_bg_white); + // abort_dlg->m_button_ok->SetBorderColor(wxColor(38, 46, 48)); + // abort_dlg->m_button_ok->SetTextColor(*wxBLACK); abort_dlg->on_show(); } @@ -1981,18 +2049,15 @@ void StatusPanel::update(MachineObject *obj) m_project_task_panel->Thaw(); #if !QDT_RELEASE_TO_PUBLIC - auto delay1 = std::chrono::duration_cast(obj->last_utc_time - std::chrono::system_clock::now()).count(); - auto delay2 = std::chrono::duration_cast(obj->last_push_time - std::chrono::system_clock::now()).count(); - auto delay = wxString::Format(" %ld/%ld", delay1, delay2); - m_staticText_timelapse - ->SetLabel((obj->is_lan_mode_printer() ? "Local Mqtt" : obj->is_tunnel_mqtt ? "Tunnel Mqtt" : "Cloud Mqtt") + delay); - m_bmToggleBtn_timelapse - ->Enable(!obj->is_lan_mode_printer()); - m_bmToggleBtn_timelapse - ->SetValue(obj->is_tunnel_mqtt); + auto delay1 = std::chrono::duration_cast(obj->last_utc_time - std::chrono::system_clock::now()).count(); + auto delay2 = std::chrono::duration_cast(obj->last_push_time - std::chrono::system_clock::now()).count(); + auto delay = wxString::Format(" %ld/%ld", delay1, delay2); + m_staticText_timelapse->SetLabel((obj->is_lan_mode_printer() ? "Local Mqtt" : obj->is_tunnel_mqtt ? "Tunnel Mqtt" : "Cloud Mqtt") + delay); + m_bmToggleBtn_timelapse->Enable(!obj->is_lan_mode_printer()); + m_bmToggleBtn_timelapse->SetValue(obj->is_tunnel_mqtt); #endif - m_machine_ctrl_panel->Freeze(); + // m_machine_ctrl_panel->Freeze(); if (obj->is_in_printing() && !obj->can_resume()) show_printing_status(false, true); @@ -2055,8 +2120,6 @@ void StatusPanel::update(MachineObject *obj) m_tempCtrl_chamber->GetTextCtrl()->SetValue(TEMP_BLANK_STR); } - //m_tempCtrl_chamber->Disable(); - } if (!obj->dev_connection_type.empty()) { @@ -2081,7 +2144,7 @@ void StatusPanel::update(MachineObject *obj) update_camera_state(obj); - m_machine_ctrl_panel->Thaw(); + //m_machine_ctrl_panel->Thaw(); } void StatusPanel::show_recenter_dialog() { @@ -2090,21 +2153,24 @@ void StatusPanel::show_recenter_dialog() { obj->command_go_home(); } -void StatusPanel::show_error_message(MachineObject* obj, wxString msg, std::string print_error_str, wxString image_url, std::vector used_button) +void StatusPanel::show_error_message(MachineObject *obj, bool is_exist, wxString msg, std::string print_error_str, wxString image_url, std::vector used_button) { - if (msg.IsEmpty()) { + if (is_exist && msg.IsEmpty()) { error_info_reset(); } else { + if (msg.IsEmpty()) { msg = _L("Unknown error."); } m_project_task_panel->show_error_msg(msg); if (!used_button.empty()) { BOOST_LOG_TRIVIAL(info) << "show print error! error_msg = " << msg; - if (m_print_error_dlg == nullptr) { - m_print_error_dlg = new PrintErrorDialog(this->GetParent(), wxID_ANY, _L("Error")); + if (m_print_error_dlg != nullptr) { + delete m_print_error_dlg; + m_print_error_dlg = nullptr; } + m_print_error_dlg = new PrintErrorDialog(this->GetParent(), wxID_ANY, _L("Error")); m_print_error_dlg->update_title_style(_L("Error"), used_button, this); - m_print_error_dlg->update_text_image(msg, image_url); + m_print_error_dlg->update_text_image(msg, print_error_str, image_url); m_print_error_dlg->Bind(EVT_SECONDARY_CHECK_CONFIRM, [this, obj](wxCommandEvent& e) { if (obj) { obj->command_clean_print_error(obj->subtask_id_, obj->print_error); @@ -2126,10 +2192,17 @@ void StatusPanel::show_error_message(MachineObject* obj, wxString msg, std::stri auto it_resume = std::find(message_containing_resume.begin(), message_containing_resume.end(), print_error_str); BOOST_LOG_TRIVIAL(info) << "show print error! error_msg = " << msg; - if (m_print_error_dlg_no_action == nullptr) { - m_print_error_dlg_no_action = new SecondaryCheckDialog(this->GetParent(), wxID_ANY, _L("Warning"), SecondaryCheckDialog::ButtonStyle::ONLY_CONFIRM); + + wxDateTime now = wxDateTime::Now(); + wxString show_time = now.Format("%H%M%d"); + wxString error_code_msg = wxString::Format("%S\n[%S %S]", msg, print_error_str, show_time); + + if (m_print_error_dlg_no_action != nullptr) { + delete m_print_error_dlg_no_action; + m_print_error_dlg_no_action = nullptr; } + m_print_error_dlg_no_action = new SecondaryCheckDialog(this->GetParent(), wxID_ANY, _L("Warning"), SecondaryCheckDialog::ButtonStyle::ONLY_CONFIRM); if (it_done != message_containing_done.end() && it_retry != message_containing_retry.end()) { m_print_error_dlg_no_action->update_title_style(_L("Warning"), SecondaryCheckDialog::ButtonStyle::DONE_AND_RETRY, this); } @@ -2145,7 +2218,7 @@ void StatusPanel::show_error_message(MachineObject* obj, wxString msg, std::stri else { m_print_error_dlg_no_action->update_title_style(_L("Warning"), SecondaryCheckDialog::ButtonStyle::ONLY_CONFIRM, this); } - m_print_error_dlg_no_action->update_text(msg); + m_print_error_dlg_no_action->update_text(error_code_msg); m_print_error_dlg_no_action->Bind(EVT_SECONDARY_CHECK_CONFIRM, [this, obj](wxCommandEvent& e) { if (obj) { obj->command_clean_print_error(obj->subtask_id_, obj->print_error); @@ -2168,7 +2241,7 @@ void StatusPanel::update_error_message() { if (obj->print_error <= 0) { before_error_code = obj->print_error; - show_error_message(obj, wxEmptyString); + show_error_message(obj, true, wxEmptyString); return; } else if (before_error_code != obj->print_error && obj->print_error != skip_print_error) { before_error_code = obj->print_error; @@ -2177,27 +2250,17 @@ void StatusPanel::update_error_message() char buf[32]; ::sprintf(buf, "%08X", obj->print_error); std::string print_error_str = std::string(buf); - if (print_error_str.size() > 4) { - print_error_str.insert(4, " "); - } + if (print_error_str.size() > 4) { print_error_str.insert(4, "-"); } - wxString error_msg = wxGetApp().get_hms_query()->query_print_error_msg(obj->print_error); + wxString error_msg; + bool is_errocode_exist = wxGetApp().get_hms_query()->query_print_error_msg(obj->print_error, error_msg); std::vector used_button; - wxString error_image_url = wxGetApp().get_hms_query()->query_print_error_url_action(obj->print_error,obj->dev_id, used_button); + wxString error_image_url = wxGetApp().get_hms_query()->query_print_error_url_action(obj->print_error, obj->dev_id, used_button); // special case - if (print_error_str == "0300 8003" || print_error_str == "0300 8002" || print_error_str == "0300 800A") + if (print_error_str == "0300-8003" || print_error_str == "0300-8002" || print_error_str == "0300-800A") { used_button.emplace_back(PrintErrorDialog::PrintErrorButton::JUMP_TO_LIVEVIEW); - if (!error_msg.IsEmpty()) { - wxDateTime now = wxDateTime::Now(); - wxString show_time = now.Format("%Y-%m-%d %H:%M:%S"); - - error_msg = wxString::Format("%s\n[%s %s]", - error_msg, - print_error_str, show_time); - show_error_message(obj, error_msg, print_error_str,error_image_url,used_button); - } else { - BOOST_LOG_TRIVIAL(info) << "show print error! error_msg is empty, print error = " << obj->print_error; } + show_error_message(obj, is_errocode_exist, error_msg, print_error_str, error_image_url, used_button); } } } @@ -2338,12 +2401,12 @@ void StatusPanel::update_misc_ctrl(MachineObject *obj) if (obj->can_unload_filament()) { if (!m_button_unload->IsShown()) { m_button_unload->Show(); - m_button_unload->GetParent()->Layout(); + // m_button_unload->GetParent()->Layout(); } } else { if (m_button_unload->IsShown()) { m_button_unload->Hide(); - m_button_unload->GetParent()->Layout(); + // m_button_unload->GetParent()->Layout(); } } @@ -2381,27 +2444,34 @@ void StatusPanel::update_misc_ctrl(MachineObject *obj) if (m_current_support_aux_fan != is_suppt_aux_fun) { if (is_suppt_aux_fun) { - m_switch_printing_fan->Show(); - m_switch_nozzle_fan->SetMinSize(MISC_BUTTON_3FAN_SIZE); - m_switch_nozzle_fan->SetMaxSize(MISC_BUTTON_3FAN_SIZE); - m_switch_cham_fan->SetMinSize(MISC_BUTTON_3FAN_SIZE); - m_switch_cham_fan->SetMaxSize(MISC_BUTTON_3FAN_SIZE); + if (!m_switch_printing_fan->IsShown()) { + m_switch_printing_fan->Show(); + m_switch_nozzle_fan->SetMinSize(MISC_BUTTON_3FAN_SIZE); + m_switch_nozzle_fan->SetMaxSize(MISC_BUTTON_3FAN_SIZE); + m_switch_cham_fan->SetMinSize(MISC_BUTTON_3FAN_SIZE); + m_switch_cham_fan->SetMaxSize(MISC_BUTTON_3FAN_SIZE); + } } else { - m_switch_printing_fan->Hide(); - m_switch_nozzle_fan->SetMinSize(MISC_BUTTON_2FAN_SIZE); - m_switch_nozzle_fan->SetMaxSize(MISC_BUTTON_2FAN_SIZE); - m_switch_cham_fan->SetMinSize(MISC_BUTTON_2FAN_SIZE); - m_switch_cham_fan->SetMaxSize(MISC_BUTTON_2FAN_SIZE); + if (m_switch_printing_fan->IsShown()) { + m_switch_printing_fan->Hide(); + m_switch_nozzle_fan->SetMinSize(MISC_BUTTON_2FAN_SIZE); + m_switch_nozzle_fan->SetMaxSize(MISC_BUTTON_2FAN_SIZE); + m_switch_cham_fan->SetMinSize(MISC_BUTTON_2FAN_SIZE); + m_switch_cham_fan->SetMaxSize(MISC_BUTTON_2FAN_SIZE); + } } m_misc_ctrl_sizer->Layout(); } if (!is_suppt_aux_fun && !is_suppt_cham_fun) { - m_switch_nozzle_fan->SetMinSize(MISC_BUTTON_1FAN_SIZE); - m_switch_nozzle_fan->SetMaxSize(MISC_BUTTON_1FAN_SIZE); - m_misc_ctrl_sizer->Layout(); + if (!m_switch_nozzle_fan->IsShown()) { + m_switch_nozzle_fan->Show(); + m_switch_nozzle_fan->SetMinSize(MISC_BUTTON_1FAN_SIZE); + m_switch_nozzle_fan->SetMaxSize(MISC_BUTTON_1FAN_SIZE); + m_misc_ctrl_sizer->Layout(); + } } @@ -2467,21 +2537,27 @@ void StatusPanel::update_misc_ctrl(MachineObject *obj) void StatusPanel::update_extruder_status(MachineObject* obj) { if (!obj) return; - if (obj->is_filament_at_extruder()) { - if (obj->extruder_axis_status == MachineObject::ExtruderAxisStatus::LOAD) { - m_bitmap_extruder_img->SetBitmap(m_bitmap_extruder_filled_load); - } - else { - m_bitmap_extruder_img->SetBitmap(m_bitmap_extruder_filled_unload); - } - } - else { - if (obj->extruder_axis_status == MachineObject::ExtruderAxisStatus::LOAD) { - m_bitmap_extruder_img->SetBitmap(m_bitmap_extruder_empty_load); - } else { - m_bitmap_extruder_img->SetBitmap(m_bitmap_extruder_empty_unload); - } - } + wxBitmap tmp; + if (obj->is_filament_at_extruder()) { + if (obj->extruder_axis_status == MachineObject::ExtruderAxisStatus::LOAD) { + tmp = m_bitmap_extruder_filled_load; + } + else { + tmp = m_bitmap_extruder_filled_unload; + } + } + else { + if (obj->extruder_axis_status == MachineObject::ExtruderAxisStatus::LOAD) { + tmp = m_bitmap_extruder_empty_load; + } + else { + tmp = m_bitmap_extruder_empty_unload; + } + } + if (!tmp.IsSameAs(m_bitmap_extruder_now)) { + m_bitmap_extruder_now = tmp; + m_bitmap_extruder_img->SetBitmap(tmp); + } } void StatusPanel::update_ams(MachineObject *obj) @@ -2500,7 +2576,7 @@ void StatusPanel::update_ams(MachineObject *obj) } if (m_filament_setting_dlg) { m_filament_setting_dlg->obj = obj; } - if (obj->cali_version != -1 && last_cali_version != obj->cali_version) { + if (obj && (!last_cali_version.has_value() || last_cali_version != obj->cali_version)) { last_cali_version = obj->cali_version; CalibUtils::emit_get_PA_calib_info(obj->nozzle_diameter, ""); } @@ -2551,18 +2627,6 @@ void StatusPanel::update_ams(MachineObject *obj) info.ams_id = ams->first; if (ams->second->is_exists && info.parse_ams_info(obj, ams->second, obj->ams_calibrate_remain_flag, obj->is_support_ams_humidity)) ams_info.push_back(info); } - //if (obj->ams_exist_bits != last_ams_exist_bits || obj->tray_exist_bits != last_tray_exist_bits || obj->tray_is_qdt_bits != last_tray_is_qdt_bits || - // obj->tray_read_done_bits != last_read_done_bits || obj->ams_version != last_ams_version) { - // m_ams_control->UpdateAms(ams_info, false); - // // select current ams - // //if (!obj->m_ams_id.empty()) m_ams_control->SwitchAms(obj->m_ams_id); - - // last_tray_exist_bits = obj->tray_exist_bits; - // last_ams_exist_bits = obj->ams_exist_bits; - // last_tray_is_qdt_bits = obj->tray_is_qdt_bits; - // last_read_done_bits = obj->tray_read_done_bits; - // last_ams_version = obj->ams_version; - //} // must select a current can m_ams_control->UpdateAms(ams_info, false); @@ -2957,7 +3021,10 @@ void StatusPanel::update_subtask(MachineObject *obj) int height = m_project_task_panel->get_bitmap_thumbnail()->GetSize().y; if (m_calib_method == CALI_METHOD_AUTO) { if (m_calib_mode == CalibMode::Calib_PA_Line) { - png_path = (boost::format("%1%/images/fd_calibration_auto.png") % resources_dir()).str(); + if (obj->get_printer_arch() == PrinterArch::ARCH_I3) + png_path = (boost::format("%1%/images/fd_calibration_auto_i3.png") % resources_dir()).str(); + else + png_path = (boost::format("%1%/images/fd_calibration_auto.png") % resources_dir()).str(); } else if (m_calib_mode == CalibMode::Calib_Flow_Rate) { png_path = (boost::format("%1%/images/flow_rate_calibration_auto.png") % resources_dir()).str(); @@ -2997,6 +3064,24 @@ void StatusPanel::update_subtask(MachineObject *obj) || obj->is_in_calibration()) { reset_printing_values(); } else if (obj->is_in_printing() || obj->print_status == "FINISH") { + + m_project_task_panel->update_subtask_name(wxString::Format("%s", GUI::from_u8(obj->subtask_name))); + + if (obj->get_modeltask() && obj->get_modeltask()->design_id > 0) { + m_project_task_panel->show_profile_info(wxString::FromUTF8(obj->get_modeltask()->profile_name)); + } else { + m_project_task_panel->show_profile_info(false); + } + + // update thumbnail + if (obj->is_sdcard_printing()) { + update_basic_print_data(false); + update_sdcard_subtask(obj); + } else { + update_basic_print_data(true); + update_cloud_subtask(obj); + } + if (obj->is_in_prepare() || obj->print_status == "SLICING") { m_project_task_panel->market_scoring_hide(); m_project_task_panel->get_request_failed_panel()->Hide(); @@ -3041,6 +3126,20 @@ void StatusPanel::update_subtask(MachineObject *obj) } else { m_project_task_panel->enable_pause_resume_button(true, "pause"); } + + // update printing stage + m_project_task_panel->update_left_time(obj->mc_left_time); + if (obj->subtask_) { + m_project_task_panel->update_stage_value(obj->get_curr_stage(), obj->subtask_->task_progress); + m_project_task_panel->update_progress_percent(wxString::Format("%d", obj->subtask_->task_progress), "%"); + m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %d/%d"), obj->curr_layer, obj->total_layers)); + + } else { + m_project_task_panel->update_stage_value(obj->get_curr_stage(), 0); + m_project_task_panel->update_progress_percent(NA_STR, wxEmptyString); + m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %s"), NA_STR)); + } + if (obj->is_printing_finished()) { obj->update_model_task(); m_project_task_panel->enable_abort_button(false); @@ -3085,43 +3184,12 @@ void StatusPanel::update_subtask(MachineObject *obj) m_project_task_panel->market_scoring_hide(); m_project_task_panel->get_request_failed_panel()->Hide(); } - // update printing stage - - m_project_task_panel->update_left_time(obj->mc_left_time); - if (obj->subtask_) { - m_project_task_panel->update_stage_value(obj->get_curr_stage(), obj->subtask_->task_progress); - m_project_task_panel->update_progress_percent(wxString::Format("%d", obj->subtask_->task_progress), "%"); - m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %d/%d"), obj->curr_layer, obj->total_layers)); - - } else { - m_project_task_panel->update_stage_value(obj->get_curr_stage(), 0); - m_project_task_panel->update_progress_percent(NA_STR, wxEmptyString); - m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %s"), NA_STR)); - } - } - - m_project_task_panel->update_subtask_name(wxString::Format("%s", GUI::from_u8(obj->subtask_name))); - - if (obj->get_modeltask() && obj->get_modeltask()->design_id > 0) { - m_project_task_panel->show_profile_info(wxString::FromUTF8(obj->get_modeltask()->profile_name)); - } - else { - m_project_task_panel->show_profile_info(false); - } - - //update thumbnail - if (obj->is_sdcard_printing()) { - update_basic_print_data(false); - update_sdcard_subtask(obj); - } else { - update_basic_print_data(true); - update_cloud_subtask(obj); } } else { reset_printing_values(); } - this->Layout(); + Layout(); } void StatusPanel::update_cloud_subtask(MachineObject *obj) @@ -3200,6 +3268,7 @@ void StatusPanel::reset_printing_values() m_project_task_panel->get_request_failed_panel()->Hide(); update_basic_print_data(false); m_project_task_panel->update_left_time(NA_STR); + m_project_task_panel->update_finish_time(NA_STR); m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %s"), NA_STR)); update_calib_bitmap(); @@ -3207,7 +3276,6 @@ void StatusPanel::reset_printing_values() m_start_loading_thumbnail = false; m_load_sdcard_thumbnail = false; skip_print_error = 0; - this->Layout(); } void StatusPanel::on_axis_ctrl_xy(wxCommandEvent &event) @@ -3221,7 +3289,20 @@ void StatusPanel::on_axis_ctrl_xy(wxCommandEvent &event) if (event.GetInt() == 5) { obj->command_axis_control("X", 1.0, -1.0f, 3000); } if (event.GetInt() == 6) { obj->command_axis_control("Y", 1.0, -1.0f, 3000); } if (event.GetInt() == 7) { obj->command_axis_control("X", 1.0, 1.0f, 3000); } - if (event.GetInt() == 8) { obj->command_go_home(); } + if (event.GetInt() == 8) { + if (axis_go_home_dlg == nullptr) { + axis_go_home_dlg = new SecondaryCheckDialog(this->GetParent(), wxID_ANY, _L("Auto homing")); + axis_go_home_dlg->update_text(_L("Are you sure you want to trigger auto homing?")); + axis_go_home_dlg->m_button_ok->SetLabel(_L("Homing")); + axis_go_home_dlg->Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) { + if (obj) { + BOOST_LOG_TRIVIAL(info) << "Axis have go home"; + obj->command_go_home(); + } + }); + } + axis_go_home_dlg->on_show(); + } //check is at home if (event.GetInt() == 1 @@ -3792,13 +3873,13 @@ void StatusPanel::on_ams_guide(wxCommandEvent& event) { wxString ams_wiki_url; if (m_ams_control && m_ams_control->m_is_none_ams_mode == AMSModel::GENERIC_AMS) { - ams_wiki_url = "https://wiki.qidilab.com/en/software/qidi-studio/use-ams-on-qidi-studio"; + ams_wiki_url = "https://wiki.qiditech.com/en/software/qidi-studio/use-ams-on-qidi-studio"; } else if (m_ams_control && m_ams_control->m_is_none_ams_mode == AMSModel::EXTRA_AMS) { - ams_wiki_url = "https://wiki.qidilab.com/en/ams-lite"; + ams_wiki_url = "https://wiki.qiditech.com/en/ams-lite"; } else { - ams_wiki_url = "https://wiki.qidilab.com/en/software/qidi-studio/use-ams-on-qidi-studio"; + ams_wiki_url = "https://wiki.qiditech.com/en/software/qidi-studio/use-ams-on-qidi-studio"; } wxLaunchDefaultBrowser(ams_wiki_url); @@ -4991,5 +5072,29 @@ void ScoreDialog::set_cloud_bitmap(std::vector cloud_bitmaps) Fit(); } -} // namespace GUI -} // namespace Slic3r +RectTextPanel::RectTextPanel(wxWindow *parent) : wxPanel(parent) +{ + Bind(wxEVT_PAINT, &RectTextPanel::OnPaint, this); +} + +void RectTextPanel::setText(const wxString text) +{ + this->text = text; + Refresh(); +} + +void RectTextPanel::OnPaint(wxPaintEvent &event) { + wxPaintDC dc(this); + dc.SetFont(::Label::Body_12); + wxSize textSize = dc.GetTextExtent(text); + + dc.SetBrush(wxBrush(wxColour("#00AE42"))); + dc.SetPen(wxPen(wxColour("#00AE42"))); + wxRect rect(0, 0, textSize.GetWidth() + 4, textSize.GetHeight() + 4); + SetSize(rect.GetSize()); + dc.DrawRoundedRectangle(rect, 4); + dc.SetTextForeground(wxColour(255, 255, 255)); + dc.DrawText(text, wxPoint(2, 2)); +} + +}} // namespace Slic3r diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index 8161eff..817a7e9 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -13,7 +13,6 @@ #include #include #include -#include "wxMediaCtrl2.h" #include "MediaPlayCtrl.h" #include "AMSSetting.hpp" #include "Calibration.hpp" @@ -148,7 +147,19 @@ protected: std::set> add_need_upload_imgs(); std::pair create_local_thumbnail(wxString &local_path); std::pair create_oss_thumbnail(std::string &oss_path); - + +}; + +class RectTextPanel : public wxPanel +{ +public: + RectTextPanel(wxWindow *parent); + + void setText(const wxString text); + + void OnPaint(wxPaintEvent &event); +private: + wxString text; }; class PrintingTaskPanel : public wxPanel @@ -181,6 +192,8 @@ private: wxStaticText* m_staticText_progress_percent; wxStaticText* m_staticText_progress_percent_icon; wxStaticText* m_staticText_progress_left; + wxStaticText* m_staticText_finish_time; + RectTextPanel* m_staticText_finish_day; wxStaticText* m_staticText_layers; wxStaticText * m_has_rated_prompt; wxStaticText * m_request_failed_info; @@ -221,6 +234,7 @@ public: void update_stage_value(wxString stage, int val); void update_progress_percent(wxString percent, wxString icon); void update_left_time(wxString time); + void update_finish_time(wxString finish_time); void update_left_time(int mc_left_time); void update_layers_num(bool show, wxString num = wxEmptyString); void show_priting_use_info(bool show, wxString time = wxEmptyString, wxString weight = wxEmptyString); @@ -229,6 +243,7 @@ public: void set_brightness_value(int value) { m_brightness_value = value; } void set_plate_index(int plate_idx = -1); void market_scoring_show(); + bool is_market_scoring_show(); void market_scoring_hide(); public: @@ -271,6 +286,7 @@ protected: wxBitmap m_bitmap_extruder_filled_load; wxBitmap m_bitmap_extruder_empty_unload; wxBitmap m_bitmap_extruder_filled_unload; + wxBitmap m_bitmap_extruder_now; CameraRecordingStatus m_state_recording{CameraRecordingStatus::RECORDING_NONE}; CameraTimelapseStatus m_state_timelapse{CameraTimelapseStatus::TIMELAPSE_NONE}; @@ -310,7 +326,7 @@ protected: wxStaticBitmap *m_bitmap_static_use_weight; - wxMediaCtrl2 * m_media_ctrl; + wxMediaCtrl3 * m_media_ctrl; MediaPlayCtrl * m_media_play_ctrl; Label * m_staticText_printing; @@ -372,6 +388,7 @@ protected: wxBoxSizer* m_ams_list; wxStaticText * m_ams_debug; bool m_show_ams_group{false}; + bool m_show_ams_group_reset{true}; AMSControl* m_ams_control; StaticBox* m_ams_control_box; wxStaticBitmap *m_ams_extruder_img; @@ -440,6 +457,7 @@ public: wxBoxSizer *create_ams_group(wxWindow *parent); wxBoxSizer *create_settings_group(wxWindow *parent); + void reset_ams_group_show_flag() {m_show_ams_group_reset = true;}; void show_ams_group(bool show = true); MediaPlayCtrl* get_media_play_ctrl() {return m_media_play_ctrl;}; }; @@ -467,6 +485,7 @@ protected: SecondaryCheckDialog* con_load_dlg = nullptr; SecondaryCheckDialog* ctrl_e_hint_dlg = nullptr; SecondaryCheckDialog* sdcard_hint_dlg = nullptr; + SecondaryCheckDialog* axis_go_home_dlg = nullptr; FanControlPopup* m_fan_control_popup{nullptr}; @@ -515,7 +534,8 @@ protected: void on_subtask_pause_resume(wxCommandEvent &event); void on_subtask_abort(wxCommandEvent &event); void on_print_error_clean(wxCommandEvent &event); - void show_error_message(MachineObject* obj, wxString msg, std::string print_error_str = "",wxString image_url="",std::vector used_button=std::vector()); + void show_error_message( + MachineObject *obj, bool is_exist, wxString msg, std::string print_error_str = "", wxString image_url = "", std::vector used_button = std::vector()); void error_info_reset(); void show_recenter_dialog(); @@ -637,7 +657,7 @@ public: long last_read_done_bits{ -1 }; long last_reading_bits { -1 }; long last_ams_version { -1 }; - int last_cali_version{-1}; + std::optional last_cali_version; enum ThumbnailState task_thumbnail_state {ThumbnailState::PLACE_HOLDER}; std::vector last_stage_list_info; diff --git a/src/slic3r/GUI/StepMeshDialog.cpp b/src/slic3r/GUI/StepMeshDialog.cpp new file mode 100644 index 0000000..3d372af --- /dev/null +++ b/src/slic3r/GUI/StepMeshDialog.cpp @@ -0,0 +1,357 @@ +#include "StepMeshDialog.hpp" + +#include +#include +#include +#include +#include +#include "GUI_App.hpp" +#include "I18N.hpp" +#include "MainFrame.hpp" +#include "Widgets/Button.hpp" +#include "Widgets/TextInput.hpp" +#include + +using namespace Slic3r; +using namespace Slic3r::GUI; + +static int _scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit() / 10; } +static int _ITEM_WIDTH() { return _scale(30); } +#define MIN_DIALOG_WIDTH FromDIP(400) +#define SLIDER_WIDTH FromDIP(200) +#define TEXT_CTRL_WIDTH FromDIP(70) +#define BUTTON_SIZE wxSize(FromDIP(58), FromDIP(24)) +#define BUTTON_BORDER FromDIP(int(400 - 58 * 2) / 8) +#define SLIDER_SCALE(val) ((val) / 0.001) +#define SLIDER_UNSCALE(val) ((val) * 0.001) +#define SLIDER_SCALE_10(val) ((val) / 0.01) +#define SLIDER_UNSCALE_10(val) ((val) * 0.01) +#define LEFT_RIGHT_PADING FromDIP(20) + +wxDEFINE_EVENT(wxEVT_THREAD_DONE, wxCommandEvent); + +class CenteredStaticText : public wxStaticText +{ +public: + CenteredStaticText(wxWindow* parent, wxWindowID id, const wxString& label, const wxPoint& position, const wxSize& size = wxDefaultSize, long style = 0) + : wxStaticText(parent, id, label, position, size, style) { + CenterOnPosition(position); + } + + void CenterOnPosition(const wxPoint& position) { + int textWidth, textHeight; + GetTextExtent(GetLabel(), &textWidth, &textHeight); + int x = position.x - textWidth / 2; + int y = position.y - textHeight / 2; + SetPosition(wxPoint(x, y)); + } +}; + +void StepMeshDialog::on_dpi_changed(const wxRect& suggested_rect) { +}; + +bool StepMeshDialog:: validate_number_range(const wxString& value, double min, double max) { + double num = 0.0; + if (value.IsEmpty()) { + return false; + } + try { + if (!value.ToDouble(&num)) { + return false; + } + } catch (...) { + return false; + } + + return (num >= min && num <= max); +} + +StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double linear_init, double angle_init) + : DPIDialog(parent ? parent : static_cast(wxGetApp().mainframe), + wxID_ANY, + _(L("Step file import parameters")), + wxDefaultPosition, + wxDefaultSize, + wxDEFAULT_DIALOG_STYLE /* | wxRESIZE_BORDER*/), m_file(file) +{ + m_linear_last = wxString::Format("%.3f", linear_init); + m_angle_last = wxString::Format("%.2f", angle_init); + + Bind(wxEVT_THREAD_DONE, &StepMeshDialog::on_task_done, this); + + std::string icon_path = (boost::format("%1%/images/QIDIStudioTitle.ico") + % Slic3r::resources_dir()).str(); + SetIcon(wxIcon(Slic3r::encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + SetBackgroundColour(*wxWHITE); + + wxBoxSizer* bSizer = new wxBoxSizer(wxVERTICAL); + bSizer->SetMinSize(wxSize(MIN_DIALOG_WIDTH, -1)); + + auto image_bitmap = create_scaled_bitmap("step_mesh_info", this, FromDIP(120)); + + // wxPanel* overlay_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, image_bitmap.GetSize(), wxTAB_TRAVERSAL); + // overlay_panel->SetBackgroundStyle(wxBG_STYLE_PAINT); + // wxStaticBitmap *image = new wxStaticBitmap(overlay_panel, wxID_ANY, image_bitmap, wxDefaultPosition, overlay_panel->GetSize(), 0); + + // CenteredStaticText* text_1 = new CenteredStaticText (overlay_panel, wxID_ANY, _L("Smooth"), + // wxPoint(overlay_panel->GetSize().GetWidth() / 6, + // overlay_panel->GetSize().GetHeight() / 2)); + // CenteredStaticText* text_2 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Rough"), + // wxPoint(overlay_panel->GetSize().GetWidth() * 5 / 6, + // overlay_panel->GetSize().GetHeight() / 2)); + // CenteredStaticText* text_3 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Reduce Linear"), + // wxPoint(overlay_panel->GetSize().GetWidth() / 2, + // overlay_panel->GetSize().GetHeight() * 1.3 / 3)); + // CenteredStaticText* text_4 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Reduce Angle"), + // wxPoint(overlay_panel->GetSize().GetWidth() / 2, + // overlay_panel->GetSize().GetHeight() * 2 / 3)); + // CenteredStaticText* text_5 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("More faces"), + // wxPoint(overlay_panel->GetSize().GetWidth() / 6, + // overlay_panel->GetSize().GetHeight() * 2.8 / 3)); + // CenteredStaticText* text_6 = new CenteredStaticText(overlay_panel, wxID_ANY, _L("Fewer faces"), + // wxPoint(overlay_panel->GetSize().GetWidth() * 5 / 6, + // overlay_panel->GetSize().GetHeight() * 2.8 / 3)); + + // bSizer->Add(overlay_panel, 0, wxALIGN_CENTER | wxALL, 10); + + wxBoxSizer* tips_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText* info = new wxStaticText(this, wxID_ANY, _L("Smaller linear and angular deflections result in higher-quality transformations but increase the processing time.")); + wxStaticText *tips = new wxStaticText(this, wxID_ANY, _L("See QIDI Tech Wiki")); + wxFont font(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false); + font.SetUnderlined(true); + tips->SetFont(font); + tips->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) { + wxLaunchDefaultBrowser("https://wiki.qidi3d.com/zh/software/qidi-studio/expand/step"); + }); + info->Wrap(FromDIP(400)); + tips_sizer->Add(info, 0, wxALIGN_LEFT); + tips_sizer->Add(tips, 0, wxALIGN_LEFT); + bSizer->Add(tips_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING); + + wxBoxSizer* linear_sizer = new wxBoxSizer(wxHORIZONTAL); + //linear_sizer->SetMinSize(wxSize(MIN_DIALOG_WIDTH, -1)); + wxStaticText* linear_title = new wxStaticText(this, + wxID_ANY, _L("Linear Deflection") + ": "); + linear_sizer->Add(linear_title, 0, wxALIGN_LEFT); + linear_sizer->AddStretchSpacer(1); + wxSlider* linear_slider = new wxSlider(this, wxID_ANY, + SLIDER_SCALE(get_linear_defletion()), + 1, 100, wxDefaultPosition, + wxSize(SLIDER_WIDTH, -1), + wxSL_HORIZONTAL); + linear_sizer->Add(linear_slider, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5)); + + auto linear_input = new ::TextInput(this, m_linear_last, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(TEXT_CTRL_WIDTH, -1), wxTE_CENTER); + linear_input->GetTextCtrl()->SetFont(Label::Body_12); + linear_input->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + linear_sizer->Add(linear_input, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5)); + linear_input->Bind(wxEVT_KILL_FOCUS, ([this, linear_input](wxFocusEvent& e) { + wxString value = linear_input->GetTextCtrl()->GetValue(); + if (validate_number_range(value, 0.001, 0.1)) { + m_linear_last = value; + update_mesh_number_text(); + } else { + MessageDialog msg_dlg(nullptr, _L("Please input a valid value (0.001 < linear deflection < 0.1)"), wxEmptyString, wxICON_WARNING | wxOK); + msg_dlg.ShowModal(); + linear_input->GetTextCtrl()->SetValue(m_linear_last); + } + e.Skip(); + })); + // textctrl bind slider + linear_input->Bind(wxEVT_TEXT, ([this, linear_slider, linear_input](wxCommandEvent& e) { + double slider_value_long; + int slider_value; + wxString value = linear_input->GetTextCtrl()->GetValue(); + if (value.ToDouble(&slider_value_long)) { + slider_value = SLIDER_SCALE(slider_value_long); + if (slider_value >= linear_slider->GetMin() && slider_value <= linear_slider->GetMax()) { + linear_slider->SetValue(slider_value); + } + } + })); + linear_slider->Bind(wxEVT_SLIDER, ([this, linear_slider, linear_input](wxCommandEvent& e) { + double slider_value = SLIDER_UNSCALE(linear_slider->GetValue()); + linear_input->GetTextCtrl()->SetValue(wxString::Format("%.3f", slider_value)); + m_linear_last = wxString::Format("%.3f", slider_value); + })); + linear_slider->Bind(wxEVT_LEFT_UP, ([this](wxMouseEvent& e) { + update_mesh_number_text(); + e.Skip(); + })); + + bSizer->Add(linear_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, LEFT_RIGHT_PADING); + + wxBoxSizer* angle_sizer = new wxBoxSizer(wxHORIZONTAL); + wxStaticText* angle_title = new wxStaticText(this, + wxID_ANY, _L("Angle Deflection") + ": "); + angle_sizer->Add(angle_title, 0, wxALIGN_LEFT); + angle_sizer->AddStretchSpacer(1); + wxSlider* angle_slider = new wxSlider(this, wxID_ANY, + SLIDER_SCALE_10(get_angle_defletion()), + 1, 100, wxDefaultPosition, + wxSize(SLIDER_WIDTH, -1), + wxSL_HORIZONTAL); + angle_sizer->Add(angle_slider, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5)); + + auto angle_input = new ::TextInput(this, m_angle_last, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(TEXT_CTRL_WIDTH, -1), wxTE_CENTER); + angle_input->GetTextCtrl()->SetFont(Label::Body_12); + angle_input->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + angle_sizer->Add(angle_input, 0, wxALIGN_RIGHT | wxLEFT, FromDIP(5)); + angle_input->Bind(wxEVT_KILL_FOCUS, ([this, angle_input](wxFocusEvent& e) { + wxString value = angle_input->GetTextCtrl()->GetValue(); + if (validate_number_range(value, 0.01, 1)) { + m_angle_last = value; + update_mesh_number_text(); + } else { + MessageDialog msg_dlg(nullptr, _L("Please input a valid value (0.01 < angle deflection < 1.0)"), wxEmptyString, wxICON_WARNING | wxOK); + msg_dlg.ShowModal(); + angle_input->GetTextCtrl()->SetValue(m_angle_last); + } + e.Skip(); + })); + // textctrl bind slider + angle_input->Bind(wxEVT_TEXT, ([this, angle_slider, angle_input](wxCommandEvent& e) { + double slider_value_long; + int slider_value; + wxString value = angle_input->GetTextCtrl()->GetValue(); + if (value.ToDouble(&slider_value_long)) { + slider_value = SLIDER_SCALE_10(slider_value_long); + if (slider_value >= angle_slider->GetMin() && slider_value <= angle_slider->GetMax()) { + angle_slider->SetValue(slider_value); + } + } + })); + + angle_slider->Bind(wxEVT_SLIDER, ([this, angle_slider, angle_input](wxCommandEvent& e) { + double slider_value = SLIDER_UNSCALE_10(angle_slider->GetValue()); + angle_input->GetTextCtrl()->SetValue(wxString::Format("%.2f", slider_value)); + m_angle_last = wxString::Format("%.2f", slider_value); + })); + angle_slider->Bind(wxEVT_LEFT_UP, ([this](wxMouseEvent& e) { + update_mesh_number_text(); + e.Skip(); + })); + + bSizer->Add(angle_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING); + + wxBoxSizer* mesh_face_number_sizer = new wxBoxSizer(wxHORIZONTAL); + wxStaticText *mesh_face_number_title = new wxStaticText(this, wxID_ANY, _L("Number of triangular facets") + ": "); + mesh_face_number_text = new wxStaticText(this, wxID_ANY, _L("0")); + mesh_face_number_text->SetMinSize(wxSize(FromDIP(150), -1)); + mesh_face_number_sizer->Add(mesh_face_number_title, 0, wxALIGN_LEFT); + mesh_face_number_sizer->Add(mesh_face_number_text, 0, wxALIGN_LEFT); + bSizer->Add(mesh_face_number_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, LEFT_RIGHT_PADING); + + wxBoxSizer* bSizer_button = new wxBoxSizer(wxHORIZONTAL); + bSizer_button->SetMinSize(wxSize(FromDIP(100), -1)); + m_checkbox = new wxCheckBox(this, wxID_ANY, _L("Don't show again"), wxDefaultPosition, wxDefaultSize, 0); + bSizer_button->Add(m_checkbox, 0, wxALIGN_LEFT); + bSizer_button->AddStretchSpacer(1); + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(AMS_CONTROL_BRAND_COLOUR, StateColor::Normal)); + m_button_ok = new Button(this, _L("OK")); + m_button_ok->SetBackgroundColor(btn_bg_green); + m_button_ok->SetBorderColor(*wxWHITE); + m_button_ok->SetTextColor(wxColour(0xFFFFFE)); + m_button_ok->SetFont(Label::Body_12); + m_button_ok->SetSize(BUTTON_SIZE); + m_button_ok->SetMinSize(BUTTON_SIZE); + m_button_ok->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_ok, 0, wxALIGN_RIGHT, BUTTON_BORDER); + + m_button_ok->Bind(wxEVT_LEFT_DOWN, [this, angle_input, linear_input](wxMouseEvent& e) { + stop_task(); + if (validate_number_range(angle_input->GetTextCtrl()->GetValue(), 0.01, 1) && + validate_number_range(linear_input->GetTextCtrl()->GetValue(), 0.001, 0.1)) { + if (m_checkbox->IsChecked()) { + wxGetApp().app_config->set_bool("enable_step_mesh_setting", false); + } + wxGetApp().app_config->set("linear_defletion", float_to_string_decimal_point(get_linear_defletion(), 3)); + wxGetApp().app_config->set("angle_defletion", float_to_string_decimal_point(get_angle_defletion(), 2)); + + EndModal(wxID_OK); + } + SetFocusIgnoringChildren(); + }); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + m_button_cancel = new Button(this, _L("Cancel")); + m_button_cancel->SetBackgroundColor(btn_bg_white); + m_button_cancel->SetBorderColor(wxColour(38, 46, 48)); + m_button_cancel->SetFont(Label::Body_12); + m_button_cancel->SetSize(BUTTON_SIZE); + m_button_cancel->SetMinSize(BUTTON_SIZE); + m_button_cancel->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_cancel, 0, wxALIGN_RIGHT | wxLEFT, BUTTON_BORDER); + + m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) { + stop_task(); + EndModal(wxID_CANCEL); + }); + + bSizer->Add(bSizer_button, 1, wxEXPAND | wxALL, LEFT_RIGHT_PADING); + + this->SetSizer(bSizer); + update_mesh_number_text(); + this->Layout(); + bSizer->Fit(this); + + this->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { + SetFocusIgnoringChildren(); + }); + mesh_face_number_text->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { + SetFocusIgnoringChildren(); + }); + + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { + stop_task(); + EndModal(wxID_CANCEL); + }); + + wxGetApp().UpdateDlgDarkUI(this); +} + +void StepMeshDialog::on_task_done(wxCommandEvent& event) +{ + wxString text = event.GetString(); + mesh_face_number_text->SetLabel(text); + if (task.valid()) { + task.get(); + } +} + +void StepMeshDialog::stop_task() +{ + if (task.valid()) { + m_file.m_stop_mesh.store(true); + unsigned int test = task.get(); + m_file.m_stop_mesh.store(false); + std::cout << test << std::endl; + } + +} + +void StepMeshDialog::update_mesh_number_text() +{ + if (m_last_linear == get_linear_defletion() && m_last_angle == get_angle_defletion()) + return; + wxString newText = wxString::Format(_L("Calculating, please wait...")); + mesh_face_number_text->SetLabel(newText); + + stop_task(); + task = std::async(std::launch::async, [&] { + unsigned int number = m_file.get_triangle_num(get_linear_defletion(), get_angle_defletion()); + if (number != 0) { + wxString number_text = wxString::Format("%d", number); + wxCommandEvent event(wxEVT_THREAD_DONE); + event.SetString(number_text); + wxPostEvent(this, event); + m_last_linear = get_linear_defletion(); + m_last_angle = get_angle_defletion(); + } + return number; + }); +} \ No newline at end of file diff --git a/src/slic3r/GUI/StepMeshDialog.hpp b/src/slic3r/GUI/StepMeshDialog.hpp new file mode 100644 index 0000000..2090bcd --- /dev/null +++ b/src/slic3r/GUI/StepMeshDialog.hpp @@ -0,0 +1,50 @@ +#ifndef _STEP_MESH_DIALOG_H_ +#define _STEP_MESH_DIALOG_H_ + +#include +#include +#include "GUI_App.hpp" +#include "GUI_Utils.hpp" +#include "libslic3r/Format/STEP.hpp" +#include "Widgets/Button.hpp" +class Button; + +class StepMeshDialog : public Slic3r::GUI::DPIDialog +{ +public: + StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double linear_init, double angle_init); + void on_dpi_changed(const wxRect& suggested_rect) override; + inline double get_linear_defletion() { + double value; + if (m_linear_last.ToDouble(&value)) { + return value; + }else { + return m_last_linear; + } + } + inline double get_angle_defletion() { + double value; + if (m_angle_last.ToDouble(&value)) { + return value; + } else { + return m_last_angle; + } + } +private: + Slic3r::Step& m_file; + Button* m_button_ok = nullptr; + Button* m_button_cancel = nullptr; + wxCheckBox* m_checkbox = nullptr; + wxString m_linear_last; + wxString m_angle_last; + wxStaticText* mesh_face_number_text; + double m_last_linear = 0.003; + double m_last_angle = 0.5; + std::future task; + bool validate_number_range(const wxString& value, double min, double max); + void update_mesh_number_text(); + void on_task_done(wxCommandEvent& event); + void stop_task(); +}; + +#endif // _STEP_MESH_DIALOG_H_ diff --git a/src/slic3r/GUI/SurfaceDrag.cpp b/src/slic3r/GUI/SurfaceDrag.cpp new file mode 100644 index 0000000..9f48489 --- /dev/null +++ b/src/slic3r/GUI/SurfaceDrag.cpp @@ -0,0 +1,716 @@ +#include "SurfaceDrag.hpp" + +#include // ModelVolume +#include + +#include "slic3r/Utils/RaycastManager.hpp" + +#include "GLCanvas3D.hpp" +#include "Camera.hpp" +#include "CameraUtils.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "Gizmos/GizmoObjectManipulation.hpp" + + +using namespace Slic3r; +using namespace Slic3r::GUI; + +namespace{ +// Distance of embossed volume from surface to be represented as distance surface +// Maximal distance is also enlarge by size of emboss depth +constexpr Slic3r::MinMax surface_distance_sq{1e-4, 10.}; // [in mm] + +/// +/// Extract position of mouse from mouse event +/// +/// Event +/// Position +Vec2d mouse_position(const wxMouseEvent &mouse_event); + +bool start_dragging(const Vec2d &mouse_pos, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager, + const std::optional &up_limit); + +bool dragging(const Vec2d &mouse_pos, + const Camera &camera, + SurfaceDrag &surface_drag, // need to write whether exist hit + GLCanvas3D &canvas, + const RaycastManager &raycast_manager, + const std::optional &up_limit); + +Transform3d get_volume_transformation( + Transform3d world, // from volume + const Vec3d& world_dir, // wanted new direction + const Vec3d& world_position, // wanted new position + const std::optional& fix, // [optional] fix matrix + // Invers transformation of text volume instance + // Help convert world transformation to instance space + const Transform3d& instance_inv, + // initial rotation in Z axis + std::optional current_angle = {}, + const std::optional &up_limit = {}); + +// distinguish between transformation of volume inside object +// and object(single full instance with one volume) +bool is_embossed_object(const Selection &selection); + +/// +/// Get fix transformation for selected volume +/// Fix after store to 3mf +/// +/// Select only wanted volume +/// Pointer on fix transformation from ModelVolume when exists otherwise nullptr +const Transform3d *get_fix_transformation(const Selection &selection); +} + +namespace Slic3r::GUI { + // Calculate scale in world for check in debug +[[maybe_unused]] static std::optional calc_scale(const Matrix3d &from, const Matrix3d &to, const Vec3d &dir) +{ + Vec3d from_dir = from * dir; + Vec3d to_dir = to * dir; + double from_scale_sq = from_dir.squaredNorm(); + double to_scale_sq = to_dir.squaredNorm(); + if (is_approx(from_scale_sq, to_scale_sq, 1e-3)) + return {}; // no scale + return sqrt(from_scale_sq / to_scale_sq); +} + +bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager, + const std::optional&up_limit) +{ + // Fix when leave window during dragging + // Fix when click right button + if (surface_drag.has_value() && !mouse_event.Dragging()) { + // write transformation from UI into model + canvas.do_move(L("Move over surface")); + wxGetApp().obj_manipul()->set_dirty(); + + // allow moving with object again + //canvas.enable_moving(true); + //canvas.enable_picking(true); + surface_drag.reset(); + + // only left up is correct + // otherwise it is fix state and return false + return mouse_event.LeftUp(); + } + + if (mouse_event.Moving()) + return false; + + if (mouse_event.LeftDown()) + return start_dragging(mouse_position(mouse_event), camera, surface_drag, canvas, raycast_manager, up_limit); + + // Dragging starts out of window + if (!surface_drag.has_value()) + return false; + + if (mouse_event.Dragging()) + return dragging(mouse_position(mouse_event), camera, *surface_drag, canvas, raycast_manager, up_limit); + + return false; +} + +std::optional calc_surface_offset(const Selection &selection, RaycastManager &raycast_manager) { + const GLVolume *gl_volume_ptr = get_selected_gl_volume(selection); + if (gl_volume_ptr == nullptr) + return {}; + const GLVolume& gl_volume = *gl_volume_ptr; + + const ModelObjectPtrs &objects = selection.get_model()->objects; + const ModelVolume* volume = get_model_volume(gl_volume, objects); + if (volume == nullptr) + return {}; + + const ModelInstance* instance = get_model_instance(gl_volume, objects); + if (instance == nullptr) + return {}; + + // Move object on surface + auto cond = RaycastManager::SkipVolume(volume->id().id); + raycast_manager.actualize(*instance, &cond); + + Transform3d to_world = world_matrix_fixed(gl_volume, objects); + Vec3d point = to_world.translation(); + Vec3d dir = -get_z_base(to_world); + // ray in direction of text projection(from volume zero to z-dir) + std::optional hit_opt = raycast_manager.closest_hit(point, dir, &cond); + + // Try to find closest point when no hit object in emboss direction + if (!hit_opt.has_value()) { + std::optional close_point_opt = raycast_manager.closest(point); + + // It should NOT appear. Closest point always exists. + assert(close_point_opt.has_value()); + if (!close_point_opt.has_value()) + return {}; + + // It is no neccesary to move with origin by very small value + if (close_point_opt->squared_distance < EPSILON) + return {}; + + const RaycastManager::ClosePoint &close_point = *close_point_opt; + Transform3d hit_tr = raycast_manager.get_transformation(close_point.tr_key); + Vec3d hit_world = hit_tr * close_point.point; + Vec3d offset_world = hit_world - point; // vector in world + Vec3d offset_volume = to_world.inverse().linear() * offset_world; + return offset_volume; + } + + // It is no neccesary to move with origin by very small value + const RaycastManager::Hit &hit = *hit_opt; + if (hit.squared_distance < EPSILON) + return {}; + Transform3d hit_tr = raycast_manager.get_transformation(hit.tr_key); + Vec3d hit_world = hit_tr * hit.position; + Vec3d offset_world = hit_world - point; // vector in world + // TIP: It should be close to only z move + Vec3d offset_volume = to_world.inverse().linear() * offset_world; + return offset_volume; +} + +std::optional calc_distance(const GLVolume &gl_volume, RaycastManager &raycaster, GLCanvas3D &canvas) +{ + const ModelObject *object = get_model_object(gl_volume, canvas.get_model()->objects); + if (object == nullptr) + return {}; + + const ModelInstance *instance = get_model_instance(gl_volume, *object); + const ModelVolume *volume = get_model_volume(gl_volume, *object); + assert(instance != nullptr && volume != nullptr); + if (object == nullptr || instance == nullptr || volume == nullptr) + return {}; + + if (volume->is_the_only_one_part()) + return {}; + + if (!volume->emboss_shape.has_value()) + return {}; + + RaycastManager::AllowVolumes condition = create_condition(object->volumes, volume->id()); + //RaycastManager::Meshes meshes = create_meshes(canvas, condition); + //raycaster.actualize(*instance, &condition, &meshes); + return calc_distance(gl_volume, raycaster, &condition, volume->emboss_shape->fix_3mf_tr); +} + +std::optional calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, + const RaycastManager::ISkip *condition, const std::optional& fix) { + Transform3d w = gl_volume.world_matrix(); + if (fix.has_value()) + w = w * fix->inverse(); + Vec3d p = w.translation(); + Vec3d dir = -get_z_base(w); + auto hit_opt = raycaster.closest_hit(p, dir, condition); + if (!hit_opt.has_value()) + return {}; + + const RaycastManager::Hit &hit = *hit_opt; + // NOTE: hit.squared_distance is in volume space not world + + const Transform3d &tr = raycaster.get_transformation(hit.tr_key); + Vec3d hit_world = tr * hit.position; + Vec3d p_to_hit = hit_world - p; + double distance_sq = p_to_hit.squaredNorm(); + + // too small distance is calculated as zero distance + if (distance_sq < ::surface_distance_sq.min) + return {}; + + // check maximal distance + const BoundingBoxf3& bb = gl_volume.bounding_box(); + double max_squared_distance = std::max(std::pow(2 * bb.size().z(), 2), ::surface_distance_sq.max); + if (distance_sq > max_squared_distance) + return {}; + + // calculate sign + float sign = (p_to_hit.dot(dir) > 0)? 1.f : -1.f; + + // distiguish sign + return sign * static_cast(sqrt(distance_sq)); +} + +std::optional calc_angle(const Selection &selection) +{ + const GLVolume *gl_volume = selection.get_first_volume(); + assert(gl_volume != nullptr); + if (gl_volume == nullptr) + return {}; + + Transform3d to_world = gl_volume->world_matrix(); + const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects); + assert(volume != nullptr); + assert(volume->emboss_shape.has_value()); + if (volume == nullptr || !volume->emboss_shape.has_value() || !volume->emboss_shape->fix_3mf_tr) + return Emboss::calc_up(to_world, UP_LIMIT); + + // exist fix matrix and must be applied before calculation + to_world = to_world * volume->emboss_shape->fix_3mf_tr->inverse(); + return Emboss::calc_up(to_world, UP_LIMIT); +} + +Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs &objects) +{ + Transform3d res = gl_volume.world_matrix(); + + const ModelVolume *mv = get_model_volume(gl_volume, objects); + if (!mv) + return res; + + const std::optional &es = mv->emboss_shape; + if (!es.has_value()) + return res; + + const std::optional &fix = es->fix_3mf_tr; + if (!fix.has_value()) + return res; + + return res * fix->inverse(); +} + +Transform3d world_matrix_fixed(const Selection &selection) +{ + const GLVolume *gl_volume = get_selected_gl_volume(selection); + assert(gl_volume != nullptr); + if (gl_volume == nullptr) + return Transform3d::Identity(); + + return world_matrix_fixed(*gl_volume, selection.get_model()->objects); +} + +void selection_transform(Selection &selection, const std::function &selection_transformation_fnc) +{ + if (const Transform3d *fix = get_fix_transformation(selection); fix != nullptr) { + // NOTE: need editable gl volume .. can't use selection.get_first_volume() + GLVolume *gl_volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix(); + gl_volume->set_volume_transformation(volume_tr * fix->inverse()); + selection.setup_cache(); + + selection_transformation_fnc(); + + volume_tr = gl_volume->get_volume_transformation().get_matrix(); + gl_volume->set_volume_transformation(volume_tr * (*fix)); + selection.setup_cache(); + } else { + selection_transformation_fnc(); + } + + if (selection.is_single_full_instance()) + selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); +} + +bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas, const std::optional &wanted_up_limit) +{ + GLVolume *gl_volume_ptr = get_selected_gl_volume(canvas); + if (gl_volume_ptr == nullptr) + return false; + GLVolume &gl_volume = *gl_volume_ptr; + + const ModelObjectPtrs &objects = canvas.get_model()->objects; + ModelObject *object_ptr = get_model_object(gl_volume, objects); + assert(object_ptr != nullptr); + if (object_ptr == nullptr) + return false; + ModelObject &object = *object_ptr; + + const ModelInstance *instance_ptr = get_model_instance(gl_volume, object); + assert(instance_ptr != nullptr); + if (instance_ptr == nullptr) + return false; + const ModelInstance &instance = *instance_ptr; + + ModelVolume *volume_ptr = get_model_volume(gl_volume, object); + assert(volume_ptr != nullptr); + if (volume_ptr == nullptr) + return false; + ModelVolume &volume = *volume_ptr; + + // Calculate new volume transformation + Transform3d volume_tr = volume.get_matrix(); + std::optional fix; + if (volume.emboss_shape.has_value()) { + fix = volume.emboss_shape->fix_3mf_tr; + if (fix.has_value()) + volume_tr = volume_tr * fix->inverse(); + } + + Transform3d instance_tr = instance.get_matrix(); + Transform3d instance_tr_inv = instance_tr.inverse(); + Transform3d world_tr = instance_tr * volume_tr; // without sla !!! + std::optional current_angle; + if (wanted_up_limit.has_value()) + current_angle = Emboss::calc_up(world_tr, *wanted_up_limit); + + Vec3d world_position = gl_volume.world_matrix()*Vec3d::Zero(); + + assert(camera.get_type() == Camera::EType::Perspective || + camera.get_type() == Camera::EType::Ortho); + Vec3d wanted_direction = (camera.get_type() == Camera::EType::Perspective) ? + Vec3d(camera.get_position() - world_position).normalized() : + (-camera.get_dir_forward()); + + Transform3d new_volume_tr = get_volume_transformation(world_tr, wanted_direction, world_position, + fix, instance_tr_inv, current_angle, wanted_up_limit); + + Selection &selection = canvas.get_selection(); + if (is_embossed_object(selection)) { + // transform instance instead of volume + Transform3d new_instance_tr = instance_tr * new_volume_tr * volume.get_matrix().inverse(); + gl_volume.set_instance_transformation(new_instance_tr); + + // set same transformation to other instances when instance is embossed object + if (selection.is_single_full_instance()) + selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); + } else { + // write result transformation + gl_volume.set_volume_transformation(new_volume_tr); + } + + if (volume.type() == ModelVolumeType::MODEL_PART) { + object.invalidate_bounding_box(); + object.ensure_on_bed(); + } + + canvas.do_rotate(L("Face the camera")); + wxGetApp().obj_manipul()->set_dirty(); + return true; +} + +void do_local_z_rotate(Selection &selection, double relative_angle) { + assert(!selection.is_empty()); + if(selection.is_empty()) return; + + bool is_single_volume = selection.volumes_count() == 1; + assert(is_single_volume); + if (!is_single_volume) return; + + // Fix angle for mirrored volume + bool is_mirrored = false; + const GLVolume* gl_volume = selection.get_first_volume(); + if (gl_volume != nullptr) { + const ModelInstance *instance = get_model_instance(*gl_volume, selection.get_model()->objects); + bool is_instance_mirrored = (instance != nullptr)? has_reflection(instance->get_matrix()) : false; + if (is_embossed_object(selection)) { + is_mirrored = is_instance_mirrored; + } else { + const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects); + if (volume != nullptr) + is_mirrored = is_instance_mirrored != has_reflection(volume->get_matrix()); + } + } + if (is_mirrored) + relative_angle *= -1; + + selection.setup_cache(); + auto selection_rotate_fnc = [&selection, &relative_angle](){ + selection.rotate(Vec3d(0., 0., relative_angle), get_drag_transformation_type(selection)); + }; + selection_transform(selection, selection_rotate_fnc); +} + +void do_local_z_move(Selection &selection, double relative_move) { + assert(!selection.is_empty()); + if (selection.is_empty()) return; + + selection.setup_cache(); + auto selection_translate_fnc = [&selection, relative_move]() { + Vec3d translate = Vec3d::UnitZ() * relative_move; + selection.translate(translate, TransformationType::Local); + }; + selection_transform(selection, selection_translate_fnc); +} + +TransformationType get_drag_transformation_type(const Selection &selection) +{ + return is_embossed_object(selection) ? + TransformationType::Instance_Relative_Joint : + TransformationType::Local_Relative_Joint; +} + +void dragging_rotate_gizmo(double gizmo_angle, std::optional& current_angle, std::optional &start_angle, Selection &selection) +{ + if (!start_angle.has_value()) + // create cache for initial angle + start_angle = current_angle.value_or(0.f); + + gizmo_angle -= PI / 2; // Grabber is upward + + double new_angle = gizmo_angle + *start_angle; + + const GLVolume *gl_volume = selection.get_first_volume(); + assert(gl_volume != nullptr); + if (gl_volume == nullptr) + return; + + bool is_volume_mirrored = has_reflection(gl_volume->get_volume_transformation().get_matrix()); + bool is_instance_mirrored = has_reflection(gl_volume->get_instance_transformation().get_matrix()); + if (is_volume_mirrored != is_instance_mirrored) + new_angle = -gizmo_angle + *start_angle; + + // move to range <-M_PI, M_PI> + Geometry::to_range_pi_pi(new_angle); + + const Transform3d* fix = get_fix_transformation(selection); + double z_rotation = (fix!=nullptr) ? (new_angle - current_angle.value_or(0.f)) : // relative angle + gizmo_angle; // relativity is keep by selection cache + + auto selection_rotate_fnc = [z_rotation, &selection]() { + selection.rotate(Vec3d(0., 0., z_rotation), get_drag_transformation_type(selection)); + }; + selection_transform(selection, selection_rotate_fnc); + + // propagate angle into property + current_angle = static_cast(new_angle); + + // do not store zero + if (is_approx(*current_angle, 0.f)) + current_angle.reset(); +} + +} // namespace Slic3r::GUI + +// private implementation +namespace { + +Vec2d mouse_position(const wxMouseEvent &mouse_event){ + // wxCoord == int --> wx/types.h + Vec2i32 mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + return mouse_coord.cast(); +} + +bool start_dragging(const Vec2d &mouse_pos, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager, + const std::optional&up_limit) +{ + // selected volume + GLVolume *gl_volume_ptr = get_selected_gl_volume(canvas);//is svg_volume or text volume + if (gl_volume_ptr == nullptr) + return false; + const GLVolume &gl_volume = *gl_volume_ptr; + + // is selected volume closest hovered?//modify by qds + //const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + /*if (int hovered_idx = canvas.get_first_hover_volume_idx(); hovered_idx < 0) + return false; + else if (auto hovered_idx_ = static_cast(hovered_idx); + hovered_idx_ >= gl_volumes.size() || gl_volumes[hovered_idx_] != gl_volume_ptr) + return false;*/ + + const ModelObjectPtrs &objects = canvas.get_model()->objects; + const ModelObject *mo = get_model_object(gl_volume, objects); + if (mo == nullptr) + return false; + + const ModelInstance *instance = get_model_instance(gl_volume, *mo); + const ModelVolume * volume = get_model_volume(gl_volume, *mo); + assert(instance != nullptr && volume != nullptr); + if (mo == nullptr || instance == nullptr || volume == nullptr) + return false; + + // allowed drag&drop by canvas for object + if (volume->is_the_only_one_part()) + return false; + if (raycast_manager.get_meshes().size() == 0) { + return false; + } + RaycastManager::AllowVolumes condition = create_condition(mo->volumes, volume->id()); + //RaycastManager::Meshes meshes = create_meshes(canvas, condition); + // initialize raycasters + // INFO: It could slows down for big objects + // (may be move to thread and do not show drag until it finish) + //raycast_manager.actualize(*instance, &condition, &meshes); + + // world_matrix_fixed() without sla shift + Transform3d to_world = world_matrix_fixed(gl_volume, objects); + + // zero point of volume in world coordinate system + Vec3d volume_center = to_world.translation(); + // screen coordinate of volume center + auto coor = CameraUtils::project(camera, volume_center); + Vec2d mouse_offset = coor.cast() - mouse_pos; + Vec2d mouse_offset_without_sla_shift = mouse_offset; + if (double sla_shift = gl_volume.get_sla_shift_z(); !is_approx(sla_shift, 0.)) { + Transform3d to_world_without_sla_move = instance->get_matrix() * volume->get_matrix(); + if (volume->emboss_shape.has_value() && volume->emboss_shape->fix_3mf_tr.has_value()) + to_world_without_sla_move = to_world_without_sla_move * (*volume->emboss_shape->fix_3mf_tr); + // zero point of volume in world coordinate system + volume_center = to_world_without_sla_move.translation(); + // screen coordinate of volume center + coor = CameraUtils::project(camera, volume_center); + mouse_offset_without_sla_shift = coor.cast() - mouse_pos; + } + + Transform3d volume_tr = gl_volume.get_volume_transformation().get_matrix(); + + // fix baked transformation from .3mf store process + if (const std::optional &es_opt = volume->emboss_shape; es_opt.has_value()) { + const std::optional &fix = es_opt->fix_3mf_tr; + if (fix.has_value()) + volume_tr = volume_tr * fix->inverse(); + } + + Transform3d instance_tr = instance->get_matrix(); + Transform3d instance_tr_inv = instance_tr.inverse(); + Transform3d world_tr = instance_tr * volume_tr; + std::optional start_angle; + if (up_limit.has_value()) { + start_angle = Emboss::calc_up(world_tr, *up_limit); + if (start_angle.has_value() && has_reflection(world_tr)) + start_angle = -(*start_angle); + } + + std::optional start_distance; + if (!volume->emboss_shape->projection.use_surface) + start_distance = calc_distance(gl_volume, raycast_manager, &condition, volume->emboss_shape->fix_3mf_tr); + surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, + gl_volume_ptr, condition, start_angle, + start_distance, true, mouse_offset_without_sla_shift}; + + // disable moving with object by mouse + //canvas.enable_moving(false);//modify by qds + //canvas.enable_picking(false); + return true; +} + +Transform3d get_volume_transformation( + Transform3d world, // from volume + const Vec3d& world_dir, // wanted new direction + const Vec3d& world_position, // wanted new position + const std::optional& fix, // [optional] fix matrix + // Invers transformation of text volume instance + // Help convert world transformation to instance space + const Transform3d& instance_inv, + // initial rotation in Z axis + std::optional current_angle, + const std::optional &up_limit) +{ + auto world_linear = world.linear(); + // Calculate offset: transformation to wanted position + { + // Reset skew of the text Z axis: + // Project the old Z axis into a new Z axis, which is perpendicular to the old XY plane. + Vec3d old_z = world_linear.col(2); + Vec3d new_z = world_linear.col(0).cross(world_linear.col(1)); + world_linear.col(2) = new_z * (old_z.dot(new_z) / new_z.squaredNorm()); + } + + Vec3d text_z_world = world_linear.col(2); // world_linear * Vec3d::UnitZ() + auto z_rotation = Eigen::Quaternion::FromTwoVectors(text_z_world, world_dir); + Transform3d world_new = z_rotation * world; + auto world_new_linear = world_new.linear(); + + // Fix direction of up vector to zero initial rotation + if(up_limit.has_value()){ + Vec3d z_world = world_new_linear.col(2); + z_world.normalize(); + Vec3d wanted_up = Emboss::suggest_up(z_world, *up_limit); + + Vec3d y_world = world_new_linear.col(1); + auto y_rotation = Eigen::Quaternion::FromTwoVectors(y_world, wanted_up); + + world_new = y_rotation * world_new; + world_new_linear = world_new.linear(); + } + + // Edit position from right + Transform3d volume_new{Eigen::Translation(instance_inv * world_position)}; + volume_new.linear() = instance_inv.linear() * world_new_linear; + + // Check that transformation matrix is valid transformation + assert(volume_new.matrix()(0, 0) == volume_new.matrix()(0, 0)); // Check valid transformation not a NAN + if (volume_new.matrix()(0, 0) != volume_new.matrix()(0, 0)) + return Transform3d::Identity(); + + // Check that scale in world did not changed + assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitY()).has_value()); + assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitZ()).has_value()); + + // fix baked transformation from .3mf store process + if (fix.has_value()) + volume_new = volume_new * (*fix); + + // apply move in Z direction and rotation by up vector + Emboss::apply_transformation(current_angle, {}, volume_new); + + return volume_new; +} + +bool dragging(const Vec2d &mouse_pos, + const Camera &camera, + SurfaceDrag &surface_drag, + GLCanvas3D &canvas, + const RaycastManager &raycast_manager, + const std::optional &up_limit) +{ + Vec2d offseted_mouse = mouse_pos + surface_drag.mouse_offset_without_sla_shift; + std::optional hit = ray_from_camera( + raycast_manager, offseted_mouse, camera, &surface_drag.condition); + + surface_drag.exist_hit = hit.has_value(); + if (!hit.has_value()) { + // cross hair need redraw + canvas.set_as_dirty(); + return true; + } + + const ModelVolume *volume = get_model_volume(*surface_drag.gl_volume, canvas.get_model()->objects); + std::optional fix; + if (volume !=nullptr && + volume->emboss_shape.has_value() && + volume->emboss_shape->fix_3mf_tr.has_value()) + fix = volume->emboss_shape->fix_3mf_tr; + Transform3d volume_new = get_volume_transformation(surface_drag.world, hit->normal, hit->position, + fix, surface_drag.instance_inv, surface_drag.start_angle, up_limit); + + // Update transformation for all instances + for (GLVolume *vol : canvas.get_volumes().volumes) { + if (vol->object_idx() != surface_drag.gl_volume->object_idx() || + vol->volume_idx() != surface_drag.gl_volume->volume_idx()) + continue; + vol->set_volume_transformation(volume_new); + } + + canvas.set_as_dirty(); + // Show current position in manipulation panel + wxGetApp().obj_manipul()->set_dirty(); + return true; +} + +bool is_embossed_object(const Selection &selection) +{ + assert(selection.volumes_count() == 1); + return selection.is_single_full_object() || selection.is_single_full_instance(); +} + +const Transform3d *get_fix_transformation(const Selection &selection) { + const GLVolume *gl_volume = get_selected_gl_volume(selection); + assert(gl_volume != nullptr); + if (gl_volume == nullptr) + return nullptr; + + const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects); + assert(volume != nullptr); + if (volume == nullptr) + return nullptr; + + const std::optional &es = volume->emboss_shape; + if (!volume->emboss_shape.has_value()) + return nullptr; + if (!es->fix_3mf_tr.has_value()) + return nullptr; + return &(*es->fix_3mf_tr); +} + +} // namespace diff --git a/src/slic3r/GUI/SurfaceDrag.hpp b/src/slic3r/GUI/SurfaceDrag.hpp new file mode 100644 index 0000000..a57142a --- /dev/null +++ b/src/slic3r/GUI/SurfaceDrag.hpp @@ -0,0 +1,170 @@ +#ifndef slic3r_SurfaceDrag_hpp_ +#define slic3r_SurfaceDrag_hpp_ + +#include +#include "libslic3r/Point.hpp" // Vec2d, Transform3d +#include "slic3r/Utils/RaycastManager.hpp" +#include "wx/event.h" // wxMouseEvent +#include + +namespace Slic3r { +class GLVolume; +class ModelVolume; +} // namespace Slic3r + +namespace Slic3r::GUI { +class GLCanvas3D; +class Selection; +class TransformationType; +struct Camera; + +// Data for drag&drop over surface with mouse +struct SurfaceDrag +{ + // hold screen coor offset of cursor from object center + Vec2d mouse_offset; + + // Start dragging text transformations to world + Transform3d world; + + // Invers transformation of text volume instance + // Help convert world transformation to instance space + Transform3d instance_inv; + + // Dragged gl volume + GLVolume *gl_volume; + + // condition for raycaster + RaycastManager::AllowVolumes condition; + + // initial rotation in Z axis of volume + std::optional start_angle; + + // initial Z distance from surface + std::optional start_distance; + + // Flag whether coordinate hit some volume + bool exist_hit = true; + + // hold screen coor offset of cursor from object center without SLA shift + Vec2d mouse_offset_without_sla_shift; +}; + +// Limit direction of up vector on model +// Between side and top surface +constexpr double UP_LIMIT = 0.9; + +/// +/// Mouse event handler, when move(drag&drop) volume over model surface +/// NOTE: Dragged volume has to be selected. And also has to be hovered on start of dragging. +/// +/// Contain type of event and mouse position +/// Actual viewport of camera +/// Structure which keep information about dragging +/// Contain gl_volumes and selection +/// AABB trees for raycast in object +/// Refresh state inside of function +/// When set than use correction of up vector +/// True when event is processed otherwise false +bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager, + const std::optional&up_limit = {}); + +/// +/// Calculate translation of volume onto surface of model +/// +/// Must contain only one selected volume, Transformation of current instance +/// AABB trees of object. Actualize object +/// Offset of volume in volume coordinate +std::optional calc_surface_offset(const Selection &selection, RaycastManager &raycast_manager); + +/// +/// Calculate distance by ray to surface of object in emboss direction +/// +/// Define embossed volume +/// Way to cast rays to object +/// Contain model +/// Calculated distance from surface +std::optional calc_distance(const GLVolume &gl_volume, RaycastManager &raycaster, GLCanvas3D &canvas); +std::optional calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, + const RaycastManager::ISkip *condition, const std::optional& fix); + +/// +/// Calculate up vector angle +/// +/// Calculation of angle is for selected one volume +/// +std::optional calc_angle(const Selection &selection); + +/// +/// Get transformation to world +/// - use fix after store to 3mf when exists +/// +/// Scene volume +/// To identify Model volume with fix transformation +/// Fixed Transformation of gl_volume +Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs& objects); + +/// +/// Get transformation to world +/// - use fix after store to 3mf when exists +/// NOTE: when not one volume selected return identity +/// +/// Selected volume +/// Fixed Transformation of selected volume in selection +Transform3d world_matrix_fixed(const Selection &selection); + +/// +/// Wrap function around selection transformation to apply fix transformation +/// Fix transformation is needed because of (store/load) volume (to/from) 3mf +/// +/// Selected gl volume will be modified +/// Function modified Selection transformation +void selection_transform(Selection &selection, const std::function& selection_transformation_fnc); + +/// +/// Apply camera direction for emboss direction +/// +/// Define view vector +/// Containe Selected ModelVolume to modify orientation +/// [Optional]Limit for direction of up vector +/// True when apply change otherwise false +bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas, const std::optional &wanted_up_limit = {}); + +/// +/// Rotation around z Axis(emboss direction) +/// +/// Selected volume for rotation +/// Relative angle to rotate around emboss direction +void do_local_z_rotate(Selection &selection, double relative_angle); + +/// +/// Translation along local z Axis (emboss direction) +/// +/// Selected volume for translate +/// Relative move along emboss direction +void do_local_z_move(Selection &selection, double relative_move); + +/// +/// Distiguish between object and volume +/// Differ in possible transformation type +/// +/// Contain selected volume/object +/// Transformation to use +TransformationType get_drag_transformation_type(const Selection &selection); + +/// +/// On dragging rotate gizmo func +/// Transform GLVolume from selection +/// +/// GLGizmoRotate::get_angle() +/// In/Out current angle visible in UI +/// Cache for start dragging angle +/// Selected only Actual embossed volume +void dragging_rotate_gizmo(double gizmo_angle, std::optional& current_angle, std::optional &start_angle, Selection &selection); + +} // namespace Slic3r::GUI +#endif // slic3r_SurfaceDrag_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bb10e0b..a01c61d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -876,6 +876,7 @@ void TabPrinter::init_options_list() case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coFloatsOrPercents: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; // QDS case coEnums: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; @@ -1450,44 +1451,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) new_conf.set_key_value("support_style", new ConfigOptionEnum(smsDefault)); m_config_manipulation.apply(m_config, &new_conf); } -#if 0 - // QDS popup a message to ask the user to set optimum parameters for tree support - if (opt_key == "support_type" || opt_key == "support_style") { - if (is_tree_slim(m_config->opt_enum("support_type"), m_config->opt_enum("support_style")) && - !(m_config->opt_float("support_top_z_distance") == 0 && m_config->opt_int("support_interface_top_layers") == 0 && m_config->opt_int("tree_support_wall_count") == 2)) { - wxString msg_text = _L("We have added an experimental style \"Tree Slim\" that features smaller support volume but weaker strength.\n" - "We recommend using it with: 0 interface layers, 0 top distance, 2 walls."); - msg_text += "\n\n" + _L("Change these settings automatically? \n" - "Yes - Change these settings automatically\n" - "No - Do not change these settings for me"); - MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_YES) { - new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_interface_top_layers", new ConfigOptionInt(0)); - new_conf.set_key_value("tree_support_wall_count", new ConfigOptionInt(2)); - m_config_manipulation.apply(m_config, &new_conf); - } - wxGetApp().plater()->update(); - } else if ((m_config->opt_enum("support_type")==stTreeAuto && (m_config->opt_enum("support_style")==smsTreeStrong || m_config->opt_enum("support_style") == smsTreeHybrid)) && - !((m_config->opt_float("support_top_z_distance") >=0.1 || is_support_filament(m_config->opt_int("support_interface_filament") - 1)) - && m_config->opt_int("support_interface_top_layers") >1) ) { - wxString msg_text = _L("For \"Tree Strong\" and \"Tree Hybrid\" styles, we recommend the following settings: at least 2 interface layers, at least 0.1mm top z distance or using support materials on interface."); - msg_text += "\n\n" + _L("Change these settings automatically? \n" - "Yes - Change these settings automatically\n" - "No - Do not change these settings for me"); - MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_YES) { - if (!is_support_filament(m_config->opt_int("support_interface_filament") - 1) && m_config->opt_float("support_top_z_distance") < 0.1) - new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0.2)); - new_conf.set_key_value("support_interface_top_layers", new ConfigOptionInt(2)); - m_config_manipulation.apply(m_config, &new_conf); - } - wxGetApp().plater()->update(); - } - } -#endif + // QDS popup a message to ask the user to set optimum parameters for support interface if support materials are used if (opt_key == "support_interface_filament") { int interface_filament_id = m_config->opt_int("support_interface_filament") - 1; // the displayed id is based from 1, while internal id is based from 0 @@ -1516,25 +1480,37 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) auto max_layer_height_from_nozzle=wxGetApp().preset_bundle->full_config().option("max_layer_height")->values; auto layer_height_floor = *std::min_element(min_layer_height_from_nozzle.begin(), min_layer_height_from_nozzle.end()); auto layer_height_ceil = *std::max_element(max_layer_height_from_nozzle.begin(), max_layer_height_from_nozzle.end()); - bool exceed_minimum_flag = m_config->opt_float("layer_height") < layer_height_floor; - bool exceed_maximum_flag = m_config->opt_float("layer_height") > layer_height_ceil; + float layer_height = m_config->opt_float("layer_height"); + bool exceed_minimum_flag = layer_height < layer_height_floor; + bool exceed_maximum_flag = layer_height > layer_height_ceil; if (exceed_maximum_flag || exceed_minimum_flag) { - wxString msg_text = _(L("Layer height exceeds the limit in Printer Settings -> Extruder -> Layer height limits ,this may cause printing quality issues.")); - msg_text += "\n\n" + _(L("Adjust to the set range automatically? \n")); - MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); - dialog.SetButtonLabel(wxID_YES, _L("Adjust")); - dialog.SetButtonLabel(wxID_NO, _L("Ignore")); - auto answer = dialog.ShowModal(); - auto new_conf = *m_config; - if (answer == wxID_YES) { - if (exceed_maximum_flag) - new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_ceil)); - if (exceed_minimum_flag) - new_conf.set_key_value("layer_height",new ConfigOptionFloat(layer_height_floor)); + if(layer_height < EPSILON){ + wxString msg_text = _(L("Layer height is too small.\nIt will set to min_layer_height\n")); + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxOK); + dialog.SetButtonLabel(wxID_OK, _L("OK")); + dialog.ShowModal(); + auto new_conf = *m_config; + new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_floor)); m_config_manipulation.apply(m_config, &new_conf); } - wxGetApp().plater()->update(); + else{ + wxString msg_text = _(L("Layer height exceeds the limit in Printer Settings -> Extruder -> Layer height limits ,this may cause printing quality issues.")); + msg_text += "\n\n" + _(L("Adjust to the set range automatically? \n")); + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); + dialog.SetButtonLabel(wxID_YES, _L("Adjust")); + dialog.SetButtonLabel(wxID_NO, _L("Ignore")); + auto answer = dialog.ShowModal(); + auto new_conf = *m_config; + if (answer == wxID_YES) { + if (exceed_maximum_flag) + new_conf.set_key_value("layer_height", new ConfigOptionFloat(layer_height_ceil)); + if (exceed_minimum_flag) + new_conf.set_key_value("layer_height",new ConfigOptionFloat(layer_height_floor)); + m_config_manipulation.apply(m_config, &new_conf); + } + wxGetApp().plater()->update(); + } } } @@ -1894,6 +1870,15 @@ void Tab::update_preset_description_line() m_parent->Layout(); } +static void validate_custom_note_cb(Tab *tab, ConfigOptionsGroupShp opt_group, const t_config_option_key &opt_key, const boost::any &value) +{ + if (boost::any_cast(value).size() > 40 * 1024) { + MessageDialog dialog(static_cast(wxGetApp().mainframe), _L("The notes are too large, and may not be synchronized to the cloud. Please keep it within 40k."), + "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + } +} + void Tab::update_frequently_changed_parameters() { const bool is_fff = supports_printer_technology(ptFFF); @@ -1942,15 +1927,13 @@ void TabPrint::build() //y32 optgroup->append_single_option_line("seam_position", "print-settings/seam"); optgroup->append_single_option_line("seam_gap", "print-settings/seam"); - optgroup->append_single_option_line("seam_slope_type"); optgroup->append_single_option_line("seam_slope_conditional"); optgroup->append_single_option_line("scarf_angle_threshold"); - optgroup->append_single_option_line("seam_slope_start_height"); optgroup->append_single_option_line("seam_slope_entire_loop"); - optgroup->append_single_option_line("seam_slope_min_length"); optgroup->append_single_option_line("seam_slope_steps"); optgroup->append_single_option_line("seam_slope_inner_walls"); optgroup->append_single_option_line("wipe_speed", "print-settings/seam"); + optgroup->append_single_option_line("role_base_wipe_speed", "print-settings/seam"); optgroup = page->new_optgroup(L("Precision"), L"param_precision"); optgroup->append_single_option_line("slice_closing_radius"); @@ -1969,6 +1952,7 @@ void TabPrint::build() optgroup->append_single_option_line("ironing_speed"); optgroup->append_single_option_line("ironing_flow"); optgroup->append_single_option_line("ironing_spacing"); + optgroup->append_single_option_line("ironing_inset"); optgroup->append_single_option_line("ironing_direction"); optgroup = page->new_optgroup(L("Wall generator"), L"param_wall"); @@ -1982,22 +1966,21 @@ void TabPrint::build() optgroup->append_single_option_line("min_feature_size"); optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("wall_sequence"); - optgroup->append_single_option_line("is_infill_first"); - //y32 + optgroup->append_single_option_line("wall_sequence","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("is_infill_first","print-settings/quality-advance-settings"); + //y32 optgroup->append_single_option_line("bridge_flow","print-settings/bridge"); optgroup->append_single_option_line("thick_bridges","print-settings/bridge"); - optgroup->append_single_option_line("top_solid_infill_flow_ratio"); - optgroup->append_single_option_line("initial_layer_flow_ratio"); - optgroup->append_single_option_line("top_one_wall_type"); - optgroup->append_single_option_line("top_area_threshold"); - optgroup->append_single_option_line("only_one_wall_first_layer"); - optgroup->append_single_option_line("detect_overhang_wall"); - //1.9.5 - optgroup->append_single_option_line("smooth_speed_discontinuity_area"); - optgroup->append_single_option_line("smooth_coefficient"); - optgroup->append_single_option_line("reduce_crossing_wall"); - optgroup->append_single_option_line("max_travel_detour_distance"); + optgroup->append_single_option_line("top_solid_infill_flow_ratio","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("initial_layer_flow_ratio","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("top_one_wall_type","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("top_area_threshold","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("only_one_wall_first_layer","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("detect_overhang_wall","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("smooth_speed_discontinuity_area","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("smooth_coefficient","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("reduce_crossing_wall","print-settings/quality-advance-settings"); + optgroup->append_single_option_line("max_travel_detour_distance","print-settings/quality-advance-settings"); page = add_options_page(L("Strength"), "empty"); optgroup = page->new_optgroup(L("Walls"), L"param_wall"); @@ -2113,7 +2096,6 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Raft"), L"param_raft"); optgroup->append_single_option_line("raft_layers"); optgroup->append_single_option_line("raft_contact_distance"); - optgroup->append_single_option_line("raft_first_layer_density"); optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); //y32 @@ -2125,8 +2107,8 @@ void TabPrint::build() //QDS optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - //1.9.5 - optgroup->append_single_option_line("raft_first_layer_expansion"); // not only for raft, but for support too + optgroup->append_single_option_line("raft_first_layer_density"); // not only for raft, but for support too + optgroup->append_single_option_line("raft_first_layer_expansion"); // not only for raft, but for support too optgroup->append_single_option_line("tree_support_wall_count"); //y32 optgroup->append_single_option_line("support_top_z_distance", "print-settings/support"); @@ -2150,12 +2132,12 @@ void TabPrint::build() optgroup->append_single_option_line("max_bridge_length", "print-settings/support"); optgroup->append_single_option_line("independent_support_layer_height", "print-settings/support"); - //1.9.5 optgroup = page->new_optgroup(L("Tree Support"), L"param_advanced"); //y32 optgroup->append_single_option_line("tree_support_branch_distance", "print-settings/support"); optgroup->append_single_option_line("tree_support_branch_diameter", "print-settings/support"); optgroup->append_single_option_line("tree_support_branch_angle", "print-settings/support"); + optgroup->append_single_option_line("tree_support_branch_diameter_angle", "print-settings/support"); page = add_options_page(L("Others"), "advanced"); optgroup = page->new_optgroup(L("Bed adhension"), L"param_adhension"); @@ -2215,6 +2197,7 @@ void TabPrint::build() option.opt.is_code = true; option.opt.height = 15; optgroup->append_single_option_line(option); +optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; optgroup = page->new_optgroup(L("Notes"),"note"); optgroup->label_width = 0; @@ -2222,6 +2205,7 @@ void TabPrint::build() option.opt.full_width = true; option.opt.height = 25; optgroup->append_single_option_line(option); + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; #if 0 //page = add_options_page(L("Dependencies"), "advanced.png"); @@ -2267,7 +2251,7 @@ void TabPrint::update_description_lines() void TabPrint::toggle_options() { if (!m_active_page) return; - // QDS: whether the preset is QIDI Lab printer + // QDS: whether the preset is QIDI Tech printer if (m_preset_bundle) { bool is_QDT_printer = m_preset_bundle->printers.get_edited_preset().is_qdt_vendor_preset(m_preset_bundle); m_config_manipulation.set_is_QDT_Printer(is_QDT_printer); @@ -2905,6 +2889,10 @@ static void validate_custom_gcode_cb(Tab* tab, ConfigOptionsGroupShp opt_group, tab->validate_custom_gcodes_was_shown = !Tab::validate_custom_gcode(opt_group->title, boost::any_cast(value)); tab->update_dirty(); tab->on_value_change(opt_key, value); + if (boost::any_cast(value).size() > 40 * 1024) { + MessageDialog dialog(static_cast(wxGetApp().mainframe), _L("Custom G-code files are too large, and may not be synchronized to the cloud. Please keep it within 40k."), "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + } } void TabFilament::add_filament_overrides_page() @@ -3051,7 +3039,9 @@ void TabFilament::build() optgroup->append_single_option_line("enable_pressure_advance"); optgroup->append_single_option_line("pressure_advance"); optgroup->append_single_option_line("filament_density"); + optgroup->append_single_option_line("filament_shrink"); optgroup->append_single_option_line("filament_cost"); + //QDS optgroup->append_single_option_line("temperature_vitrification"); Line line = { L("Recommended nozzle temperature"), L("Recommended nozzle temperature range of this filament. 0 means no set") }; @@ -3081,7 +3071,12 @@ void TabFilament::build() //w34 optgroup = page->new_optgroup(L("Bed temperature"), L"param_temperature"); - line = { L("Cool Plate / PLA Plate"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Cool Plate") }; + line = {L("QIDI Cool Plate SuperTack"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the QIDI Cool Plate SuperTack")}; + line.append_option(optgroup->get_option("supertack_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("supertack_plate_temp")); + optgroup->append_line(line); + + line = { L("Cool Plate"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Cool Plate") }; line.append_option(optgroup->get_option("cool_plate_temp_initial_layer")); line.append_option(optgroup->get_option("cool_plate_temp")); optgroup->append_line(line); @@ -3135,6 +3130,13 @@ void TabFilament::build() optgroup = page->new_optgroup(L("Volumetric speed limitation"), L"param_volumetric_speed"); optgroup->append_single_option_line("filament_max_volumetric_speed"); + // QDS + optgroup = page->new_optgroup(L("Filament scarf seam settings"), L"param_volumetric_speed"); + optgroup->append_single_option_line("filament_scarf_seam_type"); + optgroup->append_single_option_line("filament_scarf_height"); + optgroup->append_single_option_line("filament_scarf_gap"); + optgroup->append_single_option_line("filament_scarf_length"); + //line = { "", "" }; //line.full_width = 1; //line.widget = [this](wxWindow* parent) { @@ -3175,6 +3177,7 @@ void TabFilament::build() optgroup->append_single_option_line("enable_overhang_bridge_fan", "print-settings/auto-cooling"); optgroup->append_single_option_line("overhang_fan_threshold", "print-settings/auto-cooling"); + optgroup->append_single_option_line("overhang_threshold_participating_cooling", "print-settings/auto-cooling"); optgroup->append_single_option_line("overhang_fan_speed", "print-settings/auto-cooling"); optgroup = page->new_optgroup(L("Auxiliary part cooling fan"), L"param_cooling_fan"); @@ -3233,7 +3236,7 @@ void TabFilament::build() option.opt.full_width = true; option.opt.height = notes_field_height; optgroup->append_single_option_line(option); - + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; //QDS #if 0 //page = add_options_page(L("Dependencies"), "advanced"); @@ -3351,8 +3354,7 @@ void TabFilament::toggle_options() //w19 //w34 auto support_multi_bed_types = cfg.opt_bool("support_multi_bed_types"); - for (auto el : - {"cool_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp", "eng_plate_temp_initial_layer","hot_plate_temp_initial_layer","hot_plate_temp"}) + for (auto el : {"supertack_plate_temp", "supertack_plate_temp_initial_layer", "cool_plate_temp", "cool_plate_temp_initial_layer", "eng_plate_temp", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer","hot_plate_temp" }) toggle_line(el, support_multi_bed_types); } @@ -3609,6 +3611,7 @@ void TabPrinter::build_fff() optgroup = page->new_optgroup(L("Extruder Clearance")); optgroup->append_single_option_line("extruder_clearance_max_radius"); + optgroup->append_single_option_line("extruder_clearance_dist_to_rod"); optgroup->append_single_option_line("extruder_clearance_height_to_rod"); optgroup->append_single_option_line("extruder_clearance_height_to_lid"); @@ -3725,7 +3728,7 @@ void TabPrinter::build_fff() option.opt.full_width = true; option.opt.height = notes_field_height; optgroup->append_single_option_line(option); - + optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) { validate_custom_note_cb(this, optgroup, opt_key, value); }; build_unregular_pages(true); } @@ -4193,7 +4196,8 @@ void TabPrinter::toggle_options() if (!m_active_page || m_presets->get_edited_preset().printer_technology() == ptSLA) return; - //QDS: whether the preset is QIDI Lab printer + auto config_mode = wxGetApp().get_mode(); + //QDS: whether the preset is QIDI Tech printer bool is_QDT_printer = false; if (m_preset_bundle) { is_QDT_printer = m_preset_bundle->printers.get_edited_preset().is_qdt_vendor_preset(m_preset_bundle); @@ -4220,7 +4224,7 @@ void TabPrinter::toggle_options() // Disable silent mode for non-marlin firmwares. toggle_option("silent_mode", is_marlin_flavor); //QDS: extruder clearance of QDT printer can't be edited. - for (auto el : { "extruder_clearance_max_radius", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" }) + for (auto el : {"extruder_clearance_max_radius", "extruder_clearance_dist_to_rod", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid"}) toggle_option(el, !is_QDT_printer); } @@ -4237,8 +4241,13 @@ void TabPrinter::toggle_options() bool have_retract_length = m_config->opt_float("retraction_length", i) > 0; //QDS - for (auto el : {"extruder_type" , "nozzle_diameter", "extruder_offset"}) + for (auto el : { "extruder_type" , "nozzle_diameter"}) { toggle_option(el, !is_QDT_printer, i); + } + + toggle_option("extruder_type", !is_QDT_printer, i); + toggle_option("nozzle_diameter", !is_QDT_printer || config_mode == ConfigOptionMode::comDevelop, i); + toggle_option("extruder_offset", !is_QDT_printer || config_mode == ConfigOptionMode::comDevelop, i); bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); toggle_option("retract_length",!use_firmware_retraction, i); @@ -4374,7 +4383,6 @@ void Tab::load_current_preset() update_btns_enabling(); - update(); if (m_type == Slic3r::Preset::TYPE_PRINTER) { // For the printer profile, generate the extruder pages. if (preset.printer_technology() == ptFFF) @@ -4382,6 +4390,7 @@ void Tab::load_current_preset() else wxGetApp().obj_list()->update_objects_list_filament_column(1); } + update(); // Reload preset pages with the new configuration values. reload_config(); @@ -5205,7 +5214,29 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_proje //QDS record current preset name std::string curr_preset_name = m_presets->get_edited_preset().name; - + auto curr_preset = m_presets->get_edited_preset(); + std::map extra_map; + { + bool is_configed_by_QDT = PresetUtils::system_printer_bed_model(curr_preset).size() > 0; + if (is_configed_by_QDT) {//only record svg + if (wxGetApp().app_config->has_section("user_qdt_svg_list")) { + auto user_qdt_svg_list = wxGetApp().app_config->get_section("user_qdt_svg_list"); + if (user_qdt_svg_list.size() > 0 && user_qdt_svg_list[curr_preset_name].size() > 0) { + extra_map["bed_custom_texture"] = ConfigOptionString(user_qdt_svg_list[curr_preset_name]); + } + } + } + else {//for cutom machine + auto bed_model_path = wxGetApp().plater()->get_partplate_list().get_bed3d()->get_model_filename(); + if (!bed_model_path.empty()) { + extra_map["bed_custom_model"] = bed_model_path; + } + auto logo = wxGetApp().plater()->get_partplate_list().get_logo_texture_filename(); + if (!logo.empty()) { + extra_map["bed_custom_texture"] = logo; + } + } + } bool exist_preset = false; Preset* new_preset = m_presets->find_preset(name, false); if (new_preset) { @@ -5213,7 +5244,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_proje } // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini - m_presets->save_current_preset(name, detach, save_to_project); + m_presets->save_current_preset(name, detach, save_to_project, nullptr, &extra_map); //QDS create new settings new_preset = m_presets->find_preset(name, false, true); @@ -5585,22 +5616,50 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { bool is_configed_by_QDT = PresetUtils::system_printer_bed_model(m_preset_bundle->printers.get_edited_preset()).size() > 0; + ConfigOptionString custom_texture = *m_config->option("bed_custom_texture"); + PresetBundle & preset_bundle = *wxGetApp().preset_bundle; + auto cur_preset_name = preset_bundle.printers.get_edited_preset().name; + if (is_configed_by_QDT && wxGetApp().app_config->has_section("user_qdt_svg_list")) { + auto user_qdt_svg_list = wxGetApp().app_config->get_section("user_qdt_svg_list"); + if (user_qdt_svg_list.size() > 0 && user_qdt_svg_list[cur_preset_name].size() > 0) { + custom_texture = ConfigOptionString(user_qdt_svg_list[cur_preset_name]); + } + } BedShapeDialog dlg(this); - dlg.build_dialog(*m_config->option("printable_area"), - *m_config->option("bed_custom_texture"), + dlg.build_dialog(*m_config->option("printable_area"), custom_texture, *m_config->option("bed_custom_model") , !is_configed_by_QDT); - if (dlg.ShowModal() == wxID_OK && !is_configed_by_QDT) { + if (dlg.ShowModal() == wxID_OK) { if (dlg.get_valid()) { - const std::vector &shape = dlg.get_shape(); - const std::string & custom_texture = dlg.get_custom_texture(); - const std::string & custom_model = dlg.get_custom_model(); - if (!shape.empty()) { - load_key_value("printable_area", shape); + std::string custom_texture = dlg.get_custom_texture(); + if (is_configed_by_QDT) { + {//save to user_qdt_svg_list + if (!wxGetApp().app_config->has_section("user_qdt_svg_list")) { + std::map data; + data[cur_preset_name] = custom_texture; + wxGetApp().app_config->set_section("user_qdt_svg_list", data); + } else { + auto data = wxGetApp().app_config->get_section("user_qdt_svg_list"); + auto data_modify = const_cast *>(&data); + (*data_modify)[cur_preset_name] = custom_texture; + wxGetApp().app_config->set_section("user_qdt_svg_list", *data_modify); + } + } load_key_value("bed_custom_texture", custom_texture); - load_key_value("bed_custom_model", custom_model); update_changed_ui(); + } else { + const std::vector &shape = dlg.get_shape(); + const std::string & custom_model = dlg.get_custom_model(); + if (!shape.empty()) { + load_key_value("printable_area", shape); + load_key_value("bed_custom_texture", custom_texture); + load_key_value("bed_custom_model", custom_model); + update_changed_ui(); + } } - + if (custom_texture == "") { + wxGetApp().plater()->get_partplate_list().update_logo_texture_filename(""); + } + } else { show_error(m_parent, _L("Invalid input.")); } diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 15d0b39..be1cd5c 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1925,6 +1925,7 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) } m_show_all_presets = new wxCheckBox(this, wxID_ANY, _L("Show all presets (including incompatible)")); + m_show_all_presets->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#000000"))); m_show_all_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { bool show_all = m_show_all_presets->GetValue(); for (auto preset_combos : m_preset_combos) { diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index 2a8b980..70ed375 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -930,7 +930,7 @@ bool GuideFrame::run() int main_frame_display_index = wxDisplay::GetFromWindow(wxGetApp().mainframe); int guide_display_index = wxDisplay::GetFromWindow(this); if (main_frame_display_index != guide_display_index) { - wxDisplay display = wxDisplay(main_frame_display_index); + wxDisplay display(wxGetApp().mainframe); wxRect screenRect = display.GetGeometry(); int guide_x = screenRect.x + (screenRect.width - this->GetSize().GetWidth()) / 2; int guide_y = screenRect.y + (screenRect.height - this->GetSize().GetHeight()) / 2; @@ -1139,7 +1139,7 @@ int GuideFrame::LoadProfile() wxString strVendor = from_u8(iter->path().string()).BeforeLast('.'); strVendor = strVendor.AfterLast( '\\'); - strVendor = strVendor.AfterLast('\/'); + strVendor = strVendor.AfterLast('/'); wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower(); if (w2s(strVendor) == PresetBundle::QDT_BUNDLE && strExtension.CmpNoCase("json") == 0) @@ -1158,7 +1158,7 @@ int GuideFrame::LoadProfile() //cout << iter->path().string() << endl; wxString strVendor = from_u8(iter->path().string()).BeforeLast('.'); strVendor = strVendor.AfterLast( '\\'); - strVendor = strVendor.AfterLast('\/'); + strVendor = strVendor.AfterLast('/'); wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower(); if (w2s(strVendor) != PresetBundle::QDT_BUNDLE && strExtension.CmpNoCase("json")==0) @@ -1467,7 +1467,7 @@ std::string GuideFrame::w2s(wxString sSrc) void GuideFrame::GetStardardFilePath(std::string &FilePath) { StrReplace(FilePath, "\\", w2s(wxString::Format("%c", boost::filesystem::path::preferred_separator))); - StrReplace(FilePath, "\/", w2s(wxString::Format("%c", boost::filesystem::path::preferred_separator))); + StrReplace(FilePath, "/", w2s(wxString::Format("%c", boost::filesystem::path::preferred_separator))); } // bool GuideFrame::LoadFile(std::string jPath, std::string &sContent) diff --git a/src/slic3r/GUI/WebUserLoginDialog.hpp b/src/slic3r/GUI/WebUserLoginDialog.hpp index 19d991f..c212b30 100644 --- a/src/slic3r/GUI/WebUserLoginDialog.hpp +++ b/src/slic3r/GUI/WebUserLoginDialog.hpp @@ -71,7 +71,6 @@ namespace Slic3r { wxString TargetUrl; wxWebView* m_browser; - std::string m_AutotestToken; #if wxUSE_WEBVIEW_IE wxMenuItem* m_script_object_el; diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index dafe542..c55d52d 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -41,12 +41,22 @@ WebViewPanel::WebViewPanel(wxWindow *parent) m_Region = wxGetApp().app_config->get_country_code(); m_loginstatus = -1; - wxString UrlLeft = wxString::Format("file://%s/web/homepage3/left.html", from_u8(resources_dir())); + // Connect the webview events + Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::OnNavigationRequest, this); + Bind(wxEVT_WEBVIEW_NAVIGATED, &WebViewPanel::OnNavigationComplete, this); + Bind(wxEVT_WEBVIEW_LOADED, &WebViewPanel::OnDocumentLoaded, this); + Bind(wxEVT_WEBVIEW_TITLE_CHANGED, &WebViewPanel::OnTitleChanged, this); + Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::OnError, this); + Bind(wxEVT_WEBVIEW_NEWWINDOW, &WebViewPanel::OnNewWindow, this); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::OnScriptMessage, this); + Bind(EVT_RESPONSE_MESSAGE, &WebViewPanel::OnScriptResponseMessage, this); + + wxString UrlLeft = wxString::Format("file://%s/web/homepage3/left.html", from_u8(resources_dir())); wxString UrlRight = wxString::Format("file://%s/web/homepage3/home.html", from_u8(resources_dir())); wxString strlang = wxGetApp().current_language_code_safe(); - if (strlang != "") - { + if (strlang != "") + { UrlLeft = wxString::Format("file://%s/web/homepage3/left.html?lang=%s", from_u8(resources_dir()), strlang); UrlRight = wxString::Format("file://%s/web/homepage3/home.html?lang=%s", from_u8(resources_dir()), strlang); } @@ -96,22 +106,7 @@ WebViewPanel::WebViewPanel(wxWindow *parent) //Create Webview Panel m_home_web = new wxBoxSizer(wxHORIZONTAL); - // Create the webview - m_browser = WebView::CreateWebView(this, UrlRight); - if (m_browser == nullptr) { - wxLogError("Could not init m_browser"); - return; - } - - m_browserMW = WebView::CreateWebView(this, "about:blank"); - if (m_browserMW == nullptr) { - wxLogError("Could not init m_browserMW"); - return; - } - m_browserMW->Hide(); - SetMakerworldModelID(""); - m_onlinefirst = false; - + // LeftMenu webview m_leftfirst = false; m_browserLeft = WebView::CreateWebView(this, UrlLeft); if (m_browserLeft == nullptr) { @@ -122,9 +117,38 @@ WebViewPanel::WebViewPanel(wxWindow *parent) m_browserLeft->SetMinSize(wxSize(FromDIP(224), -1)); m_browserLeft->SetMaxSize(wxSize(FromDIP(224), -1)); + + // Create the webview + m_browser = WebView::CreateWebView(this, UrlRight); + if (m_browser == nullptr) { + wxLogError("Could not init m_browser"); + return; + } + + // Makerworld webview + m_browserMW = WebView::CreateWebView(this, "about:blank"); + if (m_browserMW == nullptr) { + wxLogError("Could not init m_browserMW"); + return; + } + m_browserMW->Hide(); + SetMakerworldModelID(""); + m_onlinefirst = false; + + // PrintHistory webview + m_browserPH = WebView::CreateWebView(this, "about:blank"); + if (m_browserPH == nullptr) { + wxLogError("Could not init m_browserPH"); + return; + } + m_browserPH->Hide(); + SetPrintHistoryTaskID(0); + m_printhistoryfirst = false; + m_home_web->Add(m_browserLeft, 0, wxEXPAND | wxALL, 0); m_home_web->Add(m_browser, 1, wxEXPAND | wxALL, 0); m_home_web->Add(m_browserMW, 1, wxEXPAND | wxALL, 0); + m_home_web->Add(m_browserPH, 1, wxEXPAND | wxALL, 0); topsizer->Add(m_home_web,1, wxEXPAND | wxALL, 0); @@ -201,16 +225,6 @@ WebViewPanel::WebViewPanel(wxWindow *parent) #endif //QDT_RELEASE_TO_PUBLIC - // Connect the webview events - Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::OnNavigationRequest, this); - Bind(wxEVT_WEBVIEW_NAVIGATED, &WebViewPanel::OnNavigationComplete, this); - Bind(wxEVT_WEBVIEW_LOADED, &WebViewPanel::OnDocumentLoaded, this); - Bind(wxEVT_WEBVIEW_TITLE_CHANGED, &WebViewPanel::OnTitleChanged, this); - Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::OnError, this); - Bind(wxEVT_WEBVIEW_NEWWINDOW, &WebViewPanel::OnNewWindow, this); - Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::OnScriptMessage, this); - Bind(EVT_RESPONSE_MESSAGE, &WebViewPanel::OnScriptResponseMessage, this); - // Connect the menu events Bind(wxEVT_MENU, &WebViewPanel::OnViewSourceRequest, this, viewSource->GetId()); Bind(wxEVT_MENU, &WebViewPanel::OnViewTextRequest, this, viewText->GetId()); @@ -277,6 +291,9 @@ void WebViewPanel::ResetWholePage() m_Region = tmp_Region; + //loginstatus + m_loginstatus = -1; + //left if (m_browserLeft != nullptr && m_leftfirst) m_browserLeft->Reload(); @@ -291,6 +308,19 @@ void WebViewPanel::ResetWholePage() //online SetMakerworldModelID(""); m_onlinefirst = false; + + //PrintHistory + SetPrintHistoryTaskID(0); + m_printhistoryfirst = false; +} + +wxString WebViewPanel::MakeDisconnectUrl(std::string MenuName) +{ + wxString UrlDisconnect = wxString::Format("file://%s/web/homepage3/disconnect.html?menu=%s", from_u8(resources_dir()), MenuName); + wxString strlang = wxGetApp().current_language_code_safe(); + if (strlang != "") { UrlDisconnect = wxString::Format("file://%s/web/homepage3/disconnect.html?menu=%s&lang=%s", from_u8(resources_dir()), MenuName, strlang); } + + return UrlDisconnect; } void WebViewPanel::load_url(wxString& url) @@ -482,21 +512,32 @@ void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event) if (mainframe && mainframe->m_webview == this) Slic3r::GUI::wxGetApp().get_login_info(); - if (wxGetApp().is_user_login()) { - if (m_loginstatus != 1) - { + std::string phShow = wxGetApp().app_config->get("app", "show_print_history"); + + if (wxGetApp().is_user_login()) + { + if (m_loginstatus != 1) + { m_loginstatus = 1; if (m_onlinefirst) UpdateMakerworldLoginStatus(); } + + if (m_TaskInfo == "" && m_browser && phShow != "false") + { + SetPrintHistoryTaskID(0); + ShowUserPrintTask(true); + } } else { if (m_loginstatus != 0) { m_loginstatus = 0; if (m_onlinefirst) SetMakerworldPageLoginStatus(false); - } + } + + if (m_TaskInfo != "" && m_browser) ShowUserPrintTask(false); } } @@ -812,7 +853,12 @@ void WebViewPanel::UpdateMakerworldLoginStatus() std::string newticket; int ret = agent->request_bind_ticket(&newticket); - if (ret==0) SetMakerworldPageLoginStatus(true, newticket); + if (ret==0) + SetMakerworldPageLoginStatus(true, newticket); + else { + wxString UrlDisconnect = MakeDisconnectUrl("online"); + m_browserMW->LoadURL(UrlDisconnect); + } } @@ -886,6 +932,77 @@ int WebViewPanel::get_model_mall_detail_url(std::string *url, std::string id) return 0; } +void WebViewPanel::ShowUserPrintTask(bool bShow) +{ + std::string phShow = wxGetApp().app_config->get("app", "show_print_history"); + if (bShow && phShow == "false") bShow = false; + + if (bShow) + { + NetworkAgent *agent = GUI::wxGetApp().getAgent(); + if (agent && agent->is_user_login()) { + static long long PrintTaskMs = 0; + + auto now = std::chrono::system_clock::now(); + long long TmpMs = std::chrono::duration_cast(now.time_since_epoch()).count(); + long long nInterval = TmpMs - PrintTaskMs; + if (nInterval < 2000) return; + PrintTaskMs = TmpMs; + + QDT::TaskQueryParams task_query_params; + task_query_params.limit = 5; + task_query_params.offset = 0; + int result = agent->get_user_tasks(task_query_params, &m_TaskInfo); + BOOST_LOG_TRIVIAL(trace) << "task_manager: get_task_list task_info=" << m_TaskInfo; + if (result == 0) { + try { + json j = json::parse(m_TaskInfo); + BOOST_LOG_TRIVIAL(trace) << "task_manager: get_task_list task count =" << j["hits"].size(); + + auto body2 = from_u8(m_TaskInfo); + body2.insert(1, "\"command\": \"printhistory_task_show\", "); + RunScript(wxString::Format("window.postMessage(%s)", body2)); + + SetLeftMenuShow("printhistory", 1); + + return; + } catch (...) {} + } + else + m_TaskInfo = ""; + } + } + else + { + //Hide Left Menu + SetLeftMenuShow("printhistory", 0); + m_TaskInfo = ""; + + //Hide WebBrowser + if (m_contentname == "printhistory") SwitchLeftMenu("home"); + + //refresh url + auto host = wxGetApp().get_model_http_url(wxGetApp().app_config->get_country_code()); + + wxString language_code = wxGetApp().current_language_code().BeforeFirst('_'); + language_code = language_code.ToStdString(); + + wxString mw_OffUrl = (boost::format("%1%%2%/studio/print-history?from=qidistudio") % host % language_code.mb_str()).str(); + wxString Finalurl = wxString::Format("%sapi/sign-out?to=%s", host, UrlEncode("about:blank")); + + m_browserPH->LoadURL(Finalurl); + SetPrintHistoryTaskID(0); + m_TaskInfo = ""; + + //First Enter False + m_printhistoryfirst = false; + } + + return; +} + + + void WebViewPanel::update_mode() { GetSizer()->Show(size_t(0), wxGetApp().app_config->get("internal_developer_mode") == "true"); @@ -924,7 +1041,9 @@ void WebViewPanel::OnNavigationRequest(wxWebViewEvent& evt) surl.EndsWith(".3mf") || surl.EndsWith(".xlsx") || surl.EndsWith(".xls") || - surl.EndsWith(".txt") + surl.EndsWith(".txt") || + surl.EndsWith("qdscfg") || + surl.EndsWith("qdsflmt") ) { wxLaunchDefaultBrowser(url); @@ -1313,24 +1432,48 @@ void WebViewPanel::OnError(wxWebViewEvent& evt) { wxString errurl = evt.GetURL(); - wxString UrlRight = wxString::Format("file://%s/web/homepage3/disconnect.html", from_u8(resources_dir())); - - wxString strlang = wxGetApp().current_language_code_safe(); - if (strlang != "") { - UrlRight = wxString::Format("file://%s/web/homepage3/disconnect.html?lang=%s", from_u8(resources_dir()), strlang); - } - - m_browserMW->LoadURL(UrlRight); + wxString UrlDisconnect = MakeDisconnectUrl("online"); + m_browserMW->LoadURL(UrlDisconnect); SetWebviewShow("online", true); SetWebviewShow("right", false); + SetWebviewShow("printhistory", false); + } + } + + if (evt.GetInt() == wxWEBVIEW_NAV_ERR_CONNECTION && evt.GetId() == m_browserPH->GetId()) { + m_print_history_LastUrl = m_browserPH->GetCurrentURL(); + + if (m_contentname == "printhistory") { + wxString errurl = evt.GetURL(); + + wxString UrlDisconnect = MakeDisconnectUrl("printhistory"); + m_browserPH->LoadURL(UrlDisconnect); + + SetWebviewShow("printhistory", true); + SetWebviewShow("online", false); + SetWebviewShow("right", false); } } UpdateState(); } -void WebViewPanel::SetMakerworldModelID(std::string ModelID) +void WebViewPanel::OpenMakerworldSearchPage(std::string KeyWord) +{ + if (KeyWord.empty()) return; + + auto host = wxGetApp().get_model_http_url(wxGetApp().app_config->get_country_code()); + + wxString language_code = wxGetApp().current_language_code().BeforeFirst('_'); + language_code = language_code.ToStdString(); + + m_online_LastUrl = (boost::format("%1%%2%/studio/webview/search?keyword=%3%&from=qidistudio") % host % language_code.mb_str() % UrlEncode(KeyWord)).str(); + + SwitchLeftMenu("online"); +} + +void WebViewPanel::SetMakerworldModelID(std::string ModelID) { auto host = wxGetApp().get_model_http_url(wxGetApp().app_config->get_country_code()); @@ -1343,6 +1486,20 @@ void WebViewPanel::SetMakerworldModelID(std::string ModelID) m_online_LastUrl = (boost::format("%1%%2%/studio/webview?from=qidistudio") % host % language_code.mb_str()).str(); } +void WebViewPanel::SetPrintHistoryTaskID(int TaskID) +{ + auto host = wxGetApp().get_model_http_url(wxGetApp().app_config->get_country_code()); + + wxString language_code = wxGetApp().current_language_code().BeforeFirst('_'); + language_code = language_code.ToStdString(); + + if (TaskID != 0) + m_print_history_LastUrl = (boost::format("%1%%2%/studio/print-history/%3%?from=qidistudio") % host % language_code.mb_str() % TaskID).str(); + else + m_print_history_LastUrl = (boost::format("%1%%2%/studio/print-history?from=qidistudio") % host % language_code.mb_str()).str(); +} + + void WebViewPanel::SwitchWebContent(std::string modelname, int refresh) { m_contentname = modelname; @@ -1358,6 +1515,8 @@ void WebViewPanel::SwitchWebContent(std::string modelname, int refresh) wxString FinalUrl = LabUrl; NetworkAgent *agent = GUI::wxGetApp().getAgent(); if (agent && agent->is_user_login()) { + std::string QIDIHost=agent->get_qiditech_host(); + std::string newticket; int ret = agent->request_bind_ticket(&newticket); if (ret == 0) GetJumpUrl(true, newticket, FinalUrl, FinalUrl); @@ -1391,6 +1550,7 @@ void WebViewPanel::SwitchWebContent(std::string modelname, int refresh) SetWebviewShow("online", true); SetWebviewShow("right", false); + SetWebviewShow("printhistory", false); GetSizer()->Layout(); @@ -1398,6 +1558,45 @@ void WebViewPanel::SwitchWebContent(std::string modelname, int refresh) wxGetApp().app_config->set_str("homepage", "online_clicked", "1"); wxGetApp().app_config->save(); wxGetApp().CallAfter([this] { ShowMenuNewTag("online", "0"); }); + + + } else if (modelname.compare("printhistory") == 0) { + + if (!m_printhistoryfirst) + { + NetworkAgent *agent = GUI::wxGetApp().getAgent(); + if (agent == nullptr) return; + + std::string QIDIHost = agent->get_qiditech_host(); + wxString FinalUrl = m_print_history_LastUrl; + std::string newticket; + int ret = agent->request_bind_ticket(&newticket); + if (ret == 0) { + GetJumpUrl(true, newticket, FinalUrl, FinalUrl); + m_browserPH->LoadURL(FinalUrl); + + m_print_history_LastUrl = ""; + m_printhistoryfirst = true; + } else { + wxString UrlDisconnect = MakeDisconnectUrl("printhistory"); + m_browserPH->LoadURL(UrlDisconnect); + } + } else { + if (m_print_history_LastUrl != "") { + m_browserPH->LoadURL(m_print_history_LastUrl); + + m_print_history_LastUrl = ""; + } else { + + } + } + + SetWebviewShow("online", false); + SetWebviewShow("right", false); + SetWebviewShow("printhistory", true); + + GetSizer()->Layout(); + } else if (modelname.compare("home") == 0 || modelname.compare("recent") == 0 || modelname.compare("manual") == 0) { if (!m_browser) return; @@ -1411,12 +1610,11 @@ void WebViewPanel::SwitchWebContent(std::string modelname, int refresh) WebView::RunScript(m_browser, strJS); - CallAfter([this]{ - SetWebviewShow("online", false); - SetWebviewShow("right", true); + SetWebviewShow("online", false); + SetWebviewShow("printhistory", false); + SetWebviewShow("right", true); - GetSizer()->Layout(); - }); + GetSizer()->Layout(); } } @@ -1499,6 +1697,7 @@ void WebViewPanel::SetLeftMenuShow(std::string menuname, int show) wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true)); WebView::RunScript(m_browserLeft, strJS); + WebView::RunScript(m_browser, strJS); } void WebViewPanel::SetWebviewShow(wxString name, bool show) @@ -1510,9 +1709,11 @@ void WebViewPanel::SetWebviewShow(wxString name, bool show) TmpWeb = m_browser; else if (name == "online") TmpWeb = m_browserMW; - - if (TmpWeb != nullptr) - { + else if (name == "printhistory") + TmpWeb = m_browserPH; + + if (TmpWeb != nullptr) + { if (show) TmpWeb->Show(); else diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 9fe8138..4809959 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -97,10 +97,14 @@ public: void ResetWholePage(); void SetMakerworldModelID(std::string ModelID); + void OpenMakerworldSearchPage(std::string KeyWord); + void SetPrintHistoryTaskID(int TaskID); void SwitchWebContent(std::string modelname, int refresh=0); void SwitchLeftMenu(std::string strMenu); void OpenOneMakerlab(std::string url); + wxString MakeDisconnectUrl(std::string MenuName); + void CheckMenuNewTag(); void ShowMenuNewTag(std::string menuname, std::string show); void SetLeftMenuShow(std::string menuname, int show); @@ -120,6 +124,9 @@ public: void get_makerlab_list(std::function callback); int get_model_mall_detail_url(std::string *url, std::string id); + std::string m_TaskInfo; + void ShowUserPrintTask(bool bShow); + void UpdateMakerworldLoginStatus(); void SetMakerworldPageLoginStatus(bool login, wxString ticket = ""); @@ -136,12 +143,15 @@ private: wxWebView* m_browser; wxWebView* m_browserLeft; wxWebView * m_browserMW; + wxWebView *m_browserPH; //PrintHistory std::string m_contentname; - bool m_leftfirst; //Left First Loaded - bool m_onlinefirst; //Online Page First Load - //std::string m_online_spec_id; // Online Page Spec_ID - wxString m_online_type; //recommend & browse - wxString m_online_LastUrl; //PageLastError Url + bool m_leftfirst; //Left First Loaded + bool m_onlinefirst; //Online Page First Load + bool m_printhistoryfirst; //print history first load + //std::string m_online_spec_id; // Online Page Spec_ID + wxString m_online_type; //recommend & browse + wxString m_online_LastUrl; //PageLastError Url + wxString m_print_history_LastUrl; wxBoxSizer *bSizer_toolbar; wxButton * m_button_back; diff --git a/src/slic3r/GUI/Widgets/AMSControl.cpp b/src/slic3r/GUI/Widgets/AMSControl.cpp index 2b9a141..d4aad36 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.cpp +++ b/src/slic3r/GUI/Widgets/AMSControl.cpp @@ -43,7 +43,7 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, Ams *ams, bool remain_flag, boo else { this->ams_humidity = -1; } - + cans.clear(); for (int i = 0; i < 4; i++) { auto it = ams->trayList.find(std::to_string(i)); @@ -54,6 +54,8 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, Ams *ams, bool remain_flag, boo info.can_id = it->second->id; info.ctype = it->second->ctype; info.material_name = it->second->get_display_filament_type(); + info.cali_idx = it->second->cali_idx; + info.filament_id = it->second->setting_id; if (!it->second->color.empty()) { info.material_colour = AmsTray::decode_color(it->second->color); } else { @@ -70,18 +72,20 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, Ams *ams, bool remain_flag, boo } else { info.material_state = AMSCanType::AMS_CAN_TYPE_THIRDBRAND; } - + if (!MachineObject::is_qdt_filament(it->second->tag_uid) || !remain_flag) { info.material_remain = 100; } else { info.material_remain = it->second->remain < 0 ? 0 : it->second->remain; info.material_remain = it->second->remain > 100 ? 100 : info.material_remain; } - - + + } else { info.can_id = it->second->id; info.material_name = ""; + info.cali_idx = -1; + info.filament_id = ""; info.ctype = 0; info.material_colour = AMS_TRAY_DEFAULT_COL; info.material_state = AMSCanType::AMS_CAN_TYPE_THIRDBRAND; @@ -312,16 +316,20 @@ void AMSrefresh::DoSetSize(int x, int y, int width, int height, int sizeFlags) /************************************************* Description:AMSextruder **************************************************/ -void AMSextruderImage::TurnOn(wxColour col) +void AMSextruderImage::TurnOn(wxColour col) { - m_colour = col; - Refresh(); + if (m_colour != col) { + m_colour = col; + Refresh(); + } } -void AMSextruderImage::TurnOff() +void AMSextruderImage::TurnOff() { - m_colour = AMS_EXTRUDER_DEF_COLOUR; - Refresh(); + if (m_colour != AMS_EXTRUDER_DEF_COLOUR) { + m_colour = AMS_EXTRUDER_DEF_COLOUR; + Refresh(); + } } void AMSextruderImage::msw_rescale() @@ -429,16 +437,22 @@ void AMSextruder::create(wxWindow *parent, wxWindowID id, const wxPoint &pos, co void AMSextruder::OnVamsLoading(bool load, wxColour col) { - m_vams_loading = load; - if (load)m_current_colur = col; - Refresh(); + if (m_vams_loading != load) { + m_vams_loading = load; + if (load) m_current_colur = col; + //m_current_colur = col; + Refresh(); + } } void AMSextruder::OnAmsLoading(bool load, wxColour col /*= AMS_CONTROL_GRAY500*/) { - m_ams_loading = load; - if (load)m_current_colur = col; - Refresh(); + if (m_ams_loading != load) { + m_ams_loading = load; + //m_current_colur = col; + if (load) m_current_colur = col; + Refresh(); + } } void AMSextruder::paintEvent(wxPaintEvent& evt) @@ -542,9 +556,12 @@ AMSVirtualRoad::~AMSVirtualRoad() {} void AMSVirtualRoad::OnVamsLoading(bool load, wxColour col) { - m_vams_loading = load; - if (load)m_current_color = col; - Refresh(); + if (m_vams_loading != load) { + m_vams_loading = load; + if (load)m_current_color = col; + //m_current_color = col; + Refresh(); + } } void AMSVirtualRoad::create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size) @@ -840,9 +857,12 @@ void AMSLib::render_extra_text(wxDC& dc) void AMSLib::render_generic_text(wxDC &dc) { bool show_k_value = true; - if (m_obj && (m_obj->cali_version >= 0) && (abs(m_info.k - 0) < 1e-3)) { + if (m_info.material_name.empty()) { show_k_value = false; } + else if (m_info.cali_idx == -1 || (m_obj && (CalibUtils::get_selected_calib_idx(m_obj->pa_calib_tab, m_info.cali_idx) == -1))) { + get_default_k_n_value(m_info.filament_id, m_info.k, m_info.n); + } auto tmp_lib_colour = m_info.material_colour; change_the_opacity(tmp_lib_colour); @@ -1324,9 +1344,14 @@ void AMSLib::Update(Caninfo info, bool refresh) if (info.material_colour.Alpha() != 0 && info.material_colour.Alpha() != 255 && info.material_colour.Alpha() != 254 && m_info.material_colour != info.material_colour) { transparent_changed = true; } - m_info = info; - Layout(); - if (refresh) Refresh(); + + if (m_info == info) { + //todo + } else { + m_info = info; + Layout(); + if (refresh) Refresh(); + } } wxColour AMSLib::GetLibColour() { return m_info.material_colour; } @@ -1431,6 +1456,10 @@ void AMSRoad::create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const void AMSRoad::Update(AMSinfo amsinfo, Caninfo info, int canindex, int maxcan) { + if (amsinfo == m_amsinfo && m_info == info && m_canindex == canindex) { + return; + } + m_amsinfo = amsinfo; m_info = info; m_canindex = canindex; @@ -1449,17 +1478,22 @@ void AMSRoad::Update(AMSinfo amsinfo, Caninfo info, int canindex, int maxcan) void AMSRoad::OnVamsLoading(bool load, wxColour col /*= AMS_CONTROL_GRAY500*/) { - m_vams_loading = load; - if(load)m_road_color = col; - Refresh(); + if (m_vams_loading != load) { + m_vams_loading = load; + if (load) m_road_color = col; + //m_road_color = col; + Refresh(); + } } void AMSRoad::SetPassRoadColour(wxColour col) { m_road_color = col; } void AMSRoad::SetMode(AMSRoadMode mode) { - m_rode_mode = mode; - Refresh(); + if (m_rode_mode != mode) { + m_rode_mode = mode; + Refresh(); + } } void AMSRoad::paintEvent(wxPaintEvent &evt) @@ -2005,9 +2039,10 @@ void AmsCans::Update(AMSinfo info) Canrefreshs *refresh = m_can_refresh_list[i]; if (i < m_can_count) { refresh->canrefresh->Update(info.ams_id, info.cans[i]); - refresh->canrefresh->Show(); + if (!refresh->canrefresh->IsShown()) { refresh->canrefresh->Show();} + } else { - refresh->canrefresh->Hide(); + if (refresh->canrefresh->IsShown()) { refresh->canrefresh->Hide();} } } @@ -2015,21 +2050,20 @@ void AmsCans::Update(AMSinfo info) CanLibs *lib = m_can_lib_list[i]; if (i < m_can_count) { lib->canLib->Update(info.cans[i]); - lib->canLib->Show(); + if(!lib->canLib->IsShown()) { lib->canLib->Show();} } else { - lib->canLib->Hide(); + if(lib->canLib->IsShown()) { lib->canLib->Hide(); } } } if (m_ams_model == AMSModel::GENERIC_AMS) { for (auto i = 0; i < m_can_road_list.GetCount(); i++) { - CanRoads* road = m_can_road_list[i]; + CanRoads *road = m_can_road_list[i]; if (i < m_can_count) { road->canRoad->Update(m_info, info.cans[i], i, m_can_count); - road->canRoad->Show(); - } - else { - road->canRoad->Hide(); + if (!road->canRoad->IsShown()) { road->canRoad->Show(); } + } else { + if (road->canRoad->IsShown()) { road->canRoad->Hide(); } } } } @@ -2051,8 +2085,8 @@ void AmsCans::SelectCan(std::string canid) { for (auto i = 0; i < m_can_lib_list.GetCount(); i++) { CanLibs *lib = m_can_lib_list[i]; - if (lib->canLib->m_info.can_id == canid) { - m_canlib_selection = lib->canLib->m_can_index; + if (lib->canLib->m_info.can_id == canid) { + m_canlib_selection = lib->canLib->m_can_index; } } @@ -3282,10 +3316,13 @@ void AMSControl::show_noams_mode() void AMSControl::show_auto_refill(bool show) { - m_ams_backup_tip->Show(show); - m_img_ams_backup->Show(show); - m_amswin->Layout(); - m_amswin->Fit(); + if (m_auto_reill_show != show) { + m_ams_backup_tip->Show(show); + m_img_ams_backup->Show(show); + m_amswin->Layout(); + m_amswin->Fit(); + m_auto_reill_show = show; + } } void AMSControl::show_vams(bool show) @@ -3313,6 +3350,9 @@ void AMSControl::show_vams_kn_value(bool show) void AMSControl::update_vams_kn_value(AmsTray tray, MachineObject* obj) { + auto last_k_value = m_vams_info.k; + auto last_n_value = m_vams_info.n; + m_vams_lib->m_obj = obj; if (obj->cali_version >= 0) { float k_value = 0; @@ -3329,11 +3369,19 @@ void AMSControl::update_vams_kn_value(AmsTray tray, MachineObject* obj) m_vams_lib->m_info.k = tray.k; m_vams_lib->m_info.n = tray.n; } + m_vams_info.material_name = tray.get_display_filament_type(); m_vams_info.material_colour = tray.get_color(); + m_vams_info.cali_idx = tray.cali_idx; + m_vams_info.filament_id = tray.setting_id; m_vams_lib->m_info.material_name = tray.get_display_filament_type(); m_vams_lib->m_info.material_colour = tray.get_color(); - m_vams_lib->Refresh(); + m_vams_lib->m_info.cali_idx = tray.cali_idx; + m_vams_lib->m_info.filament_id = tray.setting_id; + + if (last_k_value != m_vams_info.k || last_n_value != m_vams_info.n) { + m_vams_lib->Refresh(); + } } void AMSControl::reset_vams() @@ -3342,8 +3390,12 @@ void AMSControl::reset_vams() m_vams_lib->m_info.n = 0; m_vams_lib->m_info.material_name = wxEmptyString; m_vams_lib->m_info.material_colour = AMS_CONTROL_WHITE_COLOUR; + m_vams_lib->m_info.cali_idx = -1; + m_vams_lib->m_info.filament_id = ""; m_vams_info.material_name = wxEmptyString; m_vams_info.material_colour = AMS_CONTROL_WHITE_COLOUR; + m_vams_info.cali_idx = -1; + m_vams_info.filament_id = ""; m_vams_lib->Refresh(); } @@ -3357,7 +3409,7 @@ void AMSControl::UpdateAms(std::vector info, bool is_reset) m_button_area->Fit(); // update item - m_ams_info = info; + if (m_ams_model == AMSModel::GENERIC_AMS){ m_ams_cans_list = m_ams_generic_cans_list; } @@ -3365,20 +3417,24 @@ void AMSControl::UpdateAms(std::vector info, bool is_reset) m_ams_cans_list = m_ams_extra_cans_list; } - if (info.size() > 1) { - m_simplebook_amsitems->Show(); - m_amswin->Layout(); - m_amswin->Fit(); - SetSize(m_amswin->GetSize()); - SetMinSize(m_amswin->GetSize()); - } else { - m_simplebook_amsitems->Hide(); - m_amswin->Layout(); - m_amswin->Fit(); - SetSize(m_amswin->GetSize()); - SetMinSize(m_amswin->GetSize()); + if (m_ams_info.size() != info.size()) { + if (info.size() > 1) { + m_simplebook_amsitems->Show(); + m_amswin->Layout(); + m_amswin->Fit(); + SetSize(m_amswin->GetSize()); + SetMinSize(m_amswin->GetSize()); + } else { + m_simplebook_amsitems->Hide(); + m_amswin->Layout(); + m_amswin->Fit(); + SetSize(m_amswin->GetSize()); + SetMinSize(m_amswin->GetSize()); + } } + m_ams_info = info; + for (auto i = 0; i < m_ams_item_list.GetCount(); i++) { AmsItems *item = m_ams_item_list[i]; if (i < info.size() && info.size() > 1) { @@ -3751,9 +3807,7 @@ void AMSControl::SetAmsStep(std::string ams_id, std::string canid, AMSPassRoadTy if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2) { cans->amsCans->SetAmsStep(canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_1); cans->amsCans->SetAmsStep(canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_2); - if (m_current_show_ams == ams_id) { - m_extruder->OnAmsLoading(true, cans->amsCans->GetTagColr(canid)); - } + if (m_current_show_ams == ams_id) { m_extruder->OnAmsLoading(true, cans->amsCans->GetTagColr(canid)); } } if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3) { diff --git a/src/slic3r/GUI/Widgets/AMSControl.hpp b/src/slic3r/GUI/Widgets/AMSControl.hpp index fb5f5e3..723d1d4 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.hpp +++ b/src/slic3r/GUI/Widgets/AMSControl.hpp @@ -142,9 +142,26 @@ struct Caninfo AMSCanType material_state; int ctype=0; int material_remain = 100; + int cali_idx = -1; + std::string filament_id; float k = 0.0f; float n = 0.0f; std::vector material_cols; + + bool operator==(const Caninfo &other) const + { + return can_id == other.can_id && + material_name == other.material_name && + material_colour == other.material_colour && + material_state == other.material_state && + ctype == other.ctype && + material_remain == other.material_remain && + cali_idx == other.cali_idx && + filament_id == other.filament_id && + k == other.k && + n == other.n && + material_cols == other.material_cols; + } }; struct AMSinfo @@ -158,6 +175,13 @@ public: int curreent_filamentstep; int ams_humidity = 0; + bool operator==(const AMSinfo &other) const + { + return ams_id == other.ams_id && cans == other.cans && + current_can_id == other.current_can_id && current_step == other.current_step && current_action == other.current_action && + curreent_filamentstep == other.curreent_filamentstep && ams_humidity == other.ams_humidity; + } + bool parse_ams_info(MachineObject* obj, Ams *ams, bool remain_flag = false, bool humidity_flag = false); }; @@ -597,6 +621,7 @@ protected: wxBoxSizer* m_vams_sizer = {nullptr}; wxBoxSizer* m_sizer_vams_tips = {nullptr}; + bool m_auto_reill_show = {false}; Label* m_ams_backup_tip = {nullptr}; Label* m_ams_tip = {nullptr}; diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp index 33ba144..1ae26a4 100644 --- a/src/slic3r/GUI/Widgets/Button.cpp +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -76,15 +76,19 @@ bool Button::SetFont(const wxFont& font) void Button::SetIcon(const wxString& icon) { + auto tmpBitmap = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt()); if (!icon.IsEmpty()) { //QDS set button icon default size to 20 - this->active_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt()); + if (!tmpBitmap.bmp().IsSameAs(this->active_icon.bmp())) { + this->active_icon = tmpBitmap; + Refresh(); + } } else { this->active_icon = ScalableBitmap(); + Refresh(); } - Refresh(); } void Button::SetInactiveIcon(const wxString &icon) diff --git a/src/slic3r/GUI/Widgets/ComboBox.cpp b/src/slic3r/GUI/Widgets/ComboBox.cpp index 5c86ae5..ebc695a 100644 --- a/src/slic3r/GUI/Widgets/ComboBox.cpp +++ b/src/slic3r/GUI/Widgets/ComboBox.cpp @@ -84,10 +84,13 @@ void ComboBox::SetSelection(int n) return; drop.SetSelection(n); SetLabel(drop.GetValue()); - if (drop.selection >= 0 && drop.iconSize.y > 0) + if (drop.selection >= 0 && drop.iconSize.y > 0 && icons[drop.selection].IsOk()) SetIcon(icons[drop.selection]); + else + SetIcon("drop_down"); } -void ComboBox::SelectAndNotify(int n) { + +void ComboBox::SelectAndNotify(int n) { SetSelection(n); sendComboBoxEvent(); } @@ -107,8 +110,10 @@ void ComboBox::SetValue(const wxString &value) { drop.SetValue(value); SetLabel(value); - if (drop.selection >= 0 && drop.iconSize.y > 0) + if (drop.selection >= 0 && drop.iconSize.y > 0 && icons[drop.selection].IsOk()) SetIcon(icons[drop.selection]); + else + SetIcon("drop_down"); } void ComboBox::SetLabel(const wxString &value) diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index a32a9f0..a554553 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -332,7 +332,8 @@ void DropDown::messureSize() wxSize size1 = text_off ? wxSize() : dc.GetMultiLineTextExtent(texts[i]); if (icons[i].IsOk()) { wxSize size2 = GetBmpSize(icons[i]); - if (size2.x > iconSize.x) iconSize = size2; + if (size2.x > iconSize.x) + iconSize = size2; if (!align_icon) { size1.x += size2.x + (text_off ? 0 : 5); } diff --git a/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp b/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp index 22beb72..de45f4a 100644 --- a/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp +++ b/src/slic3r/GUI/Widgets/ImageSwitchButton.cpp @@ -52,10 +52,11 @@ void ImageSwitchButton::SetLabels(wxString const &lbl_on, wxString const &lbl_of labels[0] = lbl_on; labels[1] = lbl_off; auto fina_txt = GetValue() ? labels[0] : labels[1]; - if (GetToolTipText() != fina_txt) + if (GetToolTipText() != fina_txt){ SetToolTip(fina_txt); - messureSize(); - Refresh(); + messureSize(); + Refresh(); + } } void ImageSwitchButton::SetImages(ScalableBitmap &img_on, ScalableBitmap &img_off) @@ -76,9 +77,11 @@ void ImageSwitchButton::SetTextColor(StateColor const &color) void ImageSwitchButton::SetValue(bool value) { - m_on_off = value; - messureSize(); - Refresh(); + if (m_on_off != value) { + m_on_off = value; + messureSize(); + Refresh(); + } } void ImageSwitchButton::SetPadding(int padding) @@ -236,16 +239,20 @@ void FanSwitchButton::SetTextColor(StateColor const& color) void FanSwitchButton::SetValue(bool value) { - m_on_off = value; - messureSize(); - Refresh(); + if (m_on_off != value) { + m_on_off = value; + messureSize(); + Refresh(); + } } void FanSwitchButton::SetPadding(int padding) { - m_padding = padding; - messureSize(); - Refresh(); + if (m_padding != padding) { + m_padding = padding; + messureSize(); + Refresh(); + } } void FanSwitchButton::messureSize() @@ -325,8 +332,10 @@ void FanSwitchButton::Rescale() void FanSwitchButton::setFanValue(int val) { - m_speed = val; - Refresh(); + if (m_speed != val) { + m_speed = val; + Refresh(); + } } void FanSwitchButton::mouseDown(wxMouseEvent& event) diff --git a/src/slic3r/GUI/Widgets/Label.cpp b/src/slic3r/GUI/Widgets/Label.cpp index 1c084a3..8f3ab4a 100644 --- a/src/slic3r/GUI/Widgets/Label.cpp +++ b/src/slic3r/GUI/Widgets/Label.cpp @@ -19,8 +19,11 @@ wxFont Label::sysFont(int size, bool bold, std::string lang_code) wxString face; if (lang_code == "ja") { - face = wxString::FromUTF8("Noto Sans JP"); - } else { + face = wxString::FromUTF8("Source Han Sans JP Normal"); + } else if (lang_code == "ko") { + face = wxString::FromUTF8("NanumGothic"); + } + else { face = wxString::FromUTF8("HarmonyOS Sans SC"); } diff --git a/src/slic3r/GUI/Widgets/ProgressDialog.hpp b/src/slic3r/GUI/Widgets/ProgressDialog.hpp index 227ad52..3b917f0 100644 --- a/src/slic3r/GUI/Widgets/ProgressDialog.hpp +++ b/src/slic3r/GUI/Widgets/ProgressDialog.hpp @@ -34,7 +34,7 @@ public: void OnPaint(wxPaintEvent &evt); virtual ~ProgressDialog(); - virtual void DoSetSize(int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO); + virtual void DoSetSize(int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO)override; bool Create(const wxString &title, const wxString &message, int maximum = 100, wxWindow *parent = NULL, int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE); virtual bool Update(int value, const wxString &newmsg = wxEmptyString, bool *skip = NULL); diff --git a/src/slic3r/GUI/Widgets/SideTools.cpp b/src/slic3r/GUI/Widgets/SideTools.cpp index dffdee0..0740eb5 100644 --- a/src/slic3r/GUI/Widgets/SideTools.cpp +++ b/src/slic3r/GUI/Widgets/SideTools.cpp @@ -273,7 +273,7 @@ SideTools::SideTools(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxBoxSizer* connection_sizer_V = new wxBoxSizer(wxVERTICAL); wxBoxSizer* connection_sizer_H = new wxBoxSizer(wxHORIZONTAL); - m_hyperlink = new wxHyperlinkCtrl(m_connection_info, wxID_ANY, _L("Failed to connect to the server"), wxT("https://wiki.qidilab.com/en/software/qidi-studio/failed-to-connect-printer"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE); + m_hyperlink = new wxHyperlinkCtrl(m_connection_info, wxID_ANY, _L("Failed to connect to the server"), wxT("https://wiki.qidi3d.com/en/software/qidi-studio/failed-to-connect-printer"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE); m_hyperlink->SetBackgroundColour(wxColour(255, 111, 0)); m_more_err_open = ScalableBitmap(this, "monitir_err_open", 16); diff --git a/src/slic3r/GUI/Widgets/TabCtrl.cpp b/src/slic3r/GUI/Widgets/TabCtrl.cpp index 71ced9a..b07edbd 100644 --- a/src/slic3r/GUI/Widgets/TabCtrl.cpp +++ b/src/slic3r/GUI/Widgets/TabCtrl.cpp @@ -108,7 +108,7 @@ int TabCtrl::AppendItem(const wxString &item, btns.push_back(btn); if (btns.size() > 1) sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0}); - sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxALL, TAB_BUTTON_SPACE * 2); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxALL, TAB_BUTTON_SPACE); sizer->AddStretchSpacer(1); relayout(); return btns.size() - 1; @@ -198,8 +198,9 @@ bool TabCtrl::IsVisible(unsigned int item) const void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags) { + auto size = GetSize(); wxWindow::DoSetSize(x, y, width, height, sizeFlags); - if (sizeFlags & wxSIZE_USE_EXISTING) return; + if (size == GetSize()) return; relayout(); } diff --git a/src/slic3r/GUI/Widgets/TempInput.cpp b/src/slic3r/GUI/Widgets/TempInput.cpp index cc095f9..3eb1032 100644 --- a/src/slic3r/GUI/Widgets/TempInput.cpp +++ b/src/slic3r/GUI/Widgets/TempInput.cpp @@ -163,26 +163,36 @@ wxString TempInput::erasePending(wxString &str) void TempInput::SetTagTemp(int temp) { - text_ctrl->SetValue(wxString::Format("%d", temp)); - messureSize(); - Refresh(); + auto tp = wxString::Format("%d", temp); + if (text_ctrl->GetValue() != tp) { + text_ctrl->SetValue(tp); + messureSize(); + Refresh(); + } } void TempInput::SetTagTemp(wxString temp) { - text_ctrl->SetValue(temp); - messureSize(); - Refresh(); + if (text_ctrl->GetValue() != temp) { + text_ctrl->SetValue(temp); + messureSize(); + Refresh(); + } } void TempInput::SetCurrTemp(int temp) { - SetLabel(wxString::Format("%d", temp)); + auto tp = wxString::Format("%d", temp); + if (GetLabel() != tp) { + SetLabel(tp); + } } void TempInput::SetCurrTemp(wxString temp) { - SetLabel(temp); + if (GetLabel() != temp) { + SetLabel(temp); + } } void TempInput::Warning(bool warn, WarningType type) @@ -262,9 +272,11 @@ void TempInput::SetMinTemp(int temp) { min_temp = temp; } void TempInput::SetLabel(const wxString &label) { - wxWindow::SetLabel(label); - messureSize(); - Refresh(); + if (label != wxWindow::GetLabel()) { + wxWindow::SetLabel(label); + messureSize(); + Refresh(); + } } void TempInput::SetTextColor(StateColor const &color) diff --git a/src/slic3r/GUI/Widgets/WebView.cpp b/src/slic3r/GUI/Widgets/WebView.cpp index dbeaff2..415d271 100644 --- a/src/slic3r/GUI/Widgets/WebView.cpp +++ b/src/slic3r/GUI/Widgets/WebView.cpp @@ -6,6 +6,7 @@ #include #include +#include #if wxUSE_WEBVIEW_EDGE #include #elif defined(__WXMAC__) @@ -64,6 +65,10 @@ public: } settings->Release(); return false; + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create web view failed."; + wxDynamicLibrary loaderDll; + loaderDll.Load("WebView2Loader.dll", wxDL_DEFAULT); } pendingUserAgent = userAgent; return true; diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 113dee3..db09b7c 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -21,7 +21,7 @@ int scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit() / 10; int ITEM_WIDTH() { return scale(30); } static const wxColour g_text_color = wxColour(107, 107, 107, 255); -#define ICON_SIZE wxSize(FromDIP(16), FromDIP(16)) +#define WIPE_TOWER_ICON_SIZE wxSize(FromDIP(16), FromDIP(16)) #define TABLE_BORDER FromDIP(28) #define HEADER_VERT_PADDING FromDIP(12) #define HEADER_BEG_PADDING FromDIP(30) @@ -271,7 +271,7 @@ void WipingPanel::create_panels(wxWindow* parent, const int num) { auto sizer = new wxBoxSizer(wxHORIZONTAL); panel->SetSizer(sizer); - wxButton* icon = new wxButton(panel, wxID_ANY, {}, wxDefaultPosition, ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW); + wxButton* icon = new wxButton(panel, wxID_ANY, {}, wxDefaultPosition, WIPE_TOWER_ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW); icon->SetBitmap(*get_extruder_color_icon(m_colours[i].GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(i + 1), FromDIP(16), FromDIP(16))); icon->SetCanFocus(false); icon_list2.push_back(icon); @@ -279,7 +279,7 @@ void WipingPanel::create_panels(wxWindow* parent, const int num) { sizer->AddSpacer(ROW_BEG_PADDING); sizer->Add(icon, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, ROW_VERT_PADDING); - for (unsigned int j = 0; j < num; ++j) { + for (unsigned int j = 0; j < (unsigned int)num; ++j) { edit_boxes[j][i]->Reparent(panel); edit_boxes[j][i]->SetBackgroundColour(panel->GetBackgroundColour()); edit_boxes[j][i]->SetFont(::Label::Body_13); @@ -402,7 +402,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con header_line_sizer->AddSpacer(HEADER_BEG_PADDING); for (unsigned int i = 0; i < m_number_of_extruders; ++i) { - wxButton* icon = new wxButton(header_line_panel, wxID_ANY, {}, wxDefaultPosition, ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW); + wxButton* icon = new wxButton(header_line_panel, wxID_ANY, {}, wxDefaultPosition, WIPE_TOWER_ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW); icon->SetBitmap(*get_extruder_color_icon(m_colours[i].GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(i + 1), FromDIP(16), FromDIP(16))); icon->SetCanFocus(false); icon_list1.push_back(icon); @@ -525,7 +525,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con add_spin_ctrl(m_new, extruders[2 * i+1]); auto hsizer = new wxBoxSizer(wxHORIZONTAL); - wxWindow* w = new wxWindow(m_page_simple, wxID_ANY, wxDefaultPosition, ICON_SIZE, wxBORDER_SIMPLE); + wxWindow* w = new wxWindow(m_page_simple, wxID_ANY, wxDefaultPosition, WIPE_TOWER_ICON_SIZE, wxBORDER_SIMPLE); w->SetCanFocus(false); w->SetBackgroundColour(m_colours[i]); hsizer->Add(w, wxALIGN_CENTER_VERTICAL); @@ -555,7 +555,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con wxSize to_text_size = dc.GetTextExtent(to_text); int base_y = (header_line_panel->GetSize().y - from_text_size.y - to_text_size.y) / 2; - int vol_width = ROW_BEG_PADDING + EDIT_BOXES_GAP / 2 + ICON_SIZE.x; + int vol_width = ROW_BEG_PADDING + EDIT_BOXES_GAP / 2 + WIPE_TOWER_ICON_SIZE.x; int base_x = (vol_width - from_text_size.x - to_text_size.x) / 2; // draw from text diff --git a/src/slic3r/GUI/calib_dlg.cpp b/src/slic3r/GUI/calib_dlg.cpp index 83510e7..2dcbf6f 100644 --- a/src/slic3r/GUI/calib_dlg.cpp +++ b/src/slic3r/GUI/calib_dlg.cpp @@ -261,7 +261,7 @@ Temp_Calibration_Dlg::Temp_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plat SetSizer(v_sizer); wxBoxSizer* choice_sizer = new wxBoxSizer(wxHORIZONTAL); - wxString m_rbFilamentTypeChoices[] = {"PLA", "ABS/ASA", "PETG", "PCTG", "TPU", "PA-CF", "PET-CF", _L("Custom")}; + wxString m_rbFilamentTypeChoices[] = {"PLA", "ABS/ASA", "PETG", "PCTG", "TPU", "TPU-AMS", "PA-CF", "PET-CF", _L("Custom")}; int m_rbFilamentTypeNChoices = sizeof(m_rbFilamentTypeChoices) / sizeof(wxString); m_rbFilamentType = new wxRadioBox(this, wxID_ANY, _L("Filament type"), wxDefaultPosition, wxDefaultSize, m_rbFilamentTypeNChoices, m_rbFilamentTypeChoices, 2, wxRA_SPECIFY_COLS); m_rbFilamentType->SetSelection(0); diff --git a/src/slic3r/GUI/wxMediaCtrl2.cpp b/src/slic3r/GUI/wxMediaCtrl2.cpp index b24bd57..5f8db75 100644 --- a/src/slic3r/GUI/wxMediaCtrl2.cpp +++ b/src/slic3r/GUI/wxMediaCtrl2.cpp @@ -1,6 +1,8 @@ #include "wxMediaCtrl2.h" #include "I18N.hpp" -#include "GUI_App.hpp" +#include "libslic3r/Utils.hpp" +#include +#include #ifdef __WIN32__ #include #include @@ -194,8 +196,9 @@ void wxMediaCtrl2::Play() { wxMediaCtrl::Play(); } void wxMediaCtrl2::Stop() { - wxMediaCtrl::Stop(); -} + wxMediaCtrl::Stop(); } + +void wxMediaCtrl2::SetIdleImage(wxString const &image) {} #ifdef __LINUX__ extern "C" int gst_qidi_last_error; @@ -230,6 +233,13 @@ wxSize wxMediaCtrl2::DoGetBestSize() const return {-1, -1}; } +void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + if (sizeFlags & wxSIZE_USE_EXISTING) return; + wxMediaCtrl_OnSize(this, m_video_size, width, height); +} + #ifdef __WIN32__ WXLRESULT wxMediaCtrl2::MSWWindowProc(WXUINT nMsg, diff --git a/src/slic3r/GUI/wxMediaCtrl2.h b/src/slic3r/GUI/wxMediaCtrl2.h index f227bdb..69fd38f 100644 --- a/src/slic3r/GUI/wxMediaCtrl2.h +++ b/src/slic3r/GUI/wxMediaCtrl2.h @@ -13,6 +13,8 @@ wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); +void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); + #ifdef __WXMAC__ class wxMediaCtrl2 : public wxWindow diff --git a/src/slic3r/GUI/wxMediaCtrl2.mm b/src/slic3r/GUI/wxMediaCtrl2.mm index 2eb09f0..9ba715f 100644 --- a/src/slic3r/GUI/wxMediaCtrl2.mm +++ b/src/slic3r/GUI/wxMediaCtrl2.mm @@ -140,6 +140,10 @@ void wxMediaCtrl2::Stop() NotifyStopped(); } +void wxMediaCtrl2::SetIdleImage(wxString const &image) +{ +} + void wxMediaCtrl2::NotifyStopped() { if (m_state != wxMEDIASTATE_STOPPED) { @@ -168,3 +172,11 @@ wxSize wxMediaCtrl2::GetVideoSize() const return {0, 0}; } } + + +void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + if (sizeFlags & wxSIZE_USE_EXISTING) return; + wxMediaCtrl_OnSize(this, m_video_size, width, height); +} diff --git a/src/slic3r/GUI/wxMediaCtrl3.cpp b/src/slic3r/GUI/wxMediaCtrl3.cpp new file mode 100644 index 0000000..d76c883 --- /dev/null +++ b/src/slic3r/GUI/wxMediaCtrl3.cpp @@ -0,0 +1,289 @@ +#include "wxMediaCtrl3.h" +#include "AVVideoDecoder.hpp" +#include "I18N.hpp" +#include "libslic3r/Utils.hpp" +#include +#include +#ifdef __WIN32__ +#include +#include +#include +#endif + +//wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); + +BEGIN_EVENT_TABLE(wxMediaCtrl3, wxWindow) + +// catch paint events +EVT_PAINT(wxMediaCtrl3::paintEvent) + +END_EVENT_TABLE() + +struct StaticQIDILib : QIDILib +{ + static StaticQIDILib &get(QIDILib *); +}; + +wxMediaCtrl3::wxMediaCtrl3(wxWindow *parent) + : wxWindow(parent, wxID_ANY) + , QIDILib(StaticQIDILib::get(this)) + , m_thread([this] { PlayThread(); }) +{ + SetBackgroundColour("#000001ff"); +} + +wxMediaCtrl3::~wxMediaCtrl3() +{ + { + std::unique_lock lk(m_mutex); + m_url.reset(new wxURI); + m_frame = wxImage(m_idle_image); + m_cond.notify_all(); + } + m_thread.join(); +} + +void wxMediaCtrl3::Load(wxURI url) +{ + std::unique_lock lk(m_mutex); + m_video_size = wxDefaultSize; + m_error = 0; + m_url.reset(new wxURI(url)); + m_cond.notify_all(); +} + +void wxMediaCtrl3::Play() +{ + std::unique_lock lk(m_mutex); + if (m_state != wxMEDIASTATE_PLAYING) { + m_state = wxMEDIASTATE_PLAYING; + wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); + event.SetId(GetId()); + event.SetEventObject(this); + wxPostEvent(this, event); + } +} + +void wxMediaCtrl3::Stop() +{ + std::unique_lock lk(m_mutex); + m_url.reset(); + m_frame = wxImage(m_idle_image); + NotifyStopped(); + m_cond.notify_all(); + Refresh(); +} + +void wxMediaCtrl3::SetIdleImage(wxString const &image) +{ + if (m_idle_image == image) + return; + m_idle_image = image; + if (m_url == nullptr) { + std::unique_lock lk(m_mutex); + m_frame = wxImage(m_idle_image); + assert(m_frame.IsOk()); + Refresh(); + } +} + +wxMediaState wxMediaCtrl3::GetState() +{ + std::unique_lock lk(m_mutex); + return m_state; +} + +int wxMediaCtrl3::GetLastError() +{ + std::unique_lock lk(m_mutex); + return m_error; +} + +wxSize wxMediaCtrl3::GetVideoSize() +{ + std::unique_lock lk(m_mutex); + return m_video_size; +} + +wxSize wxMediaCtrl3::DoGetBestSize() const +{ + return {-1, -1}; +} + +static void adjust_frame_size(wxSize & frame, wxSize const & video, wxSize const & window) +{ + if (video.x * window.y < video.y * window.x) + frame = { video.x * window.y / video.y, window.y }; + else + frame = { window.x, video.y * window.x / video.x }; +} + +void wxMediaCtrl3::paintEvent(wxPaintEvent &evt) +{ + wxPaintDC dc(this); + auto size = GetSize(); + if (size.x <= 0 || size.y <= 0) + return; + std::unique_lock lk(m_mutex); + if (!m_frame.IsOk()) + return; + auto size2 = m_frame.GetSize(); + if (size2.x != m_frame_size.x && size2.y == m_frame_size.y) + size2.x = m_frame_size.x; + auto size3 = (size - size2) / 2; + if (size2.x != size.x && size2.y != size.y) { + double scale = 1.; + if (size.x * size2.y > size.y * size2.x) { + size3 = {size.x * size2.y / size.y, size2.y}; + scale = double(size.y) / size2.y; + } else { + size3 = {size2.x, size.y * size2.x / size.x}; + scale = double(size.x) / size2.x; + } + dc.SetUserScale(scale, scale); + size3 = (size3 - size2) / 2; + } + dc.DrawBitmap(m_frame, size3.x, size3.y); +} + +void wxMediaCtrl3::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + if (sizeFlags == wxSIZE_USE_EXISTING) return; + wxMediaCtrl_OnSize(this, m_video_size, width, height); + std::unique_lock lk(m_mutex); + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + Refresh(); +} + +void wxMediaCtrl3::qidi_log(void *ctx, int level, tchar const *msg2) +{ +#ifdef _WIN32 + wxString msg(msg2); +#else + wxString msg = wxString::FromUTF8(msg2); +#endif + if (level == 1) { + if (msg.EndsWith("]")) { + int n = msg.find_last_of('['); + if (n != wxString::npos) { + long val = 0; + wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx; + if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val)) { + std::unique_lock lk(ctrl->m_mutex); + ctrl->m_error = (int) val; + } + } + } else if (msg.Contains("stat_log")) { + wxCommandEvent evt(EVT_MEDIA_CTRL_STAT); + wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx; + evt.SetEventObject(ctrl); + evt.SetString(msg.Mid(msg.Find(' ') + 1)); + wxPostEvent(ctrl, evt); + } + } + BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data(); +} + +void wxMediaCtrl3::PlayThread() +{ + using namespace std::chrono_literals; + std::shared_ptr url; + std::unique_lock lk(m_mutex); + while (true) { + m_cond.wait(lk, [this, &url] { return m_url != url; }); + url = m_url; + if (url == nullptr) + continue; + if (!url->HasScheme()) + break; + lk.unlock(); + QIDI_Tunnel tunnel = nullptr; + int error = QIDI_Create(&tunnel, m_url->BuildURI().ToUTF8()); + if (error == 0) { + QIDI_SetLogger(tunnel, &wxMediaCtrl3::qidi_log, this); + error = QIDI_Open(tunnel); + if (error == 0) + error = QIDI_would_block; + } + lk.lock(); + while (error == int(QIDI_would_block)) { + m_cond.wait_for(lk, 100ms); + if (m_url != url) { + error = 1; + break; + } + lk.unlock(); + error = QIDI_StartStream(tunnel, true); + lk.lock(); + } + QIDI_StreamInfo info; + if (error == 0) + error = QIDI_GetStreamInfo(tunnel, 0, &info); + AVVideoDecoder decoder; + if (error == 0) { + decoder.open(info); + m_video_size = { info.format.video.width, info.format.video.height }; + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + NotifyStopped(); + } + QIDI_Sample sample; + while (error == 0) { + lk.unlock(); + error = QIDI_ReadSample(tunnel, &sample); + lk.lock(); + while (error == int(QIDI_would_block)) { + m_cond.wait_for(lk, 100ms); + if (m_url != url) { + error = 1; + break; + } + lk.unlock(); + error = QIDI_ReadSample(tunnel, &sample); + lk.lock(); + } + if (error == 0) { + auto frame_size = m_frame_size; + lk.unlock(); + decoder.decode(sample); +#ifdef _WIN32 + wxBitmap bm; + decoder.toWxBitmap(bm, frame_size); +#else + wxImage bm; + decoder.toWxImage(bm, frame_size); +#endif + lk.lock(); + if (m_url != url) { + error = 1; + break; + } + if (bm.IsOk()) + m_frame = bm; + CallAfter([this] { Refresh(); }); + } + } + if (tunnel) { + lk.unlock(); + QIDI_Close(tunnel); + QIDI_Destroy(tunnel); + tunnel = nullptr; + lk.lock(); + } + if (m_url == url) + m_error = error; + m_frame_size = wxDefaultSize; + m_video_size = wxDefaultSize; + NotifyStopped(); + } + +} + +void wxMediaCtrl3::NotifyStopped() +{ + m_state = wxMEDIASTATE_STOPPED; + wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); + event.SetId(GetId()); + event.SetEventObject(this); + wxPostEvent(this, event); +} diff --git a/src/slic3r/GUI/wxMediaCtrl3.h b/src/slic3r/GUI/wxMediaCtrl3.h new file mode 100644 index 0000000..b246316 --- /dev/null +++ b/src/slic3r/GUI/wxMediaCtrl3.h @@ -0,0 +1,91 @@ +// +// wxMediaCtrl3.h +// libslic3r_gui +// +// Created by cmguo on 2024/6/22. +// + +#ifndef wxMediaCtrl3_h +#define wxMediaCtrl3_h + +#include "wx/uri.h" +#include "wx/mediactrl.h" + +wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); + +void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); + +#ifdef __WXMAC__ + +#include "wxMediaCtrl2.h" +#define wxMediaCtrl3 wxMediaCtrl2 + +#else + +#define QIDI_DYNAMIC +#include +#include +#ifndef _WIN32 +#include +#endif +#include "Printer/QIDITunnel.h" + +class AVVideoDecoder; + +class wxMediaCtrl3 : public wxWindow, QIDILib +{ +public: + wxMediaCtrl3(wxWindow *parent); + + ~wxMediaCtrl3(); + + void Load(wxURI url); + + void Play(); + + void Stop(); + + void SetIdleImage(wxString const & image); + + wxMediaState GetState(); + + int GetLastError(); + + wxSize GetVideoSize(); + +protected: + DECLARE_EVENT_TABLE() + + void paintEvent(wxPaintEvent &evt); + + wxSize DoGetBestSize() const override; + + void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; + + static void qidi_log(void *ctx, int level, tchar const *msg); + + void PlayThread(); + + void NotifyStopped(); + +private: + wxString m_idle_image; + wxMediaState m_state = wxMEDIASTATE_STOPPED; + int m_error = 0; + wxSize m_video_size = wxDefaultSize; + wxSize m_frame_size = wxDefaultSize; +#ifdef _WIN32 + wxBitmap m_frame; +#else + wxImage m_frame; +#endif + + std::shared_ptr m_url; + std::mutex m_mutex; + std::condition_variable m_cond; + std::thread m_thread; +}; + +#endif + +#endif /* wxMediaCtrl3_h */ diff --git a/src/slic3r/Utils/.vscode/settings.json b/src/slic3r/Utils/.vscode/settings.json new file mode 100644 index 0000000..79d2336 --- /dev/null +++ b/src/slic3r/Utils/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "charconv": "cpp" + } +} \ No newline at end of file diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index 72777b1..346bea9 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -21,14 +21,39 @@ static const std::string temp_gcode_path = temp_dir + "/temp.gcode"; static const std::string path = temp_dir + "/test.3mf"; static const std::string config_3mf_path = temp_dir + "/test_config.3mf"; -static std::string MachineBedTypeString[5] = { +static std::string MachineBedTypeString[6] = { "auto", "pc", "ep", "pei", - "pte" + "pte", + "suprtack" }; +void get_default_k_n_value(const std::string &filament_id, float &k, float &n) +{ + if (filament_id.compare("GFG00") == 0) { + // PETG + k = 0.04; + n = 1.0; + } else if (filament_id.compare("GFB00") == 0 || filament_id.compare("GFB50") == 0) { + // ABS + k = 0.04; + n = 1.0; + } else if (filament_id.compare("GFU01") == 0) { + // TPU + k = 0.2; + n = 1.0; + } else if (filament_id.compare("GFB01") == 0) { + // ASA + k = 0.04; + n = 1.0; + } else { + // PLA , other + k = 0.02; + n = 1.0; + } +} std::string get_calib_mode_name(CalibMode cali_mode, int stage) { @@ -296,7 +321,7 @@ static void read_model_from_file(const std::string& input_file, Model& model) std::vector project_presets; model = Model::read_from_file(input_file, &config, &config_substitutions, strategy, &plate_data_src, &project_presets, - &is_qdt_3mf, &file_version, nullptr, nullptr, nullptr, nullptr, nullptr, plate_to_slice); + &is_qdt_3mf, &file_version, nullptr, nullptr, nullptr, plate_to_slice); model.add_default_instances(); for (auto object : model.objects) @@ -1038,9 +1063,9 @@ bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f if (params.mode == CalibMode::Calib_PA_Pattern) { ModelInstance *instance = model->objects[0]->instances[0]; - Vec3d offset = model->calib_pa_pattern->get_start_offset() + - Vec3d(model->calib_pa_pattern->handle_xy_size() / 2, -model->calib_pa_pattern->handle_xy_size() / 2 - model->calib_pa_pattern->handle_spacing(), 0); - instance->set_offset(offset); + instance->set_offset(Axis::X, model->calib_pa_pattern->get_start_offset().x() + model->calib_pa_pattern->handle_xy_size() / 2); + instance->set_offset(Axis::Y, model->calib_pa_pattern->get_start_offset().y() - model->calib_pa_pattern->handle_xy_size() / 2 - model->calib_pa_pattern->handle_spacing()); + } else if (model->objects.size() == 1) { ModelInstance *instance = model->objects[0]->instances[0]; @@ -1125,7 +1150,7 @@ bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f const ModelVolume &model_volume = *model_object.volumes[volume_idx]; for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { const ModelInstance &model_instance = *model_object.instances[instance_idx]; - glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, "volume", true, false, true); + glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, "volume", true, false, true, false); glvolume_collection.volumes.back()->set_render_color( new_color[0], new_color[1], new_color[2], new_color[3]); glvolume_collection.volumes.back()->set_color(new_color); //glvolume_collection.volumes.back()->printable = model_instance.printable; diff --git a/src/slic3r/Utils/CalibUtils.hpp b/src/slic3r/Utils/CalibUtils.hpp index 980ba30..6927aa2 100644 --- a/src/slic3r/Utils/CalibUtils.hpp +++ b/src/slic3r/Utils/CalibUtils.hpp @@ -74,5 +74,7 @@ private: static void send_to_print(const CalibInfo &calib_info, wxString& error_message, int flow_ratio_mode = 0); // 0: none 1: coarse 2: fine }; +extern void get_default_k_n_value(const std::string &filament_id, float &k, float &n); + } } \ No newline at end of file diff --git a/src/slic3r/Utils/FontUtils.cpp b/src/slic3r/Utils/FontUtils.cpp index 733038c..dfe84a6 100644 --- a/src/slic3r/Utils/FontUtils.cpp +++ b/src/slic3r/Utils/FontUtils.cpp @@ -1,4 +1,5 @@ #include "FontUtils.hpp" +//#define STB_TRUETYPE_IMPLEMENTATION #include "imgui/imstb_truetype.h" #include "libslic3r/Utils.hpp" #include @@ -58,8 +59,6 @@ std::string get_file_path(const wxFont &font) return path_str; } #endif // __APPLE__ - -using fontinfo_opt = std::optional; std::string get_human_readable_name(const wxFont &font) { @@ -72,6 +71,8 @@ std::string get_human_readable_name(const wxFont &font) } } +using fontinfo_opt = std::optional; + fontinfo_opt load_font_info(const unsigned char *data, unsigned int index) { int font_offset = stbtt_GetFontOffsetForIndex(data, index); diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 8098d95..079a968 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -70,8 +70,8 @@ struct CurlGlobalInit else message = (boost::format("use system SSL certificate: %1%") % bundle).str(); - message += "\n" + (boost::format("To manually specify the system certificate store, " - "set the %1% environment variable to the correct CA and restart the application") % SSL_CA_FILE).str(); + message += "\n" + (boost::format("To manually specify the system certificate store, " + "set the %1% environment variable to the correct CA and restart the application.") % SSL_CA_FILE).str(); } #endif // OPENSSL_CERT_OVERRIDE diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index df0bb0b..0934a70 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -75,9 +75,11 @@ func_build_logout_cmd NetworkAgent::build_logout_cmd_ptr = nullptr func_build_login_info NetworkAgent::build_login_info_ptr = nullptr; func_get_model_id_from_desgin_id NetworkAgent::get_model_id_from_desgin_id_ptr = nullptr; func_ping_bind NetworkAgent::ping_bind_ptr = nullptr; +func_bind_detect NetworkAgent::bind_detect_ptr = nullptr; +func_set_server_callback NetworkAgent::set_server_callback_ptr = nullptr; func_bind NetworkAgent::bind_ptr = nullptr; func_unbind NetworkAgent::unbind_ptr = nullptr; -func_get_qidilab_host NetworkAgent::get_qidilab_host_ptr = nullptr; +func_get_qiditech_host NetworkAgent::get_qiditech_host_ptr = nullptr; func_get_user_selected_machine NetworkAgent::get_user_selected_machine_ptr = nullptr; func_set_user_selected_machine NetworkAgent::set_user_selected_machine_ptr = nullptr; func_start_print NetworkAgent::start_print_ptr = nullptr; @@ -145,7 +147,6 @@ NetworkAgent::~NetworkAgent() BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", this %1%, network_agent=%2%, destroy_agent_ptr=%3%, ret %4%")%this %network_agent %destroy_agent_ptr %ret; } -//1.9.5 std::string NetworkAgent::get_libpath_in_current_directory(std::string library_name) { std::string lib_path; @@ -170,6 +171,7 @@ std::string NetworkAgent::get_libpath_in_current_directory(std::string library_n return lib_path; } + int NetworkAgent::initialize_network_module(bool using_backup) { //int ret = -1; @@ -195,8 +197,7 @@ int NetworkAgent::initialize_network_module(bool using_backup) ::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str()) + 1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0])); netwoking_module = LoadLibrary(lib_wstr); }*/ - //1.9.5 - if (!netwoking_module) { + if (!netwoking_module) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", try load library directly from current directory"); std::string library_path = get_libpath_in_current_directory(std::string(QIDI_NETWORK_LIBRARY)); @@ -285,10 +286,12 @@ int NetworkAgent::initialize_network_module(bool using_backup) build_logout_cmd_ptr = reinterpret_cast(get_network_function("qidi_network_build_logout_cmd")); build_login_info_ptr = reinterpret_cast(get_network_function("qidi_network_build_login_info")); ping_bind_ptr = reinterpret_cast(get_network_function("qidi_network_ping_bind")); + bind_detect_ptr = reinterpret_cast(get_network_function("qidi_network_bind_detect")); + set_server_callback_ptr = reinterpret_cast(get_network_function("qidi_network_set_server_callback")); get_model_id_from_desgin_id_ptr = reinterpret_cast(get_network_function("qidi_network_get_model_id_from_desgin_id")); bind_ptr = reinterpret_cast(get_network_function("qidi_network_bind")); unbind_ptr = reinterpret_cast(get_network_function("qidi_network_unbind")); - get_qidilab_host_ptr = reinterpret_cast(get_network_function("qidi_network_get_qidilab_host")); + get_qiditech_host_ptr = reinterpret_cast(get_network_function("qidi_network_get_qiditech_host")); get_user_selected_machine_ptr = reinterpret_cast(get_network_function("qidi_network_get_user_selected_machine")); set_user_selected_machine_ptr = reinterpret_cast(get_network_function("qidi_network_set_user_selected_machine")); start_print_ptr = reinterpret_cast(get_network_function("qidi_network_start_print")); @@ -410,7 +413,7 @@ int NetworkAgent::unload_network_module() ping_bind_ptr = nullptr; bind_ptr = nullptr; unbind_ptr = nullptr; - get_qidilab_host_ptr = nullptr; + get_qiditech_host_ptr = nullptr; get_user_selected_machine_ptr = nullptr; set_user_selected_machine_ptr = nullptr; start_print_ptr = nullptr; @@ -456,7 +459,7 @@ int NetworkAgent::unload_network_module() put_rating_picture_oss_ptr = nullptr; put_model_mall_rating_url_ptr = nullptr; get_model_mall_rating_result_ptr = nullptr; - + get_mw_user_preference_ptr = nullptr; get_mw_user_4ulist_ptr = nullptr; @@ -485,7 +488,6 @@ void* NetworkAgent::get_qidi_source_entry() memset(lib_wstr, 0, sizeof(lib_wstr)); ::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0])); source_module = LoadLibrary(lib_wstr); - //1.9.5 if (!source_module) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", try load QIDISource directly from current directory"); std::string library_path = get_libpath_in_current_directory(std::string(QIDI_SOURCE_LIBRARY)); @@ -927,11 +929,11 @@ bool NetworkAgent::is_user_login() return ret; } -int NetworkAgent::user_logout() +int NetworkAgent::user_logout(bool request) { int ret = 0; if (network_agent && user_logout_ptr) { - ret = user_logout_ptr(network_agent); + ret = user_logout_ptr(network_agent, request); if (ret) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" error: network_agent=%1%, ret=%2%")%network_agent %ret; } @@ -1025,6 +1027,30 @@ int NetworkAgent::ping_bind(std::string ping_code) return ret; } +int NetworkAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) +{ + int ret = 0; + if (network_agent && bind_detect_ptr) { + ret = bind_detect_ptr(network_agent, dev_ip, sec_link, detect); + if (ret) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" error: network_agent=%1%, ret=%2%, dev_ip=%3%") + % network_agent % ret % dev_ip; + } + return ret; +} + +int NetworkAgent::set_server_callback(OnServerErrFn fn) +{ + int ret = 0; + if (network_agent && set_server_callback_ptr) { + ret = set_server_callback_ptr(network_agent, fn); + if (ret) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(" error: network_agent=%1%, ret=%2%") + % network_agent % ret; + } + return ret; +} + int NetworkAgent::bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) { int ret = 0; @@ -1048,11 +1074,11 @@ int NetworkAgent::unbind(std::string dev_id) return ret; } -std::string NetworkAgent::get_qidilab_host() +std::string NetworkAgent::get_qiditech_host() { std::string ret; - if (network_agent && get_qidilab_host_ptr) { - ret = get_qidilab_host_ptr(network_agent); + if (network_agent && get_qiditech_host_ptr) { + ret = get_qiditech_host_ptr(network_agent); } return ret; } diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index 79a178c..b13ea0f 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -45,7 +45,7 @@ typedef int (*func_send_message_to_printer)(void *agent, std::string dev_id, std typedef bool (*func_start_discovery)(void *agent, bool start, bool sending); typedef int (*func_change_user)(void *agent, std::string user_info); typedef bool (*func_is_user_login)(void *agent); -typedef int (*func_user_logout)(void *agent); +typedef int (*func_user_logout)(void *agent, bool request); typedef std::string (*func_get_user_id)(void *agent); typedef std::string (*func_get_user_name)(void *agent); typedef std::string (*func_get_user_avatar)(void *agent); @@ -55,9 +55,11 @@ typedef std::string (*func_build_logout_cmd)(void *agent); typedef std::string (*func_build_login_info)(void *agent); typedef int (*func_get_model_id_from_desgin_id)(void *agent, std::string& desgin_id, std::string& model_id); typedef int (*func_ping_bind)(void *agent, std::string ping_code); +typedef int (*func_bind_detect)(void *agent, std::string dev_ip, std::string sec_link, detectResult& detect); +typedef int (*func_set_server_callback)(void *agent, OnServerErrFn fn); typedef int (*func_bind)(void *agent, std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn); typedef int (*func_unbind)(void *agent, std::string dev_id); -typedef std::string (*func_get_qidilab_host)(void *agent); +typedef std::string (*func_get_qiditech_host)(void *agent); typedef std::string (*func_get_user_selected_machine)(void *agent); typedef int (*func_set_user_selected_machine)(void *agent, std::string dev_id); typedef int (*func_start_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); @@ -115,7 +117,7 @@ class NetworkAgent { public: - static std::string get_libpath_in_current_directory(std::string library_name); //1.9.5 + static std::string get_libpath_in_current_directory(std::string library_name); static int initialize_network_module(bool using_backup = false); static int unload_network_module(); #if defined(_MSC_VER) || defined(_WIN32) @@ -162,7 +164,7 @@ public: bool start_discovery(bool start, bool sending); int change_user(std::string user_info); bool is_user_login(); - int user_logout(); + int user_logout(bool request = false); std::string get_user_id(); std::string get_user_name(); std::string get_user_avatar(); @@ -172,9 +174,11 @@ public: std::string build_login_info(); int get_model_id_from_desgin_id(std::string& desgin_id, std::string& model_id); int ping_bind(std::string ping_code); + int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect); + int set_server_callback(OnServerErrFn fn); int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn); int unbind(std::string dev_id); - std::string get_qidilab_host(); + std::string get_qiditech_host(); std::string get_user_selected_machine(); int set_user_selected_machine(std::string dev_id); int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); @@ -225,6 +229,7 @@ public: int get_mw_user_preference(std::function callback); int get_mw_user_4ulist(int seed, int limit, std::function callback); + void *get_network_agent() { return network_agent; } private: bool enable_track = false; @@ -278,9 +283,11 @@ private: static func_build_login_info build_login_info_ptr; static func_get_model_id_from_desgin_id get_model_id_from_desgin_id_ptr; static func_ping_bind ping_bind_ptr; + static func_bind_detect bind_detect_ptr; + static func_set_server_callback set_server_callback_ptr; static func_bind bind_ptr; static func_unbind unbind_ptr; - static func_get_qidilab_host get_qidilab_host_ptr; + static func_get_qiditech_host get_qiditech_host_ptr; static func_get_user_selected_machine get_user_selected_machine_ptr; static func_set_user_selected_machine set_user_selected_machine_ptr; static func_start_print start_print_ptr; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 06d27da..e5a7e95 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -51,7 +51,8 @@ namespace Slic3r { static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".data"; - +static const char *PRESET_SUBPATH = "presets"; +static const char *PLUGINS_SUBPATH = "plugins"; void copy_file_fix(const fs::path &source, const fs::path &target) { @@ -80,13 +81,13 @@ void copy_directory_fix(const fs::path &source, const fs::path &target) if (fs::exists(target)) fs::remove_all(target); fs::create_directories(target); - for (auto &dir_entry : boost::filesystem::directory_iterator(source)) + for (auto &dir_entry : fs::directory_iterator(source)) { std::string source_file = dir_entry.path().string(); std::string name = dir_entry.path().filename().string(); std::string target_file = target.string() + "/" + name; - if (boost::filesystem::is_directory(dir_entry)) { + if (fs::is_directory(dir_entry)) { const auto target_path = target / name; copy_directory_fix(dir_entry, target_path); } @@ -220,6 +221,7 @@ struct PresetUpdater::priv std::string version; std::string description; std::string url; + std::string vendor; bool force{false}; std::string cache_root; std::vector sub_caches; @@ -234,8 +236,14 @@ struct PresetUpdater::priv void prune_tmps() const; void sync_version() const; void parse_version_string(const std::string& body) const; - void sync_resources(std::string http_url, std::map &resources, bool check_patch = false, std::string current_version="", std::string changelog_file=""); - void sync_config(std::string http_url, const VendorMap vendors); + bool sync_resources(std::string http_url, std::map &resources, bool check_patch = false, std::string current_version="", std::string changelog_file=""); + + void remove_config_files(std::string vendor, std::string sub_path) const; + //compare_count means how many sub-versions in AA.BB.CC.DD need to be checked + bool get_valid_ota_version(Semver& app_version, Semver& current_version, Semver& cached_version, int compare_count) const; + void parse_ota_files(std::string ota_json, std::string& version, bool& force_upgrade, std::string& description) const; + + bool sync_config(std::string http_url, const VendorMap vendors); void sync_tooltip(std::string http_url, std::string language); void sync_plugins(std::string http_url, std::string plugin_version); void sync_printer_config(std::string http_url); @@ -380,7 +388,7 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa // Remove leftover paritally downloaded files, if any. void PresetUpdater::priv::prune_tmps() const { - for (auto &dir_entry : boost::filesystem::directory_iterator(cache_path)) + for (auto &dir_entry : fs::directory_iterator(cache_path)) if (is_plain_file(dir_entry) && dir_entry.path().extension() == TMP_EXTENSION) { BOOST_LOG_TRIVIAL(debug) << "[QDT Updater]remove old cached files: " << dir_entry.path().string(); fs::remove(dir_entry.path()); @@ -489,8 +497,9 @@ void PresetUpdater::priv::parse_version_string(const std::string& body) const //QDS: refine the Preset Updater logic // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_resources(std::string http_url, std::map &resources, bool check_patch, std::string current_version_str, std::string changelog_file) +bool PresetUpdater::priv::sync_resources(std::string http_url, std::map &resources, bool check_patch, std::string current_version_str, std::string changelog_file) { + bool has_new_update = false; std::map resource_list; BOOST_LOG_TRIVIAL(info) << boost::format("[QDT Updater]: sync_resources get preferred setting version for app version %1%, url: %2%, current_version_str %3%, check_patch %4%")%SLIC3R_APP_NAME%http_url%current_version_str%check_patch; @@ -498,7 +507,7 @@ void PresetUpdater::priv::sync_resources(std::string http_url, std::map current_version.min()) + || ((cached_version.min() == current_version.min()) && (cached_patch_cc > curent_patch_cc)) + || ((cached_version.min() == current_version.min()) && (cached_patch_cc == curent_patch_cc) && (cached_patch_dd > curent_patch_dd) ))) + valid = true; + } + else if (compare_count == 2) { + if ((cached_version.maj() == app_version.maj()) && (cached_version.min() == app_version.min()) + && ((cached_patch_cc > curent_patch_cc) + || ((cached_patch_cc == curent_patch_cc) && (cached_patch_dd > curent_patch_dd) ))) + valid = true; + } + else if (compare_count == 3) { + if ((cached_version.maj() == app_version.maj()) && (cached_version.min() == app_version.min()) && (cached_patch_cc == app_patch_cc) + && (cached_patch_dd > curent_patch_dd)) + valid = true; + } + return valid; +} + + +void PresetUpdater::priv::parse_ota_files(std::string ota_json, std::string& version, bool& force_upgrade, std::string& description) const +{ + version.clear(); + if (fs::exists(ota_json)) { + try { + boost::nowide::ifstream ifs(ota_json); + json j; + ifs >> j; + + if (j.contains("version")) + version = j["version"]; + if (j.contains("force")) + force_upgrade = j["force"]; + if (j.contains("description")) + description = j["description"]; + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": ota_json %1%, version %2%, force %3%, description %4%")%ota_json %version %force_upgrade %description; + } + catch(nlohmann::detail::parse_error &err) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<> vendor_list; - std::map vendor_descriptions; + bool has_new_config = false; + //std::map> vendor_list; + //std::map vendor_descriptions; BOOST_LOG_TRIVIAL(info) << boost::format("[QDT Updater]: sync_config Syncing configuration enter"); - if (!enabled_config_update) { return; } + if (!enabled_config_update) { return has_new_config; } BOOST_LOG_TRIVIAL(info) << boost::format("[QDT Updater]: sync_config get preferred setting version for app version %1%, http_url: %2%")%SLIC3R_VERSION%http_url; + auto cache_folder = cache_path / PRESET_SUBPATH; + std::string curr_app_version = SLIC3R_VERSION; + std::string using_app_version = curr_app_version.substr(0, 6) + "00.00"; + + for (auto vendor_it :vendors) { + const VendorProfile& vendor_profile = vendor_it.second; + std::string vendor_name = vendor_profile.id, vendor = vendor_profile.id; + boost::to_lower(vendor_name); + if (vendor != PresetBundle::QDT_BUNDLE) + continue; + + std::string vendor_ota_json = cache_folder.string() + "/"+vendor+".changelog"; + std::string vendor_resource_json = vendor_path.string() + "/"+vendor+".json"; + Semver current_preset_semver = get_version_from_json(vendor_resource_json); + std::string cached_version, description; + bool force = false, remove_previous = true, valid_version = false; + + //check previous cached config files, and delete unused + parse_ota_files(vendor_ota_json, cached_version, force, description); + if (!cached_version.empty()) { + Semver app_semver = curr_app_version; + Semver cached_semver = cached_version; + + valid_version = get_valid_ota_version(app_semver, current_preset_semver, cached_semver, 2); + + if (valid_version) { + remove_previous = false; + current_preset_semver = cached_semver; + has_new_config = true; + } + } + + //remove previous config files + if (remove_previous) + remove_config_files(vendor, PRESET_SUBPATH); + + //check online versions + try { + std::map resources + { + {"slicer/settings/"+vendor_name, { using_app_version, "", "", vendor, false, cache_folder.string()}} + }; + has_new_config |= sync_resources(http_url, resources, false, current_preset_semver.to_string(), vendor+".changelog"); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << format("[QDT Updater] sync_plugins: %1%", e.what()); + } + } +#if 0 std::string app_version = SLIC3R_VERSION; std::string query_version = app_version.substr(0, 6) + "00.00"; std::string query_params = "?"; @@ -721,6 +879,7 @@ void PresetUpdater::priv::sync_config(std::string http_url, const VendorMap vend std::string type; std::string vendor; std::string description; + bool force_upgrade = false; for (auto sub_iter = iter.value().begin(); sub_iter != iter.value().end(); sub_iter++) { if (boost::iequals(sub_iter.key(),"type")) { type = sub_iter.value(); @@ -735,6 +894,10 @@ void PresetUpdater::priv::sync_config(std::string http_url, const VendorMap vend else if (boost::iequals(sub_iter.key(),"url")) { url = sub_iter.value(); } + else if (boost::iequals(sub_iter.key(), "force_update")) { + force_upgrade = sub_iter.value(); + } + } BOOST_LOG_TRIVIAL(info) << "[QDT Updater]: get type "<< type <<", version "< resources { - {"slicer/tooltip/common", { common_version, "", "", false, (cache_root / "common").string() }}, - {"slicer/tooltip/" + language, { language_version, "", "", false, (cache_root / language).string() }} + {"slicer/tooltip/common", { common_version, "", "", "", false, (cache_root / "common").string() }}, + {"slicer/tooltip/" + language, { language_version, "", "", "", false, (cache_root / language).string() }} }; sync_resources(http_url, resources); for (auto &r : resources) { @@ -903,9 +1068,7 @@ void PresetUpdater::priv::sync_tooltip(std::string http_url, std::string languag // return true means there are plugins files bool PresetUpdater::priv::get_cached_plugins_version(std::string& cached_version, bool &force) { - std::string data_dir_str = data_dir(); - boost::filesystem::path data_dir_path(data_dir_str); - auto cache_folder = data_dir_path / "ota"; + auto cache_folder = cache_path / "plugins"; std::string network_library, player_library, live555_library; bool has_plugins = false; @@ -924,28 +1087,14 @@ bool PresetUpdater::priv::get_cached_plugins_version(std::string& cached_version #endif std::string changelog_file = cache_folder.string() + "/network_plugins.json"; - if (boost::filesystem::exists(network_library) - && boost::filesystem::exists(player_library) - && boost::filesystem::exists(live555_library) - && boost::filesystem::exists(changelog_file)) + if (fs::exists(network_library) + && fs::exists(player_library) + && fs::exists(live555_library) + && fs::exists(changelog_file)) { has_plugins = true; - try { - boost::nowide::ifstream ifs(changelog_file); - json j; - ifs >> j; - - if (j.contains("version")) - cached_version = j["version"]; - if (j.contains("force")) - force = j["force"]; - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": cached_version = "< resources { - {"slicer/plugins/cloud", { using_version, "", "", false, cache_path.string(), {"plugins"}}} + {"slicer/plugins/cloud", { using_version, "", "", "", false, cache_plugin_folder.string()}} }; sync_resources(http_url, resources, true, plugin_version, "network_plugins.json"); } @@ -1086,7 +1177,7 @@ void PresetUpdater::priv::sync_printer_config(std::string http_url) std::string cached_version; std::string data_dir_str = data_dir(); - boost::filesystem::path data_dir_path(data_dir_str); + fs::path data_dir_path(data_dir_str); auto config_folder = data_dir_path / "printers"; auto cache_folder = data_dir_path / "ota" / "printers"; @@ -1104,28 +1195,32 @@ void PresetUpdater::priv::sync_printer_config(std::string http_url) } catch (...) {} if (!cached_version.empty()) { bool need_delete_cache = false; - Semver current_semver = curr_version; - Semver cached_semver = cached_version; + Semver current_semver = curr_version; + Semver cached_semver = cached_version; if ((cached_semver.maj() != current_semver.maj()) || (cached_semver.min() != current_semver.min())) { need_delete_cache = true; BOOST_LOG_TRIVIAL(info) << boost::format("cached printer config version %1% not match with current %2%") % cached_version % curr_version; - } else if (cached_semver.patch() <= current_semver.patch()) { + } + else if (cached_semver.patch() <= current_semver.patch()) { need_delete_cache = true; BOOST_LOG_TRIVIAL(info) << boost::format("cached printer config version %1% not newer than current %2%") % cached_version % curr_version; - } else { + } + else { using_version = cached_version; } if (need_delete_cache) { boost::system::error_code ec; - boost::filesystem::remove_all(cache_folder, ec); - cached_version = curr_version; + fs::remove_all(cache_folder, ec); + cached_version = curr_version; } } + else + cached_version = curr_version; try { - std::map resources{{"slicer/printer/qdt", {using_version, "", "", false, cache_folder.string()}}}; + std::map resources{{"slicer/printer/qdt", {using_version, "", "", "", false, cache_folder.string()}}}; sync_resources(http_url, resources, false, cached_version, "printer.json"); } catch (std::exception &e) { BOOST_LOG_TRIVIAL(warning) << format("[QDT Updater] sync_printer_config: %1%", e.what()); @@ -1185,7 +1280,7 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const //QDS: refine the init check logic std::vector bundles; - for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_path)) { + for (auto &dir_entry : fs::directory_iterator(rsrc_path)) { const auto &path = dir_entry.path(); std::string file_path = path.string(); if (is_json_file(file_path)) { @@ -1214,6 +1309,7 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const fs::remove_all(path_of_vendor); } } + //y else if ((vendor_name == PresetBundle::QDT_BUNDLE) || (enabled_vendors.find(vendor_name) != enabled_vendors.end())) {//if vendor has no file, copy it from resource for QDT bundles.push_back(vendor_name); } @@ -1234,8 +1330,8 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const Updates PresetUpdater::priv::get_printer_config_updates(bool update) const { std::string data_dir_str = data_dir(); - boost::filesystem::path data_dir_path(data_dir_str); - boost::filesystem::path resc_dir_path(resources_dir()); + fs::path data_dir_path(data_dir_str); + fs::path resc_dir_path(resources_dir()); auto config_folder = data_dir_path / "printers"; auto resc_folder = (update ? cache_path : resc_dir_path) / "printers"; std::string curr_version; @@ -1288,67 +1384,57 @@ Updates PresetUpdater::priv::get_printer_config_updates(bool update) const //QDS: refine the Preset Updater logic Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version) const { - Updates updates; + Updates updates; + auto cache_folder = cache_path / PRESET_SUBPATH; - BOOST_LOG_TRIVIAL(info) << "[QDT Updater]:Checking for cached configuration updates..."; + BOOST_LOG_TRIVIAL(info) << "[QDT Updater]:Checking for cached configuration updates..."; - for (auto &dir_entry : boost::filesystem::directory_iterator(cache_path)) { - const auto &path = dir_entry.path(); - std::string file_path = path.string(); - if (is_json_file(file_path)) { - const auto path_in_vendor = vendor_path / path.filename(); - std::string vendor_name = path.filename().string(); - // Remove the .json suffix. - vendor_name.erase(vendor_name.size() - 5); - auto print_in_cache = (cache_path / vendor_name / PRESET_PRINT_NAME); - auto filament_in_cache = (cache_path / vendor_name / PRESET_FILAMENT_NAME); - auto machine_in_cache = (cache_path / vendor_name / PRESET_PRINTER_NAME); + //remove previous old datas + remove_config_files("QDT", std::string()); - if (( fs::exists(path_in_vendor)) - &&( fs::exists(print_in_cache)) - &&( fs::exists(filament_in_cache)) - &&( fs::exists(machine_in_cache))) { - Semver vendor_ver = get_version_from_json(path_in_vendor.string()); + if (fs::exists(cache_folder)) { + for (auto& dir_entry : fs::directory_iterator(cache_folder)) { + const auto& path = dir_entry.path(); + std::string file_path = path.string(); + if (is_json_file(file_path)) { + const auto path_in_vendor = vendor_path / path.filename(); + std::string vendor_name = path.filename().string(), cached_version, description; + // Remove the .json suffix. + vendor_name.erase(vendor_name.size() - 5); + auto print_in_cache = (cache_folder / vendor_name / PRESET_PRINT_NAME); + auto filament_in_cache = (cache_folder / vendor_name / PRESET_FILAMENT_NAME); + auto machine_in_cache = (cache_folder / vendor_name / PRESET_PRINTER_NAME); - std::map key_values; - std::vector keys(3); - Semver cache_ver; - keys[0] = QDT_JSON_KEY_VERSION; - keys[1] = QDT_JSON_KEY_DESCRIPTION; - keys[2] = QDT_JSON_KEY_FORCE_UPDATE; - get_values_from_json(file_path, keys, key_values); - std::string description = key_values[QDT_JSON_KEY_DESCRIPTION]; - bool force_update = false; - if (key_values.find(QDT_JSON_KEY_FORCE_UPDATE) != key_values.end()) - force_update = (key_values[QDT_JSON_KEY_FORCE_UPDATE] == "1")?true:false; - auto config_version = Semver::parse(key_values[QDT_JSON_KEY_VERSION]); - if (config_version) - cache_ver = *config_version; + std::string changelog_file = (cache_folder / (vendor_name + ".changelog")).string(); + bool force_update; - std::string changelog; - std::string changelog_file = (cache_path / (vendor_name + ".changelog")).string(); - boost::nowide::ifstream ifs(changelog_file); - if (ifs) { - std::ostringstream oss; - oss<< ifs.rdbuf(); - changelog = oss.str(); - //ifs>>changelog; - ifs.close(); - } + if ((fs::exists(path_in_vendor)) + && (fs::exists(print_in_cache)) + && (fs::exists(filament_in_cache)) + && (fs::exists(machine_in_cache))) { - bool version_match = ((vendor_ver.maj() == cache_ver.maj()) && (vendor_ver.min() == cache_ver.min())); - if (version_match && (vendor_ver < cache_ver)) { - Semver app_ver = *Semver::parse(SLIC3R_VERSION); - if (cache_ver.maj() == app_ver.maj()){ - BOOST_LOG_TRIVIAL(info) << "[QDT Updater]:need to update settings from "<set_download_prefs(GUI::wxGetApp().app_config); - if (!p->enabled_version_check && !p->enabled_config_update) { return; } + //p->set_download_prefs(GUI::wxGetApp().app_config); + if (!p->enabled_version_check && !p->enabled_config_update) { return; } - // Copy the whole vendors data for use in the background thread - // Unfortunatelly as of C++11, it needs to be copied again - // into the closure (but perhaps the compiler can elide this). + // Copy the whole vendors data for use in the background thread + // Unfortunatelly as of C++11, it needs to be copied again + // into the closure (but perhaps the compiler can elide this). VendorMap vendors = preset_bundle ? preset_bundle->vendors : VendorMap{}; - p->thread = std::thread([this, vendors, http_url, language, plugin_version]() { - this->p->prune_tmps(); - if (p->cancel) - return; - this->p->sync_version(); - if (p->cancel) - return; + p->thread = std::thread([this, vendors, http_url, language, plugin_version]() { + this->p->prune_tmps(); + if (p->cancel) + return; + //not used + //this->p->sync_version(); + //if (p->cancel) + // return; if (!vendors.empty()) { - this->p->sync_config(http_url, std::move(vendors)); - if (p->cancel) - return; - GUI::wxGetApp().CallAfter([] { - GUI::wxGetApp().check_config_updates_from_updater(); - }); + bool has_new_config = this->p->sync_config(http_url, std::move(vendors)); + if (p->cancel) + return; + if (has_new_config) { + GUI::wxGetApp().CallAfter([] { + GUI::wxGetApp().check_config_updates_from_updater(); + }); + } } - if (p->cancel) - return; + if (p->cancel) + return; this->p->sync_plugins(http_url, plugin_version); this->p->sync_printer_config(http_url); - //if (p->cancel) - // return; - //remove the tooltip currently - //this->p->sync_tooltip(http_url, language); - }); + //if (p->cancel) + // return; + //remove the tooltip currently + //this->p->sync_tooltip(http_url, language); + }); } void PresetUpdater::slic3r_update_notify() diff --git a/src/slic3r/Utils/ProfileDescription.hpp b/src/slic3r/Utils/ProfileDescription.hpp index dfec0e8..0045200 100644 --- a/src/slic3r/Utils/ProfileDescription.hpp +++ b/src/slic3r/Utils/ProfileDescription.hpp @@ -34,4 +34,16 @@ namespace ProfileDescrption { const std::string PROFILE_DESCRIPTION_26 = _L("Compared with the default profile of a 0.8 mm nozzle, it has a slightly smaller layer height, and results in slightly less but still apparent layer lines and slightly higher printing quality, but longer printing time in some printing cases."); const std::string PROFILE_DESCRIPTION_27 = _L("Compared with the default profile of a 0.8 mm nozzle, it has a smaller layer height, and results in less but still apparent layer lines and slightly higher printing quality, but longer printing time in some printing cases."); const std::string PROFILE_DESCRIPTION_28 = _L("This is neither a commonly used filament, nor one of QIDI filaments, and it varies a lot from brand to brand. So, it's highly recommended to ask its vendor for suitable profile before printing and adjust some parameters according to its performances."); + const std::string PROFILE_DESCRIPTION_29 = _L("When printing this filament, there's a risk of warping and low layer adhesion strength. To get better results, please refer to this wiki: Printing Tips for High Temp / Engineering materials."); + const std::string PROFILE_DESCRIPTION_30 = _L("When printing this filament, there's a risk of nozzle clogging, oozing, warping and low layer adhesion strength. To get better results, please refer to this wiki: Printing Tips for High Temp / Engineering materials."); + const std::string PROFILE_DESCRIPTION_31 = _L("To get better transparent or translucent results with the corresponding filament, please refer to this wiki: Printing tips for transparent PETG."); + const std::string PROFILE_DESCRIPTION_32 = _L("To make the prints get higher gloss, please dry the filament before use, and set the outer wall speed to be 40 to 60 mm/s when slicing."); + const std::string PROFILE_DESCRIPTION_33 = _L("This filament is only used to print models with a low density usually, and some special parameters are required. To get better printing quality, please refer to this wiki: Instructions for printing RC model with foaming PLA (PLA Aero)."); + const std::string PROFILE_DESCRIPTION_34 = _L("This filament is only used to print models with a low density usually, and some special parameters are required. To get better printing quality, please refer to this wiki: ASA Aero Printing Guide."); + const std::string PROFILE_DESCRIPTION_35 = _L("This filament is too soft and not compatible with the AMS. Printing it is of many requirements, and to get better printing quality, please refer to this wiki: TPU printing guide."); + const std::string PROFILE_DESCRIPTION_36 = _L("This filament has high enough hardness (about 67D) and is compatible with the AMS. Printing it is of many requirements, and to get better printing quality, please refer to this wiki: TPU printing guide."); + const std::string PROFILE_DESCRIPTION_37 = _L("If you are to print a kind of soft TPU, please don't slice with this profile, and it is only for TPU that has high enough hardness (not less than 55D) and is compatible with the AMS. To get better printing quality, please refer to this wiki: TPU printing guide."); + const std::string PROFILE_DESCRIPTION_38 = _L("This is a water-soluble support filament, and usually it is only for the support structure and not for the model body. Printing this filament is of many requirements, and to get better printing quality, please refer to this wiki: PVA Printing Guide."); + const std::string PROFILE_DESCRIPTION_39 = _L("This is a non-water-soluble support filament, and usually it is only for the support structure and not for the model body. To get better printing quality, please refer to this wiki: Printing Tips for Support Filament and Support Function."); + const std::string PROFILE_DESCRIPTION_40 = _L("The generic presets are conservatively tuned for compatibility with a wider range of filaments. For higher printing quality and speeds, please use QIDI filaments with QIDI presets."); } \ No newline at end of file diff --git a/src/slic3r/Utils/RaycastManager.cpp b/src/slic3r/Utils/RaycastManager.cpp new file mode 100644 index 0000000..1297dde --- /dev/null +++ b/src/slic3r/Utils/RaycastManager.cpp @@ -0,0 +1,391 @@ +#include "RaycastManager.hpp" +#include + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/CameraUtils.hpp" + +using namespace Slic3r::GUI; + +namespace{ +using namespace Slic3r; +void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes *input = nullptr); +const sla::IndexedMesh * get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id); +RaycastManager::TrKey create_key(const ModelVolume& volume, const ModelInstance& instance){ + return std::make_pair(instance.id().id, volume.id().id); } +RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key); +RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key); +bool is_lower_key(const RaycastManager::TrKey &k1, const RaycastManager::TrKey &k2) { + return k1.first < k2.first || (k1.first == k2.first && k1.second < k2.second); } +bool is_lower(const RaycastManager::TrItem &i1, const RaycastManager::TrItem &i2) { + return is_lower_key(i1.first, i2.first); }; +template inline void erase(std::vector &vec, const std::vector &flags); +} + +void RaycastManager::actualize(const ModelObject &object, const ISkip *skip, Meshes *meshes) +{ + // actualize MeshRaycaster + ::actualize(m_meshes, object.volumes, skip, meshes); + + // check if inscance was removed + std::vector removed_transf(m_transformations.size(), {true}); + + bool need_sort = false; + // actualize transformation matrices + for (const ModelVolume *volume : object.volumes) { + if (skip != nullptr && skip->skip(volume->id().id)) continue; + const Transform3d &volume_tr = volume->get_matrix(); + for (const ModelInstance *instance : object.instances) { + const Transform3d &instrance_tr = instance->get_matrix(); + Transform3d transformation = instrance_tr * volume_tr; + TrKey key = ::create_key(*volume, *instance); + auto item = ::find(m_transformations, key); + if (item != m_transformations.end()) { + // actualize transformation all the time + item->second = transformation; + size_t index = item - m_transformations.begin(); + removed_transf[index] = false; + } else { + // add new transformation + m_transformations.emplace_back(key, transformation); + need_sort = true; + } + } + } + + // clean other transformation + ::erase(m_transformations, removed_transf); + + if (need_sort) + std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower); +} + +void RaycastManager::actualize(const ModelInstance &instance, const ISkip *skip, Meshes *meshes) +{ + const ModelVolumePtrs &volumes = instance.get_object()->volumes; + + // actualize MeshRaycaster + ::actualize(m_meshes, volumes, skip, meshes); + + // check if inscance was removed + std::vector removed_transf(m_transformations.size(), {true}); + + bool need_sort = false; + // actualize transformation matrices + for (const ModelVolume *volume : volumes) { + if (skip != nullptr && skip->skip(volume->id().id)) + continue; + const Transform3d &volume_tr = volume->get_matrix(); + const Transform3d &instrance_tr = instance.get_matrix(); + Transform3d transformation = instrance_tr * volume_tr; + TrKey key = ::create_key(*volume, instance); + auto item = ::find(m_transformations, key); + if (item != m_transformations.end()) { + // actualize transformation all the time + item->second = transformation; + size_t index = item - m_transformations.begin(); + removed_transf[index] = false; + } else { + // add new transformation + m_transformations.emplace_back(key, transformation); + need_sort = true; + } + } + + // clean other transformation + ::erase(m_transformations, removed_transf); + + if (need_sort) + std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower); +} + +std::optional RaycastManager::first_hit(const Vec3d& point, const Vec3d& direction, const ISkip *skip) const +{ + // Improve: it is not neccessaru to use AABBMesh and calc normal for every hit + + // Results + const sla::IndexedMesh *hit_mesh = nullptr; + double hit_squared_distance = 0.; + int hit_face = -1; + Vec3d hit_world; + const Transform3d *hit_tramsformation = nullptr; + const TrKey *hit_key = nullptr; + + for (const auto &[key, transformation]: m_transformations) { + size_t volume_id = key.second; + if (skip != nullptr && skip->skip(volume_id)) + continue; + const sla::IndexedMesh *mesh = ::get_mesh(m_meshes, volume_id); + if (mesh == nullptr) continue; + Transform3d inv = transformation.inverse(); + + // transform input into mesh world + Vec3d point_ = inv * point; + Vec3d direction_= inv.linear() * direction; + + std::vector hits = mesh->query_ray_hits(point_, direction_); + if (hits.empty()) continue; // no intersection found + + const sla::IndexedMesh::hit_result &hit = hits.front(); + + // convert to world + Vec3d world = transformation * hit.position(); + double squared_distance = (point - world).squaredNorm(); + if (hit_mesh != nullptr && + hit_squared_distance < squared_distance) + continue; // exist closer one + + hit_mesh = mesh; + hit_squared_distance = squared_distance; + hit_face = hit.face(); + hit_world = world; + hit_tramsformation = &transformation; + hit_key = &key; + } + + if (hit_mesh == nullptr) + return {}; + + // Calculate normal from transformed triangle + // NOTE: Anisotropic transformation of normal is not perpendiculat to triangle + const Vec3i32 tri = hit_mesh->indices(hit_face); + std::array pts; + auto tr = hit_tramsformation->linear(); + for (int i = 0; i < 3; ++i) + pts[i] = tr * hit_mesh->vertices(tri[i]).cast(); + Vec3d normal_world = (pts[1] - pts[0]).cross(pts[2] - pts[1]); + if (has_reflection(*hit_tramsformation)) + normal_world *= -1; + normal_world.normalize(); + + SurfacePoint point_world{hit_world, normal_world}; + return RaycastManager::Hit{point_world, *hit_key, hit_squared_distance}; +} + +std::optional RaycastManager::closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const +{ + std::optional closest; + for (const auto &[key, transformation] : m_transformations) { + size_t volume_id = key.second; + if (skip != nullptr && skip->skip(volume_id)) continue; + const sla::IndexedMesh *mesh = ::get_mesh(m_meshes, volume_id); + if (mesh == nullptr) continue; + Transform3d tr_inv = transformation.inverse(); + Vec3d mesh_point = tr_inv * point; + Vec3d mesh_direction = tr_inv.linear() * direction; + + // Need for detect that actual point position is on correct place + Vec3d point_positive = mesh_point - mesh_direction; + Vec3d point_negative = mesh_point + mesh_direction; + + // Throw ray to both directions of ray + std::vector hits = mesh->query_ray_hits(point_positive, mesh_direction); + std::vector hits_neg = mesh->query_ray_hits(point_negative, -mesh_direction); + hits.insert(hits.end(), std::make_move_iterator(hits_neg.begin()), std::make_move_iterator(hits_neg.end())); + for (const sla::IndexedMesh::hit_result &hit : hits) { + Vec3d diff = mesh_point - hit.position(); + double squared_distance = diff.squaredNorm(); + if (closest.has_value() && + closest->squared_distance < squared_distance) + continue; + closest = Hit{{hit.position(), hit.normal()}, key, squared_distance}; + } + } + return closest; +} + +std::optional RaycastManager::closest(const Vec3d &point, const ISkip *skip) const +{ + std::optional closest; + for (const auto &[key, transformation] : m_transformations) { + size_t volume_id = key.second; + if (skip != nullptr && skip->skip(volume_id)) + continue; + const sla::IndexedMesh *mesh = ::get_mesh(m_meshes, volume_id); + if (mesh == nullptr) continue; + Transform3d tr_inv = transformation.inverse(); + Vec3d mesh_point = tr_inv * point; + + int face_idx = 0; + Vec3d closest_point; + Vec3d pointd = point.cast(); + mesh->squared_distance(pointd, face_idx, closest_point); + + double squared_distance = (mesh_point - closest_point).squaredNorm(); + if (closest.has_value() && closest->squared_distance < squared_distance) + continue; + + closest = ClosePoint{key, closest_point, squared_distance}; + } + return closest; +} + +Slic3r::Transform3d RaycastManager::get_transformation(const TrKey &tr_key) const { + auto tr = ::find(m_transformations, tr_key); + if (tr == m_transformations.end()) + return Transform3d::Identity(); + return tr->second; +} + +void Slic3r::GUI::RaycastManager::clear() { + m_meshes.clear(); + m_transformations.clear(); +} + +namespace { +void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes* inputs) +{ + // check if volume was removed + std::vector removed_meshes(meshes.size(), {true}); + bool need_sort = false; + // actualize MeshRaycaster + for (const ModelVolume *volume : volumes) { + size_t oid = volume->id().id; + if (skip != nullptr && skip->skip(oid)) + continue; + auto is_oid = [oid](const RaycastManager::Mesh &it) { return oid == it.first; }; + if (auto item = std::find_if(meshes.begin(), meshes.end(), is_oid); + item != meshes.end()) { + size_t index = item - meshes.begin(); + removed_meshes[index] = false; + continue; + } + + // exist AABB in inputs ? + if (inputs != nullptr) { + auto input = std::find_if(inputs->begin(), inputs->end(), is_oid); + if (input != inputs->end()) { + meshes.emplace_back(std::move(*input)); + need_sort = true; + continue; + } + } + + // add new raycaster + bool calculate_epsilon = true; + auto mesh = std::make_unique(volume->mesh(), calculate_epsilon); + meshes.emplace_back(std::make_pair(oid, std::move(mesh))); + need_sort = true; + } + + // clean other raycasters + erase(meshes, removed_meshes); + + // All the time meshes must be sorted by volume id - for faster search + if (need_sort) { + auto is_lower = [](const RaycastManager::Mesh &m1, const RaycastManager::Mesh &m2) { return m1.first < m2.first; }; + std::sort(meshes.begin(), meshes.end(), is_lower); + } +} + +const Slic3r::sla::IndexedMesh *get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id) +{ + auto is_lower_index = [](const RaycastManager::Mesh &m, size_t i) { return m.first < i; }; + auto it = std::lower_bound(meshes.begin(), meshes.end(), volume_id, is_lower_index); + if (it == meshes.end() || it->first != volume_id) + return nullptr; + return &(*(it->second)); +} + +RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key) { + auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); }; + auto it = std::lower_bound(items.begin(), items.end(), key, fnc); + if (it != items.end() && it->first != key) + return items.end(); + return it; +} + +RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key) +{ + auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); }; + auto it = std::lower_bound(items.begin(), items.end(), key, fnc); + if (it != items.end() && it->first != key) + return items.end(); + return it; +} + +template inline void erase(std::vector &vec, const std::vector &flags) +{ + if (vec.size() < flags.size() || flags.empty()) + return; + + // reverse iteration over flags to erase indices from back to front. + for (int i = static_cast(flags.size()) - 1; i >= 0; --i) + if (flags[i]) + vec.erase(vec.begin() + i); +} + +} // namespace + +namespace Slic3r::GUI{ + +RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition) +{ + RaycastManager::Meshes meshes;//from + throw; + //std::map> m_mesh_raycaster_map;//for text + /* + m_mesh_raycaster_map[v] = std::make_shared(mesh, -1); + SceneRaycaster::EType type = SceneRaycaster::EType::Volume; + auto scene_casters = canvas.get_raycasters_for_picking(type); + if (scene_casters == nullptr) + return {}; + const std::vector> &casters = *scene_casters; + + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + const ModelObjectPtrs &objects = canvas.get_model()->objects; + + for (const std::shared_ptr &caster : casters) { + int index = SceneRaycaster::decode_id(type, caster->get_id()); + if (index < 0) + continue; + auto index_ = static_cast(index); + if(index_ >= gl_volumes.size()) + continue; + const GLVolume *gl_volume = gl_volumes[index_]; + if (gl_volume == nullptr) + continue; + const ModelVolume *volume = get_model_volume(*gl_volume, objects); + if (volume == nullptr) + continue; + size_t id = volume->id().id; + if (condition.skip(id)) + continue; + auto mesh = std::make_unique(caster->get_raycaster()->get_aabb_mesh()); + meshes.emplace_back(std::make_pair(id, std::move(mesh))); + }*/ + return meshes; +} + + +std::optional ray_from_camera(const RaycastManager &raycaster, + const Vec2d &mouse_pos, + const Camera &camera, + const RaycastManager::ISkip *skip) +{ + Vec3d point; + Vec3d direction; + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); + return raycaster.first_hit(point, direction, skip); +} + +RaycastManager::AllowVolumes create_condition(const ModelVolumePtrs &volumes, const ObjectID &disallowed_volume_id) { + std::vector allowed_volumes_id; + if (volumes.size() > 1) { + allowed_volumes_id.reserve(volumes.size() - 1); + for (const ModelVolume *v : volumes) { + // drag only above part not modifiers or negative surface + if (!v->is_model_part()) + continue; + + // skip actual selected object + if (v->id() == disallowed_volume_id) + continue; + + allowed_volumes_id.emplace_back(v->id().id); + } + } + return RaycastManager::AllowVolumes(allowed_volumes_id); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/Utils/RaycastManager.hpp b/src/slic3r/Utils/RaycastManager.hpp new file mode 100644 index 0000000..3ac2c22 --- /dev/null +++ b/src/slic3r/Utils/RaycastManager.hpp @@ -0,0 +1,188 @@ +#ifndef slic3r_RaycastManager_hpp_ +#define slic3r_RaycastManager_hpp_ + +#include // unique_ptr +#include +#include "libslic3r/SLA/IndexedMesh.hpp" //#include "libslic3r/AABBMesh.hpp" // Structure to cast rays +#include "libslic3r/Point.hpp" // Transform3d +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Model.hpp" // ModelObjectPtrs, ModelObject, ModelInstance, ModelVolume + +namespace Slic3r::GUI{ + +/// +/// Cast rays from camera to scene +/// Used for find hit point on model volume under mouse cursor +/// +class RaycastManager +{ +// Public structures used by RaycastManager +public: + + // ModelVolume.id + using Mesh = std::pair >;//AABBMesh + using Meshes = std::vector; + + // Key for transformation consist of unique volume and instance id ... ObjectId() + // ModelInstance, ModelVolume + using TrKey = std::pair; + using TrItem = std::pair; + using TrItems = std::vector; + + /// + /// Interface for identify allowed volumes to cast rays. + /// + class ISkip{ + public: + virtual ~ISkip() = default; + + /// + /// Condition to not process model volume + /// + /// ObjectID of model volume to not process + /// True on skip otherwise false + virtual bool skip(const size_t &model_volume_id) const { return false; } + }; + + // TODO: it is more general object move outside of this class + template + struct SurfacePoint { + using Vec3 = Eigen::Matrix; + Vec3 position = Vec3::Zero(); + Vec3 normal = Vec3::UnitZ(); + }; + + struct Hit : public SurfacePoint + { + TrKey tr_key; + double squared_distance; + }; + + struct ClosePoint + { + TrKey tr_key; + Vec3d point; + double squared_distance; + }; + const Meshes& get_meshes() { return m_meshes; } + // Members +private: + // Keep structure to fast cast rays + // meshes are sorted by volume_id for faster search + Meshes m_meshes; + + // Keep transformation of meshes + TrItems m_transformations; + // Note: one mesh could have more transformations ... instances + +public: + + /// + /// Actualize raycasters + transformation + /// Detection of removed object + /// Detection of removed instance + /// Detection of removed volume + /// + /// Model representation + /// Condifiton for skip actualization + /// Speed up for already created AABBtrees + void actualize(const ModelObject &object, const ISkip *skip = nullptr, Meshes *meshes = nullptr); + void actualize(const ModelInstance &instance, const ISkip *skip = nullptr, Meshes* meshes = nullptr); + + class SkipVolume: public ISkip + { + size_t volume_id; + public: + SkipVolume(size_t volume_id) : volume_id(volume_id) {} + bool skip(const size_t &model_volume_id) const override { return model_volume_id == volume_id; } + }; + + class AllowVolumes: public ISkip + { + std::vector allowed_id; + public: + AllowVolumes() = default; + AllowVolumes(std::vector allowed_id) : allowed_id(allowed_id) {} + void clear() { + allowed_id.clear(); + } + bool skip(const size_t &model_volume_id) const override { + if (allowed_id.size() == 0) { + return false; + } + auto it = std::find(allowed_id.begin(), allowed_id.end(), model_volume_id); + return it == allowed_id.end(); + } + }; + + /// + /// Unproject on mesh and return closest hit to point in given direction + /// + /// Position in space + /// Casted ray direction + /// Define which caster will be skipped, null mean no skip + /// Position on surface, normal direction in world coorinate + /// + key, to know hitted instance and volume + std::optional first_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const; + + /// + /// Unproject Ray(point direction) on mesh to find closest hit of surface in given direction + /// NOTE: It inspect also oposit direction of ray !! + /// + /// Start point for ray + /// Direction of ray, orientation doesn't matter, both are used + /// Define which caster will be skipped, null mean no skip + /// Position on surface, normal direction and transformation key, which define hitted object instance + std::optional closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const; + + /// + /// Search of closest point + /// + /// Point + /// Define which caster will be skipped, null mean no skip + /// + std::optional closest(const Vec3d &point, const ISkip *skip = nullptr) const; + + /// + /// Getter on transformation from hitted volume to world + /// + /// Define transformation + /// Transformation for key + Transform3d get_transformation(const TrKey &tr_key) const; + void clear(); +}; + +class GLCanvas3D; +/// +/// Use scene Raycasters and prepare data for actualize RaycasterManager +/// +/// contain Scene raycasters +/// Limit for scene casters +/// Meshes +RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition); + +struct Camera; +/// +/// Unproject on mesh by Mesh raycasters +/// +/// Position of mouse on screen +/// Projection params +/// Define which caster will be skipped, null mean no skip +/// Position on surface, normal direction in world coorinate +/// + key, to know hitted instance and volume +std::optional ray_from_camera(const RaycastManager &raycaster, + const Vec2d &mouse_pos, + const Camera &camera, + const RaycastManager::ISkip *skip); + +/// +/// Create condition to allowe only parts from volumes without one given +/// +/// List of allowed volumes included one which is dissalowed and non parts +/// Disallowed volume +/// Condition +RaycastManager::AllowVolumes create_condition(const ModelVolumePtrs &volumes, const ObjectID &disallowed_volume_id); + +} // namespace Slic3r::GUI + +#endif // slic3r_RaycastManager_hpp_ diff --git a/src/slic3r/Utils/qidi_networking.hpp b/src/slic3r/Utils/qidi_networking.hpp index 60af476..320652c 100644 --- a/src/slic3r/Utils/qidi_networking.hpp +++ b/src/slic3r/Utils/qidi_networking.hpp @@ -95,7 +95,7 @@ namespace QDT { #define QIDI_NETWORK_LIBRARY "qidi_networking" #define QIDI_NETWORK_AGENT_NAME "qidi_network_agent" -#define QIDI_NETWORK_AGENT_VERSION "01.09.02.05" +#define QIDI_NETWORK_AGENT_VERSION "01.10.01.01" //iot preset type strings #define IOT_PRINTER_TYPE_STRING "printer" @@ -138,6 +138,9 @@ typedef std::function ResultFn; typedef std::function CancelFn; typedef std::function info)> CheckFn; +//err callbacks +typedef std::function OnServerErrFn; + enum SendingPrintJobStage { PrintingStageCreate = 0, PrintingStageUpload = 1, @@ -172,6 +175,17 @@ enum ConnectStatus { ConnectStatusLost = 2, }; +struct detectResult { + std::string result_msg; + std::string command; + std::string dev_id; + std::string model_id; + std::string dev_name; + std::string version; + std::string bind_state; + std::string connect_type; +}; + /* print job*/ struct PrintParams { /* basic info */ diff --git a/src/slic3r/pchheader.hpp b/src/slic3r/pchheader.hpp index 1413839..7519cbb 100644 --- a/src/slic3r/pchheader.hpp +++ b/src/slic3r/pchheader.hpp @@ -6,6 +6,11 @@ #define NOMINMAX #endif #include + #include +#endif + +#ifdef __APPLE__ + #include #endif #include