diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index eed93ec..dd80818 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -10,6 +10,19 @@ set(SLIC3R_GUI_SOURCES GUI/AboutDialog.hpp GUI/ArrangeSettingsDialogImgui.hpp GUI/ArrangeSettingsDialogImgui.cpp + GUI/UserAccountCommunication.cpp + GUI/UserAccountCommunication.hpp + GUI/UserAccountSession.cpp + GUI/UserAccountSession.hpp + GUI/UserAccountUtils.cpp + GUI/UserAccountUtils.hpp + GUI/UserAccount.cpp + GUI/UserAccount.hpp + GUI/WebViewDialog.cpp + GUI/WebViewDialog.hpp + GUI/WebView.cpp + GUI/WebView.hpp + GUI/WebViewPlatformUtils.hpp GUI/SysInfoDialog.cpp GUI/SysInfoDialog.hpp GUI/KBShortcutsDialog.cpp @@ -75,6 +88,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoMmuSegmentation.hpp GUI/Gizmos/GLGizmoMeasure.cpp GUI/Gizmos/GLGizmoMeasure.hpp + GUI/LibVGCode/LibVGCodeWrapper.hpp + GUI/LibVGCode/LibVGCodeWrapper.cpp GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.hpp GUI/GLModel.hpp @@ -107,6 +122,12 @@ set(SLIC3R_GUI_SOURCES GUI/IconManager.hpp GUI/MainFrame.cpp GUI/MainFrame.hpp + GUI/UpdatesUIManager.cpp + GUI/UpdatesUIManager.hpp + GUI/FrequentlyChangedParameters.cpp + GUI/FrequentlyChangedParameters.hpp + GUI/Sidebar.cpp + GUI/Sidebar.hpp GUI/Plater.cpp GUI/Plater.hpp GUI/PresetComboBoxes.hpp @@ -179,6 +200,8 @@ set(SLIC3R_GUI_SOURCES GUI/ButtonsDescription.hpp GUI/ImGuiWrapper.hpp GUI/ImGuiWrapper.cpp + GUI/ImGuiPureWrap.hpp + GUI/ImGuiPureWrap.cpp Config/Snapshot.cpp Config/Snapshot.hpp GUI/PrinterWebView.cpp @@ -194,6 +217,8 @@ set(SLIC3R_GUI_SOURCES GUI/ConfigWizard.cpp GUI/ConfigWizard.hpp GUI/ConfigWizard_private.hpp + GUI/ConfigWizardWebViewPage.cpp + GUI/ConfigWizardWebViewPage.hpp GUI/MsgDialog.cpp GUI/MsgDialog.hpp GUI/UpdateDialogs.cpp @@ -233,10 +258,22 @@ set(SLIC3R_GUI_SOURCES GUI/ProgressStatusBar.cpp GUI/Mouse3DController.cpp GUI/Mouse3DController.hpp - GUI/DoubleSlider.cpp - GUI/DoubleSlider.hpp + GUI/ImGuiDoubleSlider.cpp + GUI/ImGuiDoubleSlider.hpp + GUI/TickCodesManager.cpp + GUI/TickCodesManager.hpp + GUI/DoubleSliderForLayers.cpp + GUI/DoubleSliderForLayers.hpp + GUI/DoubleSliderForGcode.cpp + GUI/DoubleSliderForGcode.hpp + GUI/RulerForDoubleSlider.cpp + GUI/RulerForDoubleSlider.hpp GUI/Notebook.cpp GUI/Notebook.hpp + GUI/TopBar.cpp + GUI/TopBar.hpp + GUI/TopBarMenus.cpp + GUI/TopBarMenus.hpp GUI/ObjectDataViewModel.cpp GUI/ObjectDataViewModel.hpp GUI/InstanceCheck.cpp @@ -295,12 +332,18 @@ set(SLIC3R_GUI_SOURCES GUI/Downloader.hpp GUI/DownloaderFileGet.cpp GUI/DownloaderFileGet.hpp + GUI/LoginDialog.cpp + GUI/LoginDialog.hpp + GUI/PresetArchiveDatabase.cpp + GUI/PresetArchiveDatabase.hpp Utils/AppUpdater.cpp Utils/AppUpdater.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp Utils/FixModelByWin10.hpp + Utils/Jwt.cpp + Utils/Jwt.hpp Utils/Moonraker.cpp Utils/Moonraker.hpp Utils/OctoPrint.cpp @@ -343,6 +386,12 @@ set(SLIC3R_GUI_SOURCES Utils/WxFontUtils.hpp Utils/WifiScanner.hpp Utils/WifiScanner.cpp + Utils/Secrets.hpp + Utils/Secrets.cpp + Utils/QIDIConnect.hpp + Utils/QIDIConnect.cpp + Utils/ServiceConfig.hpp + Utils/ServiceConfig.cpp ) find_package(NanoSVG REQUIRED) @@ -366,9 +415,18 @@ if (APPLE) GUI/Mouse3DHandlerMac.mm GUI/InstanceCheckMac.mm GUI/InstanceCheckMac.h + GUI/WebViewPlatformUtilsMac.mm ) FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration) FIND_LIBRARY(COREWLAN_LIBRARY CoreWLAN) +elseif (WIN32) + list(APPEND SLIC3R_GUI_SOURCES + GUI/WebViewPlatformUtilsWin32.cpp + ) +else() # Linux + list(APPEND SLIC3R_GUI_SOURCES + GUI/WebViewPlatformUtilsLinux.cpp + ) endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) @@ -385,14 +443,31 @@ endforeach() encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES} NanoSVG::nanosvg NanoSVG::nanosvgrast) +target_link_libraries( + libslic3r_gui + PUBLIC + libslic3r + avrdude + libcereal + imgui + libvgcode + GLEW::GLEW + OpenGL::GL + hidapi + libcurl + ${wxWidgets_LIBRARIES} + NanoSVG::nanosvg + NanoSVG::nanosvgrast + stb_dxt + fastfloat +) if (MSVC) - target_link_libraries(libslic3r_gui Setupapi.lib) + target_link_libraries(libslic3r_gui PUBLIC Setupapi.lib) elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") - target_link_libraries(libslic3r_gui ${DBUS_LIBRARIES}) + target_link_libraries(libslic3r_gui PUBLIC ${DBUS_LIBRARIES}) elseif (APPLE) - target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} ${COREWLAN_LIBRARY}) + target_link_libraries(libslic3r_gui PUBLIC ${DISKARBITRATION_LIBRARY} ${COREWLAN_LIBRARY}) endif() #if (SLIC3R_STATIC) @@ -410,8 +485,10 @@ endif () # link these libraries. if (UNIX AND NOT APPLE) find_package(GTK${SLIC3R_GTK} REQUIRED) - target_include_directories(libslic3r_gui PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS}) - target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig) + find_package(PkgConfig REQUIRED) + + pkg_search_module(WEBKIT2GTK REQUIRED IMPORTED_TARGET webkit2gtk-4.0 webkit2gtk-4.1) + target_link_libraries(libslic3r_gui PUBLIC ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig PkgConfig::WEBKIT2GTK) endif () # Add a definition so that we can tell we are compiling slic3r. diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 3647ab5..55c9470 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -88,7 +88,6 @@ bool Bed3D::set_shape(const Pointfs & bed_shape, const BoundingBox bbox = m_contour.contour.bounding_box(); if (!bbox.defined) throw RuntimeError(std::string("Invalid bed shape")); - m_polygon = offset(m_contour.contour, (float)bbox.radius() * 1.7f, jtRound, scale_(0.5)).front(); m_triangles.reset(); m_gridlines.reset(); @@ -107,16 +106,6 @@ bool Bed3D::set_shape(const Pointfs & bed_shape, return true; } -bool Bed3D::contains(const Point& point) const -{ - return m_polygon.contains(point); -} - -Point Bed3D::point_projection(const Point& point) const -{ - return m_polygon.point_projection(point); -} - void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture) { render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_texture, false); @@ -522,10 +511,10 @@ void Bed3D::render_default(bool bottom, bool picking, bool show_texture, const T if (show_texture) { // draw grid -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(1.5f * m_scale_factor)); +#endif // !SLIC3R_OPENGL_ES m_gridlines.set_color(has_model && !bottom ? DEFAULT_SOLID_GRID_COLOR : DEFAULT_TRANSPARENT_GRID_COLOR); m_gridlines.render(); } @@ -553,10 +542,10 @@ void Bed3D::render_contour(const Transform3d& view_matrix, const Transform3d& pr glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); // draw contour -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(1.5f * m_scale_factor)); +#endif // !SLIC3R_OPENGL_ES m_contourlines.render(); glsafe(::glDisable(GL_BLEND)); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index f4512b2..92ae9d1 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -37,8 +37,6 @@ private: BoundingBoxf3 m_extended_bounding_box; // Print bed polygon ExPolygon m_contour; - // Slightly expanded print bed polygon, for collision detection. - Polygon m_polygon; GLModel m_triangles; GLModel m_gridlines; GLModel m_contourlines; @@ -78,11 +76,6 @@ public: // Bounding box around the print bed, axes and model, for rendering. const BoundingBoxf3& extended_bounding_box() const { return m_extended_bounding_box; } - // Check against an expanded 2d bounding box. - //FIXME shall one check against the real build volume? - bool contains(const Point& point) const; - Point point_projection(const Point& point) const; - void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture); void render_axes(); void render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 3835052..f588105 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -7,6 +7,8 @@ #include "BitmapCache.hpp" #include "Camera.hpp" +#include "Gizmos/GLGizmoMmuSegmentation.hpp" + #include "libslic3r/BuildVolume.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" @@ -136,10 +138,10 @@ void GLVolume::NonManifoldEdges::render() { update(); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!GUI::OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(2.0f)); +#endif // !SLIC3R_OPENGL_ES GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); if (shader == nullptr) @@ -148,12 +150,16 @@ void GLVolume::NonManifoldEdges::render() const GUI::Camera& camera = GUI::wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_parent.world_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.5f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (GUI::OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.5f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES m_model.set_color(complementary(m_parent.render_color)); m_model.render(); } @@ -259,14 +265,6 @@ void GLVolume::set_render_color(bool force_transparent) } } else { - //B22 - /*if (hover == HS_Select) - set_render_color(HOVER_SELECT_COLOR); - else if (hover == HS_Deselect) - set_render_color(HOVER_DESELECT_COLOR); - else if (selected) - set_render_color(outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR); - else if (disabled)*/ if (disabled) set_render_color(DISABLED_COLOR); else if (outside && shader_outside_printer_detection_enabled) @@ -483,7 +481,7 @@ int GLVolumeCollection::load_object_volume( return int(this->volumes.size() - 1); } -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES int GLVolumeCollection::load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh) @@ -491,19 +489,20 @@ int GLVolumeCollection::load_wipe_tower_preview( int GLVolumeCollection::load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width) -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES { if (height == 0.0f) height = 0.1f; // Because the GLVolume is also used for arrangement, it must be safely larger // than the actual extruded tower, otherwise the arranged tower ends up out of bed. - float offset = 0.3; + const float offset = 0.3f; pos_x -= offset; pos_y -= offset; width += 2.f * offset; depth += 2.f * offset; brim_width += offset; + static const float brim_height = 0.2f; // const float scaled_brim_height = brim_height / height; @@ -577,10 +576,10 @@ int GLVolumeCollection::load_wipe_tower_preview( volumes.emplace_back(new GLVolume(color)); GLVolume& v = *volumes.back(); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES if (out_mesh != nullptr) *out_mesh = mesh; -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES v.model.init_from(mesh); v.model.set_color(color); v.mesh_raycaster = std::make_unique(std::make_shared(mesh)); @@ -733,16 +732,19 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab if (to_render.empty()) return; - GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); - if (shader == nullptr) + GLShaderProgram* curr_shader = GUI::wxGetApp().get_current_shader(); + GLShaderProgram* sink_shader = GUI::wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* edges_shader = GUI::wxGetApp().get_shader("dashed_lines"); +#else + GLShaderProgram* edges_shader = GUI::OpenGLManager::get_gl_info().is_core_profile() ? GUI::wxGetApp().get_shader("dashed_thick_lines") : GUI::wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES + GLShaderProgram* mmu_painted_shader = GUI::wxGetApp().get_shader("mm_gouraud"); + if (curr_shader == nullptr || sink_shader == nullptr || edges_shader == nullptr || mmu_painted_shader == nullptr) return; - GLShaderProgram* sink_shader = GUI::wxGetApp().get_shader("flat"); -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* edges_shader = GUI::OpenGLManager::get_gl_info().is_core_profile() ? GUI::wxGetApp().get_shader("dashed_thick_lines") : GUI::wxGetApp().get_shader("flat"); -#else - GLShaderProgram* edges_shader = GUI::wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = curr_shader; + shader->stop_using(); if (type == ERenderType::Transparent) { glsafe(::glEnable(GL_BLEND)); @@ -754,12 +756,41 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab if (disable_cullface) glsafe(::glDisable(GL_CULL_FACE)); + + // This block is here to render the pained triangles. It is not very nice, but it works. + // There is a cache that holds the OpenGL models of the painted areas to render, one for + // each ModelVolume. The cache is invalidated based on changes in extruder_colors, + // default extruder idx and timestamp of the painted data. The data belonging to objects + // // which no longer exist are removed from the cache periodically. + const ModelObjectPtrs& model_objects = GUI::wxGetApp().model().objects; + const std::vector extruders_colors = GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + const bool is_render_as_mmu_painted_enabled = !model_objects.empty() && !extruders_colors.empty(); + + if (m_mm_paint_cache.extruders_colors != extruders_colors) { + m_mm_paint_cache.extruders_colors = extruders_colors; + m_mm_paint_cache.volume_data.clear(); + } + auto time_now = std::chrono::system_clock::now(); + + + + for (GLVolumeWithIdAndZ& volume : to_render) { - const Transform3d& world_matrix = volume.first->world_matrix(); + if (!volume.first->is_active) + continue; + + const Transform3d world_matrix = volume.first->world_matrix(); + const Matrix3d world_matrix_inv_transp = world_matrix.linear().inverse().transpose(); + const Matrix3d view_normal_matrix = view_matrix.linear() * world_matrix_inv_transp; + const int obj_idx = volume.first->object_idx(); + const int vol_idx = volume.first->volume_idx(); + const bool render_as_mmu_painted = is_render_as_mmu_painted_enabled && !volume.first->selected && + !volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower && obj_idx >= 0 && vol_idx >= 0 && + !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty() && + type != GLVolumeCollection::ERenderType::Transparent; // to filter out shells (not very nice) volume.first->set_render_color(true); // render sinking contours of non-hovered volumes - shader->stop_using(); if (sink_shader != nullptr) { sink_shader->start_using(); if (m_show_sinking_contours) { @@ -770,50 +801,114 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab } sink_shader->stop_using(); } - shader->start_using(); - shader->set_uniform("z_range", m_z_range); - shader->set_uniform("clipping_plane", m_clipping_plane); - shader->set_uniform("use_color_clip_plane", m_use_color_clip_plane); - shader->set_uniform("color_clip_plane", m_color_clip_plane); - shader->set_uniform("uniform_color_clip_plane_1", m_color_clip_plane_colors[0]); - shader->set_uniform("uniform_color_clip_plane_2", m_color_clip_plane_colors[1]); - shader->set_uniform("print_volume.type", static_cast(m_print_volume.type)); - shader->set_uniform("print_volume.xy_data", m_print_volume.data); - shader->set_uniform("print_volume.z_data", m_print_volume.zs); - shader->set_uniform("volume_world_matrix", world_matrix); - shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); - shader->set_uniform("slope.volume_world_normal_matrix", static_cast(world_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast())); - shader->set_uniform("slope.normal_z", m_slope.normal_z); + if (render_as_mmu_painted && shader != mmu_painted_shader) + shader = mmu_painted_shader; + else if (!render_as_mmu_painted && shader != curr_shader) + shader = curr_shader; + + if (render_as_mmu_painted) { + shader->start_using(); + const std::array clp_data = { 0.0f, 0.0f, 1.0f, FLT_MAX }; + const std::array z_range = { -FLT_MAX, FLT_MAX }; + const bool is_left_handed = volume.first->is_left_handed(); + shader->set_uniform("volume_world_matrix", world_matrix); + shader->set_uniform("volume_mirrored", is_left_handed); + shader->set_uniform("clipping_plane", clp_data); + shader->set_uniform("z_range", z_range); + shader->set_uniform("view_model_matrix", view_matrix * world_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; + const size_t extruder_idx = ModelVolume::get_extruder_color_idx(model_volume, GUI::wxGetApp().extruders_edited_cnt()); + + + // This block retrieves the painted geometry from the cache or adds it to it. + ObjectID vol_id = model_volume.id(); + auto it = m_mm_paint_cache.volume_data.find(vol_id); + GUI::TriangleSelectorMmGui* ts = nullptr; + uint64_t timestamp = model_volume.mm_segmentation_facets.timestamp(); + if (it == m_mm_paint_cache.volume_data.end() || it->second.extruder_id != extruder_idx || timestamp != it->second.mm_timestamp) { + auto ts_uptr = std::make_unique(model_volume.mesh(), m_mm_paint_cache.extruders_colors, m_mm_paint_cache.extruders_colors[extruder_idx]); + ts = ts_uptr.get(); + ts->deserialize(model_volume.mm_segmentation_facets.get_data(), true); + ts->request_update_render_data(); + m_mm_paint_cache.volume_data[vol_id] = MMPaintCachePerVolume{ extruder_idx, std::move(ts_uptr), std::chrono::system_clock::now(), timestamp }; + } + else { + ts = it->second.triangle_selector_mm.get(); + it->second.time_used = time_now; + } + + + ts->render(nullptr, world_matrix); + + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + + shader->stop_using(); + } + else { + shader->start_using(); + shader->set_uniform("z_range", m_z_range); + shader->set_uniform("clipping_plane", m_clipping_plane); + shader->set_uniform("use_color_clip_plane", m_use_color_clip_plane); + shader->set_uniform("color_clip_plane", m_color_clip_plane); + shader->set_uniform("uniform_color_clip_plane_1", m_color_clip_plane_colors[0]); + shader->set_uniform("uniform_color_clip_plane_2", m_color_clip_plane_colors[1]); + shader->set_uniform("print_volume.type", static_cast(m_print_volume.type)); + shader->set_uniform("print_volume.xy_data", m_print_volume.data); + shader->set_uniform("print_volume.z_data", m_print_volume.zs); + shader->set_uniform("volume_world_matrix", world_matrix); + shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); + shader->set_uniform("slope.volume_world_normal_matrix", static_cast(world_matrix_inv_transp.cast())); + shader->set_uniform("slope.normal_z", m_slope.normal_z); #if ENABLE_ENVIRONMENT_MAP - unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id(); - bool use_environment_texture = environment_texture_id > 0 && GUI::wxGetApp().app_config->get_bool("use_environment_map"); - shader->set_uniform("use_environment_tex", use_environment_texture); - if (use_environment_texture) - glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id)); + unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id(); + bool use_environment_texture = environment_texture_id > 0 && GUI::wxGetApp().app_config->get_bool("use_environment_map"); + shader->set_uniform("use_environment_tex", use_environment_texture); + if (use_environment_texture) + glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id)); #endif // ENABLE_ENVIRONMENT_MAP - glcheck(); + glcheck(); - volume.first->model.set_color(volume.first->render_color); - const Transform3d model_matrix = world_matrix; - shader->set_uniform("view_model_matrix", view_matrix * model_matrix); - shader->set_uniform("projection_matrix", projection_matrix); - const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); - shader->set_uniform("view_normal_matrix", view_normal_matrix); - volume.first->render(); + volume.first->model.set_color(volume.first->render_color); + shader->set_uniform("view_model_matrix", view_matrix * world_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + volume.first->render(); #if ENABLE_ENVIRONMENT_MAP - if (use_environment_texture) - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + if (use_environment_texture) + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); #endif // ENABLE_ENVIRONMENT_MAP - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + shader->stop_using(); + } } + + // Purge the painted triangles cache from everything that was not used for some time. + // Only do this occasionally (once a second). + using namespace std::chrono_literals; + static auto time_since_last_check = time_now; + if (time_now - time_since_last_check > 1000ms) + for (auto it = m_mm_paint_cache.volume_data.begin(); it != m_mm_paint_cache.volume_data.end(); ) { + auto it_delete = it; // The iterator to the deleted element will be invalidated, the others will not. + ++it; + if (time_now - it_delete->second.time_used > 5000ms) + m_mm_paint_cache.volume_data.erase(it_delete); + } + + if (m_show_sinking_contours) { - shader->stop_using(); if (sink_shader != nullptr) { sink_shader->start_using(); for (GLVolumeWithIdAndZ& volume : to_render) { @@ -825,12 +920,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDepthFunc(GL_LESS)); } } - sink_shader->start_using(); + sink_shader->stop_using(); } - shader->start_using(); } - shader->stop_using(); if (edges_shader != nullptr) { edges_shader->start_using(); if (m_show_non_manifold_edges && GUI::wxGetApp().app_config->get_bool("non_manifold_edges")) { @@ -840,7 +933,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab } edges_shader->stop_using(); } - shader->start_using(); + + curr_shader->start_using(); if (disable_cullface) glsafe(::glEnable(GL_CULL_FACE)); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 5268adc..1348618 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -8,6 +8,9 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Color.hpp" +#include "libslic3r/ObjectID.hpp" + +#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "GLModel.hpp" #include "MeshUtils.hpp" @@ -381,6 +384,18 @@ private: bool m_show_non_manifold_edges{ true }; bool m_use_raycasters{ true }; + struct MMPaintCachePerVolume { + size_t extruder_id; + std::unique_ptr triangle_selector_mm; + std::chrono::system_clock::time_point time_used; + uint64_t mm_timestamp; + }; + struct MMPaintCache { + std::vector extruders_colors; + std::map volume_data; + }; + mutable MMPaintCache m_mm_paint_cache; + public: GLVolumePtrs volumes; @@ -398,13 +413,13 @@ public: int volume_idx, int instance_idx); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES int load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr); #else int load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES // Load SLA auxiliary GLVolumes (for support trees or pad). void load_object_auxiliary( diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 4c77276..027df6c 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -73,6 +73,7 @@ CopyrightsDialog::CopyrightsDialog() m_html->Bind(wxEVT_HTML_LINK_CLICKED, &CopyrightsDialog::onLinkClicked, this); wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); + wxGetApp().SetWindowVariantForButton(buttons->GetCancelButton()); wxGetApp().UpdateDlgDarkUI(this, true); this->SetEscapeId(wxID_CLOSE); this->Bind(wxEVT_BUTTON, &CopyrightsDialog::onCloseDialog, this, wxID_CLOSE); @@ -211,7 +212,7 @@ void CopyrightsDialog::onCloseDialog(wxEvent &) AboutDialog::AboutDialog() : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, format_wxstr(_L("About %s"), wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME), wxDefaultPosition, - wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE) + wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetFont(wxGetApp().normal_font()); @@ -220,7 +221,7 @@ AboutDialog::AboutDialog() wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); auto main_sizer = new wxBoxSizer(wxVERTICAL); - main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 0); + main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo m_logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle("QIDI_Back", 99)); @@ -261,12 +262,12 @@ AboutDialog::AboutDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/); { m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); - wxFont font = get_default_font(this); + wxFont font = wxGetApp().normal_font();// get_default_font(this); const auto text_clr = wxGetApp().get_label_clr_default(); const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); - const int fs = font.GetPointSize(); + const int fs = font.GetPointSize()-1; int size[] = {fs,fs,fs,fs,fs,fs,fs}; m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); m_html->SetBorders(2); @@ -289,6 +290,7 @@ AboutDialog::AboutDialog() "%6%" "

" "%7%
" + "%12% © 2023-2024 QIDI Technology.
" "%8% © 2016-2024 Prusa Research.
" "%9% © 2011-2018 Alessandro Ranellucci.
" "Slic3r %10% " @@ -298,7 +300,7 @@ AboutDialog::AboutDialog() "", bgr_clr_str, text_clr_str, text_clr_str , contributors_str, a_url_str, s_url_str ,version_str , copyright_str, copyright_str - , is_lecensed_str, license_str); + , is_lecensed_str, license_str, copyright_str); m_html->SetPage(text); m_html->SetForegroundColour(wxColour(68, 121, 251)); vsizer->Add(m_html, 1, wxEXPAND | wxTOP, -30); @@ -307,17 +309,25 @@ AboutDialog::AboutDialog() wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); + wxGetApp().SetWindowVariantForButton(buttons->GetCancelButton()); m_copy_rights_btn_id = NewControlId(); auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots); buttons->Insert(0, copy_rights_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this); + wxGetApp().SetWindowVariantForButton(copy_rights_btn); + + m_copy_version_btn_id = NewControlId(); + auto copy_version_btn = new wxButton(this, m_copy_version_btn_id, _L("Copy Version Info")); + buttons->Insert(1, copy_version_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); + copy_version_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyToClipboard, this); + wxGetApp().SetWindowVariantForButton(copy_version_btn); wxGetApp().UpdateDlgDarkUI(this, true); this->SetEscapeId(wxID_CLOSE); this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE); - vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxTOP | wxBOTTOM, 15); + vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); SetSizer(main_sizer); main_sizer->SetSizeHints(this); @@ -365,5 +375,12 @@ void AboutDialog::onCopyrightBtn(wxEvent &) dlg.ShowModal(); } +void AboutDialog::onCopyToClipboard(wxEvent&) +{ + wxTheClipboard->Open(); + wxTheClipboard->SetData(new wxTextDataObject(_L("Version") + " " + std::string(SLIC3R_VERSION))); + wxTheClipboard->Close(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp index 5f93837..a0d33e2 100644 --- a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp @@ -35,16 +35,16 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) { assert(m_imgui && m_db); - m_imgui->set_next_window_pos(pos_x, pos_y, ImGuiCond_Always, 0.5f, 0.0f); + ImGuiPureWrap::set_next_window_pos(pos_x, pos_y, ImGuiCond_Always, 0.5f, 0.0f); - m_imgui->begin(_L("Arrange options"), + ImGuiPureWrap::begin(_u8L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); Settings settings; read_settings(settings, m_db.get()); - m_imgui->text(GUI::format_wxstr( + ImGuiPureWrap::text(GUI::format( _L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); @@ -76,13 +76,13 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) m_db->set_distance_from_bed(settings.d_bed); } - if (m_imgui->checkbox(_L("Enable rotations (slow)"), settings.rotations)) { + if (ImGuiPureWrap::checkbox(_u8L("Enable rotations (slow)"), settings.rotations)) { m_db->set_rotation_enabled(settings.rotations); } if (m_show_xl_combo_predicate() && settings.xl_align >= 0 && - m_imgui->combo(_L("Alignment"), + ImGuiPureWrap::combo(_u8L("Alignment"), {_u8L("Center"), _u8L("Rear left"), _u8L("Front left"), _u8L("Front right"), _u8L("Rear right"), _u8L("Random")}, @@ -94,7 +94,7 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) } // TRN ArrangeDialog - if (m_imgui->combo(_L("Geometry handling"), + if (ImGuiPureWrap::combo(_u8L("Geometry handling"), // TRN ArrangeDialog: Type of "Geometry handling" {_u8L("Fast"), // TRN ArrangeDialog: Type of "Geometry handling" @@ -111,7 +111,7 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) ImGui::Separator(); - if (m_imgui->button(_L("Reset defaults"))) { + if (ImGuiPureWrap::button(_u8L("Reset defaults"))) { arr2::ArrangeSettingsDb::Values df = m_db->get_defaults(); m_db->set_distance_from_objects(df.d_obj); m_db->set_distance_from_bed(df.d_bed); @@ -128,11 +128,11 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) ImGui::SameLine(); - if (m_imgui->button(_L("Arrange")) && m_on_arrange_btn) { + if (ImGuiPureWrap::button(_u8L("Arrange")) && m_on_arrange_btn) { m_on_arrange_btn(); } - m_imgui->end(); + ImGuiPureWrap::end(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 7b10099..bd79f4a 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -191,7 +191,7 @@ void BackgroundSlicingProcess::process_sla() sizes.emplace_back(size); } ThumbnailsList thumbnails = this->render_thumbnails(ThumbnailsParams{sizes, true, true, true, true }); - m_sla_print->export_print(export_path, thumbnails); + m_sla_print->export_print(export_path, thumbnails); m_print->set_status(100, GUI::format(_L("Masked SLA file exported to %1%"), export_path)); } else if (! m_upload_job.empty()) { @@ -742,9 +742,22 @@ void BackgroundSlicingProcess::prepare_upload() m_upload_job.upload_data.upload_path = output_name_str; } else { m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - - ThumbnailsList thumbnails = this->render_thumbnails( - ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); + + auto [thumbnails_list, errors] = GCodeThumbnails::make_and_check_thumbnail_list(current_print()->full_print_config()); + + if (errors != enum_bitmask()) { + std::string error_str = format("Invalid thumbnails value:"); + error_str += GCodeThumbnails::get_error_string(errors); + throw Slic3r::ExportError(error_str); + } + + Vec2ds sizes; + if (!thumbnails_list.empty()) { + sizes.reserve(thumbnails_list.size()); + for (const auto& [format, size] : thumbnails_list) + sizes.emplace_back(size); + } + ThumbnailsList thumbnails = this->render_thumbnails(ThumbnailsParams{ sizes, true, true, true, true }); m_sla_print->export_print(source_path.string(),thumbnails, m_upload_job.upload_data.upload_path.filename().string()); } diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 71aa645..f5431d0 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -290,6 +290,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf } m_canvas->repaint(tem_shape); }); + m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); }); m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); }); wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL); @@ -298,6 +299,8 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf left_sizer->Add(exclude_panel, 0, wxEXPAND); left_sizer->Add(texture_panel, 0, wxEXPAND); left_sizer->Add(model_panel, 0, wxEXPAND); + left_sizer->Add(texture_panel, 1, wxEXPAND); + left_sizer->Add(model_panel, 1, wxEXPAND); wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); @@ -319,7 +322,7 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& tit ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Settings")); optgroup->label_width = 10; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + optgroup->on_change = [this](t_config_option_key opt_key, boost::any value) { update_shape(); }; @@ -378,7 +381,7 @@ wxPanel* BedShapePanel::init_texture_panel() ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Texture")); optgroup->label_width = 10; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + optgroup->on_change = [this](t_config_option_key opt_key, boost::any value) { update_shape(); }; @@ -451,7 +454,7 @@ wxPanel* BedShapePanel::init_model_panel() ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Model")); optgroup->label_width = 10; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + optgroup->on_change = [this](t_config_option_key opt_key, boost::any value) { update_shape(); }; @@ -525,7 +528,7 @@ wxPanel* BedShapePanel::init_model_panel() //B52 void BedShapePanel::set_shape(const ConfigOptionPoints &points1, const ConfigOptionPoints &points2) { - BedShape shape(points1,points2); + BedShape shape(points1, points2); m_shape_options_book->SetSelection(int(shape.get_page_type())); shape.apply_optgroup_values(m_optgroups[int(shape.get_page_type())]); diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 1fca5b4..799716e 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -472,14 +472,19 @@ wxBitmapBundle *BitmapCache::from_png_of_login(const std::string &bitmap_name, u int radius = height / 2; int cx = image.GetWidth() / 2; int cy = image.GetHeight() / 2; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < height; ++x) { + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < height; ++x) + { int dx = x - radius; int dy = y - radius; - if (dx * dx + dy * dy <= radius * radius) { + if (dx * dx + dy * dy <= radius * radius) + { image.SetRGB(x, y, image.GetRed(cx + dx, cy + dy), image.GetGreen(cx + dx, cy + dy), image.GetBlue(cx + dx, cy + dy)); image.SetAlpha(x, y, 255); - } else { + } else + { image.SetRGB(x, y, 38, 38, 41); image.SetAlpha(x, y, 0); } diff --git a/src/slic3r/GUI/BitmapComboBox.hpp b/src/slic3r/GUI/BitmapComboBox.hpp index 23da5d7..4cefa29 100644 --- a/src/slic3r/GUI/BitmapComboBox.hpp +++ b/src/slic3r/GUI/BitmapComboBox.hpp @@ -5,6 +5,7 @@ #include #include "Widgets/ComboBox.hpp" + #include "GUI_Utils.hpp" // --------------------------------- diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index b4a84e1..081e4c4 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -1,5 +1,5 @@ #include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers -#include "slic3r/Utils/Udp.hpp" + #include "BonjourDialog.hpp" #include @@ -137,7 +137,6 @@ bool BonjourDialog::show_and_lookup() timer->Start(1000); on_timer_process(); - // The background thread needs to queue messages for this dialog // and for that it needs a valid pointer to it (mandated by the wxWidgets API). // Here we put the pointer under a shared_ptr and protect it by a mutex, @@ -171,7 +170,7 @@ bool BonjourDialog::show_and_lookup() // Note: More can be done here when we support discovery of hosts other than Octoprint and SL1 - Bonjour::TxtKeys txt_keys{"version", "model"}; + Bonjour::TxtKeys txt_keys { "version", "model" }; bonjour = Bonjour("octoprint") .set_txt_keys(std::move(txt_keys)) @@ -215,58 +214,54 @@ wxString BonjourDialog::get_selected() const void BonjourDialog::on_reply(BonjourReplyEvent &e) { - if (replies->find(e.reply) != replies->end()) { - // We already have this reply - return; - } + if (replies->find(e.reply) != replies->end()) { + // We already have this reply + return; + } - // Filter replies based on selected technology - const auto model = e.reply.txt_data.find("model"); - const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; - if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { - return; - } + // Filter replies based on selected technology + const auto model = e.reply.txt_data.find("model"); + const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; + if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { + return; + } - replies->insert(std::move(e.reply)); + replies->insert(std::move(e.reply)); - auto selected = get_selected(); + auto selected = get_selected(); - wxWindowUpdateLocker freeze_guard(this); - (void) freeze_guard; + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; - list->DeleteAllItems(); + list->DeleteAllItems(); - // The whole list is recreated so that we benefit from it already being sorted in the set. - // (And also because wxListView's sorting API is bananas.) - for (const auto &reply : *replies) { - auto item = list->InsertItem(0, reply.full_address); - list->SetItem(item, 1, reply.hostname); - list->SetItem(item, 2, reply.service_name); + // The whole list is recreated so that we benefit from it already being sorted in the set. + // (And also because wxListView's sorting API is bananas.) + for (const auto &reply : *replies) { + auto item = list->InsertItem(0, reply.full_address); + list->SetItem(item, 1, reply.hostname); + list->SetItem(item, 2, reply.service_name); - if (tech == ptFFF) { - const auto it = reply.txt_data.find("version"); - if (it != reply.txt_data.end()) { - list->SetItem(item, 3, GUI::from_u8(it->second)); - } - } - } + if (tech == ptFFF) { + const auto it = reply.txt_data.find("version"); + if (it != reply.txt_data.end()) { + list->SetItem(item, 3, GUI::from_u8(it->second)); + } + } + } - const int em = GUI::wxGetApp().em_unit(); + const int em = GUI::wxGetApp().em_unit(); - for (int i = 0; i < list->GetColumnCount(); i++) { - list->SetColumnWidth(i, wxLIST_AUTOSIZE); - if (list->GetColumnWidth(i) < 10 * em) { - list->SetColumnWidth(i, 10 * em); - } - } + for (int i = 0; i < list->GetColumnCount(); i++) { + list->SetColumnWidth(i, wxLIST_AUTOSIZE); + if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); } + } - if (!selected.IsEmpty()) { - // Attempt to preserve selection - auto hit = list->FindItem(-1, selected); - if (hit >= 0) { - list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - } - } + if (!selected.IsEmpty()) { + // Attempt to preserve selection + auto hit = list->FindItem(-1, selected); + if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } + } } // B29 diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index f45baaa..8b61930 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -370,6 +370,7 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo auto& [near_z, far_z] = ret; set_distance(DefaultDistance); + // box in eye space const BoundingBoxf3 eye_box = box.transformed(m_view_matrix); near_z = -eye_box.max.z(); diff --git a/src/slic3r/GUI/CameraUtils.cpp b/src/slic3r/GUI/CameraUtils.cpp index 99d022e..0b12500 100644 --- a/src/slic3r/GUI/CameraUtils.cpp +++ b/src/slic3r/GUI/CameraUtils.cpp @@ -1,9 +1,20 @@ #include "CameraUtils.hpp" -#include // projecting points -#include + +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include +#include +#include +#include #include "slic3r/GUI/3DScene.hpp" // GLVolume #include "libslic3r/Geometry/ConvexHull.hpp" +#include "admesh/stl.h" +#include "libslic3r/Exception.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/GLModel.hpp" using namespace Slic3r; using namespace GUI; diff --git a/src/slic3r/GUI/CameraUtils.hpp b/src/slic3r/GUI/CameraUtils.hpp index c3e938e..06bbe8c 100644 --- a/src/slic3r/GUI/CameraUtils.hpp +++ b/src/slic3r/GUI/CameraUtils.hpp @@ -1,10 +1,18 @@ #ifndef slic3r_CameraUtils_hpp_ #define slic3r_CameraUtils_hpp_ +#include + #include "Camera.hpp" #include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" + namespace Slic3r { class GLVolume; + +namespace GUI { +struct Camera; +} // namespace GUI } namespace Slic3r::GUI { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 735e23e..e776a4a 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -350,7 +350,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool has_ironing = config->opt_bool("ironing"); //w33 for (auto el : {"ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed", "ironing_pattern"}) - toggle_field(el, has_ironing); + toggle_field(el, has_ironing); bool have_sequential_printing = config->opt_bool("complete_objects"); for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" }) @@ -381,9 +381,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("min_feature_size", have_arachne); toggle_field("min_bead_width", have_arachne); toggle_field("thin_walls", !have_arachne); - //w21 - bool is_top_one_wall = config->opt_enum("top_one_wall_type") != TopOneWallType::Disable; - toggle_field("top_area_threshold", is_top_one_wall); //w38 bool has_detect_overhang_wall = config->opt_bool("overhangs"); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 831f42a..eb31c69 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,6 +1,7 @@ // FIXME: extract absolute units -> em #include "ConfigWizard_private.hpp" +#include "ConfigWizardWebViewPage.hpp" #include #include @@ -56,15 +57,15 @@ #include "format.hpp" #include "MsgDialog.hpp" #include "UnsavedChangesDialog.hpp" +#include "UpdatesUIManager.hpp" +#include "PresetArchiveDatabase.hpp" +#include "Plater.hpp" #include "slic3r/Utils/AppUpdater.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/Config/Version.hpp" +#include "WebUserLoginDialog.hpp" + -#if defined(__linux__) && defined(__WXGTK3__) -#define wxLinux_gtk3 true -#else -#define wxLinux_gtk3 false -#endif //defined(__linux__) && defined(__WXGTK3__) namespace Slic3r { namespace GUI { @@ -74,6 +75,22 @@ using Config::Snapshot; using Config::SnapshotDB; +ConfigWizardLoadingDialog::ConfigWizardLoadingDialog(wxWindow* parent, const wxString& message) + : wxDialog(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxFRAME_FLOAT_ON_PARENT) +{ + auto* text = new wxStaticText(this, wxID_ANY, message, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); + auto* vsizer = new wxBoxSizer(wxVERTICAL); + auto *top_sizer = new wxBoxSizer(wxVERTICAL); + vsizer->Add(text, 1, wxEXPAND); + top_sizer->Add(vsizer, 1, wxEXPAND | wxALL, 15); + SetSizer(top_sizer); + #ifdef _WIN32 + wxGetApp().UpdateDlgDarkUI(this); + #endif + Fit(); +} + + // Configuration data structures extensions needed for the wizard bool Bundle::load(fs::path source_path, BundleLocation location, bool ais_qidi_bundle) @@ -117,6 +134,7 @@ BundleMap BundleMap::load() { BundleMap res; + const PresetArchiveDatabase* pad = wxGetApp().plater()->get_preset_archive_database(); const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); const auto archive_dir = (boost::filesystem::path(Slic3r::data_dir()) / "cache" / "vendor").make_preferred(); const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); @@ -158,11 +176,11 @@ BundleMap BundleMap::load() fs::path idx_path (archive_dir / (id + ".idx")); if (!boost::filesystem::exists(idx_path)) { - BOOST_LOG_TRIVIAL(error) << format("Missing index %1% when loading bundle %2%. Going to search for it in cache folder.", idx_path.string(), id); + BOOST_LOG_TRIVIAL(info) << format("Missing index %1% when loading bundle %2%. Going to search for it in cache folder.", idx_path.string(), id); idx_path = fs::path(cache_dir / (id + ".idx")); } if (!boost::filesystem::exists(idx_path)) { - BOOST_LOG_TRIVIAL(error) << format("Missing index %1% when loading bundle %2%. Going to search for it in vendor folder. Is it a 3rd party profile?", idx_path.string(), id); + BOOST_LOG_TRIVIAL(info) << format("Missing index %1% when loading bundle %2%. Going to search for it in vendor folder. Is it a 3rd party profile?", idx_path.string(), id); idx_path = fs::path(vendor_dir / (id + ".idx")); } if (!boost::filesystem::exists(idx_path)) { @@ -185,6 +203,21 @@ BundleMap BundleMap::load() } const auto recommended = recommended_it->config_version; VendorProfile vp; + // Check if in selected repo. + try { + vp = VendorProfile::from_ini(dir_entry, false); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << format("Could not load bundle %1% due to corrupted profile file %2%. Message: %3%", id, dir_entry.path().string(), e.what()); + continue; + } + if (vp.repo_id.empty() || !pad->is_selected_repository_by_id(vp.repo_id)) { + continue; + } + // Don't load + if (vp.config_version > recommended) + continue; + // Load full VP. try { vp = VendorProfile::from_ini(dir_entry, true); } @@ -192,10 +225,6 @@ BundleMap BundleMap::load() BOOST_LOG_TRIVIAL(error) << format("Could not load bundle %1% due to corrupted profile file %2%. Message: %3%", id, dir_entry.path().string(), e.what()); continue; } - // Don't load - if (vp.config_version > recommended) - continue; - Bundle bundle; if (bundle.load(dir_entry.path(), dir.second)) res.emplace(std::move(id), std::move(bundle)); @@ -252,6 +281,7 @@ const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png" PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) : wxPanel(parent) , vendor_id(vendor.id) + , vendor_repo_id(vendor.repo_id) , width(0) { wxGetApp().UpdateDarkUI(this); @@ -527,10 +557,13 @@ ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxStrin SetSizer(sizer); + /* ysFIXME - delete after testing and release + // Update!!! -> it looks like this workaround is no need any more after update of the wxWidgets to 3.2.0 + // There is strange layout on Linux with GTK3, - // see https://github.com/qidi3d/QIDISlicer/issues/5103 and https://github.com/qidi3d/QIDISlicer/issues/4861 // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages if (!wxLinux_gtk3) + */ this->Hide(); Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { @@ -580,7 +613,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent) { welcome_text->Hide(); cbox_reset->Hide(); - cbox_integrate->Hide(); + cbox_integrate->Hide(); } void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) @@ -598,6 +631,66 @@ void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) #endif } +PageUpdateManager::PageUpdateManager(ConfigWizard* parent_in) + : ConfigWizardPage(parent_in, _L("Configuration sources"), _L("Configuration Sources")) +{ + this->SetFont(wxGetApp().normal_font()); + + const int em = em_unit(this); + + manager = std::make_unique(this, wxGetApp().plater()->get_preset_archive_database(), em); + + warning_text = new wxStaticText(this, wxID_ANY, _L("WARNING: Select at least one source.")); + warning_text->SetFont(wxGetApp().bold_font()); + warning_text->Hide(); + + warning_text->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& event) { + const bool show_warning = !manager->has_selections(); + if (warning_text->IsShown() != show_warning) { + warning_text->Show(show_warning); + this->Layout(); + } + }); + + auto sizer = manager->get_sizer(); + sizer->Add(warning_text, 0, wxEXPAND | wxTOP, 2 * em); + + append(sizer, 0, wxTOP, 2 * em); + + this->Bind(wxEVT_SHOW, [this, parent_in](wxShowEvent& evt) { + + auto revert_page_selection = [this]() -> void { + CallAfter([this]() { + wizard_p()->index->go_to(this); + if (!this->IsShown()) + this->Show(); + }); + }; + + if (evt.IsShown()) + is_active = true; + else if (is_active && parent_in->IsShown()) { + if (manager->has_selections()) { + + if (wizard_p()->can_clear_printer_pages()) { + wxBusyCursor wait; + if (manager->set_selected_repositories()) { + wizard_p()->is_config_from_archive = true; + wizard_p()->set_config_updated_from_archive(true, true); + } + else + revert_page_selection(); + } + else + revert_page_selection(); + } + else + revert_page_selection(); + + is_active = false; + } + }); +} PagePrinters::PagePrinters(ConfigWizard *parent, wxString title, @@ -651,6 +744,53 @@ void PagePrinters::select_all(bool select, bool alternates) } } +void PagePrinters::unselect_all_presets() +{ + assert(!printer_pickers.empty()); + const std::string vendor_id = printer_pickers[0]->vendor_id; + + PresetBundle* preset_bundle{ nullptr }; + for (const auto& [bundle_name, bundle] : wizard_p()->bundles) { + if (bundle_name == vendor_id) { + preset_bundle = bundle.preset_bundle.get(); + break; + } + } + + if (preset_bundle) { + auto unselect = [preset_bundle](const std::string& vendor_id, const std::string& model, const std::string& variant) { + for (auto& preset : preset_bundle->printers) { + if (preset.config.opt_string("printer_model") == model + && preset.config.opt_string("printer_variant") == variant) { + preset.is_visible = false; + } + } + }; + + // unselect presets in preset bundle, if related model and variant was checked in Picker + for (auto picker : printer_pickers) { + for (const auto& cb : picker->cboxes) { + if (cb->GetValue()) + unselect(picker->vendor_id, cb->model, cb->variant); + } + + for (const auto& cb : picker->cboxes_alt) { + if (cb->GetValue()) + unselect(picker->vendor_id, cb->model, cb->variant); + } + } + } + + // remove vendor from appconfig_new + AppConfig* appconfig = &wizard_p()->appconfig_new; + + AppConfig::VendorMap new_vendors = appconfig->vendors(); + if (new_vendors.find(vendor_id) != new_vendors.end()) { + new_vendors.erase(vendor_id); + appconfig->set_vendors(new_vendors); + } +} + int PagePrinters::get_width() const { return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, @@ -763,6 +903,10 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); + // html_window->Bind(wxEVT_HTML_LINK_CLICKED, [](wxHtmlLinkEvent& event) { + // wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref()); + // event.Skip(false); + // }); append(html_window, 0, wxEXPAND); list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { @@ -852,9 +996,19 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector(\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); } else { + bool has_medical = false; + for (const Preset *printer : materials->printers) { + if (printer->vendor && printer->vendor->id == "QIDIProMedical") { + has_medical = true; + } + } + // TRN QIDISlicer-Medical ConfigWizard: Materials" + wxString zero_line = _L("The list of validated workflows for Medical One can be found in this article. Profiles for other materials are not verified by the material manufacturer and therefore may not correspond to the current version of the material."); + if (!has_medical) { + zero_line.Clear(); + } // TRN ConfigWizard: Materials : "%1%" = "Filaments"/"SLA materials" wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - if (all_printers) { // TRN ConfigWizard: Materials : "%1%" = "filament"/"SLA material" wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); @@ -865,12 +1019,14 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector" "" "" - "%s

%s" + "" + "%s

%s

%s" "
" "" "" , bgr_clr_str , text_clr_str + , zero_line , first_line , second_line ); @@ -888,13 +1044,16 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector" "" "" - "%s

%s" + "" + "%s

%s

%s" "" "" , bgr_clr_str , text_clr_str + , zero_line , first_line - , second_line); + , second_line + ); for (size_t i = 0; i < printer_names.size(); ++i) { text += wxString::Format("", boost::nowide::widen(printer_names[i])); @@ -997,6 +1156,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected // Refresh type list list_type->Clear(); list_type->append(_L("(All)"), &EMPTY); + std::vector appended_types; if (sel_printers_count > 1) { // If all is selected with other printers // unselect "all" or all printers depending on last value @@ -1031,10 +1191,11 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected break; } } - materials->filter_presets(printer, printer_name, EMPTY, EMPTY, [this](const Preset* p) { + materials->filter_presets(printer, printer_name, EMPTY, EMPTY, [this, &appended_types](const Preset* p) { const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { + if (std::find(appended_types.begin(), appended_types.end(), type) == appended_types.end()) { list_type->append(type, &type); + appended_types.emplace_back(type); } }); } @@ -1045,10 +1206,11 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected list_printer->SetSelection(0); sel_printers_count = list_printer->GetSelections(sel_printers); - materials->filter_presets(nullptr, EMPTY, EMPTY, EMPTY, [this](const Preset* p) { + materials->filter_presets(nullptr, EMPTY, EMPTY, EMPTY, [this, &appended_types](const Preset* p) { const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { + if (std::find(appended_types.begin(), appended_types.end(), type) == appended_types.end()) { list_type->append(type, &type); + appended_types.emplace_back(type); } }); } @@ -1059,10 +1221,11 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected sel_printers_count = list_printer->GetSelections(sel_printers); template_shown = true; materials->filter_presets(nullptr, TEMPLATES, EMPTY, EMPTY, - [this](const Preset* p) { + [this, &appended_types](const Preset* p) { const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { + if (std::find(appended_types.begin(), appended_types.end(), type) == appended_types.end()) { list_type->append(type, &type); + appended_types.emplace_back(type); } }); } @@ -1083,6 +1246,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected list_vendor->Clear(); list_vendor->append(_L("(All)"), &EMPTY); + std::vector appended_vendors; if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { const std::string& type = list_type->get_data(sel_type); // find printer preset @@ -1095,10 +1259,11 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected break; } } - materials->filter_presets(printer, printer_name, type, EMPTY, [this](const Preset* p) { + materials->filter_presets(printer, printer_name, type, EMPTY, [this, &appended_vendors](const Preset* p) { const std::string& vendor = this->materials->get_vendor(p); - if (list_vendor->find(vendor) == wxNOT_FOUND) { + if (std::find(appended_vendors.begin(), appended_vendors.end(), vendor) == appended_vendors.end()) { list_vendor->append(vendor, &vendor); + appended_vendors.emplace_back(vendor); } }); } @@ -1116,6 +1281,7 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected // Refresh material list list_profile->Clear(); + std::vector appended_aliases; clear_compatible_printers_label(); if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { const std::string& type = list_type->get_data(sel_type); @@ -1131,17 +1297,20 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected break; } } - materials->filter_presets(printer, printer_name, type, vendor, [this, &to_list](const Preset* p) { + materials->filter_presets(printer, printer_name, type, vendor, [this, &to_list, &appended_aliases](const Preset* p) { const std::string& section = materials->appconfig_section(); bool checked = wizard_p()->appconfig_new.has(section, p->name); bool was_checked = false; - int cur_i = list_profile->find(p->alias); - if (cur_i == wxNOT_FOUND) { + auto it = std::find(appended_aliases.begin(), appended_aliases.end(), p->alias); + size_t cur_i = 0; + if (it == appended_aliases.end()) { cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) || template_shown ? "" : " *"), &p->alias); + appended_aliases.emplace_back(p->alias); to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); } else { + cur_i = it - appended_aliases.begin(); was_checked = list_profile->IsChecked(cur_i); to_list[cur_i].checked = checked || was_checked; } @@ -1322,7 +1491,7 @@ PageCustom::PageCustom(ConfigWizard *parent) auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL); - profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name }; + profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name, wxGetApp().preset_bundle}; profile_name_editor->Enable(false); cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { @@ -1423,7 +1592,7 @@ Worker::Worker(wxWindow* parent) wxGetApp().SetWindowVariantForButton(button_path); this->Add(button_path, 0, wxEXPAND | wxTOP | wxLEFT, 5); button_path->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { - boost::filesystem::path chosen_dest(boost::nowide::narrow(m_input_path->GetValue())); + boost::filesystem::path chosen_dest(into_u8(m_input_path->GetValue())); wxDirDialog dialog(m_parent, _L("Choose folder") + ":", chosen_dest.string() ); if (dialog.ShowModal() == wxID_OK) @@ -1533,11 +1702,13 @@ bool PageDownloader::on_finish_downloader() const return m_downloader->on_finish(); } -bool DownloaderUtils::Worker::perform_register(const std::string& path_override/* = {}*/) +#ifdef __linux__ +bool DownloaderUtils::Worker::perform_registration_linux = false; +#endif // __linux__ + +bool DownloaderUtils::Worker::perform_download_register(const std::string& path) { - boost::filesystem::path aux_dest (GUI::into_u8(path_name())); - if (!path_override.empty()) - aux_dest = boost::filesystem::path(path_override); + boost::filesystem::path aux_dest (path); boost::system::error_code ec; boost::filesystem::path chosen_dest = boost::filesystem::absolute(aux_dest, ec); if(ec) @@ -1546,11 +1717,15 @@ bool DownloaderUtils::Worker::perform_register(const std::string& path_override/ if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) { std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not exist.") ,chosen_dest.string()); BOOST_LOG_TRIVIAL(error) << err_msg; - show_error(m_parent, err_msg); + show_error(/*m_parent*/ nullptr, err_msg); return false; } BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string(); wxGetApp().app_config->set("url_downloader_dest", chosen_dest.string()); + return perform_url_register(); +} +bool DownloaderUtils::Worker::perform_url_register() +{ #ifdef _WIN32 // Registry key creation for "qidislicer://" URL @@ -1578,7 +1753,7 @@ bool DownloaderUtils::Worker::perform_register(const std::string& path_override/ key_full = key_string; #elif __APPLE__ // Apple registers for custom url in info.plist thus it has to be already registered since build. - // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) + // The url will always trigger opening of QIDIslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) #elif defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) // the performation should be called later during desktop integration perform_registration_linux = true; @@ -1610,12 +1785,12 @@ bool DownloaderUtils::Worker::on_finish() { BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked; if (ac_value && downloader_checked) { // already registered but we need to do it again - if (!perform_register()) + if (!perform_download_register(GUI::into_u8(path_name()))) return false; app_config->set("downloader_url_registered", "1"); } else if (!ac_value && downloader_checked) { // register - if (!perform_register()) + if (!perform_download_register(GUI::into_u8(path_name()))) return false; app_config->set("downloader_url_registered", "1"); } else if (ac_value && !downloader_checked) { @@ -1705,8 +1880,17 @@ void PageMode::serialize_mode(AppConfig *app_config) const app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); } -PageVendors::PageVendors(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) +wxString repo_title(const std::string& repo_id, const std::string& repo_name) +{ + if (repo_name.empty()) + { + return repo_id.empty() ? wxString::FromUTF8("Unknown repo") : format_wxstr("Unnamed repo (ID %1%)", repo_id); + } + return repo_name; +} + +PageVendors::PageVendors(ConfigWizard* parent, std::string repo_id /*= wxEmptyString*/, std::string repo_name) + : ConfigWizardPage(parent, repo_title(repo_id, repo_name),repo_title(repo_id, repo_name)) { const AppConfig &appconfig = this->wizard_p()->appconfig_new; @@ -1717,20 +1901,65 @@ PageVendors::PageVendors(ConfigWizard *parent) // Copy vendors from bundle map to vector, so we can sort it without case sensitivity std::vector> vendors; for (const auto& pair : wizard_p()->bundles) { + if (pair.second.vendor_profile->repo_id != repo_id) + continue; + vendors.emplace_back(boost::algorithm::to_lower_copy(boost::nowide::widen(pair.second.vendor_profile->name)),pair.second.vendor_profile); } + std::sort(vendors.begin(), vendors.end(), [](const std::pair& a, const std::pair& b) { return a.first < b.first; }); for (const std::pair& v : vendors) { const VendorProfile* vendor = v.second; - if (vendor->id == PresetBundle::QIDI_BUNDLE) { continue; } +//! if (vendor->id == PresetBundle::QIDI_BUNDLE) { continue; } if (vendor && vendor->templates_profile) continue; auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { + if (cbox->IsChecked()) { + // create PrinterPages for this vendor, if they aren't created jet + { + auto repo = wizard_p()->get_repo(repo_id); + assert(repo); + if (repo->printers_pages.find(vendor->id) == repo->printers_pages.end()) { + wxWindowUpdateLocker freeze_guard(parent); + wizard_p()->create_vendor_printers_page(repo_id, vendor); + } + } + + wxString user_presets_list{ wxString() }; + int user_presets_cnt { 0 }; + + // Check if some of preset doesn't exist as a user_preset + // to avoid rewrite those user_presets by new installed system presets + const PresetCollection& presets = wizard_p()->bundles.at(vendor->id).preset_bundle.get()->printers; + for (const Preset& preset : presets) + if (!preset.is_default && boost::filesystem::exists(preset.file)) { + user_presets_list += " * " + from_u8(preset.name) + "\n"; + user_presets_cnt++; + } + + if (!user_presets_list.IsEmpty()) { + wxString message = format_wxstr(_L_PLURAL("Existing user preset '%2%' has the same name as one of new system presets from vendor '%1%'.\n" + "Please note that this user preset will be rewritten by the system preset.\n\n" + "Do you still wish to add presets from vendor '%1%'?", + "Existing user presets (%2%) have the same names as some of new system presets from vendor '%1%'.\n" + "Please note that these user presets will be rewritten by the system presets.\n\n" + "Do you still wish to add presets from vendor '%1%'?", + user_presets_cnt), vendor->name, user_presets_list); + + MessageDialog msg(this->GetParent(), message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_NO) { + // uncheck checked ckeckbox + cbox->SetValue(false); + return; + } + } + } + wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); }); @@ -1738,12 +1967,7 @@ PageVendors::PageVendors(ConfigWizard *parent) const bool enabled = acvendors.find(vendor->id) != acvendors.end(); if (enabled) { cbox->SetValue(true); - - auto pages = wizard_p()->pages_3rdparty.find(vendor->id); - wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); - - for (PagePrinters* page : { pages->second.first, pages->second.second }) - if (page) page->install = true; + wizard_p()->create_vendor_printers_page(repo_id, vendor, true); } append(cbox); @@ -2154,12 +2378,12 @@ void ConfigWizardIndex::go_to(size_t i) if (i != item_active && i < items.size() && items[i].page != nullptr) { - auto *new_active = items[i].page; auto *former_active = active_page(); if (former_active != nullptr) { former_active->Hide(); } + auto *new_active = items[i].page; item_active = i; new_active->Show(); @@ -2359,39 +2583,95 @@ void ConfigWizard::priv::load_pages() wxWindowUpdateLocker freeze_guard(q); (void)freeze_guard; - const ConfigWizardPage *former_active = index->active_page(); + const ConfigWizardPage* former_active = index->active_page(); index->clear(); index->add_page(page_welcome); + index->add_page(page_login); - // Printers +// index->add_page(page_update_manager); +// +// if (is_config_from_archive) { +// +// // Printers +// if (!only_sla_mode) +// for (const auto page : pages_fff) +// index->add_page(page); +// +// for (const auto page : pages_msla) +// index->add_page(page); +// +// if (!only_sla_mode) { +// +// for (const auto& repos : repositories) { +// if (!repos.vendors_page) +// continue; +// index->add_page(repos.vendors_page); +// +// // Copy pages names from map to vector, so we can sort it without case sensitivity +// std::vector> sorted_vendors; +// for (const auto& pages : repos.printers_pages) { +// sorted_vendors.emplace_back(boost::algorithm::to_lower_copy(boost::nowide::widen(pages.first)), pages.first); +// } +// std::sort(sorted_vendors.begin(), sorted_vendors.end(), [](const std::pair& a, const std::pair& b) { +// return a.first < b.first; +// }); +// +// for (const std::pair& v : sorted_vendors) { +// const auto& pages = repos.printers_pages.find(v.second); +// if (pages == repos.printers_pages.end()) +// continue; // Should not happen +// for (PagePrinters* page : { pages->second.first, pages->second.second }) +// if (page && page->install) +// index->add_page(page); +// } +// } +// +// index->add_page(page_custom); +// if (page_custom->custom_wanted()) { +// index->add_page(page_firmware); +// index->add_page(page_bed); +// index->add_page(page_bvolume); +// index->add_page(page_diams); +// index->add_page(page_temps); +// } +// +// // Filaments & Materials +// if (any_fff_selected) { index->add_page(page_filaments); } +// // Filaments page if only custom printer is selected +// const AppConfig* app_config = wxGetApp().app_config; +// if (!any_fff_selected && (custom_printer_selected || custom_printer_in_bundle) && (app_config->get("no_templates") == "0")) { +// update_materials(T_ANY); +// index->add_page(page_filaments); +// } +// } +// +// if (any_sla_selected) +// index->add_page(page_sla_materials); +// +// index->add_page(page_update); +// index->add_page(page_downloader); +// index->add_page(page_reload_from_disk); +//#ifdef _WIN32 +// index->add_page(page_files_association); +//#endif // _WIN32 +// index->add_page(page_mode); +// +// } +// +// if (former_active != page_update_manager) { +// if (pages_fff.empty() && pages_msla.empty() && installed_multivendors_repos()) +// index->go_to(repositories[0].vendors_page); // Activate Vendor page, if no one printer is selected +// else +// index->go_to(former_active); // Will restore the active item/page if possible +// } + + // Printers if (!only_sla_mode) index->add_page(page_fff); //B9 - // index->add_page(page_msla); if (!only_sla_mode) { - //B9 - /*index->add_page(page_vendors); - - // Copy pages names from map to vector, so we can sort it without case sensitivity - std::vector> sorted_vendors; - for (const auto& pages : pages_3rdparty) { - sorted_vendors.emplace_back(boost::algorithm::to_lower_copy(boost::nowide::widen(pages.first)), pages.first); - } - std::sort(sorted_vendors.begin(), sorted_vendors.end(), [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }); - - for (const std::pair v : sorted_vendors) { - const auto& pages = pages_3rdparty.find(v.second); - if (pages == pages_3rdparty.end()) - continue; // Should not happen - for ( PagePrinters* page : { pages->second.first, pages->second.second }) - if (page && page->install) - index->add_page(page); - }*/ - index->add_page(page_custom); if (page_custom->custom_wanted()) { index->add_page(page_firmware); @@ -2400,7 +2680,7 @@ void ConfigWizard::priv::load_pages() index->add_page(page_diams); index->add_page(page_temps); } - + // Filaments & Materials if (any_fff_selected) { index->add_page(page_filaments); } // Filaments page if only custom printer is selected @@ -2410,15 +2690,12 @@ void ConfigWizard::priv::load_pages() index->add_page(page_filaments); } } - //B9 - // if (any_sla_selected) { index->add_page(page_sla_materials); } // there should to be selected at least one printer btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle); index->add_page(page_update); - //B9 - // index->add_page(page_downloader); + index->add_page(page_reload_from_disk); #ifdef _WIN32 index->add_page(page_files_association); @@ -2427,6 +2704,7 @@ void ConfigWizard::priv::load_pages() index->go_to(former_active); // Will restore the active item/page if possible + q->Layout(); // This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig q->Refresh(); @@ -2446,7 +2724,15 @@ void ConfigWizard::priv::init_dialog_size() 9*disp_rect.width / 10, 9*disp_rect.height / 10); - const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution + int min_width = em(); + if (only_sla_mode) + for (auto page : pages_msla) + min_width = std::max(min_width, page->get_width()); + else + for (auto page : pages_fff) + min_width = std::max(min_width, page->get_width()); + + const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), min_width + 30 * em()); // XXX: magic constant, I found no better solution if (width_hint < window_rect.width) { window_rect.x += (window_rect.width - width_hint) / 2; window_rect.width = width_hint; @@ -2528,7 +2814,7 @@ void ConfigWizard::priv::load_vendors() void ConfigWizard::priv::add_page(ConfigWizardPage *page) { - const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; + const int proportion = (page == page_login || page == page_filaments || page == page_sla_materials); hscroll_sizer->Add(page, proportion, wxEXPAND); all_pages.push_back(page); } @@ -2542,9 +2828,28 @@ void ConfigWizard::priv::enable_next(bool enable) void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) { switch (start_page) { - case ConfigWizard::SP_PRINTERS: - index->go_to(page_fff); - btn_next->SetFocus(); + case ConfigWizard::SP_PRINTERS: { + // find start + PagePrinters* page = !pages_fff.empty() ? pages_fff[0] : + !pages_msla.empty() ? pages_msla[0] : nullptr; + for (const auto& repo : repositories) { + if (page) + break; + for (const auto& [name, pages] : repo.printers_pages) { + if (pages.first && pages.first->install) { + page = pages.first; + break; + } + if (pages.second && pages.second->install) { + page = pages.second; + break; + } + } + } + + index->go_to(page); + btn_next->SetFocus(); + } break; case ConfigWizard::SP_FILAMENTS: index->go_to(page_filaments); @@ -2595,6 +2900,71 @@ void ConfigWizard::priv::create_3rdparty_pages() } } +ConfigWizard::priv::Repository* ConfigWizard::priv::get_repo(const std::string& repo_id) +{ + auto it = std::find(repositories.begin(), repositories.end(), repo_id); + if (it == repositories.end()) + return nullptr; + return &repositories[it - repositories.begin()]; +} + +void ConfigWizard::priv::create_vendor_printers_page(const std::string& repo_id, const VendorProfile* vendor, bool install/* = false*/, bool from_single_vendor_repo /*= false*/) +{ + bool is_fff_technology = false; + bool is_sla_technology = false; + + for (auto& model: vendor->models) + { + if (!is_fff_technology && model.technology == ptFFF) + is_fff_technology = true; + if (!is_sla_technology && model.technology == ptSLA) + is_sla_technology = true; + + if (is_fff_technology && is_sla_technology) + break; + } + + PagePrinters* pageFFF = nullptr; + PagePrinters* pageSLA = nullptr; + + const bool is_qidi_vendor = vendor->name.find("QIDI") != std::string::npos; + const unsigned indent = from_single_vendor_repo ? 0 : 1; + + if (is_fff_technology) + { + pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name + (is_qidi_vendor ? "" : " FFF"), *vendor, indent, T_FFF); + pageFFF->install = install; + if (only_sla_mode) + only_sla_mode = false; + add_page(pageFFF); + } + + if (is_sla_technology) + { + pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name + (is_qidi_vendor ? "" : " MLSA"), *vendor, indent, T_SLA); + pageSLA->install = install; + add_page(pageSLA); + } + + if (from_single_vendor_repo) + { + // single vendor repository + if (pageFFF) { + pages_fff.emplace_back(pageFFF); + } + if (pageSLA) { + pages_msla.emplace_back(pageSLA); + } + } + if (pageFFF || pageSLA) + { + // multiple vendor repository + auto repo = get_repo(repo_id); + //assert(repo); + //repo->printers_pages.insert({vendor->id, {pageFFF, pageSLA}}); + } +} + void ConfigWizard::priv::set_run_reason(RunReason run_reason) { this->run_reason = run_reason; @@ -2694,8 +3064,18 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker if (page->technology & T_FFF) { page_filaments->clear(); + if (!any_fff_selected) { + // clear all filament's info, when no one printer is selected + filaments.clear(); + aliases_fff.clear(); + } } else if (page->technology & T_SLA) { page_sla_materials->clear(); + if (!any_sla_selected) { + // clear all material's info, when no one printer is selected + sla_materials.clear(); + aliases_sla.clear(); + } } } @@ -2765,23 +3145,62 @@ void ConfigWizard::priv::select_default_materials_for_printer_models(Technology void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) { - auto it = pages_3rdparty.find(vendor->id); - wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); + for (const auto& repo : repositories) { + if (repo.id_name != vendor->repo_id) + continue; - for (PagePrinters* page : { it->second.first, it->second.second }) - if (page) { - if (page->install && !install) - page->select_all(false); - page->install = install; - // if some 3rd vendor is selected, select first printer for them - if (install) - page->printer_pickers[0]->select_one(0, true); - page->Layout(); - } + auto pages = repo.printers_pages.find(vendor->id); + wxCHECK_RET(pages != repo.printers_pages.end(), "Internal error: 3rd party vendor printers page not created"); + for (PagePrinters* page : { pages->second.first, pages->second.second }) + if (page) { + if (page->install && !install) + page->select_all(false); + page->install = install; + // if some 3rd vendor is selected, select first printer for them + if (install) + page->printer_pickers[0]->select_one(0, true); + page->Layout(); + } + + break; + } load_pages(); } +bool ConfigWizard::priv::can_finish() +{ + // if (index->active_page() == page_update_manager) + // return false; + // Set enabling fo "Finish" button -> there should to be selected at least one printer + return any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle; +} + +bool ConfigWizard::priv::can_go_next() +{ + // if (index->active_page() == page_update_manager) + // return page_update_manager->manager->has_selections(); + return true; +} + +bool ConfigWizard::priv::can_show_next() +{ + const bool is_last = index->active_is_last(); + + // if (index->active_page() == page_update_manager && is_last) + // return true; + + return !is_last; +} + +bool ConfigWizard::priv::can_select_all() +{ + // if (index->active_page() == page_update_manager) + // return false; + // set enabling for "Select all..." -> there should to be exist at least one printer page + return !pages_fff.empty() || !pages_msla.empty(); +} + bool ConfigWizard::priv::on_bnt_finish() { wxBusyCursor wait; @@ -2893,7 +3312,6 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) { - //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); MessageDialog msg(q, message, _L("Notice"), wxYES_NO); if (msg.ShowModal() == wxID_YES) select_default_materials_for_printer_models(technology, printer_models); @@ -3075,10 +3493,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) // Desktop integration on Linux - BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->m_downloader->get_perform_registration_linux(); + BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << DownloaderUtils::Worker::perform_registration_linux; if (page_welcome->integrate_desktop()) DesktopIntegrationDialog::perform_desktop_integration(); - if (page_downloader->m_downloader->get_perform_registration_linux()) + if (DownloaderUtils::Worker::perform_registration_linux) DesktopIntegrationDialog::perform_downloader_desktop_integration(); #endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) @@ -3113,8 +3531,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (install_bundles.size() > 0) { // Install bundles from resources or cache / vendor. // Don't create snapshot - we've already done that above if applicable. - - bool install_result = updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false); + GUI_App& app = wxGetApp(); + const auto* archive_db = app.plater()->get_preset_archive_database(); + bool install_result = updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), archive_db->get_selected_archive_repositories(), false); if (!install_result) return false; } else { @@ -3217,6 +3636,36 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } } + // Save used repo into manifest. + std::vector used_repo_ids; + for (const auto& vendor : enabled_vendors) { + // here vendor might be empty - it causes false has_installed_printers : 1 entries in manifest. + if (vendor.second.empty()){ + continue; + } + bool not_empty = false; + for (const auto& it : vendor.second) { + if (!it.second.empty()) { + not_empty = true; + break; + } + } + if (!not_empty) { + continue; + } + + const auto& it = bundles.find(vendor.first); + // This is a last resort solution of missing secret repo in manifest while some of its printers are installed. + if (it == bundles.end()) { + continue; + } + const std::string repo_id = it->second.vendor_profile->repo_id; + if (std::find(used_repo_ids.begin(), used_repo_ids.end(), repo_id) == used_repo_ids.end()) { + used_repo_ids.emplace_back(repo_id); + } + } + wxGetApp().plater()->get_preset_archive_database()->set_installed_printer_repositories(std::move(used_repo_ids)); + // apply materials in app_config for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) if (appconfig_new.has_section(section_name)) @@ -3300,6 +3749,17 @@ void ConfigWizard::priv::update_presets_in_config(const std::string& section, co bool ConfigWizard::priv::check_fff_selected() { + // for (const auto page : pages_fff) + // if (page->any_selected()) + // return true; + + // for (const auto& repos : repositories) + // for (const auto& printers : repos.printers_pages) + // if (const auto page = printers.second.first;// FFF page + // page && page->any_selected()) + // return true; + + // return false; bool ret = page_fff->any_selected(); for (const auto& printer: pages_3rdparty) if (printer.second.first) // FFF page @@ -3309,11 +3769,265 @@ bool ConfigWizard::priv::check_fff_selected() bool ConfigWizard::priv::check_sla_selected() { - bool ret = page_msla->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.second) // SLA page - ret |= printer.second.second->any_selected(); - return ret; + for (const auto page : pages_msla) + if (page->any_selected()) + return true; + + for (const auto& repos : repositories) + for (const auto& printers : repos.printers_pages) + if (const auto page = printers.second.second;// SLA page + page && page->any_selected()) + return true; + + return false; +} + +void ConfigWizard::priv::set_config_updated_from_archive(bool load_installed_printers, bool run_preset_updater) +{ + if (run_preset_updater) { + // This block of preset_updater functions is done in GUI_App::run_wizard before ConfigWizard::run() + // It needs to be also done when repos are confirmed inside wizard. + // Possible optimalization - do not run this block if no repos were changed. + GUI_App& app = wxGetApp(); + // Do blocking sync on every change of archive repos, so user is always offered recent profiles. + const SharedArchiveRepositoryVector &repos = app.plater()->get_preset_archive_database()->get_selected_archive_repositories(); + app.preset_updater->sync_blocking(app.preset_bundle, &app, repos); + // Offer update installation. It used to be offered only when wizard run reason was RR_USER. + app.preset_updater->update_index_db(); + app.preset_updater->config_update(app.app_config->orig_version(), PresetUpdater::UpdateParams::SHOW_TEXT_BOX, repos); + + // We have now probably changed data. We need to rebuild database from which wizards constructs. + // Just reload bundles and upadte installed printer from appconfig_new. + bundles = BundleMap::load(); + } + if (load_installed_printers) { + // Initialize the is_visible flag in printer Presets + for (auto& pair : bundles) + pair.second.preset_bundle->load_installed_printers(appconfig_new); + } + + load_pages_from_archive(); +} + +bool ConfigWizard::priv::any_installed_vendor_for_repo(const std::string& repo_id, std::vector& vendors_for_repo) +{ + // fill vendors_for_repo + for (const auto& pair : bundles) { + if (pair.second.vendor_profile->repo_id != repo_id) + continue; + vendors_for_repo.emplace_back(pair.second.vendor_profile); + } + + // check if any of vendor is installed + const auto& appconf_vendors = appconfig_new.vendors(); + for (const VendorProfile* vendor : vendors_for_repo) { + if (vendor && !vendor->templates_profile) + if (appconf_vendors.find(vendor->id) != appconf_vendors.end()) + return true; + } + + return false; +} + +static bool to_delete(PagePrinters* page, const std::set& selected_uuids) +{ + const PresetArchiveDatabase* pad = wxGetApp().plater()->get_preset_archive_database(); + const SharedArchiveRepositoryVector& archs = pad->get_all_archive_repositories(); + + bool unselect_all = true; + + for (const auto& archive : archs) { + if (page->get_vendor_repo_id() == archive->get_manifest().id) { + if (selected_uuids.find(archive->get_uuid()) != selected_uuids.end()) + unselect_all = false; + //break; ! don't break here, because there can be several archives with same repo_id + } + } + return unselect_all; +} + +static void unselect(PagePrinters* page) +{ + const PresetArchiveDatabase* pad = wxGetApp().plater()->get_preset_archive_database(); + const SharedArchiveRepositoryVector& archs = pad->get_all_archive_repositories(); + + bool unselect_all = true; + + for (const auto* archive : archs) { + if (page->get_vendor_repo_id() == archive->get_manifest().id) { + if (pad->is_selected_repository_by_uuid(archive->get_uuid())) + unselect_all = false; + //break; ! don't break here, because there can be several archives with same repo_id + } + } + + if (unselect_all) + page->unselect_all_presets(); +} + +bool ConfigWizard::priv::can_clear_printer_pages() +{ + // const auto& selected_uuids = page_update_manager->manager->get_selected_uuids(); + + wxString msg; + + //for (Repository& repo : repositories) { + // for (auto& [name, printers] : repo.printers_pages) { + // if (PagePrinters* page = printers.first; + // page && to_delete(page, selected_uuids)) + // msg += "* " + page->shortname + "\n"; + + // if (PagePrinters* page = printers.second; + // page && to_delete(page, selected_uuids)) + // msg += "* " + page->shortname + "\n"; + // } + //} + + if (msg.IsEmpty()) + return true; + + // TRN: %1% contains list of pages to be removed, each on its own line and ending with a line break. + wxString message = format_wxstr( _L("Following Configuration Wizard pages will be removed after the configuration update:\n\n%1%\n" + "Installed presets for the respective printers will also be removed.\n" + "Do you want to continue?"), msg); + + MessageDialog msg_dlg(this->q, message, _L("Notice"), wxYES_NO); + return msg_dlg.ShowModal() == wxID_YES; +} + +void ConfigWizard::priv::clear_printer_pages() +{ + auto delelete_page = [this](PagePrinters* page) { + // unselect page to correct process those changes in app_config + unselect(page); + + // remove page + hscroll->RemoveChild(page);// Under OSX call of Reparent(nullptr) causes a crash, so as a workaround use RemoveChild() instead + page->Destroy(); + }; + + for (PagePrinters* page : pages_fff) + delelete_page(page); + pages_fff.clear(); + + for (PagePrinters* page : pages_msla) + delelete_page(page); + pages_msla.clear(); + + for (Repository& repo : repositories) { + if (!repo.vendors_page) + continue; + for (auto& [name, printers] : repo.printers_pages) { + if (printers.first) delelete_page(printers.first); + if (printers.second) delelete_page(printers.second); + } + } + repositories.clear(); +} + +bool ConfigWizard::priv::installed_multivendors_repos() +{ + for (const auto& repo : repositories) + if (repo.vendors_page) + return true; + return false; +} + +void ConfigWizard::priv::load_pages_from_archive() +{ + if (!is_config_from_archive) + return; + + wxBusyCursor wait; + wxWindowUpdateLocker freeze_guard(q); + + // clear vendors and printers pages if any exists + clear_printer_pages(); + + // fill vendors and printers pages from Update manager + + auto pad = wxGetApp().plater()->get_preset_archive_database(); + + const SharedArchiveRepositoryVector& archs = pad->get_all_archive_repositories(); + + only_sla_mode = true; + bool is_primary_printer_page_set = false; + + for (const auto* archive : archs) { + const auto& data = archive->get_manifest(); + const bool is_selected_arch = pad->is_selected_repository_by_uuid(archive->get_uuid()); + + std::vector vendors; + const bool any_installed_vendor = any_installed_vendor_for_repo(data.id, vendors); + + const bool is_already_added_repo = std::find(repositories.begin(), repositories.end(), data.id) != repositories.end(); + + if (is_already_added_repo || (!is_selected_arch && !any_installed_vendor)) + continue; + + if (!vendors.empty()) + { + // repository item with repo_id needs to be added into repositories before page_vendors creation + repositories.push_back({ data.id }); + + const bool is_non_qidi = data.id.find("non-qidi") == 0; + if (is_non_qidi || vendors.size() > 1) + { + // it's multiple vendor or non-qidi repository + + PageVendors* page_vendors = new PageVendors(q, data.id, data.name); + repositories[repositories.size() - 1].vendors_page = page_vendors; + + add_page(page_vendors); + } + else + { + // it's single qidi vendor repository + create_vendor_printers_page(data.id, vendors[0], true, true); + + if (!is_primary_printer_page_set && !pages_fff.empty()) + { + pages_fff.back()->is_primary_printer_page = true; + is_primary_printer_page_set = true; + } + else if (!is_primary_printer_page_set && !pages_msla.empty()) + { + pages_msla.back()->is_primary_printer_page = true; + is_primary_printer_page_set = true; + } + } + } + + } + + if (only_sla_mode && installed_multivendors_repos()) { + only_sla_mode = false; + } + + if (!only_sla_mode) { + add_page(page_custom = new PageCustom(q)); + custom_printer_selected = page_custom->custom_wanted(); + } + + any_sla_selected = check_sla_selected(); + any_fff_selected = !only_sla_mode && check_fff_selected(); + + if(!only_sla_mode && !page_filaments) + add_page(page_filaments = new PageMaterials(q, &filaments, + _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:"))); + if (!page_sla_materials) + add_page(page_sla_materials = new PageMaterials(q, &sla_materials, + _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:"))); + + check_and_install_missing_materials(T_ANY); + update_materials(T_ANY); + if (any_fff_selected) + page_filaments->reload_presets(); + + if (any_sla_selected) + page_sla_materials->reload_presets(); + + load_pages(); } @@ -3378,20 +4092,23 @@ ConfigWizard::ConfigWizard(wxWindow *parent) wxGetApp().SetWindowVariantForButton(p->btn_finish); wxGetApp().SetWindowVariantForButton(p->btn_cancel); + p->add_page(p->page_welcome = new PageWelcome(this)); + p->add_page(p->page_login = new ConfigWizardWebViewPage(this)); + // p->add_page(p->page_update_manager = new PageUpdateManager(this)); + + // other pages will be loaded later after confirm repositories selection + const auto qidi_it = p->bundles.find("QIDITechnology"); wxCHECK_RET(qidi_it != p->bundles.cend(), "Vendor QIDITechnology not found"); - const VendorProfile *vendor_qidi = qidi_it->second.vendor_profile; + const VendorProfile* vendor_qidi = qidi_it->second.vendor_profile; - p->add_page(p->page_welcome = new PageWelcome(this)); - - p->page_fff = new PagePrinters(this, _L("QIDI FFF Technology Printers"), "QIDI FFF", *vendor_qidi, 0, T_FFF); p->only_sla_mode = !p->page_fff->has_printers; if (!p->only_sla_mode) { p->add_page(p->page_fff); p->page_fff->is_primary_printer_page = true; } - + p->page_msla = new PagePrinters(this, _L("QIDI MSLA Technology Printers"), "QIDI MSLA", *vendor_qidi, 0, T_SLA); p->add_page(p->page_msla); @@ -3400,23 +4117,23 @@ ConfigWizard::ConfigWizard(wxWindow *parent) } if (!p->only_sla_mode) { - // Pages for 3rd party vendors - p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors - p->add_page(p->page_vendors = new PageVendors(this)); - p->add_page(p->page_custom = new PageCustom(this)); + // Pages for 3rd party vendors + p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors + p->add_page(p->page_vendors = new PageVendors(this)); + p->add_page(p->page_custom = new PageCustom(this)); p->custom_printer_selected = p->page_custom->custom_wanted(); } p->any_sla_selected = p->check_sla_selected(); - p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected(); + p->any_fff_selected = !p->only_sla_mode && p->check_fff_selected(); p->update_materials(T_ANY); if (!p->only_sla_mode) p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, - _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") )); + _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:"))); p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, - _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); + _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:"))); p->add_page(p->page_update = new PageUpdate(this)); @@ -3431,9 +4148,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_bvolume = new PageBuildVolume(this)); p->add_page(p->page_diams = new PageDiameters(this)); p->add_page(p->page_temps = new PageTemperatures(this)); - + p->load_pages(); - p->index->go_to(size_t{0}); + p->index->go_to(size_t{ 0 }); vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); @@ -3451,6 +4168,18 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); + p->btn_prev->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) + { + evt.Enable(p->can_go_next()); + }); + + //p->page_login->Bind(wxEVT_WEBVIEW_NAVIGATED, [this](wxWebViewEvent& evt) + // { + // wxString url = evt.GetURL(); + // if (url.ends_with(_L("userCenter"))) + // this->p->index->go_next(); + // }); + p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { // check, that there is selected at least one filament/material @@ -3461,32 +4190,59 @@ ConfigWizard::ConfigWizard(wxWindow *parent) ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) // In that case don't leave the page and the function above queried the user whether to install default materials. return; + // if (active_page == p->page_update_manager && p->index->active_is_last()) { + // size_t next_active = p->index->pages_cnt(); + // p->page_update_manager->Hide(); + // p->index->go_to(next_active); + // return; + // } this->p->index->go_next(); }); + p->btn_next->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) + { + evt.Enable(p->can_go_next()); + }); + p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { if (p->on_bnt_finish()) this->EndModal(wxID_OK); }); + p->btn_finish->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) + { + evt.Enable(p->can_finish()); + }); + p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { - p->any_sla_selected = true; + // p->any_sla_selected = true; p->load_pages(); - p->page_fff->select_all(true, false); - p->page_msla->select_all(true, false); + + for (auto page : p->pages_msla) + page->select_all(true, false); + for (auto page : p->pages_fff) + page->select_all(true, false); + p->index->go_to(p->page_mode); }); - p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { - const bool is_last = p->index->active_is_last(); - p->btn_next->Show(! is_last); - if (is_last) - p->btn_finish->SetFocus(); + // p->btn_sel_all->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) + // { + // evt.Enable(p->can_select_all()); + // }); + p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) + { + p->btn_next->Show(p->can_show_next()); + + if (p->index->active_is_last()) + p->btn_finish->SetFocus(); Layout(); }); + /* ysFIXME - delete after testing and release + // it looks like this workaround is no need any more after update of the wxWidgets to 3.2.0 if (wxLinux_gtk3) this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { ConfigWizardPage* active_page = p->index->active_page(); @@ -3500,6 +4256,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) // set initial dialog size p->init_dialog_size(); }); + */ } ConfigWizard::~ConfigWizard() {} @@ -3512,6 +4269,8 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page) p->set_run_reason(reason); p->set_start_page(start_page); + p->is_config_from_archive = reason == RR_USER; + // p->set_config_updated_from_archive(p->is_config_from_archive, false); if (ShowModal() == wxID_OK) { bool apply_keeped_changes = false; @@ -3532,6 +4291,16 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page) } } +void ConfigWizard::update_login() +{ + if (p->page_login && p->page_login->login_changed()) { + // repos changed - we need rebuild + wxGetApp().plater()->get_preset_archive_database()->sync_blocking(); + // now change PageUpdateManager + // p->page_update_manager->manager->update(); + } +} + const wxString& ConfigWizard::name(const bool from_menu/* = false*/) { // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. @@ -3560,8 +4329,9 @@ void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) p->btn_next->GetId(), p->btn_prev->GetId() }); - for (auto printer_picker: p->page_fff->printer_pickers) - msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); + for (auto page : p->pages_fff) + for (auto printer_picker : page->printer_pickers) + msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); p->init_dialog_size(); diff --git a/src/slic3r/GUI/ConfigWizard.hpp b/src/slic3r/GUI/ConfigWizard.hpp index dcd0297..664d4a2 100644 --- a/src/slic3r/GUI/ConfigWizard.hpp +++ b/src/slic3r/GUI/ConfigWizard.hpp @@ -16,15 +16,18 @@ class PresetUpdater; namespace GUI { +class ConfigWizardLoadingDialog : public wxDialog +{ +public: + ConfigWizardLoadingDialog(wxWindow* parent, const wxString& message); +}; + namespace DownloaderUtils { class Worker : public wxBoxSizer { wxWindow* m_parent{ nullptr }; wxTextCtrl* m_input_path{ nullptr }; bool downloader_checked{ false }; -#ifdef __linux__ - bool perform_registration_linux{ false }; -#endif // __linux__ void deregister(); @@ -40,9 +43,10 @@ namespace DownloaderUtils { void set_path_name(const std::string& name); bool on_finish(); - bool perform_register(const std::string& path_override = {}); + static bool perform_download_register(const std::string& path); + static bool perform_url_register(); #ifdef __linux__ - bool get_perform_registration_linux() { return perform_registration_linux; } + static bool perform_registration_linux; #endif // __linux__ }; } @@ -75,6 +79,7 @@ public: // Run the Wizard. Return whether it was completed. bool run(RunReason reason, StartPage start_page = SP_WELCOME); + void update_login(); static const wxString& name(const bool from_menu = false); protected: diff --git a/src/slic3r/GUI/ConfigWizardWebViewPage.cpp b/src/slic3r/GUI/ConfigWizardWebViewPage.cpp new file mode 100644 index 0000000..68522d1 --- /dev/null +++ b/src/slic3r/GUI/ConfigWizardWebViewPage.cpp @@ -0,0 +1,185 @@ +#include "ConfigWizardWebViewPage.hpp" + +#include "WebView.hpp" +#include "UserAccount.hpp" +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "format.hpp" +#include "Event.hpp" +#include "slic3r/GUI/WebViewPlatformUtils.hpp" + +#include + +#include +using namespace std; +using namespace nlohmann; + +wxDEFINE_EVENT(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, wxCommandEvent); + +namespace Slic3r { +namespace GUI { +wxDEFINE_EVENT(EVT_LOGIN_VIA_WIZARD, Event); + +ConfigWizardWebViewPage::ConfigWizardWebViewPage(ConfigWizard *parent) + // TRN Config wizard page headline. + : ConfigWizardPage(parent, _L("Log in with Your QIDI Account (optional)"), _L("Log in (optional)")) +{ + p_user_account = wxGetApp().plater()->get_user_account(); + assert(p_user_account); + bool logged = p_user_account->is_logged(); + + // Create the webview + m_browser_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxString TargetUrl = ""; + +#if QDT_RELEASE_TO_PUBLIC + wxString msg; + QIDINetwork m_qidinetwork; + TargetUrl = m_qidinetwork.get_qidi_host(); +#endif + + BOOST_LOG_TRIVIAL(error) << "login url = " << TargetUrl.ToStdString(); +// +// TargetUrl = "https://login_aliyun.qidi3dprinter.com/#/account/login"; +// m_browser = WebView::CreateWebView(this, TargetUrl, {}); + // wxString test_url = "https://www.baidu.com"; + m_browser = WebView::CreateWebView(this, TargetUrl, {"wx"}); + if (!m_browser) { + // TRN Config wizard page with a log in page. + wxStaticText* fail_text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser. Logging in is not possible in the moment.")); + append(fail_text); + return; + } + if (logged) { + // TRN Config wizard page with a log in web. + m_text = new wxStaticText(this, wxID_ANY, format_wxstr(_L("You are logged as %1%."), p_user_account->get_username())); + } else { + // TRN Config wizard page with a log in web. first line of text. + m_text = new wxStaticText(this, wxID_ANY, _L("Log in to control your printers remotely through the built-in Connect in QIDISlicer.")); + // TRN Config wizard page with a log in web. second line of text. + } + append(m_text); + m_browser_sizer->Add(m_browser, 1, wxEXPAND); + append(m_browser_sizer, 1, wxEXPAND); + + m_browser_sizer->Show(true); + + this->Layout(); + // Connect the webview events + // Bind(wxEVT_WEBVIEW_ERROR, &ConfigWizardWebViewPage::on_error, this, m_browser->GetId()); + // Bind(wxEVT_WEBVIEW_NAVIGATED, &ConfigWizardWebViewPage::on_navigation_request, this, m_browser->GetId()); + // Bind(wxEVT_IDLE, &ConfigWizardWebViewPage::on_idle, this); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ConfigWizardWebViewPage::is_login, this); +} + +bool ConfigWizardWebViewPage::login_changed() +{ + assert(p_user_account && m_browser_sizer && m_text); + bool logged = p_user_account->is_logged(); + m_browser_sizer->Show(!logged); + if (logged) { + // TRN Config wizard page with a log in web. + m_text->SetLabel(format_wxstr(_L("You are logged as %1%."), p_user_account->get_username())); + } else { + // TRN Config wizard page with a log in web. first line of text. + m_text->SetLabel(_L("Log in to control your printers remotely through the built-in Connect in QIDISlicer.")); + } + return logged; +} + +void ConfigWizardWebViewPage::on_error(wxWebViewEvent &evt) +{ +#define WX_ERROR_CASE(type) \ +case type: \ + category = #type; \ + break; + + wxString category; + switch (evt.GetInt()) + { + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CONNECTION); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CERTIFICATE); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_AUTH); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_SECURITY); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_NOT_FOUND); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_REQUEST); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_USER_CANCELLED); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_OTHER); + } + + BOOST_LOG_TRIVIAL(error) << "ConfigWizardWebViewPage error: " << category; + load_error_page(); +} + +void ConfigWizardWebViewPage::load_error_page() { + if (!m_browser) + return; + if (m_vetoed) + return; + m_browser->Stop(); + m_load_error_page = true; +} + +void ConfigWizardWebViewPage::on_idle(wxIdleEvent &WXUNUSED(evt)) { + if (!m_browser) + return; + if (m_browser->IsBusy()) { + wxSetCursor(wxCURSOR_ARROWWAIT); + } else { + wxSetCursor(wxNullCursor); + + if (!m_vetoed && m_load_error_page) { + m_load_error_page = false; + m_browser->LoadURL(GUI::format_wxstr( + "file://%1%/web/connection_failed.html", + boost::filesystem::path(resources_dir()).generic_string() + )); + } + } +} + + +void ConfigWizardWebViewPage::on_navigation_request(wxWebViewEvent &evt) +{ + wxString url = evt.GetURL(); + if (url.starts_with(L"qidislicer")) { + delete_cookies(m_browser, "https://account.qidi3d.com"); + delete_cookies(m_browser, "https://accounts.google.com"); + delete_cookies(m_browser, "https://appleid.apple.com"); + delete_cookies(m_browser, "https://facebook.com"); + evt.Veto(); + m_vetoed = true; + wxPostEvent(wxGetApp().plater(), Event(EVT_LOGIN_VIA_WIZARD, into_u8(url))); + } else if (url.Find("accounts.google.com") != wxString::npos + || url.Find("appleid.apple.com") != wxString::npos + || url.Find("facebook.com") != wxString::npos) + { + auto& sc = Utils::ServiceConfig::instance(); + if (!m_evt_sent && !url.starts_with(GUI::from_u8(sc.account_url()))) { + wxCommandEvent evt(EVT_OPEN_EXTERNAL_LOGIN_WIZARD); + evt.SetString(url); + wxPostEvent(wxGetApp().plater(), evt); + m_evt_sent = true; + } + } +} + +void ConfigWizardWebViewPage::is_login(wxWebViewEvent& evt) +{ + wxString str_input = evt.GetString(); + BOOST_LOG_TRIVIAL(error) << evt.GetString(); + std::string token; + try { + json j = json::parse(into_u8(str_input)); + token = j["data"]["token"]; + } + catch (std::exception& e) { + wxMessageBox(e.what(), "parse json failed", wxICON_WARNING); + } + BOOST_LOG_TRIVIAL(error) << token; + wxGetApp().app_config->set("user_token", token); +} + +}} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/ConfigWizardWebViewPage.hpp b/src/slic3r/GUI/ConfigWizardWebViewPage.hpp new file mode 100644 index 0000000..275b2c7 --- /dev/null +++ b/src/slic3r/GUI/ConfigWizardWebViewPage.hpp @@ -0,0 +1,72 @@ +#ifndef slic3r_ConfigWizardWebViewPage_hpp_ +#define slic3r_ConfigWizardWebViewPage_hpp_ + +#include "ConfigWizard_private.hpp" +#include + +class wxWebView; +class wxWebViewEvent; + +wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, wxCommandEvent); + +namespace Slic3r { +namespace GUI { + +wxDECLARE_EVENT(EVT_LOGIN_VIA_WIZARD, Event); + +/* +struct ConfigWizardPage: wxPanel +{ + ConfigWizard *parent; + const wxString shortname; + wxBoxSizer *content; + const unsigned indent; + + ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent = 0); + virtual ~ConfigWizardPage(); + + template + T* append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10) + { + content->Add(thing, proportion, flag, border); + return thing; + } + + wxStaticText* append_text(wxString text); + void append_spacer(int space); + + ConfigWizard::priv *wizard_p() const { return parent->p.get(); } + + virtual void apply_custom_config(DynamicPrintConfig &config) {} + virtual void set_run_reason(ConfigWizard::RunReason run_reason) {} + virtual void on_activate() {} +}; +*/ + +class UserAccount; +class ConfigWizardWebViewPage : public ConfigWizardPage +{ +public: + ConfigWizardWebViewPage( ConfigWizard *parent); + virtual ~ConfigWizardWebViewPage() {} + + void on_error(wxWebViewEvent &evt); + void on_navigation_request(wxWebViewEvent &evt); + void on_idle(wxIdleEvent &evt); + void load_error_page(); + // returns true if logged in - wizard needs to update repos + bool login_changed(); + void is_login(wxWebViewEvent& evt); + +private: + wxWebView *m_browser{nullptr}; + UserAccount *p_user_account{nullptr}; + wxBoxSizer *m_browser_sizer{nullptr}; + wxStaticText *m_text{nullptr}; + bool m_load_error_page{false}; + bool m_vetoed{false}; + bool m_evt_sent{false}; +}; + +}} // namespace Slic3r::GUI +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index ce141e8..f0a7a45 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -92,7 +92,8 @@ struct BundleMap : std::map struct Materials; - +class RepositoryUpdateUIManager; +class ConfigWizardWebViewPage; struct PrinterPickerEvent; @@ -115,6 +116,7 @@ struct PrinterPicker: wxPanel }; const std::string vendor_id; + const std::string vendor_repo_id; std::vector cboxes; std::vector cboxes_alt; @@ -178,6 +180,15 @@ struct PageWelcome: ConfigWizardPage virtual void set_run_reason(ConfigWizard::RunReason run_reason) override; }; +struct PageUpdateManager : ConfigWizardPage +{ + std::unique_ptr manager; + wxStaticText* warning_text { nullptr }; + bool is_active { false }; + + PageUpdateManager(ConfigWizard* parent); +}; + struct PagePrinters: ConfigWizardPage { std::vector printer_pickers; @@ -196,6 +207,10 @@ struct PagePrinters: ConfigWizardPage std::set get_selected_models(); std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; } + std::string get_vendor_repo_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_repo_id; } + + // unselect all printers in appconfig_new and bundles + void unselect_all_presets(); virtual void set_run_reason(ConfigWizard::RunReason run_reason) override; @@ -450,7 +465,7 @@ public: bool associate_3mf() const { return cb_3mf->IsChecked(); } bool associate_stl() const { return cb_stl->IsChecked(); } bool associate_step() const { return cb_step->IsChecked(); } - // bool associate_gcode() const { return cb_gcode->IsChecked(); } +// bool associate_gcode() const { return cb_gcode->IsChecked(); } }; #endif // _WIN32 @@ -469,7 +484,7 @@ struct PageMode: ConfigWizardPage struct PageVendors: ConfigWizardPage { - PageVendors(ConfigWizard *parent); + PageVendors(ConfigWizard *parent, std::string repos_id = std::string(), std::string name = std::string()); }; struct PageFirmware: ConfigWizardPage @@ -532,6 +547,7 @@ public: size_t active_item() const { return item_active; } ConfigWizardPage* active_page() const; bool active_is_last() const { return item_active < items.size() && item_active == last_page; } + size_t pages_cnt() const { return items.size(); } void go_prev(); void go_next(); @@ -594,8 +610,8 @@ struct ConfigWizard::priv PresetAliases aliases_fff; // Map of alias to material presets PresetAliases aliases_sla; // Map of alias to material presets std::unique_ptr custom_config; // Backing for custom printer definition - bool any_fff_selected; // Used to decide whether to display Filaments page - bool any_sla_selected; // Used to decide whether to display SLA Materials page + bool any_fff_selected { false }; // Used to decide whether to display Filaments page + bool any_sla_selected { false }; // Used to decide whether to display SLA Materials page bool custom_printer_selected { false }; // New custom printer is requested bool custom_printer_in_bundle { false }; // Older custom printer already exists when wizard starts // Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custom printers) @@ -616,6 +632,8 @@ struct ConfigWizard::priv PageWelcome *page_welcome = nullptr; PagePrinters *page_fff = nullptr; PagePrinters *page_msla = nullptr; + ConfigWizardWebViewPage *page_login = nullptr; + PageUpdateManager*page_update_manager = nullptr; PageMaterials *page_filaments = nullptr; PageMaterials *page_sla_materials = nullptr; PageCustom *page_custom = nullptr; @@ -626,7 +644,7 @@ struct ConfigWizard::priv PageFilesAssociation* page_files_association = nullptr; #endif // _WIN32 PageMode *page_mode = nullptr; - PageVendors *page_vendors = nullptr; + PageVendors* page_vendors = nullptr; Pages3rdparty pages_3rdparty; // Custom setup pages @@ -636,6 +654,22 @@ struct ConfigWizard::priv PageTemperatures *page_temps = nullptr; PageBuildVolume* page_bvolume = nullptr; + std::vector pages_fff; + std::vector pages_msla; + + struct Repository { + bool operator==(const std::string& other_id_name) const { return other_id_name == this->id_name; } + + std::string id_name; + PageVendors* vendors_page{ nullptr }; + Pages3rdparty printers_pages; + }; + std::vector repositories; + + bool installed_multivendors_repos(); + + bool is_config_from_archive{ false }; + // Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex) std::vector all_pages; @@ -654,6 +688,7 @@ struct ConfigWizard::priv void enable_next(bool enable); void set_start_page(ConfigWizard::StartPage start_page); void create_3rdparty_pages(); + void create_vendor_printers_page(const std::string& repo_id, const VendorProfile* vendor, bool install = false, bool from_single_vendor_repo = false); void set_run_reason(RunReason run_reason); void update_materials(Technology technology); @@ -663,6 +698,10 @@ struct ConfigWizard::priv void select_default_materials_for_printer_models(Technology technology, const std::set &printer_models); void on_3rdparty_install(const VendorProfile *vendor, bool install); + bool can_finish(); + bool can_go_next(); + bool can_show_next(); + bool can_select_all(); bool on_bnt_finish(); bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string()); bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes); @@ -675,6 +714,17 @@ struct ConfigWizard::priv bool check_sla_selected(); // Used to decide whether to display SLA Materials page int em() const { return index->em(); } + void set_config_updated_from_archive(bool load_installed_printers, bool run_preset_updater); + + Repository* get_repo(const std::string& repo_id); + + // Fills vendors_for_repo in respect to repo_id + // and return true if any of vendors_for_repo is installed (is in app_config) + bool any_installed_vendor_for_repo(const std::string& repo_id, std::vector& ); + + bool can_clear_printer_pages(); + void clear_printer_pages(); + void load_pages_from_archive(); }; } diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp index 330d63d..7ca7adf 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.cpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp @@ -10,7 +10,8 @@ #include "libslic3r/Platform.hpp" #include "libslic3r/Config.hpp" -#include +#include // IWYU pragma: keep +#include #include #include #include @@ -118,7 +119,7 @@ void resolve_path_from_var(const std::string& var, std::vector& pat wxString wxdirs; if (! wxGetEnv(boost::nowide::widen(var), &wxdirs) || wxdirs.empty() ) return; - std::string dirs = boost::nowide::narrow(wxdirs); + std::string dirs = into_u8(wxdirs); for (size_t i = dirs.find(':'); i != std::string::npos; i = dirs.find(':')) { paths.push_back(dirs.substr(0, i)); @@ -133,20 +134,35 @@ bool contains_path_dir(const std::string& p, const std::string& dir_name) if (p.empty() || dir_name.empty()) return false; boost::filesystem::path path(p + (p[p.size()-1] == '/' ? "" : "/") + dir_name); - if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)) { + boost::system::error_code ec; + if (boost::filesystem::exists(path, ec) && !ec) { //BOOST_LOG_TRIVIAL(debug) << path.string() << " " << std::oct << boost::filesystem::status(path).permissions(); - return true; //boost::filesystem::status(path).permissions() & boost::filesystem::owner_write; + return boost::filesystem::is_directory(path); //boost::filesystem::status(path).permissions() & boost::filesystem::owner_write; } else BOOST_LOG_TRIVIAL(debug) << path.string() << " doesnt exists"; return false; } + +boost::filesystem::path get_existing_dir(const std::string& sub, const std::string& dir_name) +{ + assert(!sub.empty() && !dir_name.empty()); + boost::filesystem::path path = boost::filesystem::path(sub) / dir_name; + boost::system::error_code ec; + if (!boost::filesystem::exists(path, ec) || ec) { + return boost::filesystem::path(); + } + if (!boost::filesystem::is_directory(path, ec) || ec) { + return boost::filesystem::path(); + } + return path; +} // Creates directory in path if not exists yet void create_dir(const boost::filesystem::path& path) { - if (boost::filesystem::exists(path)) + boost::system::error_code ec; + if (boost::filesystem::exists(path, ec) && !ec) return; BOOST_LOG_TRIVIAL(debug)<< "creating " << path.string(); - boost::system::error_code ec; boost::filesystem::create_directory(path, ec); if (ec) BOOST_LOG_TRIVIAL(error)<< "create directory failed: " << ec.message(); @@ -187,7 +203,7 @@ bool copy_icon(const std::string& icon_path, const std::string& dest_path) bool create_desktop_file(const std::string& path, const std::string& data) { BOOST_LOG_TRIVIAL(debug) <<".desktop to "<< path; - std::ofstream output(path); + boost::nowide::ofstream output(path); output << data; struct stat buffer; if (stat(path.c_str(), &buffer) == 0) @@ -303,7 +319,7 @@ void DesktopIntegrationDialog::perform_desktop_integration() // if all failed - try creating default home folder if (i == target_candidates.size() - 1) { // create $HOME/.local/share - create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs); + create_path(into_u8(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs); // copy icon target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir()); std::string icon_path = GUI::format("%1%/icons/QIDISlicer.png",resources_dir()); @@ -359,7 +375,7 @@ void DesktopIntegrationDialog::perform_desktop_integration() // if all failed - try creating default home folder if (!candidate_found) { // create $HOME/.local/share - create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications"); + create_path(into_u8(wxFileName::GetHomeDir()), ".local/share/applications"); // create desktop file target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir()); std::string path = GUI::format("%1%/applications/QIDISlicer%2%.desktop", target_dir_desktop, version_suffix); @@ -508,12 +524,12 @@ void DesktopIntegrationDialog::perform_downloader_desktop_integration() std::string version(SLIC3R_VERSION); if (version.find("alpha") != std::string::npos) { - version_suffix = "-alpha"; + version_suffix = "_alpha"; name_suffix = " - alpha"; } else if (version.find("beta") != std::string::npos) { - version_suffix = "-beta"; + version_suffix = "_beta"; name_suffix = " - beta"; } @@ -577,7 +593,7 @@ void DesktopIntegrationDialog::perform_downloader_desktop_integration() // if all failed - try creating default home folder if (!candidate_found) { // create $HOME/.local/share - create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications"); + create_path(into_u8(wxFileName::GetHomeDir()), ".local/share/applications"); // create desktop file target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir()); std::string path = GUI::format("%1%/applications/QIDISlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix); @@ -621,6 +637,38 @@ void DesktopIntegrationDialog::undo_downloader_registration() } // There is no need to undo xdg-mime default command. It is done automatically when desktop file is deleted. } +void DesktopIntegrationDialog::undo_downloader_registration_rigid() +{ + // Try ro find any QIDISlicerURLProtocol.desktop files including alpha and beta and get rid of them + + // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. + // If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. + // $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory. + // The directories in $XDG_DATA_DIRS should be seperated with a colon ':'. + // If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used. + std::vectortarget_candidates; + target_candidates.emplace_back(GUI::into_u8(wxFileName::GetHomeDir()) + "/.local/share"); + resolve_path_from_var("XDG_DATA_HOME", target_candidates); + resolve_path_from_var("XDG_DATA_DIRS", target_candidates); + for (const std::string cand : target_candidates) { + boost::filesystem::path apps_path = get_existing_dir(cand, "applications"); + if (apps_path.empty()) { + continue; + } + for (const std::string& suffix : {"" , "-beta", "-alpha" , "_beta", "_alpha"}) { + boost::filesystem::path file_path = apps_path / GUI::format("QIDISlicerURLProtocol%1%.desktop", suffix); + boost::system::error_code ec; + if (!boost::filesystem::exists(file_path, ec) || ec) { + continue; + } + if (!boost::filesystem::remove(file_path, ec) || ec) { + BOOST_LOG_TRIVIAL(error) << "Failed to remove file " << file_path << " ec: " << ec.message(); + continue; + } + BOOST_LOG_TRIVIAL(info) << "Desktop File removed: " << file_path; + } + } +} DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent) : wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.hpp b/src/slic3r/GUI/DesktopIntegrationDialog.hpp index 631c919..093b4f4 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.hpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.hpp @@ -35,6 +35,7 @@ public: static void perform_downloader_desktop_integration(); static void undo_downloader_registration(); + static void undo_downloader_registration_rigid(); private: }; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp deleted file mode 100644 index f778246..0000000 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ /dev/null @@ -1,2916 +0,0 @@ -#include "libslic3r/libslic3r.h" -#include "DoubleSlider.hpp" -#include "libslic3r/GCode.hpp" -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "Plater.hpp" -#include "I18N.hpp" -#include "ExtruderSequenceDialog.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/AppConfig.hpp" -#include "GUI_Utils.hpp" -#include "MsgDialog.hpp" -#include "Tab.hpp" -#include "GUI_ObjectList.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "Field.hpp" -#include "format.hpp" -#include "NotificationManager.hpp" - -namespace Slic3r { - -using GUI::from_u8; -using GUI::into_u8; -using GUI::format_wxstr; - -namespace DoubleSlider { - -constexpr double min_delta_area = scale_(scale_(25)); // equal to 25 mm2 -constexpr double miscalculation = scale_(scale_(1)); // equal to 1 mm2 - -bool equivalent_areas(const double& bottom_area, const double& top_area) -{ - return fabs(bottom_area - top_area) <= miscalculation; -} - -wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); - -static std::string gcode(Type type) -{ - const PrintConfig& config = GUI::wxGetApp().plater()->fff_print().config(); - switch (type) { - case ColorChange: return config.color_change_gcode; - case PausePrint: return config.pause_print_gcode; - case Template: return config.template_custom_gcode; - default: return ""; - } -} - -Control::Control( wxWindow *parent, - wxWindowID id, - int lowerValue, - int higherValue, - int minValue, - int maxValue, - const wxPoint& pos, - const wxSize& size, - long style, - const wxValidator& val, - const wxString& name) : - wxControl(parent, id, pos, size, wxWANTS_CHARS | wxBORDER_NONE), - m_lower_value(lowerValue), - m_higher_value (higherValue), - m_min_value(minValue), - m_max_value(maxValue), - m_style(style == wxSL_HORIZONTAL || style == wxSL_VERTICAL ? style: wxSL_HORIZONTAL), - m_extra_style(style == wxSL_VERTICAL ? wxSL_AUTOTICKS | wxSL_VALUE_LABEL : 0) -{ -#ifdef __WXOSX__ - is_osx = true; -#endif //__WXOSX__ - if (!is_osx) - SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX - - m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up")); - m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left") : ScalableBitmap(this, "thumb_down")); - m_thumb_size = m_bmp_thumb_lower.GetSize(); - - m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add"); - m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f"); - m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del"); - m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f"); - m_tick_icon_dim = m_bmp_add_tick_on.GetWidth(); - - m_bmp_one_layer_lock_on = ScalableBitmap(this, "lock_closed"); - m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f"); - m_bmp_one_layer_unlock_on = ScalableBitmap(this, "lock_open"); - m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f"); - m_lock_icon_dim = m_bmp_one_layer_lock_on.GetWidth(); - - m_bmp_revert = ScalableBitmap(this, "undo"); - m_revert_icon_dim = m_bmp_revert.GetWidth(); - m_bmp_cog = ScalableBitmap(this, "cog"); - m_cog_icon_dim = m_bmp_cog.GetWidth(); - - m_selection = ssUndef; - m_ticks.set_pause_print_msg(_u8L("Place bearings in slots and resume printing")); - m_ticks.set_extruder_colors(&m_extruder_colors); - - // slider events - this->Bind(wxEVT_PAINT, &Control::OnPaint, this); - this->Bind(wxEVT_CHAR, &Control::OnChar, this); - this->Bind(wxEVT_LEFT_DOWN, &Control::OnLeftDown, this); - this->Bind(wxEVT_MOTION, &Control::OnMotion, this); - this->Bind(wxEVT_LEFT_UP, &Control::OnLeftUp, this); - this->Bind(wxEVT_MOUSEWHEEL, &Control::OnWheel, this); - this->Bind(wxEVT_ENTER_WINDOW,&Control::OnEnterWin, this); - this->Bind(wxEVT_LEAVE_WINDOW,&Control::OnLeaveWin, this); - this->Bind(wxEVT_KEY_DOWN, &Control::OnKeyDown, this); - this->Bind(wxEVT_KEY_UP, &Control::OnKeyUp, this); - this->Bind(wxEVT_RIGHT_DOWN, &Control::OnRightDown,this); - this->Bind(wxEVT_RIGHT_UP, &Control::OnRightUp, this); - this->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { - m_ruler.update(m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step()); - event.Skip(); - }); - //B18 - // control's view variables - SLIDER_MARGIN = 4 + GUI::wxGetApp().em_unit(); - - DARK_ORANGE_PEN = wxPen(wxColour(237, 107, 33)); - ORANGE_PEN = wxPen(wxColour(253, 126, 66)); - LIGHT_ORANGE_PEN = wxPen(wxColour(254, 177, 139)); - LIGHT_BLUE_PEN = wxPen(wxColour(68, 121, 251)); - - DARK_GREY_PEN = wxPen(wxColour(128, 128, 128)); - GREY_PEN = wxPen(wxColour(164, 164, 164)); - LIGHT_GREY_PEN = wxPen(wxColour(204, 204, 204)); - - m_line_pens = { &DARK_GREY_PEN, &GREY_PEN, &LIGHT_GREY_PEN }; - m_segm_pens = { &LIGHT_BLUE_PEN, &LIGHT_BLUE_PEN, &LIGHT_BLUE_PEN }; - - FOCUS_RECT_PEN = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT); - FOCUS_RECT_BRUSH = wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT); - - m_font = GetFont(); - this->SetMinSize(get_min_size()); - - if (style == wxSL_VERTICAL) - m_ruler.set_parent(this->GetParent()); -} - -void Control::msw_rescale() -{ - m_font = GUI::wxGetApp().normal_font(); - - m_thumb_size = m_bmp_thumb_lower.GetSize(); - m_tick_icon_dim = m_bmp_add_tick_on.GetWidth(); - m_lock_icon_dim = m_bmp_one_layer_lock_on.GetWidth(); - m_revert_icon_dim = m_bmp_revert.GetWidth(); - m_cog_icon_dim = m_bmp_cog.GetWidth(); - - SLIDER_MARGIN = 4 + GUI::wxGetApp().em_unit(); - - SetMinSize(get_min_size()); - GetParent()->Layout(); - - m_ruler.update_dpi(); - m_ruler.update(m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step()); -} - -void Control::sys_color_changed() -{ - GUI::wxGetApp().UpdateDarkUI(GetParent()); - - m_bmp_add_tick_on .sys_color_changed(); - m_bmp_add_tick_off.sys_color_changed(); - m_bmp_del_tick_on .sys_color_changed(); - m_bmp_del_tick_off.sys_color_changed(); - - m_bmp_one_layer_lock_on .sys_color_changed(); - m_bmp_one_layer_lock_off .sys_color_changed(); - m_bmp_one_layer_unlock_on .sys_color_changed(); - m_bmp_one_layer_unlock_off.sys_color_changed(); - - m_bmp_revert.sys_color_changed(); - m_bmp_cog .sys_color_changed(); -} - -int Control::GetActiveValue() const -{ - return m_selection == ssLower ? - m_lower_value : m_selection == ssHigher ? - m_higher_value : -1; -} - -wxSize Control::get_min_size() const -{ - const int min_side = GUI::wxGetApp().em_unit() * ( is_horizontal() ? 5 : 11 ); - return wxSize(min_side, min_side); -} - -wxSize Control::DoGetBestSize() const -{ - const wxSize size = wxControl::DoGetBestSize(); - if (size.x > 1 && size.y > 1) - return size; - return get_min_size(); -} - -void Control::SetLowerValue(const int lower_val) -{ - m_selection = ssLower; - m_lower_value = lower_val; - correct_lower_value(); - Refresh(); - Update(); - - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - ProcessWindowEvent(e); -} - -void Control::SetHigherValue(const int higher_val) -{ - m_selection = ssHigher; - m_higher_value = higher_val; - correct_higher_value(); - Refresh(); - Update(); - - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - ProcessWindowEvent(e); -} - -void Control::SetSelectionSpan(const int lower_val, const int higher_val) -{ - m_lower_value = std::max(lower_val, m_min_value); - m_higher_value = std::max(std::min(higher_val, m_max_value), m_lower_value); - if (m_lower_value < m_higher_value) - m_is_one_layer = false; - - Refresh(); - Update(); - - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - ProcessWindowEvent(e); -} - -void Control::SetMaxValue(const int max_value) -{ - m_max_value = max_value; - Refresh(); - Update(); -} - -void Control::SetSliderValues(const std::vector& values) -{ - m_values = values; - m_ruler.init(m_values, get_scroll_step()); - - // When "No sparce layer" is enabled, use m_layers_values for ruler update. - // Because of m_values has duplicate values in this case. -// m_ruler.update(this->GetParent(), m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step()); -} - -void Control::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos) -{ - int width; - int height; - get_size(&width, &height); - - wxCoord line_beg_x = is_horizontal() ? SLIDER_MARGIN : width*0.5 - 1; - wxCoord line_beg_y = is_horizontal() ? height*0.5 - 1 : SLIDER_MARGIN; - wxCoord line_end_x = is_horizontal() ? width - SLIDER_MARGIN + 1 : width*0.5 - 1; - wxCoord line_end_y = is_horizontal() ? height*0.5 - 1 : height - SLIDER_MARGIN + 1; - - wxCoord segm_beg_x = is_horizontal() ? lower_pos : width*0.5 - 1; - wxCoord segm_beg_y = is_horizontal() ? height*0.5 - 1 : lower_pos/*-1*/; - wxCoord segm_end_x = is_horizontal() ? higher_pos : width*0.5 - 1; - wxCoord segm_end_y = is_horizontal() ? height*0.5 - 1 : higher_pos-1; - - for (size_t id = 0; id < m_line_pens.size(); id++) { - dc.SetPen(*m_line_pens[id]); - dc.DrawLine(line_beg_x, line_beg_y, line_end_x, line_end_y); - dc.SetPen(*m_segm_pens[id]); - dc.DrawLine(segm_beg_x, segm_beg_y, segm_end_x, segm_end_y); - if (is_horizontal()) - line_beg_y = line_end_y = segm_beg_y = segm_end_y += 1; - else - line_beg_x = line_end_x = segm_beg_x = segm_end_x += 1; - } -} - -double Control::get_scroll_step() -{ - const wxSize sz = get_size(); - const int& slider_len = m_style == wxSL_HORIZONTAL ? sz.x : sz.y; - return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value); -} - -// get position on the slider line from entered value -wxCoord Control::get_position_from_value(const int value) -{ - const double step = get_scroll_step(); - const int val = is_horizontal() ? value : m_max_value - value; - return wxCoord(SLIDER_MARGIN + int(val*step + 0.5)); -} - -wxSize Control::get_size() const -{ - int w, h; - get_size(&w, &h); - return wxSize(w, h); -} - -void Control::get_size(int* w, int* h) const -{ - GetSize(w, h); - if (m_draw_mode == dmSequentialGCodeView) - return; // we have no more icons for drawing - is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim; -} - -double Control::get_double_value(const SelectedSlider& selection) -{ - if (m_values.empty() || m_lower_value<0) - return 0.0; - if (m_values.size() <= size_t(m_higher_value)) { - correct_higher_value(); - return m_values.back(); - } - return m_values[selection == ssLower ? m_lower_value : m_higher_value]; -} - -int Control::get_tick_from_value(double value, bool force_lower_bound/* = false*/) -{ - std::vector::iterator it; - if (m_is_wipe_tower && !force_lower_bound) - it = std::find_if(m_values.begin(), m_values.end(), - [value](const double & val) { return fabs(value - val) <= epsilon(); }); - else - it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); - - if (it == m_values.end()) - return -1; - return int(it - m_values.begin()); -} - -Info Control::GetTicksValues() const -{ - Info custom_gcode_per_print_z; - std::vector& values = custom_gcode_per_print_z.gcodes; - - const int val_size = m_values.size(); - if (!m_values.empty()) - for (const TickCode& tick : m_ticks.ticks) { - if (tick.tick > val_size) - break; - values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra }); - } - - if (m_force_mode_apply) - custom_gcode_per_print_z.mode = m_mode; - - return custom_gcode_per_print_z; -} - -void Control::SetTicksValues(const Info& custom_gcode_per_print_z) -{ - if (m_values.empty()) { - m_ticks.mode = m_mode; - return; - } - - const bool was_empty = m_ticks.empty(); - - m_ticks.ticks.clear(); - const std::vector& heights = custom_gcode_per_print_z.gcodes; - for (auto h : heights) { - int tick = get_tick_from_value(h.print_z); - if (tick >=0) - m_ticks.ticks.emplace(TickCode{ tick, h.type, h.extruder, h.color, h.extra }); - } - - if (!was_empty && m_ticks.empty()) - // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one - post_ticks_changed_event(); - - // init extruder sequence in respect to the extruders count - if (m_ticks.empty()) - m_extruders_sequence.init(m_extruder_colors.size()); - - if (custom_gcode_per_print_z.mode && !custom_gcode_per_print_z.gcodes.empty()) - m_ticks.mode = custom_gcode_per_print_z.mode; - - Refresh(); - Update(); -} - -void Control::SetLayersTimes(const std::vector& layers_times, float total_time) -{ - m_layers_times.clear(); - if (layers_times.empty()) - return; - m_layers_times.resize(layers_times.size(), 0.0); - m_layers_times[0] = layers_times[0]; - for (size_t i = 1; i < layers_times.size(); i++) - m_layers_times[i] = m_layers_times[i - 1] + layers_times[i]; - - // Erase duplicates values from m_values and save it to the m_layers_values - // They will be used for show the correct estimated time for MM print, when "No sparce layer" is enabled - // See https://github.com/qidi3d/QIDISlicer/issues/6232 - if (m_is_wipe_tower && m_values.size() != m_layers_times.size()) { - m_layers_values = m_values; - sort(m_layers_values.begin(), m_layers_values.end()); - m_layers_values.erase(unique(m_layers_values.begin(), m_layers_values.end()), m_layers_values.end()); - - // When whipe tower is used to the end of print, there is one layer which is not marked in layers_times - // So, add this value from the total print time value - if (m_layers_values.size() != m_layers_times.size()) - for (size_t i = m_layers_times.size(); i < m_layers_values.size(); i++) - m_layers_times.push_back(total_time); - m_ruler.update(m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step()); - Refresh(); - Update(); - } - else - m_ruler.update(m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step()); -} - -void Control::SetLayersTimes(const std::vector& layers_times) -{ - m_is_wipe_tower = false; - m_layers_times = layers_times; - for (size_t i = 1; i < m_layers_times.size(); i++) - m_layers_times[i] += m_layers_times[i - 1]; -} - -void Control::SetDrawMode(bool is_sla_print, bool is_sequential_print) -{ - m_draw_mode = is_sla_print ? dmSlaPrint : - is_sequential_print ? dmSequentialFffPrint : - dmRegular; -} - -void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder) -{ - m_mode = !is_one_extruder_printed_model ? MultiExtruder : - only_extruder < 0 ? SingleExtruder : - MultiAsSingle; - if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode)) - m_ticks.mode = m_mode; - m_only_extruder = only_extruder; - - UseDefaultColors(m_mode == SingleExtruder); - - m_is_wipe_tower = m_mode != SingleExtruder; -} - -void Control::SetExtruderColors( const std::vector& extruder_colors) -{ - m_extruder_colors = extruder_colors; -} - -bool Control::IsNewPrint() -{ - if (GUI::wxGetApp().plater()->printer_technology() == ptSLA) - return false; - const Print& print = GUI::wxGetApp().plater()->fff_print(); - std::string idxs; - for (auto object : print.objects()) - idxs += std::to_string(object->id().id) + "_"; - - if (idxs == m_print_obj_idxs) - return false; - - m_print_obj_idxs = idxs; - return true; -} - -void Control::get_lower_and_higher_position(int& lower_pos, int& higher_pos) -{ - const double step = get_scroll_step(); - if (is_horizontal()) { - lower_pos = SLIDER_MARGIN + int(m_lower_value*step + 0.5); - higher_pos = SLIDER_MARGIN + int(m_higher_value*step + 0.5); - } - else { - lower_pos = SLIDER_MARGIN + int((m_max_value - m_lower_value)*step + 0.5); - higher_pos = SLIDER_MARGIN + int((m_max_value - m_higher_value)*step + 0.5); - } -} - -void Control::draw_focus_rect(wxDC& dc) -{ - if (!m_is_focused) - return; - const wxSize sz = GetSize(); -// wxPaintDC dc(this); - //const wxPen pen = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT); - //dc.SetPen(pen); - //dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); - dc.SetPen(FOCUS_RECT_PEN); - dc.SetBrush(FOCUS_RECT_BRUSH); - dc.DrawRectangle(1, 1, sz.x - 2, sz.y - 2); -} - -void Control::render() -{ -#ifdef _WIN32 - GUI::wxGetApp().UpdateDarkUI(this); -#else - SetBackgroundColour(GetParent()->GetBackgroundColour()); -#endif // _WIN32 - - wxPaintDC dc(this); - dc.SetFont(m_font); - - draw_focus_rect(dc); - - const wxCoord lower_pos = get_position_from_value(m_lower_value); - const wxCoord higher_pos = get_position_from_value(m_higher_value); - - // draw colored band on the background of a scroll line - // and only in a case of no-empty m_values - draw_colored_band(dc); - - if (m_extra_style & wxSL_AUTOTICKS) - draw_ruler(dc); - - if (!m_render_as_disabled) { - // draw line - draw_scroll_line(dc, lower_pos, higher_pos); - - // draw color print ticks - draw_ticks(dc); - - // draw both sliders - draw_thumbs(dc, lower_pos, higher_pos); - - // draw lock/unlock - draw_one_layer_icon(dc); - - // draw revert bitmap (if it's shown) - draw_revert_icon(dc); - - // draw cog bitmap (if it's shown) - draw_cog_icon(dc); - - // draw mouse position - draw_tick_on_mouse_position(dc); - } -} - -bool Control::is_wipe_tower_layer(int tick) const -{ - if (!m_is_wipe_tower || tick >= (int)m_values.size()) - return false; - if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1])) - return false; - if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) || - (tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer - return true; - - return false; -} - -void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) -{ - const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - - if (!m_enable_action_icon) - return; - - // suppress add tick on first layer - if (tick == 0) - return; - - if (is_wipe_tower_layer(tick)) { - m_rect_tick_action = wxRect(); - return; - } - - //wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); - //if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end()) - // icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); - ScalableBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off : &m_bmp_add_tick_on; - if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end()) - icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off : &m_bmp_del_tick_on; - - wxCoord x_draw, y_draw; - is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim; - if (m_selection == ssLower) - is_horizontal() ? y_draw = pt_end.y + 3 : x_draw = pt_beg.x - m_tick_icon_dim-2; - else - is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3; - - if (m_draw_mode == dmSequentialFffPrint) { - wxBitmap disabled_add = get_bmp_bundle("colorchange_add")->GetBitmapFor(this).ConvertToDisabled(); - dc.DrawBitmap(disabled_add, x_draw, y_draw); - } - else - dc.DrawBitmap((*icon).get_bitmap(), x_draw, y_draw); - - //update rect of the tick action icon - m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim); -} -//B18 -void Control::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const SelectedSlider selection) -{ - if (m_selection == selection) { - //draw info line - dc.SetPen(LIGHT_BLUE_PEN); - const wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x, pos.y/* - 1*/); - const wxPoint pt_end = is_horizontal() ? wxPoint(pos.x, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x, pos.y/* - 1*/); - dc.DrawLine(pt_beg, pt_end); - - //draw action icon - if (m_draw_mode == dmRegular || m_draw_mode == dmSequentialFffPrint) - draw_action_icon(dc, pt_beg, pt_end); - } -} - -void Control::draw_tick_on_mouse_position(wxDC& dc) -{ - if (!m_is_focused || m_moving_pos == wxDefaultPosition) - return; - - //calculate thumb position on slider line - int width, height; - get_size(&width, &height); - - int tick = get_tick_near_point(m_moving_pos); - if (tick == m_higher_value || tick == m_lower_value) - return ; - - auto draw_ticks = [this](wxDC& dc, wxPoint pos, int margin=0 ) - { - wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x+margin, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x , pos.y+margin); - wxPoint pt_end = is_horizontal() ? wxPoint(pos.x+margin, pos.y + m_thumb_size.y) : wxPoint(pos.x - 0.5 * m_thumb_size.x + 1, pos.y+margin); - dc.DrawLine(pt_beg, pt_end); - - pt_beg = is_horizontal() ? wxPoint(pos.x + margin, pos.y - m_thumb_size.y) : wxPoint(pos.x + 0.5 * m_thumb_size.x, pos.y+margin); - pt_end = is_horizontal() ? wxPoint(pos.x + margin, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x + 1, pos.y+margin); - dc.DrawLine(pt_beg, pt_end); - }; - - auto draw_touch = [this](wxDC& dc, wxPoint pos, int margin, bool right_side ) - { - int mult = right_side ? 1 : -1; - wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x - margin, pos.y + mult * m_thumb_size.y) : wxPoint(pos.x + mult * m_thumb_size.x, pos.y - margin); - wxPoint pt_end = is_horizontal() ? wxPoint(pos.x + margin, pos.y + mult * m_thumb_size.y) : wxPoint(pos.x + mult * m_thumb_size.x, pos.y + margin); - dc.DrawLine(pt_beg, pt_end); - }; - //B18 - if (tick > 0) // this tick exists and should be marked as a focused - { - wxCoord new_pos = get_position_from_value(tick); - const wxPoint pos = is_horizontal() ? wxPoint(new_pos, height * 0.5) : wxPoint(0.5 * width, new_pos); - - dc.SetPen(LIGHT_BLUE_PEN); - - draw_ticks(dc, pos, -2); - draw_ticks(dc, pos, 2 ); - draw_touch(dc, pos, 2, true); - draw_touch(dc, pos, 2, false); - - return; - } - - tick = get_value_from_position(m_moving_pos); - if (tick > m_max_value || tick < m_min_value || tick == m_higher_value || tick == m_lower_value) - return; - - wxCoord new_pos = get_position_from_value(tick); - const wxPoint pos = is_horizontal() ? wxPoint(new_pos, height * 0.5) : wxPoint(0.5 * width, new_pos); - - //draw info line - dc.SetPen(LIGHT_GREY_PEN); - draw_ticks(dc, pos); - - if (m_extra_style & wxSL_VALUE_LABEL) { - wxColour old_clr = dc.GetTextForeground(); - dc.SetTextForeground(GREY_PEN.GetColour()); - draw_tick_text(dc, pos, tick, ltEstimatedTime, false); - dc.SetTextForeground(old_clr); - } -} - -static wxString short_and_splitted_time(const std::string& time) -{ - // Parse the dhms time format. - int days = 0; - int hours = 0; - int minutes = 0; - int seconds = 0; - if (time.find('d') != std::string::npos) - ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); - else if (time.find('h') != std::string::npos) - ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); - else if (time.find('m') != std::string::npos) - ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); - else if (time.find('s') != std::string::npos) - ::sscanf(time.c_str(), "%ds", &seconds); - - // Format the dhm time. - auto get_d = [days]() { return format(_u8L("%1%d"), days); }; - auto get_h = [hours]() { return format(_u8L("%1%h"), hours); }; - auto get_m = [minutes](){ return format(_u8L("%1%m"), minutes); }; - auto get_s = [seconds](){ return format(_u8L("%1%s"), seconds); }; - - if (days > 0) - return format_wxstr("%1%%2%\n%3%", get_d(), get_h(), get_m()); - if (hours > 0) { - if (hours < 10 && minutes < 10 && seconds < 10) - return format_wxstr("%1%%2%%3%", get_h(), get_m(), get_s()); - if (hours > 10 && minutes > 10 && seconds > 10) - return format_wxstr("%1%\n%2%\n%3%", get_h(), get_m(), get_s()); - if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10)) - return format_wxstr("%1%\n%2%%3%", get_h(), get_m(), get_s()); - return format_wxstr("%1%%2%\n%3%", get_h(), get_m(), get_s()); - } - if (minutes > 0) { - if (minutes > 10 && seconds > 10) - return format_wxstr("%1%\n%2%", get_m(), get_s()); - return format_wxstr("%1%%2%", get_m(), get_s()); - } - return from_u8(get_s()); -} - -wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer*/) const -{ - const size_t value = tick; - - if (m_label_koef == 1.0 && m_values.empty()) - return wxString::Format("%lu", static_cast(value)); - if (value >= m_values.size()) - return "ErrVal"; - - // When "Print Settings -> Multiple Extruders -> No sparse layer" is enabled, then "Smart" Wipe Tower is used for wiping. - // As a result, each layer with tool changes is splited for min 3 parts: first tool, wiping, second tool ... - // So, vertical slider have to respect to this case. - // see https://github.com/qidi3d/QIDISlicer/issues/6232. - // m_values contains data for all layer's parts, - // but m_layers_values contains just unique Z values. - // Use this function for correct conversion slider position to number of printed layer - auto get_layer_number = [this](int value, LabelType label_type) { - if (label_type == ltEstimatedTime && m_layers_times.empty()) - return size_t(-1); - double layer_print_z = m_values[is_wipe_tower_layer(value) ? std::max(value - 1, 0) : value]; - auto it = std::lower_bound(m_layers_values.begin(), m_layers_values.end(), layer_print_z - epsilon()); - if (it == m_layers_values.end()) { - it = std::lower_bound(m_values.begin(), m_values.end(), layer_print_z - epsilon()); - if (it == m_values.end()) - return size_t(-1); - return size_t(value); - } - return size_t(it - m_layers_values.begin()); - }; - - if (m_draw_mode == dmSequentialGCodeView) - return wxString::Format("%lu", static_cast(m_alternate_values[value])); - else { - if (label_type == ltEstimatedTime) { - if (m_is_wipe_tower) { - size_t layer_number = get_layer_number(value, label_type); - return (layer_number == size_t(-1) || layer_number == m_layers_times.size()) ? "" : short_and_splitted_time(get_time_dhms(m_layers_times[layer_number])); - } - return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; - } - wxString str = m_values.empty() ? - wxString::Format("%.*f", 2, m_label_koef * value) : - wxString::Format("%.*f", 2, m_values[value]); - if (label_type == ltHeight) - return str; - if (label_type == ltHeightWithLayer) { - size_t layer_number = m_is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1); - return format_wxstr("%1%\n(%2%)", str, layer_number); - } - } - - return wxEmptyString; -} - -void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, LabelType label_type/* = ltHeight*/, bool right_side/*=true*/) const -{ - wxCoord text_width, text_height; - const wxString label = get_label(tick, label_type); - dc.GetMultiLineTextExtent(label, &text_width, &text_height); - wxPoint text_pos; - if (right_side) { - if (is_horizontal()) { - int width; - int height; - get_size(&width, &height); - - int x_right = pos.x + 1 + text_width; - int xx = (x_right < width) ? pos.x + 1 : pos.x - text_width - 1; - text_pos = wxPoint(xx, pos.y + m_thumb_size.x / 2 + 1); - } - else - text_pos = wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1); - } - else { - if (is_horizontal()) { - int x = pos.x - text_width - 1; - int xx = (x > 0) ? x : pos.x + 1; - text_pos = wxPoint(xx, pos.y - m_thumb_size.x / 2 - text_height - 1); - } - else - text_pos = wxPoint(std::max(2, pos.x - text_width - 1 - m_thumb_size.x), pos.y - 0.5 * text_height + 1); - } - //B18 - wxColour old_clr = dc.GetTextForeground(); - const wxPen& pen = is_wipe_tower_layer(tick) && (tick == m_lower_value || tick == m_higher_value) ? LIGHT_BLUE_PEN : /*wxPen(old_clr)*/GREY_PEN; - dc.SetPen(pen); - dc.SetTextForeground(pen.GetColour()); - - if (label_type == ltEstimatedTime) - dc.DrawLabel(label, wxRect(text_pos, wxSize(text_width, text_height)), wxALIGN_RIGHT); - else - dc.DrawText(label, text_pos); - - dc.SetTextForeground(old_clr); -} - -void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const -{ - draw_tick_text(dc, pos, selection == ssLower ? m_lower_value : m_higher_value, ltHeightWithLayer, selection == ssLower); -} - -void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) -{ - wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x); - wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y); - dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.get_bitmap() : m_bmp_thumb_higher.get_bitmap(), x_draw, y_draw); - - // Update thumb rect - update_thumb_rect(x_draw, y_draw, selection); -} - -void Control::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection) -{ - //calculate thumb position on slider line - int width, height; - get_size(&width, &height); - const wxPoint pos = is_horizontal() ? wxPoint(pos_coord, height*0.5) : wxPoint(0.5*width, pos_coord); - - // Draw thumb - draw_thumb_item(dc, pos, selection); - - // Draw info_line - draw_info_line_with_icon(dc, pos, selection); - - // Draw thumb text - draw_thumb_text(dc, pos, selection); -} - -void Control::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos) -{ - //calculate thumb position on slider line - int width, height; - get_size(&width, &height); - const wxPoint pos_l = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos); - const wxPoint pos_h = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos); - - // Draw lower thumb - draw_thumb_item(dc, pos_l, ssLower); - // Draw lower info_line - draw_info_line_with_icon(dc, pos_l, ssLower); - - // Draw higher thumb - draw_thumb_item(dc, pos_h, ssHigher); - // Draw higher info_line - draw_info_line_with_icon(dc, pos_h, ssHigher); - // Draw higher thumb text - draw_thumb_text(dc, pos_h, ssHigher); - - // Draw lower thumb text - draw_thumb_text(dc, pos_l, ssLower); -} - -void Control::draw_ticks_pair(wxDC& dc, wxCoord pos, wxCoord mid, int tick_len) -{ - int mid_space = 9; - is_horizontal() ? dc.DrawLine(pos, mid - (mid_space + tick_len), pos, mid - mid_space) : - dc.DrawLine(mid - (mid_space + tick_len), pos, mid - mid_space, pos); - is_horizontal() ? dc.DrawLine(pos, mid + (mid_space + tick_len), pos, mid + mid_space) : - dc.DrawLine(mid + (mid_space + tick_len), pos, mid + mid_space, pos); -} - -void Control::draw_ticks(wxDC& dc) -{ - if (m_draw_mode == dmSlaPrint) - return; - - dc.SetPen(m_draw_mode == dmRegular ? DARK_GREY_PEN : LIGHT_GREY_PEN ); - int height, width; - get_size(&width, &height); - const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; - for (const TickCode& tick : m_ticks.ticks) { - if (size_t(tick.tick) >= m_values.size()) { - // The case when OnPaint is called before m_ticks.ticks data are updated (specific for the vase mode) - break; - } - - const wxCoord pos = get_position_from_value(tick.tick); - draw_ticks_pair(dc, pos, mid, 7); - - // if current tick if focused, we should to use a specific "focused" icon - bool focused_tick = m_moving_pos != wxDefaultPosition && tick.tick == get_tick_near_point(m_moving_pos); - - // get icon name if it is - std::string icon_name; - - // if we have non-regular draw mode, all ticks should be marked with error icon - if (m_draw_mode != dmRegular) - icon_name = focused_tick ? "error_tick_f" : "error_tick"; - else if (tick.type == ColorChange || tick.type == ToolChange) { - if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick])) - icon_name = focused_tick ? "error_tick_f" : "error_tick"; - } - else if (tick.type == PausePrint) - icon_name = focused_tick ? "pause_print_f" : "pause_print"; - else - icon_name = focused_tick ? "edit_gcode_f" : "edit_gcode"; - - // Draw icon for "Pause print", "Custom Gcode" or conflict tick - if (!icon_name.empty()) { - wxBitmapBundle* icon = get_bmp_bundle(icon_name); - wxCoord x_draw, y_draw; - is_horizontal() ? x_draw = pos - 0.5 * m_tick_icon_dim : y_draw = pos - 0.5 * m_tick_icon_dim; - is_horizontal() ? y_draw = mid + 22 : x_draw = mid + m_thumb_size.x + 3; - - dc.DrawBitmap(icon->GetBitmapFor(this), x_draw, y_draw); - } - } -} - -std::string Control::get_color_for_tool_change_tick(std::set::const_iterator it) const -{ - const int current_extruder = it->extruder == 0 ? std::max(m_only_extruder, 1) : it->extruder; - - auto it_n = it; - while (it_n != m_ticks.ticks.begin()) { - --it_n; - if (it_n->type == ColorChange && it_n->extruder == current_extruder) - return it_n->color; - } - - return m_extruder_colors[current_extruder-1]; // return a color for a specific extruder from the colors list -} - -std::string Control::get_color_for_color_change_tick(std::set::const_iterator it) const -{ - const int def_extruder = std::max(1, m_only_extruder); - auto it_n = it; - bool is_tool_change = false; - while (it_n != m_ticks.ticks.begin()) { - --it_n; - if (it_n->type == ToolChange) { - is_tool_change = true; - if (it_n->extruder == it->extruder) - return it->color; - break; - } - if (it_n->type == ColorChange && it_n->extruder == it->extruder) - return it->color; - } - if (!is_tool_change && it->extruder == def_extruder) - return it->color; - - return ""; -} - -wxRect Control::get_colored_band_rect() -{ - int height, width; - get_size(&width, &height); - - const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width; - - return is_horizontal() ? - wxRect(SLIDER_MARGIN, lround(mid - 0.375 * m_thumb_size.y), - width - 2 * SLIDER_MARGIN + 1, lround(0.75 * m_thumb_size.y)) : - wxRect(lround(mid - 0.375 * m_thumb_size.x), SLIDER_MARGIN, - lround(0.75 * m_thumb_size.x), height - 2 * SLIDER_MARGIN + 1); -} - -void Control::draw_colored_band(wxDC& dc) -{ - if (m_draw_mode != dmRegular) - return; - - auto draw_band = [](wxDC& dc, const wxColour& clr, const wxRect& band_rc) - { - dc.SetPen(clr); - dc.SetBrush(clr); - dc.DrawRectangle(band_rc); - }; - - wxRect main_band = get_colored_band_rect(); - - // don't color a band for MultiExtruder mode - if (m_ticks.empty() || m_mode == MultiExtruder) { - draw_band(dc, GetParent()->GetBackgroundColour(), main_band); - return; - } - - const int default_color_idx = m_mode==MultiAsSingle ? std::max(m_only_extruder - 1, 0) : 0; - draw_band(dc, wxColour(m_extruder_colors[default_color_idx]), main_band); - - std::set::const_iterator tick_it = m_ticks.ticks.begin(); - - while (tick_it != m_ticks.ticks.end()) - { - if ( (m_mode == SingleExtruder && tick_it->type == ColorChange ) || - (m_mode == MultiAsSingle && (tick_it->type == ToolChange || tick_it->type == ColorChange)) ) - { - const wxCoord pos = get_position_from_value(tick_it->tick); - is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) : - main_band.SetBottom(pos - 1); - - const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : - tick_it->type == ToolChange ? - get_color_for_tool_change_tick(tick_it) : - get_color_for_color_change_tick(tick_it); - - if (!clr_str.empty()) - draw_band(dc, wxColour(clr_str), main_band); - } - ++tick_it; - } -} - -void Control::Ruler::init(const std::vector& values, double scroll_step) -{ - if (!m_parent) - return; - max_values.clear(); - max_values.reserve(std::count(values.begin(), values.end(), values.front())); - - auto it = std::find(values.begin() + 1, values.end(), values.front()); - while (it != values.end()) { - max_values.push_back(*(it - 1)); - it = std::find(it + 1, values.end(), values.front()); - } - max_values.push_back(*(it - 1)); - - update(values, scroll_step); -} - -void Control::Ruler::set_parent(wxWindow* parent) -{ - m_parent = parent; - update_dpi(); -} - -void Control::Ruler::update_dpi() -{ - if (m_parent) - m_DPI = GUI::get_dpi_for_window(m_parent); -} - -void Control::Ruler::update(const std::vector& values, double scroll_step) -{ - if (!m_parent || values.empty() || - // check if need to update ruler in respect to input values - (values.front() == m_min_val && values.back() == m_max_val && m_scroll_step == scroll_step && max_values.size() == m_max_values_cnt)) - return; - - m_min_val = values.front(); - m_max_val = values.back(); - m_scroll_step = scroll_step; - m_max_values_cnt = max_values.size(); - - int pixels_per_sm = lround((double)(m_DPI) * 5.0/25.4); - - if (lround(scroll_step) > pixels_per_sm) { - long_step = -1.0; - return; - } - - int pow = -2; - int step = 0; - auto end_it = std::find(values.begin() + 1, values.end(), values.front()); - - while (pow < 3) { - for (int istep : {1, 2, 5}) { - double val = (double)istep * std::pow(10,pow); - auto val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); - - if (val_it == values.end()) - break; - int tick = val_it - values.begin(); - - // find next tick with istep - val *= 2; - val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); - // count of short ticks between ticks - int short_ticks_cnt = val_it == values.end() ? tick : val_it - values.begin() - tick; - - if (lround(short_ticks_cnt * scroll_step) > pixels_per_sm) { - step = istep; - // there couldn't be more then 10 short ticks between ticks - short_step = 0.1 * short_ticks_cnt; - break; - } - } - if (step > 0) - break; - pow++; - } - - long_step = step == 0 ? -1.0 : (double)step* std::pow(10, pow); - if (long_step < 0) - short_step = long_step; -} - -void Control::draw_ruler(wxDC& dc) -{ - if (m_values.empty() || !m_ruler.can_draw()) - return; - // When "No sparce layer" is enabled, use m_layers_values for ruler update. - // Because of m_values has duplicate values in this case. -// m_ruler.update(this->GetParent(), m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step()); - - int height, width; - get_size(&width, &height); - const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width; - - dc.SetPen(GREY_PEN); - wxColour old_clr = dc.GetTextForeground(); - dc.SetTextForeground(GREY_PEN.GetColour()); - - auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { - if (m_ruler.short_step <= 0.0) - return; - while (current_tick < max_tick) { - wxCoord pos = get_position_from_value(lround(current_tick)); - draw_ticks_pair(dc, pos, mid, 2); - current_tick += m_ruler.short_step; - if (current_tick > m_max_value) - break; - } - }; - - double short_tick = NaNd; - int tick = 0; - double value = 0.0; - size_t sequence = 0; - int prev_y_pos = -1; - wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; - int values_size = (int)m_values.size(); - - if (m_ruler.long_step < 0) { - // sequential print when long_step wasn't detected because of a lot of printed objects - if (m_ruler.max_values.size() > 1) { - while (tick <= m_max_value && sequence < m_ruler.count()) { - // draw just ticks with max value - value = m_ruler.max_values[sequence]; - short_tick = tick; - - for (; tick < values_size; tick++) { - if (m_values[tick] == value) - break; - if (m_values[tick] > value) { - if (tick > 0) - tick--; - break; - } - } - if (tick > m_max_value) - break; - - wxCoord pos = get_position_from_value(tick); - draw_ticks_pair(dc, pos, mid, 5); - if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { - draw_tick_text(dc, wxPoint(mid, pos), tick); - prev_y_pos = pos; - } - draw_short_ticks(dc, short_tick, tick); - - sequence++; - tick++; - } - } - // very short object or some non-trivial ruler with non-regular step (see https://github.com/qidi3d/QIDISlicer/issues/7263) - else { - if (get_scroll_step() < 1) // step less then 1 px indicates very tall object with non-regular laayer step (probably in vase mode) - return; - for (size_t tick = 1; tick < m_values.size(); tick++) { - wxCoord pos = get_position_from_value(tick); - draw_ticks_pair(dc, pos, mid, 5); - draw_tick_text(dc, wxPoint(mid, pos), tick); - } - } - } - else { - while (tick <= m_max_value) { - value += m_ruler.long_step; - - if (sequence < m_ruler.count() && value > m_ruler.max_values[sequence]) - value = m_ruler.max_values[sequence]; - - short_tick = tick; - - for (; tick < values_size; tick++) { - if (m_values[tick] == value) - break; - if (m_values[tick] > value) { - if (tick > 0) - tick--; - break; - } - } - if (tick > m_max_value) - break; - - wxCoord pos = get_position_from_value(tick); - draw_ticks_pair(dc, pos, mid, 5); - if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { - draw_tick_text(dc, wxPoint(mid, pos), tick); - prev_y_pos = pos; - } - - draw_short_ticks(dc, short_tick, tick); - - if (sequence < m_ruler.count() && value == m_ruler.max_values[sequence]) { - value = 0.0; - sequence++; - tick++; - } - } - // short ticks from the last tick to the end - draw_short_ticks(dc, short_tick, m_max_value); - } - - dc.SetTextForeground(old_clr); -} - -void Control::draw_one_layer_icon(wxDC& dc) -{ - if (m_draw_mode == dmSequentialGCodeView) - return; - - //const wxBitmap& icon = m_is_one_layer ? - // m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() : - // m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp(); - const ScalableBitmap& icon = m_is_one_layer ? - m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off : m_bmp_one_layer_lock_on : - m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off : m_bmp_one_layer_unlock_on; - - int width, height; - get_size(&width, &height); - - wxCoord x_draw, y_draw; - is_horizontal() ? x_draw = width-2 : x_draw = 0.5*width - 0.5*m_lock_icon_dim; - is_horizontal() ? y_draw = 0.5*height - 0.5*m_lock_icon_dim : y_draw = height-2; - - dc.DrawBitmap(icon.bmp().GetBitmapFor(this), x_draw, y_draw); - - //update rect of the lock/unlock icon - m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim); -} - -void Control::draw_revert_icon(wxDC& dc) -{ - if (m_ticks.empty() || m_draw_mode != dmRegular) - return; - - int width, height; - get_size(&width, &height); - - wxCoord x_draw, y_draw; - is_horizontal() ? x_draw = width-2 : x_draw = 0.25*SLIDER_MARGIN; - is_horizontal() ? y_draw = 0.25*SLIDER_MARGIN: y_draw = height-2; - - dc.DrawBitmap(m_bmp_revert.get_bitmap(), x_draw, y_draw); - - //update rect of the lock/unlock icon - m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim); -} - -void Control::draw_cog_icon(wxDC& dc) -{ - if (m_draw_mode == dmSequentialGCodeView) - return; - - int width, height; - get_size(&width, &height); - - wxCoord x_draw, y_draw; - if (m_draw_mode == dmSequentialGCodeView) { - is_horizontal() ? x_draw = width - 2 : x_draw = 0.5 * width - 0.5 * m_cog_icon_dim; - is_horizontal() ? y_draw = 0.5 * height - 0.5 * m_cog_icon_dim : y_draw = height - 2; - } - else { - is_horizontal() ? x_draw = width - 2 : x_draw = width - m_cog_icon_dim - 2; - is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2; - } - - dc.DrawBitmap(m_bmp_cog.get_bitmap(), x_draw, y_draw); - - //update rect of the lock/unlock icon - m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim); -} - -void Control::update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection) -{ - const wxRect rect = is_horizontal() ? - wxRect(begin_x + (selection == ssHigher ? m_thumb_size.x / 2 : 0), begin_y, m_thumb_size.x / 2, m_thumb_size.y) : - wxRect(begin_x, begin_y + (selection == ssLower ? m_thumb_size.y / 2 : 0), m_thumb_size.x, m_thumb_size.y / 2); - - if (selection == ssLower) - m_rect_lower_thumb = rect; - else - m_rect_higher_thumb = rect; -} - -int Control::get_value_from_position(const wxCoord x, const wxCoord y) -{ - const int height = get_size().y; - const double step = get_scroll_step(); - - if (is_horizontal()) - return int(double(x - SLIDER_MARGIN) / step + 0.5); - - return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); -} - -bool Control::is_lower_thumb_editable() -{ - if (m_draw_mode == dmSequentialGCodeView) - return Slic3r::GUI::get_app_config()->get("seq_top_layer_only") == "0"; - return true; -} - -bool Control::detect_selected_slider(const wxPoint& pt) -{ - if (is_point_in_rect(pt, m_rect_lower_thumb)) - m_selection = is_lower_thumb_editable() ? ssLower : ssUndef; - else if(is_point_in_rect(pt, m_rect_higher_thumb)) - m_selection = ssHigher; - else - return false; // pt doesn't referenced to any thumb - return true; -} - -bool Control::is_point_in_rect(const wxPoint& pt, const wxRect& rect) -{ - return rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && - rect.GetTop() <= pt.y && pt.y <= rect.GetBottom(); -} - -int Control::get_tick_near_point(const wxPoint& pt) -{ - for (auto tick : m_ticks.ticks) { - const wxCoord pos = get_position_from_value(tick.tick); - - if (is_horizontal()) { - if (pos - 4 <= pt.x && pt.x <= pos + 4) - return tick.tick; - } - else { - if (pos - 4 <= pt.y && pt.y <= pos + 4) - return tick.tick; - } - } - return -1; -} - -void Control::ChangeOneLayerLock() -{ - m_is_one_layer = !m_is_one_layer; - m_selection == ssLower ? correct_lower_value() : correct_higher_value(); - if (!m_selection) m_selection = ssHigher; - - Refresh(); - Update(); - - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - ProcessWindowEvent(e); -} - -void Control::OnLeftDown(wxMouseEvent& event) -{ - if (HasCapture()) - return; - this->CaptureMouse(); - - m_is_left_down = true; - m_mouse = maNone; - - wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); - - if (is_point_in_rect(pos, m_rect_one_layer_icon)) - m_mouse = maOneLayerIconClick; - else if (is_point_in_rect(pos, m_rect_cog_icon)) - m_mouse = maCogIconClick; - else if (m_draw_mode == dmRegular) { - if (is_point_in_rect(pos, m_rect_tick_action)) { - auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); - m_mouse = it == m_ticks.ticks.end() ? maAddTick : maDeleteTick; - } - else if (is_point_in_rect(pos, m_rect_revert_icon)) - m_mouse = maRevertIconClick; - } - - if (m_mouse == maNone) - detect_selected_slider(pos); - - event.Skip(); -} - -void Control::correct_lower_value() -{ - if (m_lower_value < m_min_value) - m_lower_value = m_min_value; - else if (m_lower_value > m_max_value) - m_lower_value = m_max_value; - - if ((m_lower_value >= m_higher_value && m_lower_value <= m_max_value) || m_is_one_layer) - m_higher_value = m_lower_value; -} - -void Control::correct_higher_value() -{ - if (m_higher_value > m_max_value) - m_higher_value = m_max_value; - else if (m_higher_value < m_min_value) - m_higher_value = m_min_value; - - if ((m_higher_value <= m_lower_value && m_higher_value >= m_min_value) || m_is_one_layer) - m_lower_value = m_higher_value; -} - -wxString Control::get_tooltip(int tick/*=-1*/) -{ - if (m_focus == fiNone) - return ""; - if (m_focus == fiOneLayerIcon) - return _L("One layer mode"); - if (m_focus == fiRevertIcon) - return _L("Discard all custom changes"); - if (m_focus == fiCogIcon) - { - if (m_draw_mode == dmSequentialGCodeView) - return _L("Jump to move") + " (Shift + G)"; - else - return m_mode == MultiAsSingle ? - GUI::from_u8((boost::format(_u8L("Jump to height %s\n" - "Set ruler mode\n" - "or Set extruder sequence for the entire print")) % "(Shift + G)").str()) : - GUI::from_u8((boost::format(_u8L("Jump to height %s\n" - "or Set ruler mode")) % "(Shift + G)").str()); - } - if (m_focus == fiColorBand) - return m_mode != SingleExtruder ? "" : - _L("Edit current color - Right click the colored slider segment"); - if (m_focus == fiSmartWipeTower) - return _L("This is wipe tower layer"); - if (m_draw_mode == dmSlaPrint) - return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode - - wxString tooltip; - const auto tick_code_it = m_ticks.ticks.find(TickCode{tick}); - - if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist - { - if (m_draw_mode == dmSequentialFffPrint) - return (_L("The sequential print is on.\n" - "It's impossible to apply any custom G-code for objects printing sequentually.") + "\n"); - - // Show mode as a first string of tooltop - tooltip = " " + _L("Print mode") + ": "; - tooltip += (m_mode == SingleExtruder ? SingleExtruderMode : - m_mode == MultiAsSingle ? MultiAsSingleMode : - MultiExtruderMode ); - tooltip += "\n\n"; - - /* Note: just on OSX!!! - * Right click event causes a little scrolling. - * So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick - * Show this information in tooltip - * */ - - // Show list of actions with new tick - tooltip += ( m_mode == MultiAsSingle ? - _L("Add extruder change - Left click") : - m_mode == SingleExtruder ? - _L("Add color change - Left click for predefined color or " - "Shift + Left click for custom color selection") : - _L("Add color change - Left click") ) + " " + - _L("or press \"+\" key") + "\n" + ( - is_osx ? - _L("Add another code - Ctrl + Left click") : - _L("Add another code - Right click") ); - } - - if (tick_code_it != m_ticks.ticks.end()) // tick exists - { - if (m_draw_mode == dmSequentialFffPrint) - return _L("The sequential print is on.\n" - "It's impossible to apply any custom G-code for objects printing sequentually.\n" - "This code won't be processed during G-code generation."); - - // Show custom Gcode as a first string of tooltop - std::string space = " "; - tooltip = space; - auto format_gcode = [space](std::string gcode) { - // when the tooltip is too long, it starts to flicker, see: https://github.com/qidi3d/QIDISlicer/issues/7368 - // so we limit the number of lines shown - std::vector lines; - boost::split(lines, gcode, boost::is_any_of("\n"), boost::token_compress_off); - static const size_t MAX_LINES = 10; - if (lines.size() > MAX_LINES) { - gcode = lines.front() + '\n'; - for (size_t i = 1; i < MAX_LINES; ++i) { - gcode += lines[i] + '\n'; - } - gcode += "[" + into_u8(_L("continue")) + "]\n"; - } - boost::replace_all(gcode, "\n", "\n" + space); - return gcode; - }; - tooltip += - tick_code_it->type == ColorChange ? - (m_mode == SingleExtruder ? - format_wxstr(_L("Color change (\"%1%\")"), gcode(ColorChange)) : - format_wxstr(_L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) : - tick_code_it->type == PausePrint ? - format_wxstr(_L("Pause print (\"%1%\")"), gcode(PausePrint)) : - tick_code_it->type == Template ? - format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) : - tick_code_it->type == ToolChange ? - format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : - from_u8(format_gcode(tick_code_it->extra));// tick_code_it->type == Custom - - // If tick is marked as a conflict (exclamation icon), - // we should to explain why - ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]); - if (conflict != ctNone) - tooltip += "\n\n" + _L("Note") + "! "; - if (conflict == ctModeConflict) - tooltip += _L("G-code associated to this tick mark is in a conflict with print mode.\n" - "Editing it will cause changes of Slider data."); - else if (conflict == ctMeaninglessColorChange) - tooltip += _L("There is a color change for extruder that won't be used till the end of print job.\n" - "This code won't be processed during G-code generation."); - else if (conflict == ctMeaninglessToolChange) - tooltip += _L("There is an extruder change set to the same extruder.\n" - "This code won't be processed during G-code generation."); - else if (conflict == ctRedundant) - tooltip += _L("There is a color change for extruder that has not been used before.\n" - "Check your settings to avoid redundant color changes."); - - // Show list of actions with existing tick - if (m_focus == fiActionIcon) - tooltip += "\n\n" + _L("Delete tick mark - Left click or press \"-\" key") + "\n" + ( - is_osx ? - _L("Edit tick mark - Ctrl + Left click") : - _L("Edit tick mark - Right click") ); - } - return tooltip; - -} - -int Control::get_edited_tick_for_position(const wxPoint pos, Type type /*= ColorChange*/) -{ - if (m_ticks.empty()) - return -1; - - int tick = get_value_from_position(pos); - auto it = std::lower_bound(m_ticks.ticks.begin(), m_ticks.ticks.end(), TickCode{ tick }); - - while (it != m_ticks.ticks.begin()) { - --it; - if (it->type == type) - return it->tick; - } - - return -1; -} - -void Control::OnMotion(wxMouseEvent& event) -{ - bool action = false; - - const wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); - int tick = -1; - - if (!m_is_left_down && !m_is_right_down) { - if (is_point_in_rect(pos, m_rect_one_layer_icon)) - m_focus = fiOneLayerIcon; - else if (is_point_in_rect(pos, m_rect_tick_action)) { - m_focus = fiActionIcon; - tick = m_selection == ssLower ? m_lower_value : m_higher_value; - } - else if (!m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon)) - m_focus = fiRevertIcon; - else if (is_point_in_rect(pos, m_rect_cog_icon)) - m_focus = fiCogIcon; - else if (m_mode == SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) && - get_edited_tick_for_position(pos) >= 0 ) - m_focus = fiColorBand; - else if (is_point_in_rect(pos, m_rect_lower_thumb)) - m_focus = fiLowerThumb; - else if (is_point_in_rect(pos, m_rect_higher_thumb)) - m_focus = fiHigherThumb; - else { - tick = get_tick_near_point(pos); - if (tick < 0 && m_is_wipe_tower) { - tick = get_value_from_position(pos); - m_focus = tick > 0 && is_wipe_tower_layer(tick) && (tick == m_lower_value || tick == m_higher_value) ? - fiSmartWipeTower : fiTick; - } - else - m_focus = fiTick; - } - m_moving_pos = pos; - } - else if (m_is_left_down || m_is_right_down) { - if (m_selection == ssLower) { - int current_value = m_lower_value; - m_lower_value = get_value_from_position(pos.x, pos.y); - correct_lower_value(); - action = (current_value != m_lower_value); - } - else if (m_selection == ssHigher) { - int current_value = m_higher_value; - m_higher_value = get_value_from_position(pos.x, pos.y); - correct_higher_value(); - action = (current_value != m_higher_value); - } - m_moving_pos = wxDefaultPosition; - } - Refresh(); - Update(); - event.Skip(); - - // Set tooltips with information for each icon - if (GUI::wxGetApp().is_editor()) - this->SetToolTip(get_tooltip(tick)); - - if (action) { - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - e.SetString("moving"); - ProcessWindowEvent(e); - } -} - -void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current_code/* = false*/) -{ - const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); - if (extruders_cnt > 1) { - std::array active_extruders = get_active_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); - - std::vector icons = get_extruder_color_icons(true); - - wxMenu* change_extruder_menu = new wxMenu(); - - for (int i = 1; i <= extruders_cnt; i++) { - const bool is_active_extruder = i == active_extruders[0] || i == active_extruders[1]; - const wxString item_name = wxString::Format(_L("Extruder %d"), i) + - (is_active_extruder ? " (" + _L("active") + ")" : ""); - - if (m_mode == MultiAsSingle) - append_menu_item(change_extruder_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, icons[i-1], menu, - [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); - } - - const wxString change_extruder_menu_name = m_mode == MultiAsSingle ? - (switch_current_code ? _L("Switch code to Change extruder") : _L("Change extruder") ) : - _L("Change extruder (N/A)"); - - append_submenu(menu, change_extruder_menu, wxID_ANY, change_extruder_menu_name, _L("Use another extruder"), - active_extruders[1] > 0 ? "edit_uni" : "change_extruder", - [this]() {return m_mode == MultiAsSingle && !GUI::wxGetApp().obj_list()->has_paint_on_segmentation(); }, GUI::wxGetApp().plater()); - } -} - -void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_current_code/* = false*/) -{ - const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); - if (extruders_cnt > 1) { - int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - std::set used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_only_extruder, m_values[tick]); - - wxMenu* add_color_change_menu = new wxMenu(); - - for (int i = 1; i <= extruders_cnt; i++) { - const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder - used_extruders_for_tick.find(i) != used_extruders_for_tick.end(); - const wxString item_name = wxString::Format(_L("Extruder %d"), i) + - (is_used_extruder ? " (" + _L("used") + ")" : ""); - - append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code_as_tick(ColorChange, i); }, "", menu, - []() { return true; }, GUI::wxGetApp().plater()); - } - - const wxString menu_name = switch_current_code ? - format_wxstr(_L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) : - format_wxstr(_L("Add color change (%1%) for:"), gcode(ColorChange)); - wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); - add_color_change_menu_item->SetBitmap(*get_bmp_bundle("colorchange_add_m")); - } -} - -void Control::OnLeftUp(wxMouseEvent& event) -{ - if (!HasCapture()) - return; - this->ReleaseMouse(); - m_is_left_down = false; - - switch (m_mouse) { - case maNone : - move_current_thumb_to_pos(event.GetLogicalPosition(wxClientDC(this))); - break; - case maDeleteTick : - delete_current_tick(); - break; - case maAddTick : - add_current_tick(); - break; - case maCogIconClick : - show_cog_icon_context_menu(); - break; - case maOneLayerIconClick: - switch_one_layer_mode(); - break; - case maRevertIconClick: - discard_all_thicks(); - break; - default : - break; - } - - Refresh(); - Update(); - event.Skip(); - - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - ProcessWindowEvent(e); -} - -void Control::enter_window(wxMouseEvent& event, const bool enter) -{ - m_is_focused = enter; - Refresh(); - Update(); - event.Skip(); -} - -// "condition" have to be true for: -// - value increase (if wxSL_VERTICAL) -// - value decrease (if wxSL_HORIZONTAL) -void Control::move_current_thumb(const bool condition) -{ -// m_is_one_layer = wxGetKeyState(WXK_CONTROL); - int delta = condition ? -1 : 1; - if (is_horizontal()) - delta *= -1; - - // accelerators - int accelerator = 0; - if (wxGetKeyState(WXK_SHIFT)) - accelerator += 5; - if (wxGetKeyState(WXK_CONTROL)) - accelerator += 5; - if (accelerator > 0) - delta *= accelerator; - - if (m_selection == ssUndef) - m_selection = ssHigher; - - if (m_selection == ssLower) { - m_lower_value -= delta; - correct_lower_value(); - } - else if (m_selection == ssHigher) { - m_higher_value -= delta; - correct_higher_value(); - } - Refresh(); - Update(); - - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - ProcessWindowEvent(e); -} - -void Control::OnWheel(wxMouseEvent& event) -{ - // Set nearest to the mouse thumb as a selected, if there is not selected thumb - if (m_selection == ssUndef) { - const wxPoint& pt = event.GetLogicalPosition(wxClientDC(this)); - - if (is_horizontal()) - m_selection = abs(pt.x - m_rect_lower_thumb.GetRight()) <= - abs(pt.x - m_rect_higher_thumb.GetLeft()) ? - ssLower : ssHigher; - else - m_selection = abs(pt.y - m_rect_lower_thumb.GetTop()) <= - abs(pt.y - m_rect_higher_thumb.GetBottom()) ? - ssLower : ssHigher; - } - - if (m_selection == ssLower && !is_lower_thumb_editable()) - m_selection = ssUndef; - - move_current_thumb((m_draw_mode == dmSequentialGCodeView) ? event.GetWheelRotation() < 0 : event.GetWheelRotation() > 0); -} - -void Control::OnKeyDown(wxKeyEvent &event) -{ - const int key = event.GetKeyCode(); - if (m_draw_mode != dmSequentialGCodeView && key == WXK_NUMPAD_ADD) { - // OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice. - // To avoid this case we should suppress second add_tick() call. - m_ticks.suppress_plus(true); - add_current_tick(true); - } - else if (m_draw_mode != dmSequentialGCodeView && (key == WXK_NUMPAD_SUBTRACT || key == WXK_DELETE || key == WXK_BACK)) { - // OnChar() is called immediately after OnKeyDown(), which can cause call of delete_tick() twice. - // To avoid this case we should suppress second delete_tick() call. - m_ticks.suppress_minus(true); - delete_current_tick(); - } - else if (m_draw_mode != dmSequentialGCodeView && event.GetKeyCode() == WXK_SHIFT) - UseDefaultColors(false); - else if (is_horizontal()) { - if (m_is_focused) { - if (key == WXK_LEFT || key == WXK_RIGHT) - move_current_thumb(key == WXK_LEFT); - else if (key == WXK_UP || key == WXK_DOWN) { - if (key == WXK_DOWN) - m_selection = ssHigher; - else if (key == WXK_UP && is_lower_thumb_editable()) - m_selection = ssLower; - Refresh(); - } - } - else { - if (key == WXK_LEFT || key == WXK_RIGHT) - move_current_thumb(key == WXK_LEFT); - } - } - else { - if (m_is_focused) { - if (key == WXK_LEFT || key == WXK_RIGHT) { - if (key == WXK_LEFT) - m_selection = ssHigher; - else if (key == WXK_RIGHT && is_lower_thumb_editable()) - m_selection = ssLower; - Refresh(); - } - else if (key == WXK_UP || key == WXK_DOWN) - move_current_thumb(key == WXK_UP); - } - else { - if (key == WXK_UP || key == WXK_DOWN) - move_current_thumb(key == WXK_UP); - } - } - - event.Skip(); // !Needed to have EVT_CHAR generated as well -} - -void Control::OnKeyUp(wxKeyEvent &event) -{ - if (event.GetKeyCode() == WXK_CONTROL) - m_is_one_layer = false; - else if (event.GetKeyCode() == WXK_SHIFT) - UseDefaultColors(true); - - Refresh(); - Update(); - event.Skip(); -} - -void Control::OnChar(wxKeyEvent& event) -{ - const int key = event.GetKeyCode(); - if (m_draw_mode != dmSequentialGCodeView) { - if (key == '+' && !m_ticks.suppressed_plus()) { - add_current_tick(true); - m_ticks.suppress_plus(false); - } - else if (key == '-' && !m_ticks.suppressed_minus()) { - delete_current_tick(); - m_ticks.suppress_minus(false); - } - } - if (key == 'G') - jump_to_value(); -} - -void Control::OnRightDown(wxMouseEvent& event) -{ - if (HasCapture() || m_is_left_down) - return; - this->CaptureMouse(); - - const wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); - - m_mouse = maNone; - if (m_draw_mode == dmRegular) { - if (is_point_in_rect(pos, m_rect_tick_action)) { - const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - m_mouse = m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end() ? - maAddMenu : maEditMenu; - } - else if (m_mode == SingleExtruder && !detect_selected_slider(pos) && is_point_in_rect(pos, get_colored_band_rect())) - m_mouse = maForceColorEdit; - else if (m_mode == MultiAsSingle && is_point_in_rect(pos, m_rect_cog_icon)) - m_mouse = maCogIconMenu; - } - if (m_mouse != maNone || !detect_selected_slider(pos)) - return; - - if (m_selection == ssLower) - m_higher_value = m_lower_value; - else - m_lower_value = m_higher_value; - - // set slider to "one layer" mode - m_is_right_down = m_is_one_layer = true; - - Refresh(); - Update(); - event.Skip(); -} - -// Get active extruders for tick. -// Means one current extruder for not existing tick OR -// 2 extruders - for existing tick (extruder before ToolChange and extruder of current existing tick) -// Use those values to disable selection of active extruders -std::array Control::get_active_extruders_for_tick(int tick) const -{ - int default_initial_extruder = m_mode == MultiAsSingle ? std::max(1, m_only_extruder) : 1; - std::array extruders = { default_initial_extruder, -1 }; - if (m_ticks.empty()) - return extruders; - - auto it = m_ticks.ticks.lower_bound(TickCode{tick}); - - if (it != m_ticks.ticks.end() && it->tick == tick) // current tick exists - extruders[1] = it->extruder; - - while (it != m_ticks.ticks.begin()) { - --it; - if(it->type == ToolChange) { - extruders[0] = it->extruder; - break; - } - } - - return extruders; -} - -// Get used extruders for tick. -// Means all extruders(tools) which will be used during printing from current tick to the end -std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode/* = Undef*/) const -{ - Mode e_mode = !force_mode ? mode : force_mode; - - if (e_mode == MultiExtruder) { - // #ys_FIXME: get tool ordering from _correct_ place - const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering(); - - if (tool_ordering.empty()) - return {}; - - std::set used_extruders; - - auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), print_z, [](const LayerTools &lhs, double rhs){ return lhs.print_z < rhs; }); - for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) { - const std::vector& extruders = it_layer_tools->extruders; - for (const auto& extruder : extruders) - used_extruders.emplace(extruder+1); - } - - return used_extruders; - } - - const int default_initial_extruder = e_mode == MultiAsSingle ? std::max(only_extruder, 1) : 1; - if (ticks.empty() || e_mode == SingleExtruder) - return {default_initial_extruder}; - - std::set used_extruders; - - auto it_start = ticks.lower_bound(TickCode{tick}); - auto it = it_start; - if (it == ticks.begin() && it->type == ToolChange && - tick != it->tick ) // In case of switch of ToolChange to ColorChange, when tick exists, - // we shouldn't change color for extruder, which will be deleted - { - used_extruders.emplace(it->extruder); - if (tick < it->tick) - used_extruders.emplace(default_initial_extruder); - } - - while (it != ticks.begin()) { - --it; - if (it->type == ToolChange && tick != it->tick) { - used_extruders.emplace(it->extruder); - break; - } - } - - if (it == ticks.begin() && used_extruders.empty()) - used_extruders.emplace(default_initial_extruder); - - for (it = it_start; it != ticks.end(); ++it) - if (it->type == ToolChange && tick != it->tick) - used_extruders.emplace(it->extruder); - - return used_extruders; -} - -void Control::show_add_context_menu() -{ - wxMenu menu; - - if (m_mode == SingleExtruder) { - append_menu_item(&menu, wxID_ANY, _L("Add color change") + " (" + gcode(ColorChange) + ")", "", - [this](wxCommandEvent&) { add_code_as_tick(ColorChange); }, "colorchange_add_m", &menu); - - UseDefaultColors(false); - } - else { - append_change_extruder_menu_item(&menu); - append_add_color_change_menu_item(&menu); - } - - if (!gcode(PausePrint).empty()) - append_menu_item(&menu, wxID_ANY, _L("Add pause print") + " (" + gcode(PausePrint) + ")", "", - [this](wxCommandEvent&) { add_code_as_tick(PausePrint); }, "pause_print", &menu); - - if (!gcode(Template).empty()) - append_menu_item(&menu, wxID_ANY, _L("Add custom template") + " (" + gcode(Template) + ")", "", - [this](wxCommandEvent&) { add_code_as_tick(Template); }, "edit_gcode", &menu); - - append_menu_item(&menu, wxID_ANY, _L("Add custom G-code"), "", - [this](wxCommandEvent&) { add_code_as_tick(Custom); }, "edit_gcode", &menu); - - GUI::wxGetApp().plater()->PopupMenu(&menu); -} - -void Control::show_edit_context_menu() -{ - wxMenu menu; - - std::set::iterator it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); - - if (it->type == ToolChange) { - if (m_mode == MultiAsSingle) - append_change_extruder_menu_item(&menu); - append_add_color_change_menu_item(&menu, true); - } - else - append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _L("Edit color") : - it->type == PausePrint ? _L("Edit pause print message") : - _L("Edit custom G-code"), "", - [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu); - - if (it->type == ColorChange && m_mode == MultiAsSingle) - append_change_extruder_menu_item(&menu, true); - - append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _L("Delete color change") : - it->type == ToolChange ? _L("Delete tool change") : - it->type == PausePrint ? _L("Delete pause print") : - _L("Delete custom G-code"), "", - [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu); - - GUI::wxGetApp().plater()->PopupMenu(&menu); -} - -void Control::show_cog_icon_context_menu() -{ - wxMenu menu; - - append_menu_item(&menu, wxID_ANY, _L("Jump to height") + " (Shift+G)", "", - [this](wxCommandEvent&) { jump_to_value(); }, "", & menu); - - wxMenu* ruler_mode_menu = new wxMenu(); - if (ruler_mode_menu) { - append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("None"), _L("Hide ruler"), - [this](wxCommandEvent&) { if (m_extra_style != 0) m_extra_style = 0; }, ruler_mode_menu, - []() { return true; }, [this]() { return m_extra_style == 0; }, GUI::wxGetApp().plater()); - - append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show object height"), _L("Show object height on the ruler"), - [this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style ^= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu, - []() { return true; }, [this]() { return m_extra_style & wxSL_AUTOTICKS; }, GUI::wxGetApp().plater()); - - append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show estimated print time"), _L("Show estimated print time on the ruler"), - [this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style ^= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu, - []() { return true; }, [this]() { return m_extra_style & wxSL_VALUE_LABEL; }, GUI::wxGetApp().plater()); - - append_submenu(&menu, ruler_mode_menu, wxID_ANY, _L("Ruler mode"), _L("Set ruler mode"), "", - []() { return true; }, this); - } - - if (m_mode == MultiAsSingle && m_draw_mode == dmRegular) - append_menu_item(&menu, wxID_ANY, _L("Set extruder sequence for the entire print"), "", - [this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu); - - if (GUI::wxGetApp().is_editor() && m_mode != MultiExtruder && m_draw_mode == dmRegular) - append_menu_item(&menu, wxID_ANY, _L("Set auto color changes"), "", - [this](wxCommandEvent&) { auto_color_change(); }, "", &menu); - - GUI::wxGetApp().plater()->PopupMenu(&menu); -} - -bool check_color_change(const PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, std::function break_condition) -{ - double prev_area = area(object->get_layer(frst_layer_id)->lslices); - - bool detected = false; - for (size_t i = frst_layer_id+1; i < layers_cnt; i++) { - const Layer* layer = object->get_layer(i); - double cur_area = area(layer->lslices); - - // check for overhangs - if (check_overhangs && cur_area > prev_area && !equivalent_areas(prev_area, cur_area)) - break; - - // Check percent of the area decrease. - // This value have to be more than min_delta_area and more then 10% - if ((prev_area - cur_area > min_delta_area) && (cur_area / prev_area < 0.9)) { - detected = true; - if (break_condition(layer)) - break; - } - - prev_area = cur_area; - } - return detected; -} - -void Control::auto_color_change() -{ - if (!m_ticks.empty()) { - wxString msg_text = _L("This action will cause deletion of all ticks on vertical slider.") + "\n\n" + - _L("This action is not revertible.\nDo you want to proceed?"); - GUI::WarningDialog dialog(m_parent, msg_text, _L("Warning"), wxYES | wxNO); - if (dialog.ShowModal() == wxID_NO) - return; - m_ticks.ticks.clear(); - } - - int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); -// int extruder = 2; - - const Print& print = GUI::wxGetApp().plater()->fff_print(); - for (auto object : print.objects()) { - // An object should to have at least 2 layers to apply an auto color change - if (object->layer_count() < 2) - continue; - - check_color_change(object, 1, object->layers().size(), false, [this, extruders_cnt](const Layer* layer) - { - int tick = get_tick_from_value(layer->print_z); - if (tick >= 0 && !m_ticks.has_tick(tick)) { - if (m_mode == SingleExtruder) { - m_ticks.set_default_colors(true); - m_ticks.add_tick(tick, ColorChange, 1, layer->print_z); - } - else { - int extruder = 2; - if (!m_ticks.empty()) { - auto it = m_ticks.ticks.end(); - it--; - extruder = it->extruder + 1; - if (extruder > extruders_cnt) - extruder = 1; - } - m_ticks.add_tick(tick, ToolChange, extruder, layer->print_z); - } - } - // allow max 3 auto color changes - return m_ticks.ticks.size() > 2; - }); - } - - if (m_ticks.empty()) - GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyAutoColorChange); - - post_ticks_changed_event(); -} - -void Control::OnRightUp(wxMouseEvent& event) -{ - if (!HasCapture() || m_is_left_down) - return; - this->ReleaseMouse(); - m_is_right_down = m_is_one_layer = false; - - if (m_mouse == maForceColorEdit) { - wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); - int edited_tick = get_edited_tick_for_position(pos); - if (edited_tick >= 0) - edit_tick(edited_tick); - } - else if (m_mouse == maAddMenu) - show_add_context_menu(); - else if (m_mouse == maEditMenu) - show_edit_context_menu(); - else if (m_mouse == maCogIconMenu) - show_cog_icon_context_menu(); - - Refresh(); - Update(); - event.Skip(); -} - -static std::string get_new_color(const std::string& color) -{ - wxColour clr(color); - if (!clr.IsOk()) - clr = wxColour(0, 0, 0); // Don't set alfa to transparence - - auto data = new wxColourData(); - data->SetChooseFull(1); - data->SetColour(clr); - - wxColourDialog dialog(GUI::wxGetApp().GetTopWindow(), data); - dialog.CenterOnParent(); - if (dialog.ShowModal() == wxID_OK) - return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); - return ""; -} - -/* To avoid get an empty string from wxTextEntryDialog - * Let disable OK button, if TextCtrl is empty - * OR input value is our of range (min..max), when min a nd max are positive - * */ -static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, double max = -1.0) -{ - GUI::wxGetApp().UpdateDlgDarkUI(dlg); - - // detect TextCtrl and OK button - wxTextCtrl* textctrl {nullptr}; - wxWindowList& dlg_items = dlg->GetChildren(); - for (auto item : dlg_items) { - textctrl = dynamic_cast(item); - if (textctrl) - break; - } - - if (!textctrl) - return; - - textctrl->SetInsertionPointEnd(); - - wxButton* btn_OK = static_cast(dlg->FindWindowById(wxID_OK)); - btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt) - { - bool disable = textctrl->IsEmpty(); - if (!disable && min >= 0.0 && max >= 0.0) { - double value = -1.0; - if (!textctrl->GetValue().ToDouble(&value)) // input value couldn't be converted to double - disable = true; - else - disable = value < min - epsilon() || value > max + epsilon(); // is input value is out of valid range ? - } - - evt.Enable(!disable); - }, btn_OK->GetId()); -} - -static std::string get_custom_code(const std::string& code_in, double height) -{ - wxString msg_text = _L("Enter custom G-code used on current layer") + ":"; - wxString msg_header = format_wxstr(_L("Custom G-code on current layer (%1% mm)."), height); - - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, - wxTextEntryDialogStyle | wxTE_MULTILINE); - upgrade_text_entry_dialog(&dlg); - - bool valid = true; - std::string value; - do { - if (dlg.ShowModal() != wxID_OK) - return ""; - - value = into_u8(dlg.GetValue()); - valid = GUI::Tab::validate_custom_gcode("Custom G-code", value); - } while (!valid); - return value; -} - -static std::string get_pause_print_msg(const std::string& msg_in, double height) -{ - wxString msg_text = _L("Enter short message shown on Printer display when a print is paused") + ":"; - wxString msg_header = format_wxstr(_L("Message for pause print on current layer (%1% mm)."), height); - - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), - wxTextEntryDialogStyle); - upgrade_text_entry_dialog(&dlg); - - if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) - return ""; - - return into_u8(dlg.GetValue()); -} - -static double get_value_to_jump(double active_value, double min_z, double max_z, DrawMode mode) -{ - wxString msg_text = (mode == dmSequentialGCodeView) ? _L("Enter the move you want to jump to") + ":" : _L("Enter the height you want to jump to") + ":"; - wxString msg_header = (mode == dmSequentialGCodeView) ? _L("Jump to move") : _L("Jump to height"); - wxString msg_in = GUI::double_to_string(active_value); - - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, msg_in, wxTextEntryDialogStyle); - upgrade_text_entry_dialog(&dlg, min_z, max_z); - - if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) - return -1.0; - - double value = -1.0; - return dlg.GetValue().ToDouble(&value) ? value : -1.0; -} - -void Control::add_code_as_tick(Type type, int selected_extruder/* = -1*/) -{ - if (m_selection == ssUndef) - return; - const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - - if ( !check_ticks_changed_event(type) ) - return; - - if (type == ColorChange && gcode(ColorChange).empty()) - GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyColorChangeCode); - - const int extruder = selected_extruder > 0 ? selected_extruder : std::max(1, m_only_extruder); - const auto it = m_ticks.ticks.find(TickCode{ tick }); - - if ( it == m_ticks.ticks.end() ) { - // try to add tick - if (!m_ticks.add_tick(tick, type, extruder, m_values[tick])) - return; - } - else if (type == ToolChange || type == ColorChange) { - // try to switch tick code to ToolChange or ColorChange accordingly - if (!m_ticks.switch_code_for_tick(it, type, extruder)) - return; - } - else - return; - - post_ticks_changed_event(type); -} - -void Control::add_current_tick(bool call_from_keyboard /*= false*/) -{ - if (m_selection == ssUndef) - return; - const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - auto it = m_ticks.ticks.find(TickCode{ tick }); - - if (it != m_ticks.ticks.end() || // this tick is already exist - !check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange)) - return; - - if (m_mode == SingleExtruder) - add_code_as_tick(ColorChange); - else - { - wxMenu menu; - - if (m_mode == MultiAsSingle) - append_change_extruder_menu_item(&menu); - else - append_add_color_change_menu_item(&menu); - - wxPoint pos = wxDefaultPosition; - /* Menu position will be calculated from mouse click position, but... - * if function is called from keyboard (pressing "+"), we should to calculate it - * */ - if (call_from_keyboard) { - int width, height; - get_size(&width, &height); - - const wxCoord coord = 0.75 * (is_horizontal() ? height : width); - this->GetPosition(&width, &height); - - pos = is_horizontal() ? - wxPoint(get_position_from_value(tick), height + coord) : - wxPoint(width + coord, get_position_from_value(tick)); - } - - GUI::wxGetApp().plater()->PopupMenu(&menu, pos); - } -} - -void Control::delete_current_tick() -{ - if (m_selection == ssUndef) - return; - - auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); - if (it == m_ticks.ticks.end() || - !check_ticks_changed_event(it->type)) - return; - - Type type = it->type; - m_ticks.ticks.erase(it); - post_ticks_changed_event(type); -} - -void Control::edit_tick(int tick/* = -1*/) -{ - if (tick < 0) - tick = m_selection == ssLower ? m_lower_value : m_higher_value; - const std::set::iterator it = m_ticks.ticks.find(TickCode{ tick }); - - if (it == m_ticks.ticks.end() || - !check_ticks_changed_event(it->type)) - return; - - Type type = it->type; - if (m_ticks.edit_tick(it, m_values[it->tick])) - post_ticks_changed_event(type); -} - -// switch on/off one layer mode -void Control::switch_one_layer_mode() -{ - m_is_one_layer = !m_is_one_layer; - if (!m_is_one_layer) { - SetLowerValue(m_min_value); - SetHigherValue(m_max_value); - } - m_selection == ssLower ? correct_lower_value() : correct_higher_value(); - if (m_selection == ssUndef) m_selection = ssHigher; -} - -// discard all custom changes on DoubleSlider -void Control::discard_all_thicks() -{ - SetLowerValue(m_min_value); - SetHigherValue(m_max_value); - - m_selection == ssLower ? correct_lower_value() : correct_higher_value(); - if (m_selection == ssUndef) m_selection = ssHigher; - - m_ticks.ticks.clear(); - post_ticks_changed_event(); - -} - -// Set current thumb position to the nearest tick (if it is) -// OR to a value corresponding to the mouse click (pos) -void Control::move_current_thumb_to_pos(wxPoint pos) -{ - const int tick_val = get_tick_near_point(pos); - const int mouse_val = tick_val >= 0 && m_draw_mode == dmRegular ? tick_val : - get_value_from_position(pos); - if (mouse_val >= 0) { - if (m_selection == ssLower) { - SetLowerValue(mouse_val); - correct_lower_value(); - } - else { // even m_selection is ssUndef, upper thumb should be selected - SetHigherValue(mouse_val); - correct_higher_value(); - } - } -} - -void Control::edit_extruder_sequence() -{ - if (!check_ticks_changed_event(ToolChange)) - return; - - GUI::ExtruderSequenceDialog dlg(m_extruders_sequence); - if (dlg.ShowModal() != wxID_OK) - return; - m_extruders_sequence = dlg.GetValue(); - - m_ticks.erase_all_ticks_with_code(ToolChange); - - const int extr_cnt = m_extruders_sequence.extruders.size(); - if (extr_cnt == 1) - return; - - int tick = 0; - double value = 0.0; - int extruder = -1; - - std::random_device rd; //Will be used to obtain a seed for the random number engine - std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() - std::uniform_int_distribution<> distrib(0, extr_cnt-1); - - while (tick <= m_max_value) - { - bool color_repetition = false; - if (m_extruders_sequence.random_sequence) { - int rand_extr = distrib(gen); - if (m_extruders_sequence.color_repetition) - color_repetition = rand_extr == extruder; - else - while (rand_extr == extruder) - rand_extr = distrib(gen); - extruder = rand_extr; - } - else { - extruder++; - if (extruder == extr_cnt) - extruder = 0; - } - - const int cur_extruder = m_extruders_sequence.extruders[extruder]; - - bool meaningless_tick = tick == 0.0 && cur_extruder == extruder; - if (!meaningless_tick && !color_repetition) - m_ticks.ticks.emplace(TickCode{tick, ToolChange,cur_extruder + 1, m_extruder_colors[cur_extruder]}); - - if (m_extruders_sequence.is_mm_intervals) { - value += m_extruders_sequence.interval_by_mm; - tick = get_tick_from_value(value, true); - if (tick < 0) - break; - } - else - tick += m_extruders_sequence.interval_by_layers; - } - - post_ticks_changed_event(ToolChange); -} - -void Control::jump_to_value() -{ - double value = get_value_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value], - m_values[m_min_value], m_values[m_max_value], m_draw_mode); - if (value < 0.0) - return; - - int tick_value = get_tick_from_value(value); - - if (m_selection == ssLower) - SetLowerValue(tick_value); - else - SetHigherValue(tick_value); -} - -void Control::post_ticks_changed_event(Type type /*= Custom*/) -{ -// m_force_mode_apply = type != ToolChange; // It looks like this condition is no needed now. Leave it for the testing - - wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); -} - -bool Control::check_ticks_changed_event(Type type) -{ - if ( m_ticks.mode == m_mode || - (type != ColorChange && type != ToolChange) || - (m_ticks.mode == SingleExtruder && m_mode == MultiAsSingle) || // All ColorChanges will be applied for 1st extruder - (m_ticks.mode == MultiExtruder && m_mode == MultiAsSingle) ) // Just mark ColorChanges for all unused extruders - return true; - - if ((m_ticks.mode == SingleExtruder && m_mode == MultiExtruder ) || - (m_ticks.mode == MultiExtruder && m_mode == SingleExtruder) ) - { - if (!m_ticks.has_tick_with_code(ColorChange)) - return true; - - wxString message = (m_ticks.mode == SingleExtruder ? - _L("The last color change data was saved for a single extruder printing.") : - _L("The last color change data was saved for a multi extruder printing.") - ) + "\n" + - _L("Your current changes will delete all saved color changes.") + "\n\n\t" + - _L("Are you sure you want to continue?"); - - //wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO); - GUI::MessageDialog msg(this, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_YES) { - m_ticks.erase_all_ticks_with_code(ColorChange); - post_ticks_changed_event(ColorChange); - } - return false; - } - // m_ticks_mode == MultiAsSingle - if( m_ticks.has_tick_with_code(ToolChange) ) { - wxString message = m_mode == SingleExtruder ? ( - _L("The last color change data was saved for a multi extruder printing.") + "\n\n" + - _L("Select YES if you want to delete all saved tool changes, \n" - "NO if you want all tool changes switch to color changes, \n" - "or CANCEL to leave it unchanged.") + "\n\n\t" + - _L("Do you want to delete all saved tool changes?") - ): ( // MultiExtruder - _L("The last color change data was saved for a multi extruder printing with tool changes for whole print.") + "\n\n" + - _L("Your current changes will delete all saved extruder (tool) changes.") + "\n\n\t" + - _L("Are you sure you want to continue?") ) ; - - //wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO | (m_mode == SingleExtruder ? wxCANCEL : 0)); - GUI::MessageDialog msg(this, message, _L("Notice"), wxYES_NO | (m_mode == SingleExtruder ? wxCANCEL : 0)); - const int answer = msg.ShowModal(); - if (answer == wxID_YES) { - m_ticks.erase_all_ticks_with_code(ToolChange); - post_ticks_changed_event(ToolChange); - } - else if (m_mode == SingleExtruder && answer == wxID_NO) { - m_ticks.switch_code(ToolChange, ColorChange); - post_ticks_changed_event(ColorChange); - } - return false; - } - - return true; -} - -std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder) -{ - auto opposite_one_color = [](const std::string& color) { - ColorRGB rgb; - decode_color(color, rgb); - return encode_color(opposite(rgb)); - }; - auto opposite_two_colors = [](const std::string& a, const std::string& b) { - ColorRGB rgb1; decode_color(a, rgb1); - ColorRGB rgb2; decode_color(b, rgb2); - return encode_color(opposite(rgb1, rgb2)); - }; - - if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) { -#if 1 - if (ticks.empty()) - return opposite_one_color((*m_colors)[0]); - - auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick); - if (before_tick_it == ticks.end()) { - while (before_tick_it != ticks.begin()) - if (--before_tick_it; before_tick_it->type == ColorChange) - break; - if (before_tick_it->type == ColorChange) - return opposite_one_color(before_tick_it->color); - - return opposite_one_color((*m_colors)[0]); - } - - if (before_tick_it == ticks.begin()) { - const std::string& frst_color = (*m_colors)[0]; - if (before_tick_it->type == ColorChange) - return opposite_two_colors(frst_color, before_tick_it->color); - - auto next_tick_it = before_tick_it; - while (next_tick_it != ticks.end()) - if (++next_tick_it; next_tick_it->type == ColorChange) - break; - if (next_tick_it->type == ColorChange) - return opposite_two_colors(frst_color, next_tick_it->color); - - return opposite_one_color(frst_color); - } - - std::string frst_color = ""; - if (before_tick_it->type == ColorChange) - frst_color = before_tick_it->color; - else { - auto next_tick_it = before_tick_it; - while (next_tick_it != ticks.end()) - if (++next_tick_it; next_tick_it->type == ColorChange) { - frst_color = next_tick_it->color; - break; - } - } - - while (before_tick_it != ticks.begin()) - if (--before_tick_it; before_tick_it->type == ColorChange) - break; - - if (before_tick_it->type == ColorChange) { - if (frst_color.empty()) - return opposite_one_color(before_tick_it->color); - - return opposite_two_colors(before_tick_it->color, frst_color); - } - - if (frst_color.empty()) - return opposite_one_color((*m_colors)[0]); - - return opposite_two_colors((*m_colors)[0], frst_color); -#else - const std::vector& colors = ColorPrintColors::get(); - if (ticks.empty()) - return colors[0]; - m_default_color_idx++; - - return colors[m_default_color_idx % colors.size()]; -#endif - } - - std::string color = (*m_colors)[extruder - 1]; - - if (type == ColorChange) { - if (!ticks.empty()) { - auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick ); - while (before_tick_it != ticks.begin()) { - --before_tick_it; - if (before_tick_it->type == ColorChange && before_tick_it->extruder == extruder) { - color = before_tick_it->color; - break; - } - } - } - - color = get_new_color(color); - } - return color; -} - -bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, double print_z) -{ - std::string color; - std::string extra; - if (type == Custom) // custom Gcode - { - extra = get_custom_code(custom_gcode, print_z); - if (extra.empty()) - return false; - custom_gcode = extra; - } - else if (type == PausePrint) { - extra = get_pause_print_msg(pause_print_msg, print_z); - if (extra.empty()) - return false; - pause_print_msg = extra; - } - else { - color = get_color_for_tick(TickCode{ tick }, type, extruder); - if (color.empty()) - return false; - } - - if (mode == SingleExtruder) - m_use_default_colors = true; - - ticks.emplace(TickCode{ tick, type, extruder, color, extra }); - return true; -} - -bool TickCodeInfo::edit_tick(std::set::iterator it, double print_z) -{ - // Save previously value of the tick before the call a Dialog from get_... functions, - // otherwise a background process can change ticks values and current iterator wouldn't be valid for the moment of a Dialog close - TickCode changed_tick = *it; - - std::string edited_value; - if (it->type == ColorChange) - edited_value = get_new_color(it->color); - else if (it->type == PausePrint) - edited_value = get_pause_print_msg(it->extra, print_z); - else - edited_value = get_custom_code(it->type == Template ? gcode(Template) : it->extra, print_z); - - if (edited_value.empty()) - return false; - - // Update iterator. For this moment its value can be invalid - if (it = ticks.find(changed_tick); it == ticks.end()) - return false; - - if (it->type == ColorChange) { - if (it->color == edited_value) - return false; - changed_tick.color = edited_value; - } - else if (it->type == Template) { - if (gcode(Template) == edited_value) - return false; - changed_tick.extra = edited_value; - changed_tick.type = Custom; - } - else if (it->type == Custom || it->type == PausePrint) { - if (it->extra == edited_value) - return false; - changed_tick.extra = edited_value; - } - - ticks.erase(it); - ticks.emplace(changed_tick); - - return true; -} - -void TickCodeInfo::switch_code(Type type_from, Type type_to) -{ - for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) - if (it->type == type_from) { - TickCode tick = *it; - tick.type = type_to; - tick.extruder = 1; - ticks.erase(it); - it = ticks.emplace(tick).first; - } - else - ++it; -} - -bool TickCodeInfo::switch_code_for_tick(std::set::iterator it, Type type_to, const int extruder) -{ - const std::string color = get_color_for_tick(*it, type_to, extruder); - if (color.empty()) - return false; - - TickCode changed_tick = *it; - changed_tick.type = type_to; - changed_tick.extruder = extruder; - changed_tick.color = color; - - ticks.erase(it); - ticks.emplace(changed_tick); - - return true; -} - -void TickCodeInfo::erase_all_ticks_with_code(Type type) -{ - for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) { - if (it->type == type) - it = ticks.erase(it); - else - ++it; - } -} - -bool TickCodeInfo::has_tick_with_code(Type type) -{ - for (const TickCode& tick : ticks) - if (tick.type == type) - return true; - - return false; -} - -bool TickCodeInfo::has_tick(int tick) -{ - return ticks.find(TickCode{ tick }) != ticks.end(); -} - -ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z) -{ - if ((tick.type == ColorChange && ( - (mode == SingleExtruder && out_mode == MultiExtruder ) || - (mode == MultiExtruder && out_mode == SingleExtruder) )) || - (tick.type == ToolChange && - (mode == MultiAsSingle && out_mode != MultiAsSingle)) ) - return ctModeConflict; - - // check ColorChange tick - if (tick.type == ColorChange) { - // We should mark a tick as a "MeaninglessColorChange", - // if it has a ColorChange for unused extruder from current print to end of the print - std::set used_extruders_for_tick = get_used_extruders_for_tick(tick.tick, only_extruder, print_z, out_mode); - - if (used_extruders_for_tick.find(tick.extruder) == used_extruders_for_tick.end()) - return ctMeaninglessColorChange; - - // We should mark a tick as a "Redundant", - // if it has a ColorChange for extruder that has not been used before - if (mode == MultiAsSingle && tick.extruder != std::max(only_extruder, 1) ) - { - auto it = ticks.lower_bound( tick ); - if (it == ticks.begin() && it->type == ToolChange && tick.extruder == it->extruder) - return ctNone; - - while (it != ticks.begin()) { - --it; - if (it->type == ToolChange && tick.extruder == it->extruder) - return ctNone; - } - - return ctRedundant; - } - } - - // check ToolChange tick - if (mode == MultiAsSingle && tick.type == ToolChange) { - // We should mark a tick as a "MeaninglessToolChange", - // if it has a ToolChange to the same extruder - auto it = ticks.find(tick); - if (it == ticks.begin()) - return tick.extruder == std::max(only_extruder, 1) ? ctMeaninglessToolChange : ctNone; - - while (it != ticks.begin()) { - --it; - if (it->type == ToolChange) - return tick.extruder == it->extruder ? ctMeaninglessToolChange : ctNone; - } - } - - return ctNone; -} - -} // DoubleSlider - -} // Slic3r - - diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp deleted file mode 100644 index dc61715..0000000 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ /dev/null @@ -1,484 +0,0 @@ -#ifndef slic3r_GUI_DoubleSlider_hpp_ -#define slic3r_GUI_DoubleSlider_hpp_ - -#include "libslic3r/CustomGCode.hpp" -#include "wxExtensions.hpp" - -#include -#include -#include -#include - -#include -#include - -class wxMenu; - -namespace Slic3r { - -using namespace CustomGCode; -class PrintObject; -class Layer; - -namespace DoubleSlider { - -/* For exporting GCode in GCodeWriter is used XYZF_NUM(val) = PRECISION(val, 3) for XYZ values. - * So, let use same value as a permissible error for layer height. - */ -constexpr double epsilon() { return 0.0011; } - -// return true when areas are mostly equivalent -bool equivalent_areas(const double& bottom_area, const double& top_area); - -// return true if color change was detected -bool check_color_change(const PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs, - // what to do with detected color change - // and return true when detection have to be desturbed - std::function break_condition); - -// custom message the slider sends to its parent to notify a tick-change: -wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); - -enum SelectedSlider { - ssUndef, - ssLower, - ssHigher -}; - -enum FocusedItem { - fiNone, - fiRevertIcon, - fiOneLayerIcon, - fiCogIcon, - fiColorBand, - fiActionIcon, - fiLowerThumb, - fiHigherThumb, - fiSmartWipeTower, - fiTick -}; - -enum ConflictType -{ - ctNone, - ctModeConflict, - ctMeaninglessColorChange, - ctMeaninglessToolChange, - ctRedundant -}; - -enum MouseAction -{ - maNone, - maAddMenu, // show "Add" context menu for NOTexist active tick - maEditMenu, // show "Edit" context menu for exist active tick - maCogIconMenu, // show context for "cog" icon - maForceColorEdit, // force color editing from colored band - maAddTick, // force tick adding - maDeleteTick, // force tick deleting - maCogIconClick, // LeftMouseClick on "cog" icon - maOneLayerIconClick, // LeftMouseClick on "one_layer" icon - maRevertIconClick, // LeftMouseClick on "revert" icon -}; - -enum DrawMode -{ - dmRegular, - dmSlaPrint, - dmSequentialFffPrint, - dmSequentialGCodeView, -}; - -enum LabelType -{ - ltHeightWithLayer, - ltHeight, - ltEstimatedTime, -}; - -struct TickCode -{ - bool operator<(const TickCode& other) const { return other.tick > this->tick; } - bool operator>(const TickCode& other) const { return other.tick < this->tick; } - - int tick = 0; - Type type = ColorChange; - int extruder = 0; - std::string color; - std::string extra; -}; - -class TickCodeInfo -{ - std::string custom_gcode; - std::string pause_print_msg; - bool m_suppress_plus = false; - bool m_suppress_minus = false; - bool m_use_default_colors= false; -// int m_default_color_idx = 0; - - std::vector* m_colors {nullptr}; - - std::string get_color_for_tick(TickCode tick, Type type, const int extruder); - -public: - std::set ticks {}; - Mode mode = Undef; - - bool empty() const { return ticks.empty(); } - void set_pause_print_msg(const std::string& message) { pause_print_msg = message; } - - bool add_tick(const int tick, Type type, int extruder, double print_z); - bool edit_tick(std::set::iterator it, double print_z); - void switch_code(Type type_from, Type type_to); - bool switch_code_for_tick(std::set::iterator it, Type type_to, const int extruder); - void erase_all_ticks_with_code(Type type); - - bool has_tick_with_code(Type type); - bool has_tick(int tick); - ConflictType is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z); - - // Get used extruders for tick. - // Means all extruders(tools) which will be used during printing from current tick to the end - std::set get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode = Undef) const; - - void suppress_plus (bool suppress) { m_suppress_plus = suppress; } - void suppress_minus(bool suppress) { m_suppress_minus = suppress; } - bool suppressed_plus () { return m_suppress_plus; } - bool suppressed_minus() { return m_suppress_minus; } - void set_default_colors(bool default_colors_on) { m_use_default_colors = default_colors_on; } - - void set_extruder_colors(std::vector* extruder_colors) { m_colors = extruder_colors; } -}; - - -struct ExtrudersSequence -{ - bool is_mm_intervals = true; - double interval_by_mm = 3.0; - int interval_by_layers = 10; - bool random_sequence { false }; - bool color_repetition { false }; - std::vector extruders = { 0 }; - - bool operator==(const ExtrudersSequence& other) const - { - return (other.is_mm_intervals == this->is_mm_intervals ) && - (other.interval_by_mm == this->interval_by_mm ) && - (other.interval_by_layers == this->interval_by_layers ) && - (other.random_sequence == this->random_sequence ) && - (other.color_repetition == this->color_repetition ) && - (other.extruders == this->extruders ) ; - } - bool operator!=(const ExtrudersSequence& other) const - { - return (other.is_mm_intervals != this->is_mm_intervals ) || - (other.interval_by_mm != this->interval_by_mm ) || - (other.interval_by_layers != this->interval_by_layers ) || - (other.random_sequence != this->random_sequence ) || - (other.color_repetition != this->color_repetition ) || - (other.extruders != this->extruders ) ; - } - - void add_extruder(size_t pos, size_t extruder_id = size_t(0)) - { - extruders.insert(extruders.begin() + pos+1, extruder_id); - } - - void delete_extruder(size_t pos) - { - if (extruders.size() == 1) - return;// last item can't be deleted - extruders.erase(extruders.begin() + pos); - } - - void init(size_t extruders_count) - { - extruders.clear(); - for (size_t extruder = 0; extruder < extruders_count; extruder++) - extruders.push_back(extruder); - } -}; - -class Control : public wxControl -{ -public: - Control( - wxWindow *parent, - wxWindowID id, - int lowerValue, - int higherValue, - int minValue, - int maxValue, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = wxSL_VERTICAL, - const wxValidator& val = wxDefaultValidator, - const wxString& name = wxEmptyString); - ~Control() {} - - void msw_rescale(); - void sys_color_changed(); - - int GetMinValue() const { return m_min_value; } - int GetMaxValue() const { return m_max_value; } - double GetMinValueD() { return m_values.empty() ? 0. : m_values[m_min_value]; } - double GetMaxValueD() { return m_values.empty() ? 0. : m_values[m_max_value]; } - int GetLowerValue() const { return m_lower_value; } - int GetHigherValue() const { return m_higher_value; } - int GetActiveValue() const; - double GetLowerValueD() { return get_double_value(ssLower); } - double GetHigherValueD() { return get_double_value(ssHigher); } - wxSize DoGetBestSize() const override; - wxSize get_min_size() const ; - - // Set low and high slider position. If the span is non-empty, disable the "one layer" mode. - void SetLowerValue (const int lower_val); - void SetHigherValue(const int higher_val); - void SetSelectionSpan(const int lower_val, const int higher_val); - - void SetMaxValue(const int max_value); - void SetKoefForLabels(const double koef) { m_label_koef = koef; } - void SetSliderValues(const std::vector& values); - void ChangeOneLayerLock(); - void SetSliderAlternateValues(const std::vector& values) { m_alternate_values = values; } - - Info GetTicksValues() const; - void SetTicksValues(const Info &custom_gcode_per_print_z); - void SetLayersTimes(const std::vector& layers_times, float total_time); - void SetLayersTimes(const std::vector& layers_times); - - void SetDrawMode(bool is_sla_print, bool is_sequential_print); - void SetDrawMode(DrawMode mode) { m_draw_mode = mode; } - - void SetManipulationMode(Mode mode) { m_mode = mode; } - Mode GetManipulationMode() const { return m_mode; } - void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); - void SetExtruderColors(const std::vector& extruder_colors); - - bool IsNewPrint(); - - void set_render_as_disabled(bool value) { m_render_as_disabled = value; } - bool is_rendering_as_disabled() const { return m_render_as_disabled; } - - bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } - bool is_one_layer() const { return m_is_one_layer; } - bool is_lower_at_min() const { return m_lower_value == m_min_value; } - bool is_higher_at_max() const { return m_higher_value == m_max_value; } - bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); } - - void OnPaint(wxPaintEvent& ) { render(); } - void OnLeftDown(wxMouseEvent& event); - void OnMotion(wxMouseEvent& event); - void OnLeftUp(wxMouseEvent& event); - void OnEnterWin(wxMouseEvent& event) { enter_window(event, true); } - void OnLeaveWin(wxMouseEvent& event) { enter_window(event, false); } - void UseDefaultColors(bool def_colors_on) { m_ticks.set_default_colors(def_colors_on); } - void OnWheel(wxMouseEvent& event); - void OnKeyDown(wxKeyEvent &event); - void OnKeyUp(wxKeyEvent &event); - void OnChar(wxKeyEvent &event); - void OnRightDown(wxMouseEvent& event); - void OnRightUp(wxMouseEvent& event); - - void add_code_as_tick(Type type, int selected_extruder = -1); - // add default action for tick, when press "+" - void add_current_tick(bool call_from_keyboard = false); - // delete current tick, when press "-" - void delete_current_tick(); - void edit_tick(int tick = -1); - void switch_one_layer_mode(); - void discard_all_thicks(); - void move_current_thumb_to_pos(wxPoint pos); - void edit_extruder_sequence(); - void jump_to_value(); - void enable_action_icon(bool enable) { m_enable_action_icon = enable; } - void show_add_context_menu(); - void show_edit_context_menu(); - void show_cog_icon_context_menu(); - void auto_color_change(); - - ExtrudersSequence m_extruders_sequence; - -protected: - - void render(); - void draw_focus_rect(wxDC& dc); - void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end); - void draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos); - void draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection); - void draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos); - void draw_ticks_pair(wxDC& dc, wxCoord pos, wxCoord mid, int tick_len); - void draw_ticks(wxDC& dc); - void draw_colored_band(wxDC& dc); - void draw_ruler(wxDC& dc); - void draw_one_layer_icon(wxDC& dc); - void draw_revert_icon(wxDC& dc); - void draw_cog_icon(wxDC &dc); - void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection); - void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection); - void draw_tick_on_mouse_position(wxDC &dc); - void draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, LabelType label_type = ltHeight, bool right_side = true) const; - void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; - - void update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection); - bool is_lower_thumb_editable(); - bool detect_selected_slider(const wxPoint& pt); - void correct_lower_value(); - void correct_higher_value(); - void move_current_thumb(const bool condition); - void enter_window(wxMouseEvent& event, const bool enter); - bool is_wipe_tower_layer(int tick) const; - -private: - - bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); - int get_tick_near_point(const wxPoint& pt); - - double get_scroll_step(); - wxString get_label(int tick, LabelType label_type = ltHeightWithLayer) const; - void get_lower_and_higher_position(int& lower_pos, int& higher_pos); - int get_value_from_position(const wxCoord x, const wxCoord y); - int get_value_from_position(const wxPoint pos) { return get_value_from_position(pos.x, pos.y); } - wxCoord get_position_from_value(const int value); - wxSize get_size() const; - void get_size(int* w, int* h) const; - double get_double_value(const SelectedSlider& selection); - int get_tick_from_value(double value, bool force_lower_bound = false); - wxString get_tooltip(int tick = -1); - int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange); - - std::string get_color_for_tool_change_tick(std::set::const_iterator it) const; - std::string get_color_for_color_change_tick(std::set::const_iterator it) const; - wxRect get_colored_band_rect(); - - // Get active extruders for tick. - // Means one current extruder for not existing tick OR - // 2 extruders - for existing tick (extruder before ToolChangeCode and extruder of current existing tick) - // Use those values to disable selection of active extruders - std::array get_active_extruders_for_tick(int tick) const; - - void post_ticks_changed_event(Type type = Custom); - bool check_ticks_changed_event(Type type); - - void append_change_extruder_menu_item (wxMenu*, bool switch_current_code = false); - void append_add_color_change_menu_item(wxMenu*, bool switch_current_code = false); - - bool is_osx { false }; - wxFont m_font; - int m_min_value; - int m_max_value; - int m_lower_value; - int m_higher_value; - - bool m_render_as_disabled{ false }; - - ScalableBitmap m_bmp_thumb_higher; - ScalableBitmap m_bmp_thumb_lower; - ScalableBitmap m_bmp_add_tick_on; - ScalableBitmap m_bmp_add_tick_off; - ScalableBitmap m_bmp_del_tick_on; - ScalableBitmap m_bmp_del_tick_off; - ScalableBitmap m_bmp_one_layer_lock_on; - ScalableBitmap m_bmp_one_layer_lock_off; - ScalableBitmap m_bmp_one_layer_unlock_on; - ScalableBitmap m_bmp_one_layer_unlock_off; - ScalableBitmap m_bmp_revert; - ScalableBitmap m_bmp_cog; - SelectedSlider m_selection; - bool m_is_left_down = false; - bool m_is_right_down = false; - bool m_is_one_layer = false; - bool m_is_focused = false; - bool m_force_mode_apply = true; - bool m_enable_action_icon = true; - bool m_is_wipe_tower = false; //This flag indicates that there is multiple extruder print with wipe tower - - DrawMode m_draw_mode = dmRegular; - - Mode m_mode = SingleExtruder; - int m_only_extruder = -1; - - MouseAction m_mouse = maNone; - FocusedItem m_focus = fiNone; - wxPoint m_moving_pos = wxDefaultPosition; - - wxRect m_rect_lower_thumb; - wxRect m_rect_higher_thumb; - wxRect m_rect_tick_action; - wxRect m_rect_one_layer_icon; - wxRect m_rect_revert_icon; - wxRect m_rect_cog_icon; - wxSize m_thumb_size; - int m_tick_icon_dim; - int m_lock_icon_dim; - int m_revert_icon_dim; - int m_cog_icon_dim; - long m_style; - long m_extra_style; - float m_label_koef{ 1.0 }; - - std::vector m_values; - TickCodeInfo m_ticks; - std::vector m_layers_times; - std::vector m_layers_values; - std::vector m_extruder_colors; - std::string m_print_obj_idxs; - - std::vector m_alternate_values; - -// control's view variables - wxCoord SLIDER_MARGIN; // margin around slider - - wxPen DARK_ORANGE_PEN; - wxPen ORANGE_PEN; - wxPen LIGHT_ORANGE_PEN; - - //B18 - wxPen LIGHT_BLUE_PEN; - - - wxPen DARK_GREY_PEN; - wxPen GREY_PEN; - wxPen LIGHT_GREY_PEN; - - wxPen FOCUS_RECT_PEN; - wxBrush FOCUS_RECT_BRUSH; - - std::vector m_line_pens; - std::vector m_segm_pens; - - class Ruler { - wxWindow* m_parent{nullptr}; // m_parent is nullptr for Unused ruler - // in this case we will not init/update/render it - // values to check if ruler has to be updated - double m_min_val; - double m_max_val; - double m_scroll_step; - size_t m_max_values_cnt; - int m_DPI; - - public: - - double long_step; - double short_step; - std::vector max_values;// max value for each object/instance in sequence print - // > 1 for sequential print - - void set_parent(wxWindow* parent); - void update_dpi(); - void init(const std::vector& values, double scroll_step); - void update(const std::vector& values, double scroll_step); - bool is_ok() { return long_step > 0 && short_step > 0; } - size_t count() { return max_values.size(); } - bool can_draw() { return m_parent != nullptr; } - } m_ruler; -}; - -} // DoubleSlider; - -} // Slic3r - - - -#endif // slic3r_GUI_DoubleSlider_hpp_ diff --git a/src/slic3r/GUI/DoubleSliderForGcode.cpp b/src/slic3r/GUI/DoubleSliderForGcode.cpp new file mode 100644 index 0000000..54bffae --- /dev/null +++ b/src/slic3r/GUI/DoubleSliderForGcode.cpp @@ -0,0 +1,27 @@ + +#include "DoubleSliderForGcode.hpp" + +#include + +namespace DoubleSlider { + +static const float LEFT_MARGIN = 13.0f + 100.0f; // avoid thumbnail toolbar +static const float HORIZONTAL_SLIDER_HEIGHT = 40.0f; + +void DSForGcode::Render(const int canvas_width, const int canvas_height, float extra_scale/* = 0.1f*/, float offset/* = 0.f*/) +{ + if (!m_ctrl.IsShown()) + return; + m_scale = extra_scale * 0.1f * m_em; + + ImVec2 pos = ImVec2{std::max(LEFT_MARGIN, 0.2f * canvas_width), canvas_height - HORIZONTAL_SLIDER_HEIGHT * m_scale}; + ImVec2 size = ImVec2(canvas_width - 2 * pos.x, HORIZONTAL_SLIDER_HEIGHT * m_scale); + + m_ctrl.Init(pos, size, m_scale); + if (m_ctrl.render()) + process_thumb_move(); +} + +} // DoubleSlider + + diff --git a/src/slic3r/GUI/DoubleSliderForGcode.hpp b/src/slic3r/GUI/DoubleSliderForGcode.hpp new file mode 100644 index 0000000..97c0301 --- /dev/null +++ b/src/slic3r/GUI/DoubleSliderForGcode.hpp @@ -0,0 +1,35 @@ + +#ifndef slic3r_GUI_DoubleSliderForGcode_hpp_ +#define slic3r_GUI_DoubleSliderForGcode_hpp_ + +#include "ImGuiDoubleSlider.hpp" + +namespace DoubleSlider { + +class DSForGcode : public Manager +{ +public: + DSForGcode() : Manager() {} + DSForGcode( int lowerPos, + int higherPos, + int minPos, + int maxPos) + { + Init(lowerPos, higherPos, minPos, maxPos, "moves_slider", true); + } + ~DSForGcode() {} + + void Render(const int canvas_width, const int canvas_height, float extra_scale = 1.f, float offset = 0.f) override; + + void set_render_as_disabled(bool value) { m_render_as_disabled = value; } + bool is_rendering_as_disabled() const { return m_render_as_disabled; } + +private: + + bool m_render_as_disabled{ false }; +}; + +} // DoubleSlider; + + +#endif // slic3r_GUI_DoubleSliderForGcode_hpp_ diff --git a/src/slic3r/GUI/DoubleSliderForLayers.cpp b/src/slic3r/GUI/DoubleSliderForLayers.cpp new file mode 100644 index 0000000..128acca --- /dev/null +++ b/src/slic3r/GUI/DoubleSliderForLayers.cpp @@ -0,0 +1,1405 @@ + +#include "DoubleSliderForLayers.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Utils.hpp" // -> get_time_dhms() +#include "libslic3r/format.hpp" // -> format() +#include "I18N.hpp" +#include "ImGuiWrapper.hpp" +#include "libslic3r/libslic3r.h" +#include "slic3r/GUI/ImGuiDoubleSlider.hpp" +#include "slic3r/GUI/ImGuiPureWrap.hpp" +#include "slic3r/GUI/RulerForDoubleSlider.hpp" +#include "slic3r/GUI/TickCodesManager.hpp" + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include + +using namespace Slic3r; +using namespace CustomGCode; +using Slic3r::format; + +namespace DoubleSlider { + +//static const float VERTICAL_SLIDER_WIDTH = 105.0f; + +DSForLayers::DSForLayers( int lowerValue, + int higherValue, + int minValue, + int maxValue, + bool allow_editing) : + m_allow_editing(allow_editing) +{ +#ifdef __WXOSX__ + is_osx = true; +#endif //__WXOSX__ + Init(lowerValue, higherValue, minValue, maxValue, "layers_slider", false); + m_ctrl.ShowLabelOnMouseMove(true); + + m_ctrl.set_get_label_on_move_cb([this](int pos) { + m_pos_on_move = pos; + return m_show_estimated_times ? get_label(pos, ltEstimatedTime) : ""; + }); + m_ctrl.set_extra_draw_cb([this](const ImRect& draw_rc) {return draw_ticks(draw_rc); }); + + m_ticks.set_values(&m_values); +} + +Info DSForLayers::GetTicksValues() const +{ + Info custom_gcode_per_print_z; + std::vector& values = custom_gcode_per_print_z.gcodes; + + const int val_size = m_values.size(); + if (!m_values.empty()) + for (const TickCode& tick : m_ticks.ticks) { + if (tick.tick > val_size) + break; + values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra }); + } + + custom_gcode_per_print_z.mode = m_mode; + + return custom_gcode_per_print_z; +} + +void DSForLayers::SetTicksValues(const Info& custom_gcode_per_print_z) +{ + if (m_values.empty()) { + m_ticks.mode = m_mode; + return; + } + + if (m_cb_get_print) + m_ticks.set_print(m_cb_get_print()); + + const bool was_empty = m_ticks.empty(); + + m_ticks.set_ticks(custom_gcode_per_print_z); + + if (!was_empty && m_ticks.empty()) + // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one + process_ticks_changed(); + + update_draw_scroll_line_cb(); +} + +void DSForLayers::SetLayersTimes(const std::vector& layers_times, float total_time) +{ + m_layers_times.clear(); + if (layers_times.empty()) + return; + m_layers_times.resize(layers_times.size(), 0.0); + m_layers_times[0] = layers_times[0]; + for (size_t i = 1; i < layers_times.size(); i++) + m_layers_times[i] = m_layers_times[i - 1] + layers_times[i]; + + // Erase duplicates values from m_values and save it to the m_layers_values + // They will be used for show the correct estimated time for MM print, when "No sparce layer" is enabled + // See https://github.com/qidi3d/QIDISlicer/issues/6232 + if (m_ticks.is_wipe_tower && m_values.size() != m_layers_times.size()) { + m_layers_values = m_values; + sort(m_layers_values.begin(), m_layers_values.end()); + m_layers_values.erase(unique(m_layers_values.begin(), m_layers_values.end()), m_layers_values.end()); + + // When whipe tower is used to the end of print, there is one layer which is not marked in layers_times + // So, add this value from the total print time value + if (m_layers_values.size() != m_layers_times.size()) + for (size_t i = m_layers_times.size(); i < m_layers_values.size(); i++) + m_layers_times.push_back(total_time); + } +} + +void DSForLayers::SetLayersTimes(const std::vector& layers_times) +{ + m_ticks.is_wipe_tower = false; + m_layers_times = layers_times; + std::copy(layers_times.begin(), layers_times.end(), m_layers_times.begin()); +} + +void DSForLayers::SetDrawMode(bool is_sla_print, bool is_sequential_print) +{ + m_draw_mode = is_sla_print ? dmSlaPrint : + is_sequential_print ? dmSequentialFffPrint : + dmRegular; + + update_draw_scroll_line_cb(); +} + +void DSForLayers::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder) +{ + m_mode = !is_one_extruder_printed_model ? MultiExtruder : + only_extruder < 0 ? SingleExtruder : + MultiAsSingle; + if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode)) + m_ticks.mode = m_mode; + + m_ticks.only_extruder_id = only_extruder; + m_ticks.is_wipe_tower = m_mode != SingleExtruder; + + if (m_mode != SingleExtruder) + UseDefaultColors(false); +} + +void DSForLayers::SetExtruderColors( const std::vector& extruder_colors) +{ + m_ticks.colors = extruder_colors; +} + +bool DSForLayers::is_new_print(const std::string& idxs) +{ + if (idxs == "sla" || idxs == m_print_obj_idxs) + return false; + + m_print_obj_idxs = idxs; + return true; +} + +void DSForLayers::update_draw_scroll_line_cb() +{ + if (m_ticks.empty() || m_draw_mode == dmSequentialFffPrint || m_draw_mode == dmSlaPrint) + m_ctrl.set_draw_scroll_line_cb(nullptr); + else + m_ctrl.set_draw_scroll_line_cb([this](const ImRect& scroll_line, const ImRect& slideable_region) { draw_colored_band(scroll_line, slideable_region); }); +} + +using namespace ImGui; + +void DSForLayers::draw_ticks(const ImRect& slideable_region) +{ + if (m_show_ruler) + draw_ruler(slideable_region); + + if (m_ticks.empty() || m_draw_mode == dmSlaPrint) + return; + + // distance form center begin end + const ImVec2 tick_border = ImVec2(23.0f, 2.0f) * m_scale; + + const float inner_x = 11.f * m_scale; + const float outer_x = 19.f * m_scale; + const float x_center = slideable_region.GetCenter().x; + + const float tick_width = float(int(1.0f * m_scale + 0.5f)); + const float icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; + const float icon_offset = 0.5f * icon_side;; + + const ImU32 tick_clr = ImGui::ColorConvertFloat4ToU32(m_show_ruler ? ImGuiPureWrap::COL_BLUE_LIGHT : ImGuiPureWrap::COL_BLUE_DARK); + const ImU32 tick_hovered_clr = ImGui::ColorConvertFloat4ToU32(m_show_ruler ? ImGuiPureWrap::COL_BLUE_DARK : ImGuiPureWrap::COL_WINDOW_BACKGROUND); + + auto get_tick_pos = [this, slideable_region](int tick) { + return m_ctrl.GetPositionInRect(tick, slideable_region); + }; + + std::set::const_iterator tick_it = m_ticks.ticks.begin(); + bool is_hovered_tick = false; + while (tick_it != m_ticks.ticks.end()) + { + float tick_pos = get_tick_pos(tick_it->tick); + + //draw tick hover box when hovered + ImRect tick_hover_box = ImRect(x_center - tick_border.x, tick_pos - tick_border.y, + x_center + tick_border.x, tick_pos + tick_border.y - tick_width); + + if (ImGui::IsMouseHoveringRect(tick_hover_box.Min, tick_hover_box.Max)) { + ImGui::RenderFrame(tick_hover_box.Min, tick_hover_box.Max, tick_hovered_clr, false); + if (tick_it->type == ColorChange || tick_it->type == ToolChange) { + m_focus = fiTick; + ImGuiPureWrap::tooltip(get_tooltip(tick_it->tick), ImGui::GetFontSize() * 20.f); + } + is_hovered_tick = true; + m_ctrl.SetHoveredRegion(tick_hover_box); + if (m_ctrl.IsLClickOnHoveredPos()) + m_ctrl.IsActiveHigherThumb() ? SetHigherPos(tick_it->tick) : SetLowerPos(tick_it->tick); + break; + } + ++tick_it; + } + if (!is_hovered_tick) + m_ctrl.InvalidateHoveredRegion(); + + auto active_tick_it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos() }); + + tick_it = m_ticks.ticks.begin(); + while (tick_it != m_ticks.ticks.end()) + { + float tick_pos = get_tick_pos(tick_it->tick); + + //draw ticks + ImRect tick_left = ImRect(x_center - outer_x, tick_pos - tick_width, x_center - inner_x, tick_pos); + ImRect tick_right = ImRect(x_center + inner_x, tick_pos - tick_width, x_center + outer_x, tick_pos); + ImGui::RenderFrame(tick_left.Min, tick_left.Max, tick_clr, false); + ImGui::RenderFrame(tick_right.Min, tick_right.Max, tick_clr, false); + + ImVec2 icon_pos = ImVec2(m_ctrl.GetCtrlPos().x + GetWidth(), tick_pos - icon_offset); + std::string btn_label = "tick " + std::to_string(tick_it->tick); + + //draw tick icon-buttons + bool activate_this_tick = false; + if (tick_it == active_tick_it && m_allow_editing) { + // delete tick + if (render_button(ImGui::RemoveTick, ImGui::RemoveTickHovered, btn_label, icon_pos, fiActionIcon, tick_it->tick)) { + m_ticks.ticks.erase(tick_it); + process_ticks_changed(); + break; + } + } + else if (m_draw_mode != dmRegular)// if we have non-regular draw mode, all ticks should be marked with error icon + activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiTick, tick_it->tick); + else if (tick_it->type == ColorChange || tick_it->type == ToolChange) { + if (m_ticks.is_conflict_tick(*tick_it, m_mode, m_values[tick_it->tick])) + activate_this_tick = render_button(ImGui::ErrorTick, ImGui::ErrorTickHovered, btn_label, icon_pos, fiTick, tick_it->tick); + } + else if (tick_it->type == CustomGCode::PausePrint) + activate_this_tick = render_button(ImGui::PausePrint, ImGui::PausePrintHovered, btn_label, icon_pos, fiTick, tick_it->tick); + else + activate_this_tick = render_button(ImGui::EditGCode, ImGui::EditGCodeHovered, btn_label, icon_pos, fiTick, tick_it->tick); + + if (activate_this_tick) { + m_ctrl.IsActiveHigherThumb() ? SetHigherPos(tick_it->tick) : SetLowerPos(tick_it->tick); + break; + } + + ++tick_it; + } +} + +void DSForLayers::draw_ruler(const ImRect& slideable_region) +{ + if (m_values.empty()) + return; + + const double step = double(slideable_region.GetHeight()) / (m_ctrl.GetMaxPos() - m_ctrl.GetMinPos()); + + if (!m_ruler.valid()) + m_ruler.init(m_values, step); + + const float inner_x = 11.f * m_scale; + const float long_outer_x = 17.f * m_scale; + const float short_outer_x = 14.f * m_scale; + const float tick_width = float(int(1.0f * m_scale +0.5f)); + const float label_height = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; + + const ImU32 tick_clr = IM_COL32(255, 255, 255, 255); + + const float x_center = slideable_region.GetCenter().x; + + double max_val = 0.; + for (const auto& val : m_ruler.max_values) + if (max_val < val) + max_val = val; + + if (m_show_ruler_bg) { + // draw ruler BG + ImRect bg_rect = slideable_region; + bg_rect.Expand(ImVec2(0.f, long_outer_x)); + bg_rect.Min.x -= tick_width; + bg_rect.Max.x = m_ctrl.GetCtrlPos().x + GetWidth(); + bg_rect.Min.y = m_ctrl.GetCtrlPos().y + label_height; + bg_rect.Max.y = m_ctrl.GetCtrlPos().y + GetHeight() - label_height; + const ImU32 bg_color = ImGui::ColorConvertFloat4ToU32(ImVec4(0.13f, 0.13f, 0.13f, 0.5f)); + + ImGui::RenderFrame(bg_rect.Min, bg_rect.Max, bg_color, false, 2.f * m_ctrl.rounding()); + } + + auto get_tick_pos = [this, slideable_region](int tick) -> float { + return m_ctrl.GetPositionInRect(tick, slideable_region); + }; + + auto draw_text = [max_val, x_center, label_height, long_outer_x, this](const int tick, const float tick_pos) + { + ImVec2 start = ImVec2(x_center + long_outer_x + 1, tick_pos - (0.5f * label_height)); + std::string label = get_label(tick, ltHeight, max_val > 100.0 ? "%1$.1f" : "%1$.2f"); + ImGui::RenderText(start, label.c_str()); + }; + + auto draw_tick = [tick_clr, x_center, tick_width, inner_x](const float tick_pos, const float outer_x) + { + ImRect tick_right = ImRect(x_center + inner_x, tick_pos - tick_width, x_center + outer_x, tick_pos); + ImGui::RenderFrame(tick_right.Min, tick_right.Max, tick_clr, false); + }; + + auto draw_short_ticks = [this, short_outer_x, draw_tick, get_tick_pos](double& current_tick, int max_tick) + { + if (m_ruler.short_step <= 0.0) + return; + while (current_tick < max_tick) { + float pos = get_tick_pos(lround(current_tick)); + draw_tick(pos, short_outer_x); + current_tick += m_ruler.short_step; + if (current_tick > m_ctrl.GetMaxPos()) + break; + } + }; + + double short_tick = NaNd; + int tick = 0; + double value = 0.0; + size_t sequence = 0; + float prev_y_pos = -1.f; + int values_size = (int)m_values.size(); + + const float label_shift = 0.5f * label_height; + + if (m_ruler.long_step < 0) { + // sequential print when long_step wasn't detected because of a lot of printed objects + if (m_ruler.max_values.size() > 1) { + float last_pos = get_tick_pos(m_ctrl.GetMaxPos()); + while (tick <= m_ctrl.GetMaxPos() && sequence < m_ruler.count()) { + // draw just ticks with max value + value = m_ruler.max_values[sequence]; + short_tick = tick; + + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_ctrl.GetMaxPos()) + break; + + float pos = get_tick_pos(tick); + draw_tick(pos, long_outer_x); + if (prev_y_pos < 0 || pos == last_pos || (prev_y_pos - pos >= label_shift && pos - last_pos >= label_shift)) { + draw_text(tick, pos); + prev_y_pos = pos; + } + draw_short_ticks(short_tick, tick); + + sequence++; + tick++; + } + } + // very short object or some non-trivial ruler with non-regular step (see https://github.com/qidi3d/QIDISlicer/issues/7263) + else { + if (step < 1) // step less then 1 px indicates very tall object with non-regular laayer step (probably in vase mode) + return; + for (size_t tick = 1; tick < m_values.size(); tick++) { + float pos = get_tick_pos(tick); + draw_tick(pos, long_outer_x); + draw_text(tick, pos); + } + } + } + else { + std::vector last_positions; + if (m_ruler.count() == 1) + last_positions.emplace_back(m_ctrl.GetMaxPos()); + else { + // fill last positions for each object in sequential print + last_positions.reserve(m_ruler.count()); + + int tick = 0; + double value = 0.0; + size_t sequence = 0; + + while (tick <= m_ctrl.GetMaxPos()) { + value += m_ruler.long_step; + + if (sequence < m_ruler.count() && value > m_ruler.max_values[sequence]) + value = m_ruler.max_values[sequence]; + + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_ctrl.GetMaxPos()) + break; + + if (sequence < m_ruler.count() && value == m_ruler.max_values[sequence]) { + last_positions.emplace_back(tick); + value = 0.0; + sequence++; + tick++; + } + } + } + + float last_pos = get_tick_pos(last_positions[sequence]); + + while (tick <= m_ctrl.GetMaxPos()) { + value += m_ruler.long_step; + + if (sequence < m_ruler.count() && value > m_ruler.max_values[sequence]) + value = m_ruler.max_values[sequence]; + + short_tick = tick; + + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_ctrl.GetMaxPos()) + break; + + float pos = get_tick_pos(tick); + draw_tick(pos, long_outer_x); + if (prev_y_pos < 0 || pos == last_pos || (prev_y_pos - pos >= label_shift && pos - last_pos >= label_shift) ) { + draw_text(tick, pos); + prev_y_pos = pos; + } + + draw_short_ticks(short_tick, tick); + + if (sequence < m_ruler.count() && value == m_ruler.max_values[sequence]) { + value = 0.0; + sequence++; + tick++; + + if (sequence < m_ruler.count()) + last_pos = get_tick_pos(last_positions[sequence]); + } + } + // short ticks from the last tick to the end + draw_short_ticks(short_tick, m_ctrl.GetMaxPos()); + } + + // draw mose move line + if (m_pos_on_move > 0) { + float line_pos = get_tick_pos(m_pos_on_move); + + ImRect move_line = ImRect(x_center + 0.75f * inner_x, line_pos - tick_width, x_center + 1.5f * long_outer_x, line_pos); + ImGui::RenderFrame(move_line.Min, move_line.Max, ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_BLUE_LIGHT), false); + m_pos_on_move = -1; + } +} + +static std::array decode_color_to_float_array(const std::string color) +{ + auto hex_digit_to_int = [](const char c) { + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + }; + + // set alpha to 1.0f by default + std::array ret = { 0, 0, 0, 1.0f }; + const char* c = color.data() + 1; + if (color.size() == 7 && color.front() == '#') { + for (size_t j = 0; j < 3; ++j) { + int digit1 = hex_digit_to_int(*c++); + int digit2 = hex_digit_to_int(*c++); + if (digit1 == -1 || digit2 == -1) break; + ret[j] = float(digit1 * 16 + digit2) / 255.0f; + } + } + return ret; +} + +std::string encode_color_from_float_array(const std::array& color) +{ + char buffer[64]; + ::sprintf(buffer, "#%02X%02X%02X", int(color[0] * 255.0f), int(color[1] * 255.0f), int(color[2] * 255.0f)); + return std::string(buffer); +} + +void DSForLayers::draw_colored_band(const ImRect& groove, const ImRect& slideable_region) +{ + if (m_ticks.empty() || m_draw_mode == dmSequentialFffPrint) + return; + + ImVec2 blank_padding = ImVec2(0.5f * m_ctrl.GetGrooveRect().GetWidth(), 2.0f * m_scale); + float blank_width = 1.0f * m_scale; + + ImRect blank_rect = ImRect(groove.GetCenter().x - blank_width, groove.Min.y, groove.GetCenter().x + blank_width, groove.Max.y); + + ImRect main_band = ImRect(blank_rect); + main_band.Expand(blank_padding); + + auto draw_band = [this](const ImU32& clr, const ImRect& band_rc) { + ImGui::RenderFrame(band_rc.Min, band_rc.Max, clr, false, band_rc.GetWidth() * 0.5); + //cover round corner + ImGui::RenderFrame(ImVec2(band_rc.Min.x, band_rc.Max.y - band_rc.GetWidth() * 0.5), band_rc.Max, clr, false); + + // add tooltip + if (ImGui::IsMouseHoveringRect(band_rc.Min, band_rc.Max)) + m_focus = fiColorBand; + }; + + auto draw_main_band = [&main_band](const ImU32& clr) { + ImGui::RenderFrame(main_band.Min, main_band.Max, clr, false, main_band.GetWidth() * 0.5); + }; + + //draw main colored band + const int default_color_idx = m_mode == MultiAsSingle ? std::max(m_ticks.only_extruder_id - 1, 0) : 0; + std::arrayrgba = decode_color_to_float_array(m_ticks.colors[default_color_idx]); + ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + draw_main_band(band_clr); + + static float tick_pos; + std::set::const_iterator tick_it = m_ticks.ticks.begin(); + + int rclicked_tick = -1; + while (tick_it != m_ticks.ticks.end()) + { + //get position from tick + tick_pos = m_ctrl.GetPositionInRect(tick_it->tick, slideable_region); + + ImRect band_rect = ImRect(ImVec2(main_band.Min.x, std::min(tick_pos, main_band.Min.y)), + ImVec2(main_band.Max.x, std::min(tick_pos, main_band.Max.y))); + + if (main_band.Contains(band_rect)) { + if ((m_mode == SingleExtruder && tick_it->type == ColorChange) || + (m_mode == MultiAsSingle && (tick_it->type == ToolChange || tick_it->type == ColorChange))) + { + const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : + tick_it->type == ToolChange ? + m_ticks.get_color_for_tool_change_tick(tick_it) : + m_ticks.get_color_for_color_change_tick(tick_it); + + if (!clr_str.empty()) { + std::arrayrgba = decode_color_to_float_array(clr_str); + ImU32 band_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + if (tick_it->tick == 0) + draw_main_band(band_clr); + else { + draw_band(band_clr, band_rect); + + ImGuiContext& g = *GImGui; + if (ImGui::IsMouseHoveringRect(band_rect.Min, band_rect.Max) && + g.IO.MouseClicked[1] && !m_ctrl.IsRClickOnThumb()) { + rclicked_tick = tick_it->tick; + } + } + } + } + } + tick_it++; + } + + if (m_focus == fiColorBand) { + if (rclicked_tick > 0) + edit_tick(rclicked_tick); + else if (auto tip = get_tooltip(); !tip.empty()) + ImGuiPureWrap::tooltip(tip, ImGui::GetFontSize() * 20.f); + } +} + +void DSForLayers::render_menu() +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 10.0f) * m_scale); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 4.0f * m_scale); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, ImGui::GetStyle().ItemSpacing.y }); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_ChildRounding, 4.0f * m_scale); + + ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + + if (m_ctrl.IsRClickOnThumb()) + ImGui::OpenPopup("slider_full_menu_popup"); + else if (m_show_just_color_change_menu) + ImGui::OpenPopup("slider_add_tick_menu_popup"); + else if (m_show_cog_menu) + ImGui::OpenPopup("cog_menu_popup"); + else if (m_show_edit_menu) + ImGui::OpenPopup("edit_menu_popup"); + + if (can_edit()) + render_add_tick_menu(); + render_cog_menu(); + render_edit_menu(); + + ImGui::PopStyleColor(1); + ImGui::PopStyleVar(4); + + ImGuiContext& context = *GImGui; + if (context.IO.MouseReleased[0]) { + m_show_just_color_change_menu = false; + m_show_cog_menu = false; + m_show_edit_menu = false; + } +} + +void DSForLayers::render_add_tick_menu() +{ + if (ImGui::BeginPopup("slider_full_menu_popup")) { + if (m_mode == SingleExtruder) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Color Change").c_str(), "")) { + add_code_as_tick(ColorChange); + } + } + else + render_multi_extruders_menu(); + + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Pause").c_str(), "")) { + add_code_as_tick(CustomGCode::PausePrint); + } + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Add Custom G-code").c_str(), "")) { + add_code_as_tick(Custom); + } + if (!gcode(Template).empty() && + ImGuiPureWrap::menu_item_with_icon(_u8L("Add Custom Template").c_str(), "")) { + add_code_as_tick(Template); + } + + ImGui::EndPopup(); + return; + } + + const std::string longest_menu_name = format(_u8L("Add color change (%1%) for:"), gcode(ColorChange)); + + const ImVec2 label_size = ImGui::CalcTextSize(longest_menu_name.c_str(), NULL, true); + const ImRect active_thumb_rect = m_ctrl.GetActiveThumbRect(); + const ImVec2 pos = active_thumb_rect.GetCenter(); + + ImGui::SetNextWindowPos(ImVec2(pos.x - label_size.x - active_thumb_rect.GetWidth(), pos.y)); + + if (ImGui::BeginPopup("slider_add_tick_menu_popup")) { + render_multi_extruders_menu(); + ImGui::EndPopup(); + } +} + +bool DSForLayers::render_multi_extruders_menu(bool switch_current_code/* = false*/) +{ + bool ret = false; + + std::vector colors; + if (m_cb_get_extruder_colors) + colors = m_cb_get_extruder_colors(); + + int extruders_cnt = colors.size(); + + if (extruders_cnt > 1) { + const int tick = m_ctrl.GetActivePos(); + + if (m_mode == MultiAsSingle) { + const std::string menu_name = switch_current_code ? _u8L("Switch code to Change extruder") : _u8L("Change extruder"); + if (ImGuiPureWrap::begin_menu(menu_name.c_str())) { + std::array active_extruders = m_ticks.get_active_extruders_for_tick(tick, m_mode); + for (int i = 1; i <= extruders_cnt; i++) { + const bool is_active_extruder = i == active_extruders[0] || i == active_extruders[1]; + std::string item_name = format(_u8L("Extruder %d"), i); + if (is_active_extruder) + item_name += " (" + _u8L("active") + ")"; + + std::array rgba = decode_color_to_float_array(colors[i - 1]); + ImU32 icon_clr = IM_COL32(rgba[0] * 255.0f, rgba[1] * 255.0f, rgba[2] * 255.0f, rgba[3] * 255.0f); + if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "", ImVec2(14, 14) * m_scale, icon_clr, false, !is_active_extruder)) { + add_code_as_tick(ToolChange, i); + ret = true; + } + } + ImGuiPureWrap::end_menu(); + } + } + + const std::string menu_name = switch_current_code ? + format(_u8L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) : + format(_u8L("Add color change (%1%) for:"), gcode(ColorChange)); + if (ImGuiPureWrap::begin_menu(menu_name.c_str())) { + std::set used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_values[tick]); + + for (int i = 1; i <= extruders_cnt; i++) { + const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder + used_extruders_for_tick.find(i) != used_extruders_for_tick.end(); + std::string item_name = format(_u8L("Extruder %d"), i); + if (is_used_extruder) + item_name += " (" + _u8L("used") + ")"; + + if (ImGuiPureWrap::menu_item_with_icon(item_name.c_str(), "")) { + add_code_as_tick(ColorChange, i); + ret = true; + } + } + ImGuiPureWrap::end_menu(); + } + } + return ret; +} + +void DSForLayers::render_color_picker() +{ + ImGuiContext& context = *GImGui; + const std::string title = ("Select color for Color Change"); + if (m_show_color_picker) { + + ImGuiPureWrap::set_next_window_pos(1200, 200, ImGuiCond_Always, 0.5f, 0.0f); + ImGuiPureWrap::begin(title, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse); + + ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoDragDrop; + + auto col = decode_color_to_float_array(m_selectable_color); + if (ImGui::ColorPicker4("color_picker", (float*)&col, misc_flags)) { + m_selectable_color = encode_color_from_float_array(col); + m_show_color_picker = false; + } + ImGuiPureWrap::end(); + } + + if (auto clr_pcr_win = ImGui::FindWindowByName(title.c_str()); clr_pcr_win && context.CurrentWindow != clr_pcr_win) + m_show_color_picker = false; +} + +void DSForLayers::render_cog_menu() +{ + const ImVec2 icon_sz = ImVec2(14, 14); + if (ImGui::BeginPopup("cog_menu_popup")) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Jump to height").c_str(), "Shift+G")) { + jump_to_value(); + } + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show estimated print time on hover").c_str(), "", icon_sz, 0, m_show_estimated_times)) { + m_show_estimated_times = !m_show_estimated_times; + if (m_cb_change_app_config) + m_cb_change_app_config("show_estimated_times_in_dbl_slider", m_show_estimated_times ? "1" : "0"); + } + if (m_mode == MultiAsSingle && m_draw_mode == dmRegular && + ImGuiPureWrap::menu_item_with_icon(_u8L("Set extruder sequence for the entire print").c_str(), "")) { + if (m_ticks.edit_extruder_sequence(m_ctrl.GetMaxPos(), m_mode)) + process_ticks_changed(); + } + if (ImGuiPureWrap::begin_menu(_u8L("Ruler").c_str())) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show").c_str(), "", icon_sz, 0, m_show_ruler)) { + m_show_ruler = !m_show_ruler; + if (m_show_ruler) + m_imgui->set_requires_extra_frame(); + if (m_cb_change_app_config) + m_cb_change_app_config("show_ruler_in_dbl_slider", m_show_ruler ? "1" : "0"); + } + + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Show background").c_str(), "", icon_sz, 0, m_show_ruler_bg)) { + m_show_ruler_bg = !m_show_ruler_bg; + if (m_cb_change_app_config) + m_cb_change_app_config("show_ruler_bg_in_dbl_slider", m_show_ruler_bg ? "1" : "0"); + } + + ImGuiPureWrap::end_menu(); + } + if (can_edit()) { + if (ImGuiPureWrap::menu_item_with_icon(_u8L("Use default colors").c_str(), "", icon_sz, 0, m_ticks.used_default_colors())) { + UseDefaultColors(!m_ticks.used_default_colors()); + } + + if (m_mode != MultiExtruder && m_draw_mode == dmRegular && + ImGuiPureWrap::menu_item_with_icon(_u8L("Set auto color changes").c_str(), "")) { + auto_color_change(); + } + } + + ImGui::EndPopup(); + } +} + +void DSForLayers::render_edit_menu() +{ + if (!m_show_edit_menu) + return; + + if (m_ticks.has_tick(m_ctrl.GetActivePos()) && ImGui::BeginPopup("edit_menu_popup")) { + std::set::iterator it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos()}); + + if (it->type == ToolChange) { + if (render_multi_extruders_menu(true)) { + ImGui::EndPopup(); + return; + } + } + else { + std::string edit_item_name = it->type == CustomGCode::ColorChange ? _u8L("Edit color") : + it->type == CustomGCode::PausePrint ? _u8L("Edit pause print message") : + _u8L("Edit custom G-code"); + if (ImGuiPureWrap::menu_item_with_icon(edit_item_name.c_str(), "")) { + edit_tick(); + ImGui::EndPopup(); + return; + } + } + + if (it->type == ColorChange && m_mode == MultiAsSingle) { + if (render_multi_extruders_menu(true)) { + ImGui::EndPopup(); + return; + } + } + + std::string delete_item_name = it->type == CustomGCode::ColorChange ? _u8L("Delete color change") : + it->type == CustomGCode::ToolChange ? _u8L("Delete tool change") : + it->type == CustomGCode::PausePrint ? _u8L("Delete pause print") : + _u8L("Delete custom G-code"); + if (ImGuiPureWrap::menu_item_with_icon(delete_item_name.c_str(), "")) + delete_current_tick(); + + ImGui::EndPopup(); + } +} + +bool DSForLayers::render_button(const wchar_t btn_icon, const wchar_t btn_icon_hovered, const std::string& label_id, const ImVec2& pos, FocusedItem focus, int tick /*= -1*/) +{ + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + + int windows_flag = ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse + | ImGuiWindowFlags_NoFocusOnAppearing; + + ImGuiPureWrap::set_next_window_pos(pos.x, pos.y, ImGuiCond_Always); + std::string win_name = label_id + "##btn_win"; + ImGuiPureWrap::begin(win_name, windows_flag); + + ImGuiContext& g = *GImGui; + + m_focus = focus; + std::string tooltip = m_allow_editing ? get_tooltip(tick) : ""; + ImGui::SetCursorPos(ImVec2(0, 0)); + const bool ret = m_imgui->image_button(g.HoveredWindow == g.CurrentWindow ? btn_icon_hovered : btn_icon, tooltip, false); + + if (tick > 0 && tick == m_ctrl.GetActivePos() && g.HoveredWindow == g.CurrentWindow && g.IO.MouseClicked[1]) + m_show_edit_menu = true; + + ImGuiPureWrap::end(); + + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(3); + + return ret; +} + +bool DSForLayers::render_jump_to_window(const ImVec2& pos, double* active_value, double min_z, double max_z) +{ + if (!m_show_get_jump_value) + return false; + + const std::string msg_text = _u8L("Enter the height you want to jump to") + ":"; + const std::string win_name = _u8L("Jump to height") + "##btn_win"; + const ImVec2 msg_size = ImGui::CalcTextSize(msg_text.c_str(), NULL, true); + + const float ctrl_pos_x = msg_size.x + 15 * m_scale; + const float ctrl_width = 50.f * m_scale; + + ImGui::SetNextWindowPos(pos, ImGuiCond_Always); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 12.0f, 8.0f }); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.13f, 0.13f, 0.13f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + + ImGuiWindowFlags windows_flag = ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse; + + ImGui::Begin(win_name.c_str(), &m_show_get_jump_value, windows_flag); + + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", msg_text.c_str()); + ImGui::SameLine(ctrl_pos_x); + ImGui::PushItemWidth(ctrl_width); + + ImGui::InputDouble("##jump_to", active_value, 0.0, 0.0, "%.2f", ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll); + //check if Enter was pressed + bool enter_pressed = ImGui::IsItemDeactivatedAfterEdit(); + + //check out of range + bool disable_ok = *active_value < min_z || *active_value > max_z; + + ImGui::Text("%s", ""); + ImGui::SameLine(ctrl_pos_x); + + if (disable_ok) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + + bool ok_pressed = ImGui::Button("OK##jump_to", ImVec2(ctrl_width, 0.f)); + + if (disable_ok) { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } + + ImGui::End(); + + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(3); + + return enter_pressed || ok_pressed; +} + +void DSForLayers::Render(const int canvas_width, const int canvas_height, float extra_scale/* = 0.1f*/, float offset /*= 0.f*/) +{ + if (!m_ctrl.IsShown()) + return; + m_scale = extra_scale * 0.1f * m_em; + + m_ruler.set_scale(m_scale); + + const float action_btn_sz = m_imgui->GetTextureCustomRect(ImGui::DSRevert)->Height; + const float tick_icon_side = m_imgui->GetTextureCustomRect(ImGui::PausePrint)->Height; + + ImVec2 pos; + + const float VERTICAL_SLIDER_WIDTH = m_show_ruler ? 125.f : 105.0f; + + pos.x = canvas_width - VERTICAL_SLIDER_WIDTH * m_scale - tick_icon_side; + pos.y = 1.5f * action_btn_sz + offset; + if (m_allow_editing) + pos.y += 2.f; + + ImVec2 size = ImVec2(VERTICAL_SLIDER_WIDTH * m_scale, canvas_height - 4.f * action_btn_sz - offset); + + m_ctrl.Init(pos, size, m_scale, m_show_ruler); + if (m_ctrl.render()) { + // request one more frame if value was changes with mouse wheel + if (GImGui->IO.MouseWheel != 0.0f) + m_imgui->set_requires_extra_frame(); + process_thumb_move(); + + // discard all getters dialogs + m_show_get_jump_value = false; + } + else if (m_ctrl.IsLClickOnThumb() && can_edit() && + !m_ticks.has_tick(m_ctrl.GetActivePos())) { + add_code_as_tick(ColorChange); + } + + // draw action buttons + + const float groove_center_x = m_ctrl.GetGrooveRect().GetCenter().x; + + ImVec2 btn_pos = ImVec2(groove_center_x - 0.5f * action_btn_sz, pos.y - 0.75f * action_btn_sz); + + if (!m_ticks.empty() && can_edit() && + render_button(ImGui::DSRevert, ImGui::DSRevertHovered, "revert", btn_pos, fiRevertIcon)) + discard_all_thicks(); + + btn_pos.y += 0.5f * action_btn_sz + size.y; + const bool is_one_layer = m_ctrl.IsCombineThumbs(); + if (render_button(is_one_layer ? ImGui::Lock : ImGui::Unlock, is_one_layer ? ImGui::LockHovered : ImGui::UnlockHovered, "one_layer", btn_pos, fiOneLayerIcon)) + ChangeOneLayerLock(); + + btn_pos.y += 1.2f * action_btn_sz; + if (render_button(ImGui::DSSettings, ImGui::DSSettingsHovered, "settings", btn_pos, fiCogIcon)) { + m_show_cog_menu = true; + } + + if (m_draw_mode == dmSequentialFffPrint && m_ctrl.IsRClickOnThumb()) { + std::string tooltip = _u8L("The sequential print is on.\n" + "It's impossible to apply any custom G-code for objects printing sequentually."); + ImGuiPureWrap::tooltip(tooltip, ImGui::GetFontSize() * 20.0f); + } + else + render_menu(); + + if (render_jump_to_window(ImVec2(0.5f * canvas_width, 0.5f*canvas_height), &m_jump_to_value, + m_values[m_ctrl.GetMinPos()], m_values[m_ctrl.GetMaxPos()])) + process_jump_to_value(); + + if (can_edit()) + render_color_picker(); +} + +void DSForLayers::force_ruler_update() +{ + m_ruler.invalidate(); +} + +bool DSForLayers::is_wipe_tower_layer(int tick) const +{ + if (!m_ticks.is_wipe_tower || tick >= (int)m_values.size()) + return false; + if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1])) + return false; + if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) || + (tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer + return true; + + return false; +} + +static std::string short_and_splitted_time(const std::string& time) +{ + // Parse the dhms time format. + int days = 0; + int hours = 0; + int minutes = 0; + int seconds = 0; + if (time.find('d') != std::string::npos) + ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); + else if (time.find('h') != std::string::npos) + ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); + else if (time.find('m') != std::string::npos) + ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); + else if (time.find('s') != std::string::npos) + ::sscanf(time.c_str(), "%ds", &seconds); + + // Format the dhm time. + auto get_d = [days]() { return format(_u8L("%1%d"), days); }; + auto get_h = [hours]() { return format(_u8L("%1%h"), hours); }; + auto get_m = [minutes](){ return format(_u8L("%1%m"), minutes); }; + auto get_s = [seconds](){ return format(_u8L("%1%s"), seconds); }; + + if (days > 0) + return format("%1%%2%\n%3%", get_d(), get_h(), get_m()); + if (hours > 0) { + if (hours < 10 && minutes < 10 && seconds < 10) + return format("%1%%2%%3%", get_h(), get_m(), get_s()); + if (hours > 10 && minutes > 10 && seconds > 10) + return format("%1%\n%2%\n%3%", get_h(), get_m(), get_s()); + if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10)) + return format("%1%\n%2%%3%", get_h(), get_m(), get_s()); + return format("%1%%2%\n%3%", get_h(), get_m(), get_s()); + } + if (minutes > 0) { + if (minutes > 10 && seconds > 10) + return format("%1%\n%2%", get_m(), get_s()); + return format("%1%%2%", get_m(), get_s()); + } + return get_s(); +} + +std::string DSForLayers::get_label(int pos, LabelType label_type, const std::string& fmt/* = "%1$.2f"*/) const +{ + const size_t value = pos; + + if (m_values.empty()) + return format("%1%", pos); + if (value >= m_values.size()) + return "ErrVal"; + + // When "Print Settings -> Multiple Extruders -> No sparse layer" is enabled, then "Smart" Wipe Tower is used for wiping. + // As a result, each layer with tool changes is splited for min 3 parts: first tool, wiping, second tool ... + // So, vertical slider have to respect to this case. + // see https://github.com/qidi3d/QIDISlicer/issues/6232. + // m_values contains data for all layer's parts, + // but m_layers_values contains just unique Z values. + // Use this function for correct conversion slider position to number of printed layer + auto get_layer_number = [this](int value, LabelType label_type) { + if (label_type == ltEstimatedTime && m_layers_times.empty()) + return size_t(-1); + double layer_print_z = m_values[is_wipe_tower_layer(value) ? std::max(value - 1, 0) : value]; + auto it = std::lower_bound(m_layers_values.begin(), m_layers_values.end(), layer_print_z - epsilon()); + if (it == m_layers_values.end()) { + it = std::lower_bound(m_values.begin(), m_values.end(), layer_print_z - epsilon()); + if (it == m_values.end()) + return size_t(-1); + return size_t(value); + } + return size_t(it - m_layers_values.begin()); + }; + + if (label_type == ltEstimatedTime) { + if (m_ticks.is_wipe_tower) { + size_t layer_number = get_layer_number(value, label_type); + return (layer_number == size_t(-1) || layer_number == m_layers_times.size()) ? "" : short_and_splitted_time(get_time_dhms(m_layers_times[layer_number])); + } + return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; + } + std::string str = format(fmt, m_values[value]); + if (label_type == ltHeight) + return str; + if (label_type == ltHeightWithLayer) { + size_t layer_number = m_ticks.is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1); + return format("%1%\n(%2%)", str, layer_number); + } + + return ""; +} + +void DSForLayers::ChangeOneLayerLock() +{ + m_ctrl.CombineThumbs(!m_ctrl.IsCombineThumbs()); + process_thumb_move(); +} + +std::string DSForLayers::get_tooltip(int tick/*=-1*/) +{ + if (m_focus == fiNone) + return ""; + if (m_focus == fiOneLayerIcon) + return _u8L("One layer mode"); + if (m_focus == fiRevertIcon) + return _u8L("Discard all custom changes"); + if (m_focus == fiCogIcon) + { + return m_mode == MultiAsSingle ? + (boost::format(_u8L("Jump to height %s\n" + "Set ruler mode\n" + "or Set extruder sequence for the entire print")) % "(Shift + G)").str() : + (boost::format(_u8L("Jump to height %s\n" + "or Set ruler mode")) % "(Shift + G)").str(); + } + if (m_focus == fiColorBand) + return m_mode != SingleExtruder || !can_edit() ? "" : + _u8L("Edit current color - Right click the colored slider segment"); + if (m_focus == fiSmartWipeTower) + return _u8L("This is wipe tower layer"); + if (m_draw_mode == dmSlaPrint) + return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode + + std::string tooltip; + const auto tick_code_it = m_ticks.ticks.find(TickCode{tick}); + + if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist + { + if (m_draw_mode == dmSequentialFffPrint) + return (_u8L("The sequential print is on.\n" + "It's impossible to apply any custom G-code for objects printing sequentually.") + "\n"); + + // Show mode as a first string of tooltop + tooltip = " " + _u8L("Print mode") + ": "; + tooltip += (m_mode == SingleExtruder ? SingleExtruderMode : + m_mode == MultiAsSingle ? MultiAsSingleMode : + MultiExtruderMode ); + tooltip += "\n\n"; + + /* Note: just on OSX!!! + * Right click event causes a little scrolling. + * So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick + * Show this information in tooltip + * */ + + // Show list of actions with new tick + tooltip += ( m_mode == MultiAsSingle ? + _u8L("Add extruder change - Left click") : + m_mode == SingleExtruder ? + _u8L("Add color change - Left click for predefined color or " + "Shift + Left click for custom color selection") : + _u8L("Add color change - Left click") ) + " " + + _u8L("or press \"+\" key") + "\n" + ( + is_osx ? + _u8L("Add another code - Ctrl + Left click") : + _u8L("Add another code - Right click") ); + } + + if (tick_code_it != m_ticks.ticks.end()) // tick exists + { + if (m_draw_mode == dmSequentialFffPrint) + return _u8L("The sequential print is on.\n" + "It's impossible to apply any custom G-code for objects printing sequentually.\n" + "This code won't be processed during G-code generation."); + + // Show custom Gcode as a first string of tooltop + std::string space = " "; + tooltip = space; + auto format_gcode = [space](std::string gcode) -> std::string { + // when the tooltip is too long, it starts to flicker, see: https://github.com/qidi3d/QIDISlicer/issues/7368 + // so we limit the number of lines shown + std::vector lines; + boost::split(lines, gcode, boost::is_any_of("\n"), boost::token_compress_off); + static const size_t MAX_LINES = 10; + if (lines.size() > MAX_LINES) { + gcode = lines.front() + '\n'; + for (size_t i = 1; i < MAX_LINES; ++i) { + gcode += lines[i] + '\n'; + } + gcode += "[" + _u8L("continue") + "]\n"; + } + boost::replace_all(gcode, "\n", "\n" + space); + return gcode; + }; + tooltip += + tick_code_it->type == ColorChange ? + (m_mode == SingleExtruder && tick_code_it->extruder==1 ? + format(_u8L("Color change (\"%1%\")"), gcode(ColorChange)) : + format(_u8L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) : + tick_code_it->type == CustomGCode::PausePrint ? + format(_u8L("Pause print (\"%1%\")"), gcode(CustomGCode::PausePrint)) : + tick_code_it->type == Template ? + format(_u8L("Custom template (\"%1%\")"), gcode(Template)) : + tick_code_it->type == ToolChange ? + format(_u8L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : + format_gcode(tick_code_it->extra);// tick_code_it->type == Custom + + // If tick is marked as a conflict (exclamation icon), + // we should to explain why + ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_values[tick]); + if (conflict != ctNone) + tooltip += "\n\n" + _u8L("Note") + "! "; + if (conflict == ctModeConflict) + tooltip += _u8L("G-code associated to this tick mark is in a conflict with print mode.\n" + "Editing it will cause changes of Slider data."); + else if (conflict == ctMeaninglessColorChange) + tooltip += _u8L("There is a color change for extruder that won't be used till the end of print job.\n" + "This code won't be processed during G-code generation."); + else if (conflict == ctMeaninglessToolChange) + tooltip += _u8L("There is an extruder change set to the same extruder.\n" + "This code won't be processed during G-code generation."); + else if (conflict == ctNotPossibleToolChange) + tooltip += _u8L("There is an extruder change set to a non-existing extruder.\n" + "This code won't be processed during G-code generation."); + else if (conflict == ctRedundant) + tooltip += _u8L("There is a color change for extruder that has not been used before.\n" + "Check your settings to avoid redundant color changes."); + + // Show list of actions with existing tick + if (m_focus == fiActionIcon) + tooltip += "\n\n" + _u8L("Delete tick mark - Left click or press \"-\" key") + "\n" + ( + is_osx ? + _u8L("Edit tick mark - Ctrl + Left click") : + _u8L("Edit tick mark - Right click") ); + } + + return tooltip; +} + +void DSForLayers::UseDefaultColors(bool def_colors_on) +{ + m_ticks.set_default_colors(def_colors_on); +} + +// !ysFIXME draw with imgui +void DSForLayers::auto_color_change() +{ + if (m_ticks.auto_color_change(m_mode)) { + update_draw_scroll_line_cb(); + process_ticks_changed(); + } +} + +void DSForLayers::add_code_as_tick(Type type, int selected_extruder/* = -1*/) +{ + const int tick = m_ctrl.GetActivePos(); + + if (!m_ticks.check_ticks_changed_event(type, m_mode)) { + process_ticks_changed(); + return; + } + + const int extruder = selected_extruder > 0 ? selected_extruder : std::max(1, m_ticks.only_extruder_id); + const auto it = m_ticks.ticks.find(TickCode{ tick }); + + bool was_ticks = m_ticks.empty(); + + if ( it == m_ticks.ticks.end() ) { + // try to add tick + if (!m_ticks.add_tick(tick, type, extruder, m_values[tick])) + return; + } + else if (type == ToolChange || type == ColorChange) { + // try to switch tick code to ToolChange or ColorChange accordingly + if (!m_ticks.switch_code_for_tick(it, type, extruder)) + return; + } + else + return; + + if (was_ticks != m_ticks.empty()) + update_draw_scroll_line_cb(); + + m_show_just_color_change_menu = false; + process_ticks_changed(); +} + +void DSForLayers::add_current_tick() +{ + if (!can_edit()) + return; + + const int tick = m_ctrl.GetActivePos(); + auto it = m_ticks.ticks.find(TickCode{ tick }); + + if (it != m_ticks.ticks.end()) // this tick is already exist + return; + if (!m_ticks.check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange, m_mode)) { + process_ticks_changed(); + return; + } + + if (m_mode == SingleExtruder) + add_code_as_tick(ColorChange); + else { + m_show_just_color_change_menu = true; + m_imgui->set_requires_extra_frame(); + } +} + +void DSForLayers::delete_current_tick() +{ + auto it = m_ticks.ticks.find(TickCode{ m_ctrl.GetActivePos()}); + if (it == m_ticks.ticks.end()) // this tick doesn't exist + return; + + m_ticks.ticks.erase(it); + process_ticks_changed(); +} + +void DSForLayers::edit_tick(int tick/* = -1*/) +{ + if (tick < 0) + tick = m_ctrl.GetActivePos(); + const std::set::iterator it = m_ticks.ticks.find(TickCode{ tick }); + + if (it == m_ticks.ticks.end()) // this tick doesn't exist + return; + + if (!m_ticks.check_ticks_changed_event(it->type, m_mode) || + m_ticks.edit_tick(it, m_values[it->tick])) + process_ticks_changed(); +} + +// discard all custom changes on DoubleSlider +void DSForLayers::discard_all_thicks() +{ + m_ticks.ticks.clear(); + m_ctrl.ResetPositions(); + update_draw_scroll_line_cb(); + process_ticks_changed(); +} + +void DSForLayers::jump_to_value() +{ + //Init "jump to value"; + m_show_get_jump_value = true; + m_jump_to_value = m_values[m_ctrl.GetActivePos()]; + + m_imgui->set_requires_extra_frame(); +} + +void DSForLayers::process_jump_to_value() +{ + if (int tick_value = m_ticks.get_tick_from_value(m_jump_to_value, true); tick_value > 0.0) { + m_show_get_jump_value = false; + + if (m_ctrl.IsActiveHigherThumb()) + SetHigherPos(tick_value); + else + SetLowerPos(tick_value); + } +} + +bool DSForLayers::can_edit() const +{ + return m_allow_editing && m_draw_mode != dmSlaPrint; +} + +} // DoubleSlider + + diff --git a/src/slic3r/GUI/DoubleSliderForLayers.hpp b/src/slic3r/GUI/DoubleSliderForLayers.hpp new file mode 100644 index 0000000..8f2ee7a --- /dev/null +++ b/src/slic3r/GUI/DoubleSliderForLayers.hpp @@ -0,0 +1,219 @@ + +#ifndef slic3r_GUI_DoubleSliderForLayers_hpp_ +#define slic3r_GUI_DoubleSliderForLayers_hpp_ + +#include +#include +#include +#include + +#include "ImGuiDoubleSlider.hpp" +#include "RulerForDoubleSlider.hpp" +#include "TickCodesManager.hpp" +#include "libslic3r/CustomGCode.hpp" + +struct ImVec2; + +namespace Slic3r { +class Print; + +namespace GUI +{ +class ImGuiWrapper; +} +} + +using namespace Slic3r::CustomGCode; + +namespace DoubleSlider { + +enum FocusedItem { + fiNone, + fiRevertIcon, + fiOneLayerIcon, + fiCogIcon, + fiColorBand, + fiActionIcon, + fiSmartWipeTower, + fiTick +}; + +enum DrawMode +{ + dmRegular, + dmSlaPrint, + dmSequentialFffPrint, +}; + +enum LabelType +{ + ltHeightWithLayer, + ltHeight, + ltEstimatedTime, +}; + +class DSForLayers : public Manager +{ +public: + DSForLayers() : Manager() {} + DSForLayers(int lowerValue, + int higherValue, + int minValue, + int maxValue, + bool allow_editing); + ~DSForLayers() {} + + void ChangeOneLayerLock(); + + Info GetTicksValues() const; + void SetTicksValues(const Info& custom_gcode_per_print_z); + void SetLayersTimes(const std::vector& layers_times, float total_time); + void SetLayersTimes(const std::vector& layers_times); + + void SetDrawMode(bool is_sla_print, bool is_sequential_print); + + void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); + + void Render(const int canvas_width, const int canvas_height, float extra_scale = 1.f, float offset = 0.f) override; + void force_ruler_update(); + + // jump to selected layer + void jump_to_value(); + + // just for editor + + void SetExtruderColors(const std::vector& extruder_colors); + void UseDefaultColors(bool def_colors_on); + bool is_new_print(const std::string& print_obj_idxs); + void set_imgui_wrapper(Slic3r::GUI::ImGuiWrapper* imgui) { m_imgui = imgui; } + void show_estimated_times(bool show) { m_show_estimated_times = show; } + void show_ruler(bool show, bool show_bg) { m_show_ruler = show; m_show_ruler_bg = show_bg; } + + // manipulation with slider from keyboard + + // add default action for tick, when press "+" + void add_current_tick(); + // delete current tick, when press "-" + void delete_current_tick(); + // process adding of auto color change + void auto_color_change(); + + void set_callback_on_ticks_changed(std::function cb) + { m_cb_ticks_changed = cb; }; + + void set_callback_on_check_gcode(std::function cb ) + { m_ticks.set_callback_on_check_gcode(cb); } + + void set_callback_on_get_extruder_colors(std::function()> cb) + { m_cb_get_extruder_colors = cb; } + + void set_callback_on_get_print (std::function cb) + { m_cb_get_print = cb; } + + void set_callback_on_change_app_config (std::function cb) + { m_cb_change_app_config = cb; } + + void set_callback_on_empty_auto_color_change(std::function cb) + { m_ticks.set_callback_on_empty_auto_color_change(cb); } + + void set_callback_on_get_custom_code(std::function cb) + { m_ticks.set_callback_on_get_custom_code(cb); } + + void set_callback_on_get_pause_print_msg(std::function cb) + { m_ticks.set_callback_on_get_pause_print_msg(cb); } + + void set_callback_on_get_new_color(std::function cb) + { m_ticks.set_callback_on_get_new_color(cb); } + + void set_callback_on_show_info_msg(std::function cb) + { m_ticks.set_callback_on_show_info_msg(cb); } + + void set_callback_on_show_warning_msg(std::function cb) + { m_ticks.set_callback_on_show_warning_msg(cb); } + + void set_callback_on_get_extruders_cnt(std::function cb) + { m_ticks.set_callback_on_get_extruders_cnt(cb); } + + void set_callback_on_get_extruders_sequence(std::function cb) + { m_ticks.set_callback_on_get_extruders_sequence(cb); } + + std::string gcode(Type type) { return m_ticks.gcode(type); } + +private: + + bool is_osx { false }; + bool m_allow_editing { true }; + bool m_show_estimated_times { true }; + bool m_show_ruler { false }; + bool m_show_ruler_bg { true }; + bool m_show_cog_menu { false }; + bool m_show_edit_menu { false }; + int m_pos_on_move { -1 }; + + DrawMode m_draw_mode { dmRegular }; + Mode m_mode { SingleExtruder }; + FocusedItem m_focus { fiNone }; + + Ruler m_ruler; + TickCodeManager m_ticks; + Slic3r::GUI::ImGuiWrapper* m_imgui { nullptr }; + + std::vector m_layers_times; + std::vector m_layers_values; + + bool is_wipe_tower_layer(int tick) const; + + std::string get_label(int tick, LabelType label_type, const std::string& fmt = "%1$.2f") const; + + std::string get_tooltip(int tick = -1); + + void update_draw_scroll_line_cb(); + + // functions for extend rendering of m_ctrl + + void draw_colored_band(const ImRect& groove, const ImRect& slideable_region); + void draw_ticks(const ImRect& slideable_region); + void draw_ruler(const ImRect& slideable_region); + void render_menu(); + void render_cog_menu(); + void render_edit_menu(); + bool render_button(const wchar_t btn_icon, const wchar_t btn_icon_hovered, const std::string& label_id, const ImVec2& pos, FocusedItem focus, int tick = -1); + + void add_code_as_tick(Type type, int selected_extruder = -1); + void edit_tick(int tick = -1); + void discard_all_thicks(); + void process_jump_to_value(); + bool can_edit() const; + + std::string get_label(int pos) const override { return get_label(pos, ltHeightWithLayer); } + + void process_ticks_changed() { + if (m_cb_ticks_changed) + m_cb_ticks_changed(); + } + + bool m_show_just_color_change_menu { false }; + bool m_show_get_jump_value { false }; + bool m_show_color_picker { false }; + + double m_jump_to_value { 0.0 }; + + std::string m_print_obj_idxs; + std::string m_selectable_color; + + void render_add_tick_menu(); + bool render_multi_extruders_menu(bool switch_current_code = false); + bool render_jump_to_window(const ImVec2& pos, double* active_value, double min_z, double max_z); + void render_color_picker(); + + std::function m_cb_ticks_changed { nullptr }; + std::function()> m_cb_get_extruder_colors { nullptr }; + std::function m_cb_get_print { nullptr }; + std::function m_cb_change_app_config { nullptr }; +}; + +} // DoubleSlider; + + + +#endif // slic3r_GUI_DoubleSliderForLayers_hpp_ diff --git a/src/slic3r/GUI/Downloader.cpp b/src/slic3r/GUI/Downloader.cpp index 8b8199c..043c267 100644 --- a/src/slic3r/GUI/Downloader.cpp +++ b/src/slic3r/GUI/Downloader.cpp @@ -165,7 +165,7 @@ void Downloader::start_download(const std::string& full_url) void Downloader::on_progress(wxCommandEvent& event) { size_t id = event.GetInt(); - float percent = (float)std::stoi(boost::nowide::narrow(event.GetString())) / 100.f; + float percent = (float)std::stoi(into_u8(event.GetString())) / 100.f; //BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent; NotificationManager* ntf_mngr = wxGetApp().notification_manager(); BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent; @@ -177,7 +177,7 @@ void Downloader::on_error(wxCommandEvent& event) set_download_state(event.GetInt(), DownloadState::DownloadError); BOOST_LOG_TRIVIAL(error) << "Download error: " << event.GetString(); NotificationManager* ntf_mngr = wxGetApp().notification_manager(); - ntf_mngr->set_download_URL_error(id, boost::nowide::narrow(event.GetString())); + ntf_mngr->set_download_URL_error(id, into_u8(event.GetString())); show_error(nullptr, format_wxstr(L"%1%\n%2%", _L("The download has failed") + ":", event.GetString())); } void Downloader::on_complete(wxCommandEvent& event) diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index 06ce45e..e6ea936 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "format.hpp" @@ -166,32 +167,27 @@ void FileGet::priv::get_perform() m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download"); wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE); - evt->SetString(boost::nowide::widen(m_filename)); + evt->SetString(from_u8(m_filename)); evt->SetInt(m_id); m_evt_handler->QueueEvent(evt); } boost::filesystem::path dest_path = m_dest_folder / m_filename; - - wxString temp_path_wstring(m_tmp_path.wstring()); - - //std::cout << "dest_path: " << dest_path.string() << std::endl; - //std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl; BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path); FILE* file; // open file for writting if (m_written == 0) - file = fopen(temp_path_wstring.c_str(), "wb"); + file = boost::nowide::fopen(m_tmp_path.string().c_str(), "wb"); else - file = fopen(temp_path_wstring.c_str(), "ab"); + file = boost::nowide::fopen(m_tmp_path.string().c_str(), "ab"); //assert(file != NULL); if (file == NULL) { wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR); // TRN %1% = file path - evt->SetString(GUI::format_wxstr(_L("Can't create file at %1%"), temp_path_wstring)); + evt->SetString(GUI::format_wxstr(_L("Can't create file at %1%"), m_tmp_path.string())); evt->SetInt(m_id); m_evt_handler->QueueEvent(evt); return; diff --git a/src/slic3r/GUI/EditGCodeDialog.cpp b/src/slic3r/GUI/EditGCodeDialog.cpp index 12cd8ea..21b92ee 100644 --- a/src/slic3r/GUI/EditGCodeDialog.cpp +++ b/src/slic3r/GUI/EditGCodeDialog.cpp @@ -23,6 +23,7 @@ #include "libslic3r/PlaceholderParser.hpp" #include "libslic3r/Preset.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Print.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index ed7f6cc..483ef7b 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -169,10 +169,10 @@ wxSize BitmapTextRenderer::GetSize() const if (!m_value.GetText().empty()) { wxSize size; - wxDataViewCtrl* const view = GetView(); - wxClientDC dc(view); - if (GetAttr().HasFont()) - dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); + wxDataViewCtrl* const view = GetView(); + wxClientDC dc(view); + if (GetAttr().HasFont()) + dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); else dc.SetFont(view->GetFont()); @@ -183,8 +183,8 @@ wxSize BitmapTextRenderer::GetSize() const #endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL size = dc.GetTextExtent(m_value.GetText()); - int lines = m_value.GetText().Freq('\n') + 1; - size.SetHeight(size.GetHeight() * lines); + int lines = m_value.GetText().Freq('\n') + 1; + size.SetHeight(size.GetHeight() * lines); if (m_value.GetBitmap().IsOk()) size.x += m_value.GetBitmap().GetWidth() + 4; @@ -236,7 +236,7 @@ bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value if (!text_editor || text_editor->GetValue().IsEmpty()) return false; - m_was_unusable_symbol = Slic3r::GUI::Plater::has_illegal_filename_characters(text_editor->GetValue()); + m_was_unusable_symbol = Slic3r::GUI::has_illegal_characters(text_editor->GetValue()); if (m_was_unusable_symbol) return false; @@ -330,6 +330,7 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR #ifdef _WIN32 Slic3r::GUI::wxGetApp().UpdateDarkUI(c_editor); #endif + int def_id = get_default_extruder_idx ? get_default_extruder_idx() : 0; c_editor->Append(_L("default"), def_id < 0 ? wxNullBitmap : *icons[def_id]); for (size_t i = 0; i < icons.size(); i++) diff --git a/src/slic3r/GUI/ExtruderSequenceDialog.hpp b/src/slic3r/GUI/ExtruderSequenceDialog.hpp index e47c438..fe6e09b 100644 --- a/src/slic3r/GUI/ExtruderSequenceDialog.hpp +++ b/src/slic3r/GUI/ExtruderSequenceDialog.hpp @@ -2,7 +2,8 @@ #define slic3r_GUI_ExtruderSequenceDialog_hpp_ #include "GUI_Utils.hpp" -#include "DoubleSlider.hpp" +#include "DoubleSliderForLayers.hpp" +#include "wxExtensions.hpp" class wxTextCtrl; class wxFlexGridSizer; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 0a9e8ea..f481b3d 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include "BitmapComboBox.hpp" #include "Widgets/ComboBox.hpp" + #ifdef __WXOSX__ #define wxOSX true #else @@ -73,7 +75,7 @@ ThumbnailErrors validate_thumbnails_string(wxString& str, const wxString& def_ex for (const auto& [format, size] : thumbnails_list) str += format_wxstr("%1%x%2%/%3%, ", size.x(), size.y(), extentions[int(format)]); str.resize(str.Len() - 2); -} + } return errors; } @@ -97,7 +99,6 @@ Field::~Field() void Field::PostInitialize() { - switch (m_opt.type) { case coPercents: @@ -136,7 +137,7 @@ void Field::PostInitialize() #else /* __APPLE__ */ case WXK_CONTROL_F: #endif /* __APPLE__ */ - case 'F': { wxGetApp().plater()->search(false); break; } + case 'F': { wxGetApp().show_search_dialog(); break; } default: break; } if (tab_id >= 0) @@ -186,6 +187,7 @@ void Field::on_edit_value() if (m_fn_edit_value) m_fn_edit_value(m_opt_id); } + wxString Field::get_tooltip_text(const wxString& default_string) { if (m_opt.tooltip.empty()) @@ -199,6 +201,7 @@ wxString Field::get_tooltip_text(const wxString& default_string) } bool newline_after_name = boost::iends_with(opt_id, "_gcode") && opt_id != "binary_gcode"; + return from_u8(m_opt.tooltip) + "\n" + _L("default value") + "\t: " + (newline_after_name ? "\n" : "") + default_string + (newline_after_name ? "" : "\n") + @@ -381,24 +384,23 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true if (!error_str.empty()) error_str += "\n\n"; error_str += _L("Input value is out of range"); - } + } if (errors.has(ThumbnailError::InvalidExt)) { if (!error_str.empty()) error_str += "\n\n"; error_str += _L("Some extension in the input is invalid"); - } + } show_error(m_parent, error_str); - } + } else if (str_out != str) { str = str_out; set_value(str, true); - } } + } m_value = into_u8(str); break; - } - + } default: break; @@ -674,6 +676,7 @@ void TextCtrl::change_field_value(wxEvent& event) }; #endif //__WXGTK__ + wxWindow* CheckBox::GetNewWin(wxWindow* parent, const wxString& label /*= wxEmptyString*/) { if (wxGetApp().suppress_round_corners()) @@ -731,6 +734,7 @@ bool CheckBox::GetValue() return dynamic_cast<::SwitchButton*>(window)->GetValue(); } + void CheckBox::BUILD() { auto size = wxSize(wxDefaultSize); if (m_opt.height >= 0) @@ -835,6 +839,7 @@ void CheckBox::disable() window->Disable(); } + void SpinCtrl::BUILD() { auto size = wxSize(def_width() * m_em_unit, wxDefaultCoord); if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit); @@ -871,6 +876,7 @@ void SpinCtrl::BUILD() { auto temp = new ::SpinInput(m_parent, text_value, "", wxDefaultPosition, size, wxTE_PROCESS_ENTER | wxSP_ARROW_KEYS + , min_val, max_val, default_value); #ifdef __WXGTK3__ @@ -908,8 +914,7 @@ void SpinCtrl::BUILD() { temp->SetToolTip(get_tooltip_text(text_value)); temp->Bind(wxEVT_TEXT, [this, temp](wxCommandEvent e) { - - long value; + long value; if (!e.GetString().ToLong(&value)) return; if (value < INT_MIN || value > INT_MAX) @@ -1191,6 +1196,10 @@ void Choice::set_selection() field->SetSelection(m_opt.default_value->getInt()); break; } + case coEnums:{ + field->SetSelection(m_opt.default_value->getInts()[m_opt_idx]); + break; + } case coFloat: case coPercent: { double val = m_opt.default_value->getFloat(); @@ -1278,7 +1287,8 @@ void Choice::set_value(const boost::any& value, bool change_event) break; } - case coEnum: { + case coEnum: + case coEnums: { auto val = m_opt.enum_def->enum_to_index(boost::any_cast(value)); assert(val.has_value()); field->SetSelection(val.has_value() ? *val : 0); @@ -1343,7 +1353,7 @@ boost::any& Choice::get_value() if (m_opt_id == rp_option) return m_value = boost::any(ret_str); - if (m_opt.type == coEnum) + if (m_opt.type == coEnum || m_opt.type == coEnums) // Closed enum: The combo box item index returned by the field must be convertible to an enum value. m_value = m_opt.enum_def->index_to_enum(field->GetSelection()); else if (m_opt.gui_type == ConfigOptionDef::GUIType::f_enum_open || m_opt.gui_type == ConfigOptionDef::GUIType::i_enum_open) { @@ -1782,3 +1792,4 @@ boost::any& SliderCtrl::get_value() } // Slic3r :: GUI + diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index cb7d95f..f4705fe 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -121,6 +121,7 @@ class UndoValueUIManager }; EditValueUI m_edit_ui; + public: UndoValueUIManager() {} ~UndoValueUIManager() {} @@ -133,6 +134,7 @@ public: bool set_edit_bitmap(const ScalableBitmap* bmp) { return m_edit_ui.set_bitmap(bmp); } bool set_edit_tooltip(const wxString& tip) { return m_edit_ui.set_tooltip(tip); } + // ui items used for revert line value bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; } const wxBitmapBundle& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); } @@ -140,11 +142,13 @@ public: const wxBitmapBundle& undo_to_sys_bitmap() const { return m_undo_ui.undo_to_sys_bitmap->bmp(); } const wxString* undo_to_sys_tooltip() const { return m_undo_ui.undo_to_sys_tooltip; } const wxColour* label_color() const { return m_undo_ui.label_color; } + // Extentions // Search blinker const bool blink() const { return m_undo_ui.blink; } bool* get_blink_ptr() { return &m_undo_ui.blink; } + // Edit field button bool has_edit_ui() const { return !m_edit_ui.tooltip.IsEmpty(); } const wxBitmapBundle* edit_bitmap() const { return &m_edit_ui.bitmap->bmp(); } @@ -201,6 +205,7 @@ public: /// Callback function to edit field value t_back_to_init m_fn_edit_value{ nullptr }; + // This is used to avoid recursive invocation of the field change/update by wxWidgets. bool m_disable_change_event {false}; bool m_is_modified_value {false}; @@ -290,6 +295,7 @@ inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && o inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; } using text_ctrl = ::TextInput; //wxTextCtrl + class TextCtrl : public Field { using Field::Field; #ifdef __WXGTK__ @@ -339,6 +345,7 @@ public: static bool GetValue(wxWindow* win); static void Rescale(wxWindow* win); static void SysColorChanged(wxWindow* win); + wxWindow* window{ nullptr }; void BUILD() override; @@ -354,6 +361,7 @@ public: void enable() override; void disable() override; wxWindow* getWindow() override { return window; } + private: void SetValue(bool value); bool GetValue(); @@ -387,14 +395,13 @@ public: m_value = value; dynamic_cast<::SpinInput*>(window)->SetValue(tmp_value); m_disable_change_event = false; - } + } */ void set_value(const boost::any& value, bool change_event = false) override; void set_last_meaningful_value() override; void set_na_value() override; boost::any& get_value() override; - /* boost::any& get_value() override { int value = static_cast<::SpinInput*>(window)->GetValue(); diff --git a/src/slic3r/GUI/FileArchiveDialog.cpp b/src/slic3r/GUI/FileArchiveDialog.cpp index 0157634..b650fb6 100644 --- a/src/slic3r/GUI/FileArchiveDialog.cpp +++ b/src/slic3r/GUI/FileArchiveDialog.cpp @@ -223,7 +223,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar path = boost::filesystem::path(extra.substr(0, extra_size)); } else { wxString wname = boost::nowide::widen(stat.m_filename); - std::string name = boost::nowide::narrow(wname); + std::string name = into_u8(wname); path = boost::filesystem::path(name); } assert(!path.empty()); @@ -384,4 +384,4 @@ void FileArchiveDialog::on_none_button() } } // namespace GUI -} // namespace Slic3r \ No newline at end of file +} // namespace Slic3r diff --git a/src/slic3r/GUI/FrequentlyChangedParameters.cpp b/src/slic3r/GUI/FrequentlyChangedParameters.cpp new file mode 100644 index 0000000..1a58f18 --- /dev/null +++ b/src/slic3r/GUI/FrequentlyChangedParameters.cpp @@ -0,0 +1,361 @@ + + +#include "FrequentlyChangedParameters.hpp" +#include "Plater.hpp" + +#include + +#include +#include + +#include "libslic3r/PresetBundle.hpp" + +#include "GUI_App.hpp" +#include "wxExtensions.hpp" +#include "format.hpp" +#include "Tab.hpp" +#include "I18N.hpp" + +#include "WipeTowerDialog.hpp" + +using Slic3r::Preset; +using Slic3r::GUI::format_wxstr; + +namespace Slic3r { +namespace GUI { + +// Trigger Plater::schedule_background_process(). +wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); + +FreqChangedParams::FreqChangedParams(wxWindow* parent) +{ + DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; + + // Frequently changed parameters for FFF_technology + + m_og_fff = std::make_shared(parent, ""); + m_og_fff->set_config(config); + m_og_fff->hide_labels(); + + m_og_fff->on_change = [config, this](t_config_option_key opt_key, boost::any value) { + Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); + if (!tab_print) return; + + if (opt_key == "fill_density") { + tab_print->update_dirty(); + tab_print->reload_config(); + tab_print->update(); + } + else + { + DynamicPrintConfig new_conf = *config; + if (opt_key == "brim") { + double new_val; + double brim_width = config->opt_float("brim_width"); + if (boost::any_cast(value) == true) + { + new_val = m_brim_width == 0.0 ? 5 : + m_brim_width < 0.0 ? m_brim_width * (-1) : + m_brim_width; + } + else { + m_brim_width = brim_width * (-1); + new_val = 0; + } + new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val)); + } + else { + assert(opt_key == "support"); + const wxString& selection = boost::any_cast(value); + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + + auto support_material = selection == _("None") ? false : true; + new_conf.set_key_value("support_material", new ConfigOptionBool(support_material)); + + if (selection == _("Everywhere")) { + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); + if (printer_technology == ptFFF) + new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true)); + } else if (selection == _("Support on build plate only")) { + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); + if (printer_technology == ptFFF) + new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true)); + } else if (selection == _("For support enforcers only")) { + assert(printer_technology == ptFFF); + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); + new_conf.set_key_value("support_material_auto", new ConfigOptionBool(false)); + } + } + tab_print->load_config(new_conf); + } + }; + + + Line line = Line { "", "" }; + + ConfigOptionDef support_def; + support_def.label = L("Supports"); + support_def.type = coStrings; + support_def.tooltip = L("Select what kind of support do you need"); + support_def.set_enum_labels(ConfigOptionDef::GUIType::select_close, { + L("None"), + L("Support on build plate only"), + L("For support enforcers only"), + L("Everywhere") + }); + support_def.set_default_value(new ConfigOptionStrings{ "None" }); + Option option = Option(support_def, "support"); + option.opt.full_width = true; + line.append_option(option); + + /* Not a best solution, but + * Temporary workaround for right border alignment + */ + auto empty_widget = [this] (wxWindow* parent) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + return sizer; + }; + line.append_widget(empty_widget); + + m_og_fff->append_line(line); + + + line = Line { "", "" }; + + option = m_og_fff->get_option("fill_density"); + option.opt.label = L("Infill"); + option.opt.width = 8; + option.opt.sidetext = " "; + line.append_option(option); + + m_brim_width = config->opt_float("brim_width"); + ConfigOptionDef def; + def.label = L("Brim"); + def.type = coBool; + def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); + def.gui_type = ConfigOptionDef::GUIType::undefined; + def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }); + option = Option(def, "brim"); + option.opt.sidetext = ""; + line.append_option(option); + + auto wiping_dialog_btn = [this](wxWindow* parent) { + m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _L("Purging volumes") + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + wxGetApp().SetWindowVariantForButton(m_wiping_dialog_button); + wxGetApp().UpdateDarkUI(m_wiping_dialog_button, true); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); + m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) + { + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + DynamicPrintConfig& project_config = preset_bundle->project_config; + const bool use_custom_matrix = (project_config.option("wiping_volumes_use_custom_matrix"))->value; + const std::vector &init_matrix = (project_config.option("wiping_volumes_matrix"))->values; + + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_color_strings_from_plater_config(); + + // Extract the relevant config options, even values from possibly modified presets. + const double default_purge = static_cast(preset_bundle->printers.get_edited_preset().config.option("multimaterial_purging"))->value; + std::vector filament_purging_multipliers = preset_bundle->get_config_options_for_current_filaments("filament_purge_multiplier"); + + WipingDialog dlg(parent, cast(init_matrix), extruder_colours, default_purge, filament_purging_multipliers, use_custom_matrix); + + if (dlg.ShowModal() == wxID_OK) { + std::vector matrix = dlg.get_matrix(); + (project_config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(), matrix.end()); + (project_config.option("wiping_volumes_use_custom_matrix"))->value = dlg.get_use_custom_matrix(); + // Update Project dirty state, update application title bar. + Plater* plater = wxGetApp().plater(); + plater->update_project_dirty_from_presets(); + wxPostEvent(plater, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, plater)); + } + })); + + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, + int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + + return sizer; + }; + line.append_widget(wiping_dialog_btn); + m_og_fff->append_line(line); + + m_og_fff->activate(); + + Choice* choice = dynamic_cast(m_og_fff->get_field("support")); + choice->suppress_scroll(); + + // Frequently changed parameters for SLA_technology + + m_og_sla = std::make_shared(parent, ""); + m_og_sla->hide_labels(); + DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_og_sla->set_config(config_sla); + + m_og_sla->on_change = [config_sla](t_config_option_key opt_key, boost::any value) { + Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); + if (!tab) return; + + DynamicPrintConfig new_conf = *config_sla; + if (opt_key == "pad") { + const wxString& selection = boost::any_cast(value); + + const bool pad_enable = selection == _("None") ? false : true; + new_conf.set_key_value("pad_enable", new ConfigOptionBool(pad_enable)); + + if (selection == _("Below object")) + new_conf.set_key_value("pad_around_object", new ConfigOptionBool(false)); + else if (selection == _("Around object")) + new_conf.set_key_value("pad_around_object", new ConfigOptionBool(true)); + } + else + { + assert(opt_key == "support"); + const wxString& selection = boost::any_cast(value); + + const bool supports_enable = selection == _("None") ? false : true; + new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); + + std::string treetype = get_sla_suptree_prefix(new_conf); + + if (selection == _("Everywhere")) { + new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(false)); + new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); + } + else if (selection == _("Support on build plate only")) { + new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(true)); + new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); + } + else if (selection == _("For support enforcers only")) { + new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(true)); + } + } + + tab->load_config(new_conf); + tab->update_dirty(); + }; + + line = Line{ "", "" }; + + ConfigOptionDef support_def_sla = support_def; + support_def_sla.set_default_value(new ConfigOptionStrings{ "None" }); + option = Option(support_def_sla, "support"); + option.opt.full_width = true; + line.append_option(option); + line.append_widget(empty_widget); + m_og_sla->append_line(line); + + line = Line{ "", "" }; + + ConfigOptionDef pad_def; + pad_def.label = L("Pad"); + pad_def.type = coStrings; + pad_def.tooltip = L("Select what kind of pad do you need"); + pad_def.set_enum_labels(ConfigOptionDef::GUIType::select_close, { + L("None"), + L("Below object"), + L("Around object") + }); + pad_def.set_default_value(new ConfigOptionStrings{ "Below object" }); + option = Option(pad_def, "pad"); + option.opt.full_width = true; + line.append_option(option); + line.append_widget(empty_widget); + + m_og_sla->append_line(line); + + m_og_sla->activate(); + choice = dynamic_cast(m_og_sla->get_field("support")); + choice->suppress_scroll(); + choice = dynamic_cast(m_og_sla->get_field("pad")); + choice->suppress_scroll(); + +//Y26 + m_og_filament = std::make_shared(parent, ""); + DynamicPrintConfig* filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config; + + m_og_filament->set_config(filament_config); + m_og_filament->hide_labels(); + + m_og_filament->on_change = [filament_config, this](t_config_option_key opt_key, boost::any value) { + Tab* tab_filament = wxGetApp().get_tab(Preset::TYPE_FILAMENT); + if (!tab_filament) return; + + if (opt_key == "seal_print") { + tab_filament->update_dirty(); + tab_filament->reload_config(); + tab_filament->update(); + } + }; + + line = Line { "", "" }; + + option = m_og_filament->get_option("seal_print"); + option.opt.label = L("Seal"); + line.append_option(option); + line.append_widget(empty_widget); + + m_og_filament->append_line(line); + m_og_filament->activate(); + + m_sizer = new wxBoxSizer(wxVERTICAL); + m_sizer->Add(m_og_fff->sizer, 0, wxEXPAND); + + //Y26 + m_sizer->Add(m_og_filament->sizer, 0, wxEXPAND); + m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND); +} + +void FreqChangedParams::msw_rescale() +{ + m_og_fff->msw_rescale(); +//Y26 + m_og_filament->msw_rescale(); + m_og_sla->msw_rescale(); +} + +void FreqChangedParams::sys_color_changed() +{ + m_og_fff->sys_color_changed(); +//Y26 + m_og_filament->sys_color_changed(); + m_og_sla->sys_color_changed(); + + for (auto btn: m_empty_buttons) + btn->sys_color_changed(); + + wxGetApp().UpdateDarkUI(m_wiping_dialog_button, true); +} + +void FreqChangedParams::Show(bool is_fff) const +{ + const bool is_wdb_shown = m_wiping_dialog_button->IsShown(); + m_og_fff->Show(is_fff); +//Y26 + m_og_filament->Show(is_fff); + m_og_sla->Show(!is_fff); + + // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden + if (is_fff && !is_wdb_shown) + m_wiping_dialog_button->Hide(); +} + +ConfigOptionsGroup* FreqChangedParams::get_og(bool is_fff) +{ + return is_fff ? m_og_fff.get() : m_og_sla.get(); +} + +//Y26 +ConfigOptionsGroup* FreqChangedParams::get_og_filament() +{ + return m_og_filament.get(); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/FrequentlyChangedParameters.hpp b/src/slic3r/GUI/FrequentlyChangedParameters.hpp new file mode 100644 index 0000000..91daedb --- /dev/null +++ b/src/slic3r/GUI/FrequentlyChangedParameters.hpp @@ -0,0 +1,52 @@ + +#ifndef slic3r_FreqChangedParams_hpp_ +#define slic3r_FreqChangedParams_hpp_ + +#include + +#include "Event.hpp" + +class wxButton; +class wxSizer; +class wxWindow; +class ScalableButton; + +namespace Slic3r { + +namespace GUI { + +class ConfigOptionsGroup; + +class FreqChangedParams +{ + double m_brim_width = 0.0; + wxButton* m_wiping_dialog_button{ nullptr }; + wxSizer* m_sizer {nullptr}; + +//Y26 + std::shared_ptr m_og_filament; + std::shared_ptr m_og_fff; + std::shared_ptr m_og_sla; + + std::vector m_empty_buttons; + +public: + + FreqChangedParams(wxWindow* parent); + ~FreqChangedParams() = default; + + wxButton* get_wiping_dialog_button() noexcept { return m_wiping_dialog_button; } + wxSizer* get_sizer() noexcept { return m_sizer; } + void Show(bool is_fff) const; + + ConfigOptionsGroup* get_og(bool is_fff); +//Y26 + ConfigOptionsGroup* get_og_filament(); + void msw_rescale(); + void sys_color_changed(); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ee7d6cb..6cd8621 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -6,8 +6,9 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Utils.hpp" -#include "libslic3r/LocalesUtils.hpp" +#include #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/CustomGCode.hpp" #include "slic3r/GUI/format.hpp" @@ -18,12 +19,15 @@ #include "I18N.hpp" #include "GUI_Utils.hpp" #include "GUI.hpp" -#include "DoubleSlider.hpp" #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" #include "GUI_ObjectManipulation.hpp" +#include "MsgDialog.hpp" +#if ENABLE_ACTUAL_SPEED_DEBUG +#define IMGUI_DEFINE_MATH_OPERATORS +#endif // ENABLE_ACTUAL_SPEED_DEBUG #include #include @@ -38,154 +42,25 @@ #include #include + namespace Slic3r { namespace GUI { -static unsigned char buffer_id(EMoveType type) { - return static_cast(type) - static_cast(EMoveType::Retract); -} - -static EMoveType buffer_type(unsigned char id) { - return static_cast(static_cast(EMoveType::Retract) + id); -} - -// Round to a bin with minimum two digits resolution. -// Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. -static float round_to_bin(const float value) -{ -// assert(value >= 0); - constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f }; - constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f }; - constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f }; - // Scaling factor, pointer to the tables above. - int i = 0; - // While the scaling factor is not yet large enough to get two integer digits after scaling and rounding: - for (; value < threshold[i] && i < 4; ++ i) ; - // At least on MSVC std::round() calls a complex function, which is pretty expensive. - // our fast_round_up is much cheaper and it could be inlined. -// return std::round(value * scale[i]) * invscale[i]; - double a = value * scale[i]; - assert(std::abs(a) < double(std::numeric_limits::max())); - return fast_round_up(a) * invscale[i]; -} - -void GCodeViewer::VBuffer::reset() -{ - // release gpu memory - if (!vbos.empty()) { - glsafe(::glDeleteBuffers(static_cast(vbos.size()), static_cast(vbos.data()))); - vbos.clear(); - } - -#if ENABLE_GL_CORE_PROFILE - if (!vaos.empty()) { - glsafe(::glDeleteVertexArrays(static_cast(vaos.size()), static_cast(vaos.data()))); - vaos.clear(); - } -#endif // ENABLE_GL_CORE_PROFILE - - sizes.clear(); - count = 0; -} - -void GCodeViewer::InstanceVBuffer::Ranges::reset() -{ - for (Range& range : ranges) { - // release gpu memory - if (range.vbo > 0) - glsafe(::glDeleteBuffers(1, &range.vbo)); - } - - ranges.clear(); -} - -void GCodeViewer::InstanceVBuffer::reset() -{ - s_ids.clear(); - buffer.clear(); - render_ranges.reset(); -} - -void GCodeViewer::IBuffer::reset() -{ - // release gpu memory - if (ibo > 0) { - glsafe(::glDeleteBuffers(1, &ibo)); - ibo = 0; - } - - vbo = 0; - count = 0; -} - -bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const -{ - auto matches_percent = [](float value1, float value2, float max_percent) { - return std::abs(value2 - value1) / value1 <= max_percent; - }; - - switch (move.type) - { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: - case EMoveType::Extrude: { - // use rounding to reduce the number of generated paths - if (account_for_volumetric_rate) - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_bin(move.height) && width == round_to_bin(move.width) && - matches_percent(volumetric_rate, move.volumetric_rate(), 0.001f); - else - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_bin(move.height) && width == round_to_bin(move.width); - } - case EMoveType::Travel: { - return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; - } - default: { return false; } - } -} - -void GCodeViewer::TBuffer::Model::reset() -{ - instances.reset(); -} - -void GCodeViewer::TBuffer::reset() -{ - vertices.reset(); - for (IBuffer& buffer : indices) { - buffer.reset(); - } - - indices.clear(); - paths.clear(); - render_paths.clear(); - model.reset(); -} - -void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) -{ - Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; - // use rounding to reduce the number of generated paths - paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, - round_to_bin(move.height), round_to_bin(move.width), - move.feedrate, move.fan_speed, move.temperature, - move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); -} - +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS +void GCodeViewer::COG::render(bool fixed_screen_size) +#else void GCodeViewer::COG::render() +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS { if (!m_visible) return; +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + fixed_screen_size = true; + init(fixed_screen_size); +#else init(); +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS GLShaderProgram* shader = wxGetApp().get_shader("toolpaths_cog"); if (shader == nullptr) @@ -196,8 +71,12 @@ void GCodeViewer::COG::render() glsafe(::glDisable(GL_DEPTH_TEST)); const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d model_matrix = Geometry::translation_transform(cog()); - if (m_fixed_size) { + Transform3d model_matrix = Geometry::translation_transform(cog()) * Geometry::scale_transform(m_scale_factor); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + if (fixed_screen_size) { +#else + if (m_fixed_screen_size) { +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS const double inv_zoom = camera.get_inv_zoom(); model_matrix = model_matrix * Geometry::scale_transform(inv_zoom); } @@ -216,16 +95,16 @@ void GCodeViewer::COG::render() //ImGuiWrapper& imgui = *wxGetApp().imgui(); //const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - //imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 0.5f, 0.0f); + //ImGuiPureWrap::set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 0.5f, 0.0f); //ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); //ImGui::SetNextWindowBgAlpha(0.25f); - //imgui.begin(std::string("COG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - //imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Center of mass") + ":"); + //ImGuiPureWrap::begin(std::string("COG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + //ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Center of mass") + ":"); //ImGui::SameLine(); //char buf[1024]; //const Vec3d position = cog(); //sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); - //imgui.text(std::string(buf)); + //ImGuiPureWrap::text(std::string(buf)); //// force extra frame to automatically update window size //const float width = ImGui::GetWindowWidth(); @@ -240,57 +119,92 @@ void GCodeViewer::COG::render() //ImGui::PopStyleVar(); } -float GCodeViewer::Extrusions::Range::step_size(EType type) const +#if ENABLE_ACTUAL_SPEED_DEBUG +int GCodeViewer::SequentialView::ActualSpeedImguiWidget::plot(const char* label, const std::array& frame_size) { - switch (type) - { - default: - case EType::Linear: { return (max > min) ? (max - min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } - case EType::Logarithmic: { return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; } - } -} + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return -1; -ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const -{ - // Input value scaled to the colors range - float global_t = 0.0f; - const float step = step_size(type); - if (step > 0.0f) { - switch (type) - { - default: - case EType::Linear: { global_t = (value > min) ? (value - min) / step : 0.0f; break; } - case EType::Logarithmic: { global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; break; } + const ImGuiStyle& style = ImGui::GetStyle(); + const ImGuiIO& io = ImGui::GetIO(); + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true); + ImVec2 internal_frame_size(frame_size[0], frame_size[1]); + internal_frame_size = ImGui::CalcItemSize(internal_frame_size, ImGui::CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + internal_frame_size); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, 0, &frame_bb)) + return -1; + + const bool hovered = ImGui::ItemHoverable(frame_bb, id); + + ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + static const int values_count_min = 2; + const int values_count = static_cast(data.size()); + int idx_hovered = -1; + + const ImVec2 offset(10.0f, 0.0f); + + const float size_y = y_range.second - y_range.first; + const float size_x = data.back().pos - data.front().pos; + if (size_x > 0.0f && values_count >= values_count_min) { + const float inv_scale_y = (size_y == 0.0f) ? 0.0f : 1.0f / size_y; + const float inv_scale_x = 1.0f / size_x; + const float x0 = data.front().pos; + const float y0 = y_range.first; + + const ImU32 grid_main_color = ImGui::GetColorU32(ImVec4(0.5f, 0.5f, 0.5f, 0.5f)); + const ImU32 grid_secondary_color = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.5f, 0.5f)); + + // horizontal levels + for (const auto& [level, color] : levels) { + const float y = 1.0f - ImSaturate((level - y_range.first) * inv_scale_y); + + window->DrawList->AddLine(ImLerp(inner_bb.Min, ImVec2(inner_bb.Min.x + offset.x, inner_bb.Max.y), ImVec2(0.1f, y)), + ImLerp(inner_bb.Min, ImVec2(inner_bb.Min.x + offset.x, inner_bb.Max.y), ImVec2(0.9f, y)), ImGuiPSWrap::to_ImU32(color), 3.0f); + + window->DrawList->AddLine(ImLerp(inner_bb.Min + offset, inner_bb.Max, ImVec2(0.0f, y)), + ImLerp(inner_bb.Min + offset, inner_bb.Max, ImVec2(1.0f, y)), grid_main_color); + } + + // vertical positions + for (int n = 0; n < values_count - 1; ++n) { + const float x = ImSaturate((data[n].pos - x0) * inv_scale_x); + window->DrawList->AddLine(ImLerp(inner_bb.Min + offset, inner_bb.Max, ImVec2(x, 0.0f)), + ImLerp(inner_bb.Min + offset, inner_bb.Max, ImVec2(x, 1.0f)), data[n].internal ? grid_secondary_color : grid_main_color); + } + window->DrawList->AddLine(ImLerp(inner_bb.Min + offset, inner_bb.Max, ImVec2(1.0f, 0.0f)), + ImLerp(inner_bb.Min + offset, inner_bb.Max, ImVec2(1.0f, 1.0f)), grid_main_color); + + // profiile + const ImU32 col_base = ImGui::GetColorU32(ImVec4(0.8f, 0.8f, 0.8f, 1.0f)); + const ImU32 col_hovered = ImGui::GetColorU32(ImGuiCol_PlotLinesHovered); + for (int n = 0; n < values_count - 1; ++n) { + const ImVec2 tp1(ImSaturate((data[n].pos - x0) * inv_scale_x), 1.0f - ImSaturate((data[n].speed - y0) * inv_scale_y)); + const ImVec2 tp2(ImSaturate((data[n + 1].pos - x0) * inv_scale_x), 1.0f - ImSaturate((data[n + 1].speed - y0) * inv_scale_y)); + // Tooltip on hover + if (hovered && inner_bb.Contains(io.MousePos)) { + const float t = ImClamp((io.MousePos.x - inner_bb.Min.x - offset.x) / (inner_bb.Max.x - inner_bb.Min.x - offset.x), 0.0f, 0.9999f); + if (tp1.x < t && t < tp2.x) + idx_hovered = n; + } + window->DrawList->AddLine(ImLerp(inner_bb.Min + offset, inner_bb.Max, tp1), ImLerp(inner_bb.Min + offset, inner_bb.Max, tp2), + idx_hovered == n ? col_hovered : col_base, 2.0f); } } - const size_t color_max_idx = Range_Colors.size() - 1; + if (label_size.x > 0.0f) + ImGui::RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); - // Compute the two colors just below (low) and above (high) the input value - const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); - const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); - - // Interpolate between the low and high colors to find exactly which color the input value should get - return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast(color_low_idx)); -} - -GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { - if (ibo > 0) - glsafe(::glDeleteBuffers(1, &ibo)); -} - -void GCodeViewer::SequentialRangeCap::reset() { - if (ibo > 0) - glsafe(::glDeleteBuffers(1, &ibo)); - - buffer = nullptr; - ibo = 0; -#if ENABLE_GL_CORE_PROFILE - vao = 0; -#endif // ENABLE_GL_CORE_PROFILE - vbo = 0; - color = { 0.0f, 0.0f, 0.0f, 1.0f }; + return idx_hovered; } +#endif // ENABLE_ACTUAL_SPEED_DEBUG void GCodeViewer::SequentialView::Marker::init() { @@ -325,7 +239,12 @@ void GCodeViewer::SequentialView::Marker::render(EViewType &view_type) shader->set_uniform("emission_factor", 0.0f); const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d& view_matrix = camera.get_view_matrix(); - const Transform3d model_matrix = m_world_transform.cast(); + float scale_factor = m_scale_factor; + if (m_fixed_screen_size) + scale_factor *= 10.0f * camera.get_inv_zoom(); + const Transform3d model_matrix = (Geometry::translation_transform((m_world_position + m_model_z_offset * Vec3f::UnitZ()).cast()) * + Geometry::translation_transform(scale_factor * m_model.get_bounding_box().size().z() * Vec3d::UnitZ()) * Geometry::rotation_transform({ M_PI, 0.0, 0.0 })) * + Geometry::scale_transform(scale_factor); shader->set_uniform("view_model_matrix", view_matrix * model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); @@ -336,67 +255,278 @@ void GCodeViewer::SequentialView::Marker::render(EViewType &view_type) shader->stop_using(); glsafe(::glDisable(GL_BLEND)); +} +static std::string to_string(libvgcode::EMoveType type) +{ + switch (type) + { + // TRN: Following strings are labels in the G-code Viewer legend. + case libvgcode::EMoveType::Noop: { return ("Noop"); } + case libvgcode::EMoveType::Retract: { return _u8L("Retract"); } + case libvgcode::EMoveType::Unretract: { return _u8L("Unretract"); } + case libvgcode::EMoveType::Seam: { return _u8L("Seam"); } + case libvgcode::EMoveType::ToolChange: { return _u8L("Tool Change"); } + case libvgcode::EMoveType::ColorChange: { return _u8L("Color Change"); } + case libvgcode::EMoveType::PausePrint: { return _u8L("Pause Print"); } + case libvgcode::EMoveType::CustomGCode: { return _u8L("Custom G-code"); } + case libvgcode::EMoveType::Travel: { return _u8L("Travel"); } + case libvgcode::EMoveType::Wipe: { return _u8L("Wipe"); } + case libvgcode::EMoveType::Extrude: { return _u8L("Extrude"); } + default: { return _u8L("Unknown"); } + } +} + +static std::string to_string(libvgcode::EGCodeExtrusionRole role) +{ + switch (role) + { + // TRN: Following strings are labels in the G-code Viewer legend. + case libvgcode::EGCodeExtrusionRole::None: { return _u8L("Unknown"); } + case libvgcode::EGCodeExtrusionRole::Perimeter: { return _u8L("Perimeter"); } + case libvgcode::EGCodeExtrusionRole::ExternalPerimeter: { return _u8L("External perimeter"); } + case libvgcode::EGCodeExtrusionRole::OverhangPerimeter: { return _u8L("Overhang perimeter"); } + case libvgcode::EGCodeExtrusionRole::InternalInfill: { return _u8L("Internal infill"); } + case libvgcode::EGCodeExtrusionRole::SolidInfill: { return _u8L("Solid infill"); } + case libvgcode::EGCodeExtrusionRole::TopSolidInfill: { return _u8L("Top solid infill"); } + case libvgcode::EGCodeExtrusionRole::Ironing: { return _u8L("Ironing"); } + case libvgcode::EGCodeExtrusionRole::BridgeInfill: { return _u8L("Bridge infill"); } + case libvgcode::EGCodeExtrusionRole::GapFill: { return _u8L("Gap fill"); } + case libvgcode::EGCodeExtrusionRole::Skirt: { return _u8L("Skirt/Brim"); } + case libvgcode::EGCodeExtrusionRole::SupportMaterial: { return _u8L("Support material"); } + case libvgcode::EGCodeExtrusionRole::SupportMaterialInterface: { return _u8L("Support material interface"); } + case libvgcode::EGCodeExtrusionRole::WipeTower: { return _u8L("Wipe tower"); } + case libvgcode::EGCodeExtrusionRole::Custom: { return _u8L("Custom"); } + default: { return _u8L("Unknown"); } + } +} + +void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode::Viewer* viewer) +{ static float last_window_width = 0.0f; static size_t last_text_length = 0; + static bool properties_shown = false; - ImGuiWrapper& imgui = *wxGetApp().imgui(); - const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.25f); - imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - //B18 - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Tool position") + ":"); - ImGui::SameLine(); - char buf[1024]; - const Vec3f position = m_world_position + m_world_offset; - //B43 - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); - switch (view_type) { - case EViewType::Height: { - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f, Height: %.2f mm", position.x(), position.y(), position.z(), m_curr_move.height); - break; - } - case EViewType::Width: { - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f, Width: %.2f mm", position.x(), position.y(), position.z(), m_curr_move.width); - break; - } - case EViewType::Feedrate: { - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f, Speed: %.0f mm/s", position.x(), position.y(), position.z(), m_curr_move.feedrate); - break; - } - case EViewType::VolumetricRate: { - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f, Flow: %.2f mm³/s", position.x(), position.y(), position.z(), m_curr_move.volumetric_rate()); - break; - } - case EViewType::FanSpeed: { - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f, Fan Speed: %.0f %%", position.x(), position.y(), position.z(), m_curr_move.fan_speed); - break; - } - case EViewType::Temperature: { - sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f, Temperature: %.0f °C", position.x(), position.y(), position.z(), m_curr_move.temperature); - break; - } - } + if (viewer != nullptr) { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const ImGuiViewport& viewport = *ImGui::GetMainViewport(); - imgui.text(std::string(buf)); + Preview* preview = dynamic_cast(wxGetApp().plater()->get_current_canvas3D()->get_wxglcanvas_parent()); + assert(preview); - // force extra frame to automatically update window size - const float width = ImGui::GetWindowWidth(); - const size_t length = strlen(buf); - if (width != last_window_width || length != last_text_length) { - last_window_width = width; - last_text_length = length; - imgui.set_requires_extra_frame(); + ImGuiPureWrap::set_next_window_pos(viewport.GetCenter().x, viewport.Size.y - preview->get_moves_slider_height(), ImGuiCond_Always, 0.5f, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(properties_shown ? 0.8f : 0.25f); + ImGuiPureWrap::begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing); + ImGui::AlignTextToFramePadding(); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Position") + ":"); + ImGui::SameLine(); + libvgcode::PathVertex vertex = viewer->get_current_vertex(); + size_t vertex_id = viewer->get_current_vertex_id(); + if (vertex.type == libvgcode::EMoveType::Seam) { + vertex_id = static_cast(viewer->get_view_visible_range()[1]) - 1; + vertex = viewer->get_vertex_at(vertex_id); + } + + char buf[1024]; + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", vertex.position[0], vertex.position[1], vertex.position[2]); + ImGuiPureWrap::text(std::string(buf)); + + ImGui::SameLine(); + // TRN: Show/hide properties is a tooltip on a button which toggles an extra window in the G-code Viewer, showing properties of current G-code segment. + if (imgui.image_button(properties_shown ? ImGui::HorizontalHide : ImGui::HorizontalShow, properties_shown ? _u8L("Hide properties") : _u8L("Show properties"))) { + properties_shown = !properties_shown; + imgui.requires_extra_frame(); + } + + if (properties_shown) { + auto append_table_row = [](const std::string& label, std::function value_callback) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, label); + ImGui::TableSetColumnIndex(1); + value_callback(); + }; + + ImGui::Separator(); + if (ImGui::BeginTable("Properties", 2)) { + char buff[1024]; + + append_table_row(_u8L("Type"), [&vertex]() { + ImGuiPureWrap::text(_u8L(to_string(vertex.type))); + }); + append_table_row(_u8L("Feature type"), [&vertex]() { + std::string text; + if (vertex.is_extrusion()) + text = _u8L(to_string(vertex.role)); + else + text = _u8L("N/A"); + ImGuiPureWrap::text(text); + }); + append_table_row(_u8L("Width") + " (" + _u8L("mm") + ")", [&vertex, &buff]() { + std::string text; + if (vertex.is_extrusion()) { + sprintf(buff, "%.3f", vertex.width); + text = std::string(buff); + } + else + text = _u8L("N/A"); + ImGuiPureWrap::text(text); + }); + append_table_row(_u8L("Height") + " (" + _u8L("mm") + ")", [&vertex, &buff]() { + std::string text; + if (vertex.is_extrusion()) { + sprintf(buff, "%.3f", vertex.height); + text = std::string(buff); + } + else + text = _u8L("N/A"); + ImGuiPureWrap::text(text); + }); + append_table_row(_u8L("Layer"), [&vertex, &buff]() { + sprintf(buff, "%d", vertex.layer_id + 1); + const std::string text = std::string(buff); + ImGuiPureWrap::text(text); + }); + append_table_row(_u8L("Speed") + " (" + _u8L("mm/s") + ")", [&vertex, &buff]() { + std::string text; + if (vertex.is_extrusion()) { + sprintf(buff, "%.1f", vertex.feedrate); + text = std::string(buff); + } + else + text = _u8L("N/A"); + ImGuiPureWrap::text(text); + }); + append_table_row(_u8L("Volumetric flow rate") + " (" + _u8L("mm³/s") + ")", [&vertex, &buff]() { + std::string text; + if (vertex.is_extrusion()) { + sprintf(buff, "%.3f", vertex.volumetric_rate()); + text = std::string(buff); + } + else + text = _u8L("N/A"); + ImGuiPureWrap::text(text); + }); + append_table_row(_u8L("Fan speed") + " (" + _u8L("%") + ")", [&vertex, &buff]() { + std::string text; + if (vertex.is_extrusion()) { + sprintf(buff, "%.0f", vertex.fan_speed); + text = std::string(buff); + } + else + text = _u8L("N/A"); + ImGuiPureWrap::text(text); + }); + append_table_row(_u8L("Temperature") + " (" + _u8L("°C") + ")", [&vertex, &buff]() { + sprintf(buff, "%.0f", vertex.temperature); + ImGuiPureWrap::text(std::string(buff)); + }); + append_table_row(_u8L("Time"), [viewer, &vertex, &buff, vertex_id]() { + const float estimated_time = viewer->get_estimated_time_at(vertex_id); + sprintf(buff, "%s (%.3fs)", get_time_dhms(estimated_time).c_str(), vertex.times[static_cast(viewer->get_time_mode())]); + const std::string text = std::string(buff); + ImGuiPureWrap::text(text); + }); + + ImGui::EndTable(); + } + +#if ENABLE_ACTUAL_SPEED_DEBUG + if (vertex.is_extrusion() || vertex.is_travel() || vertex.is_wipe()) { + ImGui::Spacing(); + ImGuiPureWrap::text(_u8L("Actual speed profile")); + ImGui::SameLine(); + static bool table_shown = false; + if (ImGuiPureWrap::button(table_shown ? _u8L("Hide table") : _u8L("Show table"))) + table_shown = !table_shown; + ImGui::Separator(); + const int hover_id = m_actual_speed_imgui_widget.plot("##ActualSpeedProfile", { -1.0f, 150.0f }); + if (table_shown) { + static float table_wnd_height = 0.0f; + const ImVec2 wnd_size = ImGui::GetWindowSize(); + ImGuiPureWrap::set_next_window_pos(ImGui::GetWindowPos().x + wnd_size.x, viewport.Size.y - preview->get_moves_slider_height(), ImGuiCond_Always, 0.0f, 1.0f); + ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, wnd_size.y }); + ImGuiPureWrap::begin(std::string("ToolPositionTableWnd"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); + if (ImGui::BeginTable("ToolPositionTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY)) { + char buff[1024]; + ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible + ImGui::TableSetupColumn("Position (mm)"); + ImGui::TableSetupColumn("Speed (mm/s)"); + ImGui::TableHeadersRow(); + int counter = 0; + for (const ActualSpeedImguiWidget::Item& item : m_actual_speed_imgui_widget.data) { + const bool highlight = hover_id >= 0 && (counter == hover_id || counter == hover_id + 1); + if (highlight && counter == hover_id) + ImGui::SetScrollHereY(); + ImGui::TableNextRow(); + const ImU32 row_bg_color = ImGui::GetColorU32(item.internal ? ImVec4(0.0f, 0.0f, 0.5f, 0.25f) : ImVec4(0.5f, 0.5f, 0.5f, 0.25f)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color); + ImGui::TableSetColumnIndex(0); + sprintf(buff, "%.3f", item.pos); + ImGuiPureWrap::text_colored(highlight ? ImGuiPureWrap::COL_BLUE_LIGHT : ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE()), buff); + ImGui::TableSetColumnIndex(1); + sprintf(buff, "%.1f", item.speed); + ImGuiPureWrap::text_colored(highlight ? ImGuiPureWrap::COL_BLUE_LIGHT : ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE()), buff); + ++counter; + } + ImGui::EndTable(); + } + const float curr_table_wnd_height = ImGui::GetWindowHeight(); + if (table_wnd_height != curr_table_wnd_height) { + table_wnd_height = curr_table_wnd_height; + // require extra frame to hide the table scroll bar (bug in imgui) + imgui.set_requires_extra_frame(); + } + ImGuiPureWrap::end(); + } + } +#endif // ENABLE_ACTUAL_SPEED_DEBUG + } + + // force extra frame to automatically update window size + const float width = ImGui::GetWindowWidth(); + const size_t length = strlen(buf); + if (width != last_window_width || length != last_text_length) { + last_window_width = width; + last_text_length = length; + imgui.set_requires_extra_frame(); + } + + ImGuiPureWrap::end(); + ImGui::PopStyleVar(); } + else { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + ImGuiPureWrap::set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.25f); + ImGuiPureWrap::begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Tool position") + ":"); + ImGui::SameLine(); + char buf[1024]; + const Vec3f position = m_world_position + m_world_offset + m_z_offset * Vec3f::UnitZ(); + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); + ImGuiPureWrap::text(std::string(buf)); - imgui.end(); - ImGui::PopStyleVar(); + // force extra frame to automatically update window size + const float width = ImGui::GetWindowWidth(); + const size_t length = strlen(buf); + if (width != last_window_width || length != last_text_length) { + last_window_width = width; + last_text_length = length; + imgui.set_requires_extra_frame(); + } + + ImGuiPureWrap::end(); + ImGui::PopStyleVar(); + } } void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const GCodeProcessorResult& gcode_result) - { +{ m_filename = gcode_result.filename; m_is_binary_file = gcode_result.is_binary_file; m_lines_ends = gcode_result.lines_ends; @@ -404,28 +534,28 @@ void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const GCodeProcessorRe void GCodeViewer::SequentialView::GCodeWindow::add_gcode_line_to_lines_cache(const std::string& src) { + std::string command; + std::string parameters; + std::string comment; - std::string command; - std::string parameters; - std::string comment; - - // extract comment - std::vector tokens; + // extract comment + std::vector tokens; boost::split(tokens, src, boost::is_any_of(";"), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) - comment = ";" + tokens.back(); + command = tokens.front(); + if (tokens.size() > 1) + comment = ";" + tokens.back(); - // extract gcode command and parameters - if (!command.empty()) { - boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) { - for (size_t i = 1; i < tokens.size(); ++i) { - parameters += " " + tokens[i]; - } - } + // extract gcode command and parameters + if (!command.empty()) { + boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) { + for (size_t i = 1; i < tokens.size(); ++i) { + parameters += " " + tokens[i]; } + } + } + m_lines_cache.push_back({ command, parameters, comment }); } @@ -475,8 +605,8 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, s break; } } - size_t last_block_id = 0; - for (size_t i = 0; i < cumulative_lines_counts.size(); ++i) { + size_t last_block_id = first_block_id; + for (size_t i = last_block_id; i < cumulative_lines_counts.size(); ++i) { if (*m_cache_range.max <= cumulative_lines_counts[i]) { last_block_id = i; break; @@ -542,8 +672,9 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, s } } } + } } - } + assert(m_lines_cache.size() == m_cache_range.size()); }; //B18 static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_BLUE_LIGHT; @@ -583,6 +714,7 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, s range.min = *range.max - lines_count + 1; } }; + // visible range Range visible_range; resize_range(visible_range, visible_lines_count); @@ -597,14 +729,14 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, s } if (m_lines_cache.empty()) - return; + return; // line number's column width const float id_width = ImGui::CalcTextSize(std::to_string(*visible_range.max).c_str()).x; ImGuiWrapper& imgui = *wxGetApp().imgui(); - auto add_item_to_line = [&imgui](const std::string& txt, const ImVec4& color, float spacing, size_t& current_length) { + auto add_item_to_line = [](const std::string& txt, const ImVec4& color, float spacing, size_t& current_length) { static const size_t LENGTH_THRESHOLD = 60; if (txt.empty()) @@ -620,21 +752,21 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, s current_length += out_text.length(); ImGui::SameLine(0.0f, spacing); - imgui.text_colored(color, out_text); + ImGuiPureWrap::text_colored(color, out_text); if (reduced) { ImGui::SameLine(0.0f, 0.0f); - imgui.text_colored(ELLIPSIS_COLOR, "..."); + ImGuiPureWrap::text_colored(ELLIPSIS_COLOR, "..."); } return reduced; }; - imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); - imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); + ImGuiPureWrap::set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); + ImGuiPureWrap::set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); - imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - + ImGuiPureWrap::begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing); + // center the text in the window by pushing down the first line const float f_lines_count = static_cast(visible_lines_count); ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y)); @@ -670,11 +802,13 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, s if (!stop_adding && !line.comment.empty()) // render comment stop_adding = add_item_to_line(line.comment, COMMENT_COLOR, line.command.empty() ? -1.0f : 0.0f, line_length); + max_line_length = std::max(max_line_length, line_length); } - imgui.end(); + ImGuiPureWrap::end(); ImGui::PopStyleVar(); + // request an extra frame if window's width changed if (m_max_line_length != max_line_length) { m_max_line_length = max_line_length; @@ -684,97 +818,20 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, s //B43 void GCodeViewer::SequentialView::render(float legend_height, EViewType &view_type) { - marker.render(view_type); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + if (viewer == nullptr) +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + marker.render(); + marker.render_position_window(viewer); float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height(); if (wxGetApp().is_editor()) bottom -= wxGetApp().plater()->get_view_toolbar().get_height(); - gcode_window.render(legend_height, bottom, static_cast(gcode_ids[current.last])); + gcode_window.render(legend_height, bottom, gcode_id); } -const std::array(GCodeExtrusionRole::Count)> GCodeViewer::Extrusion_Role_Colors{ { - { 0.90f, 0.70f, 0.70f, 1.0f }, // GCodeExtrusionRole::None - { 1.00f, 0.90f, 0.30f, 1.0f }, // GCodeExtrusionRole::Perimeter - { 1.00f, 0.49f, 0.22f, 1.0f }, // GCodeExtrusionRole::ExternalPerimeter - { 0.12f, 0.12f, 1.00f, 1.0f }, // GCodeExtrusionRole::OverhangPerimeter - { 0.69f, 0.19f, 0.16f, 1.0f }, // GCodeExtrusionRole::InternalInfill - { 0.59f, 0.33f, 0.80f, 1.0f }, // GCodeExtrusionRole::SolidInfill - { 0.94f, 0.25f, 0.25f, 1.0f }, // GCodeExtrusionRole::TopSolidInfill - { 1.00f, 0.55f, 0.41f, 1.0f }, // GCodeExtrusionRole::Ironing - { 0.30f, 0.50f, 0.73f, 1.0f }, // GCodeExtrusionRole::BridgeInfill - { 1.00f, 1.00f, 1.00f, 1.0f }, // GCodeExtrusionRole::GapFill - { 0.00f, 0.53f, 0.43f, 1.0f }, // GCodeExtrusionRole::Skirt - { 0.00f, 1.00f, 0.00f, 1.0f }, // GCodeExtrusionRole::SupportMaterial - { 0.00f, 0.50f, 0.00f, 1.0f }, // GCodeExtrusionRole::SupportMaterialInterface - { 0.70f, 0.89f, 0.67f, 1.0f }, // GCodeExtrusionRole::WipeTower - { 0.37f, 0.82f, 0.58f, 1.0f }, // GCodeExtrusionRole::Custom -}}; - -const std::vector GCodeViewer::Options_Colors{ { - { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions - { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions - { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams - { 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges - { 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges - { 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints - { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes -}}; - -const std::vector GCodeViewer::Travel_Colors{ { - { 0.219f, 0.282f, 0.609f, 1.0f }, // Move - { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude - { 0.505f, 0.064f, 0.028f, 1.0f } // Retract -}}; - -#if 1 -// Normal ranges -const std::vector GCodeViewer::Range_Colors{ { - { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish - { 0.075f, 0.349f, 0.522f, 1.0f }, - { 0.110f, 0.533f, 0.569f, 1.0f }, - { 0.016f, 0.839f, 0.059f, 1.0f }, - { 0.667f, 0.949f, 0.000f, 1.0f }, - { 0.988f, 0.975f, 0.012f, 1.0f }, - { 0.961f, 0.808f, 0.039f, 1.0f }, - { 0.890f, 0.533f, 0.125f, 1.0f }, - { 0.820f, 0.408f, 0.188f, 1.0f }, - { 0.761f, 0.322f, 0.235f, 1.0f }, - { 0.581f, 0.149f, 0.087f, 1.0f } // reddish -}}; -#else -// Detailed ranges -const std::vector GCodeViewer::Range_Colors{ { - { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish - { 0.5f * (0.043f + 0.075f), 0.5f * (0.173f + 0.349f), 0.5f * (0.478f + 0.522f), 1.0f }, - { 0.075f, 0.349f, 0.522f, 1.0f }, - { 0.5f * (0.075f + 0.110f), 0.5f * (0.349f + 0.533f), 0.5f * (0.522f + 0.569f), 1.0f }, - { 0.110f, 0.533f, 0.569f, 1.0f }, - { 0.5f * (0.110f + 0.016f), 0.5f * (0.533f + 0.839f), 0.5f * (0.569f + 0.059f), 1.0f }, - { 0.016f, 0.839f, 0.059f, 1.0f }, - { 0.5f * (0.016f + 0.667f), 0.5f * (0.839f + 0.949f), 0.5f * (0.059f + 0.000f), 1.0f }, - { 0.667f, 0.949f, 0.000f, 1.0f }, - { 0.5f * (0.667f + 0.988f), 0.5f * (0.949f + 0.975f), 0.5f * (0.000f + 0.012f), 1.0f }, - { 0.988f, 0.975f, 0.012f, 1.0f }, - { 0.5f * (0.988f + 0.961f), 0.5f * (0.975f + 0.808f), 0.5f * (0.012f + 0.039f), 1.0f }, - { 0.961f, 0.808f, 0.039f, 1.0f }, - { 0.5f * (0.961f + 0.890f), 0.5f * (0.808f + 0.533f), 0.5f * (0.039f + 0.125f), 1.0f }, - { 0.890f, 0.533f, 0.125f, 1.0f }, - { 0.5f * (0.890f + 0.820f), 0.5f * (0.533f + 0.408f), 0.5f * (0.125f + 0.188f), 1.0f }, - { 0.820f, 0.408f, 0.188f, 1.0f }, - { 0.5f * (0.820f + 0.761f), 0.5f * (0.408f + 0.322f), 0.5f * (0.188f + 0.235f), 1.0f }, - { 0.761f, 0.322f, 0.235f, 1.0f }, - { 0.5f * (0.761f + 0.581f), 0.5f * (0.322f + 0.149f), 0.5f * (0.235f + 0.087f), 1.0f }, - { 0.581f, 0.149f, 0.087f, 1.0f } // reddishgit -} }; -#endif - -const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW(); -const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); - GCodeViewer::GCodeViewer() { - m_extrusions.reset_role_visibility_flags(); m_shells.volumes.set_use_raycasters(false); -// m_sequential_view.skip_invisible_moves = true; } void GCodeViewer::init() @@ -864,18 +921,17 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr //B43 m_gcode_result = &gcode_result; + m_extruders_count = gcode_result.extruders_count; m_sequential_view.gcode_window.load_gcode(gcode_result); - if (wxGetApp().is_gcode_viewer()) - m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; + m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; m_max_print_height = gcode_result.max_print_height; m_z_offset = gcode_result.z_offset; - load_toolpaths(gcode_result); load_wipetower_shell(print); - if (m_layers.empty()) + if (m_viewer.get_layers_count() == 0) return; m_settings_ids = gcode_result.settings_ids; @@ -928,92 +984,35 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr m_print_statistics = gcode_result.print_statistics; - if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { - const float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; + PrintEstimatedStatistics::ETimeMode time_mode = convert(m_viewer.get_time_mode()); + if (m_viewer.get_time_mode() != libvgcode::ETimeMode::Normal) { + const float time = m_print_statistics.modes[static_cast(time_mode)].time; if (time == 0.0f || short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) - m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; + m_viewer.set_time_mode(libvgcode::convert(PrintEstimatedStatistics::ETimeMode::Normal)); } m_conflict_result = gcode_result.conflict_result; - if (m_conflict_result.has_value()) { m_conflict_result->layer = m_layers.get_l_at(m_conflict_result->_height); } + if (m_conflict_result.has_value()) + m_conflict_result->layer = m_viewer.get_layer_id_at(static_cast(m_conflict_result->_height)); } -void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) +void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data) { -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS + m_loaded_as_preview = true; - if (m_moves_count == 0) - return; + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::Skirt, { 127, 255, 127 }); + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::ExternalPerimeter, { 255, 255, 0 }); + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::SupportMaterial, { 127, 255, 127 }); + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::SupportMaterialInterface, { 127, 255, 127 }); + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::InternalInfill, { 255, 127, 127 }); + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::SolidInfill, { 255, 127, 127 }); + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::WipeTower, { 127, 255, 127 }); + m_viewer.load(std::move(data)); - wxBusyCursor busy; - - if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) - // update tool colors from config stored in the gcode - decode_colors(gcode_result.extruder_colors, m_tool_colors); - else - // update tool colors - decode_colors(str_tool_colors, m_tool_colors); - - ColorRGBA default_color; - decode_color("#FF8000", default_color); - - // ensure there are enough colors defined - while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) - m_tool_colors.push_back(default_color); - - // update ranges for coloring / legend - m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_moves_count; ++i) { - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - - switch (curr.type) - { - case EMoveType::Extrude: - { - m_extrusions.ranges.height.update_from(round_to_bin(curr.height)); - if (curr.extrusion_role != GCodeExtrusionRole::Custom || is_visible(GCodeExtrusionRole::Custom)) - m_extrusions.ranges.width.update_from(round_to_bin(curr.width)); - m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); - m_extrusions.ranges.temperature.update_from(curr.temperature); - if (curr.extrusion_role != GCodeExtrusionRole::Custom || is_visible(GCodeExtrusionRole::Custom)) - m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate())); - [[fallthrough]]; - } - case EMoveType::Travel: - { - if (m_buffers[buffer_id(curr.type)].visible) - m_extrusions.ranges.feedrate.update_from(curr.feedrate); - - break; - } - default: { break; } - } - } - - for (size_t i = 0; i < gcode_result.print_statistics.modes.size(); ++i) { - m_layers_times[i] = gcode_result.print_statistics.modes[i].layers_times; - } - - for (size_t i = 0; i < m_layers_times.size(); ++i) { - for (float time : m_layers_times[i]) { - m_extrusions.ranges.layer_time[i].update_from(time); - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // update buffers' render paths - refresh_render_paths(false, false); - log_memory_used("Refreshed G-code extrusion paths, "); + const libvgcode::AABox bbox = m_viewer.get_extrusion_bounding_box(); + const BoundingBoxf3 paths_bounding_box(libvgcode::convert(bbox[0]).cast(), libvgcode::convert(bbox[1]).cast()); + m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(GCodeProcessorResult(), paths_bounding_box); } void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) @@ -1024,57 +1023,38 @@ void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* conf void GCodeViewer::reset() { - m_moves_count = 0; - for (TBuffer& buffer : m_buffers) { - buffer.reset(); - } + m_viewer.reset(); m_paths_bounding_box.reset(); m_max_bounding_box.reset(); m_max_print_height = 0.0f; m_z_offset = 0.0f; - m_tool_colors = std::vector(); - m_extruders_count = 0; - m_extruder_ids = std::vector(); m_filament_diameters = std::vector(); m_filament_densities = std::vector(); - m_extrusions.reset_ranges(); - m_layers.reset(); - m_layers_z_range = { 0, 0 }; - m_roles = std::vector(); + m_extruders_count = 0; m_print_statistics.reset(); - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - m_layers_times[i] = std::vector(); - } m_custom_gcode_per_print_z = std::vector(); m_sequential_view.gcode_window.reset(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.reset_all(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS m_contained_in_bed = true; m_legend_resizer.reset(); } void GCodeViewer::render() { -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.reset_opengl(); - m_statistics.total_instances_gpu_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - glsafe(::glEnable(GL_DEPTH_TEST)); render_shells(); - if (m_roles.empty()) + if (m_viewer.get_extrusion_roles().empty()) return; render_toolpaths(); + float legend_height = 0.0f; - if (!m_layers.empty()) { + if (m_viewer.get_layers_count() > 0) { render_legend(legend_height); - if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { - m_sequential_view.marker.set_world_position(m_sequential_view.current_position); - m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); + if (m_viewer.get_view_enabled_range()[1] != m_viewer.get_view_visible_range()[1]) { + const libvgcode::PathVertex& curr_vertex = m_viewer.get_current_vertex(); + m_sequential_view.marker.set_world_position(libvgcode::convert(curr_vertex.position)); m_sequential_view.marker.set_z_offset(m_z_offset); //B43 m_sequential_view.render(legend_height, m_view_type); @@ -1085,140 +1065,419 @@ void GCodeViewer::render() #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + if (is_legend_shown()) { + ImGuiWrapper& imgui = *Slic3r::GUI::wxGetApp().imgui(); + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + ImGuiPureWrap::set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); + ImGuiPureWrap::begin(std::string("LibVGCode Viewer Controller"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + + ImGuiPureWrap::checkbox("Cog marker fixed screen size", m_cog_marker_fixed_screen_size); + if (ImGui::BeginTable("Cog", 2)) { + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Cog marker size"); + ImGui::TableSetColumnIndex(1); + imgui.slider_float("##CogSize", &m_cog_marker_size, 1.0f, 5.0f); + + ImGui::EndTable(); + } + + ImGuiPureWrap::checkbox("Tool marker fixed screen size", m_tool_marker_fixed_screen_size); + if (ImGui::BeginTable("Tool", 2)) { + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Tool marker size"); + ImGui::TableSetColumnIndex(1); + imgui.slider_float("##ToolSize", &m_tool_marker_size, 1.0f, 5.0f); + + ImGui::EndTable(); + } + + ImGuiPureWrap::end(); + } +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS +} + bool GCodeViewer::can_export_toolpaths() const { - return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; + const libvgcode::Interval& visible_range = m_viewer.get_view_visible_range(); + for (size_t i = visible_range[0]; i <= visible_range[1]; ++i) { + if (m_viewer.get_vertex_at(i).is_extrusion()) + return true; + } + return false; } void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) { - auto is_visible = [this](unsigned int id) { - for (const TBuffer& buffer : m_buffers) { - if (buffer.visible) { - for (const Path& path : buffer.paths) { - if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) - return true; - } + m_viewer.set_view_visible_range(static_cast(first), static_cast(last)); + const libvgcode::Interval& enabled_range = m_viewer.get_view_enabled_range(); + wxGetApp().plater()->enable_preview_moves_slider(enabled_range[1] > enabled_range[0]); + +#if ENABLE_ACTUAL_SPEED_DEBUG + if (enabled_range[1] != m_viewer.get_view_visible_range()[1]) { + const libvgcode::PathVertex& curr_vertex = m_viewer.get_current_vertex(); + if (curr_vertex.is_extrusion() || curr_vertex.is_travel() || curr_vertex.is_wipe() || + curr_vertex.type == libvgcode::EMoveType::Seam) { + const libvgcode::ColorRange& color_range = m_viewer.get_color_range(libvgcode::EViewType::ActualSpeed); + const std::array& interval = color_range.get_range(); + const size_t vertices_count = m_viewer.get_vertices_count(); + std::vector actual_speed_data; + // collect vertices sharing the same gcode_id + const size_t curr_id = m_viewer.get_current_vertex_id(); + size_t start_id = curr_id; + while (start_id > 0) { + --start_id; + if (curr_vertex.gcode_id != m_viewer.get_vertex_at(start_id).gcode_id) + break; + } + size_t end_id = curr_id; + while (end_id < vertices_count - 1) { + ++end_id; + if (curr_vertex.gcode_id != m_viewer.get_vertex_at(end_id).gcode_id) + break; } - } - return false; - }; - const int first_diff = static_cast(first) - static_cast(m_sequential_view.last_current.first); - const int last_diff = static_cast(last) - static_cast(m_sequential_view.last_current.last); + if (m_viewer.get_vertex_at(end_id - 1).type == libvgcode::convert(EMoveType::Seam)) + --end_id; - unsigned int new_first = first; - unsigned int new_last = last; + assert(end_id - start_id >= 2); - if (m_sequential_view.skip_invisible_moves) { - while (!is_visible(new_first)) { - if (first_diff > 0) - ++new_first; - else - --new_first; - } + float total_len = 0.0f; + for (size_t i = start_id; i < end_id; ++i) { + const libvgcode::PathVertex& v = m_viewer.get_vertex_at(i); + const float len = (i > start_id) ? + (libvgcode::convert(v.position) - libvgcode::convert(m_viewer.get_vertex_at(i - 1).position)).norm() : 0.0f; + total_len += len; + if (i == start_id || len > EPSILON) + actual_speed_data.push_back({ total_len, v.actual_feedrate, v.times[0] == 0.0f }); + } - while (!is_visible(new_last)) { - if (last_diff > 0) - ++new_last; - else - --new_last; + std::vector> levels; + const std::vector values = color_range.get_values(); + for (float value : values) { + levels.push_back(std::make_pair(value, libvgcode::convert(color_range.get_color_at(value)))); + levels.back().second.a(0.5f); + } + + m_sequential_view.marker.set_actual_speed_data(actual_speed_data); + m_sequential_view.marker.set_actual_speed_y_range(std::make_pair(interval[0], interval[1])); + m_sequential_view.marker.set_actual_speed_levels(levels); } } - - m_sequential_view.current.first = new_first; - m_sequential_view.current.last = new_last; - m_sequential_view.last_current = m_sequential_view.current; - - refresh_render_paths(true, true); - - if (new_first != first || new_last != last) - wxGetApp().plater()->update_preview_moves_slider(); - - update_marker_curr_move(); -} - -//B43 -void GCodeViewer::update_marker_curr_move() -{ - if ((int) m_last_result_id != -1) { - auto it = std::find_if(m_gcode_result->moves.begin(), m_gcode_result->moves.end(), [this](auto move) { - if (m_sequential_view.current.last < m_sequential_view.gcode_ids.size() && m_sequential_view.current.last >= 0) { - return move.gcode_id == static_cast(m_sequential_view.gcode_ids[m_sequential_view.current.last]); - } - return false; - }); - if (it != m_gcode_result->moves.end()) - m_sequential_view.marker.update_curr_move(*it); - } - - -} - -bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const -{ - size_t id = static_cast(buffer_id(type)); - return (id < m_buffers.size()) ? m_buffers[id].visible : false; -} - -void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible) -{ - size_t id = static_cast(buffer_id(type)); - if (id < m_buffers.size()) - m_buffers[id].visible = visible; -} - -unsigned int GCodeViewer::get_options_visibility_flags() const -{ - auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { - return active ? (flags | (1 << flag)) : flags; - }; - - unsigned int flags = 0; - flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel)); - flags = set_flag(flags, static_cast(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe)); - flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract)); - flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract)); - flags = set_flag(flags, static_cast(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam)); - flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); - flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); - flags = set_flag(flags, static_cast(Preview::OptionType::CenterOfGravity), m_cog.is_visible()); - flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); - flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); - return flags; -} - -void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) -{ - auto is_flag_set = [flags](unsigned int flag) { - return (flags & (1 << flag)) != 0; - }; - - set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); - set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast(Preview::OptionType::Wipe))); - set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); - set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); - set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast(Preview::OptionType::Seams))); - set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); - set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); - set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); - set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); - m_cog.set_visible(is_flag_set(static_cast(Preview::OptionType::CenterOfGravity))); - m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); - m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); +#endif // ENABLE_ACTUAL_SPEED_DEBUG } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) { - bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0]; - bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1]; - m_layers_z_range = layers_z_range; - refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); + m_viewer.set_layers_view_range(static_cast(layers_z_range[0]), static_cast(layers_z_range[1])); wxGetApp().plater()->update_preview_moves_slider(); } +class ToolpathsObjExporter +{ +public: + explicit ToolpathsObjExporter(const libvgcode::Viewer& viewer) + : m_viewer(viewer) { + } + + void export_to(const std::string& filename) { + CNumericLocalesSetter locales_setter; + + // open geometry file + FilePtr f_geo = boost::nowide::fopen(filename.c_str(), "w"); + if (f_geo.f == nullptr) { + BOOST_LOG_TRIVIAL(error) << "ToolpathsObjExporter: Couldn't open " << filename << " for writing"; + return; + } + + boost::filesystem::path materials_filename(filename); + materials_filename.replace_extension("mtl"); + + // write header to geometry file + fprintf(f_geo.f, "# G-Code Toolpaths\n"); + fprintf(f_geo.f, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); + fprintf(f_geo.f, "\nmtllib ./%s\n", materials_filename.filename().string().c_str()); + + // open material file + FilePtr f_mat = boost::nowide::fopen(materials_filename.string().c_str(), "w"); + if (f_mat.f == nullptr) { + BOOST_LOG_TRIVIAL(error) << "ToolpathsObjExporter: Couldn't open " << materials_filename.string() << " for writing"; + return; + } + + // write header to material file + fprintf(f_mat.f, "# G-Code Toolpaths Materials\n"); + fprintf(f_mat.f, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); + + libvgcode::Interval visible_range = m_viewer.get_view_visible_range(); + if (m_viewer.is_top_layer_only_view_range()) + visible_range[0] = m_viewer.get_view_full_range()[0]; + for (size_t i = visible_range[0]; i <= visible_range[1]; ++i) { + const libvgcode::PathVertex& curr = m_viewer.get_vertex_at(i); + const libvgcode::PathVertex& next = m_viewer.get_vertex_at(i + 1); + if (!curr.is_extrusion() || !next.is_extrusion()) + continue; + const libvgcode::PathVertex& nextnext = m_viewer.get_vertex_at(i + 2); + unsigned char flags = 0; + if (curr.gcode_id == next.gcode_id) + flags |= Flag_First; + if (i + 1 == visible_range[1] || !nextnext.is_extrusion()) + flags |= Flag_Last; + else + flags |= Flag_Internal; + export_segment(*f_geo.f, flags, i, curr, next, nextnext); + } + export_materials(*f_mat.f); + } + +private: + const libvgcode::Viewer& m_viewer; + size_t m_vertices_count{ 0 }; + std::vector m_colors; + static const unsigned char Flag_First = 0x01; + static const unsigned char Flag_Last = 0x02; + static const unsigned char Flag_Internal = 0x04; + static const float Cap_Rounding_Factor; + + struct SegmentLocalAxes + { + Vec3f forward; + Vec3f right; + Vec3f up; + }; + + SegmentLocalAxes segment_local_axes(const Vec3f& v1, const Vec3f& v2) { + SegmentLocalAxes ret; + ret.forward = (v2 - v1).normalized(); + ret.right = ret.forward.cross(Vec3f::UnitZ()).normalized(); + ret.up = ret.right.cross(ret.forward); + return ret; + } + + struct Vertex + { + Vec3f position; + Vec3f normal; + }; + + struct CrossSection + { + Vertex right; + Vertex top; + Vertex left; + Vertex bottom; + }; + + CrossSection cross_section(const Vec3f& v, const Vec3f& right, const Vec3f& up, float width, float height) { + CrossSection ret; + const Vec3f w_shift = 0.5f * width * right; + const Vec3f h_shift = 0.5f * height * up; + ret.right.position = v + w_shift; + ret.right.normal = right; + ret.top.position = v + h_shift; + ret.top.normal = up; + ret.left.position = v - w_shift; + ret.left.normal = -right; + ret.bottom.position = v - h_shift; + ret.bottom.normal = -up; + return ret; + } + + CrossSection normal_cross_section(const Vec3f& v, const SegmentLocalAxes& axes, float width, float height) { + return cross_section(v, axes.right, axes.up, width, height); + } + + enum CornerType : unsigned char + { + RightTurn, + LeftTurn, + Straight + }; + + CrossSection corner_cross_section(const Vec3f& v, const SegmentLocalAxes& axes1, const SegmentLocalAxes& axes2, + float width, float height, CornerType& corner_type) { + if (std::abs(std::abs(axes1.forward.dot(axes2.forward)) - 1.0f) < EPSILON) + corner_type = CornerType::Straight; + else if (axes1.up.dot(axes1.forward.cross(axes2.forward)) < 0.0f) + corner_type = CornerType::RightTurn; + else + corner_type = CornerType::LeftTurn; + const Vec3f right = (0.5f * (axes1.right + axes2.right)).normalized(); + return cross_section(v, right, axes1.up, width, height); + } + + void export_segment(FILE& f, unsigned char flags, size_t v1_id, const libvgcode::PathVertex& v1, const libvgcode::PathVertex& v2, const libvgcode::PathVertex& v3) { + const Vec3f v1_pos = libvgcode::convert(v1.position); + const Vec3f v2_pos = libvgcode::convert(v2.position); + const Vec3f v3_pos = libvgcode::convert(v3.position); + const SegmentLocalAxes v1_v2 = segment_local_axes(v1_pos, v2_pos); + const SegmentLocalAxes v2_v3 = segment_local_axes(v2_pos, v3_pos); + + // starting cap + if ((flags & Flag_First) > 0) { + const Vertex v0 = { v1_pos - Cap_Rounding_Factor * v1.width * v1_v2.forward, -v1_v2.forward }; + const CrossSection ncs = normal_cross_section(v1_pos, v1_v2, v1.width, v1.height); + export_vertex(f, v0); // 0 + export_vertex(f, ncs.right); // 1 + export_vertex(f, ncs.top); // 2 + export_vertex(f, ncs.left); // 3 + export_vertex(f, ncs.bottom); // 4 + export_material(f, color_id(v1_id)); + export_triangle(f, vertex_id(0), vertex_id(1), vertex_id(2)); + export_triangle(f, vertex_id(0), vertex_id(2), vertex_id(3)); + export_triangle(f, vertex_id(0), vertex_id(3), vertex_id(4)); + export_triangle(f, vertex_id(0), vertex_id(4), vertex_id(1)); + m_vertices_count += 5; + } + // segment body + ending cap + if ((flags & Flag_Last) > 0) { + const Vertex v0 = { v2_pos + Cap_Rounding_Factor * v2.width * v1_v2.forward, v1_v2.forward }; + const CrossSection ncs = normal_cross_section(v2_pos, v1_v2, v2.width, v2.height); + export_vertex(f, v0); // 0 + export_vertex(f, ncs.right); // 1 + export_vertex(f, ncs.top); // 2 + export_vertex(f, ncs.left); // 3 + export_vertex(f, ncs.bottom); // 4 + export_material(f, color_id(v1_id + 1)); + // segment body + export_triangle(f, vertex_id(-4), vertex_id(1), vertex_id(2)); + export_triangle(f, vertex_id(-4), vertex_id(2), vertex_id(-3)); + export_triangle(f, vertex_id(-3), vertex_id(2), vertex_id(3)); + export_triangle(f, vertex_id(-3), vertex_id(3), vertex_id(-2)); + export_triangle(f, vertex_id(-2), vertex_id(3), vertex_id(4)); + export_triangle(f, vertex_id(-2), vertex_id(4), vertex_id(-1)); + export_triangle(f, vertex_id(-1), vertex_id(4), vertex_id(1)); + export_triangle(f, vertex_id(-1), vertex_id(1), vertex_id(-4)); + // ending cap + export_triangle(f, vertex_id(0), vertex_id(3), vertex_id(2)); + export_triangle(f, vertex_id(0), vertex_id(2), vertex_id(1)); + export_triangle(f, vertex_id(0), vertex_id(1), vertex_id(4)); + export_triangle(f, vertex_id(0), vertex_id(4), vertex_id(3)); + m_vertices_count += 5; + } + else { + CornerType corner_type; + const CrossSection ccs = corner_cross_section(v2_pos, v1_v2, v2_v3, v2.width, v2.height, corner_type); + const CrossSection ncs12 = normal_cross_section(v2_pos, v1_v2, v2.width, v2.height); + const CrossSection ncs23 = normal_cross_section(v2_pos, v2_v3, v2.width, v2.height); + if (corner_type == CornerType::Straight) { + export_vertex(f, ncs12.right); // 0 + export_vertex(f, ncs12.top); // 1 + export_vertex(f, ncs12.left); // 2 + export_vertex(f, ncs12.bottom); // 3 + export_material(f, color_id(v1_id + 1)); + // segment body + export_triangle(f, vertex_id(-4), vertex_id(0), vertex_id(1)); + export_triangle(f, vertex_id(-4), vertex_id(1), vertex_id(-3)); + export_triangle(f, vertex_id(-3), vertex_id(1), vertex_id(2)); + export_triangle(f, vertex_id(-3), vertex_id(2), vertex_id(-2)); + export_triangle(f, vertex_id(-2), vertex_id(2), vertex_id(3)); + export_triangle(f, vertex_id(-2), vertex_id(3), vertex_id(-1)); + export_triangle(f, vertex_id(-1), vertex_id(3), vertex_id(0)); + export_triangle(f, vertex_id(-1), vertex_id(0), vertex_id(-4)); + m_vertices_count += 4; + } + else if (corner_type == CornerType::RightTurn) { + export_vertex(f, ncs12.left); // 0 + export_vertex(f, ccs.left); // 1 + export_vertex(f, ccs.right); // 2 + export_vertex(f, ncs12.top); // 3 + export_vertex(f, ncs23.left); // 4 + export_vertex(f, ncs12.bottom); // 5 + export_material(f, color_id(v1_id + 1)); + // segment body + export_triangle(f, vertex_id(-4), vertex_id(2), vertex_id(3)); + export_triangle(f, vertex_id(-4), vertex_id(3), vertex_id(-3)); + export_triangle(f, vertex_id(-3), vertex_id(3), vertex_id(0)); + export_triangle(f, vertex_id(-3), vertex_id(0), vertex_id(-2)); + export_triangle(f, vertex_id(-2), vertex_id(0), vertex_id(5)); + export_triangle(f, vertex_id(-2), vertex_id(5), vertex_id(-1)); + export_triangle(f, vertex_id(-1), vertex_id(5), vertex_id(2)); + export_triangle(f, vertex_id(-1), vertex_id(2), vertex_id(-4)); + // corner + export_triangle(f, vertex_id(1), vertex_id(0), vertex_id(3)); + export_triangle(f, vertex_id(1), vertex_id(3), vertex_id(4)); + export_triangle(f, vertex_id(1), vertex_id(4), vertex_id(5)); + export_triangle(f, vertex_id(1), vertex_id(5), vertex_id(0)); + m_vertices_count += 6; + } + else { + export_vertex(f, ncs12.right); // 0 + export_vertex(f, ccs.right); // 1 + export_vertex(f, ncs23.right); // 2 + export_vertex(f, ncs12.top); // 3 + export_vertex(f, ccs.left); // 4 + export_vertex(f, ncs12.bottom); // 5 + export_material(f, color_id(v1_id + 1)); + // segment body + export_triangle(f, vertex_id(-4), vertex_id(0), vertex_id(3)); + export_triangle(f, vertex_id(-4), vertex_id(3), vertex_id(-3)); + export_triangle(f, vertex_id(-3), vertex_id(3), vertex_id(4)); + export_triangle(f, vertex_id(-3), vertex_id(4), vertex_id(-2)); + export_triangle(f, vertex_id(-2), vertex_id(4), vertex_id(5)); + export_triangle(f, vertex_id(-2), vertex_id(5), vertex_id(-1)); + export_triangle(f, vertex_id(-1), vertex_id(5), vertex_id(0)); + export_triangle(f, vertex_id(-1), vertex_id(0), vertex_id(-4)); + // corner + export_triangle(f, vertex_id(1), vertex_id(2), vertex_id(3)); + export_triangle(f, vertex_id(1), vertex_id(3), vertex_id(0)); + export_triangle(f, vertex_id(1), vertex_id(0), vertex_id(5)); + export_triangle(f, vertex_id(1), vertex_id(5), vertex_id(2)); + m_vertices_count += 6; + } + } + } + + size_t vertex_id(int id) { return static_cast(1 + static_cast(m_vertices_count) + id); } + + void export_vertex(FILE& f, const Vertex& v) { + fprintf(&f, "v %g %g %g\n", v.position.x(), v.position.y(), v.position.z()); + fprintf(&f, "vn %g %g %g\n", v.normal.x(), v.normal.y(), v.normal.z()); + } + + void export_material(FILE& f, size_t material_id) { + fprintf(&f, "\nusemtl material_%zu\n", material_id + 1); + } + + void export_triangle(FILE& f, size_t v1, size_t v2, size_t v3) { + fprintf(&f, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3); + } + + void export_materials(FILE& f) { + static const float inv_255 = 1.0f / 255.0f; + size_t materials_counter = 0; + for (const auto& color : m_colors) { + fprintf(&f, "\nnewmtl material_%zu\n", ++materials_counter); + fprintf(&f, "Ka 1 1 1\n"); + fprintf(&f, "Kd %g %g %g\n", static_cast(color[0]) * inv_255, + static_cast(color[1]) * inv_255, + static_cast(color[2]) * inv_255); + fprintf(&f, "Ks 0 0 0\n"); + } + } + + size_t color_id(size_t vertex_id) { + const libvgcode::PathVertex& v = m_viewer.get_vertex_at(vertex_id); + const size_t top_layer_id = m_viewer.is_top_layer_only_view_range() ? m_viewer.get_layers_view_range()[1] : 0; + const bool color_top_layer_only = m_viewer.get_view_full_range()[1] != m_viewer.get_view_visible_range()[1]; + const libvgcode::Color color = (color_top_layer_only && v.layer_id < top_layer_id && + (!m_viewer.is_spiral_vase_mode() || vertex_id != m_viewer.get_view_enabled_range()[0])) ? + libvgcode::DUMMY_COLOR : m_viewer.get_vertex_color(v); + auto color_it = std::find_if(m_colors.begin(), m_colors.end(), [&color](const libvgcode::Color& m) { return m == color; }); + if (color_it == m_colors.end()) { + m_colors.emplace_back(color); + color_it = std::prev(m_colors.end()); + } + return std::distance(m_colors.begin(), color_it); + } +}; + +const float ToolpathsObjExporter::Cap_Rounding_Factor = 0.25f; + void GCodeViewer::export_toolpaths_to_obj(const char* filename) const { if (filename == nullptr) @@ -1229,1155 +1488,8 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const wxBusyCursor busy; - // the data needed is contained into the Extrude TBuffer - const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - if (!t_buffer.has_data()) - return; - - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) - return; - - // collect color information to generate materials - std::vector colors; - for (const RenderPath& path : t_buffer.render_paths) { - colors.push_back(path.color); - } - sort_remove_duplicates(colors); - - // save materials file - boost::filesystem::path mat_filename(filename); - mat_filename.replace_extension("mtl"); - - CNumericLocalesSetter locales_setter; - - FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; - return; - } - - fprintf(fp, "# G-Code Toolpaths Materials\n"); - fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); - - unsigned int colors_count = 1; - for (const ColorRGBA& color : colors) { - fprintf(fp, "\nnewmtl material_%d\n", colors_count++); - fprintf(fp, "Ka 1 1 1\n"); - fprintf(fp, "Kd %g %g %g\n", color.r(), color.g(), color.b()); - fprintf(fp, "Ks 0 0 0\n"); - } - - fclose(fp); - - // save geometry file - fp = boost::nowide::fopen(filename, "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; - return; - } - - fprintf(fp, "# G-Code Toolpaths\n"); - fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); - fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); - - const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); - - std::vector out_vertices; - std::vector out_normals; - - struct VerticesOffset - { - unsigned int vbo; - size_t offset; - }; - std::vector vertices_offsets; - vertices_offsets.push_back({ t_buffer.vertices.vbos.front(), 0 }); - - // get vertices/normals data from vertex buffers on gpu - for (size_t i = 0; i < t_buffer.vertices.vbos.size(); ++i) { - const size_t floats_count = t_buffer.vertices.sizes[i] / sizeof(float); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.vbos[i])); -#if ENABLE_OPENGL_ES - const VertexBuffer vertices = *static_cast(::glMapBufferRange(GL_ARRAY_BUFFER, 0, - static_cast(t_buffer.vertices.sizes[i]), GL_MAP_READ_BIT)); - glcheck(); - glsafe(::glUnmapBuffer(GL_ARRAY_BUFFER)); -#else - VertexBuffer vertices(floats_count); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(t_buffer.vertices.sizes[i]), static_cast(vertices.data()))); -#endif // ENABLE_OPENGL_ES - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - const size_t vertices_count = floats_count / floats_per_vertex; - for (size_t j = 0; j < vertices_count; ++j) { - const size_t base = j * floats_per_vertex; - out_vertices.push_back({ vertices[base + 0], vertices[base + 1], vertices[base + 2] }); - out_normals.push_back({ vertices[base + 3], vertices[base + 4], vertices[base + 5] }); - } - - if (i < t_buffer.vertices.vbos.size() - 1) - vertices_offsets.push_back({ t_buffer.vertices.vbos[i + 1], vertices_offsets.back().offset + vertices_count }); - } - - // save vertices to file - fprintf(fp, "\n# vertices\n"); - for (const Vec3f& v : out_vertices) { - fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z()); - } - - // save normals to file - fprintf(fp, "\n# normals\n"); - for (const Vec3f& n : out_normals) { - fprintf(fp, "vn %g %g %g\n", n.x(), n.y(), n.z()); - } - - size_t i = 0; - for (const ColorRGBA& color : colors) { - // save material triangles to file - fprintf(fp, "\nusemtl material_%zu\n", i + 1); - fprintf(fp, "# triangles material %zu\n", i + 1); - - for (const RenderPath& render_path : t_buffer.render_paths) { - if (render_path.color != color) - continue; - - const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id]; - size_t vertices_offset = 0; - for (size_t j = 0; j < vertices_offsets.size(); ++j) { - const VerticesOffset& offset = vertices_offsets[j]; - if (offset.vbo == ibuffer.vbo) { - vertices_offset = offset.offset; - break; - } - } - - // get indices data from index buffer on gpu - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo)); - for (size_t j = 0; j < render_path.sizes.size(); ++j) { -#if ENABLE_OPENGL_ES - const IndexBuffer indices = *static_cast(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, - static_cast(render_path.offsets[j]), static_cast(render_path.sizes[j] * sizeof(IBufferType)), - GL_MAP_READ_BIT)); - glcheck(); - glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER)); -#else - IndexBuffer indices(render_path.sizes[j]); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(render_path.offsets[j]), - static_cast(render_path.sizes[j] * sizeof(IBufferType)), static_cast(indices.data()))); -#endif // ENABLE_OPENGL_ES - - const size_t triangles_count = render_path.sizes[j] / 3; - for (size_t k = 0; k < triangles_count; ++k) { - const size_t base = k * 3; - const size_t v1 = 1 + static_cast(indices[base + 0]) + vertices_offset; - const size_t v2 = 1 + static_cast(indices[base + 1]) + vertices_offset; - const size_t v3 = 1 + static_cast(indices[base + 2]) + vertices_offset; - if (v1 != v2) - // do not export dummy triangles - fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3); - } - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - ++i; - } - - fclose(fp); -} - -void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) -{ - // max index buffer size, in bytes - static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024; - - auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { - int64_t vertices_size = 0; - for (const MultiVertexBuffer& buffers : vertices) { - for (const VertexBuffer& buffer : buffers) { - vertices_size += SLIC3R_STDVEC_MEMSIZE(buffer, float); - } - } - int64_t indices_size = 0; - for (const MultiIndexBuffer& buffers : indices) { - for (const IndexBuffer& buffer : buffers) { - indices_size += SLIC3R_STDVEC_MEMSIZE(buffer, IBufferType); - } - } - log_memory_used(label, vertices_size + indices_size); - }; - - // format data into the buffers to be rendered as lines - auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { - auto add_vertex = [&vertices](const GCodeProcessorResult::MoveVertex& vertex) { - // add position - vertices.push_back(vertex.position.x()); - vertices.push_back(vertex.position.y()); - vertices.push_back(vertex.position.z()); - }; - - // add previous vertex - add_vertex(prev); - // add current vertex - add_vertex(curr); - }; - auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { - // add starting index - indices.push_back(static_cast(indices.size())); - buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); - buffer.paths.back().sub_paths.front().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) { - // add previous index - indices.push_back(static_cast(indices.size())); - } - - // add current index - indices.push_back(static_cast(indices.size())); - last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - }; - - // format data into the buffers to be rendered as solid - auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id, bool account_for_volumetric_rate) { - auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { - // append position - vertices.push_back(position.x()); - vertices.push_back(position.y()); - vertices.push_back(position.z()); - // append normal - vertices.push_back(normal.x()); - vertices.push_back(normal.y()); - vertices.push_back(normal.z()); - }; - - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { - buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); - buffer.paths.back().sub_paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - - const Vec3f dir = (curr.position - prev.position).normalized(); - const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); - const Vec3f left = -right; - const Vec3f up = right.cross(dir); - const Vec3f down = -up; - const float half_width = 0.5f * last_path.width; - const float half_height = 0.5f * last_path.height; - const Vec3f prev_pos = prev.position - half_height * up; - const Vec3f curr_pos = curr.position - half_height * up; - const Vec3f d_up = half_height * up; - const Vec3f d_down = -half_height * up; - const Vec3f d_right = half_width * right; - const Vec3f d_left = -half_width * right; - - // vertices 1st endpoint - if (last_path.vertices_count() == 1 || vertices.empty()) { - // 1st segment or restart into a new vertex buffer - // =============================================== - store_vertex(vertices, prev_pos + d_up, up); - store_vertex(vertices, prev_pos + d_right, right); - store_vertex(vertices, prev_pos + d_down, down); - store_vertex(vertices, prev_pos + d_left, left); - } - else { - // any other segment - // ================= - store_vertex(vertices, prev_pos + d_right, right); - store_vertex(vertices, prev_pos + d_left, left); - } - - // vertices 2nd endpoint - store_vertex(vertices, curr_pos + d_up, up); - store_vertex(vertices, curr_pos + d_right, right); - store_vertex(vertices, curr_pos + d_down, down); - store_vertex(vertices, curr_pos + d_left, left); - - last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; - }; - auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, - const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, - IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float sq_prev_length; - auto store_triangle = [](IndexBuffer& indices, IBufferType i1, IBufferType i2, IBufferType i3) { - indices.push_back(i1); - indices.push_back(i2); - indices.push_back(i3); - }; - auto append_dummy_cap = [store_triangle](IndexBuffer& indices, IBufferType id) { - store_triangle(indices, id, id, id); - store_triangle(indices, id, id, id); - }; - auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { - std::array ret = { - static_cast(static_cast(vbuffer_size) + v_offsets[0]), - static_cast(static_cast(vbuffer_size) + v_offsets[1]), - static_cast(static_cast(vbuffer_size) + v_offsets[2]), - static_cast(static_cast(vbuffer_size) + v_offsets[3]), - static_cast(static_cast(vbuffer_size) + v_offsets[4]), - static_cast(static_cast(vbuffer_size) + v_offsets[5]), - static_cast(static_cast(vbuffer_size) + v_offsets[6]), - static_cast(static_cast(vbuffer_size) + v_offsets[7]) - }; - return ret; - }; - auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[0], v_offsets[2], v_offsets[1]); - store_triangle(indices, v_offsets[0], v_offsets[3], v_offsets[2]); - }; - auto append_stem_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[0], v_offsets[1], v_offsets[4]); - store_triangle(indices, v_offsets[1], v_offsets[5], v_offsets[4]); - store_triangle(indices, v_offsets[1], v_offsets[2], v_offsets[5]); - store_triangle(indices, v_offsets[2], v_offsets[6], v_offsets[5]); - store_triangle(indices, v_offsets[2], v_offsets[3], v_offsets[6]); - store_triangle(indices, v_offsets[3], v_offsets[7], v_offsets[6]); - store_triangle(indices, v_offsets[3], v_offsets[0], v_offsets[7]); - store_triangle(indices, v_offsets[0], v_offsets[4], v_offsets[7]); - }; - auto append_ending_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { - store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); - store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); - }; - - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); - buffer.paths.back().sub_paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - - const Vec3f dir = (curr.position - prev.position).normalized(); - const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); - const Vec3f up = right.cross(dir); - const float sq_length = (curr.position - prev.position).squaredNorm(); - - const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); - const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); - const bool is_first_segment = (last_path.vertices_count() == 1); - if (is_first_segment || vbuffer_size == 0) { - // 1st segment or restart into a new vertex buffer - // =============================================== - if (is_first_segment) - // starting cap triangles - append_starting_cap_triangles(indices, first_seg_v_offsets); - // dummy triangles outer corner cap - append_dummy_cap(indices, vbuffer_size); - - // stem triangles - append_stem_triangles(indices, first_seg_v_offsets); - - vbuffer_size += 8; - } - else { - // any other segment - // ================= - float displacement = 0.0f; - const float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - const Vec3f med_dir = (prev_dir + dir).normalized(); - const float half_width = 0.5f * last_path.width; - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - const float sq_displacement = sqr(displacement); - const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length; - - const bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - const bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - if (!is_sharp && can_displace) { - if (is_right_turn) - left_displaced = true; - else - right_displaced = true; - } - - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(indices, vbuffer_size); - else { - store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1); - store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1); - } - } - else { - if (right_displaced) - // dummy triangles - append_dummy_cap(indices, vbuffer_size); - else { - store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0); - store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0); - } - } - - // stem triangles - append_stem_triangles(indices, non_first_seg_v_offsets); - - vbuffer_size += 6; - } - - if (next != nullptr && (curr.type != next->type || !last_path.matches(*next, account_for_volumetric_rate))) - // ending cap triangles - append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); - - last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - prev_dir = dir; - prev_up = up; - sq_prev_length = sq_length; - }; - - // format data into the buffers to be rendered as instanced model - auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { - // append position - instances.push_back(curr.position.x()); - instances.push_back(curr.position.y()); - instances.push_back(curr.position.z()); - // append width - instances.push_back(curr.width); - // append height - instances.push_back(curr.height); - - // append id - instances_ids.push_back(move_id); - }; - - // format data into the buffers to be rendered as batched model - auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { - const double width = static_cast(1.5f * curr.width); - const double height = static_cast(1.5f * curr.height); - - const Transform3d trafo = Geometry::translation_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast()) * - Geometry::scale_transform({ width, width, height }); - const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); - - // append vertices - const size_t vertices_count = data.vertices_count(); - for (size_t i = 0; i < vertices_count; ++i) { - // append position - const Vec3d position = trafo * data.extract_position_3(i).cast(); - vertices.push_back(float(position.x())); - vertices.push_back(float(position.y())); - vertices.push_back(float(position.z())); - - // append normal - const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast(); - vertices.push_back(float(normal.x())); - vertices.push_back(float(normal.y())); - vertices.push_back(float(normal.z())); - } - - // append instance position - instances.push_back(curr.position.x()); - instances.push_back(curr.position.y()); - instances.push_back(curr.position.z()); - // append instance id - instances_ids.push_back(move_id); - }; - - auto add_indices_as_model_batch = [](const GLModel::Geometry& data, IndexBuffer& indices, IBufferType base_index) { - const size_t indices_count = data.indices_count(); - for (size_t i = 0; i < indices_count; ++i) { - indices.push_back(static_cast(data.extract_index(i) + base_index)); - } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); - m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex); - m_statistics.results_time = gcode_result.time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - m_max_bounding_box.reset(); - - m_moves_count = gcode_result.moves.size(); - if (m_moves_count == 0) - return; - - m_extruders_count = gcode_result.extruders_count; - - unsigned int progress_count = 0; - static const unsigned int progress_threshold = 1000; - wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? - new wxProgressDialog(_L("Generating toolpaths"), "...", - 100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; - - wxBusyCursor busy; - - // extract approximate paths bounding box from result - for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { - if (wxGetApp().is_gcode_viewer()) - // for the gcode viewer we need to take in account all moves to correctly size the printbed - m_paths_bounding_box.merge(move.position.cast()); - else { - if (move.type == EMoveType::Extrude && move.extrusion_role != GCodeExtrusionRole::Custom && move.width != 0.0f && move.height != 0.0f) - m_paths_bounding_box.merge(move.position.cast()); - } - } - - if (wxGetApp().is_editor()) - m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); - - m_cog.reset(); - - m_sequential_view.gcode_ids.clear(); - for (size_t i = 0; i < gcode_result.moves.size(); ++i) { - const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; - if (move.type != EMoveType::Seam) - m_sequential_view.gcode_ids.push_back(move.gcode_id); - } - - bool account_for_volumetric_rate = m_view_type == EViewType::VolumetricRate; - - std::vector vertices(m_buffers.size()); - std::vector indices(m_buffers.size()); - std::vector instances(m_buffers.size()); - std::vector instances_ids(m_buffers.size()); - std::vector instances_offsets(m_buffers.size()); - std::vector options_zs; - - std::vector biased_seams_ids; - - // toolpaths data -> extract vertices from result - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - if (curr.type == EMoveType::Seam) - biased_seams_ids.push_back(i - biased_seams_ids.size() - 1); - - const size_t move_id = i - biased_seams_ids.size(); - - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; - - if (curr.type == EMoveType::Extrude && - curr.extrusion_role != GCodeExtrusionRole::Skirt && - curr.extrusion_role != GCodeExtrusionRole::SupportMaterial && - curr.extrusion_role != GCodeExtrusionRole::SupportMaterialInterface && - curr.extrusion_role != GCodeExtrusionRole::WipeTower && - curr.extrusion_role != GCodeExtrusionRole::Custom) { - const Vec3d curr_pos = curr.position.cast(); - const Vec3d prev_pos = prev.position.cast(); - m_cog.add_segment(curr_pos, prev_pos, curr.mm3_per_mm * (curr_pos - prev_pos).norm()); - } - - // update progress dialog - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), - _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const unsigned char id = buffer_id(curr.type); - TBuffer& t_buffer = m_buffers[id]; - MultiVertexBuffer& v_multibuffer = vertices[id]; - InstanceBuffer& inst_buffer = instances[id]; - InstanceIdBuffer& inst_id_buffer = instances_ids[id]; - InstancesOffsets& inst_offsets = instances_offsets[id]; - - // ensure there is at least one vertex buffer - if (v_multibuffer.empty()) - v_multibuffer.push_back(VertexBuffer()); - - // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer - // add another vertex buffer - size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); - if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { - v_multibuffer.push_back(VertexBuffer()); - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - Path& last_path = t_buffer.paths.back(); - if (prev.type == curr.type && last_path.matches(curr, account_for_volumetric_rate)) - last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - VertexBuffer& v_buffer = v_multibuffer.back(); - - switch (t_buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } - case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id, account_for_volumetric_rate); break; } - case TBuffer::ERenderPrimitiveType::InstancedModel: - { - add_model_instance(curr, inst_buffer, inst_id_buffer, move_id); - inst_offsets.push_back(prev.position - curr.position); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.instances_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - break; - } - case TBuffer::ERenderPrimitiveType::BatchedModel: - { - add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id); - inst_offsets.push_back(prev.position - curr.position); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.batched_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - break; - } - } - - // collect options zs for later use - if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { - const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); - if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) - options_zs.emplace_back(curr.position[2]); - } - } - - // smooth toolpaths corners for the given TBuffer using triangles - auto smooth_triangle_toolpaths_corners = [&gcode_result, &biased_seams_ids](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) { - auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) { - return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); - }; - auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) { - vertices[offset + 0] = position.x(); - vertices[offset + 1] = position.y(); - vertices[offset + 2] = position.z(); - }; - auto match_right_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, - size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { - if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer - VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - // offset into the vertex buffer of the next segment 1st vertex - const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; - // offset into the vertex buffer of the right vertex of the previous segment - const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; - // new position of the right vertices - const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; - // update previous segment - update_position_at(vbuffer, prev_right_offset, shared_vertex); - // offset into the vertex buffer of the right vertex of the next segment - const size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset; - // update next segment - update_position_at(vbuffer, next_right_offset, shared_vertex); - } - else { // previous and next segment are contained into different vertex buffers - VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; - // offset into the previous vertex buffer of the right vertex of the previous segment - const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; - // new position of the right vertices - const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; - // update previous segment - update_position_at(prev_vbuffer, prev_right_offset, shared_vertex); - // offset into the next vertex buffer of the right vertex of the next segment - const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; - // update next segment - update_position_at(next_vbuffer, next_right_offset, shared_vertex); - } - }; - auto match_left_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, - size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { - if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer - VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - // offset into the vertex buffer of the next segment 1st vertex - const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; - // offset into the vertex buffer of the left vertex of the previous segment - const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; - // new position of the left vertices - const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; - // update previous segment - update_position_at(vbuffer, prev_left_offset, shared_vertex); - // offset into the vertex buffer of the left vertex of the next segment - const size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; - // update next segment - update_position_at(vbuffer, next_left_offset, shared_vertex); - } - else { // previous and next segment are contained into different vertex buffers - VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; - VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; - // offset into the previous vertex buffer of the left vertex of the previous segment - const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; - // new position of the left vertices - const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; - // update previous segment - update_position_at(prev_vbuffer, prev_left_offset, shared_vertex); - // offset into the next vertex buffer of the left vertex of the next segment - const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; - // update next segment - update_position_at(next_vbuffer, next_left_offset, shared_vertex); - } - }; - - auto extract_move_id = [&biased_seams_ids](size_t id) { - size_t new_id = size_t(-1); - auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id); - if (it == biased_seams_ids.end()) - new_id = id + biased_seams_ids.size(); - else { - if (it == biased_seams_ids.begin() && *it < id) - new_id = id; - else if (it != biased_seams_ids.begin()) - new_id = id + std::distance(biased_seams_ids.begin(), it); - } - return (new_id == size_t(-1)) ? id : new_id; - }; - - const size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats(); - for (const Path& path : t_buffer.paths) { - // the two segments of the path sharing the current vertex may belong - // to two different vertex buffers - size_t prev_sub_path_id = 0; - size_t next_sub_path_id = 0; - const size_t path_vertices_count = path.vertices_count(); - const float half_width = 0.5f * path.width; - for (size_t j = 1; j < path_vertices_count - 1; ++j) { - const size_t curr_s_id = path.sub_paths.front().first.s_id + j; - const size_t move_id = extract_move_id(curr_s_id); - const Vec3f& prev = gcode_result.moves[move_id - 1].position; - const Vec3f& curr = gcode_result.moves[move_id].position; - const Vec3f& next = gcode_result.moves[move_id + 1].position; - - // select the subpaths which contains the previous/next segments - if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id)) - ++prev_sub_path_id; - if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1)) - ++next_sub_path_id; - const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id]; - const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id]; - - const Vec3f prev_dir = (curr - prev).normalized(); - const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized(); - const Vec3f prev_up = prev_right.cross(prev_dir); - - const Vec3f next_dir = (next - curr).normalized(); - - const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; - const float cos_dir = prev_dir.dot(next_dir); - // whether the angle between adjacent segments is greater than 45 degrees - const bool is_sharp = cos_dir < 0.7071068f; - - float displacement = 0.0f; - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - const Vec3f med_dir = (prev_dir + next_dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f))); - } - - const float sq_prev_length = (curr - prev).squaredNorm(); - const float sq_next_length = (next - curr).squaredNorm(); - const float sq_displacement = sqr(displacement); - const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length; - - if (can_displace) { - // displacement to apply to the vertices to match - const Vec3f displacement_vec = displacement * prev_dir; - // matches inner corner vertices - if (is_right_turn) - match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); - else - match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); - - if (!is_sharp) { - // matches outer corner vertices - if (is_right_turn) - match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); - else - match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); - } - } - } - } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto load_vertices_time = std::chrono::high_resolution_clock::now(); - m_statistics.load_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // smooth toolpaths corners for TBuffers using triangles - for (size_t i = 0; i < m_buffers.size(); ++i) { - const TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - smooth_triangle_toolpaths_corners(t_buffer, vertices[i]); - } - - // dismiss, no more needed - std::vector().swap(biased_seams_ids); - - for (MultiVertexBuffer& v_multibuffer : vertices) { - for (VertexBuffer& v_buffer : v_multibuffer) { - v_buffer.shrink_to_fit(); - } - } - - // move the wipe toolpaths half height up to render them on proper position - MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; - for (VertexBuffer& v_buffer : wipe_vertices) { - for (size_t i = 2; i < v_buffer.size(); i += 3) { - v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height; - } - } - - // send vertices data to gpu, where needed - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { - const InstanceBuffer& inst_buffer = instances[i]; - if (!inst_buffer.empty()) { - t_buffer.model.instances.buffer = inst_buffer; - t_buffer.model.instances.s_ids = instances_ids[i]; - t_buffer.model.instances.offsets = instances_offsets[i]; - } - } - else { - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - const InstanceBuffer& inst_buffer = instances[i]; - if (!inst_buffer.empty()) { - t_buffer.model.instances.buffer = inst_buffer; - t_buffer.model.instances.s_ids = instances_ids[i]; - t_buffer.model.instances.offsets = instances_offsets[i]; - } - } - const MultiVertexBuffer& v_multibuffer = vertices[i]; - for (const VertexBuffer& v_buffer : v_multibuffer) { - const size_t size_elements = v_buffer.size(); - const size_t size_bytes = size_elements * sizeof(float); - const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); - t_buffer.vertices.count += vertices_count; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_vertices_gpu_size += static_cast(size_bytes); - m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.vbuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -#if ENABLE_GL_CORE_PROFILE - GLuint vao_id = 0; - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) { - glsafe(::glGenVertexArrays(1, &vao_id)); - glsafe(::glBindVertexArray(vao_id)); - } -#endif // ENABLE_GL_CORE_PROFILE - - GLuint vbo_id = 0; - glsafe(::glGenBuffers(1, &vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) { - glsafe(::glBindVertexArray(0)); - t_buffer.vertices.vaos.push_back(static_cast(vao_id)); - } -#endif // ENABLE_GL_CORE_PROFILE - t_buffer.vertices.vbos.push_back(static_cast(vbo_id)); - t_buffer.vertices.sizes.push_back(size_bytes); - } - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto smooth_vertices_time = std::chrono::high_resolution_clock::now(); - m_statistics.smooth_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - load_vertices_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices); - - // dismiss vertices data, no more needed - std::vector().swap(vertices); - std::vector().swap(instances); - std::vector().swap(instances_ids); - - // toolpaths data -> extract indices from result - // paths may have been filled while extracting vertices, - // so reset them, they will be filled again while extracting indices - for (TBuffer& buffer : m_buffers) { - buffer.paths.clear(); - } - - // variable used to keep track of the current vertex buffers index and size - using CurrVertexBuffer = std::pair; - std::vector curr_vertex_buffers(m_buffers.size(), { 0, 0 }); - -#if ENABLE_GL_CORE_PROFILE - // variable used to keep track of the vertex buffers ids - using VIndexList = std::vector; - std::vector vao_indices(m_buffers.size()); - std::vector vbo_indices(m_buffers.size()); -#else - // variable used to keep track of the vertex buffers ids - using VboIndexList = std::vector; - std::vector vbo_indices(m_buffers.size()); -#endif // ENABLE_GL_CORE_PROFILE - - size_t seams_count = 0; - - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - if (curr.type == EMoveType::Seam) - ++seams_count; - - const size_t move_id = i - seams_count; - - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessorResult::MoveVertex* next = nullptr; - if (i < m_moves_count - 1) - next = &gcode_result.moves[i + 1]; - - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), - _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const unsigned char id = buffer_id(curr.type); - TBuffer& t_buffer = m_buffers[id]; - MultiIndexBuffer& i_multibuffer = indices[id]; - CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id]; -#if ENABLE_GL_CORE_PROFILE - VIndexList& vao_index_list = vao_indices[id]; - VIndexList& vbo_index_list = vbo_indices[id]; -#else - VboIndexList& vbo_index_list = vbo_indices[id]; -#endif // ENABLE_GL_CORE_PROFILE - - // ensure there is at least one index buffer - if (i_multibuffer.empty()) { - i_multibuffer.push_back(IndexBuffer()); -#if ENABLE_GL_CORE_PROFILE - if (!t_buffer.vertices.vaos.empty() && OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - vao_index_list.push_back(t_buffer.vertices.vaos[curr_vertex_buffer.first]); - - if (!t_buffer.vertices.vbos.empty()) - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); -#else - if (!t_buffer.vertices.vbos.empty()) - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); -#endif // ENABLE_GL_CORE_PROFILE - } - - // if adding the indices for the current segment exceeds the threshold size of the current index buffer - // create another index buffer - size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : t_buffer.max_indices_per_segment_size_bytes(); - if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) { - i_multibuffer.push_back(IndexBuffer()); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - vao_index_list.push_back(t_buffer.vertices.vaos[curr_vertex_buffer.first]); -#endif // ENABLE_GL_CORE_PROFILE - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { - Path& last_path = t_buffer.paths.back(); - last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer - // create another index buffer - size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); - if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { - i_multibuffer.push_back(IndexBuffer()); - - ++curr_vertex_buffer.first; - curr_vertex_buffer.second = 0; -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - vao_index_list.push_back(t_buffer.vertices.vaos[curr_vertex_buffer.first]); -#endif // ENABLE_GL_CORE_PROFILE - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); - - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { - Path& last_path = t_buffer.paths.back(); - last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, move_id - 1); - } - } - - IndexBuffer& i_buffer = i_multibuffer.back(); - - switch (t_buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: { - add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); - curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); - break; - } - case TBuffer::ERenderPrimitiveType::BatchedModel: { - add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second); - curr_vertex_buffer.second += t_buffer.model.data.vertices_count(); - break; - } - default: { break; } - } - } - - for (MultiIndexBuffer& i_multibuffer : indices) { - for (IndexBuffer& i_buffer : i_multibuffer) { - i_buffer.shrink_to_fit(); - } - } - - // toolpaths data -> send indices data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& t_buffer = m_buffers[i]; - if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) { - const MultiIndexBuffer& i_multibuffer = indices[i]; - for (const IndexBuffer& i_buffer : i_multibuffer) { - const size_t size_elements = i_buffer.size(); - const size_t size_bytes = size_elements * sizeof(IBufferType); - - // stores index buffer informations into TBuffer - t_buffer.indices.push_back(IBuffer()); - IBuffer& ibuf = t_buffer.indices.back(); - ibuf.count = size_elements; -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - ibuf.vao = vao_indices[i][t_buffer.indices.size() - 1]; -#endif // ENABLE_GL_CORE_PROFILE - ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_indices_gpu_size += static_cast(size_bytes); - m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.ibuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - glsafe(::glGenBuffers(1, &ibuf.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - } - } - - if (progress_dialog != nullptr) { - progress_dialog->Update(100, ""); - progress_dialog->Fit(); - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } - - auto update_segments_count = [&](EMoveType type, int64_t& count) { - unsigned int id = buffer_id(type); - const MultiIndexBuffer& buffers = indices[id]; - int64_t indices_count = 0; - for (const IndexBuffer& buffer : buffers) { - indices_count += buffer.size(); - } - const TBuffer& t_buffer = m_buffers[id]; - if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles - - count += indices_count / t_buffer.indices_per_segment(); - }; - - update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); - update_segments_count(EMoveType::Wipe, m_statistics.wipe_segments_count); - update_segments_count(EMoveType::Extrude, m_statistics.extrude_segments_count); - - m_statistics.load_indices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - smooth_vertices_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices); - - // dismiss indices data, no more needed - std::vector().swap(indices); - - // layers zs / roles / extruder ids -> extract from result - size_t last_travel_s_id = 0; - size_t first_travel_s_id = 0; - seams_count = 0; - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; - if (move.type == EMoveType::Seam) - ++seams_count; - - const size_t move_id = i - seams_count; - - if (move.type == EMoveType::Extrude) { - // layers zs/ranges - const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); - const double z = static_cast(move.position.z()); - if (move.extrusion_role != GCodeExtrusionRole::Custom && - (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z)) { - // start a new layer - const size_t start_it = (m_layers.empty() && first_travel_s_id != 0) ? first_travel_s_id : last_travel_s_id; - m_layers.append(z, { start_it, move_id }); - } - else if (!m_layers.empty() && !move.internal_only) - // update last layer - m_layers.get_ranges().back().last = move_id; - // extruder ids - m_extruder_ids.emplace_back(move.extruder_id); - // roles - if (i > 0) - m_roles.emplace_back(move.extrusion_role); - } - else if (move.type == EMoveType::Travel) { - if (move_id - last_travel_s_id > 1 && !m_layers.empty()) - m_layers.get_ranges().back().last = move_id; - else if (m_layers.empty() && first_travel_s_id == 0) - first_travel_s_id = move_id; - last_travel_s_id = move_id; - } - } - - // roles -> remove duplicates - sort_remove_duplicates(m_roles); - m_roles.shrink_to_fit(); - - // extruder ids -> remove duplicates - sort_remove_duplicates(m_extruder_ids); - m_extruder_ids.shrink_to_fit(); - - // replace layers for spiral vase mode - if (!gcode_result.spiral_vase_layers.empty()) { - m_layers.reset(); - for (const auto& layer : gcode_result.spiral_vase_layers) { - m_layers.append(layer.first, { layer.second.first, layer.second.second }); - } - } - - // set layers z range - if (!m_layers.empty()) - m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; - - // change color of paths whose layer contains option points - if (!options_zs.empty()) { - TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - for (Path& path : extrude_buffer.paths) { - const float z = path.sub_paths.front().first.position.z(); - if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) - path.cp_color_id = 255 - path.cp_color_id; - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (progress_dialog != nullptr) - progress_dialog->Destroy(); + ToolpathsObjExporter exporter(m_viewer); + exporter.export_to(filename); } void GCodeViewer::load_shells(const Print& print) @@ -2419,7 +1531,6 @@ void GCodeViewer::load_shells(const Print& print) v->set_volume_offset(v->get_volume_offset() + z_offset); } } - } wxGetApp().plater()->get_current_canvas3D()->check_volumes_outside_state(m_shells.volumes); @@ -2483,7 +1594,7 @@ void GCodeViewer::load_wipetower_shell(const Print& print) // adds wipe tower's volume const double max_z = print.objects()[0]->model_object()->get_model()->max_z(); const PrintConfig& config = print.config(); - const size_t extruders_count = config.nozzle_diameter.size(); + const size_t extruders_count = get_extruders_count(); if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count); const float depth = wipe_tower_data.depth; @@ -2503,915 +1614,185 @@ void GCodeViewer::load_wipetower_shell(const Print& print) } } -void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - auto extrusion_color = [this](const Path& path) { - ColorRGBA color; - switch (m_view_type) - { - case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } - case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } - case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } - case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } - case EViewType::LayerTimeLinear: - case EViewType::LayerTimeLogarithmic: { - if (!m_layers_times.empty() && m_layers.size() == m_layers_times.front().size()) { - const Path::Sub_Path& sub_path = path.sub_paths.front(); - double z = static_cast(sub_path.first.position.z()); - const std::vector& zs = m_layers.get_zs(); - const std::vector& ranges = m_layers.get_ranges(); - size_t time_mode_id = static_cast(m_time_estimate_mode); - for (size_t i = 0; i < zs.size(); ++i) { - if (std::abs(zs[i] - z) < EPSILON) { - if (ranges[i].contains(sub_path.first.s_id)) { - color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i], - (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic); - break; - } - } - } - } - break; - } - case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } - case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } - case EViewType::ColorPrint: { - if (path.cp_color_id >= static_cast(m_tool_colors.size())) - color = ColorRGBA::GRAY(); - else - color = m_tool_colors[path.cp_color_id]; - - break; - } - default: { color = ColorRGBA::WHITE(); break; } - } - - return color; - }; - - auto travel_color = [](const Path& path) { - return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : - Travel_Colors[0] /* Move */); - }; - - auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { - auto in_layers_range = [this, min_id, max_id](size_t id) { - return m_layers.get_range_at(min_id).first <= id && id <= m_layers.get_range_at(max_id).last; - }; - - return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id); - }; - - auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { - const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; - if (path_id >= buffer.paths.size()) - return false; - - Path path = buffer.paths[path_id]; - size_t first = path_id; - size_t last = path_id; - - // check adjacent paths - while (first > 0) { - const Path& ref_path = buffer.paths[first - 1]; - if (!path.sub_paths.front().first.position.isApprox(ref_path.sub_paths.back().last.position) || - path.role != ref_path.role) - break; - - path.sub_paths.front().first = ref_path.sub_paths.front().first; - --first; - } - while (last < buffer.paths.size() - 1) { - const Path& ref_path = buffer.paths[last + 1]; - if (!path.sub_paths.back().last.position.isApprox(ref_path.sub_paths.front().first.position) || - path.role != ref_path.role) - break; - - path.sub_paths.back().last = ref_path.sub_paths.back().last; - ++last; - } - - const size_t min_s_id = m_layers.get_range_at(min_id).first; - const size_t max_s_id = m_layers.get_range_at(max_id).last; - - const bool top_layer_shown = max_id == m_layers.size() - 1; - - return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || // the leading vertex is contained - (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id) || // the trailing vertex is contained - (top_layer_shown && max_s_id < path.sub_paths.front().first.s_id); // the leading vertex is above the top layer and the top layer is shown - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - Statistics* statistics = const_cast(&m_statistics); - statistics->render_paths_size = 0; - statistics->models_instances_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - const bool top_layer_only = get_app_config()->get_bool("seq_top_layer_only"); - - SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; - SequentialView::Endpoints top_layer_endpoints = global_endpoints; - SequentialView* sequential_view = const_cast(&m_sequential_view); - if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; - if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; - - // first pass: collect visible paths and update sequential view data - std::vector> paths; - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - // reset render paths - buffer.render_paths.clear(); - - if (!buffer.visible) - continue; - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || - buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - for (size_t id : buffer.model.instances.s_ids) { - if (id < m_layers.get_range_at(m_layers_z_range[0]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) - continue; - - global_endpoints.first = std::min(global_endpoints.first, id); - global_endpoints.last = std::max(global_endpoints.last, id); - - if (top_layer_only) { - if (id < m_layers.get_range_at(m_layers_z_range[1]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id) - continue; - - top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, id); - } - } - } - else { - for (size_t i = 0; i < buffer.paths.size(); ++i) { - const Path& path = buffer.paths[i]; - if (path.type == EMoveType::Travel) { - if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) - continue; - } - else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) - continue; - - if (path.type == EMoveType::Extrude && !is_visible(path)) - continue; - - // store valid path - for (size_t j = 0; j < path.sub_paths.size(); ++j) { - paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); - } - - global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); - global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); - - if (top_layer_only) { - if (path.type == EMoveType::Travel) { - if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); - } - } - else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); - } - } - } - } - } - - // update current sequential position - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; - sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; - - // get the world position from the vertex buffer - bool found = false; - for (const TBuffer& buffer : m_buffers) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || - buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) { - if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) { - size_t offset = i * buffer.model.instances.instance_size_floats(); - sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0]; - sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1]; - sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2]; - sequential_view->current_offset = buffer.model.instances.offsets[i]; - found = true; - break; - } - } - } - else { - // searches the path containing the current position - for (const Path& path : buffer.paths) { - if (path.contains(m_sequential_view.current.last)) { - const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); - if (sub_path_id != -1) { - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - unsigned int offset = static_cast(m_sequential_view.current.last - sub_path.first.s_id); - if (offset > 0) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) - offset = 2 * offset - 1; - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - unsigned int indices_count = buffer.indices_per_segment(); - offset = indices_count * (offset - 1) + (indices_count - 2); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - } - } - offset += static_cast(sub_path.first.i_id); - - // gets the vertex index from the index buffer on gpu - const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); -#if ENABLE_OPENGL_ES - IBufferType index = *static_cast(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, - static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), - GL_MAP_READ_BIT)); - glcheck(); - glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER)); -#else - IBufferType index = 0; - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); -#endif // ENABLE_OPENGL_ES - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // gets the position from the vertices buffer on gpu - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); -#if ENABLE_OPENGL_ES - sequential_view->current_position = *static_cast(::glMapBufferRange(GL_ARRAY_BUFFER, - static_cast(index * buffer.vertices.vertex_size_bytes()), - static_cast(buffer.vertices.position_size_bytes()), GL_MAP_READ_BIT)); - glcheck(); - glsafe(::glUnmapBuffer(GL_ARRAY_BUFFER)); -#else - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); -#endif // ENABLE_OPENGL_ES - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - sequential_view->current_offset = Vec3f::Zero(); - found = true; - break; - } - } - } - } - - if (found) - break; - } - - // second pass: filter paths by sequential data and collect them by color - RenderPath* render_path = nullptr; - for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { - TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); - const Path& path = buffer.paths[path_id]; - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) - continue; - - ColorRGBA color; - switch (path.type) - { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: - case EMoveType::Seam: { color = option_color(path.type); break; } - case EMoveType::Extrude: { - if (!top_layer_only || - m_sequential_view.current.last == global_endpoints.last || - is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) - color = extrusion_color(path); - else - color = Neutral_Color; - - break; - } - case EMoveType::Travel: { - if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) - color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); - else - color = Neutral_Color; - - break; - } - case EMoveType::Wipe: { color = Wipe_Color; break; } - default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; } - } - - RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; - if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) { - buffer.render_paths.emplace_back(key); - render_path = const_cast(&buffer.render_paths.back()); - } - - unsigned int delta_1st = 0; - if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) - delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); - - unsigned int size_in_indices = 0; - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { - unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id); - size_in_indices = buffer.indices_per_segment() * segments_count; - break; - } - default: { break; } - } - - if (size_in_indices == 0) - continue; - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - if (sub_path_id == 0 && delta_1st == 0) - size_in_indices += 6; // add 2 triangles for starting cap - if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last) - size_in_indices += 6; // add 2 triangles for ending cap - if (delta_1st > 0) - size_in_indices -= 6; // remove 2 triangles for corner cap - } - - render_path->sizes.push_back(size_in_indices); - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - delta_1st *= buffer.indices_per_segment(); - if (delta_1st > 0) { - delta_1st += 6; // skip 2 triangles for corner cap - if (sub_path_id == 0) - delta_1st += 6; // skip 2 triangles for starting cap - } - } - - render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); - -#if 0 - // check sizes and offsets against index buffer size on gpu - GLint buffer_size; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indices[render_path->ibuffer_id].ibo)); - glsafe(::glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &buffer_size)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - if (render_path->offsets.back() + render_path->sizes.back() * sizeof(IBufferType) > buffer_size) - BOOST_LOG_TRIVIAL(error) << "GCodeViewer::refresh_render_paths: Invalid render path data"; -#endif - } - - // Removes empty render paths and sort. - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer* buffer = const_cast(&m_buffers[b]); - buffer->render_paths.erase(std::remove_if(buffer->render_paths.begin(), buffer->render_paths.end(), - [](const auto &path){ return path.sizes.empty() || path.offsets.empty(); }), - buffer->render_paths.end()); - } - - // second pass: for buffers using instanced and batched models, update the instances render ranges - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel && - buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) - continue; - - buffer.model.instances.render_ranges.reset(); - - if (!buffer.visible || buffer.model.instances.s_ids.empty()) - continue; - - buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color }); - bool has_second_range = top_layer_only && m_sequential_view.current.last != m_sequential_view.global.last; - if (has_second_range) - buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, Neutral_Color }); - - if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) { - for (size_t id : buffer.model.instances.s_ids) { - if (has_second_range) { - if (id < m_sequential_view.endpoints.first) { - ++buffer.model.instances.render_ranges.ranges.front().offset; - if (id <= m_sequential_view.current.first) - ++buffer.model.instances.render_ranges.ranges.back().offset; - else - ++buffer.model.instances.render_ranges.ranges.back().count; - } - else if (id <= m_sequential_view.current.last) - ++buffer.model.instances.render_ranges.ranges.front().count; - else - break; - } - else { - if (id <= m_sequential_view.current.first) - ++buffer.model.instances.render_ranges.ranges.front().offset; - else if (id <= m_sequential_view.current.last) - ++buffer.model.instances.render_ranges.ranges.front().count; - else - break; - } - } - } - } - - // set sequential data to their final value - sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; - sequential_view->global = global_endpoints; - - // updates sequential range caps - std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); - (*sequential_range_caps)[0].reset(); - (*sequential_range_caps)[1].reset(); - - if (m_sequential_view.current.first != m_sequential_view.current.last) { - for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { - TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) - continue; - - const Path& path = buffer.paths[path_id]; - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) - continue; - - // update cap for first endpoint of current range - if (m_sequential_view.current.first > sub_path.first.s_id) { - SequentialRangeCap& cap = (*sequential_range_caps)[0]; - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - cap.buffer = &buffer; -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - cap.vao = i_buffer.vao; -#endif // ENABLE_GL_CORE_PROFILE - cap.vbo = i_buffer.vbo; - - // calculate offset into the index buffer - unsigned int offset = sub_path.first.i_id; - offset += 6; // add 2 triangles for corner cap - offset += static_cast(m_sequential_view.current.first - sub_path.first.s_id) * buffer.indices_per_segment(); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - - // extract indices from index buffer - std::array indices{ 0, 0, 0, 0, 0, 0 }; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); -#if ENABLE_OPENGL_ES - IBufferType* index_ptr = static_cast(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, - static_cast(offset * sizeof(IBufferType)), static_cast(14 * sizeof(IBufferType)), - GL_MAP_READ_BIT)); - glcheck(); - indices[0] = *(index_ptr + 0); - indices[1] = *(index_ptr + 7); - indices[2] = *(index_ptr + 1); - indices[4] = *(index_ptr + 13); - glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER)); -#else - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 0) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 7) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 1) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 13) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[4]))); -#endif // ENABLE_OPENGL_ES - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - indices[3] = indices[0]; - indices[5] = indices[1]; - - // send indices to gpu - glsafe(::glGenBuffers(1, &cap.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // extract color from render path - size_t offset_bytes = offset * sizeof(IBufferType); - for (const RenderPath& render_path : buffer.render_paths) { - if (render_path.ibuffer_id == ibuffer_id) { - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - if (render_path.contains(offset_bytes)) { - cap.color = render_path.color; - break; - } - } - } - } - } - - // update cap for last endpoint of current range - if (m_sequential_view.current.last < sub_path.last.s_id) { - SequentialRangeCap& cap = (*sequential_range_caps)[1]; - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - cap.buffer = &buffer; -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - cap.vao = i_buffer.vao; -#endif // ENABLE_GL_CORE_PROFILE - cap.vbo = i_buffer.vbo; - - // calculate offset into the index buffer - unsigned int offset = sub_path.first.i_id; - offset += 6; // add 2 triangles for corner cap - offset += static_cast(m_sequential_view.current.last - 1 - sub_path.first.s_id) * buffer.indices_per_segment(); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - - // extract indices from index buffer - std::array indices{ 0, 0, 0, 0, 0, 0 }; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); -#if ENABLE_OPENGL_ES - IBufferType* index_ptr = static_cast(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, - static_cast(offset * sizeof(IBufferType)), static_cast(17 * sizeof(IBufferType)), - GL_MAP_READ_BIT)); - glcheck(); - indices[0] = *(index_ptr + 2); - indices[1] = *(index_ptr + 4); - indices[2] = *(index_ptr + 10); - indices[5] = *(index_ptr + 16); - glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER)); -#else - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 2) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 4) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 10) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 16) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[5]))); -#endif // ENABLE_OPENGL_ES - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - indices[3] = indices[0]; - indices[4] = indices[2]; - - // send indices to gpu - glsafe(::glGenBuffers(1, &cap.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // extract color from render path - size_t offset_bytes = offset * sizeof(IBufferType); - for (const RenderPath& render_path : buffer.render_paths) { - if (render_path.ibuffer_id == ibuffer_id) { - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - if (render_path.contains(offset_bytes)) { - cap.color = render_path.color; - break; - } - } - } - } - } - - if ((*sequential_range_caps)[0].is_renderable() && (*sequential_range_caps)[1].is_renderable()) - break; - } - } - - wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float); - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t); - statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range); - } - statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} - void GCodeViewer::render_toolpaths() { const Camera& camera = wxGetApp().plater()->get_camera(); -#if !ENABLE_GL_CORE_PROFILE - const double zoom = camera.get_zoom(); -#endif // !ENABLE_GL_CORE_PROFILE + const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(static_cast(camera.get_view_matrix().matrix().cast())); + const libvgcode::Mat4x4 converted_projetion_matrix = libvgcode::convert(static_cast(camera.get_projection_matrix().matrix().cast())); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + m_viewer.set_cog_marker_scale_factor(m_cog_marker_fixed_screen_size ? 10.0f * m_cog_marker_size * camera.get_inv_zoom() : m_cog_marker_size); + m_viewer.set_tool_marker_scale_factor(m_tool_marker_fixed_screen_size ? 10.0f * m_tool_marker_size * camera.get_inv_zoom() : m_tool_marker_size); +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + m_viewer.render(converted_view_matrix, converted_projetion_matrix); - auto render_as_lines = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - shader.set_uniform(uniform_color, path.color); -#if ENABLE_GL_CORE_PROFILE - const Camera& camera = wxGetApp().plater()->get_camera(); - const std::array& viewport = camera.get_viewport(); - const float zoom = float(camera.get_zoom()); - shader.set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader.set_uniform("width", (zoom < 5.0f) ? 0.5f : (0.5f + 5.0f * (zoom - 5.0f) / (100.0f - 5.0f))); - shader.set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if ENABLE_NEW_GCODE_VIEWER_DEBUG + if (is_legend_shown()) { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + ImGuiPureWrap::set_next_window_pos(static_cast(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 1.0f, 0.0f); + ImGuiPureWrap::begin(std::string("LibVGCode Viewer Debug"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); -#if ENABLE_OPENGL_ES - for (size_t i = 0; i < path.sizes.size(); ++i) { - glsafe(::glDrawElements(GL_LINES, (GLsizei)path.sizes[i], GL_UNSIGNED_SHORT, (const void*)path.offsets[i])); - } -#else - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#endif // ENABLE_OPENGL_ES -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_lines_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - }; + if (ImGui::BeginTable("Data", 2)) { - auto render_as_triangles = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](std::vector::iterator it_path, std::vector::iterator it_end, GLShaderProgram& shader, int uniform_color) { - for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) { - const RenderPath& path = *it; - // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415. - assert(! path.sizes.empty()); - assert(! path.offsets.empty()); - shader.set_uniform(uniform_color, path.color); -#if ENABLE_OPENGL_ES - for (size_t i = 0; i < path.sizes.size(); ++i) { - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)path.sizes[i], GL_UNSIGNED_SHORT, (const void*)path.offsets[i])); - } -#else - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#endif // ENABLE_OPENGL_ES -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - }; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "# vertices"); + ImGui::TableSetColumnIndex(1); + ImGuiPureWrap::text(std::to_string(m_viewer.get_vertices_count())); - auto render_as_instanced_model = [ -#if ENABLE_GCODE_VIEWER_STATISTICS - this -#endif // ENABLE_GCODE_VIEWER_STATISTICS - ](TBuffer& buffer, GLShaderProgram & shader) { - for (auto& range : buffer.model.instances.render_ranges.ranges) { - if (range.vbo == 0 && range.count > 0) { - glsafe(::glGenBuffers(1, &range.vbo)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } + ImGui::Separator(); - if (range.vbo > 0) { - buffer.model.model.set_color(range.color); - buffer.model.model.render_instanced(range.vbo, range.count); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_instanced_models_calls_count; - m_statistics.total_instances_gpu_size += static_cast(range.count * buffer.model.instances.instance_size_bytes()); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - }; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "cpu memory"); + ImGui::TableSetColumnIndex(1); + ImGuiPureWrap::text(format_memsize(m_viewer.get_used_cpu_memory())); -#if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { -#else - auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { -#endif // ENABLE_GCODE_VIEWER_STATISTICS + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "gpu memory"); + ImGui::TableSetColumnIndex(1); + ImGuiPureWrap::text(format_memsize(m_viewer.get_used_gpu_memory())); - struct Range - { - unsigned int first; - unsigned int last; - bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } - }; - Range buffer_range = { 0, 0 }; - const size_t indices_per_instance = buffer.model.data.indices_count(); + ImGui::Separator(); - for (size_t j = 0; j < buffer.indices.size(); ++j) { - const IBuffer& i_buffer = buffer.indices[j]; - buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - glsafe(::glBindVertexArray(i_buffer.vao)); -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableVertexAttribArray(position_id)); - } - const bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableVertexAttribArray(normal_id)); - } - } + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "layers range"); + ImGui::TableSetColumnIndex(1); + const libvgcode::Interval& layers_range = m_viewer.get_layers_view_range(); + ImGuiPureWrap::text(std::to_string(layers_range[0]) + " - " + std::to_string(layers_range[1])); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "view range (full)"); + ImGui::TableSetColumnIndex(1); + const libvgcode::Interval& full_view_range = m_viewer.get_view_full_range(); + ImGuiPureWrap::text(std::to_string(full_view_range[0]) + " - " + std::to_string(full_view_range[1]) + " | " + + std::to_string(m_viewer.get_vertex_at(full_view_range[0]).gcode_id) + " - " + + std::to_string(m_viewer.get_vertex_at(full_view_range[1]).gcode_id)); - for (auto& range : buffer.model.instances.render_ranges.ranges) { - const Range range_range = { range.offset, range.offset + range.count }; - if (range_range.intersects(buffer_range)) { - shader.set_uniform("uniform_color", range.color); - const unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; - const size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); - const Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; - const size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; - if (count > 0) { - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_batched_models_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - } + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "view range (enabled)"); + ImGui::TableSetColumnIndex(1); + const libvgcode::Interval& enabled_view_range = m_viewer.get_view_enabled_range(); + ImGuiPureWrap::text(std::to_string(enabled_view_range[0]) + " - " + std::to_string(enabled_view_range[1]) + " | " + + std::to_string(m_viewer.get_vertex_at(enabled_view_range[0]).gcode_id) + " - " + + std::to_string(m_viewer.get_vertex_at(enabled_view_range[1]).gcode_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "view range (visible)"); + ImGui::TableSetColumnIndex(1); + const libvgcode::Interval& visible_view_range = m_viewer.get_view_visible_range(); + ImGuiPureWrap::text(std::to_string(visible_view_range[0]) + " - " + std::to_string(visible_view_range[1]) + " | " + + std::to_string(m_viewer.get_vertex_at(visible_view_range[0]).gcode_id) + " - " + + std::to_string(m_viewer.get_vertex_at(visible_view_range[1]).gcode_id)); - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE + auto add_range_property_row = [&imgui](const std::string& label, const std::array& range) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, label); + ImGui::TableSetColumnIndex(1); + char buf[128]; + sprintf(buf, "%.3f - %.3f", range[0], range[1]); + ImGuiPureWrap::text(buf); + }; - buffer_range.first = buffer_range.last; - } - }; + add_range_property_row("height range", m_viewer.get_color_range(libvgcode::EViewType::Height).get_range()); + add_range_property_row("width range", m_viewer.get_color_range(libvgcode::EViewType::Width).get_range()); + add_range_property_row("speed range", m_viewer.get_color_range(libvgcode::EViewType::Speed).get_range()); + add_range_property_row("fan speed range", m_viewer.get_color_range(libvgcode::EViewType::FanSpeed).get_range()); + add_range_property_row("temperature range", m_viewer.get_color_range(libvgcode::EViewType::Temperature).get_range()); + add_range_property_row("volumetric rate range", m_viewer.get_color_range(libvgcode::EViewType::VolumetricFlowRate).get_range()); + add_range_property_row("layer time linear range", m_viewer.get_color_range(libvgcode::EViewType::LayerTimeLinear).get_range()); + add_range_property_row("layer time logarithmic range", m_viewer.get_color_range(libvgcode::EViewType::LayerTimeLogarithmic).get_range()); - auto line_width = [](double zoom) { - return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); - }; - - const unsigned char begin_id = buffer_id(EMoveType::Retract); - const unsigned char end_id = buffer_id(EMoveType::Count); - - for (unsigned char i = begin_id; i < end_id; ++i) { - TBuffer& buffer = m_buffers[i]; - if (!buffer.visible || !buffer.has_data()) - continue; - - GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); - if (shader == nullptr) - continue; - - shader->start_using(); - - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); - - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { - shader->set_uniform("emission_factor", 0.25f); - render_as_instanced_model(buffer, *shader); - shader->set_uniform("emission_factor", 0.0f); - } - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { - shader->set_uniform("emission_factor", 0.25f); - const int position_id = shader->get_attrib_location("v_position"); - const int normal_id = shader->get_attrib_location("v_normal"); - render_as_batched_model(buffer, *shader, position_id, normal_id); - shader->set_uniform("emission_factor", 0.0f); - } - else { - shader->set_uniform("emission_factor", 0.15f); - const int position_id = shader->get_attrib_location("v_position"); - const int normal_id = shader->get_attrib_location("v_normal"); - const int uniform_color = shader->get_uniform_location("uniform_color"); - - auto it_path = buffer.render_paths.begin(); - for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast(buffer.indices.size()); ++ibuffer_id) { - const IBuffer& i_buffer = buffer.indices[ibuffer_id]; - // Skip all paths with ibuffer_id < ibuffer_id. - for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++it_path); - if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id) - // Not found. This shall not happen. - continue; - -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - glsafe(::glBindVertexArray(i_buffer.vao)); -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableVertexAttribArray(position_id)); - } - const bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableVertexAttribArray(normal_id)); - } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - - // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors. - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Line: { -#if ENABLE_GL_CORE_PROFILE - if (!OpenGLManager::get_gl_info().is_core_profile()) - glsafe(::glLineWidth(static_cast(line_width(camera.get_zoom())))); -#else - glsafe(::glLineWidth(static_cast(line_width(zoom)))); -#endif // ENABLE_GL_CORE_PROFILE - render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color); - break; - } - default: { break; } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE - } + ImGui::EndTable(); } - shader->stop_using(); - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - auto render_sequential_range_cap = [this, &camera] -#else - auto render_sequential_range_cap = [&camera] -#endif // ENABLE_GCODE_VIEWER_STATISTICS - (const SequentialRangeCap& cap) { - const TBuffer* buffer = cap.buffer; - GLShaderProgram* shader = wxGetApp().get_shader(buffer->shader.c_str()); - if (shader == nullptr) - return; - - shader->start_using(); - - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); - - const int position_id = shader->get_attrib_location("v_position"); - const int normal_id = shader->get_attrib_location("v_normal"); - -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - glsafe(::glBindVertexArray(cap.vao)); -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); - if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, buffer->vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes())); - glsafe(::glEnableVertexAttribArray(position_id)); - } - const bool has_normals = buffer->vertices.normal_size_floats() > 0; - if (has_normals) { - if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, buffer->vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes())); - glsafe(::glEnableVertexAttribArray(normal_id)); - } - } - - shader->set_uniform("uniform_color", cap.color); - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE - - shader->stop_using(); - }; - - for (unsigned int i = 0; i < 2; ++i) { - if (m_sequential_range_caps[i].is_renderable()) - render_sequential_range_cap(m_sequential_range_caps[i]); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + ImGui::Separator(); + + if (ImGui::BeginTable("Cog", 2)) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Cog marker scale factor"); + ImGui::TableSetColumnIndex(1); + ImGuiPureWrap::text(std::to_string(get_cog_marker_scale_factor())); + + ImGui::EndTable(); + } + + ImGui::Separator(); + + if (ImGui::BeginTable("Tool", 2)) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Tool marker scale factor"); + ImGui::TableSetColumnIndex(1); + ImGuiPureWrap::text(std::to_string(m_viewer.get_tool_marker_scale_factor())); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Tool marker z offset"); + ImGui::TableSetColumnIndex(1); + float tool_z_offset = m_viewer.get_tool_marker_offset_z(); + if (imgui.slider_float("##ToolZOffset", &tool_z_offset, 0.0f, 1.0f)) + m_viewer.set_tool_marker_offset_z(tool_z_offset); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Tool marker color"); + ImGui::TableSetColumnIndex(1); + const libvgcode::Color& color = m_viewer.get_tool_marker_color(); + std::array c = { static_cast(color[0]) / 255.0f, static_cast(color[1]) / 255.0f, static_cast(color[2]) / 255.0f }; + if (ImGui::ColorPicker3("##ToolColor", c.data())) { + m_viewer.set_tool_marker_color({ static_cast(c[0] * 255.0f), + static_cast(c[1] * 255.0f), + static_cast(c[2] * 255.0f) }); + } + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Tool marker alpha"); + ImGui::TableSetColumnIndex(1); + float tool_alpha = m_viewer.get_tool_marker_alpha(); + if (imgui.slider_float("##ToolAlpha", &tool_alpha, 0.25f, 0.75f)) + m_viewer.set_tool_marker_alpha(tool_alpha); + + ImGui::EndTable(); + } +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + + ImGui::Separator(); + if (ImGui::BeginTable("Radii", 2)) { + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Travels radius"); + ImGui::TableSetColumnIndex(1); + float travels_radius = m_viewer.get_travels_radius(); + ImGui::SetNextItemWidth(200.0f); + if (imgui.slider_float("##TravelRadius", &travels_radius, 0.05f, 0.5f)) + m_viewer.set_travels_radius(travels_radius); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Wipes radius"); + ImGui::TableSetColumnIndex(1); + float wipes_radius = m_viewer.get_wipes_radius(); + ImGui::SetNextItemWidth(200.0f); + if (imgui.slider_float("##WipesRadius", &wipes_radius, 0.05f, 0.5f)) + m_viewer.set_wipes_radius(wipes_radius); + + ImGui::EndTable(); + } + + imgui.end(); } +#endif // ENABLE_NEW_GCODE_VIEWER_DEBUG } void GCodeViewer::render_shells() @@ -3433,21 +1814,20 @@ void GCodeViewer::render_shells() void GCodeViewer::render_legend(float& legend_height) { - if (!m_legend_enabled) + if (!is_legend_shown()) return; const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); + ImGuiPureWrap::set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); const float max_height = 0.75f * static_cast(cnv_size.get_height()); const float child_height = 0.3333f * max_height; ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); -//Y - imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize| ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); + ImGuiPureWrap::begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); enum class EItemType : unsigned char { @@ -3457,10 +1837,12 @@ void GCodeViewer::render_legend(float& legend_height) Line }; - const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; - bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); + const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_viewer.get_time_mode())]; + const libvgcode::EViewType curr_view_type = m_viewer.get_view_type(); + const int curr_view_type_i = static_cast(curr_view_type); + bool show_estimated_time = time_mode.time > 0.0f && (curr_view_type == libvgcode::EViewType::FeatureType || + curr_view_type == libvgcode::EViewType::LayerTimeLinear || curr_view_type == libvgcode::EViewType::LayerTimeLogarithmic || + (curr_view_type == libvgcode::EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); const float icon_size = ImGui::GetTextLineHeight(); const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); @@ -3480,21 +1862,21 @@ void GCodeViewer::render_legend(float& legend_height) default: case EItemType::Rect: { draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color)); + ImGuiPSWrap::to_ImU32(color)); break; } case EItemType::Circle: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 16); + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGuiPSWrap::to_ImU32(color), 16); break; } case EItemType::Hexagon: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 6); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGuiPSWrap::to_ImU32(color), 6); break; } case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGuiWrapper::to_ImU32(color), 3.0f); + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGuiPSWrap::to_ImU32(color), 3.0f); break; } } @@ -3515,9 +1897,9 @@ void GCodeViewer::render_legend(float& legend_height) if (ImGui::IsItemHovered()) { if (!visible) ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGuiPureWrap::text(visible ? _u8L("Click to hide") : _u8L("Click to show")); ImGui::EndTooltip(); ImGui::PopStyleColor(); if (!visible) @@ -3530,35 +1912,33 @@ void GCodeViewer::render_legend(float& legend_height) if (!time.empty()) { ImGui::SameLine(offsets[0]); - imgui.text(time); + ImGuiPureWrap::text(time); ImGui::SameLine(offsets[1]); pos = ImGui::GetCursorScreenPos(); const float width = std::max(1.0f, percent_bar_size * percent / max_percent); draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - //B18 - ImGui::GetColorU32(ImGuiWrapper::COL_BLUE_LIGHT)); + ImGui::GetColorU32(ImGuiPureWrap::COL_BLUE_LIGHT)); ImGui::Dummy({ percent_bar_size, icon_size }); ImGui::SameLine(); char buf[64]; ::sprintf(buf, "%.1f%%", 100.0f * percent); ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); ImGui::SameLine(offsets[2]); - imgui.text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres))); + ImGuiPureWrap::text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres))); ImGui::SameLine(offsets[3]); - imgui.text(format("%1$.2f %2%", used_filament_g, grams)); + ImGuiPureWrap::text(format("%1$.2f %2%", used_filament_g, grams)); } } else { - imgui.text(label); + ImGuiPureWrap::text(label); if (!time.empty()) { ImGui::SameLine(offsets[0]); - imgui.text(time); + ImGuiPureWrap::text(time); ImGui::SameLine(offsets[1]); pos = ImGui::GetCursorScreenPos(); const float width = std::max(1.0f, percent_bar_size * percent / max_percent); draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - //B18 - ImGui::GetColorU32(ImGuiWrapper::COL_BLUE_LIGHT)); + ImGui::GetColorU32(ImGuiPureWrap::COL_BLUE_LIGHT)); ImGui::Dummy({ percent_bar_size, icon_size }); ImGui::SameLine(); char buf[64]; @@ -3567,9 +1947,9 @@ void GCodeViewer::render_legend(float& legend_height) } else if (used_filament_m > 0.0) { ImGui::SameLine(offsets[0]); - imgui.text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres))); + ImGuiPureWrap::text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres))); ImGui::SameLine(offsets[1]); - imgui.text(format("%1$.2f %2%", used_filament_g, grams)); + ImGuiPureWrap::text(format("%1$.2f %2%", used_filament_g, grams)); } } @@ -3577,67 +1957,60 @@ void GCodeViewer::render_legend(float& legend_height) ImGui::PopStyleVar(); }; - auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { - auto append_range_item = [append_item](int i, float value, unsigned int decimals) { + auto append_range = [append_item](const libvgcode::ColorRange& range, unsigned int decimals) { + auto append_range_item = [append_item, &range](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); - append_item(EItemType::Rect, Range_Colors[i], buf); + append_item(EItemType::Rect, libvgcode::convert(range.get_palette()[i]), buf); }; - if (range.count == 1) + std::vector values = range.get_values(); + if (values.size() == 1) // single item use case - append_range_item(0, range.min, decimals); - else if (range.count == 2) { + append_range_item(0, values.front(), decimals); + else if (values.size() == 2) { // two items use case - append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); - append_range_item(0, range.min, decimals); + append_range_item(static_cast(range.get_palette().size()) - 1, values.back(), decimals); + append_range_item(0, values.front(), decimals); } else { - const float step_size = range.step_size(); - for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { - append_range_item(i, range.min + static_cast(i) * step_size, decimals); + for (int i = static_cast(range.get_palette().size()) - 1; i >= 0; --i) { + append_range_item(i, values[i], decimals); } } }; - auto append_time_range = [append_item](const Extrusions::Range& range, Extrusions::Range::EType type) { - auto append_range_item = [append_item](int i, float value) { + auto append_time_range = [append_item](const libvgcode::ColorRange& range) { + auto append_range_item = [append_item, &range](int i, float value) { std::string str_value = get_time_dhms(value); if (str_value == "0s") str_value = "< 1s"; - append_item(EItemType::Rect, Range_Colors[i], str_value); + append_item(EItemType::Rect, libvgcode::convert(range.get_palette()[i]), str_value); }; - if (range.count == 1) + std::vector values = range.get_values(); + if (values.size() == 1) // single item use case - append_range_item(0, range.min); - else if (range.count == 2) { + append_range_item(0, values.front()); + else if (values.size() == 2) { // two items use case - append_range_item(static_cast(Range_Colors.size()) - 1, range.max); - append_range_item(0, range.min); + append_range_item(static_cast(range.get_palette().size()) - 1, values.back()); + append_range_item(0, values.front()); } else { - float step_size = range.step_size(type); - for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { - float value = 0.0f; - switch (type) - { - default: - case Extrusions::Range::EType::Linear: { value = range.min + static_cast(i) * step_size; break; } - case Extrusions::Range::EType::Logarithmic: { value = ::exp(::log(range.min) + static_cast(i) * step_size); break; } - } - append_range_item(i, value); + for (int i = static_cast(range.get_palette().size()) - 1; i >= 0; --i) { + append_range_item(i, values[i]); } } }; - auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { + auto append_headers = [](const std::array& texts, const std::array& offsets) { size_t i = 0; for (; i < offsets.size(); i++) { - imgui.text(texts[i]); + ImGuiPureWrap::text(texts[i]); ImGui::SameLine(offsets[i]); } - imgui.text(texts[i]); + ImGuiPureWrap::text(texts[i]); ImGui::Separator(); }; @@ -3667,11 +2040,12 @@ void GCodeViewer::render_legend(float& legend_height) if (extruder_id + 1 != static_cast(item.extruder)) continue; - if (item.type != ColorChange) + if (item.type != CustomGCode::ColorChange) continue; - const std::vector zs = m_layers.get_zs(); - auto lower_b = std::lower_bound(zs.begin(), zs.end(), item.print_z - Slic3r::DoubleSlider::epsilon()); + const std::vector zs = m_viewer.get_layers_zs(); + auto lower_b = std::lower_bound(zs.begin(), zs.end(), + static_cast(item.print_z - Slic3r::CustomGCode::epsilon())); if (lower_b == zs.end()) continue; @@ -3709,9 +2083,9 @@ void GCodeViewer::render_legend(float& legend_height) return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; - auto role_time_and_percent = [time_mode](GCodeExtrusionRole role) { - auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); - return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); + auto role_time_and_percent = [this, time_mode](libvgcode::EGCodeExtrusionRole role) { + const float time = m_viewer.get_extrusion_role_estimated_time(role); + return std::make_pair(time, time / time_mode.time); }; auto used_filament_per_role = [this, imperial_units](GCodeExtrusionRole role) { @@ -3723,6 +2097,20 @@ void GCodeViewer::render_legend(float& legend_height) return std::make_pair(it->second.first * koef, it->second.second); }; + auto toggle_extrusion_role_visibility = [this](libvgcode::EGCodeExtrusionRole role) { + const libvgcode::Interval view_visible_range = m_viewer.get_view_visible_range(); + const libvgcode::Interval view_enabled_range = m_viewer.get_view_enabled_range(); + m_viewer.toggle_extrusion_role_visibility(role); + std::optional view_visible_range_min; + std::optional view_visible_range_max; + if (view_visible_range != view_enabled_range) { + view_visible_range_min = static_cast(view_visible_range[0]); + view_visible_range_max = static_cast(view_visible_range[1]); + } + wxGetApp().plater()->update_preview_moves_slider(view_visible_range_min, view_visible_range_max); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + }; + // data used to properly align items in columns when showing time std::array offsets = { 0.0f, 0.0f, 0.0f, 0.0f }; std::vector labels; @@ -3732,17 +2120,18 @@ void GCodeViewer::render_legend(float& legend_height) std::vector used_filaments_g; float max_time_percent = 0.0f; - if (m_view_type == EViewType::FeatureType) { + if (curr_view_type == libvgcode::EViewType::FeatureType) { // calculate offsets to align time/percentage data - for (GCodeExtrusionRole role : m_roles) { - assert(role < GCodeExtrusionRole::Count); - if (role < GCodeExtrusionRole::Count) { - labels.push_back(_u8L(gcode_extrusion_role_to_string(role))); + const std::vector& roles = m_viewer.get_extrusion_roles(); + for (libvgcode::EGCodeExtrusionRole role : roles) { + assert(static_cast(role) < libvgcode::GCODE_EXTRUSION_ROLES_COUNT); + if (static_cast(role) < libvgcode::GCODE_EXTRUSION_ROLES_COUNT) { + labels.push_back(_u8L(gcode_extrusion_role_to_string(convert(role)))); auto [time, percent] = role_time_and_percent(role); times.push_back((time > 0.0f) ? short_time_ui(get_time_dhms(time)) : ""); percents.push_back(percent); max_time_percent = std::max(max_time_percent, percent); - auto [used_filament_m, used_filament_g] = used_filament_per_role(role); + auto [used_filament_m, used_filament_g] = used_filament_per_role(convert(role)); used_filaments_m.push_back(used_filament_m); used_filaments_g.push_back(used_filament_g); } @@ -3778,11 +2167,13 @@ void GCodeViewer::render_legend(float& legend_height) return ret; }; - if (m_view_type == EViewType::Tool) { + if (curr_view_type == libvgcode::EViewType::Tool) { // calculate used filaments data - used_filaments_m = std::vector(m_extruders_count, 0.0); - used_filaments_g = std::vector(m_extruders_count, 0.0); - for (size_t extruder_id : m_extruder_ids) { + const size_t extruders_count = get_extruders_count(); + used_filaments_m = std::vector(extruders_count, 0.0); + used_filaments_g = std::vector(extruders_count, 0.0); + const std::vector& used_extruders_ids = m_viewer.get_used_extruders_ids(); + for (uint8_t extruder_id : used_extruders_ids) { if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) continue; double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); @@ -3805,100 +2196,129 @@ void GCodeViewer::render_legend(float& legend_height) // selection section bool view_type_changed = false; - int old_view_type = static_cast(get_view_type()); - int view_type = old_view_type; + int new_view_type_i = curr_view_type_i; ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f }); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f }); std::vector view_options; std::vector view_options_id; - if (!m_layers_times.empty() && m_layers.size() == m_layers_times.front().size()) { - view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Fan speed (%)"), - _u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Layer time (linear)"), _u8L("Layer time (logarithmic)"), - _u8L("Tool"), _u8L("Color Print") }; - view_options_id = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + const std::vector layers_times = get_layers_times(); + if (!layers_times.empty() && layers_times.size() == m_viewer.get_layers_count()) { + view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Actual speed (mm/s)"), + _u8L("Fan speed (%)"), _u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Actual volumetric flow rate (mm³/s)"), + _u8L("Layer time (linear)"), _u8L("Layer time (logarithmic)"), _u8L("Tool"), _u8L("Color Print") }; + view_options_id = { static_cast(libvgcode::EViewType::FeatureType), + static_cast(libvgcode::EViewType::Height), + static_cast(libvgcode::EViewType::Width), + static_cast(libvgcode::EViewType::Speed), + static_cast(libvgcode::EViewType::ActualSpeed), + static_cast(libvgcode::EViewType::FanSpeed), + static_cast(libvgcode::EViewType::Temperature), + static_cast(libvgcode::EViewType::VolumetricFlowRate), + static_cast(libvgcode::EViewType::ActualVolumetricFlowRate), + static_cast(libvgcode::EViewType::LayerTimeLinear), + static_cast(libvgcode::EViewType::LayerTimeLogarithmic), + static_cast(libvgcode::EViewType::Tool), + static_cast(libvgcode::EViewType::ColorPrint) }; } else { - view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Fan speed (%)"), - _u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Tool"), _u8L("Color Print") }; - view_options_id = { 0, 1, 2, 3, 4, 5, 6, 9, 10 }; - if (view_type == 7 || view_type == 8) - view_type = 0; + view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Actual speed (mm/s)"), + _u8L("Fan speed (%)"), _u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Actual volumetric flow rate (mm³/s)"), + _u8L("Tool"), _u8L("Color Print") }; + view_options_id = { static_cast(libvgcode::EViewType::FeatureType), + static_cast(libvgcode::EViewType::Height), + static_cast(libvgcode::EViewType::Width), + static_cast(libvgcode::EViewType::Speed), + static_cast(libvgcode::EViewType::ActualSpeed), + static_cast(libvgcode::EViewType::FanSpeed), + static_cast(libvgcode::EViewType::Temperature), + static_cast(libvgcode::EViewType::VolumetricFlowRate), + static_cast(libvgcode::EViewType::ActualVolumetricFlowRate), + static_cast(libvgcode::EViewType::Tool), + static_cast(libvgcode::EViewType::ColorPrint) }; + if (new_view_type_i == static_cast(libvgcode::EViewType::LayerTimeLinear) || + new_view_type_i == static_cast(libvgcode::EViewType::LayerTimeLogarithmic)) + new_view_type_i = 0; } - auto view_type_it = std::find(view_options_id.begin(), view_options_id.end(), view_type); - int view_type_id = (view_type_it == view_options_id.end()) ? 0 : std::distance(view_options_id.begin(), view_type_it); - if (imgui.combo(std::string(), view_options, view_type_id, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f)) - view_type = view_options_id[view_type_id]; + auto new_view_type_it = std::find(view_options_id.begin(), view_options_id.end(), new_view_type_i); + int new_view_type_id = (new_view_type_it == view_options_id.end()) ? 0 : std::distance(view_options_id.begin(), new_view_type_it); + if (ImGuiPureWrap::combo(std::string(), view_options, new_view_type_id, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f)) + new_view_type_i = view_options_id[new_view_type_id]; ImGui::PopStyleColor(2); - if (old_view_type != view_type) { - set_view_type(static_cast(view_type)); + if (curr_view_type_i != new_view_type_i) { + enable_view_type_cache_load(false); + set_view_type(static_cast(new_view_type_i)); + enable_view_type_cache_load(true); wxGetApp().plater()->set_keep_current_preview_type(true); - wxGetApp().plater()->refresh_print(); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); view_type_changed = true; } + const libvgcode::EViewType new_view_type = m_viewer.get_view_type(); + // extrusion paths section -> title - if (m_view_type == EViewType::FeatureType) + if (new_view_type == libvgcode::EViewType::FeatureType) append_headers({ "", _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); - else if (m_view_type == EViewType::Tool) - append_headers({ "", _u8L("Used filament"), "", ""}, offsets); + else if (new_view_type == libvgcode::EViewType::Tool) + append_headers({ "", _u8L("Used filament"), "", "" }, offsets); else ImGui::Separator(); if (!view_type_changed) { // extrusion paths section -> items - switch (m_view_type) + switch (new_view_type) { - case EViewType::FeatureType: + case libvgcode::EViewType::FeatureType: { - max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time); - - for (size_t i = 0; i < m_roles.size(); ++i) { - GCodeExtrusionRole role = m_roles[i]; - if (role >= GCodeExtrusionRole::Count) + const float travels_time = m_viewer.get_travels_estimated_time(); + max_time_percent = std::max(max_time_percent, travels_time / time_mode.time); + const std::vector& roles = m_viewer.get_extrusion_roles(); + for (size_t i = 0; i < roles.size(); ++i) { + libvgcode::EGCodeExtrusionRole role = roles[i]; + if (static_cast(role) >= libvgcode::GCODE_EXTRUSION_ROLES_COUNT) continue; - const bool visible = is_visible(role); - append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { - m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << int(role)) : m_extrusions.role_visibility_flags | (1 << int(role)); - // update buffers' render paths - refresh_render_paths(false, false); - wxGetApp().plater()->update_preview_moves_slider(); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - } + + const bool visible = m_viewer.is_extrusion_role_visible(role); + + append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_extrusion_role_color(role)), labels[i], + visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], + [role, toggle_extrusion_role_visibility]() { toggle_extrusion_role_visibility(role); } ); } - if (m_buffers[buffer_id(EMoveType::Travel)].visible) - append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time_ui(get_time_dhms(time_mode.travel_time)), - time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); + if (m_viewer.is_option_visible(libvgcode::EOptionType::Travels)) + append_item(EItemType::Line, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), _u8L("Travel"), true, short_time_ui(get_time_dhms(travels_time)), + travels_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); break; } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } - case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } - case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; } - case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; } - case EViewType::Tool: { + case libvgcode::EViewType::Height: { append_range(m_viewer.get_color_range(libvgcode::EViewType::Height), 3); break; } + case libvgcode::EViewType::Width: { append_range(m_viewer.get_color_range(libvgcode::EViewType::Width), 3); break; } + case libvgcode::EViewType::Speed: { append_range(m_viewer.get_color_range(libvgcode::EViewType::Speed), 1); break; } + case libvgcode::EViewType::ActualSpeed: { append_range(m_viewer.get_color_range(libvgcode::EViewType::ActualSpeed), 1); break; } + case libvgcode::EViewType::FanSpeed: { append_range(m_viewer.get_color_range(libvgcode::EViewType::FanSpeed), 0); break; } + case libvgcode::EViewType::Temperature: { append_range(m_viewer.get_color_range(libvgcode::EViewType::Temperature), 0); break; } + case libvgcode::EViewType::VolumetricFlowRate: { append_range(m_viewer.get_color_range(libvgcode::EViewType::VolumetricFlowRate), 3); break; } + case libvgcode::EViewType::ActualVolumetricFlowRate: { append_range(m_viewer.get_color_range(libvgcode::EViewType::ActualVolumetricFlowRate), 3); break; } + case libvgcode::EViewType::LayerTimeLinear: { append_time_range(m_viewer.get_color_range(libvgcode::EViewType::LayerTimeLinear)); break; } + case libvgcode::EViewType::LayerTimeLogarithmic: { append_time_range(m_viewer.get_color_range(libvgcode::EViewType::LayerTimeLogarithmic)); break; } + case libvgcode::EViewType::Tool: { // shows only extruders actually used - for (unsigned char extruder_id : m_extruder_ids) { + const std::vector& used_extruders_ids = m_viewer.get_used_extruders_ids(); + for (uint8_t extruder_id : used_extruders_ids) { if (used_filaments_m[extruder_id] > 0.0 && used_filaments_g[extruder_id] > 0.0) - append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), - true, "", 0.0f, 0.0f, offsets, used_filaments_m[extruder_id], used_filaments_g[extruder_id]); + append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors()[extruder_id]), _u8L("Extruder") + " " + std::to_string(extruder_id + 1), + true, "", 0.0f, 0.0f, offsets, used_filaments_m[extruder_id], used_filaments_g[extruder_id]); } break; } - case EViewType::ColorPrint: - { - const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; + case libvgcode::EViewType::ColorPrint: { size_t total_items = 1; - for (unsigned char i : m_extruder_ids) { - total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); + const std::vector& used_extruders_ids = m_viewer.get_used_extruders_ids(); + for (uint8_t extruder_id : used_extruders_ids) { + total_items += color_print_ranges(extruder_id, m_custom_gcode_per_print_z).size(); } const bool need_scrollable = static_cast(total_items) * icon_size + (static_cast(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; @@ -3906,16 +2326,16 @@ void GCodeViewer::render_legend(float& legend_height) // add scrollable region, if needed if (need_scrollable) ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); - if (m_extruders_count == 1) { // single extruder use case - const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + if (get_extruders_count() == 1) { // single extruder use case + const std::vector>> cp_values = color_print_ranges(0, m_custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); + append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors().front()), _u8L("Default color")); else { for (int i = items_cnt; i >= 0; --i) { // create label for color change item if (i == 0) { - append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); + append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors().front()), upto_label(cp_values.front().second.first)); break; } else if (i == items_cnt) { @@ -3928,19 +2348,21 @@ void GCodeViewer::render_legend(float& legend_height) } else { // multi extruder use case // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const std::vector& used_extruders_ids = m_viewer.get_used_extruders_ids(); + for (uint8_t extruder_id : used_extruders_ids) { + const std::vector>> cp_values = color_print_ranges(extruder_id, m_custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); + append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors()[extruder_id]), _u8L("Extruder") + " " + + std::to_string(extruder_id + 1) + " " + _u8L("default color")); else { for (int j = items_cnt; j >= 0; --j) { // create label for color change item - std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); + std::string label = _u8L("Extruder") + " " + std::to_string(extruder_id + 1); if (j == 0) { label += " " + upto_label(cp_values.front().second.first); - append_item(EItemType::Rect, m_tool_colors[i], label); + append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors()[extruder_id]), label); break; } else if (j == items_cnt) { @@ -3965,7 +2387,7 @@ void GCodeViewer::render_legend(float& legend_height) } // partial estimated printing time section - if (m_view_type == EViewType::ColorPrint) { + if (new_view_type == libvgcode::EViewType::ColorPrint) { using Times = std::pair; using TimesList = std::vector>; @@ -3990,10 +2412,11 @@ void GCodeViewer::render_legend(float& legend_height) auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { PartialTimes items; - std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; - std::vector last_color(m_extruders_count); - for (size_t i = 0; i < m_extruders_count; ++i) { - last_color[i] = m_tool_colors[i]; + std::vector custom_gcode_per_print_z = m_custom_gcode_per_print_z; + const size_t extruders_count = get_extruders_count(); + std::vector last_color(extruders_count); + for (size_t i = 0; i < extruders_count; ++i) { + last_color[i] = libvgcode::convert(m_viewer.get_tool_colors()[i]); } int last_extruder_id = 1; int color_change_idx = 0; @@ -4032,8 +2455,8 @@ void GCodeViewer::render_legend(float& legend_height) return items; }; - auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array& offsets, const Times& times) { - imgui.text(_u8L("Color change")); + auto append_color_change = [](const ColorRGBA& color1, const ColorRGBA& color2, const std::array& offsets, const Times& times) { + ImGuiPureWrap::text(_u8L("Color change")); ImGui::SameLine(); float icon_size = ImGui::GetTextLineHeight(); @@ -4042,17 +2465,17 @@ void GCodeViewer::render_legend(float& legend_height) pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color1)); + ImGuiPSWrap::to_ImU32(color1)); pos.x += icon_size; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color2)); + ImGuiPSWrap::to_ImU32(color2)); ImGui::SameLine(offsets[0]); - imgui.text(short_time_ui(get_time_dhms(times.second - times.first))); + ImGuiPureWrap::text(short_time_ui(get_time_dhms(times.second - times.first))); }; - auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array& offsets, const Times& times, std::pair used_filament) { - imgui.text(_u8L("Print")); + auto append_print = [imperial_units](const ColorRGBA& color, const std::array& offsets, const Times& times, std::pair used_filament) { + ImGuiPureWrap::text(_u8L("Print")); ImGui::SameLine(); float icon_size = ImGui::GetTextLineHeight(); @@ -4061,21 +2484,21 @@ void GCodeViewer::render_legend(float& legend_height) pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGuiWrapper::to_ImU32(color)); + ImGuiPSWrap::to_ImU32(color)); ImGui::SameLine(offsets[0]); - imgui.text(short_time_ui(get_time_dhms(times.second))); + ImGuiPureWrap::text(short_time_ui(get_time_dhms(times.second))); ImGui::SameLine(offsets[1]); - imgui.text(short_time_ui(get_time_dhms(times.first))); + ImGuiPureWrap::text(short_time_ui(get_time_dhms(times.first))); if (used_filament.first > 0.0f) { char buffer[64]; ImGui::SameLine(offsets[2]); ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); - imgui.text(buffer); + ImGuiPureWrap::text(buffer); ImGui::SameLine(offsets[3]); ::sprintf(buffer, "%.2f g", used_filament.second); - imgui.text(buffer); + ImGuiPureWrap::text(buffer); } }; @@ -4122,9 +2545,9 @@ void GCodeViewer::render_legend(float& legend_height) break; } case PartialTime::EType::Pause: { - imgui.text(_u8L("Pause")); + ImGuiPureWrap::text(_u8L("Pause")); ImGui::SameLine(offsets[0]); - imgui.text(short_time_ui(get_time_dhms(item.times.second - item.times.first))); + ImGuiPureWrap::text(short_time_ui(get_time_dhms(item.times.second - item.times.first))); break; } case PartialTime::EType::ColorChange: { @@ -4139,12 +2562,12 @@ void GCodeViewer::render_legend(float& legend_height) } } - auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { + auto add_strings_row_to_table = [](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(col_1_color, col_1.c_str()); + ImGuiPureWrap::text_colored(col_1_color, col_1.c_str()); ImGui::TableSetColumnIndex(1); - imgui.text_colored(col_2_color, col_2.c_str()); + ImGuiPureWrap::text_colored(col_2_color, col_2.c_str()); }; // settings section @@ -4158,11 +2581,11 @@ void GCodeViewer::render_legend(float& legend_height) } has_settings |= has_filament_settings; bool show_settings = wxGetApp().is_gcode_viewer(); - show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); + show_settings &= (new_view_type == libvgcode::EViewType::FeatureType || new_view_type == libvgcode::EViewType::Tool); show_settings &= has_settings; if (show_settings) { ImGui::Spacing(); - imgui.title(_u8L("Settings")); + ImGuiPureWrap::title(_u8L("Settings")); auto trim_text_if_needed = [](const std::string& txt) { const float max_length = 250.0f; @@ -4173,21 +2596,22 @@ void GCodeViewer::render_legend(float& legend_height) } return txt; }; - //B18 + if (ImGui::BeginTable("Settings", 2)) { if (!m_settings_ids.printer.empty()) - add_strings_row_to_table(_u8L("Printer") + ":", ImGuiWrapper::COL_WHITE_LIGHT, - trim_text_if_needed(m_settings_ids.printer), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + add_strings_row_to_table(_u8L("Printer") + ":", ImGuiPureWrap::COL_BLUE_LIGHT, + trim_text_if_needed(m_settings_ids.printer), ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE())); if (!m_settings_ids.print.empty()) - add_strings_row_to_table(_u8L("Print settings") + ":", ImGuiWrapper::COL_WHITE_LIGHT, - trim_text_if_needed(m_settings_ids.print), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + add_strings_row_to_table(_u8L("Print settings") + ":", ImGuiPureWrap::COL_BLUE_LIGHT, + trim_text_if_needed(m_settings_ids.print), ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE())); if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { + const std::vector& used_extruders_ids = m_viewer.get_used_extruders_ids(); + for (uint8_t extruder_id : used_extruders_ids) { + if (extruder_id < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[extruder_id].empty()) { std::string txt = _u8L("Filament"); - txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); - add_strings_row_to_table(txt, ImGuiWrapper::COL_WHITE_LIGHT, - trim_text_if_needed(m_settings_ids.filament[i]), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + txt += (m_viewer.get_used_extruders_count() == 1) ? ":" : " " + std::to_string(extruder_id + 1); + add_strings_row_to_table(txt, ImGuiPureWrap::COL_BLUE_LIGHT, + trim_text_if_needed(m_settings_ids.filament[extruder_id]), ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE())); } } } @@ -4195,17 +2619,16 @@ void GCodeViewer::render_legend(float& legend_height) } } - if (m_view_type == EViewType::Width || m_view_type == EViewType::VolumetricRate) { - const auto custom_it = std::find(m_roles.begin(), m_roles.end(), GCodeExtrusionRole::Custom); - if (custom_it != m_roles.end()) { - const bool custom_visible = is_visible(GCodeExtrusionRole::Custom); - const wxString btn_text = custom_visible ? _L("Hide Custom G-code") : _L("Show Custom G-code"); - ImGui::Separator(); - if (imgui.button(btn_text, ImVec2(-1.0f, 0.0f), true)) { - m_extrusions.role_visibility_flags = custom_visible ? m_extrusions.role_visibility_flags & ~(1 << int(GCodeExtrusionRole::Custom)) : - m_extrusions.role_visibility_flags | (1 << int(GCodeExtrusionRole::Custom)); - wxGetApp().plater()->refresh_print(); - } + if (new_view_type == libvgcode::EViewType::Width || new_view_type == libvgcode::EViewType::VolumetricFlowRate || + new_view_type == libvgcode::EViewType::ActualVolumetricFlowRate) { + const std::vector& roles = m_viewer.get_extrusion_roles(); + const auto custom_it = std::find(roles.begin(), roles.end(), libvgcode::EGCodeExtrusionRole::Custom); + if (custom_it != roles.end()) { + const bool custom_visible = m_viewer.is_extrusion_role_visible((libvgcode::EGCodeExtrusionRole)GCodeExtrusionRole::Custom); + const std::string btn_text = custom_visible ? _u8L("Hide Custom G-code") : _u8L("Show Custom G-code"); + ImGui::Separator(); + if (imgui.button(btn_text, ImVec2(-1.0f, 0.0f), true)) + toggle_extrusion_role_visibility(libvgcode::EGCodeExtrusionRole::Custom); } } @@ -4213,62 +2636,59 @@ void GCodeViewer::render_legend(float& legend_height) if (show_estimated_time) { ImGui::Spacing(); std::string time_title = _u8L("Estimated printing times"); - auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) { - bool show = false; - if (m_print_statistics.modes.size() > 1 && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { - for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { - if (i != static_cast(mode) && - m_print_statistics.modes[i].time > 0.0f && - short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { - show = true; - break; - } + auto can_show_mode_button = [this](libvgcode::ETimeMode mode) { + std::vector time_strs; + for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { + if (m_print_statistics.modes[i].time > 0.0f) { + const std::string time_str = short_time(get_time_dhms(m_print_statistics.modes[i].time)); + const auto it = std::find(time_strs.begin(), time_strs.end(), time_str); + if (it == time_strs.end()) + time_strs.emplace_back(time_str); } } - return show; + return time_strs.size() > 1; }; - if (can_show_mode_button(m_time_estimate_mode)) { - switch (m_time_estimate_mode) + const libvgcode::ETimeMode time_mode_id = m_viewer.get_time_mode(); + if (can_show_mode_button(time_mode_id)) { + switch (time_mode_id) { - case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } - case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; } + case libvgcode::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; } + case libvgcode::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; } default: { assert(false); break; } } } - imgui.title(time_title + ":"); - //B18 - if (ImGui::BeginTable("Times", 2)) { - if (!time_mode.layers_times.empty()) { - add_strings_row_to_table(_u8L("First layer") + ":", ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), - short_time_ui(get_time_dhms(time_mode.layers_times.front())), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); - } + ImGuiPureWrap::title(time_title + ":"); - add_strings_row_to_table(_u8L("Total") + ":", ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), - short_time_ui(get_time_dhms(time_mode.time)), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + if (ImGui::BeginTable("Times", 2)) { + const std::vector layers_times = get_layers_times(); + if (!layers_times.empty()) + add_strings_row_to_table(_u8L("First layer") + ":", ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE()), + short_time_ui(get_time_dhms(layers_times.front())), ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE())); + + add_strings_row_to_table(_u8L("Total") + ":", ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE()), + short_time_ui(get_time_dhms(time_mode.time)), ImGuiPSWrap::to_ImVec4(ColorRGBA::WHITE())); ImGui::EndTable(); } - auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { + auto show_mode_button = [this, &imgui, can_show_mode_button](const std::string& label, libvgcode::ETimeMode mode) { if (can_show_mode_button(mode)) { - if (imgui.button(label)) { - m_time_estimate_mode = mode; - if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic) - refresh_render_paths(false, false); + if (ImGuiPureWrap::button(label)) { + m_viewer.set_time_mode(mode); imgui.set_requires_extra_frame(); } } }; - switch (m_time_estimate_mode) { - case PrintEstimatedStatistics::ETimeMode::Normal: { - show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); + switch (time_mode_id) { + case libvgcode::ETimeMode::Normal: { + show_mode_button(_u8L("Show stealth mode"), libvgcode::ETimeMode::Stealth); break; } - case PrintEstimatedStatistics::ETimeMode::Stealth: { - show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); + case libvgcode::ETimeMode::Stealth: { + show_mode_button(_u8L("Show normal mode"), libvgcode::ETimeMode::Normal); break; } default : { assert(false); break; } @@ -4276,39 +2696,57 @@ void GCodeViewer::render_legend(float& legend_height) } // toolbar section - auto toggle_button = [this, &imgui, icon_size](Preview::OptionType type, const std::string& name, + auto toggle_button = [this, icon_size](Preview::OptionType type, const std::string& name, std::function draw_callback) { - auto is_flag_set = [](unsigned int flags, unsigned int flag) { - return (flags & (1 << flag)) != 0; - }; + bool active = false; +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + active = (type == Preview::OptionType::Shells) ? m_shells.visible : m_viewer.is_option_visible(libvgcode::convert(type)); +#else + switch (type) + { + case Preview::OptionType::CenterOfGravity: { active = m_cog.is_visible(); break; } + case Preview::OptionType::ToolMarker: { active = m_sequential_view.marker.is_visible(); break; } + case Preview::OptionType::Shells: { active = m_shells.visible; break; } + default: { active = m_viewer.is_option_visible(libvgcode::convert(type)); break; } + } +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS - auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { - return active ? (flags | (1 << flag)) : (flags & ~(1 << flag)); - }; - - unsigned int flags = get_options_visibility_flags(); - unsigned int flag = static_cast(type); - bool active = is_flag_set(flags, flag); - - if (imgui.draw_radio_button(name, 1.5f * icon_size, active, draw_callback)) { - unsigned int new_flags = set_flag(flags, flag, !active); - set_options_visibility_from_flags(new_flags); - - const unsigned int diff_flags = flags ^ new_flags; - if (m_view_type == GCodeViewer::EViewType::Feedrate && is_flag_set(diff_flags, static_cast(Preview::OptionType::Travel))) - wxGetApp().plater()->refresh_print(); - else { - bool keep_first = m_sequential_view.current.first != m_sequential_view.global.first; - bool keep_last = m_sequential_view.current.last != m_sequential_view.global.last; - wxGetApp().plater()->get_current_canvas3D()->refresh_gcode_preview_render_paths(keep_first, keep_last); + if (ImGuiPureWrap::draw_radio_button(name, 1.5f * icon_size, active, draw_callback)) { + // check whether we need to keep the current visible range + libvgcode::Interval view_visible_range = m_viewer.get_view_visible_range(); + const libvgcode::Interval view_enabled_range = m_viewer.get_view_enabled_range(); + // update visible range to take in account for skipped moves + const uint32_t view_first_visible_gcode_id = m_viewer.get_vertex_at(view_visible_range[0]).gcode_id; + while (view_visible_range[0] > view_enabled_range[0] && view_first_visible_gcode_id == m_viewer.get_vertex_at(view_visible_range[0] - 1).gcode_id) { + --view_visible_range[0]; } - wxGetApp().plater()->update_preview_moves_slider(); + const bool keep_visible_range = view_visible_range != view_enabled_range; +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + if (type == Preview::OptionType::Shells) + m_shells.visible = !active; + else + m_viewer.toggle_option_visibility(libvgcode::convert(type)); +#else + switch (type) + { + case Preview::OptionType::CenterOfGravity: { m_cog.set_visible(!active); break; } + case Preview::OptionType::ToolMarker: { m_sequential_view.marker.set_visible(!active); break; } + case Preview::OptionType::Shells: { m_shells.visible = !active; break; } + default: { + m_viewer.toggle_option_visibility(libvgcode::convert(type)); + break; + } + } +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + std::optional view_visible_range_min = keep_visible_range ? std::optional{ static_cast(view_visible_range[0]) } : std::nullopt; + std::optional view_visible_range_max = keep_visible_range ? std::optional{ static_cast(view_visible_range[1]) } : std::nullopt; + wxGetApp().plater()->update_preview_moves_slider(view_visible_range_min, view_visible_range_max); } if (ImGui::IsItemHovered()) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - imgui.text(name); + ImGuiPureWrap::text(name); ImGui::EndTooltip(); ImGui::PopStyleColor(); } @@ -4320,53 +2758,53 @@ void GCodeViewer::render_legend(float& legend_height) ImGui::Spacing(); toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendTravel); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendWipe); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendRetract); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendDeretract); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendSeams); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendToolChanges); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendColorChanges); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendPausePrints); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendCustomGCodes); - }); + }); ImGui::SameLine(); toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendCOG); - }); + }); ImGui::SameLine(); if (!wxGetApp().is_gcode_viewer()) { toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendShells); - }); + }); ImGui::SameLine(); } toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) { imgui.draw_icon(window, pos, size, ImGui::LegendToolMarker); - }); + }); bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth(); if (m_legend_resizer.dirty || size_dirty != m_legend_resizer.dirty) { @@ -4377,146 +2815,9 @@ void GCodeViewer::render_legend(float& legend_height) legend_height = ImGui::GetWindowHeight(); - imgui.end(); + ImGuiPureWrap::end(); ImGui::PopStyleVar(); } -#if ENABLE_GCODE_VIEWER_STATISTICS -void GCodeViewer::render_statistics() -{ - static const float offset = 275.0f; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - //B18 - auto add_time = [&imgui](const std::string& label, int64_t time) { - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, label); - ImGui::SameLine(offset); - imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast(time) * 0.001f) + ")"); - }; - - auto add_memory = [&imgui](const std::string& label, int64_t memory) { - auto format_string = [memory](const std::string& units, float value) { - return std::to_string(memory) + " bytes (" + - Slic3r::float_to_string_decimal_point(float(memory) * value, 3) - + " " + units + ")"; - }; - //B18 - static const float kb = 1024.0f; - static const float inv_kb = 1.0f / kb; - static const float mb = 1024.0f * kb; - static const float inv_mb = 1.0f / mb; - static const float gb = 1024.0f * mb; - static const float inv_gb = 1.0f / gb; - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, label); - ImGui::SameLine(offset); - if (static_cast(memory) < mb) - imgui.text(format_string("KB", inv_kb)); - else if (static_cast(memory) < gb) - imgui.text(format_string("MB", inv_mb)); - else - imgui.text(format_string("GB", inv_gb)); - }; - - auto add_counter = [&imgui](const std::string& label, int64_t counter) { - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, label); - ImGui::SameLine(offset); - imgui.text(std::to_string(counter)); - }; - - imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); - ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f }); - imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); - ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - - if (ImGui::CollapsingHeader("Time")) { - add_time(std::string("GCodeProcessor:"), m_statistics.results_time); - - ImGui::Separator(); - add_time(std::string("Load:"), m_statistics.load_time); - add_time(std::string(" Load vertices:"), m_statistics.load_vertices); - add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices); - add_time(std::string(" Load indices:"), m_statistics.load_indices); - add_time(std::string("Refresh:"), m_statistics.refresh_time); - add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); - } - - if (ImGui::CollapsingHeader("OpenGL calls")) { - add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); - add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); - add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); - ImGui::Separator(); - add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count); - add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count); - } - - if (ImGui::CollapsingHeader("CPU memory")) { - add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); - - ImGui::Separator(); - add_memory(std::string("Paths:"), m_statistics.paths_size); - add_memory(std::string("Render paths:"), m_statistics.render_paths_size); - add_memory(std::string("Models instances:"), m_statistics.models_instances_size); - } - - if (ImGui::CollapsingHeader("GPU memory")) { - add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size); - add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size); - add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size); - ImGui::Separator(); - add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size); - add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size); - } - - if (ImGui::CollapsingHeader("Other")) { - add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); - add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count); - add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); - add_counter(std::string("Instances count:"), m_statistics.instances_count); - add_counter(std::string("Batched count:"), m_statistics.batched_count); - ImGui::Separator(); - add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count); - add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); - } - - imgui.end(); -} -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) const -{ - if (Slic3r::get_logging_level() >= 5) { - int64_t paths_size = 0; - int64_t render_paths_size = 0; - for (const TBuffer& buffer : m_buffers) { - paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - } - int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); - layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_ranges(), Layers::Range); - BOOST_LOG_TRIVIAL(trace) << label - << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" - << log_memory_info(); - } -} - -ColorRGBA GCodeViewer::option_color(EMoveType move_type) const -{ - switch (move_type) - { - case EMoveType::Tool_change: { return Options_Colors[static_cast(EOptionsColors::ToolChanges)]; } - case EMoveType::Color_change: { return Options_Colors[static_cast(EOptionsColors::ColorChanges)]; } - case EMoveType::Pause_Print: { return Options_Colors[static_cast(EOptionsColors::PausePrints)]; } - case EMoveType::Custom_GCode: { return Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; } - case EMoveType::Retract: { return Options_Colors[static_cast(EOptionsColors::Retractions)]; } - case EMoveType::Unretract: { return Options_Colors[static_cast(EOptionsColors::Unretractions)]; } - case EMoveType::Seam: { return Options_Colors[static_cast(EOptionsColors::Seams)]; } - default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; } - } -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 80c345a..dd34ff9 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -6,7 +6,9 @@ #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GLModel.hpp" -#include +#include "LibVGCode/LibVGCodeWrapper.hpp" +// needed for tech VGCODE_ENABLE_COG_AND_TOOL_MARKERS +#include "../../src/libvgcode/include/Types.hpp" #include #include @@ -22,357 +24,6 @@ namespace GUI { class GCodeViewer { - using IBufferType = unsigned short; - using VertexBuffer = std::vector; - using MultiVertexBuffer = std::vector; - using IndexBuffer = std::vector; - using MultiIndexBuffer = std::vector; - using InstanceBuffer = std::vector; - using InstanceIdBuffer = std::vector; - using InstancesOffsets = std::vector; - - static const std::array(GCodeExtrusionRole::Count)> Extrusion_Role_Colors; - static const std::vector Options_Colors; - static const std::vector Travel_Colors; - static const std::vector Range_Colors; - static const ColorRGBA Wipe_Color; - static const ColorRGBA Neutral_Color; - - enum class EOptionsColors : unsigned char - { - Retractions, - Unretractions, - Seams, - ToolChanges, - ColorChanges, - PausePrints, - CustomGCodes - }; - - // vbo buffer containing vertices data used to render a specific toolpath type - struct VBuffer - { - enum class EFormat : unsigned char - { - // vertex format: 3 floats -> position.x|position.y|position.z - Position, - // vertex format: 4 floats -> position.x|position.y|position.z|normal.x - PositionNormal1, - // vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z - PositionNormal3 - }; - - EFormat format{ EFormat::Position }; -#if ENABLE_GL_CORE_PROFILE - // vaos id - std::vector vaos; -#endif // ENABLE_GL_CORE_PROFILE - // vbos id - std::vector vbos; - // sizes of the buffers, in bytes, used in export to obj - std::vector sizes; - // count of vertices, updated after data are sent to gpu - size_t count{ 0 }; - - size_t data_size_bytes() const { return count * vertex_size_bytes(); } - // We set 65536 as max count of vertices inside a vertex buffer to allow - // to use unsigned short in place of unsigned int for indices in the index buffer, to save memory - size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); } - - size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); } - size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } - - size_t position_offset_floats() const { return 0; } - size_t position_offset_bytes() const { return position_offset_floats() * sizeof(float); } - - size_t position_size_floats() const { return 3; } - size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } - - size_t normal_offset_floats() const { - assert(format == EFormat::PositionNormal1 || format == EFormat::PositionNormal3); - return position_size_floats(); - } - size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); } - - size_t normal_size_floats() const { - switch (format) - { - case EFormat::PositionNormal1: { return 1; } - case EFormat::PositionNormal3: { return 3; } - default: { return 0; } - } - } - size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); } - - void reset(); - }; - - // buffer containing instances data used to render a toolpaths using instanced or batched models - // instance record format: - // instanced models: 5 floats -> position.x|position.y|position.z|width|height (which are sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced()) - // batched models: 3 floats -> position.x|position.y|position.z - struct InstanceVBuffer - { - // ranges used to render only subparts of the intances - struct Ranges - { - struct Range - { - // offset in bytes of the 1st instance to render - unsigned int offset; - // count of instances to render - unsigned int count; - // vbo id - unsigned int vbo{ 0 }; - // Color to apply to the instances - ColorRGBA color; - }; - - std::vector ranges; - - void reset(); - }; - - enum class EFormat : unsigned char - { - InstancedModel, - BatchedModel - }; - - EFormat format; - - // cpu-side buffer containing all instances data - InstanceBuffer buffer; - // indices of the moves for all instances - std::vector s_ids; - // position offsets, used to show the correct value of the tool position - InstancesOffsets offsets; - Ranges render_ranges; - - size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); } - - size_t instance_size_floats() const { - switch (format) - { - case EFormat::InstancedModel: { return 5; } - case EFormat::BatchedModel: { return 3; } - default: { return 0; } - } - } - size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); } - - void reset(); - }; - - // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type - struct IBuffer - { -#if ENABLE_GL_CORE_PROFILE - // id of the associated vertex array buffer - unsigned int vao{ 0 }; -#endif // ENABLE_GL_CORE_PROFILE - // id of the associated vertex buffer - unsigned int vbo{ 0 }; - // ibo id - unsigned int ibo{ 0 }; - // count of indices, updated after data are sent to gpu - size_t count{ 0 }; - - void reset(); - }; - - // Used to identify different toolpath sub-types inside a IBuffer - struct Path - { - struct Endpoint - { - // index of the buffer in the multibuffer vector - // the buffer type may change: - // it is the vertex buffer while extracting vertices data, - // the index buffer while extracting indices data - unsigned int b_id{ 0 }; - // index into the buffer - size_t i_id{ 0 }; - // move id - size_t s_id{ 0 }; - Vec3f position{ Vec3f::Zero() }; - }; - - struct Sub_Path - { - Endpoint first; - Endpoint last; - - bool contains(size_t s_id) const { - return first.s_id <= s_id && s_id <= last.s_id; - } - }; - - EMoveType type{ EMoveType::Noop }; - GCodeExtrusionRole role{ GCodeExtrusionRole::None }; - float delta_extruder{ 0.0f }; - float height{ 0.0f }; - float width{ 0.0f }; - float feedrate{ 0.0f }; - float fan_speed{ 0.0f }; - float temperature{ 0.0f }; - float volumetric_rate{ 0.0f }; - unsigned char extruder_id{ 0 }; - unsigned char cp_color_id{ 0 }; - std::vector sub_paths; - - bool matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const; - size_t vertices_count() const { - return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; - } - bool contains(size_t s_id) const { - return sub_paths.empty() ? false : sub_paths.front().first.s_id <= s_id && s_id <= sub_paths.back().last.s_id; - } - int get_id_of_sub_path_containing(size_t s_id) const { - if (sub_paths.empty()) - return -1; - else { - for (int i = 0; i < static_cast(sub_paths.size()); ++i) { - if (sub_paths[i].contains(s_id)) - return i; - } - return -1; - } - } - void add_sub_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { - Endpoint endpoint = { b_id, i_id, s_id, move.position }; - sub_paths.push_back({ endpoint , endpoint }); - } - }; - - // Used to batch the indices needed to render the paths - struct RenderPath - { - // Index of the parent tbuffer - unsigned char tbuffer_id; - // Render path property - ColorRGBA color; - // Index of the buffer in TBuffer::indices - unsigned int ibuffer_id; - // Render path content - // Index of the path in TBuffer::paths - unsigned int path_id; - std::vector sizes; - std::vector offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements()) - bool contains(size_t offset) const { - for (size_t i = 0; i < offsets.size(); ++i) { - if (offsets[i] <= offset && offset <= offsets[i] + static_cast(sizes[i] * sizeof(IBufferType))) - return true; - } - return false; - } - }; - struct RenderPathPropertyLower { - bool operator() (const RenderPath &l, const RenderPath &r) const { - if (l.tbuffer_id < r.tbuffer_id) - return true; - if (l.color < r.color) - return true; - else if (l.color > r.color) - return false; - return l.ibuffer_id < r.ibuffer_id; - } - }; - struct RenderPathPropertyEqual { - bool operator() (const RenderPath &l, const RenderPath &r) const { - return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color; - } - }; - - // buffer containing data for rendering a specific toolpath type - struct TBuffer - { - enum class ERenderPrimitiveType : unsigned char - { - Line, - Triangle, - InstancedModel, - BatchedModel - }; - - ERenderPrimitiveType render_primitive_type; - - // buffers for point, line and triangle primitive types - VBuffer vertices; - std::vector indices; - - struct Model - { - GLModel model; - ColorRGBA color; - InstanceVBuffer instances; - GLModel::Geometry data; - - void reset(); - }; - - // contain the buffer for model primitive types - Model model; - - std::string shader; - std::vector paths; - std::vector render_paths; - bool visible{ false }; - - void reset(); - - // b_id index of buffer contained in this->indices - // i_id index of first index contained in this->indices[b_id] - // s_id index of first vertex contained in this->vertices - void add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); - - unsigned int max_vertices_per_segment() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: { return 2; } - case ERenderPrimitiveType::Triangle: { return 8; } - default: { return 0; } - } - } - - size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast(max_vertices_per_segment()); } - size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); } - unsigned int indices_per_segment() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: { return 2; } - case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles - default: { return 0; } - } - } - size_t indices_per_segment_size_bytes() const { return static_cast(indices_per_segment() * sizeof(IBufferType)); } - unsigned int max_indices_per_segment() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: { return 2; } - case ERenderPrimitiveType::Triangle: { return 36; } // 3 indices x 12 triangles - default: { return 0; } - } - } - size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } - - bool has_data() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: - case ERenderPrimitiveType::Triangle: { - return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; - } - case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); } - case ERenderPrimitiveType::BatchedModel: { - return !model.data.vertices.empty() && !model.data.indices.empty() && - !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; - } - default: { return false; } - } - } - }; - // helper to render shells struct Shells { @@ -386,16 +37,23 @@ class GCodeViewer { GLModel m_model; bool m_visible{ false }; +#if !VGCODE_ENABLE_COG_AND_TOOL_MARKERS // whether or not to render the model with fixed screen size - bool m_fixed_size{ true }; + bool m_fixed_screen_size{ true }; +#endif // !VGCODE_ENABLE_COG_AND_TOOL_MARKERS + float m_scale_factor{ 1.0f }; double m_total_mass{ 0.0 }; - Vec3d m_position{ Vec3d::Zero() }; + Vec3d m_total_position{ Vec3d::Zero() }; public: - void render(); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + void render(bool fixed_screen_size); +#else + void render(); +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS void reset() { - m_position = Vec3d::Zero(); + m_total_position = Vec3d::Zero(); m_total_mass = 0.0; } @@ -403,252 +61,51 @@ class GCodeViewer void set_visible(bool visible) { m_visible = visible; } void add_segment(const Vec3d& v1, const Vec3d& v2, double mass) { - assert(mass > 0.0); - m_position += mass * 0.5 * (v1 + v2); - m_total_mass += mass; + if (mass > 0.0) { + m_total_position += mass * 0.5 * (v1 + v2); + m_total_mass += mass; + } } - Vec3d cog() const { return (m_total_mass > 0.0) ? (Vec3d)(m_position / m_total_mass) : Vec3d::Zero(); } + Vec3d cog() const { return (m_total_mass > 0.0) ? (Vec3d)(m_total_position / m_total_mass) : Vec3d::Zero(); } private: +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + void init(bool fixed_screen_size) { +#else void init() { +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS if (m_model.is_initialized()) return; - const float radius = m_fixed_size ? 10.0f : 1.0f; +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + const float radius = fixed_screen_size ? 10.0f : 1.0f; +#else + const float radius = m_fixed_screen_size ? 10.0f : 1.0f; +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS m_model.init_from(smooth_sphere(32, radius)); } }; - // helper to render extrusion paths - struct Extrusions - { - struct Range - { - enum class EType : unsigned char - { - Linear, - Logarithmic - }; - - float min; - float max; - unsigned int count; - - Range() { reset(); } - - void update_from(const float value) { - if (value != max && value != min) - ++count; - min = std::min(min, value); - max = std::max(max, value); - } - void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; } - - float step_size(EType type = EType::Linear) const; - ColorRGBA get_color_at(float value, EType type = EType::Linear) const; - }; - - struct Ranges - { - // Color mapping by layer height. - Range height; - // Color mapping by extrusion width. - Range width; - // Color mapping by feedrate. - Range feedrate; - // Color mapping by fan speed. - Range fan_speed; - // Color mapping by volumetric extrusion rate. - Range volumetric_rate; - // Color mapping by extrusion temperature. - Range temperature; - // Color mapping by layer time. - std::array(PrintEstimatedStatistics::ETimeMode::Count)> layer_time; - - void reset() { - height.reset(); - width.reset(); - feedrate.reset(); - fan_speed.reset(); - volumetric_rate.reset(); - temperature.reset(); - for (auto& range : layer_time) { - range.reset(); - } - } - }; - - unsigned int role_visibility_flags{ 0 }; - Ranges ranges; - - void reset_role_visibility_flags() { - role_visibility_flags = 0; - for (uint32_t i = 0; i < uint32_t(GCodeExtrusionRole::Count); ++i) { - role_visibility_flags |= 1 << i; - } - } - - void reset_ranges() { ranges.reset(); } - }; - - class Layers - { - public: - struct Range - { - size_t first{ 0 }; - size_t last{ 0 }; - - bool operator == (const Range& other) const { return first == other.first && last == other.last; } - bool operator != (const Range& other) const { return !operator==(other); } - bool contains(size_t id) const { return first <= id && id <= last; } - }; - - private: - std::vector m_zs; - std::vector m_ranges; - - public: - void append(double z, const Range& range) { - m_zs.emplace_back(z); - m_ranges.emplace_back(range); - } - - void reset() { - m_zs = std::vector(); - m_ranges = std::vector(); - } - - size_t size() const { return m_zs.size(); } - bool empty() const { return m_zs.empty(); } - const std::vector& get_zs() const { return m_zs; } - const std::vector& get_ranges() const { return m_ranges; } - std::vector& get_ranges() { return m_ranges; } - double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } - Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); } - int get_l_at(double z) const { - auto iter = std::upper_bound(m_zs.begin(), m_zs.end(), z); - return std::distance(m_zs.begin(), iter); - } - - bool operator != (const Layers& other) const { - if (m_zs != other.m_zs) - return true; - if (m_ranges != other.m_ranges) - return true; - return false; - } - }; - - // used to render the toolpath caps of the current sequential range - // (i.e. when sliding on the horizontal slider) - struct SequentialRangeCap - { - TBuffer* buffer{ nullptr }; -#if ENABLE_GL_CORE_PROFILE - unsigned int vao{ 0 }; -#endif // ENABLE_GL_CORE_PROFILE - unsigned int vbo{ 0 }; - unsigned int ibo{ 0 }; - ColorRGBA color; - - ~SequentialRangeCap(); - bool is_renderable() const { return buffer != nullptr; } - void reset(); - size_t indices_count() const { return 6; } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - struct Statistics - { - // time - int64_t results_time{ 0 }; - int64_t load_time{ 0 }; - int64_t load_vertices{ 0 }; - int64_t smooth_vertices{ 0 }; - int64_t load_indices{ 0 }; - int64_t refresh_time{ 0 }; - int64_t refresh_paths_time{ 0 }; - // opengl calls - int64_t gl_multi_lines_calls_count{ 0 }; - int64_t gl_multi_triangles_calls_count{ 0 }; - int64_t gl_triangles_calls_count{ 0 }; - int64_t gl_instanced_models_calls_count{ 0 }; - int64_t gl_batched_models_calls_count{ 0 }; - // memory - int64_t results_size{ 0 }; - int64_t total_vertices_gpu_size{ 0 }; - int64_t total_indices_gpu_size{ 0 }; - int64_t total_instances_gpu_size{ 0 }; - int64_t max_vbuffer_gpu_size{ 0 }; - int64_t max_ibuffer_gpu_size{ 0 }; - int64_t paths_size{ 0 }; - int64_t render_paths_size{ 0 }; - int64_t models_instances_size{ 0 }; - // other - int64_t travel_segments_count{ 0 }; - int64_t wipe_segments_count{ 0 }; - int64_t extrude_segments_count{ 0 }; - int64_t instances_count{ 0 }; - int64_t batched_count{ 0 }; - int64_t vbuffers_count{ 0 }; - int64_t ibuffers_count{ 0 }; - - void reset_all() { - reset_times(); - reset_opengl(); - reset_sizes(); - reset_others(); - } - - void reset_times() { - results_time = 0; - load_time = 0; - load_vertices = 0; - smooth_vertices = 0; - load_indices = 0; - refresh_time = 0; - refresh_paths_time = 0; - } - - void reset_opengl() { - gl_multi_lines_calls_count = 0; - gl_multi_triangles_calls_count = 0; - gl_triangles_calls_count = 0; - gl_instanced_models_calls_count = 0; - gl_batched_models_calls_count = 0; - } - - void reset_sizes() { - results_size = 0; - total_vertices_gpu_size = 0; - total_indices_gpu_size = 0; - total_instances_gpu_size = 0; - max_vbuffer_gpu_size = 0; - max_ibuffer_gpu_size = 0; - paths_size = 0; - render_paths_size = 0; - models_instances_size = 0; - } - - void reset_others() { - travel_segments_count = 0; - wipe_segments_count = 0; - extrude_segments_count = 0; - instances_count = 0; - batched_count = 0; - vbuffers_count = 0; - ibuffers_count = 0; - } - }; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - public: - //B43 - enum class EViewType : unsigned char; struct SequentialView { +#if ENABLE_ACTUAL_SPEED_DEBUG + struct ActualSpeedImguiWidget + { + std::pair y_range = { 0.0f, 0.0f }; + std::vector> levels; + struct Item + { + float pos{ 0.0f }; + float speed{ 0.0f }; + bool internal{ false }; + }; + std::vector data; + int plot(const char* label, const std::array& frame_size = { 0.0f, 0.0f }); + }; +#endif // ENABLE_ACTUAL_SPEED_DEBUG + class Marker { GLModel m_model; @@ -663,18 +120,33 @@ public: // z offset of the model float m_model_z_offset{ 0.5f }; bool m_visible{ true }; - //B43 - GCodeProcessorResult::MoveVertex m_curr_move; + bool m_fixed_screen_size{ false }; + float m_scale_factor{ 1.0f }; +#if ENABLE_ACTUAL_SPEED_DEBUG + ActualSpeedImguiWidget m_actual_speed_imgui_widget; +#endif // ENABLE_ACTUAL_SPEED_DEBUG public: void init(); const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } - void set_world_position(const Vec3f& position); + void set_world_position(const Vec3f& position) { m_world_position = position; } void set_world_offset(const Vec3f& offset) { m_world_offset = offset; } void set_z_offset(float z_offset) { m_z_offset = z_offset; } +#if ENABLE_ACTUAL_SPEED_DEBUG + void set_actual_speed_y_range(const std::pair& y_range) { + m_actual_speed_imgui_widget.y_range = y_range; + } + void set_actual_speed_levels(const std::vector>& levels) { + m_actual_speed_imgui_widget.levels = levels; + } + void set_actual_speed_data(const std::vector& data) { + m_actual_speed_imgui_widget.data = data; + } +#endif // ENABLE_ACTUAL_SPEED_DEBUG + bool is_visible() const { return m_visible; } void set_visible(bool visible) { m_visible = visible; } //B43 @@ -690,6 +162,7 @@ public: std::string parameters; std::string comment; }; + struct Range { std::optional min; @@ -704,6 +177,7 @@ public: return empty() ? 0 : *this->max - *this->min + 1; } }; + bool m_visible{ true }; std::string m_filename; bool m_is_binary_file{ false }; @@ -720,7 +194,6 @@ public: m_lines_cache.clear(); m_filename.clear(); } - void toggle_visibility() { m_visible = !m_visible; } void render(float top, float bottom, size_t curr_line_id); @@ -728,48 +201,15 @@ public: void add_gcode_line_to_lines_cache(const std::string& src); }; - struct Endpoints - { - size_t first{ 0 }; - size_t last{ 0 }; - }; - - bool skip_invisible_moves{ false }; - Endpoints endpoints; - Endpoints current; - Endpoints last_current; - Endpoints global; - Vec3f current_position{ Vec3f::Zero() }; - Vec3f current_offset{ Vec3f::Zero() }; Marker marker; GCodeWindow gcode_window; - std::vector gcode_ids; - //B43 - void render(float legend_height, EViewType &view_type); - }; - enum class EViewType : unsigned char - { - FeatureType, - Height, - Width, - Feedrate, - FanSpeed, - Temperature, - VolumetricRate, - LayerTimeLinear, - LayerTimeLogarithmic, - Tool, - ColorPrint, - Count + void render(float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id); }; private: bool m_gl_data_initialized{ false }; unsigned int m_last_result_id{ 0 }; - EViewType m_last_view_type{ EViewType::Count }; - size_t m_moves_count{ 0 }; - std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; // bounding box of shells @@ -782,20 +222,29 @@ private: float m_max_print_height{ 0.0f }; float m_z_offset{ 0.0f }; - std::vector m_tool_colors; - Layers m_layers; - std::array m_layers_z_range; - std::vector m_roles; size_t m_extruders_count; - std::vector m_extruder_ids; std::vector m_filament_diameters; std::vector m_filament_densities; - Extrusions m_extrusions; SequentialView m_sequential_view; Shells m_shells; COG m_cog; - EViewType m_view_type{ EViewType::FeatureType }; +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + // whether or not to render the cog model with fixed screen size + bool m_cog_marker_fixed_screen_size{ true }; + float m_cog_marker_size{ 1.0f }; + bool m_tool_marker_fixed_screen_size{ false }; + float m_tool_marker_size{ 1.0f }; +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + bool m_legend_visible{ true }; bool m_legend_enabled{ true }; + struct ViewTypeCache + { + bool write{ false }; + bool load{ false }; + libvgcode::EViewType value{ libvgcode::EViewType::FeatureType }; + }; + ViewTypeCache m_view_type_cache; + struct LegendResizer { bool dirty{ true }; @@ -803,13 +252,7 @@ private: }; LegendResizer m_legend_resizer; PrintEstimatedStatistics m_print_statistics; - PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; -#if ENABLE_GCODE_VIEWER_STATISTICS - Statistics m_statistics; -#endif // ENABLE_GCODE_VIEWER_STATISTICS GCodeProcessorResult::SettingsIds m_settings_ids; - std::array m_sequential_range_caps; - std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> m_layers_times; std::vector m_custom_gcode_per_print_z; @@ -817,6 +260,9 @@ private: ConflictResultOpt m_conflict_result; + libvgcode::Viewer m_viewer; + bool m_loaded_as_preview{ false }; + public: GCodeViewer(); ~GCodeViewer() { reset(); } @@ -824,61 +270,78 @@ public: void init(); // extract rendering data from the given parameters - void load(const GCodeProcessorResult& gcode_result, const Print& print); - // recalculate ranges in dependence of what is visible and sets tool/print colors - void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); - void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; + void load_as_gcode(const GCodeProcessorResult& gcode_result, const Print& print, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors); + void load_as_preview(libvgcode::GCodeInputData&& data); void update_shells_color_by_extruder(const DynamicPrintConfig* config); void reset(); void render(); - void render_cog() { m_cog.render(); } +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + void render_cog() { + if (!m_loaded_as_preview && m_viewer.get_layers_count() > 0) + m_cog.render(m_cog_marker_fixed_screen_size); + } +#else + void render_cog() { + if (!m_loaded_as_preview && m_viewer.get_layers_count() > 0) + m_cog.render(); + } +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + bool has_data() const { return !m_viewer.get_extrusion_roles().empty(); } - bool has_data() const { return !m_roles.empty(); } bool can_export_toolpaths() const; const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; } const BoundingBoxf3& get_shells_bounding_box() const { return m_shells_bounding_box; } + const BoundingBoxf3& get_max_bounding_box() const { BoundingBoxf3& max_bounding_box = const_cast(m_max_bounding_box); if (!max_bounding_box.defined) { if (m_shells_bounding_box.defined) - max_bounding_box = m_shells_bounding_box; + max_bounding_box = m_shells_bounding_box; if (m_paths_bounding_box.defined) { - max_bounding_box.merge(m_paths_bounding_box); - max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); - } + max_bounding_box.merge(m_paths_bounding_box); + max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); + } } return m_max_bounding_box; } - const std::vector& get_layers_zs() const { return m_layers.get_zs(); } + + std::vector get_layers_zs() const { + const std::vector zs = m_viewer.get_layers_zs(); + std::vector ret; + std::transform(zs.begin(), zs.end(), std::back_inserter(ret), [](float z) { return static_cast(z); }); + return ret; + } + std::vector get_layers_times() const { return m_viewer.get_layers_estimated_times(); } const SequentialView& get_sequential_view() const { return m_sequential_view; } void update_sequential_view_current(unsigned int first, unsigned int last); - //B43 - void update_marker_curr_move(); - + const libvgcode::Interval& get_gcode_view_full_range() const { return m_viewer.get_view_full_range(); } + const libvgcode::Interval& get_gcode_view_enabled_range() const { return m_viewer.get_view_enabled_range(); } + const libvgcode::Interval& get_gcode_view_visible_range() const { return m_viewer.get_view_visible_range(); } + const libvgcode::PathVertex& get_gcode_vertex_at(size_t id) const { return m_viewer.get_vertex_at(id); } bool is_contained_in_bed() const { return m_contained_in_bed; } - EViewType get_view_type() const { return m_view_type; } - void set_view_type(EViewType type) { - if (type == EViewType::Count) - type = EViewType::FeatureType; - - m_view_type = type; + void set_view_type(libvgcode::EViewType type) { + m_viewer.set_view_type((m_view_type_cache.load && m_view_type_cache.value != type) ? m_view_type_cache.value : type); + const libvgcode::EViewType view_type = get_view_type(); + if (m_view_type_cache.write && m_view_type_cache.value != view_type) + m_view_type_cache.value = view_type; } - bool is_toolpath_move_type_visible(EMoveType type) const; - void set_toolpath_move_type_visible(EMoveType type, bool visible); - unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; } - void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } - unsigned int get_options_visibility_flags() const; - void set_options_visibility_from_flags(unsigned int flags); + libvgcode::EViewType get_view_type() const { return m_viewer.get_view_type(); } + void enable_view_type_cache_load(bool enable) { m_view_type_cache.load = enable; } + void enable_view_type_cache_write(bool enable) { m_view_type_cache.write = enable; } + bool is_view_type_cache_load_enabled() const { return m_view_type_cache.load; } + bool is_view_type_cache_write_enabled() const { return m_view_type_cache.write; } void set_layers_z_range(const std::array& layers_z_range); - bool is_legend_enabled() const { return m_legend_enabled; } + bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; } + void show_legend(bool show) { m_legend_visible = show; } void enable_legend(bool enable) { m_legend_enabled = enable; } void set_force_shells_visible(bool visible) { m_shells.force_visible = visible; } @@ -887,7 +350,6 @@ public: void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); } - std::vector& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; } size_t get_extruders_count() { return m_extruders_count; } void invalidate_legend() { m_legend_resizer.reset(); } @@ -896,21 +358,16 @@ public: void load_shells(const Print& print); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + float get_cog_marker_scale_factor() const { return m_viewer.get_cog_marker_scale_factor(); } + void set_cog_marker_scale_factor(float factor) { return m_viewer.set_cog_marker_scale_factor(factor); } +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + private: - void load_toolpaths(const GCodeProcessorResult& gcode_result); void load_wipetower_shell(const Print& print); void render_toolpaths(); void render_shells(); void render_legend(float& legend_height); -#if ENABLE_GCODE_VIEWER_STATISTICS - void render_statistics(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - bool is_visible(GCodeExtrusionRole role) const { - return role < GCodeExtrusionRole::Count && (m_extrusions.role_visibility_flags & (1 << int(role))) != 0; - } - bool is_visible(const Path& path) const { return is_visible(path.role); } - void log_memory_used(const std::string& label, int64_t additional = 0) const; - ColorRGBA option_color(EMoveType move_type) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 246e46f..3e1ebcf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2,6 +2,7 @@ #include "GLCanvas3D.hpp" #include +#include #include "libslic3r/BuildVolume.hpp" #include "libslic3r/ClipperUtils.hpp" @@ -11,7 +12,6 @@ #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/Utils.hpp" -#include "libslic3r/LocalesUtils.hpp" #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PresetBundle.hpp" @@ -67,7 +67,10 @@ #include #include #include -#include "DoubleSlider.hpp" + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif #include #include @@ -91,8 +94,6 @@ static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = {0.753f, 0.192f, 0.039f //Y5 bool isToolpathOutside = false; -// Number of floats -static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB #define SHOW_IMGUI_DEMO_WINDOW #ifdef SHOW_IMGUI_DEMO_WINDOW @@ -176,16 +177,6 @@ bool GLCanvas3D::LayersEditing::is_allowed() const return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; } -bool GLCanvas3D::LayersEditing::is_enabled() const -{ - return m_enabled; -} - -void GLCanvas3D::LayersEditing::set_enabled(bool enabled) -{ - m_enabled = is_allowed() && enabled; -} - float GLCanvas3D::LayersEditing::s_overlay_window_width; void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) @@ -195,63 +186,63 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const Size& cnv_size = canvas.get_canvas_size(); - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, + ImGuiPureWrap::set_next_window_pos(static_cast(cnv_size.get_width()) - wxGetApp().imgui()->get_style_scaling() * THICKNESS_BAR_WIDTH, static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(_u8L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); //B18 - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _L("Left mouse button:")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Left mouse button:")); ImGui::SameLine(); - imgui.text(_L("Add detail")); + ImGuiPureWrap::text(_u8L("Add detail")); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _L("Right mouse button:")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Right mouse button:")); ImGui::SameLine(); - imgui.text(_L("Remove detail")); + ImGuiPureWrap::text(_u8L("Remove detail")); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _L("Shift + Left mouse button:")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Shift + Left mouse button:")); ImGui::SameLine(); - imgui.text(_L("Reset to base")); + ImGuiPureWrap::text(_u8L("Reset to base")); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _L("Shift + Right mouse button:")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Shift + Right mouse button:")); ImGui::SameLine(); - imgui.text(_L("Smoothing")); + ImGuiPureWrap::text(_u8L("Smoothing")); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _L("Mouse wheel:")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Mouse wheel:")); ImGui::SameLine(); - imgui.text(_L("Increase/decrease edit area")); + ImGuiPureWrap::text(_u8L("Increase/decrease edit area")); ImGui::Separator(); - if (imgui.button(_L("Adaptive"))) + if (ImGuiPureWrap::button(_u8L("Adaptive"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); ImGui::SameLine(); float text_align = ImGui::GetCursorPosX(); ImGui::AlignTextToFramePadding(); - imgui.text(_L("Quality / Speed")); + ImGuiPureWrap::text(_u8L("Quality / Speed")); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); + ImGui::TextUnformatted(_u8L("Higher print quality versus higher print speed.").c_str()); ImGui::EndTooltip(); } ImGui::SameLine(); float widget_align = ImGui::GetCursorPosX(); - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + const float style_scaling = wxGetApp().imgui()->get_style_scaling(); + ImGui::PushItemWidth(style_scaling * 120.0f); m_adaptive_quality = std::clamp(m_adaptive_quality, 0.0f, 1.f); - imgui.slider_float("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); + wxGetApp().imgui()->slider_float("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); ImGui::Separator(); - if (imgui.button(_L("Smooth"))) + if (ImGuiPureWrap::button(_u8L("Smooth"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); ImGui::SameLine(); ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_L("Radius")); + ImGuiPureWrap::text(_u8L("Radius")); ImGui::SameLine(); ImGui::SetCursorPosX(widget_align); - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); + ImGui::PushItemWidth(style_scaling * 120.0f); int radius = (int)m_smooth_params.radius; if (ImGui::SliderInt("##1", &radius, 1, 10)) { radius = std::clamp(radius, 1, 10); @@ -260,20 +251,20 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_L("Keep min")); + ImGuiPureWrap::text(_u8L("Keep min")); ImGui::SameLine(); if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization ImGui::SetCursorPosX(widget_align); - ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); - imgui.checkbox("##2", m_smooth_params.keep_min); + ImGui::PushItemWidth(style_scaling * 120.0f); + ImGuiPureWrap::checkbox("##2", m_smooth_params.keep_min); ImGui::Separator(); - if (imgui.button(_L("Reset"))) + if (ImGuiPureWrap::button(_u8L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; - imgui.end(); + ImGuiPureWrap::end(); render_active_object_annotations(canvas); render_profile(canvas); @@ -480,21 +471,25 @@ void GLCanvas3D::LayersEditing::render_profile(const GLCanvas3D& canvas) m_profile.profile.init_from(std::move(init_data)); } -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); shader->set_uniform("view_model_matrix", Transform3d::Identity()); shader->set_uniform("projection_matrix", Transform3d::Identity()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES m_profile.baseline.render(); m_profile.profile.render(); shader->stop_using(); @@ -625,7 +620,7 @@ void GLCanvas3D::LayersEditing::generate_layer_height_texture() //w27 Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile,false), m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level); - m_layers_texture.valid = true; + m_layers_texture.valid = true; } void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) @@ -641,11 +636,10 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) m_layer_height_profile_modified = false; } -void GLCanvas3D::LayersEditing::update_slicing_parameters() -{ +void GLCanvas3D::LayersEditing::update_slicing_parameters() { if (m_slicing_parameters == nullptr) { m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z, m_shrinkage_compensation); } } @@ -665,23 +659,6 @@ const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5; -GLCanvas3D::Mouse::Drag::Drag() - : start_position_2D(Invalid_2D_Point) - , start_position_3D(Invalid_3D_Point) - , move_volume_idx(-1) - , move_requires_threshold(false) - , move_start_threshold_position_2D(Invalid_2D_Point) -{ -} - -GLCanvas3D::Mouse::Mouse() - : dragging(false) - , position(DBL_MAX, DBL_MAX) - , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) - , ignore_left_up(false) -{ -} - void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const { if (!m_enabled || !is_shown()) @@ -766,8 +743,6 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_ return (owner1.eye_center_z < owner2.eye_center_z); }); - ImGuiWrapper& imgui = *wxGetApp().imgui(); - // render info windows for (const Owner& owner : owners) { Vec3d screen_box_center = world_to_screen * owner.world_box.center(); @@ -787,28 +762,28 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_ ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, owner.selected ? 3.0f : 1.5f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleColor(ImGuiCol_Border, owner.selected ? ImVec4(0.757f, 0.404f, 0.216f, 1.0f) : ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); - imgui.set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f); - imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f); + ImGuiPureWrap::begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); float win_w = ImGui::GetWindowWidth(); float label_len = ImGui::CalcTextSize(owner.label.c_str()).x; ImGui::SetCursorPosX(0.5f * (win_w - label_len)); ImGui::AlignTextToFramePadding(); - imgui.text(owner.label); + ImGuiPureWrap::text(owner.label); if (!owner.print_order.empty()) { ImGui::Separator(); float po_len = ImGui::CalcTextSize(owner.print_order.c_str()).x; ImGui::SetCursorPosX(0.5f * (win_w - po_len)); ImGui::AlignTextToFramePadding(); - imgui.text(owner.print_order); + ImGuiPureWrap::text(owner.print_order); } // force re-render while the windows gets to its final size (it takes several frames) if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x) - imgui.set_requires_extra_frame(); + wxGetApp().imgui()->set_requires_extra_frame(); - imgui.end(); + ImGuiPureWrap::end(); ImGui::PopStyleColor(); ImGui::PopStyleVar(2); } @@ -883,9 +858,9 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); - imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f); + ImGuiPureWrap::set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f); - imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); + ImGuiPureWrap::begin("canvas_tooltip", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); ImGui::TextUnformatted(m_text.c_str()); @@ -895,7 +870,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas size = ImGui::GetWindowSize(); - imgui.end(); + ImGuiPureWrap::end(); ImGui::PopStyleVar(2); } @@ -988,11 +963,16 @@ void GLCanvas3D::SequentialPrintClearance::render() if (!m_evaluating && !m_dragging) m_fill.render(); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES shader->stop_using(); +#if SLIC3R_OPENGL_ES + shader = wxGetApp().get_shader("dashed_lines"); +#else shader = wxGetApp().get_shader("dashed_thick_lines"); +#endif // SLIC3R_OPENGL_ES if (shader == nullptr) return; @@ -1002,10 +982,11 @@ void GLCanvas3D::SequentialPrintClearance::render() shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); shader->set_uniform("width", 1.0f); shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES } else -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(2.0f)); +#endif // !SLIC3R_OPENGL_ES for (const auto& [id, trafo] : m_instances) { shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo); @@ -1046,9 +1027,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_SLIDERS_MANIPULATION, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); @@ -1230,16 +1209,15 @@ void GLCanvas3D::SLAView::render_switch_button() ss_box = selection.get_screen_space_bounding_box(); assert(ss_box.defined); - ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::SetNextWindowPos(ImVec2((float)ss_box.max.x(), (float)ss_box.center().y()), ImGuiCond_Always, ImVec2(0.0, 0.5)); - imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); + ImGuiPureWrap::begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); const float icon_size = 1.5 * ImGui::GetTextLineHeight(); - if (imgui.draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true, - [&imgui, sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) { + if (ImGuiPureWrap::draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true, + [sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) { const wchar_t icon_id = (sel_instance->second == ESLAViewType::Original) ? ImGui::SlaViewProcessed : ImGui::SlaViewOriginal; - imgui.draw_icon(window, pos, size, icon_id); + wxGetApp().imgui()->draw_icon(window, pos, size, icon_id); })) { switch (sel_instance->second) { @@ -1250,21 +1228,21 @@ void GLCanvas3D::SLAView::render_switch_button() } if (ImGui::IsItemHovered()) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - wxString tooltip; + std::string tooltip; switch (type) { - case ESLAViewType::Original: { tooltip = _L("Show as processed"); break; } - case ESLAViewType::Processed: { tooltip = _L("Show as original"); break; } + case ESLAViewType::Original: { tooltip = _u8L("Show as processed"); break; } + case ESLAViewType::Processed: { tooltip = _u8L("Show as original"); break; } default: { assert(false); break; } } - imgui.text(tooltip); + ImGuiPureWrap::text(tooltip); ImGui::EndTooltip(); ImGui::PopStyleColor(); } - imgui.end(); + ImGuiPureWrap::end(); ImGui::PopStyleColor(2); } @@ -1272,18 +1250,18 @@ void GLCanvas3D::SLAView::render_switch_button() void GLCanvas3D::SLAView::render_debug_window() { ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("SLAView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + ImGuiPureWrap::begin(std::string("SLAView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); //B18 for (const auto& [id, type] : m_instances_cache) { - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "(" + std::to_string(id.object_id) + ", " + std::to_string(id.instance_id) + ")"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "(" + std::to_string(id.object_id) + ", " + std::to_string(id.instance_id) + ")"); ImGui::SameLine(); - imgui.text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), (type == ESLAViewType::Original) ? "Original" : "Processed"); + ImGuiPureWrap::text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), (type == ESLAViewType::Original) ? "Original" : "Processed"); } if (!m_instances_cache.empty()) ImGui::Separator(); - imgui.checkbox("Use instance bounding box", m_use_instance_bbox); - imgui.end(); + ImGuiPureWrap::checkbox("Use instance bounding box", m_use_instance_bbox); + ImGuiPureWrap::end(); } #endif // ENABLE_SLA_VIEW_DEBUG_WINDOW @@ -1323,41 +1301,39 @@ bool GLCanvas3D::is_arrange_alignment_enabled() const } GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed) - : m_canvas(canvas), - m_context(nullptr), - m_bed(bed) + : m_canvas(canvas) + , m_context(nullptr) + , m_bed(bed) #if ENABLE_RETINA_GL - , - m_retina_helper(nullptr) -#endif - , - m_in_render(false), - m_main_toolbar(GLToolbar::Normal, "Main"), - m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo"), - m_gizmos(*this), - m_use_clipping_planes(false), - m_sidebar_field(""), - m_extra_frame_requested(false), - m_config(nullptr), - m_process(nullptr), - m_model(nullptr), - m_dirty(true), - m_initialized(false), - m_apply_zoom_to_volumes_filter(false), - m_picking_enabled(false), - m_moving_enabled(false), - m_dynamic_background_enabled(false), - m_multisample_allowed(false), - m_moving(false), - m_tab_down(false), - m_cursor_type(Standard), - m_reload_delayed(false), - m_render_sla_auxiliaries(true), - m_labels(*this), - m_slope(m_volumes), - m_sla_view(*this), - m_arrange_settings_db{wxGetApp().app_config}, - m_arrange_settings_dialog{wxGetApp().imgui(), &m_arrange_settings_db} + , m_retina_helper(nullptr) +#endif // ENABLE_RETINA_GL + , m_in_render(false) + , m_main_toolbar(GLToolbar::Normal, "Main") + , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") + , m_gizmos(*this) + , m_use_clipping_planes(false) + , m_sidebar_field("") + , m_extra_frame_requested(false) + , m_config(nullptr) + , m_process(nullptr) + , m_model(nullptr) + , m_dirty(true) + , m_initialized(false) + , m_apply_zoom_to_volumes_filter(false) + , m_picking_enabled(false) + , m_moving_enabled(false) + , m_dynamic_background_enabled(false) + , m_multisample_allowed(false) + , m_moving(false) + , m_tab_down(false) + , m_cursor_type(Standard) + , m_reload_delayed(false) + , m_render_sla_auxiliaries(true) + , m_labels(*this) + , m_slope(m_volumes) + , m_sla_view(*this) + , m_arrange_settings_db{wxGetApp().app_config} + , m_arrange_settings_dialog{wxGetApp().imgui(), &m_arrange_settings_db} { if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); @@ -1387,6 +1363,11 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } +wxWindow* GLCanvas3D::get_wxglcanvas_parent() +{ + return m_canvas->GetParent(); +} + bool GLCanvas3D::init() { if (m_initialized) @@ -1396,11 +1377,11 @@ bool GLCanvas3D::init() return false; glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES glsafe(::glClearDepthf(1.0f)); #else glsafe(::glClearDepth(1.0f)); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES glsafe(::glDepthFunc(GL_LESS)); @@ -1429,16 +1410,6 @@ bool GLCanvas3D::init() return true; } -void GLCanvas3D::set_as_dirty() -{ - m_dirty = true; -} - -unsigned int GLCanvas3D::get_volumes_count() const -{ - return (unsigned int)m_volumes.volumes.size(); -} - void GLCanvas3D::reset_volumes() { if (!m_initialized) @@ -1464,11 +1435,6 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(bool sele return state; } -void GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes) const -{ - check_volumes_outside_state(volumes, nullptr, false); -} - bool GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes, ModelInstanceEPrintVolumeState* out_state, bool selection_only) const { auto volume_below = [](GLVolume& volume) -> bool @@ -1664,10 +1630,11 @@ void GLCanvas3D::set_config(const DynamicPrintConfig* config) m_config = config; m_layers_editing.set_config(config); + const PrinterTechnology ptech = current_printer_technology(); + if (const Print *print = fff_print(); ptech == ptFFF && print != nullptr) + m_layers_editing.set_shrinkage_compensation(fff_print()->shrinkage_compensation()); if (config) { - PrinterTechnology ptech = current_printer_technology(); - auto slot = ArrangeSettingsDb_AppCfg::slotFFF; if (ptech == ptSLA) { @@ -1685,8 +1652,10 @@ void GLCanvas3D::set_config(const DynamicPrintConfig* config) double objdst = min_object_distance(*config); double min_obj_dst = slot == ArrangeSettingsDb_AppCfg::slotFFFSeqPrint ? objdst : 0.; m_arrange_settings_db.set_distance_from_obj_range(slot, min_obj_dst, 100.); + if (std::abs(m_arrange_settings_db.get_defaults(slot).d_obj - objdst) > EPSILON) { - m_arrange_settings_db.get_defaults(slot).d_obj = objdst; + m_arrange_settings_db.get_defaults(slot).d_obj = objdst; + // Defaults have changed, so let's sync with the app config and fill // in the missing values with the new defaults. m_arrange_settings_db.sync(); @@ -1694,11 +1663,6 @@ void GLCanvas3D::set_config(const DynamicPrintConfig* config) } } -void GLCanvas3D::set_process(BackgroundSlicingProcess *process) -{ - m_process = process; -} - void GLCanvas3D::set_model(Model* model) { m_model = model; @@ -1738,16 +1702,6 @@ BoundingBoxf3 GLCanvas3D::scene_bounding_box() const return bb; } -bool GLCanvas3D::is_layers_editing_enabled() const -{ - return m_layers_editing.is_enabled(); -} - -bool GLCanvas3D::is_layers_editing_allowed() const -{ - return m_layers_editing.is_allowed(); -} - void GLCanvas3D::reset_layer_height_profile() { wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset")); @@ -1772,62 +1726,12 @@ void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& m_dirty = true; } -bool GLCanvas3D::is_reload_delayed() const -{ - return m_reload_delayed; -} - void GLCanvas3D::enable_layers_editing(bool enable) { m_layers_editing.set_enabled(enable); set_as_dirty(); } -void GLCanvas3D::enable_legend_texture(bool enable) -{ - m_gcode_viewer.enable_legend(enable); -} - -void GLCanvas3D::enable_picking(bool enable) -{ - m_picking_enabled = enable; -} - -void GLCanvas3D::enable_moving(bool enable) -{ - m_moving_enabled = enable; -} - -void GLCanvas3D::enable_gizmos(bool enable) -{ - m_gizmos.set_enabled(enable); -} - -void GLCanvas3D::enable_selection(bool enable) -{ - m_selection.set_enabled(enable); -} - -void GLCanvas3D::enable_main_toolbar(bool enable) -{ - m_main_toolbar.set_enabled(enable); -} - -void GLCanvas3D::enable_undoredo_toolbar(bool enable) -{ - m_undoredo_toolbar.set_enabled(enable); -} - -void GLCanvas3D::enable_dynamic_background(bool enable) -{ - m_dynamic_background_enabled = enable; -} - -void GLCanvas3D::allow_multisample(bool allow) -{ - m_multisample_allowed = allow; -} - void GLCanvas3D::zoom_to_bed() { BoundingBoxf3 box = m_bed.build_volume().bounding_volume(); @@ -1936,16 +1840,16 @@ void GLCanvas3D::render() #if ENABLE_RAYCAST_PICKING_DEBUG else { ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); - imgui.text("Picking disabled"); - imgui.end(); + ImGuiPureWrap::begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + ImGuiPureWrap::text("Picking disabled"); + ImGuiPureWrap::end(); } #endif // ENABLE_RAYCAST_PICKING_DEBUG } #ifdef SHOW_IMGUI_DEMO_WINDOW if (show_imgui_demo_window) ImGui::ShowDemoWindow(); -#endif // SHOW_IMGUI_DEMO_WINDOW +#endif // SHOW_IMGUI_DEMO_WINDOW const bool is_looking_downward = camera.is_looking_downward(); @@ -1999,19 +1903,18 @@ void GLCanvas3D::render() _render_overlays(); if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text("FPS (SwapBuffers() calls per second):"); + ImGuiPureWrap::begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::text("FPS (SwapBuffers() calls per second):"); ImGui::SameLine(); - imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed())); + ImGuiPureWrap::text(std::to_string(m_render_stats.get_fps_and_reset_if_needed())); ImGui::Separator(); - imgui.text("Compressed textures:"); + ImGuiPureWrap::text("Compressed textures:"); ImGui::SameLine(); - imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported"); - imgui.text("Max texture size:"); + ImGuiPureWrap::text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported"); + ImGuiPureWrap::text("Max texture size:"); ImGui::SameLine(); - imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size())); - imgui.end(); + ImGuiPureWrap::text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size())); + ImGuiPureWrap::end(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW @@ -2042,6 +1945,7 @@ void GLCanvas3D::render() if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() != ptSLA && fff_print()->config().binary_gcode) show_binary_gcode_debug_window(); #endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + std::string tooltip; // Negative coordinate means out of the window, likely because the window was deactivated. @@ -2075,6 +1979,8 @@ void GLCanvas3D::render() wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); + wxGetApp().plater()->render_sliders(*this); + wxGetApp().imgui()->render(); m_canvas->SwapBuffers(); @@ -2123,11 +2029,6 @@ void GLCanvas3D::deselect_all() post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); } -void GLCanvas3D::delete_selected() -{ - m_selection.erase(); -} - void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z) { if (allow_negative_z) @@ -2156,37 +2057,6 @@ void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z) } } - -const std::vector& GLCanvas3D::get_gcode_layers_zs() const -{ - return m_gcode_viewer.get_layers_zs(); -} - -std::vector GLCanvas3D::get_volumes_print_zs(bool active_only) const -{ - return m_volumes.get_current_print_zs(active_only); -} - -void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags) -{ - m_gcode_viewer.set_options_visibility_from_flags(flags); -} - -void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags) -{ - m_gcode_viewer.set_toolpath_role_visibility_flags(flags); -} - -void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) -{ - m_gcode_viewer.set_view_type(type); -} - -void GLCanvas3D::set_volumes_z_range(const std::array& range) -{ - m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); -} - void GLCanvas3D::set_toolpaths_z_range(const std::array& range) { if (m_gcode_viewer.has_data()) @@ -2377,9 +2247,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (volume->is_wipe_tower) { // There is only one wipe tower. assert(volume_idx_wipe_tower_old == -1); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES m_wipe_tower_mesh.clear(); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES volume_idx_wipe_tower_old = (int)volume_id; } if (!m_reload_delayed) { @@ -2561,15 +2431,15 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const double height = height_real < 0.f ? std::max(m_model->max_z(), 10.0) : height_real; if (depth != 0.) { - #if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !print->is_step_done(psWipeTower), bw, &m_wipe_tower_mesh); - #else +#else int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !print->is_step_done(psWipeTower), bw); - #endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -2703,10 +2573,16 @@ void GLCanvas3D::load_gcode_shells() m_gcode_viewer.set_force_shells_visible(true); } -void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) +void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors) { - m_gcode_viewer.load(gcode_result, *this->fff_print()); - + m_gcode_viewer.enable_legend(true); + m_gcode_viewer.enable_view_type_cache_write(true); + m_gcode_viewer.enable_view_type_cache_load(true); + m_gcode_viewer.set_view_type(m_gcode_viewer.get_view_type()); + m_gcode_viewer.load_as_gcode(gcode_result, *this->fff_print(), str_tool_colors, str_color_print_colors); + m_gcode_layers_times_cache = m_gcode_viewer.get_layers_times(); + m_gcode_viewer.set_force_shells_visible(false); if (wxGetApp().is_editor()) { //Y5 isToolpathOutside = false; @@ -2714,14 +2590,6 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co _set_warning_notification_if_needed(EWarning::GCodeConflict); } - m_gcode_viewer.refresh(gcode_result, str_tool_colors); - set_as_dirty(); - request_extra_frame(); -} - -void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) -{ - m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); set_as_dirty(); request_extra_frame(); } @@ -2731,16 +2599,18 @@ void GLCanvas3D::load_sla_preview() const SLAPrint* print = sla_print(); if (m_canvas != nullptr && print != nullptr) { _set_current(); - // Release OpenGL data before generating new data. - reset_volumes(); + // Release OpenGL data before generating new data. + reset_volumes(); _load_sla_shells(); _update_sla_shells_outside_state(); + m_gcode_viewer.set_force_shells_visible(false); _set_warning_notification_if_needed(EWarning::ObjectClashed); _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); } } -void GLCanvas3D::load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values) +void GLCanvas3D::load_preview(const std::vector& str_tool_colors, const std::vector& str_color_print_colors, + const std::vector& color_print_values) { const Print *print = this->fff_print(); if (print == nullptr) @@ -2748,15 +2618,15 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c _set_current(); - // Release OpenGL data before generating new data. - this->reset_volumes(); - - const BuildVolume &build_volume = m_bed.build_volume(); - _load_print_toolpaths(build_volume); - _load_wipe_tower_toolpaths(build_volume, str_tool_colors); - for (const PrintObject* object : print->objects()) - _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); + libvgcode::GCodeInputData data = libvgcode::convert(*print, str_tool_colors, str_color_print_colors, color_print_values, + static_cast(wxGetApp().extruders_edited_cnt())); + // send data to the viewer + m_gcode_viewer.enable_legend(false); + m_gcode_viewer.enable_view_type_cache_write(false); + m_gcode_viewer.enable_view_type_cache_load(false); + m_gcode_viewer.set_view_type(libvgcode::EViewType::FeatureType); + m_gcode_viewer.load_as_preview(std::move(data)); m_gcode_viewer.set_force_shells_visible(false); _set_warning_notification_if_needed(EWarning::ToolpathOutside); } @@ -2824,11 +2694,6 @@ void GLCanvas3D::unbind_event_handlers() m_event_handlers_bound = false; } } - -void GLCanvas3D::on_size(wxSizeEvent& evt) -{ - m_dirty = true; -} void GLCanvas3D::on_idle(wxIdleEvent& evt) { @@ -2886,7 +2751,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; int shiftMask = wxMOD_SHIFT; - if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) + if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_arrange_menu())) return; if (m_gizmos.on_char(evt)) { @@ -2918,14 +2783,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_COPY)); break; -#ifdef __APPLE__ - case 'f': - case 'F': -#else /* __APPLE__ */ - case WXK_CONTROL_F: -#endif /* __APPLE__ */ - _activate_search_toolbar_item(); - break; #ifdef __APPLE__ case 'm': case 'M': @@ -3005,14 +2862,14 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case '6': { select_view("right"); break; } case '+': { if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + post_event(wxKeyEvent(EVT_GLCANVAS_SLIDERS_MANIPULATION, evt)); else post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } case '-': { if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + post_event(wxKeyEvent(EVT_GLCANVAS_SLIDERS_MANIPULATION, evt)); else post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; @@ -3030,7 +2887,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'g': { if ((evt.GetModifiers() & shiftMask) != 0) { if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt)); + post_event(wxKeyEvent(EVT_GLCANVAS_SLIDERS_MANIPULATION, evt)); } break; } @@ -3188,9 +3045,8 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) const int keyCode = evt.GetKeyCode(); auto imgui = wxGetApp().imgui(); - if (imgui->update_key_data(evt)) { + if (imgui->update_key_data(evt)) render(); - } else { if (!m_gizmos.on_key(evt)) { if (evt.GetEventType() == wxEVT_KEY_UP) { @@ -3205,7 +3061,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) } else if (! wxGetApp().is_gcode_viewer() && keyCode == WXK_TAB && // Use strong condition for modifiers state to avoid cases when Shift can be combined with other modifiers - // (see https://github.com/prusa3d/PrusaSlicer/issues/7799) + // (see https://github.com/qidi3d/QIDISlicer/issues/7799) evt.GetModifiers() == wxMOD_SHIFT) { // Collapse side-panel with Shift+Tab post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); @@ -3316,7 +3172,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) keyCode == WXK_UP || keyCode == WXK_DOWN) { if (dynamic_cast(m_canvas->GetParent()) != nullptr) - post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_SLIDERS, evt)); + post_event(wxKeyEvent(EVT_GLCANVAS_SLIDERS_MANIPULATION, evt)); } } } @@ -3382,10 +3238,9 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) } } - // If the Search window or Undo/Redo list is opened, + // If Undo/Redo list is opened, // update them according to the event - if (m_main_toolbar.is_item_pressed("search") || - m_undoredo_toolbar.is_item_pressed("undo") || + if (m_undoredo_toolbar.is_item_pressed("undo") || m_undoredo_toolbar.is_item_pressed("redo")) { m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); return; @@ -3656,16 +3511,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) //#if defined(__WXMSW__) || defined(__linux__) // // On Windows and Linux needs focus in order to catch key events if (m_canvas != nullptr) { + // Set focus in order to remove it from sidebar but not from TextControl (in ManipulationPanel f.e.) if (!m_canvas->HasFocus()) { wxTopLevelWindow* tlw = find_toplevel_parent(m_canvas); - // Only set focus, if the top level window of this canvas is active + // Only set focus, if the top level window of this canvas is active if (tlw->IsActive()) { auto* text_ctrl = dynamic_cast(tlw->FindFocus()); if (text_ctrl == nullptr) - m_canvas->SetFocus(); + m_canvas->SetFocus(); } } + m_mouse.position = pos.cast(); m_tooltip_enabled = false; // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while @@ -3690,7 +3547,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { - if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu()) + if (_deactivate_undo_redo_toolbar_items() || _deactivate_arrange_menu()) return; // If user pressed left or right button we first check whether this happened @@ -3979,12 +3836,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) 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; + 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) @@ -4481,7 +4339,7 @@ void GLCanvas3D::update_ui_from_settings() #endif // ENABLE_RETINA_GL if (wxGetApp().is_editor()) - wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get_bool("show_collapse_button")); + wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get_bool("show_collapse_button") || !wxGetApp().sidebar().IsShown()); } GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const @@ -4529,11 +4387,6 @@ void GLCanvas3D::set_cursor(ECursorType type) } } -void GLCanvas3D::msw_rescale() -{ - m_gcode_viewer.invalidate_legend(); -} - void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() { std::string new_tooltip = _u8L("Switch to Settings") + @@ -4544,16 +4397,6 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); } -bool GLCanvas3D::has_toolpaths_to_export() const -{ - return m_gcode_viewer.can_export_toolpaths(); -} - -void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const -{ - m_gcode_viewer.export_toolpaths_to_obj(filename); -} - void GLCanvas3D::mouse_up_cleanup() { m_moving = false; @@ -4760,11 +4603,9 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) { bool action_taken = false; - ImGuiWrapper* imgui = wxGetApp().imgui(); - - imgui->set_next_window_pos(pos_x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); - std::string title = is_undo ? L("Undo History") : L("Redo History"); - imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::set_next_window_pos(pos_x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); + std::string title = is_undo ? _u8L("Undo History") : _u8L("Redo History"); + ImGuiPureWrap::begin(title, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); int hovered = m_imgui_undo_redo_hovered_pos; int selected = -1; @@ -4773,7 +4614,7 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) em *= m_retina_helper->get_scale_factor(); #endif - if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, m_mouse_wheel)) + if (ImGuiPureWrap::undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected, m_mouse_wheel)) m_imgui_undo_redo_hovered_pos = hovered; else m_imgui_undo_redo_hovered_pos = -1; @@ -4783,69 +4624,9 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) action_taken = true; } - imgui->text(wxString::Format(is_undo ? _L_PLURAL("Undo %1$d Action", "Undo %1$d Actions", hovered + 1) : _L_PLURAL("Redo %1$d Action", "Redo %1$d Actions", hovered + 1), hovered + 1)); + ImGuiPureWrap::text(GUI::format(is_undo ? _L_PLURAL("Undo %1$d Action", "Undo %1$d Actions", hovered + 1) : _L_PLURAL("Redo %1$d Action", "Redo %1$d Actions", hovered + 1), hovered + 1)); - imgui->end(); - - return action_taken; -} - -// Getter for the const char*[] for the search list -static bool search_string_getter(int idx, const char** label, const char** tooltip) -{ - return wxGetApp().plater()->search_string_getter(idx, label, tooltip); -} - -bool GLCanvas3D::_render_search_list(float pos_x) -{ - bool action_taken = false; - ImGuiWrapper* imgui = wxGetApp().imgui(); - - imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); - std::string title = L("Search"); - imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - int selected = -1; - bool edited = false; - float em = static_cast(wxGetApp().em_unit()); -#if ENABLE_RETINA_GL - em *= m_retina_helper->get_scale_factor(); -#endif // ENABLE_RETINA_GL - - Sidebar& sidebar = wxGetApp().sidebar(); - - std::string& search_line = sidebar.get_search_line(); - char *s = new char[255]; - strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str()); - - imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s, - sidebar.get_searcher().view_params, - selected, edited, m_mouse_wheel, wxGetApp().is_localized()); - - search_line = s; - delete [] s; - if (search_line == _u8L("Enter a search term")) - search_line.clear(); - - if (edited) - sidebar.search(); - - if (selected >= 0) { - // selected == 9999 means that Esc kye was pressed - /*// revert commit https://github.com/qidi3d/QIDISlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2 - if (selected == 9999) - action_taken = true; - else - sidebar.jump_to_option(selected);*/ - if (selected != 9999) { - imgui->end(); // end imgui before the jump to option - sidebar.jump_to_option(selected); - return true; - } - action_taken = true; - } - - imgui->end(); + ImGuiPureWrap::end(); return action_taken; } @@ -4853,33 +4634,9 @@ bool GLCanvas3D::_render_search_list(float pos_x) bool GLCanvas3D::_render_arrange_menu(float pos_x) { m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height()); - return true; } -#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT -static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) -{ - // debug export of generated image - wxImage image(thumbnail_data.width, thumbnail_data.height); - image.InitAlpha(); - - for (unsigned int r = 0; r < thumbnail_data.height; ++r) - { - unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width; - for (unsigned int c = 0; c < thumbnail_data.width; ++c) - { - unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c); - image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); - image.SetAlpha((int)c, (int)r, px[3]); - } - } - - image.SaveFile("C:/qidi/test/test.png", wxBITMAP_TYPE_PNG); -} -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type) { auto is_visible = [](const GLVolume& v) { @@ -4930,8 +4687,8 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const camera.apply_projection(volumes_box, near_z, far_z); - const ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects; - std::vector extruders_colors = get_extruders_colors(); + const ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects; + std::vector extruders_colors = GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); const bool is_enabled_painted_thumbnail = !model_objects.empty() && !extruders_colors.empty(); //Y18 //B54 if (thumbnail_params.transparent_background) @@ -4939,13 +4696,12 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glCullFace(GL_BACK)); const Transform3d& projection_matrix = camera.get_projection_matrix(); const int extruders_count = wxGetApp().extruders_edited_cnt(); - for (GLVolume* vol : visible_volumes) { + for (GLVolume *vol : visible_volumes) { const int obj_idx = vol->object_idx(); const int vol_idx = vol->volume_idx(); const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty(); @@ -4971,6 +4727,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const else vol->model.set_color((vol->printable && !vol->is_outside) ? ColorRGBA { 0.2f, 0.6f, 1.0f, 1.0f } : ColorRGBA::GRAY()); } + // the volume may have been deactivated by an active gizmo const bool is_active = vol->is_active; vol->is_active = true; @@ -4978,13 +4735,14 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const shader->set_uniform("view_model_matrix", view_matrix * model_matrix); shader->set_uniform("projection_matrix", projection_matrix); const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); - shader->set_uniform("view_normal_matrix", view_normal_matrix); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + if (is_left_handed) glsafe(::glFrontFace(GL_CW)); if (render_as_painted) { - const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; - const size_t extruder_idx = get_extruder_color_idx(model_volume, extruders_count); + const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; + const size_t extruder_idx = ModelVolume::get_extruder_color_idx(model_volume, extruders_count); TriangleSelectorMmGui ts(model_volume.mesh(), extruders_colors, extruders_colors[extruder_idx]); ts.deserialize(model_volume.mm_segmentation_facets.get_data(), true); ts.request_update_render_data(); @@ -4992,14 +4750,16 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ts.render(nullptr, model_matrix); } else - vol->render(); + vol->render(); + if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); - shader->stop_using(); + shader->stop_using(); vol->is_active = is_active; } + glsafe(::glDisable(GL_DEPTH_TEST)); //Y18 // if (thumbnail_params.show_bed) @@ -5016,8 +4776,9 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un if (!thumbnail_data.is_valid()) return; - bool multisample = m_multisample_allowed; - if (multisample) + const bool multisample = ::glIsEnabled(GL_MULTISAMPLE); + glcheck(); + if (m_multisample_allowed && !multisample) glsafe(::glEnable(GL_MULTISAMPLE)); GLint max_samples; @@ -5091,10 +4852,6 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un } else glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT } glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); @@ -5105,7 +4862,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un glsafe(::glDeleteTextures(1, &render_tex)); glsafe(::glDeleteFramebuffers(1, &render_fbo)); - if (multisample) + if (!multisample) glsafe(::glDisable(GL_MULTISAMPLE)); } @@ -5115,8 +4872,9 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data if (!thumbnail_data.is_valid()) return; - bool multisample = m_multisample_allowed; - if (multisample) + const bool multisample = ::glIsEnabled(GL_MULTISAMPLE); + glcheck(); + if (m_multisample_allowed && !multisample) glsafe(::glEnable(GL_MULTISAMPLE)); GLint max_samples; @@ -5190,10 +4948,6 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data } else glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT } glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); @@ -5204,7 +4958,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data glsafe(::glDeleteTextures(1, &render_tex)); glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); - if (multisample) + if (!multisample) glsafe(::glDisable(GL_MULTISAMPLE)); } @@ -5227,9 +4981,6 @@ void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigne _render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type); glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT - debug_output_thumbnail(thumbnail_data); -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT // restore the default framebuffer size to avoid flickering on the 3D scene wxGetApp().plater()->get_camera().apply_viewport(); @@ -5254,8 +5005,8 @@ bool GLCanvas3D::_init_toolbars() bool GLCanvas3D::_init_main_toolbar() { - if (!m_main_toolbar.is_enabled()) - return true; + //if (!m_main_toolbar.is_enabled()) + // return true; BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; @@ -5281,16 +5032,19 @@ bool GLCanvas3D::_init_main_toolbar() m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + //y15 m_main_toolbar.set_border(5.0f); - m_main_toolbar.set_separator_size(5); - m_main_toolbar.set_gap_size(4); + m_main_toolbar.set_separator_size(5.f); + m_main_toolbar.set_gap_size(5.f); GLToolbarItem::Data item; + unsigned int sprite_id = 0; + item.name = "add"; item.icon_filename = "add.svg"; item.tooltip = _u8L("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]"; - item.sprite_id = 0; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; if (!m_main_toolbar.add_item(item)) return false; @@ -5298,7 +5052,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "delete"; item.icon_filename = "remove.svg"; item.tooltip = _u8L("Delete") + " [Del]"; - item.sprite_id = 1; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; if (!m_main_toolbar.add_item(item)) @@ -5307,7 +5061,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "deleteall"; item.icon_filename = "delete_all.svg"; item.tooltip = _u8L("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; - item.sprite_id = 2; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; if (!m_main_toolbar.add_item(item)) @@ -5316,7 +5070,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "arrange"; item.icon_filename = "arrange.svg"; item.tooltip = _u8L("Arrange") + " [A]\n" + _u8L("Arrange selection") + " [Shift+A]\n" + _u8L("Click right mouse button to show arrangement options"); - item.sprite_id = 3; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; item.right.toggable = true; @@ -5336,7 +5090,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "copy"; item.icon_filename = "copy.svg"; item.tooltip = _u8L("Copy") + " [" + GUI::shortkey_ctrl_prefix() + "C]"; - item.sprite_id = 4; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; if (!m_main_toolbar.add_item(item)) @@ -5345,7 +5099,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "paste"; item.icon_filename = "paste.svg"; item.tooltip = _u8L("Paste") + " [" + GUI::shortkey_ctrl_prefix() + "V]"; - item.sprite_id = 5; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; if (!m_main_toolbar.add_item(item)) @@ -5357,7 +5111,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "more"; item.icon_filename = "instance_add.svg"; item.tooltip = _u8L("Add instance") + " [+]"; - item.sprite_id = 6; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; @@ -5368,7 +5122,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "fewer"; item.icon_filename = "instance_remove.svg"; item.tooltip = _u8L("Remove instance") + " [-]"; - item.sprite_id = 7; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; @@ -5381,7 +5135,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "splitobjects"; item.icon_filename = "split_objects.svg"; item.tooltip = _u8L("Split to objects"); - item.sprite_id = 8; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; @@ -5391,7 +5145,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "splitvolumes"; item.icon_filename = "split_parts.svg"; item.tooltip = _u8L("Split to parts"); - item.sprite_id = 9; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; @@ -5400,52 +5154,29 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_separator()) return false; - +/* item.name = "settings"; item.icon_filename = "settings.svg"; item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab")) ; - item.sprite_id = 10; + item.sprite_id = sprite_id++; item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; - item.visibility_callback = []() { return wxGetApp().app_config->get_bool("new_settings_layout_mode") || - wxGetApp().app_config->get_bool("dlg_settings_layout_mode"); }; + item.visibility_callback = []() { return wxGetApp().app_config->get_bool("dlg_settings_layout_mode"); }; item.left.action_callback = []() { wxGetApp().mainframe->select_tab(); }; if (!m_main_toolbar.add_item(item)) return false; - - /* - if (!m_main_toolbar.add_separator()) - return false; - */ - - item.name = "search"; - item.icon_filename = "search_.svg"; - item.tooltip = _u8L("Search") + " [" + GUI::shortkey_ctrl_prefix() + "F]"; - item.sprite_id = 11; - item.left.toggable = true; - item.left.render_callback = [this](float left, float right, float, float) { - if (m_canvas != nullptr) { - if (!m_canvas->HasFocus()) - m_canvas->SetFocus(); - if (_render_search_list(0.5f * (left + right))) - _deactivate_search_toolbar_item(); - } - }; - item.left.action_callback = GLToolbarItem::Default_Action_Callback; - item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabling_callback = [this]()->bool { return m_gizmos.get_current_type() == GLGizmosManager::Undefined; }; - if (!m_main_toolbar.add_item(item)) - return false; - +*/ if (!m_main_toolbar.add_separator()) return false; item.name = "layersediting"; item.icon_filename = "layers_white.svg"; item.tooltip = _u8L("Variable layer height"); - item.sprite_id = 12; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.sprite_id = sprite_id++; + item.left.action_callback = [this]() { + if (m_canvas != nullptr) + wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { bool res = current_printer_technology() == ptFFF; // turns off if changing printer technology @@ -5459,13 +5190,16 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + if (!m_main_toolbar.generate_icons_texture()) + return false; + return true; } bool GLCanvas3D::_init_undoredo_toolbar() { - if (!m_undoredo_toolbar.is_enabled()) - return true; + //if (!m_undoredo_toolbar.is_enabled()) + // return true; BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; @@ -5488,16 +5222,19 @@ bool GLCanvas3D::_init_undoredo_toolbar() m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_undoredo_toolbar.set_border(5.0f); - m_undoredo_toolbar.set_separator_size(5); - m_undoredo_toolbar.set_gap_size(4); + //y15 + m_undoredo_toolbar.set_border(5.f); + m_undoredo_toolbar.set_separator_size(5.f); + m_undoredo_toolbar.set_gap_size(5.f); GLToolbarItem::Data item; + unsigned int sprite_id = 0; + item.name = "undo"; item.icon_filename = "undo_toolbar.svg"; item.tooltip = _u8L("Undo") + " [" + GUI::shortkey_ctrl_prefix() + "Z]\n" + _u8L("Click right mouse button to open/close History"); - item.sprite_id = 0; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; item.right.toggable = true; item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; @@ -5534,7 +5271,7 @@ bool GLCanvas3D::_init_undoredo_toolbar() item.name = "redo"; item.icon_filename = "redo_toolbar.svg"; item.tooltip = _u8L("Redo") + " [" + GUI::shortkey_ctrl_prefix() + "Y]\n" + _u8L("Click right mouse button to open/close History"); - item.sprite_id = 1; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; item.right.render_callback = [this](float left, float right, float, float) { @@ -5570,6 +5307,10 @@ bool GLCanvas3D::_init_undoredo_toolbar() if (!m_undoredo_toolbar.add_separator()) return false; */ + + if (!m_undoredo_toolbar.generate_icons_texture()) + return false; + return true; } @@ -5600,7 +5341,7 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) m_old_size = new_size; auto *imgui = wxGetApp().imgui(); - imgui->set_display_size(static_cast(w), static_cast(h)); + ImGuiPureWrap::set_display_size(static_cast(w), static_cast(h)); const float font_size = 1.5f * wxGetApp().em_unit(); #if ENABLE_RETINA_GL imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); @@ -5689,10 +5430,9 @@ void GLCanvas3D::_picking_pass() { if (!m_picking_enabled || m_mouse.dragging || m_mouse.position == Vec2d(DBL_MAX, DBL_MAX) || m_gizmos.is_dragging()) { #if ENABLE_RAYCAST_PICKING_DEBUG - ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); - imgui.text("Picking disabled"); - imgui.end(); + ImGuiPureWrap::begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + ImGuiPureWrap::text("Picking disabled"); + ImGuiPureWrap::end(); #endif // ENABLE_RAYCAST_PICKING_DEBUG return; } @@ -5748,7 +5488,7 @@ void GLCanvas3D::_picking_pass() #if ENABLE_RAYCAST_PICKING_DEBUG ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + ImGuiPureWrap::begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); std::string object_type = "None"; switch (hit.type) { @@ -5772,58 +5512,58 @@ void GLCanvas3D::_picking_pass() default: { break; } } - auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color, + auto add_strings_row_to_table = [](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color, const std::string& col_3 = "", const ImVec4& col_3_color = ImGui::GetStyleColorVec4(ImGuiCol_Text)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(col_1_color, col_1.c_str()); + ImGuiPureWrap::text_colored(col_1_color, col_1.c_str()); ImGui::TableSetColumnIndex(1); - imgui.text_colored(col_2_color, col_2.c_str()); + ImGuiPureWrap::text_colored(col_2_color, col_2.c_str()); if (!col_3.empty()) { ImGui::TableSetColumnIndex(2); - imgui.text_colored(col_3_color, col_3.c_str()); + ImGuiPureWrap::text_colored(col_3_color, col_3.c_str()); } }; char buf[1024]; if (hit.type != SceneRaycaster::EType::None) { - //B18 if (ImGui::BeginTable("Hit", 2)) { - add_strings_row_to_table("Object ID", ImGuiWrapper::COL_BLUE_LIGHT, std::to_string(hit.raycaster_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - add_strings_row_to_table("Type", ImGuiWrapper::COL_BLUE_LIGHT, object_type, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + //B18 + add_strings_row_to_table("Object ID", ImGuiPureWrap::COL_BLUE_LIGHT, std::to_string(hit.raycaster_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Type", ImGuiPureWrap::COL_BLUE_LIGHT, object_type, ImGui::GetStyleColorVec4(ImGuiCol_Text)); sprintf(buf, "%.3f, %.3f, %.3f", hit.position.x(), hit.position.y(), hit.position.z()); - add_strings_row_to_table("Position", ImGuiWrapper::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Position", ImGuiPureWrap::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); sprintf(buf, "%.3f, %.3f, %.3f", hit.normal.x(), hit.normal.y(), hit.normal.z()); - add_strings_row_to_table("Normal", ImGuiWrapper::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Normal", ImGuiPureWrap::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); ImGui::EndTable(); } } else - imgui.text("NO HIT"); + ImGuiPureWrap::text("NO HIT"); ImGui::Separator(); - imgui.text("Registered for picking:"); + ImGuiPureWrap::text("Registered for picking:"); if (ImGui::BeginTable("Raycasters", 2)) { //B18 sprintf(buf, "%d (%d)", (int)m_scene_raycaster.beds_count(), (int)m_scene_raycaster.active_beds_count()); - add_strings_row_to_table("Beds", ImGuiWrapper::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Beds", ImGuiPureWrap::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); sprintf(buf, "%d (%d)", (int)m_scene_raycaster.volumes_count(), (int)m_scene_raycaster.active_volumes_count()); - add_strings_row_to_table("Volumes", ImGuiWrapper::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Volumes", ImGuiPureWrap::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count()); - add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Gizmo elements", ImGuiPureWrap::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); sprintf(buf, "%d (%d)", (int)m_scene_raycaster.fallback_gizmos_count(), (int)m_scene_raycaster.active_fallback_gizmos_count()); - add_strings_row_to_table("Gizmo2 elements", ImGuiWrapper::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Gizmo2 elements", ImGuiPureWrap::COL_BLUE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); ImGui::EndTable(); } std::vector>* gizmo_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::Gizmo); if (gizmo_raycasters != nullptr && !gizmo_raycasters->empty()) { ImGui::Separator(); - imgui.text("Gizmo raycasters IDs:"); + ImGuiPureWrap::text("Gizmo raycasters IDs:"); if (ImGui::BeginTable("GizmoRaycasters", 3)) { for (size_t i = 0; i < gizmo_raycasters->size(); ++i) { //B18 - add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_BLUE_LIGHT, + add_strings_row_to_table(std::to_string(i), ImGuiPureWrap::COL_BLUE_LIGHT, std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, (*gizmo_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text), to_string(Geometry::Transformation((*gizmo_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); } @@ -5834,10 +5574,10 @@ void GLCanvas3D::_picking_pass() std::vector>* gizmo2_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::FallbackGizmo); if (gizmo2_raycasters != nullptr && !gizmo2_raycasters->empty()) { ImGui::Separator(); - imgui.text("Gizmo2 raycasters IDs:"); + ImGuiPureWrap::text("Gizmo2 raycasters IDs:"); if (ImGui::BeginTable("Gizmo2Raycasters", 3)) { for (size_t i = 0; i < gizmo2_raycasters->size(); ++i) { - add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_BLUE_LIGHT, + add_strings_row_to_table(std::to_string(i), ImGuiPureWrap::COL_BLUE_LIGHT, std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::FallbackGizmo, (*gizmo2_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text), to_string(Geometry::Transformation((*gizmo2_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); } @@ -5845,7 +5585,7 @@ void GLCanvas3D::_picking_pass() } } - imgui.end(); + ImGuiPureWrap::end(); #endif // ENABLE_RAYCAST_PICKING_DEBUG } @@ -5884,11 +5624,11 @@ void GLCanvas3D::_rectangular_selection_picking_pass() glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); glsafe(::glGenRenderbuffers(1, &render_depth)); glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height)); #else glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); } else { @@ -6107,7 +5847,7 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& void GLCanvas3D::_render_bed_axes() { - m_bed.render_axes(); + m_bed.render_axes(); } void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) @@ -6226,16 +5966,6 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) m_camera_clipping_plane = ClippingPlane::ClipsNothing(); } -void GLCanvas3D::_render_gcode() -{ - m_gcode_viewer.render(); -} - -void GLCanvas3D::_render_gcode_cog() -{ - m_gcode_viewer.render_cog(); -} - void GLCanvas3D::_render_selection() { float scale_factor = 1.0; @@ -6275,41 +6005,41 @@ void GLCanvas3D::_render_sequential_clearance() m_sequential_print_clearance.render(); } -#if ENABLE_RENDER_SELECTION_CENTER -void GLCanvas3D::_render_selection_center() -{ - m_selection.render_center(m_gizmos.is_dragging()); -} -#endif // ENABLE_RENDER_SELECTION_CENTER -void GLCanvas3D::_check_and_update_toolbar_icon_scale() +bool GLCanvas3D::check_toolbar_icon_size(float init_scale, float& new_scale_to_save, bool is_custom, int counter/* = 3*/) { - // Don't update a toolbar scale, when we are on a Preview - if (wxGetApp().plater()->is_preview_shown()) - return; - - const float scale = wxGetApp().toolbar_icon_scale(); const Size cnv_size = get_canvas_size(); - int size = int(GLToolbar::Default_Icons_Size * scale); - - // Set current size for all top toolbars. It will be used for next calculations - GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); #if ENABLE_RETINA_GL - const float sc = m_retina_helper->get_scale_factor() * scale; - m_main_toolbar.set_scale(sc); - m_undoredo_toolbar.set_scale(sc); - collapse_toolbar.set_scale(sc); - size *= int(m_retina_helper->get_scale_factor()); + float max_scale = m_retina_helper->get_scale_factor(); #else - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); - collapse_toolbar.set_icons_size(size); + float max_scale = 0.1f * wxGetApp().em_unit(); #endif // ENABLE_RETINA_GL + float scale = init_scale * max_scale; + + int size = int(GLToolbar::Default_Icons_Size * scale); + int gizmo_size = int(GLGizmosManager::Default_Icons_Size * scale); + + // Set current scale for all top toolbars. It will be used for next calculations + + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); + + if (!is_approx(scale, m_main_toolbar.get_scale(), 0.015f)) { + m_main_toolbar.set_scale(scale); + m_undoredo_toolbar.set_scale(scale); + collapse_toolbar.set_scale(scale); + view_toolbar.set_scale(scale); + m_gizmos.set_overlay_scale(scale); + + view_toolbar.set_icons_size(gizmo_size); + } + const float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); - int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); + float items_cnt = float(m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt()); const float noitems_width = top_tb_width - float(size) * items_cnt; // width of separators and borders in top toolbars + items_cnt += 1.6f; // +1.6 means a place for some minimal margin between toolbars // calculate scale needed for items in all top toolbars // the std::max() is there because on some Linux dialects/virtual machines this code is called when the canvas has not been properly initialized yet, @@ -6318,18 +6048,45 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() // https://github.com/supermerill/SuperSlicer/issues/854 const float new_h_scale = std::max((cnv_size.get_width() - noitems_width), 1.0f) / (items_cnt * GLToolbar::Default_Icons_Size); - items_cnt = m_gizmos.get_selectable_icons_cnt() + 3; // +3 means a place for top and view toolbars and separators in gizmos toolbar + float gizmos_height = m_gizmos.get_scaled_total_height(); + int giz_items_cnt = m_gizmos.get_selectable_icons_cnt(); + float noitems_height = gizmos_height - gizmo_size * giz_items_cnt; // height of separators and borders in gizmos toolbars - // calculate scale needed for items in the gizmos toolbar - const float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size); + noitems_height += m_main_toolbar.get_height(); // increase its value to main_toolbar height + giz_items_cnt += 2; // +2 means a place for view toolbar + + const float new_v_scale = std::max((cnv_size.get_height() - noitems_height), 1.0f) / (giz_items_cnt * GLGizmosManager::Default_Icons_Size); // set minimum scale as a auto scale for the toolbars float new_scale = std::min(new_h_scale, new_v_scale); -#if ENABLE_RETINA_GL - new_scale /= m_retina_helper->get_scale_factor(); -#endif - if (fabs(new_scale - scale) > 0.015) // scale is changed by 1.5% and more - wxGetApp().set_auto_toolbar_icon_scale(new_scale); + + new_scale_to_save = std::min(new_scale / max_scale, 1.f); + + if (is_custom && new_scale_to_save > init_scale) + return true; // we need to save new value, so return true + + if (is_approx(init_scale, new_scale_to_save, 0.015f) || counter == 0) + return true; + + // scale is changed by 1.5% and more + init_scale = new_scale_to_save; + counter--; + return check_toolbar_icon_size(init_scale, new_scale_to_save, is_custom, counter); +} + + +void GLCanvas3D::_check_and_update_toolbar_icon_scale() +{ + // Don't update a toolbar scale, when we are on a Preview + if (wxGetApp().plater()->is_preview_shown()) + return; + + bool is_custom; + const float init_scale = wxGetApp().toolbar_icon_scale(is_custom); + float new_scale_to_save; + if (check_toolbar_icon_size(init_scale, new_scale_to_save, is_custom) && + !is_approx(init_scale, new_scale_to_save, 0.015f)) + wxGetApp().set_auto_toolbar_icon_scale(new_scale_to_save); } void GLCanvas3D::_render_overlays() @@ -6395,24 +6152,8 @@ void GLCanvas3D::_render_volumes_for_picking(const Camera& camera) const glsafe(::glEnable(GL_CULL_FACE)); } -void GLCanvas3D::_render_current_gizmo() const -{ - m_gizmos.render_current_gizmo(); -} - void GLCanvas3D::_render_gizmos_overlay() { -#if ENABLE_RETINA_GL -// m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); - const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); - m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment -#else -// m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor()); -// m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f); - const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); - m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment -#endif /* __WXMSW__ */ - m_gizmos.render_overlay(); if (m_gizmo_highlighter.m_render_arrow) @@ -6461,7 +6202,13 @@ void GLCanvas3D::_render_collapse_toolbar() const const Size cnv_size = get_canvas_size(); const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; const float top = 0.5f * (float)cnv_size.get_height(); +#if ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC + // When the application is run as GCodeViewer, render the collapse toolbar outside of the screen + const float left = wxGetApp().is_gcode_viewer() ? 0.5f * (float)cnv_size.get_width() : + 0.5f * (float)cnv_size.get_width() - collapse_toolbar.get_width() - band; +#else const float left = 0.5f * (float)cnv_size.get_width() - collapse_toolbar.get_width() - band; +#endif // ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC collapse_toolbar.set_position(top, left); collapse_toolbar.render(*this); @@ -6471,19 +6218,6 @@ void GLCanvas3D::_render_view_toolbar() const { GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); -#if __APPLE__ - view_toolbar.set_scale(scale); -#else // if GTK3 - const float size = int(GLGizmosManager::Default_Icons_Size * scale); - view_toolbar.set_icons_size(size); -#endif // __APPLE__ -#else - const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale()); - view_toolbar.set_icons_size(size); -#endif // ENABLE_RETINA_GL - const Size cnv_size = get_canvas_size(); // places the toolbar on the bottom-left corner of the 3d scene const float top = -0.5f * (float)cnv_size.get_height() + view_toolbar.get_height(); @@ -6498,10 +6232,10 @@ void GLCanvas3D::_render_camera_target() static const float half_length = 5.0f; glsafe(::glDisable(GL_DEPTH_TEST)); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(2.0f)); +#endif // !SLIC3R_OPENGL_ES const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast(); m_camera_target.target = target.cast(); @@ -6537,22 +6271,26 @@ void GLCanvas3D::_render_camera_target() } } -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(m_camera_target.target)); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.5f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.5f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES for (int i = 0; i < 3; ++i) { m_camera_target.axis[i].render(); } @@ -6561,6 +6299,36 @@ void GLCanvas3D::_render_camera_target() } #endif // ENABLE_SHOW_CAMERA_TARGET +static void render_sla_layer_legend(const SLAPrint& print, int layer_idx, int cnv_width) +{ + const std::vector& areas = print.print_statistics().layers_areas; + const std::vector& times = print.print_statistics().layers_times_running_total; + const double display_area = print.printer_config().display_height * print.printer_config().display_width; + if (layer_idx >= 0 && layer_idx < int(areas.size())) { + const double area = areas[layer_idx]; + const double time = times[layer_idx] - (layer_idx == 0 ? 0. : times[layer_idx-1]); + const double time_until_layer = times[layer_idx]; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + ImGuiPureWrap::set_next_window_pos(float(cnv_width) - imgui.get_style_scaling() * 5.f, 5.f, ImGuiCond_Always, 1.0f, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + + ImGuiPureWrap::begin(_u8L("Layer statistics"), ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing); + ImGui::Text("%s", GUI::format(_u8L("Layer area: %1% mm²"), int(0.1 + std::round(area))).c_str()); + int area_percent_int = int(std::round(100. * area/display_area)); + ImGui::Text("%s", GUI::format(_u8L("Area fill: %1% %%"), area_percent_int == 0 ? "<1" : std::to_string(area_percent_int)).c_str()); + ImGui::Separator(); + ImGui::Text("%s", GUI::format(_u8L("Layer time: %1%"), get_time_dhms(time)).c_str()); + std::string buffer_str = _u8L("Time since start: %1%"); + ImGui::Text("%s", GUI::format(buffer_str, get_time_dhms(time_until_layer)).c_str()); + + // The dummy control below uses the assumption that the total time string will be the longest + // and forces the width of the window large enough so it does not resize depending on the current value. + ImGui::Dummy(ImVec2(ImGui::CalcTextSize(GUI::format(buffer_str, get_time_dhms(82799)).c_str()).x, 0.)); + ImGuiPureWrap::end(); + } +} + void GLCanvas3D::_render_sla_slices() { if (!m_use_clipping_planes || current_printer_technology() != ptSLA) @@ -6572,6 +6340,13 @@ void GLCanvas3D::_render_sla_slices() // nothing to render, return return; + if (print->finished()) { + double slider_width = 0.; + if (const Preview* preview = dynamic_cast(m_canvas->GetParent())) + slider_width = preview->get_layers_slider_width(); + render_sla_layer_legend(*print, m_layer_slider_index, get_canvas_size().get_width() - slider_width); + } + double clip_min_z = -m_clipping_planes[0].get_data()[3]; double clip_max_z = m_clipping_planes[1].get_data()[3]; for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) { @@ -6689,11 +6464,6 @@ void GLCanvas3D::_render_sla_slices() } } -void GLCanvas3D::_render_selection_sidebar_hints() -{ - m_selection.render_sidebar_hints(m_sidebar_field); -} - void GLCanvas3D::_update_volumes_hover_state() { // skip update if the Gizmo Measure is active @@ -6821,575 +6591,6 @@ Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) return (std::abs(ray.unit_vector().z()) < EPSILON) ? ray.a : ray.intersect_plane(0.0); } -void GLCanvas3D::_start_timer() -{ - m_timer.Start(100, wxTIMER_CONTINUOUS); -} - -void GLCanvas3D::_stop_timer() -{ - m_timer.Stop(); -} - -void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) -{ - const Print *print = this->fff_print(); - if (print == nullptr) - return; - - if (! print->is_step_done(psSkirtBrim)) - return; - - if (!print->has_skirt() && !print->has_brim()) - return; - - const ColorRGBA color = ColorRGBA::GREENISH(); - - // number of skirt layers - size_t total_layer_count = 0; - for (const PrintObject* print_object : print->objects()) { - total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); - } - size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); - if (skirt_height == 0 && print->has_brim()) - skirt_height = 1; - - // Get first skirt_height layers. - //FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature. - // This is not critical as this is just an initial preview. - const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); }); - std::vector print_zs; - print_zs.reserve(skirt_height * 2); - for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i) - print_zs.emplace_back(float(highest_object->layers()[i]->print_z)); - // Only add skirt for the raft layers. - for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i) - print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z)); - sort_remove_duplicates(print_zs); - skirt_height = std::min(skirt_height, print_zs.size()); - print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); - - GLVolume* volume = m_volumes.new_toolpath_volume(color); - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - for (size_t i = 0; i < skirt_height; ++ i) { - volume->print_zs.emplace_back(print_zs[i]); - volume->offsets.emplace_back(init_data.indices_count()); - if (i == 0) - _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data); - _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); - // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - if (init_data.vertices_size_bytes() >= MAX_VERTEX_BUFFER_SIZE) { - volume->model.init_from(std::move(init_data)); - volume->is_outside = !contains(build_volume, volume->model); - volume = m_volumes.new_toolpath_volume(volume->color); - init_data = GLModel::Geometry(); - } - } - init_data = GLModel::Geometry(); - if (init_data.is_empty()) { - delete volume; - m_volumes.volumes.pop_back(); - } - else { - volume->model.init_from(std::move(init_data)); - volume->is_outside = !contains(build_volume, volume->model); -} -} - -void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) -{ - std::vector tool_colors; - decode_colors(str_tool_colors, tool_colors); - - struct Ctxt - { - const PrintInstances *shifted_copies; - std::vector layers; - bool has_perimeters; - bool has_infill; - bool has_support; - const std::vector* tool_colors; - bool is_single_material_print; - int extruders_cnt; - const std::vector* color_print_values; - - static ColorRGBA color_perimeters() { return ColorRGBA::YELLOW(); } - static ColorRGBA color_infill() { return ColorRGBA::REDISH(); } - static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } - static ColorRGBA color_pause_or_custom_code() { return ColorRGBA::GRAY(); } - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; } - const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } - - // For coloring by a color_print(M600), return a parsed color. - bool color_by_color_print() const { return color_print_values!=nullptr; } - const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { - const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""}; - auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); - return (it - color_print_values->begin()) % number_tools(); - } - - const size_t color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const - { - const coordf_t print_z = layers[layer_idx]->print_z; - - auto it = std::find_if(color_print_values->begin(), color_print_values->end(), - [print_z](const CustomGCode::Item& code) - { return fabs(code.print_z - print_z) < EPSILON; }); - if (it != color_print_values->end()) { - CustomGCode::Type type = it->type; - // pause print or custom Gcode - if (type == CustomGCode::PausePrint || - (type != CustomGCode::ColorChange && type != CustomGCode::ToolChange)) - return number_tools()-1; // last color item is a gray color for pause print or custom G-code - - // change tool (extruder) - if (type == CustomGCode::ToolChange) - return get_color_idx_for_tool_change(it, extruder); - // change color for current extruder - if (type == CustomGCode::ColorChange) { - int color_idx = get_color_idx_for_color_change(it, extruder); - if (color_idx >= 0) - return color_idx; - } - } - - const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""}; - it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); - while (it != color_print_values->begin()) { - --it; - // change color for current extruder - if (it->type == CustomGCode::ColorChange) { - int color_idx = get_color_idx_for_color_change(it, extruder); - if (color_idx >= 0) - return color_idx; - } - // change tool (extruder) - if (it->type == CustomGCode::ToolChange) - return get_color_idx_for_tool_change(it, extruder); - } - - return std::min(extruders_cnt - 1, std::max(extruder - 1, 0));; - } - - private: - int get_m600_color_idx(std::vector::const_iterator it) const - { - int shift = 0; - while (it != color_print_values->begin()) { - --it; - if (it->type == CustomGCode::ColorChange) - shift++; - } - return extruders_cnt + shift; - } - - int get_color_idx_for_tool_change(std::vector::const_iterator it, const int extruder) const - { - const int current_extruder = it->extruder == 0 ? extruder : it->extruder; - if (number_tools() == size_t(extruders_cnt + 1)) // there is no one "M600" - return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); - - auto it_n = it; - while (it_n != color_print_values->begin()) { - --it_n; - if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder) - return get_m600_color_idx(it_n); - } - - return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); - } - - int get_color_idx_for_color_change(std::vector::const_iterator it, const int extruder) const - { - if (extruders_cnt == 1) - return get_m600_color_idx(it); - - auto it_n = it; - bool is_tool_change = false; - while (it_n != color_print_values->begin()) { - --it_n; - if (it_n->type == CustomGCode::ToolChange) { - is_tool_change = true; - if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) - return get_m600_color_idx(it); - break; - } - } - if (!is_tool_change && it->extruder == extruder) - return get_m600_color_idx(it); - - return -1; - } - - } ctxt; - - ctxt.has_perimeters = print_object.is_step_done(posPerimeters); - ctxt.has_infill = print_object.is_step_done(posInfill); - ctxt.has_support = print_object.is_step_done(posSupportMaterial); - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values; - ctxt.is_single_material_print = this->fff_print()->extruders().size()==1; - ctxt.extruders_cnt = wxGetApp().extruders_edited_cnt(); - - ctxt.shifted_copies = &print_object.instances(); - - // order layers by print_z - { - size_t nlayers = 0; - if (ctxt.has_perimeters || ctxt.has_infill) - nlayers = print_object.layers().size(); - if (ctxt.has_support) - nlayers += print_object.support_layers().size(); - ctxt.layers.reserve(nlayers); - } - if (ctxt.has_perimeters || ctxt.has_infill) - for (const Layer *layer : print_object.layers()) - ctxt.layers.emplace_back(layer); - if (ctxt.has_support) - for (const Layer *layer : print_object.support_layers()) - ctxt.layers.emplace_back(layer); - std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); - - // Maximum size of an allocation block: 32MB / sizeof(float) - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); - - const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print(); - - //FIXME Improve the heuristics for a grain size. - size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { - // Allocate the volume before locking. - GLVolume *volume = new GLVolume(color); - volume->is_extrusion_path = true; - // to prevent sending data to gpu (in the main thread) while - // editing the model geometry - volume->model.disable_render(); - tbb::spin_mutex::scoped_lock lock; - // Lock by ROII, so if the emplace_back() fails, the lock will be released. - lock.acquire(new_volume_mutex); - m_volumes.volumes.emplace_back(volume); - lock.release(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - // Limit the number of threads as the code below does not scale well due to memory pressure. - // (most of the time is spent in malloc / free / memmove) - // Not using all the threads leaves some of the threads to G-code generator. - tbb::task_arena limited_arena(std::min(tbb::this_task_arena::max_concurrency(), 4)); - limited_arena.execute([&ctxt, grain_size, &new_volume, is_selected_separate_extruder, this]{ - tbb::parallel_for( - tbb::blocked_range(0, ctxt.layers.size(), grain_size), - [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { - GLVolumePtrs vols; - std::vector geometries; - auto select_geometry = [&ctxt, &geometries](size_t layer_idx, int extruder, int feature) -> GLModel::Geometry& { - return geometries[ctxt.color_by_color_print() ? - ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : - ctxt.color_by_tool() ? - std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : - feature - ]; - }; - if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) { - vols.emplace_back(new_volume(ctxt.color_tool(i))); - geometries.emplace_back(GLModel::Geometry()); - } - } - else { - vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; - geometries = { GLModel::Geometry(), GLModel::Geometry(), GLModel::Geometry() }; - } - - assert(vols.size() == geometries.size()); - for (GLModel::Geometry& g : geometries) { - g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - } - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - const Layer *layer = ctxt.layers[idx_layer]; - - if (is_selected_separate_extruder) { - bool at_least_one_has_correct_extruder = false; - for (const LayerRegion* layerm : layer->regions()) { - if (layerm->slices().empty()) - continue; - const PrintRegionConfig& cfg = layerm->region().config(); - if (cfg.perimeter_extruder.value == m_selected_extruder || - cfg.infill_extruder.value == m_selected_extruder || - cfg.solid_infill_extruder.value == m_selected_extruder ) { - at_least_one_has_correct_extruder = true; - break; - } - } - if (!at_least_one_has_correct_extruder) - continue; - } - - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume* vol = vols[i]; - if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { - vol->print_zs.emplace_back(layer->print_z); - vol->offsets.emplace_back(geometries[i].indices_count()); - } - } - - for (const PrintInstance &instance : *ctxt.shifted_copies) { - const Point © = instance.shift; - for (const LayerRegion *layerm : layer->regions()) { - if (is_selected_separate_extruder) { - const PrintRegionConfig& cfg = layerm->region().config(); - if (cfg.perimeter_extruder.value != m_selected_extruder || - cfg.infill_extruder.value != m_selected_extruder || - cfg.solid_infill_extruder.value != m_selected_extruder) - continue; - } - if (ctxt.has_perimeters) - _3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy, - select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); - if (ctxt.has_infill) { - for (const ExtrusionEntity *ee : layerm->fills()) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast(ee); - if (! fill->entities.empty()) - _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - select_geometry(idx_layer, fill->entities.front()->role().is_solid_infill() ? - layerm->region().config().solid_infill_extruder : - layerm->region().config().infill_extruder, 1)); - } - } - } - if (ctxt.has_support) { - const SupportLayer *support_layer = dynamic_cast(layer); - if (support_layer) { - for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) - _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - select_geometry(idx_layer, (extrusion_entity->role() == ExtrusionRole::SupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, 2)); - } - } - } - // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - vol.model.init_from(std::move(geometries[i])); - vols[i] = new_volume(vol.color); - } - } - } - - for (size_t i = 0; i < vols.size(); ++i) { - if (!geometries[i].is_empty()) - vols[i]->model.init_from(std::move(geometries[i])); - } - }); - }); // task arena - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); - // Remove empty volumes from the newly added volumes. - { - for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) - if ((*ptr_it)->empty()) { - delete *ptr_it; - *ptr_it = nullptr; - } - m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); - } - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; - v->is_outside = !contains(build_volume, v->model); - // We are done editinig the model, now it can be sent to gpu - v->model.enable_render(); - } - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); -} - -void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, const std::vector& str_tool_colors) -{ - const Print *print = this->fff_print(); - if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) - return; - - if (!print->is_step_done(psWipeTower)) - return; - - std::vector tool_colors; - decode_colors(str_tool_colors, tool_colors); - - struct Ctxt - { - const Print *print; - const std::vector *tool_colors; - Vec2f wipe_tower_pos; - float wipe_tower_angle; - - static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; } - const ColorRGBA& color_tool(size_t tool) const { return (*tool_colors)[tool]; } - int volume_idx(int tool, int feature) const { - return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; - } - - const std::vector& tool_change(size_t idx) { - const auto &tool_changes = print->wipe_tower_data().tool_changes; - return priming.empty() ? - ((idx == tool_changes.size()) ? final : tool_changes[idx]) : - ((idx == 0) ? priming : (idx == tool_changes.size() + 1) ? final : tool_changes[idx - 1]); - } - std::vector priming; - std::vector final; - } ctxt; - - ctxt.print = print; - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) - for (int i=0; i<(int)print->wipe_tower_data().priming.get()->size(); ++i) - ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i)); - if (print->wipe_tower_data().final_purge) - ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); - - ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; - ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); - - //FIXME Improve the heuristics for a grain size. - size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); - size_t grain_size = std::max(n_items / 128, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { - auto *volume = new GLVolume(color); - volume->is_extrusion_path = true; - // to prevent sending data to gpu (in the main thread) while - // editing the model geometry - volume->model.disable_render(); - tbb::spin_mutex::scoped_lock lock; - lock.acquire(new_volume_mutex); - m_volumes.volumes.emplace_back(volume); - lock.release(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(n_items); - tbb::parallel_for( - tbb::blocked_range(0, n_items, grain_size), - [&ctxt, &new_volume](const tbb::blocked_range& range) { - // Bounding box of this slab of a wipe tower. - GLVolumePtrs vols; - std::vector geometries; - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) { - vols.emplace_back(new_volume(ctxt.color_tool(i))); - geometries.emplace_back(GLModel::Geometry()); - } - } - else { - vols = { new_volume(ctxt.color_support()) }; - geometries = { GLModel::Geometry() }; - } - - assert(vols.size() == geometries.size()); - for (GLModel::Geometry& g : geometries) { - g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - } - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { - const std::vector &layer = ctxt.tool_change(idx_layer); - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { - vol.print_zs.emplace_back(layer.front().print_z); - vol.offsets.emplace_back(geometries[i].indices_count()); - } - } - for (const WipeTower::ToolChangeResult &extrusions : layer) { - for (size_t i = 1; i < extrusions.extrusions.size();) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - if (e.width == 0.) { - ++i; - continue; - } - size_t j = i + 1; - if (ctxt.color_by_tool()) - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); - else - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); - size_t n_lines = j - i; - Lines lines; - std::vector widths; - std::vector heights; - lines.reserve(n_lines); - widths.reserve(n_lines); - heights.assign(n_lines, extrusions.layer_height); - WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; - - if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation - e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos; - e_prev.pos += ctxt.wipe_tower_pos; - } - - for (; i < j; ++i) { - WipeTower::Extrusion e = extrusions.extrusions[i]; - assert(e.width > 0.f); - if (!extrusions.priming) { - e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos; - e.pos += ctxt.wipe_tower_pos; - } - - lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y())); - widths.emplace_back(e.width); - - e_prev = e; - } - - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - geometries[ctxt.volume_idx(e.tool, 0)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { - vol.model.init_from(std::move(geometries[i])); - vols[i] = new_volume(vol.color); - } - } - - for (size_t i = 0; i < vols.size(); ++i) { - if (!geometries[i].is_empty()) - vols[i]->model.init_from(std::move(geometries[i])); - } - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); - // Remove empty volumes from the newly added volumes. - { - for (auto ptr_it = m_volumes.volumes.begin() + volumes_cnt_initial; ptr_it != m_volumes.volumes.end(); ++ptr_it) - if ((*ptr_it)->empty()) { - delete *ptr_it; - *ptr_it = nullptr; - } - m_volumes.volumes.erase(std::remove(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), nullptr), m_volumes.volumes.end()); - } - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { - GLVolume* v = m_volumes.volumes[i]; - v->is_outside = !contains(build_volume, v->model); - // We are done editinig the model, now it can be sent to gpu - v->model.enable_render(); - } - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); -} - // While it looks like we can call // this->reload_scene(true, true) // the two functions are quite different: @@ -7446,11 +6647,6 @@ void GLCanvas3D::_load_sla_shells() update_volumes_colors_by_extruder(); } -void GLCanvas3D::_update_sla_shells_outside_state() -{ - check_volumes_outside_state(); -} - void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) { _set_current(); @@ -7689,11 +6885,6 @@ bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() return false; } -bool GLCanvas3D::is_search_pressed() const -{ - return m_main_toolbar.is_item_pressed("search"); -} - bool GLCanvas3D::_deactivate_arrange_menu() { if (m_main_toolbar.is_item_pressed("arrange")) { @@ -7704,26 +6895,6 @@ bool GLCanvas3D::_deactivate_arrange_menu() return false; } -bool GLCanvas3D::_deactivate_search_toolbar_item() -{ - if (is_search_pressed()) { - m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); - return true; - } - - return false; -} - -bool GLCanvas3D::_activate_search_toolbar_item() -{ - if (!m_main_toolbar.is_item_pressed("search")) { - m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this); - return true; - } - - return false; -} - bool GLCanvas3D::_deactivate_collapse_toolbar_items() { GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); @@ -7772,11 +6943,6 @@ void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot) wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); } -void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const -{ - apply_wipe_tower(m_pos, m_rotation); -} - void GLCanvas3D::RenderTimer::Notify() { wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); @@ -7792,11 +6958,6 @@ void GLCanvas3D::GizmoHighlighterTimer::Notify() wxPostEvent((wxEvtHandler*)GetOwner(), GizmoHighlighterTimerEvent(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, *this)); } -void GLCanvas3D::ToolbarHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} - void GLCanvas3D::ToolbarHighlighter::init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas) { if (m_timer.IsRunning()) @@ -7814,9 +6975,9 @@ void GLCanvas3D::ToolbarHighlighter::invalidate() { m_timer.Stop(); - if (m_toolbar_item) { + if (m_toolbar_item) m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::NotHighlighted); - } + m_toolbar_item = nullptr; m_blink_counter = 0; m_render_arrow = false; @@ -7841,11 +7002,6 @@ void GLCanvas3D::ToolbarHighlighter::blink() invalidate(); } -void GLCanvas3D::GizmoHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) -{ - m_timer.SetOwner(owner, timerid); -} - void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas) { if (m_timer.IsRunning()) @@ -7898,14 +7054,14 @@ void GLCanvas3D::show_binary_gcode_debug_window() bgcode::binarize::BinarizerConfig& binarizer_config = GCodeProcessor::get_binarizer_config(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.begin(std::string("Binary GCode"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(std::string("Binary GCode"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); using namespace bgcode::core; if (ImGui::BeginTable("BinaryGCodeConfig", 2)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "File metadata compression"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "File metadata compression"); ImGui::TableSetColumnIndex(1); std::vector options = { "None", "Deflate", "heatshrink 11,4", "heatshrink 12,4" }; int option_id = (int)binarizer_config.compression.file_metadata; @@ -7914,7 +7070,7 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Printer metadata compression"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Printer metadata compression"); ImGui::TableSetColumnIndex(1); option_id = (int)binarizer_config.compression.printer_metadata; if (imgui.combo(std::string("##printer_metadata_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) @@ -7922,7 +7078,7 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Print metadata compression"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Print metadata compression"); ImGui::TableSetColumnIndex(1); option_id = (int)binarizer_config.compression.print_metadata; if (imgui.combo(std::string("##print_metadata_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) @@ -7930,7 +7086,7 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Slicer metadata compression"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Slicer metadata compression"); ImGui::TableSetColumnIndex(1); option_id = (int)binarizer_config.compression.slicer_metadata; if (imgui.combo(std::string("##slicer_metadata_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) @@ -7938,7 +7094,7 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "GCode compression"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "GCode compression"); ImGui::TableSetColumnIndex(1); option_id = (int)binarizer_config.compression.gcode; if (imgui.combo(std::string("##gcode_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) @@ -7946,7 +7102,7 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "GCode encoding"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "GCode encoding"); ImGui::TableSetColumnIndex(1); options = { "None", "MeatPack", "MeatPack Comments" }; option_id = (int)binarizer_config.gcode_encoding; @@ -7955,7 +7111,7 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Metadata encoding"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Metadata encoding"); ImGui::TableSetColumnIndex(1); options = { "INI" }; option_id = (int)binarizer_config.metadata_encoding; @@ -7964,7 +7120,7 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - imgui.text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Checksum type"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Checksum type"); ImGui::TableSetColumnIndex(1); options = { "None", "CRC32" }; option_id = (int)binarizer_config.checksum; @@ -7974,13 +7130,14 @@ void GLCanvas3D::show_binary_gcode_debug_window() ImGui::EndTable(); ImGui::Separator(); - imgui.text("!!! WARNING !!!"); - imgui.text("Changing values does NOT invalidate the current slice"); + ImGuiPureWrap::text("!!! WARNING !!!"); + ImGuiPureWrap::text("Changing values does NOT invalidate the current slice"); } - imgui.end(); + ImGuiPureWrap::end(); } #endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) { const ModelVolume * ret = nullptr; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 88ee311..2aee27b 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -173,9 +173,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_SLIDERS_MANIPULATION, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent); @@ -218,6 +216,8 @@ class GLCanvas3D SlicingParameters *m_slicing_parameters{ nullptr }; std::vector m_layer_height_profile; bool m_layer_height_profile_modified{ false }; + // Shrinkage compensation to apply when we need to use object_max_z with Z compensation. + Vec3d m_shrinkage_compensation{ Vec3d::Ones() }; mutable float m_adaptive_quality{ 0.5f }; mutable HeightProfileSmoothingParams m_smooth_params; @@ -275,8 +275,8 @@ class GLCanvas3D bool is_allowed() const; - bool is_enabled() const; - void set_enabled(bool enabled); + bool is_enabled() const { return m_enabled; } + void set_enabled(bool enabled) { m_enabled = is_allowed() && enabled; } void render_overlay(const GLCanvas3D& canvas); void render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes); @@ -298,6 +298,8 @@ class GLCanvas3D std::pair> get_layers_height_data(); + void set_shrinkage_compensation(const Vec3d &shrinkage_compensation) { m_shrinkage_compensation = shrinkage_compensation; }; + private: bool is_initialized() const; void generate_layer_height_texture(); @@ -316,23 +318,18 @@ class GLCanvas3D static const Vec3d Invalid_3D_Point; static const int MoveThresholdPx; - Point start_position_2D; - Vec3d start_position_3D; - int move_volume_idx; - bool move_requires_threshold; - Point move_start_threshold_position_2D; - - public: - Drag(); + Point start_position_2D{ Invalid_2D_Point }; + Vec3d start_position_3D{ Invalid_3D_Point }; + int move_volume_idx{ -1 }; + bool move_requires_threshold{ false }; + Point move_start_threshold_position_2D{ Invalid_2D_Point }; }; - bool dragging; - Vec2d position; - Vec3d scene_position; + bool dragging{ false }; + Vec2d position{ DBL_MAX, DBL_MAX }; + Vec3d scene_position{ DBL_MAX, DBL_MAX, DBL_MAX }; + bool ignore_left_up{ false }; Drag drag; - bool ignore_left_up; - - Mouse(); void set_start_position_2D_as_invalid() { drag.start_position_2D = Drag::Invalid_2D_Point; } void set_start_position_3D_as_invalid() { drag.start_position_3D = Drag::Invalid_3D_Point; } @@ -502,6 +499,7 @@ private: ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; std::array m_sla_caps; + int m_layer_slider_index = -1; std::string m_sidebar_field; // when true renders an extra frame by not resetting m_dirty to false // see request_extra_frame() @@ -509,9 +507,9 @@ private: bool m_event_handlers_bound{ false }; GLVolumeCollection m_volumes; -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES TriangleMesh m_wipe_tower_mesh; -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES GCodeViewer m_gcode_viewer; RenderTimer m_render_timer; @@ -595,6 +593,9 @@ private: ArrangeSettingsDb_AppCfg m_arrange_settings_db; ArrangeSettingsDialogImgui m_arrange_settings_dialog; + // used to show layers times on the layers slider when pre-gcode view is active + std::vector m_gcode_layers_times_cache; + public: struct ContoursList @@ -630,6 +631,7 @@ private: void start_dragging() { m_dragging = true; } bool is_dragging() const { return m_dragging; } void stop_dragging() { m_dragging = false; } + friend class GLCanvas3D; }; @@ -638,7 +640,7 @@ private: struct ToolbarHighlighter { - void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY); + void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY) { m_timer.SetOwner(owner, timerid); } void init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas); void blink(); void invalidate(); @@ -653,7 +655,7 @@ private: struct GizmoHighlighter { - void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY); + void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY) { m_timer.SetOwner(owner, timerid); } void init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas); void blink(); void invalidate(); @@ -690,6 +692,8 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } + wxWindow* get_wxglcanvas_parent(); + bool init(); void post_event(wxEvent &&event); @@ -712,15 +716,15 @@ public: m_scene_raycaster.set_gizmos_on_top(value); } - void set_as_dirty(); + void set_as_dirty() { m_dirty = true; } void requires_check_outside_state() { m_requires_check_outside_state = true; } - unsigned int get_volumes_count() const; + unsigned int get_volumes_count() const { return (unsigned int)m_volumes.volumes.size(); } const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); ModelInstanceEPrintVolumeState check_volumes_outside_state(bool selection_only = true) const; // update the is_outside state of all the volumes contained in the given collection - void check_volumes_outside_state(GLVolumeCollection& volumes) const; + void check_volumes_outside_state(GLVolumeCollection& volumes) const { check_volumes_outside_state(volumes, nullptr, false); } private: // returns true if all the volumes are completely contained in the print volume @@ -732,6 +736,10 @@ public: void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); } + const libvgcode::Interval& get_gcode_view_full_range() const { return m_gcode_viewer.get_gcode_view_full_range(); } + const libvgcode::Interval& get_gcode_view_enabled_range() const { return m_gcode_viewer.get_gcode_view_enabled_range(); } + const libvgcode::Interval& get_gcode_view_visible_range() const { return m_gcode_viewer.get_gcode_view_visible_range(); } + const libvgcode::PathVertex& get_gcode_vertex_at(size_t id) const { return m_gcode_viewer.get_gcode_vertex_at(id); } void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1, const ModelVolume* mv = nullptr); @@ -740,7 +748,7 @@ public: void set_config(const DynamicPrintConfig* config); const DynamicPrintConfig *config() const { return m_config; } - void set_process(BackgroundSlicingProcess* process); + void set_process(BackgroundSlicingProcess* process) { m_process = process; } void set_model(Model* model); const Model* get_model() const { return m_model; } @@ -754,6 +762,8 @@ public: void bed_shape_changed(); + void set_layer_slider_index(int i) { m_layer_slider_index = i; } + void set_clipping_plane(unsigned int id, const ClippingPlane& plane) { if (id < 2) { m_clipping_planes[id] = plane; @@ -775,28 +785,26 @@ public: BoundingBoxf3 volumes_bounding_box() const; BoundingBoxf3 scene_bounding_box() const; - bool is_layers_editing_enabled() const; - bool is_layers_editing_allowed() const; - bool is_search_pressed() const; + bool is_layers_editing_enabled() const { return m_layers_editing.is_enabled(); } + bool is_layers_editing_allowed() const { return m_layers_editing.is_allowed(); } void reset_layer_height_profile(); void adaptive_layer_height_profile(float quality_factor); void smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params); - bool is_reload_delayed() const; + bool is_reload_delayed() const { return m_reload_delayed; } void enable_layers_editing(bool enable); - void enable_legend_texture(bool enable); - void enable_picking(bool enable); - void enable_moving(bool enable); - void enable_gizmos(bool enable); - void enable_selection(bool enable); - void enable_main_toolbar(bool enable); - void enable_undoredo_toolbar(bool enable); - void enable_dynamic_background(bool enable); + void enable_picking(bool enable) { m_picking_enabled = enable; } + void enable_moving(bool enable) { m_moving_enabled = enable; } + void enable_gizmos(bool enable) { m_gizmos.set_enabled(enable); } + void enable_selection(bool enable) { m_selection.set_enabled(enable); } + void enable_main_toolbar(bool enable) { m_main_toolbar.set_enabled(enable); } + void enable_undoredo_toolbar(bool enable) { m_undoredo_toolbar.set_enabled(enable); } + void enable_dynamic_background(bool enable) { m_dynamic_background_enabled = enable; } void enable_labels(bool enable) { m_labels.enable(enable); } void enable_slope(bool enable) { m_slope.enable(enable); } - void allow_multisample(bool allow); + void allow_multisample(bool allow) { m_multisample_allowed = allow; } void zoom_to_bed(); void zoom_to_volumes(); @@ -818,21 +826,15 @@ public: void select_all(); void deselect_all(); - void delete_selected(); + void delete_selected() { m_selection.erase(); } void ensure_on_bed(unsigned int object_idx, bool allow_negative_z); - bool is_gcode_legend_enabled() const { return m_gcode_viewer.is_legend_enabled(); } - GCodeViewer::EViewType get_gcode_view_type() const { return m_gcode_viewer.get_view_type(); } - const std::vector& get_gcode_layers_zs() const; - std::vector get_volumes_print_zs(bool active_only) const; - unsigned int get_gcode_options_visibility_flags() const { return m_gcode_viewer.get_options_visibility_flags(); } - void set_gcode_options_visibility_from_flags(unsigned int flags); - unsigned int get_toolpath_role_visibility_flags() const { return m_gcode_viewer.get_toolpath_role_visibility_flags(); } - void set_toolpath_role_visibility_flags(unsigned int flags); - void set_toolpath_view_type(GCodeViewer::EViewType type); - void set_volumes_z_range(const std::array& range); + std::vector get_gcode_layers_zs() const { return m_gcode_viewer.get_layers_zs(); } + std::vector get_gcode_layers_times() const { return m_gcode_viewer.get_layers_times(); } + const std::vector& get_gcode_layers_times_cache() const { return m_gcode_layers_times_cache; } + void reset_gcode_layers_times_cache() { m_gcode_layers_times_cache.clear(); } + void set_volumes_z_range(const std::array& range) { m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); } void set_toolpaths_z_range(const std::array& range); - 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); @@ -843,16 +845,22 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); void load_gcode_shells(); - void load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); - void refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last); - void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } - GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } + void load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors); + void set_gcode_view_type(libvgcode::EViewType type) { return m_gcode_viewer.set_view_type(type); } + libvgcode::EViewType get_gcode_view_type() const { return m_gcode_viewer.get_view_type(); } + void enable_gcode_view_type_cache_load(bool enable) { m_gcode_viewer.enable_view_type_cache_load(enable); } + void enable_gcode_view_type_cache_write(bool enable) { m_gcode_viewer.enable_view_type_cache_write(enable); } + bool is_gcode_view_type_cache_load_enabled() const { return m_gcode_viewer.is_view_type_cache_load_enabled(); } + bool is_gcode_view_type_cache_write_enabled() const { return m_gcode_viewer.is_view_type_cache_write_enabled(); } + + void load_preview(const std::vector& str_tool_colors, const std::vector& str_color_print_colors, + const std::vector& color_print_values); void load_sla_preview(); - void load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values); void bind_event_handlers(); void unbind_event_handlers(); - void on_size(wxSizeEvent& evt); + void on_size(wxSizeEvent& evt) { m_dirty = true; } void on_idle(wxIdleEvent& evt); void on_char(wxKeyEvent& evt); void on_key(wxKeyEvent& evt); @@ -910,7 +918,7 @@ public: inline const Vec2d bb_size() const { return m_bb.size(); } inline const BoundingBoxf& bounding_box() const { return m_bb; } - void apply_wipe_tower() const; + void apply_wipe_tower() const { apply_wipe_tower(m_pos, m_rotation); } static void apply_wipe_tower(Vec2d pos, double rot); }; @@ -925,7 +933,7 @@ public: double get_size_proportional_to_max_bed_size(double factor) const; void set_cursor(ECursorType type); - void msw_rescale(); + void msw_rescale() { m_gcode_viewer.invalidate_legend(); } void request_extra_frame() { m_extra_frame_requested = true; } @@ -937,16 +945,16 @@ public: void force_main_toolbar_right_action(int item_id) { m_main_toolbar.force_right_action(item_id, *this); } void update_tooltip_for_settings_item_in_main_toolbar(); - bool has_toolpaths_to_export() const; - void export_toolpaths_to_obj(const char* filename) const; + bool has_toolpaths_to_export() const { return m_gcode_viewer.can_export_toolpaths(); } + void export_toolpaths_to_obj(const char* filename) const { m_gcode_viewer.export_toolpaths_to_obj(filename); } void mouse_up_cleanup(); bool are_labels_shown() const { return m_labels.is_shown(); } void show_labels(bool show) { m_labels.show(show); } - bool is_legend_shown() const { return m_gcode_viewer.is_legend_enabled(); } - void show_legend(bool show) { m_gcode_viewer.enable_legend(show); m_dirty = true; } + bool is_legend_shown() const { return m_gcode_viewer.is_legend_shown(); } + void show_legend(bool show) { m_gcode_viewer.show_legend(show); m_dirty = true; } bool is_using_slope() const { return m_slope.is_used(); } void use_slope(bool use) { m_slope.use(use); } @@ -971,7 +979,7 @@ public: if (m_sequential_print_clearance.is_dragging()) m_sequential_print_clearance_first_displacement = true; else - m_sequential_print_clearance.set_contours(ContoursList(), false); + m_sequential_print_clearance.set_contours(ContoursList(), false); set_as_dirty(); request_extra_frame(); } @@ -1039,17 +1047,18 @@ private: void _render_bed_axes(); void _render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); void _render_objects(GLVolumeCollection::ERenderType type); - void _render_gcode(); - void _render_gcode_cog(); + void _render_gcode() { m_gcode_viewer.render(); } + void _render_gcode_cog() { m_gcode_viewer.render_cog(); } void _render_selection(); void _render_sequential_clearance(); + bool check_toolbar_icon_size(float init_scale, float& new_scale_to_save, bool is_custom, int counter = 3); #if ENABLE_RENDER_SELECTION_CENTER - void _render_selection_center(); + void _render_selection_center() { m_selection.render_center(m_gizmos.is_dragging()); } #endif // ENABLE_RENDER_SELECTION_CENTER void _check_and_update_toolbar_icon_scale(); void _render_overlays(); void _render_volumes_for_picking(const Camera& camera) const; - void _render_current_gizmo() const; + void _render_current_gizmo() const { m_gizmos.render_current_gizmo(); } void _render_gizmos_overlay(); void _render_main_toolbar(); void _render_undoredo_toolbar(); @@ -1059,9 +1068,8 @@ private: void _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET void _render_sla_slices(); - void _render_selection_sidebar_hints(); + void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field); } bool _render_undo_redo_stack(const bool is_undo, float pos_x); - bool _render_search_list(float pos_x); bool _render_arrange_menu(float pos_x); void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type); // render thumbnail using an off-screen framebuffer @@ -1082,23 +1090,12 @@ private: // Convert the screen space coordinate to world coordinate on the bed. Vec3d _mouse_to_bed_3d(const Point& mouse_pos); - void _start_timer(); - void _stop_timer(); - - // Create 3D thick extrusion lines for a skirt and brim. - // Adds a new Slic3r::GUI::3DScene::Volume to volumes, updates collision with the build_volume. - void _load_print_toolpaths(const BuildVolume &build_volume); - // Create 3D thick extrusion lines for object forming extrusions. - // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, - // one for perimeters, one for infill and one for supports, updates collision with the build_volume. - void _load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume &build_volume, - const std::vector& str_tool_colors, const std::vector& color_print_values); - // Create 3D thick extrusion lines for wipe tower extrusions, updates collision with the build_volume. - void _load_wipe_tower_toolpaths(const BuildVolume &build_volume, const std::vector& str_tool_colors); + void _start_timer() { m_timer.Start(100, wxTIMER_CONTINUOUS); } + void _stop_timer() { m_timer.Stop(); } // Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished. - void _load_sla_shells(); - void _update_sla_shells_outside_state(); + void _load_sla_shells(); + void _update_sla_shells_outside_state() { check_volumes_outside_state(); } void _set_warning_notification_if_needed(EWarning warning); // generates a warning notification containing the given message @@ -1110,12 +1107,11 @@ private: void _update_selection_from_hover(); bool _deactivate_undo_redo_toolbar_items(); - bool _deactivate_search_toolbar_item(); - bool _activate_search_toolbar_item(); bool _deactivate_collapse_toolbar_items(); bool _deactivate_arrange_menu(); float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } + #if ENABLE_BINARIZED_GCODE_DEBUG_WINDOW void show_binary_gcode_debug_window(); #endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 893bc66..a854041 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -104,7 +104,6 @@ void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, c vertices.emplace_back(tex_coord.y()); } -#if ENABLE_OPENGL_ES void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, const Vec3f& extra) { assert(format.vertex_layout == EVertexLayout::P3N3E3); @@ -118,7 +117,6 @@ void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, c vertices.emplace_back(extra.y()); vertices.emplace_back(extra.z()); } -#endif // ENABLE_OPENGL_ES void GLModel::Geometry::add_vertex(const Vec4f& position) { @@ -275,9 +273,7 @@ size_t GLModel::Geometry::vertex_stride_floats(const Format& format) case EVertexLayout::P3T2: { return 5; } case EVertexLayout::P3N3: { return 6; } case EVertexLayout::P3N3T2: { return 8; } -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3E3: { return 9; } -#endif // ENABLE_OPENGL_ES case EVertexLayout::P4: { return 4; } default: { assert(false); return 0; } }; @@ -292,12 +288,8 @@ size_t GLModel::Geometry::position_stride_floats(const Format& format) case EVertexLayout::P3: case EVertexLayout::P3T2: case EVertexLayout::P3N3: -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3T2: case EVertexLayout::P3N3E3: { return 3; } -#else - case EVertexLayout::P3N3T2: { return 3; } -#endif // ENABLE_OPENGL_ES case EVertexLayout::P4: { return 4; } default: { assert(false); return 0; } }; @@ -313,9 +305,7 @@ size_t GLModel::Geometry::position_offset_floats(const Format& format) case EVertexLayout::P3T2: case EVertexLayout::P3N3: case EVertexLayout::P3N3T2: -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3E3: -#endif // ENABLE_OPENGL_ES case EVertexLayout::P4: { return 0; } default: { assert(false); return 0; } }; @@ -326,12 +316,8 @@ size_t GLModel::Geometry::normal_stride_floats(const Format& format) switch (format.vertex_layout) { case EVertexLayout::P3N3: -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3T2: case EVertexLayout::P3N3E3: { return 3; } -#else - case EVertexLayout::P3N3T2: { return 3; } -#endif // ENABLE_OPENGL_ES default: { assert(false); return 0; } }; } @@ -341,12 +327,8 @@ size_t GLModel::Geometry::normal_offset_floats(const Format& format) switch (format.vertex_layout) { case EVertexLayout::P3N3: -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3T2: case EVertexLayout::P3N3E3: { return 3; } -#else - case EVertexLayout::P3N3T2: { return 3; } -#endif // ENABLE_OPENGL_ES default: { assert(false); return 0; } }; } @@ -373,7 +355,6 @@ size_t GLModel::Geometry::tex_coord_offset_floats(const Format& format) }; } -#if ENABLE_OPENGL_ES size_t GLModel::Geometry::extra_stride_floats(const Format& format) { switch (format.vertex_layout) @@ -391,7 +372,6 @@ size_t GLModel::Geometry::extra_offset_floats(const Format& format) default: { assert(false); return 0; } }; } -#endif // ENABLE_OPENGL_ES size_t GLModel::Geometry::index_stride_bytes(const Geometry& data) { @@ -414,9 +394,7 @@ bool GLModel::Geometry::has_position(const Format& format) case EVertexLayout::P3T2: case EVertexLayout::P3N3: case EVertexLayout::P3N3T2: -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3E3: -#endif // ENABLE_OPENGL_ES case EVertexLayout::P4: { return true; } default: { assert(false); return false; } }; @@ -432,12 +410,8 @@ bool GLModel::Geometry::has_normal(const Format& format) case EVertexLayout::P3T2: case EVertexLayout::P4: { return false; } case EVertexLayout::P3N3: -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3T2: case EVertexLayout::P3N3E3: { return true; } -#else - case EVertexLayout::P3N3T2: { return true; } -#endif // ENABLE_OPENGL_ES default: { assert(false); return false; } }; } @@ -452,15 +426,12 @@ bool GLModel::Geometry::has_tex_coord(const Format& format) case EVertexLayout::P2: case EVertexLayout::P3: case EVertexLayout::P3N3: -#if ENABLE_OPENGL_ES case EVertexLayout::P3N3E3: -#endif // ENABLE_OPENGL_ES case EVertexLayout::P4: { return false; } default: { assert(false); return false; } }; } -#if ENABLE_OPENGL_ES bool GLModel::Geometry::has_extra(const Format& format) { switch (format.vertex_layout) @@ -476,7 +447,6 @@ bool GLModel::Geometry::has_extra(const Format& format) default: { assert(false); return false; } }; } -#endif // ENABLE_OPENGL_ES #if ENABLE_GLMODEL_STATISTICS GLModel::Statistics GLModel::s_statistics; @@ -711,12 +681,16 @@ void GLModel::reset() s_statistics.gpu_memory.vertices.current -= vertices_size_bytes(); #endif // ENABLE_GLMODEL_STATISTICS } -#if ENABLE_GL_CORE_PROFILE - if (m_render_data.vao_id > 0) { - glsafe(::glDeleteVertexArrays(1, &m_render_data.vao_id)); - m_render_data.vao_id = 0; +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + if (m_render_data.vao_id > 0) { + glsafe(::glDeleteVertexArrays(1, &m_render_data.vao_id)); + m_render_data.vao_id = 0; + } +#if !SLIC3R_OPENGL_ES } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES m_render_data.vertices_count = 0; m_render_data.indices_count = 0; @@ -784,23 +758,22 @@ void GLModel::render(const std::pair& range) 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); -#if ENABLE_OPENGL_ES const bool extra = Geometry::has_extra(data.format); -#endif // ENABLE_OPENGL_ES -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(m_render_data.vao_id)); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES // the following binding is needed to set the vertex attributes -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); int position_id = -1; int normal_id = -1; int tex_coord_id = -1; -#if ENABLE_OPENGL_ES int extra_id = -1; -#endif // ENABLE_OPENGL_ES if (position) { position_id = shader->get_attrib_location("v_position"); @@ -823,7 +796,6 @@ void GLModel::render(const std::pair& range) glsafe(::glEnableVertexAttribArray(tex_coord_id)); } } -#if ENABLE_OPENGL_ES if (extra) { extra_id = shader->get_attrib_location("v_extra"); if (extra_id != -1) { @@ -831,23 +803,14 @@ void GLModel::render(const std::pair& range) glsafe(::glEnableVertexAttribArray(extra_id)); } } -#endif // ENABLE_OPENGL_ES shader->set_uniform("uniform_color", data.color); -#if ENABLE_GL_CORE_PROFILE - if (!OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); glsafe(::glDrawElements(mode, range.second - range.first, index_type, (const void*)(range.first * Geometry::index_stride_bytes(data)))); -#if !ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); -#endif // !ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_ES if (extra_id != -1) glsafe(::glDisableVertexAttribArray(extra_id)); -#endif // ENABLE_OPENGL_ES if (tex_coord_id != -1) glsafe(::glDisableVertexAttribArray(tex_coord_id)); if (normal_id != -1) @@ -856,10 +819,13 @@ void GLModel::render(const std::pair& range) glsafe(::glDisableVertexAttribArray(position_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES #if ENABLE_GLMODEL_STATISTICS ++s_statistics.render_calls; @@ -892,10 +858,13 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance return; } -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(m_render_data.vao_id)); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instances_vbo)); const size_t instance_stride = 5 * sizeof(float); @@ -916,9 +885,7 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance const bool position = Geometry::has_position(data.format); const bool normal = Geometry::has_normal(data.format); -#if ENABLE_GL_CORE_PROFILE // the following binding is needed to set the vertex attributes -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); if (position) { @@ -933,13 +900,7 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance shader->set_uniform("uniform_color", data.color); -#if !ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); -#endif // !ENABLE_GL_CORE_PROFILE glsafe(::glDrawElementsInstanced(mode, indices_count(), index_type, (const void*)0, instances_count)); -#if !ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); -#endif // !ENABLE_GL_CORE_PROFILE if (normal) glsafe(::glDisableVertexAttribArray(normal_id)); @@ -950,10 +911,13 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance glsafe(::glDisableVertexAttribArray(offset_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES #if ENABLE_GLMODEL_STATISTICS ++s_statistics.render_instanced_calls; @@ -973,12 +937,14 @@ bool GLModel::send_to_gpu() return false; } -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) { +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glGenVertexArrays(1, &m_render_data.vao_id)); glsafe(::glBindVertexArray(m_render_data.vao_id)); +#if !SLIC3R_OPENGL_ES } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES // vertices glsafe(::glGenBuffers(1, &m_render_data.vbo_id)); @@ -1019,11 +985,6 @@ bool GLModel::send_to_gpu() glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices_size_bytes(), data.indices.data(), GL_STATIC_DRAW)); } -#if ENABLE_GL_CORE_PROFILE - if (!OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - m_render_data.indices_count = indices_count; #if ENABLE_GLMODEL_STATISTICS s_statistics.gpu_memory.indices.current += data.indices_size_bytes(); @@ -1031,10 +992,13 @@ bool GLModel::send_to_gpu() #endif // ENABLE_GLMODEL_STATISTICS data.indices = std::vector(); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES return true; } diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 3d3a8aa..b55c78d 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -43,9 +43,7 @@ namespace GUI { P3T2, // position 3 floats + texture coords 2 floats P3N3, // position 3 floats + normal 3 floats P3N3T2, // position 3 floats + normal 3 floats + texture coords 2 floats -#if ENABLE_OPENGL_ES P3N3E3, // position 3 floats + normal 3 floats + extra 3 floats -#endif // ENABLE_OPENGL_ES P4, // position 4 floats }; @@ -83,9 +81,7 @@ namespace GUI { vertices.insert(vertices.end(), normal.data(), normal.data() + 3); } void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord); // EVertexLayout::P3N3T2 -#if ENABLE_OPENGL_ES void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec3f& extra); // EVertexLayout::P3N3E3 -#endif // ENABLE_OPENGL_ES void add_vertex(const Vec4f& position); // EVertexLayout::P4 void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 @@ -138,29 +134,23 @@ namespace GUI { static size_t tex_coord_offset_floats(const Format& format); static size_t tex_coord_offset_bytes(const Format& format) { return tex_coord_offset_floats(format) * sizeof(float); } -#if ENABLE_OPENGL_ES static size_t extra_stride_floats(const Format& format); static size_t extra_stride_bytes(const Format& format) { return extra_stride_floats(format) * sizeof(float); } static size_t extra_offset_floats(const Format& format); static size_t extra_offset_bytes(const Format& format) { return extra_offset_floats(format) * sizeof(float); } -#endif // ENABLE_OPENGL_ES static size_t index_stride_bytes(const Geometry& data); static bool has_position(const Format& format); static bool has_normal(const Format& format); static bool has_tex_coord(const Format& format); -#if ENABLE_OPENGL_ES static bool has_extra(const Format& format); -#endif // ENABLE_OPENGL_ES }; struct RenderData { Geometry geometry; -#if ENABLE_GL_CORE_PROFILE unsigned int vao_id{ 0 }; -#endif // ENABLE_GL_CORE_PROFILE unsigned int vbo_id{ 0 }; unsigned int ibo_id{ 0 }; size_t vertices_count{ 0 }; diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 12f286d..0b920c6 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -72,33 +72,26 @@ namespace GUI { const float top = -2.0f * (get_top() * cnv_inv_height - 0.5f); const float bottom = -2.0f * (get_bottom() * cnv_inv_height - 0.5f); -#if ENABLE_GL_CORE_PROFILE - const bool core_profile = OpenGLManager::get_gl_info().is_core_profile(); - if (!core_profile) -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (!OpenGLManager::get_gl_info().is_core_profile()) glsafe(::glLineWidth(1.5f)); +#endif // !SLIC3R_OPENGL_ES glsafe(::glDisable(GL_DEPTH_TEST)); -#if !ENABLE_OPENGL_ES -#if ENABLE_GL_CORE_PROFILE - if (!core_profile) { -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glPushAttrib(GL_ENABLE_BIT)); - glsafe(::glLineStipple(4, 0xAAAA)); - glsafe(::glEnable(GL_LINE_STIPPLE)); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (!OpenGLManager::get_gl_info().is_core_profile()) { + glsafe(::glPushAttrib(GL_ENABLE_BIT)); + glsafe(::glLineStipple(4, 0xAAAA)); + glsafe(::glEnable(GL_LINE_STIPPLE)); } -#endif // ENABLE_GL_CORE_PROFILE -#endif // !ENABLE_OPENGL_ES +#endif // !SLIC3R_OPENGL_ES -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); -#elif ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = core_profile ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_OPENGL_ES + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); @@ -108,79 +101,86 @@ namespace GUI { m_rectangle.reset(); GLModel::Geometry init_data; -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P4 }; - init_data.reserve_vertices(5); - init_data.reserve_indices(8); -#else - init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(4); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P4 }; + init_data.reserve_vertices(5); + init_data.reserve_indices(8); +#if !SLIC3R_OPENGL_ES + } + else { + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(4); + } +#endif // !SLIC3R_OPENGL_ES // vertices -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - const float width = right - left; - const float height = top - bottom; - float perimeter = 0.0f; +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const float width = right - left; + const float height = top - bottom; + float perimeter = 0.0f; - init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter)); - perimeter += width; - init_data.add_vertex(Vec4f(right, bottom, 0.0f, perimeter)); - perimeter += height; - init_data.add_vertex(Vec4f(right, top, 0.0f, perimeter)); - perimeter += width; - init_data.add_vertex(Vec4f(left, top, 0.0f, perimeter)); - perimeter += height; - init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter)); + init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter)); + perimeter += width; + init_data.add_vertex(Vec4f(right, bottom, 0.0f, perimeter)); + perimeter += height; + init_data.add_vertex(Vec4f(right, top, 0.0f, perimeter)); + perimeter += width; + init_data.add_vertex(Vec4f(left, top, 0.0f, perimeter)); + perimeter += height; + init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter)); - // indices - init_data.add_line(0, 1); - init_data.add_line(1, 2); - init_data.add_line(2, 3); - init_data.add_line(3, 4); -#else - init_data.add_vertex(Vec2f(left, bottom)); - init_data.add_vertex(Vec2f(right, bottom)); - init_data.add_vertex(Vec2f(right, top)); - init_data.add_vertex(Vec2f(left, top)); + // indices + init_data.add_line(0, 1); + init_data.add_line(1, 2); + init_data.add_line(2, 3); + init_data.add_line(3, 4); +#if !SLIC3R_OPENGL_ES + } + else { + init_data.add_vertex(Vec2f(left, bottom)); + init_data.add_vertex(Vec2f(right, bottom)); + init_data.add_vertex(Vec2f(right, top)); + init_data.add_vertex(Vec2f(left, top)); - // indices - init_data.add_index(0); - init_data.add_index(1); - init_data.add_index(2); - init_data.add_index(3); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES + // indices + init_data.add_index(0); + init_data.add_index(1); + init_data.add_index(2); + init_data.add_index(3); + } +#endif // !SLIC3R_OPENGL_ES m_rectangle.init_from(std::move(init_data)); } shader->set_uniform("view_model_matrix", Transform3d::Identity()); shader->set_uniform("projection_matrix", Transform3d::Identity()); -#if ENABLE_OPENGL_ES - shader->set_uniform("dash_size", 0.01f); - shader->set_uniform("gap_size", 0.0075f); -#elif ENABLE_GL_CORE_PROFILE - if (core_profile) { - const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("dash_size", 0.01f); - shader->set_uniform("gap_size", 0.0075f); +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("dash_size", 0.01f); + shader->set_uniform("gap_size", 0.0075f); +#if !SLIC3R_OPENGL_ES } -#endif // ENABLE_OPENGL_ES +#endif // !SLIC3R_OPENGL_ES m_rectangle.set_color(ColorRGBA((m_state == EState::Select) ? 0.3f : 1.0f, (m_state == EState::Select) ? 1.0f : 0.3f, 0.3f, 1.0f)); m_rectangle.render(); shader->stop_using(); } -#if !ENABLE_OPENGL_ES -#if ENABLE_GL_CORE_PROFILE - if (!core_profile) -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glPopAttrib()); -#endif // !ENABLE_OPENGL_ES +#if !SLIC3R_OPENGL_ES + if (!OpenGLManager::get_gl_info().is_core_profile()) + glsafe(::glPopAttrib()); +#endif // !SLIC3R_OPENGL_ES } } // namespace GUI diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 5146f03..bc5ad6f 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -1,17 +1,16 @@ -#include "libslic3r/libslic3r.h" -#include "GLShader.hpp" +#include +#include +#include +#include +#include +#include +#include "GLShader.hpp" #include "3DScene.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/format.hpp" #include "libslic3r/Color.hpp" -#include -#include -#include - -#include - namespace Slic3r { GLShaderProgram::~GLShaderProgram() diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 935daaa..bc09a21 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -1,9 +1,14 @@ #ifndef slic3r_GLShader_hpp_ #define slic3r_GLShader_hpp_ +#include #include #include #include +#include +#include +#include +#include #include "libslic3r/Point.hpp" diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index e0ee02a..b90792d 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -3,9 +3,9 @@ #include "GLShadersManager.hpp" #include "3DScene.hpp" #include "GUI_App.hpp" -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES #include "OpenGLManager.hpp" -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES #include #include @@ -36,13 +36,13 @@ std::pair GLShadersManager::init() bool valid = true; -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES const std::string prefix = "ES/"; // used to render wireframed triangles valid &= append_shader("wireframe", { prefix + "wireframe.vs", prefix + "wireframe.fs" }); #else const std::string prefix = GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 1) ? "140/" : "110/"; -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES // imgui shader valid &= append_shader("imgui", { prefix + "imgui.vs", prefix + "imgui.fs" }); // basic shader, used to render all what was previously rendered using the immediate mode @@ -53,14 +53,14 @@ std::pair GLShadersManager::init() valid &= append_shader("flat_texture", { prefix + "flat_texture.vs", prefix + "flat_texture.fs" }); // used to render 3D scene background valid &= append_shader("background", { prefix + "background.vs", prefix + "background.fs" }); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES // used to render dashed lines valid &= append_shader("dashed_lines", { prefix + "dashed_lines.vs", prefix + "dashed_lines.fs" }); -#elif ENABLE_GL_CORE_PROFILE +#else if (GUI::OpenGLManager::get_gl_info().is_core_profile()) // used to render thick and/or dashed lines valid &= append_shader("dashed_thick_lines", { prefix + "dashed_thick_lines.vs", prefix + "dashed_thick_lines.fs", prefix + "dashed_thick_lines.gs" }); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES // used to render toolpaths center of gravity valid &= append_shader("toolpaths_cog", { prefix + "toolpaths_cog.vs", prefix + "toolpaths_cog.fs" }); // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 37be221..a980d57 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -19,7 +19,7 @@ #include #define STB_DXT_IMPLEMENTATION -#include "stb_dxt/stb_dxt.h" +#include #include #include @@ -344,12 +344,6 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES - glsafe(::glEnable(GL_TEXTURE_2D)); - glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)); -#endif // !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES - glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id)); GLModel::Geometry init_data; @@ -363,7 +357,7 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, init_data.add_vertex(Vec2f(right, top), Vec2f(uvs.right_top.u, uvs.right_top.v)); init_data.add_vertex(Vec2f(left, top), Vec2f(uvs.left_top.u, uvs.left_top.v)); - // indices + // indices init_data.add_triangle(0, 1, 2); init_data.add_triangle(2, 3, 0); @@ -380,10 +374,6 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, } glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - -#if !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES - glsafe(::glDisable(GL_TEXTURE_2D)); -#endif // !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES glsafe(::glDisable(GL_BLEND)); } diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 76075e1..6718f69 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -92,6 +92,7 @@ void GLToolbarItem::render(const GLCanvas3D& parent, unsigned int tex_id, float GLTexture::Quad_UVs ret; // tiles in the texture are spaced by 1 pixel const float icon_size_px = (float)(tex_width - 1) / ((float)Num_States + (float)Num_Rendered_Highlight_States); + const char render_state = (m_highlight_state == NotHighlighted ? m_state : Num_States + m_highlight_state); const float inv_tex_width = 1.0f / (float)tex_width; const float inv_tex_height = 1.0f / (float)tex_height; @@ -237,8 +238,7 @@ void GLToolbar::set_gap_size(float size) void GLToolbar::set_icons_size(float size) { - if (m_layout.icons_size != size) - { + if (m_layout.icons_size != size) { m_layout.icons_size = size; m_layout.dirty = true; m_icons_texture_dirty = true; @@ -249,6 +249,12 @@ void GLToolbar::set_scale(float scale) { if (m_layout.scale != scale) { m_layout.scale = scale; + + m_layout.icons_size = int(Default_Icons_Size * scale); + m_layout.border = int(5.f * scale); + m_layout.separator_size = int(5.f * scale); + m_layout.gap_size = int(6.f * scale); + m_layout.dirty = true; m_icons_texture_dirty = true; } @@ -552,12 +558,12 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { - return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; + return (2.0f * (m_layout.border + m_layout.gap_size)) + m_layout.icons_size; } float GLToolbar::get_height_horizontal() const { - return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; + return (2.0f * (m_layout.border + m_layout.gap_size)) + m_layout.icons_size; } float GLToolbar::get_height_vertical() const @@ -574,15 +580,12 @@ float GLToolbar::get_main_size() const continue; if (m_items[i]->is_separator()) - size += m_layout.separator_size; + size += m_layout.separator_size + m_layout.gap_size; else - size += (float)m_layout.icons_size; + size += (float)m_layout.icons_size + 2.f * m_layout.gap_size; } - if (m_items.size() > 1) - size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; - - return size * m_layout.scale; + return size; } int GLToolbar::get_visible_items_cnt() const @@ -668,16 +671,16 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D const Size cnv_size = parent.get_canvas_size(); const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - const float icons_size = m_layout.icons_size * m_layout.scale; - const float separator_size = m_layout.separator_size * m_layout.scale; - const float gap_size = m_layout.gap_size * m_layout.scale; - const float border = m_layout.border * m_layout.scale; + const float icons_size = m_layout.icons_size; + const float separator_size = m_layout.separator_size; + const float gap_size = m_layout.gap_size; + const float border = m_layout.border; const float separator_stride = separator_size + gap_size; - const float icon_stride = icons_size + gap_size; + const float icon_stride = icons_size + 2 * gap_size; float left = m_layout.left + border; - float top = m_layout.top - border; + float top = m_layout.top - border - gap_size; for (GLToolbarItem* item : m_items) { if (!item->is_visible()) @@ -767,16 +770,16 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& const Size cnv_size = parent.get_canvas_size(); const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - const float icons_size = m_layout.icons_size * m_layout.scale; - const float separator_size = m_layout.separator_size * m_layout.scale; - const float gap_size = m_layout.gap_size * m_layout.scale; - const float border = m_layout.border * m_layout.scale; + const float icons_size = m_layout.icons_size; + const float separator_size = m_layout.separator_size; + const float gap_size = m_layout.gap_size; + const float border = m_layout.border; const float separator_stride = separator_size + gap_size; const float icon_stride = icons_size + gap_size; - float left = m_layout.left + border; - float top = m_layout.top - border; + float left = m_layout.left + border + gap_size; + float top = m_layout.top - border - gap_size; for (GLToolbarItem* item : m_items) { if (!item->is_visible()) @@ -891,13 +894,13 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 const Size cnv_size = parent.get_canvas_size(); const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - const float icons_size = m_layout.icons_size * m_layout.scale; - const float separator_size = m_layout.separator_size * m_layout.scale; - const float gap_size = m_layout.gap_size * m_layout.scale; - const float border = m_layout.border * m_layout.scale; + const float icons_size = m_layout.icons_size; + const float separator_size = m_layout.separator_size; + const float gap_size = m_layout.gap_size; + const float border = m_layout.border; - float left = m_layout.left + border; - const float top = m_layout.top - border; + float left = m_layout.left + border + gap_size; + const float top = m_layout.top - border - gap_size; for (size_t id = 0; id < m_items.size(); ++id) { GLToolbarItem* item = m_items[id]; @@ -953,7 +956,7 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 return -2; } - left = right; + left = right + gap_size; } } @@ -965,10 +968,10 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& const Size cnv_size = parent.get_canvas_size(); const Vec2d scaled_mouse_pos((mouse_pos.x() - 0.5 * (double)cnv_size.get_width()), (0.5 * (double)cnv_size.get_height() - mouse_pos.y())); - const float icons_size = m_layout.icons_size * m_layout.scale; - const float separator_size = m_layout.separator_size * m_layout.scale; - const float gap_size = m_layout.gap_size * m_layout.scale; - const float border = m_layout.border * m_layout.scale; + const float icons_size = m_layout.icons_size; + const float separator_size = m_layout.separator_size; + const float gap_size = m_layout.gap_size; + const float border = m_layout.border; const float left = m_layout.left + border; float top = m_layout.top - border; @@ -1127,12 +1130,12 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte const float inv_cnv_w = 1.0f / cnv_w; const float inv_cnv_h = 1.0f / cnv_h; - const float icons_size_x = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_w; - const float icons_size_y = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_h; - const float separator_size = 2.0f * m_layout.separator_size * m_layout.scale * inv_cnv_w; - const float gap_size = 2.0f * m_layout.gap_size * m_layout.scale * inv_cnv_w; - const float border_x = 2.0f * m_layout.border * m_layout.scale * inv_cnv_w; - const float border_y = 2.0f * m_layout.border * m_layout.scale * inv_cnv_h; + const float icons_size_x = 2.0f * m_layout.icons_size * inv_cnv_w; + const float icons_size_y = 2.0f * m_layout.icons_size * inv_cnv_h; + const float separator_size = 2.0f * m_layout.separator_size * inv_cnv_w; + const float gap_size = 2.0f * m_layout.gap_size * inv_cnv_w; + const float border_x = 2.0f * m_layout.border * inv_cnv_w; + const float border_y = 2.0f * m_layout.border * inv_cnv_h; const float separator_stride = separator_size + gap_size; const float icon_stride = icons_size_x + gap_size; @@ -1165,8 +1168,8 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte const float arr_tex_width = (float)m_arrow_texture.get_width(); const float arr_tex_height = (float)m_arrow_texture.get_height(); if (tex_id != 0 && arr_tex_width > 0.0f && arr_tex_height > 0.0f) { - const float arrow_size_x = 2.0f * m_layout.scale * arr_tex_width * inv_cnv_w; - const float arrow_size_y = 2.0f * m_layout.scale * arr_tex_height * inv_cnv_h; + const float arrow_size_x = 2.0f * arr_tex_width * inv_cnv_w; + const float arrow_size_y = 2.0f * arr_tex_height * inv_cnv_h; const float left_uv = 0.0f; const float right_uv = 1.0f; @@ -1199,17 +1202,18 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const float inv_cnv_w = 1.0f / cnv_w; const float inv_cnv_h = 1.0f / cnv_h; - const float icons_size_x = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_w; - const float icons_size_y = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_h; - const float separator_size = 2.0f * m_layout.separator_size * m_layout.scale * inv_cnv_w; - const float gap_size = 2.0f * m_layout.gap_size * m_layout.scale * inv_cnv_w; - const float border_w = 2.0f * m_layout.border * m_layout.scale * inv_cnv_w; - const float border_h = 2.0f * m_layout.border * m_layout.scale * inv_cnv_h; + const float icons_size_x = 2.0f * m_layout.icons_size * inv_cnv_w; + const float icons_size_y = 2.0f * m_layout.icons_size * inv_cnv_h; + const float separator_size = 2.0f * m_layout.separator_size * inv_cnv_w; + const float gap_size_x = 2.0f * m_layout.gap_size * inv_cnv_w; + const float gap_size_y = 2.0f * m_layout.gap_size * inv_cnv_h; + const float border_w = 2.0f * m_layout.border * inv_cnv_w; + const float border_h = 2.0f * m_layout.border * inv_cnv_h; const float width = 2.0f * get_width() * inv_cnv_w; const float height = 2.0f * get_height() * inv_cnv_h; - const float separator_stride = separator_size + gap_size; - const float icon_stride = icons_size_x + gap_size; + const float separator_stride = separator_size + gap_size_x; + const float icon_stride = icons_size_x + 2 * gap_size_x; float left = 2.0f * m_layout.left * inv_cnv_w; float top = 2.0f * m_layout.top * inv_cnv_h; @@ -1218,18 +1222,30 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) render_background(left, top, right, bottom, border_w, border_h); - left += border_w; - top -= border_h; + const float margin_w = border_w + gap_size_x; + const float margin_h = border_h + gap_size_y; + left += margin_w; + top -= margin_h; + // renders icons + int id = 0; for (const GLToolbarItem* item : m_items) { + id++; if (!item->is_visible()) continue; if (item->is_separator()) left += separator_stride; else { - item->render(parent, tex_id, left, left + icons_size_x, top - icons_size_y, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); + if (item->is_pressed()) + render_background(left - (id == m_items.size() ? border_w : margin_w), + top + margin_h, + left + icons_size_x + (id == 1 ? border_w : margin_w), + top - icons_size_y - margin_h, + border_w, border_h); + + item->render(parent, tex_id, left, left + icons_size_x, top - icons_size_y, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size)); left += icon_stride; } } @@ -1254,17 +1270,18 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const float inv_cnv_w = 1.0f / cnv_w; const float inv_cnv_h = 1.0f / cnv_h; - const float icons_size_x = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_w; - const float icons_size_y = 2.0f * m_layout.icons_size * m_layout.scale * inv_cnv_h; - const float separator_size = 2.0f * m_layout.separator_size * m_layout.scale * inv_cnv_h; - const float gap_size = 2.0f * m_layout.gap_size * m_layout.scale * inv_cnv_h; - const float border_w = 2.0f * m_layout.border * m_layout.scale * inv_cnv_w; - const float border_h = 2.0f * m_layout.border * m_layout.scale * inv_cnv_h; + const float icons_size_x = 2.0f * m_layout.icons_size * inv_cnv_w; + const float icons_size_y = 2.0f * m_layout.icons_size * inv_cnv_h; + const float separator_size = 2.0f * m_layout.separator_size * inv_cnv_h; + const float gap_size_x = 2.0f * m_layout.gap_size * inv_cnv_w; + const float gap_size_y = 2.0f * m_layout.gap_size * inv_cnv_h; + const float border_w = 2.0f * m_layout.border * inv_cnv_w; + const float border_h = 2.0f * m_layout.border * inv_cnv_h; const float width = 2.0f * get_width() * inv_cnv_w; const float height = 2.0f * get_height() * inv_cnv_h; - const float separator_stride = separator_size + gap_size; - const float icon_stride = icons_size_y + gap_size; + const float separator_stride = separator_size + gap_size_y; + const float icon_stride = icons_size_y + 2 * gap_size_y; float left = 2.0f * m_layout.left * inv_cnv_w; float top = 2.0f * m_layout.top * inv_cnv_h; @@ -1273,8 +1290,8 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) render_background(left, top, right, bottom, border_w, border_h); - left += border_w; - top -= border_h; + left += border_w + gap_size_x; + top -= border_h + gap_size_y; // renders icons for (const GLToolbarItem* item : m_items) { @@ -1284,7 +1301,7 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) if (item->is_separator()) top -= separator_stride; else { - item->render(parent, tex_id, left, left + icons_size_x, top - icons_size_y, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); + item->render(parent, tex_id, left, left + icons_size_x, top - icons_size_y, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size)); top -= icon_stride; } } @@ -1322,7 +1339,7 @@ bool GLToolbar::generate_icons_texture() states.push_back({ 1, false }); // HighlightedHidden } - unsigned int sprite_size_px = (unsigned int)(m_layout.icons_size * m_layout.scale); + unsigned int sprite_size_px = (unsigned int)(m_layout.icons_size); // // force even size // if (sprite_size_px % 2 != 0) // sprite_size_px += 1; diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 6961827..9dfd6ef 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -290,6 +290,8 @@ public: void set_icons_size(float size); void set_scale(float scale); + float get_scale() const { return m_layout.scale; } + bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } @@ -329,6 +331,9 @@ public: bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); // get item pointer for highlighter timer GLToolbarItem* get_item(const std::string& item_name); + + bool generate_icons_texture(); + private: void calc_layout(); float get_width_horizontal() const; @@ -349,8 +354,6 @@ private: void render_horizontal(const GLCanvas3D& parent); void render_vertical(const GLCanvas3D& parent); - bool generate_icons_texture(); - // returns true if any item changed its state bool update_items_visibility(); // returns true if any item changed its state diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 0519061..3d361c9 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -3,7 +3,7 @@ #include "format.hpp" #include "I18N.hpp" -#include "libslic3r/LocalesUtils.hpp" +#include #include @@ -101,132 +101,6 @@ const std::string& shortkey_alt_prefix() return str; } -// opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) -void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) -{ - 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); - return; - } - - const ConfigOptionDef *opt_def = config.def()->get(opt_key); - switch (opt_def->type) { - case coFloatOrPercent:{ - 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) - config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent)); - break;} - case coPercent: - config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast(value))); - break; - case coFloat:{ - double& val = config.opt_float(opt_key); - val = boost::any_cast(value); - 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) - ConfigOptionFloatsOrPercents* vec_new = new ConfigOptionFloatsOrPercents({ {val, percent} }); - config.option(opt_key)->set_at(vec_new, opt_index, opt_index); - break; - } - case coPercents:{ - ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, 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); - break; - } - case coString: - config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast(value))); - break; - case coStrings:{ - if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "gcode_substitutions") { - config.option(opt_key)->values = - boost::any_cast>(value); - } - else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0) { - std::string str = boost::any_cast(value); - std::vector values {}; - if (!str.empty()) { - if (str.back() == ';') str.pop_back(); - // Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values. - // Currently used for the post_process config value only. - boost::split(values, str, boost::is_any_of(";")); - if (values.size() == 1 && values[0] == "") - values.resize(0); - } - 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); - } - } - break; - case coBool: - 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); - break;} - case coInt: { - //config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast(value))); - int& val_new = config.opt_int(opt_key); - val_new = 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); - } - break; - case coEnum:{ - auto *opt = opt_def->default_value.get()->clone(); - opt->setInt(boost::any_cast(value)); - config.set_key_value(opt_key, opt); - } - break; - case coPoints:{ - //Y20 //B52 - if (opt_key == "bed_shape" || opt_key == "bed_exclude_area") { - 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); - } - break; - case coNone: - break; - default: - break; - } - } - catch (const std::exception &e) - { - wxLogError(format_wxstr("Internal error when changing value for %1%: %2%", opt_key, e.what())); - } -} - void show_error(wxWindow* parent, const wxString& message, bool monospaced_font) { ErrorDialog msg(parent, message, monospaced_font); @@ -364,77 +238,6 @@ void show_substitutions_info(const ConfigSubstitutions& config_substitutions, co msg.ShowModal(); } -void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items) -{ - if (comboCtrl == nullptr) - return; - wxGetApp().UpdateDarkUI(comboCtrl); - - wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup; - if (popup != nullptr) { - // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. - // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. - comboCtrl->UseAltPopupWindow(); - - int max_width = 0; - - // the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3 -// comboCtrl->EnablePopupAnimation(false); -#ifdef _WIN32 - popup->SetFont(comboCtrl->GetFont()); -#endif // _WIN32 - comboCtrl->SetPopupControl(popup); - wxString title = from_u8(text); - max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x); - popup->SetStringValue(title); - popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); - popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); }); - popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); - popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); - - std::vector items_str; - boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off); - - // each item must be composed by 2 parts - assert(items_str.size() %2 == 0); - - for (size_t i = 0; i < items_str.size(); i += 2) { - wxString label = from_u8(items_str[i]); - max_width = std::max(max_width, 60 + popup->GetTextExtent(label).x); - popup->Append(label); - popup->Check(i / 2, items_str[i + 1] == "1"); - } - - comboCtrl->SetMinClientSize(wxSize(max_width, -1)); - wxGetApp().UpdateDarkUI(popup); - } -} - -unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl) -{ - unsigned int flags = 0; - - wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); - if (popup != nullptr) { - for (unsigned int i = 0; i < popup->GetCount(); ++i) { - if (popup->IsChecked(i)) - flags |= 1 << i; - } - } - - return flags; -} - -void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags) -{ - wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); - if (popup != nullptr) { - for (unsigned int i = 0; i < popup->GetCount(); ++i) { - popup->Check(i, (flags & (1 << i)) != 0); - } - } -} - AppConfig* get_app_config() { return wxGetApp().app_config; @@ -593,12 +396,34 @@ bool create_process(const boost::filesystem::path& path, const std::wstring& cmd return true; } else - error_msg = "CreateProcessW failed to create process " + boost::nowide::narrow(path.wstring()); + error_msg = "CreateProcessW failed to create process " + into_u8(path.wstring()); } else - error_msg = "Executable doesn't exists. Path: " + boost::nowide::narrow(path.wstring()); + error_msg = "Executable doesn't exists. Path: " + into_u8(path.wstring()); return false; } #endif //_WIN32 + +bool has_illegal_characters(const wxString& wxs_name) +{ + const std::string name = into_u8(wxs_name); + return has_illegal_characters(name); +} + +bool has_illegal_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; + + return false; +} + +void show_illegal_characters_warning(wxWindow* parent) +{ + show_error(parent, format_wxstr("%1%\n%2% %3%", _L("The provided name is not valid;"), + _L("the following characters are not allowed:"), illegal_characters)); +} + } } // namespaces GUI / Slic3r diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index 70bc269..9cf054a 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -24,6 +24,8 @@ class Print; namespace GUI { +static constexpr char illegal_characters[] = "<>:/\\|?*\""; + void disable_screensaver(); void enable_screensaver(); bool debugged(); @@ -37,9 +39,6 @@ extern AppConfig* get_app_config(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); -// Change option value in config -void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); - // If monospaced_font is true, the error message is displayed using html
tags, // so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser. void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false); @@ -52,19 +51,6 @@ void warning_catcher(wxWindow* parent, const wxString& message); void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions); void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename); -// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items. -// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true). -// For example "Item1|0|Item2|1|Item3|0", and so on. -void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items); - -// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl, -// encoded inside an unsigned int. -unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl); - -// Sets the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl, -// with the flags encoded in the given unsigned int. -void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags); - // wxString conversions: // wxString from std::string in UTF8 @@ -99,6 +85,10 @@ void desktop_execute_get_result(wxString command, wxArrayString& output); bool create_process(const boost::filesystem::path& path, const std::wstring& cmd_opt, std::string& error_msg); #endif //_WIN32 +bool has_illegal_characters(const wxString& name); +bool has_illegal_characters(const std::string& name); +void show_illegal_characters_warning(wxWindow* parent); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d02ce14..ad042ca 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -4,6 +4,8 @@ #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_Factories.hpp" +#include "TopBar.hpp" +#include "UpdatesUIManager.hpp" #include "format.hpp" // Localization headers: include libslic3r version first so everything in this file @@ -55,6 +57,7 @@ #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Color.hpp" #include "libslic3r/Format/SLAArchiveFormatRegistry.hpp" +#include "libslic3r/Utils/DirectoriesUtils.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" @@ -72,7 +75,7 @@ #include "slic3r/Config/Snapshot.hpp" #include "ConfigSnapshotDialog.hpp" #include "FirmwareDialog.hpp" -#include "Preferences.hpp" +#include "slic3r/GUI/Preferences.hpp" #include "Tab.hpp" #include "SysInfoDialog.hpp" #include "KBShortcutsDialog.hpp" @@ -89,9 +92,15 @@ #include "Downloader.hpp" #include "PhysicalPrinterDialog.hpp" #include "WifiConfigDialog.hpp" +#include "UserAccount.hpp" +#include "UserAccountUtils.hpp" +#include "WebViewDialog.hpp" +#include "LoginDialog.hpp" +#include "PresetArchiveDatabase.hpp" #include "BitmapCache.hpp" -#include "Notebook.hpp" +//#include "Notebook.hpp" +#include "TopBar.hpp" #ifdef __WXMSW__ #include @@ -409,20 +418,14 @@ bool static check_old_linux_datadir(const wxString& app_name) { // To be precise, the datadir should exist, it is created when single instance // lock happens. Instead of checking for existence, check the contents. - namespace fs = boost::filesystem; - std::string new_path = Slic3r::data_dir(); - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - std::string default_path = (dir + "/" + app_name).ToUTF8().data(); - - if (new_path != default_path) { - // This happens when the user specifies a custom --datadir. - // Do not show anything in that case. + // If the config folder is redefined - do not check + // This happens when the user specifies a custom --datadir. + if (new_path != get_default_datadir()) return true; - } + + namespace fs = boost::filesystem; fs::path data_dir = fs::path(new_path); if (! fs::is_directory(data_dir)) @@ -635,7 +638,7 @@ static void register_win32_device_notification_event() // if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { // printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, into_u8(lpdbi->dbcc_name))); } break; case DBT_DEVICEREMOVECOMPLETE: @@ -646,7 +649,7 @@ static void register_win32_device_notification_event() // if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) // printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, into_u8(lpdbi->dbcc_name))); } break; default: @@ -709,7 +712,7 @@ static void register_win32_device_notification_event() copy_data_structure = (COPYDATASTRUCT*)lParam; if (copy_data_structure->dwData == 1) { LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; - Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); + Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(into_u8(arguments)); } return true; }); @@ -829,16 +832,17 @@ void GUI_App::post_init() return; #endif CallAfter([this] { - // preset_updater->sync downloads profile updates on background so it must begin after config wizard finished. + // preset_updater->sync downloads profile updates and than via event checks updates and incompatible presets. We need to run it on startup. + // start before cw so it is canceled by cw if needed? + this->preset_updater->sync(preset_bundle, this, std::move(plater()->get_preset_archive_database()->get_selected_archive_repositories())); bool cw_showed = this->config_wizard_startup(); - this->preset_updater->sync(preset_bundle, this); //B57 //if (! cw_showed) { // // The CallAfter is needed as well, without it, GL extensions did not show. // // Also, we only want to show this when the wizard does not, so the new user // // sees something else than "we want something" on the first start. // show_send_system_info_dialog_if_needed(); - //} + //} // app version check is asynchronous and triggers blocking dialog window, better call it last this->app_version_check(false); }); @@ -886,12 +890,12 @@ std::string GUI_App::get_gl_info(bool for_github) wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) { -#if ENABLE_GL_CORE_PROFILE +#if SLIC3R_OPENGL_ES + return m_opengl_mgr.init_glcontext(canvas); +#else return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0), init_params != nullptr ? init_params->opengl_compatibiity_profile : false, init_params != nullptr ? init_params->opengl_debug : false); -#else - return m_opengl_mgr.init_glcontext(canvas); -#endif // ENABLE_GL_CORE_PROFILE +#endif // SLIC3R_OPENGL_ES } bool GUI_App::init_opengl() @@ -902,9 +906,9 @@ bool GUI_App::init_opengl() } // gets path to QIDISlicer.ini, returns semver from first line comment -static boost::optional parse_semver_from_ini(std::string path) +static boost::optional parse_semver_from_ini(const std::string& path) { - std::ifstream stream(path); + boost::nowide::ifstream stream(path); std::stringstream buffer; buffer << stream.rdbuf(); std::string body = buffer.str(); @@ -922,7 +926,7 @@ void GUI_App::init_app_config() { // Profiles for the alpha are stored into the QIDISlicer-alpha directory to not mix with the current release. - SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_FULL_NAME); // SetAppName(SLIC3R_APP_KEY "-alpha"); //B7 // SetAppName(SLIC3R_APP_KEY "-beta"); @@ -935,21 +939,6 @@ void GUI_App::init_app_config() // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" // Mac : "~/Library/Application Support/Slic3r" - if (data_dir().empty()) { - #ifndef __linux__ - set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); - #else - // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. - // https://github.com/qidi3d/QIDISlicer/issues/2911 - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); - #endif - } else { - m_datadir_redefined = true; - } - if (!app_config) app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); @@ -973,6 +962,113 @@ void GUI_App::init_app_config() } } +namespace { +// Copy ini file from resources to vendors if such file does not exists yet. +void copy_vendor_ini(const std::vector& vendors) +{ + for (const std::string &vendor : vendors) { + boost::system::error_code ec; + const boost::filesystem::path ini_in_resources = boost::filesystem::path( Slic3r::resources_dir() ) / "profiles" / (vendor + ".ini"); + assert(boost::filesystem::exists(ini_in_resources)); + const boost::filesystem::path ini_in_vendors = boost::filesystem::path(Slic3r::data_dir()) / "vendor" / (vendor + ".ini"); + if (boost::filesystem::exists(ini_in_vendors, ec)) { + continue; + } + std::string message; + CopyFileResult cfr = copy_file(ini_in_resources.string(), ini_in_vendors.string(), message, false); + if (cfr != SUCCESS) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy file " << ini_in_resources << " to " << ini_in_vendors << ": " << message; + } + } +} +} + +void GUI_App::legacy_app_config_vendor_check() +{ + // Expected state: + // User runs 2.8.0+ for the first time. They have QIDI SLA printers installed. + // QIDI SLA printers moved from QIDIResearch.ini to QIDIResearchSLA.ini + // We expect this is detected and fixed on the first run, when QIDIResearchSLA is not installed yet. + // Steps: + // Move the printers in appconfig to QIDIResearchSLA + // Moving the printers is not enough. The new ini QIDIResearchSLA needs to be installed. + // But we cannot install bundles without preset updater. + // So we just move it to the vendor folder. Since all profiles are named the same, it should not be a problem. + // Preset updater should be doing blocking update over QIDIResearch.ini. Then all should be ok. + + std::map> moved_models; + moved_models["QIDIResearch"] = {"SL1", "SL1S"}; + moved_models["Anycubic"] = {"PHOTON MONO", "PHOTON MONO SE", "PHOTON MONO X", "PHOTON MONO X 6K"}; + std::map vendors_from_to; + vendors_from_to["QIDIResearch"] = "QIDIResearchSLA"; + vendors_from_to["Anycubic"] = "AnycubicSLA"; + // resulting + std::vector vendors_to_create; + + const std::map>>& vendor_map = app_config->vendors(); + for (const auto& moved_models_of_vendor : moved_models) { + if (const auto &vendor_it = vendor_map.find(moved_models_of_vendor.first); vendor_it != vendor_map.end()) { + for (const std::string &model : moved_models_of_vendor.second) { + if (const auto &it = vendor_it->second.find(model); it != vendor_it->second.end()) { + vendors_to_create.emplace_back(vendors_from_to[moved_models_of_vendor.first]); + break; + } + } + } + } + + if (vendors_to_create.empty()) { + // If there are no printers to move, also do check if "new" vendors really has ini file in vendor folder. + // In case of running older and current slicer back and forth, there might be vendors in appconfig without ini. + std::vector vendors_to_check; + for (const auto &vendor_pair: vendors_from_to) { + if (vendor_map.find(vendor_pair.second) != vendor_map.end()) { + vendors_to_check.emplace_back(vendor_pair.second); + } + } + copy_vendor_ini(vendors_to_check); + return; + } + + BOOST_LOG_TRIVIAL(warning) << "QIDISlicer has found legacy SLA printers. The printers will be " + "moved to new vendor and its ini file will be installed. Configuration snapshot will be taken."; + + // Take snapshot now, since creation of new vendors in appconfig, snapshots wont be compatible in older slicers. + // If any of the new vendors already is in appconfig, there is no reason to do a snapshot, it will fail or wont be compatible in previous version. + bool do_snapshot = true; + for (const std::string &vendor : vendors_to_create) { + if (vendor_map.find(vendor) != vendor_map.end()) { + do_snapshot = false; + break; + } + } + if (do_snapshot) { + GUI::Config::take_config_snapshot_report_error(*app_config, Config::Snapshot::SNAPSHOT_UPGRADE, ""); + } + + // make a deep copy of vendor map with moved printers + std::map>> new_vendor_map; + for (const auto& vendor : vendor_map) { + for (const auto& model : vendor.second) { + if (vendors_from_to.find(vendor.first) != vendors_from_to.end() && std::find(moved_models[vendor.first].begin(), moved_models[vendor.first].end(), model.first) != moved_models[vendor.first].end()) { + // variants of models to be moved are placed under new vendor + for (const std::string& variant : model.second) { + new_vendor_map[vendors_from_to[vendor.first]][model.first].emplace(variant); + } + } else { + // rest is just copied + for (const std::string& variant : model.second) { + new_vendor_map[vendor.first][model.first].emplace(variant); + } + } + } + } + app_config->set_vendors(new_vendor_map); + + // copy new vendors ini file to vendors + copy_vendor_ini(vendors_to_create); +} + // returns old config path to copy from if such exists, // returns an empty string if such config path does not exists or if it cannot be loaded. std::string GUI_App::check_older_app_config(Semver current_version, bool backup) @@ -980,7 +1076,7 @@ std::string GUI_App::check_older_app_config(Semver current_version, bool backup) std::string older_data_dir_path; // If the config folder is redefined - do not check - if (m_datadir_redefined) + if (data_dir() != get_default_datadir()) return {}; // find other version app config (alpha / beta / release) @@ -1089,6 +1185,70 @@ bool GUI_App::OnInit() } } +void GUI_App::check_and_update_searcher(ConfigOptionMode mode /*= comExpert*/) +{ + std::vector search_inputs{}; + + auto print_tech = preset_bundle->printers.get_selected_preset().printer_technology(); + for (auto tab : tabs_list) + if (tab->supports_printer_technology(print_tech)) + search_inputs.emplace_back(Search::InputInfo{ tab->get_config(), tab->type() }); + + m_searcher->check_and_update(print_tech, mode, search_inputs); +} + +void GUI_App::jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category) +{ + get_tab(type)->activate_option(opt_key, category); +} + +void GUI_App::jump_to_option(size_t selected) +{ + const Search::Option& opt = m_searcher->get_option(selected); + if (opt.type == Preset::TYPE_PREFERENCES) + open_preferences(opt.opt_key(), into_u8(opt.group)); + else + get_tab(opt.type)->activate_option(opt.opt_key(), into_u8(opt.category)); +} + +void GUI_App::jump_to_option(const std::string& composite_key) +{ + const auto separator_pos = composite_key.find(";"); + const std::string opt_key = composite_key.substr(0, separator_pos); + const std::string tab_name = composite_key.substr(separator_pos + 1, composite_key.length()); + + for (Tab* tab : tabs_list) { + if (tab->name() == tab_name) { + check_and_update_searcher(); + + // Regularly searcher is sorted in respect to the options labels, + // so resort searcher before get an option + m_searcher->sort_options_by_key(); + const Search::Option& opt = m_searcher->get_option(opt_key, tab->type()); + tab->activate_option(opt_key, into_u8(opt.category)); + + // Revert sort of searcher back + m_searcher->sort_options_by_label(); + break; + } + } +} + +void GUI_App::update_search_lines() +{ + mainframe->update_search_lines(m_searcher->search_string()); +} + +void GUI_App::show_search_dialog() +{ + // To avoid endless loop caused by mutual lose focuses from serch_input and search_dialog + // invoke killFocus for serch_input by set focus to tab_panel + m_searcher->set_focus_to_parent(); + + check_and_update_searcher(get_mode()); + m_searcher->show_dialog(); +} + static int get_app_font_pt_size(const AppConfig* app_config) { if (!app_config->has("font_pt_size")) @@ -1107,6 +1267,10 @@ bool GUI_App::on_init_inner() // Set initialization of image handlers before any UI actions - See GH issue #7469 wxInitAllImageHandlers(); + // Set our own gui log as an active target + m_log_gui = new LogGui(); + wxLog::SetActiveTarget(m_log_gui); + #if defined(_WIN32) && ! defined(_WIN64) // Win32 32bit build. if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { @@ -1131,8 +1295,7 @@ bool GUI_App::on_init_inner() // Verify resources path const wxString resources_dir = from_u8(Slic3r::resources_dir()); - wxCHECK_MSG(wxDirExists(resources_dir), false, - wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); + wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); #ifdef __linux__ if (! check_old_linux_datadir(GetAppName())) { @@ -1303,6 +1466,13 @@ bool GUI_App::on_init_inner() this->check_updates(false); }); + Bind(wxEVT_ACTIVATE_APP, [this](const wxActivateEvent &evt) { + if (plater_) { + if (auto user_account = plater_->get_user_account()) + user_account->on_activate_app(evt.GetActive()); + } + }); + } else { #ifdef __WXMSW__ @@ -1347,8 +1517,11 @@ bool GUI_App::on_init_inner() if (is_editor()) mainframe->select_tab(size_t(0)); + // Call this check only after appconfig was loaded to mainframe, otherwise there will be duplicity error. + legacy_app_config_vendor_check(); + sidebar().obj_list()->init_objects(); // propagate model objects to object list -// update_mode(); // !!! do that later + update_mode(); // mode sizer doesn't exist anymore, so we came update mode here, before load_current_presets SetTopWindow(mainframe); plater_->init_notification_manager(); @@ -1378,7 +1551,10 @@ bool GUI_App::on_init_inner() obj_list()->set_min_height(); - update_mode(); // update view mode after fix of the object_list size + if (is_editor()) + update_mode(); // update view mode after fix of the object_list size + + // show_printer_webview_tab(); #ifdef __APPLE__ other_instance_message_handler()->bring_instance_forward(); @@ -1496,7 +1672,7 @@ void GUI_App::init_ui_colours() m_mode_palette = get_mode_default_palette(); bool is_dark_mode = dark_mode(); -#ifdef _WIN32 +// #ifdef _WIN32 //B10 m_color_label_default = is_dark_mode ? wxColour(255, 255, 255): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); @@ -1505,9 +1681,9 @@ void GUI_App::init_ui_colours() m_color_hovered_btn_label = is_dark_mode ? wxColour(68, 121, 251) : wxColour(68, 121, 251); m_color_default_btn_label = is_dark_mode ? wxColour(68, 121, 251): wxColour(68, 121, 251); m_color_selected_btn_bg = is_dark_mode ? wxColour(68, 68, 68) : wxColour(206, 209, 217); -#else - m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -#endif +// #else +// m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); +// #endif m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } @@ -1543,14 +1719,15 @@ void GUI_App::update_label_colours() tab->update_label_colours(); } -#ifdef _WIN32 +#if 0//def _WIN32 static bool is_focused(HWND hWnd) { HWND hFocusedWnd = ::GetFocus(); return hFocusedWnd && hWnd == hFocusedWnd; } +#endif -static bool is_default(wxWindow* win) +[[maybe_unused]] static bool is_default(wxWindow* win) { wxTopLevelWindow* tlw = find_toplevel_parent(win); if (!tlw) @@ -1558,7 +1735,6 @@ static bool is_default(wxWindow* win) return win == tlw->GetDefaultItem(); } -#endif void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) { @@ -1571,6 +1747,7 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju highlited = true; } // button marking + if (!dynamic_cast(window->GetParent())) // don't marking the button if it is from TopBar { auto mark_button = [this, btn, highlited](const bool mark) { if (btn->GetLabel().IsEmpty()) @@ -1583,12 +1760,12 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju // hovering btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); + btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(btn->HasFocus()); event.Skip(); }); // focusing btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); - is_focused_button = is_focused(btn->GetHWND()); + is_focused_button = btn->HasFocus();// is_focused(btn->GetHWND()); is_default_button = is_default(btn); if (is_focused_button || is_default_button) mark_button(is_focused_button); @@ -1805,7 +1982,7 @@ bool GUI_App::suppress_round_corners() const wxSize GUI_App::get_min_size(wxWindow* display_win) const { - wxSize min_size(76*m_em_unit, 49 * m_em_unit); + wxSize min_size(76 * m_em_unit, 49 * m_em_unit); const wxDisplay display = wxDisplay(display_win); wxRect display_rect = display.GetGeometry(); @@ -1820,7 +1997,7 @@ wxSize GUI_App::get_min_size(wxWindow* display_win) const return min_size; } -float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const +float GUI_App::toolbar_icon_scale(bool& is_custom) const { #ifdef __APPLE__ const float icon_sc = 1.0f; // for Retina display will be used its own scale @@ -1835,27 +2012,18 @@ float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const if (val.empty() || auto_val.empty() || use_val.empty()) return icon_sc; + is_custom = app_config->get_bool("use_custom_toolbar_size"); + int int_val = use_val == "0" ? 100 : atoi(val.c_str()); // correct value in respect to auto_toolbar_size int_val = std::min(atoi(auto_val.c_str()), int_val); - if (is_limited && int_val < 50) - int_val = 50; - - return 0.01f * int_val * icon_sc; + return 0.01f * int_val; } void GUI_App::set_auto_toolbar_icon_scale(float scale) const { -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit * 0.1f; -#endif // __APPLE__ - - long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); - std::string val = std::to_string(int_val); - + std::string val = std::to_string(int(std::lround(scale * 100))); app_config->set("auto_toolbar_size", val); } @@ -1969,7 +2137,15 @@ void GUI_App::shutdown() void GUI_App::SetOnlineLogin(bool status) { - mainframe->m_printer_view->SetLoginStatus(status); + if (!status) + { + wxGetApp().app_config->set("user_head_name", ""); + wxGetApp().app_config->set("user_head_url", ""); + wxGetApp().app_config->set("user_name", ""); + wxGetApp().app_config->set("user_token", ""); + } + mainframe->m_printer_view->SetLoginStatus(status); + mainframe->refresh_account_menu(status); } void GUI_App::SetPresentChange(bool status) @@ -2487,12 +2663,13 @@ bool GUI_App::save_mode(const /*ConfigOptionMode*/int mode) // Update view mode according to selected menu void GUI_App::update_mode() { + if (is_gcode_viewer()) + return; + sidebar().update_mode(); -#ifdef _WIN32 //_MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); -#endif + mainframe->m_tmp_top_bar->UpdateMode(); + mainframe->m_tabpanel->UpdateMode(); for (auto tab : tabs_list) tab->update_mode(); @@ -2501,7 +2678,7 @@ void GUI_App::update_mode() plater()->canvas3D()->update_gizmos_on_off_state(); } -void GUI_App::add_config_menu(wxMenuBar *menu) +wxMenu* GUI_App::get_config_menu(MainFrame* main_frame) { auto local_menu = new wxMenu(); wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); @@ -2521,27 +2698,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu) #endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) local_menu->AppendSeparator(); } - local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + #ifdef __APPLE__ - "\tCtrl+,", + local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + "\tCtrl+,", _L("Application preferences")); #else - "\tCtrl+P", + append_menu_item(local_menu, config_id_base + ConfigMenuPreferences, _L("&Preferences") + "\tCtrl+P", _L("Application preferences"), + [](wxCommandEvent&) { wxGetApp().open_preferences(); }, "", nullptr, []() {return true; }, main_frame); #endif - _L("Application preferences")); - wxMenu* mode_menu = nullptr; - if (is_editor()) { - local_menu->AppendSeparator(); - mode_menu = new wxMenu(); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); -// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX("Advanced", "Mode"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); - local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); - } local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); //B5 @@ -2551,15 +2714,16 @@ void GUI_App::add_config_menu(wxMenuBar *menu) // TODO: for when we're able to flash dictionaries // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a QIDI printer")); // } + // local_menu->Append(config_id_base + ConfigMenuWifiConfigFile, _L("Wi-Fi Configuration File"), _L("Generate a file to be loaded by a QIDI printer to configure its Wi-Fi connection.")); local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { switch (event.GetId() - config_id_base) { case ConfigMenuWizard: run_wizard(ConfigWizard::RR_USER); break; - case ConfigMenuUpdateConf: - check_updates(true); - break; + case ConfigMenuUpdateConf: + check_updates(true); + break; case ConfigMenuUpdateApp: app_version_check(true); break; @@ -2667,16 +2831,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } }); - using std::placeholders::_1; - - if (mode_menu != nullptr) { - auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); - } - - menu->Append(local_menu, _L("&Configuration")); + return local_menu; } void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) @@ -2686,12 +2841,8 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri if (mainframe->preferences_dialog->recreate_GUI()) recreate_GUI(_L("Restart application") + dots); -#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) -#else if (mainframe->preferences_dialog->seq_top_layer_only_changed()) -#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); + this->plater_->reload_print(); #ifdef _WIN32 if (is_editor()) { @@ -2714,7 +2865,7 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri if (mainframe->preferences_dialog->settings_layout_changed()) { // hide full main_sizer for mainFrame mainframe->GetSizer()->Show(false); - mainframe->update_layout(); + mainframe->update_layout(); mainframe->select_tab(size_t(0)); } } @@ -3034,7 +3185,23 @@ void GUI_App::MacOpenFiles(const wxArrayString &fileNames) void GUI_App::MacOpenURL(const wxString& url) { - start_download(into_u8(url)); + std::string narrow_url = into_u8(url); + if (boost::starts_with(narrow_url, "qidislicer://open?file=")) { + // This app config field applies only to downloading file + // (we need to handle login URL even if this flag is set off) + if (app_config && !app_config->get_bool("downloader_url_registered")) + { + notification_manager()->push_notification(NotificationType::URLNotRegistered); + BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; + return; + } + + start_download(std::move(narrow_url)); + } else if (boost::starts_with(narrow_url, "qidislicer://login")) { + plater()->get_user_account()->on_login_code_recieved(std::move(narrow_url)); + } else { + BOOST_LOG_TRIVIAL(error) << "MacOpenURL recieved improper URL: " << url; + } } #endif /* __APPLE */ @@ -3144,7 +3311,7 @@ wxString GUI_App::current_language_code_safe() const void GUI_App::open_web_page_localized(const std::string &http_address) { - open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); + open_browser_with_warning_dialog(from_u8(http_address + "&lng=") + this->current_language_code_safe(), nullptr, false); } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). @@ -3165,35 +3332,84 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage { wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - if (reason == ConfigWizard::RR_USER) { - // Cancel sync before starting wizard to prevent two downloads at same time + // Cancel sync before starting wizard to prevent two downloads at same time. + // preset_updater->cancel_sync(); + // Show login dialog before wizard. +#if 0 + bool user_was_logged = plater()->get_user_account()->is_logged(); + if (!user_was_logged) { + m_login_dialog = std::make_unique(mainframe, plater()->get_user_account()); + m_login_dialog->ShowModal(); + mainframe->RemoveChild(m_login_dialog.get()); + m_login_dialog->Destroy(); + // Destructor does not call Destroy. + m_login_dialog.reset(); + } +#endif // 0 + + // ConfigWizard can take some time to start. Because it is a wxWidgets window, it has to be done + // in UI thread, so displaying a nice modal dialog and letting the CW start in a worker thread + // is not an option. Let's at least show a modeless dialog before the UI thread freezes. + // TRN: Text showing while the ConfigWizard is loading, so the user knows something is happening. + auto cw_loading_dlg = new ConfigWizardLoadingDialog(mainframe, _L("Loading Configuration Wizard...")); + cw_loading_dlg->CenterOnParent(); + cw_loading_dlg->Show(); + wxYield(); + + // We have to update repos + // plater()->get_preset_archive_database()->sync_blocking(); + + if (reason == ConfigWizard::RunReason::RR_USER) { + // Since there might be new repos, we need to sync preset updater preset_updater->cancel_sync(); + const SharedArchiveRepositoryVector &repos = plater()->get_preset_archive_database()->get_selected_archive_repositories(); + // preset_updater->sync_blocking(preset_bundle, this, repos); preset_updater->update_index_db(); - if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) - return false; + // Offer update installation. + preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::SHOW_TEXT_BOX, repos); } - auto wizard = new ConfigWizard(mainframe); - const bool res = wizard->run(reason, start_page); + m_config_wizard = new ConfigWizard(mainframe); + cw_loading_dlg->Close(); + + const bool res = m_config_wizard->run(reason, start_page); + // !!! Deallocate memory after close ConfigWizard. // Note, that mainframe is a parent of ConfigWizard. // So, wizard will be destroyed only during destroying of mainframe // To avoid this state the wizard have to be disconnected from mainframe and Destroyed explicitly - mainframe->RemoveChild(wizard); - wizard->Destroy(); + mainframe->RemoveChild(m_config_wizard); + m_config_wizard->Destroy(); + m_config_wizard = nullptr; if (res) { load_current_presets(); + for (Tab* tab : tabs_list) { + if (tab->type() == Preset::TYPE_PRINTER) { + if (!tab->IsShown()) + mainframe->select_tab(size_t(0)); + break; + } + } // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); } - + //y14 + wxGetApp().SetOnlineLogin(wxGetApp().app_config->get("user_token") != ""); return res; } +void GUI_App::update_wizard_login_page() +{ + if (!m_config_wizard) { + return; + } + m_config_wizard->update_login(); +} + void GUI_App::show_desktop_integration_dialog() { #ifdef __linux__ @@ -3215,9 +3431,9 @@ void GUI_App::show_downloader_registration_dialog() , true, wxYES_NO); if (msg.ShowModal() == wxID_YES) { auto downloader_worker = new DownloaderUtils::Worker(nullptr); - downloader_worker->perform_register(app_config->get("url_downloader_dest")); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - if (downloader_worker->get_perform_registration_linux()) + downloader_worker->perform_download_register(app_config->get("url_downloader_dest")); +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + if (DownloaderUtils::Worker::perform_registration_linux) DesktopIntegrationDialog::perform_downloader_desktop_integration(); #endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) } else { @@ -3246,14 +3462,10 @@ void GUI_App::gcode_thumbnails_debug() boost::nowide::ifstream in_file(in_filename.c_str()); std::vector rows; std::string row; - if (in_file.good()) - { - while (std::getline(in_file, gcode_line)) - { - if (in_file.good()) - { - if (boost::starts_with(gcode_line, BEGIN_MASK)) - { + if (in_file.good()) { + while (std::getline(in_file, gcode_line)) { + if (in_file.good()) { + if (boost::starts_with(gcode_line, BEGIN_MASK)) { reading_image = true; gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); std::string::size_type x_pos = gcode_line.find('x'); @@ -3263,12 +3475,10 @@ void GUI_App::gcode_thumbnails_debug() height = (unsigned int)::atoi(height_str.c_str()); row.clear(); } - else if (reading_image && boost::starts_with(gcode_line, END_MASK)) - { + else if (reading_image && boost::starts_with(gcode_line, END_MASK)) { std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); - if (out_file.good()) - { + if (out_file.good()) { std::string decoded; decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); @@ -3363,10 +3573,12 @@ void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) bool GUI_App::config_wizard_startup() { - if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { + if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) + { run_wizard(ConfigWizard::RR_DATA_EMPTY); return true; - } else if (get_app_config()->legacy_datadir()) { + } + else if (get_app_config()->legacy_datadir()) { // Looks like user has legacy pre-vendorbundle data directory, // explain what this is and run the wizard @@ -3385,12 +3597,30 @@ bool GUI_App::config_wizard_startup() return false; } -bool GUI_App::check_updates(const bool verbose) +bool GUI_App::check_updates(const bool invoked_by_user) { + if (invoked_by_user) { + // do preset_updater sync so if user runs slicer for a long time, check for updates actually delivers updates. + // for preset_updater sync we need to sync archive database first + plater()->get_preset_archive_database()->sync_blocking(); + // Now re-extract offline repos + std::string failed_paths; + if (!plater()->get_preset_archive_database()->extract_archives_with_check(failed_paths)) { + int cnt = std::count(failed_paths.begin(), failed_paths.end(), '\n') + 1; + // TRN: %1% contains paths from which loading failed. They are separated by \n, there is no \n at the end. + failed_paths = GUI::format(_L_PLURAL("It was not possible to extract data from %1%. The source will not be updated.", + "It was not possible to extract data for following local sources. They will not be updated.\n\n %1%", cnt), failed_paths); + show_error(nullptr, failed_paths); + } + // then its time for preset_updater sync + preset_updater->sync_blocking(preset_bundle, this, plater()->get_preset_archive_database()->get_selected_archive_repositories()); + // and then we check updates + } + PresetUpdater::UpdateResult updater_result; try { preset_updater->update_index_db(); - 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(), invoked_by_user ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION, plater()->get_preset_archive_database()->get_selected_archive_repositories()); if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { mainframe->Close(); // Applicaiton is closing. @@ -3399,7 +3629,7 @@ bool GUI_App::check_updates(const bool verbose) else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { m_app_conf_exists = true; } - else if (verbose && updater_result == PresetUpdater::R_NOOP) { + else if (invoked_by_user && updater_result == PresetUpdater::R_NOOP) { MsgNoUpdates dlg; dlg.ShowModal(); } @@ -3410,45 +3640,79 @@ bool GUI_App::check_updates(const bool verbose) // Applicaiton will continue. return true; } +namespace { +bool open_dialog_hyperlink_checkbox(wxWindow* parent, AppConfig* app_config) +{ + RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("QIDISlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + dialog.ShowCheckBox(_L("Remember my choice")); + auto answer = dialog.ShowModal(); + bool launch = answer == wxID_YES; + if (dialog.IsCheckBoxChecked()) { + wxString preferences_item = _L("Suppress to open hyperlink in browser"); + wxString msg = + _L("QIDISlicer will remember your choice.") + "\n\n" + + _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + + format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); + MessageDialog msg_dlg(parent, msg, _L("QIDISlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); + if (msg_dlg.ShowModal() == wxID_CANCEL) + return false; + app_config->set("suppress_hyperlinks", answer == wxID_NO ? "1" : "0"); + } + return launch; +} +bool open_dialog_hyperlink(wxWindow* parent) +{ + MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("QIDISlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + return dialog.ShowModal() == wxID_YES; +} +} bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) { + enum class SupressHyperLinksOption{ + SHLO_UNCHECKED, + SHLO_ALWAYS_SUPRESS, + SHLO_ALWAYS_ALLOW + }; + bool empty = app_config->get("suppress_hyperlinks").empty(); + bool checked = app_config->get_bool("suppress_hyperlinks"); + SupressHyperLinksOption opt_val = + (empty + ? SupressHyperLinksOption::SHLO_UNCHECKED + : (checked + ? SupressHyperLinksOption::SHLO_ALWAYS_SUPRESS + : SupressHyperLinksOption::SHLO_ALWAYS_ALLOW)); bool launch = true; - - // warning dialog containes a "Remember my choice" checkbox - std::string option_key = "suppress_hyperlinks"; - if (force_remember_choice || app_config->get(option_key).empty()) { - if (app_config->get(option_key).empty()) { - RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("QIDISlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - dialog.ShowCheckBox(_L("Remember my choice")); - auto answer = dialog.ShowModal(); - launch = answer == wxID_YES; - if (dialog.IsCheckBoxChecked()) { - wxString preferences_item = _L("Suppress to open hyperlink in browser"); - wxString msg = - _L("QIDISlicer will remember your choice.") + "\n\n" + - _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + - format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); - - MessageDialog msg_dlg(parent, msg, _L("QIDISlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - if (msg_dlg.ShowModal() == wxID_CANCEL) - return false; - app_config->set(option_key, answer == wxID_NO ? "1" : "0"); - } - } - if (launch) - launch = !app_config->get_bool(option_key); - } - // warning dialog doesn't containe a "Remember my choice" checkbox - // and will be shown only when "Suppress to open hyperlink in browser" is ON. - else if (app_config->get_bool(option_key)) { - MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("QIDISlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - launch = dialog.ShowModal() == wxID_YES; - } - + if (opt_val == SupressHyperLinksOption::SHLO_UNCHECKED) { + // no previous action from user + // open dialog with remember checkbox + launch = open_dialog_hyperlink_checkbox(parent, app_config); + } else if (opt_val == SupressHyperLinksOption::SHLO_ALWAYS_ALLOW) { + // user already set checkbox to always open + launch = true; + } else if (opt_val == SupressHyperLinksOption::SHLO_ALWAYS_SUPRESS && force_remember_choice) { + // user already set checkbox or preferences to always supress + launch = false; + } else if (opt_val == SupressHyperLinksOption::SHLO_ALWAYS_SUPRESS && !force_remember_choice) { + // user already set checkbox or preferences to always supress but it is overriden + // no checkbox in dialog + launch = open_dialog_hyperlink(parent); + } return launch && wxLaunchDefaultBrowser(url, flags); } +bool GUI_App::open_login_browser_with_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, int flags/* = 0*/) +{ + bool auth_login_dialog_confirmed = app_config->get_bool("auth_login_dialog_confirmed"); + if (!auth_login_dialog_confirmed) { + RichMessageDialog dialog(parent, _L("Open default browser with QIDI Account Log in page?\n(If you select 'Yes', you will not be asked again.)"), _L("QIDISlicer: Open Log in page"), wxICON_QUESTION | wxYES_NO); + if (dialog.ShowModal() != wxID_YES) + return false; + app_config->set("auth_login_dialog_confirmed", "1"); + } + return wxLaunchDefaultBrowser(url, flags); +} + // static method accepting a wxWindow object as first parameter // void warning_catcher{ // my($self, $message_dialog) = @_; @@ -3561,7 +3825,7 @@ void GUI_App::app_updater(bool from_user) assert(!app_data.target_path.empty()); // dialog with new version info - AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version, from_user); + AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version, from_user, app_data.action == AppUpdaterURLAction::AUUA_OPEN_IN_BROWSER); auto dialog_result = dialog.ShowModal(); // checkbox "do not show again" if (dialog.disable_version_check()) { @@ -3571,6 +3835,10 @@ void GUI_App::app_updater(bool from_user) if (dialog_result != wxID_OK) { return; } + if (app_data.action == AppUpdaterURLAction::AUUA_OPEN_IN_BROWSER) { + open_browser_with_warning_dialog(from_u8(app_data.url), nullptr, false); + return; + } // dialog with new version download (installer or app dependent on system) including path selection AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); dialog_result = dwnld_dlg.ShowModal(); @@ -3659,6 +3927,338 @@ void GUI_App::open_wifi_config_dialog(bool forced, const wxString& drive_path/* } m_wifi_config_dialog_shown = false; } +// Returns true if preset had to be installed. +bool GUI_App::select_printer_preset(const Preset* preset) +{ + assert(preset); + + bool is_installed{ false }; + + // When physical printer is selected, it somehow remains selected in printer tab + // TabPresetComboBox::update() looks at physical_printers and if some has selected = true, it overrides the selection. + // This might be, because OnSelect event callback is not triggered + if(preset_bundle->physical_printers.get_selected_printer_config()) { + preset_bundle->physical_printers.unselect_printer(); + } + + if (!preset->is_visible) { + size_t preset_id = preset_bundle->printers.get_preset_idx_by_name(preset->name); + assert(preset_id != size_t(-1)); + preset_bundle->printers.select_preset(preset_id); + is_installed = true; + } + + get_tab(Preset::Type::TYPE_PRINTER)->select_preset(preset->name); + return is_installed; +} + +namespace { +const Preset* find_preset_by_nozzle_and_options( + const PrinterPresetCollection& collection + , const std::string& model_id + , std::map>& options) +{ + // find all matching presets when repo prefix is ommited + std::vector results; + for (const Preset &preset : collection) { + // trim repo prefix + std::string printer_model = preset.config.opt_string("printer_model"); + std::string vendor_repo_prefix; + if (preset.vendor) { + vendor_repo_prefix = preset.vendor->repo_prefix; + } else if (std::string inherits = preset.inherits(); !inherits.empty()) { + const Preset *parent = wxGetApp().preset_bundle->printers.find_preset(inherits); + if (parent && parent->vendor) { + vendor_repo_prefix = parent->vendor->repo_prefix; + } + } + if (printer_model.find(vendor_repo_prefix) == 0) { + printer_model = printer_model.substr(vendor_repo_prefix.size() + ); + boost::trim_left(printer_model); + } + + if (!preset.is_system || printer_model != model_id) + continue; + // options (including nozzle_diameter) + bool failed = false; + for (const auto& opt : options) { + assert(preset.config.has(opt.first)); + // We compare only first value now, but options contains data for all (some might be empty tho) + std::string opt_val; + if (preset.config.option(opt.first)->is_scalar()) { + opt_val = preset.config.option(opt.first)->serialize(); + } else { + switch (preset.config.option(opt.first)->type()) { + case coInts: opt_val = std::to_string(static_cast(preset.config.option(opt.first))->values[0]); break; + case coFloats: + opt_val = into_u8(double_to_string(static_cast(preset.config.option(opt.first))->values[0])); + if (size_t pos = opt_val.find(",") != std::string::npos) + opt_val.replace(pos, 1, 1, '.'); + break; + case coStrings: opt_val = static_cast(preset.config.option(opt.first))->values[0]; break; + case coBools: opt_val = static_cast(preset.config.option(opt.first))->values[0] ? "1" : "0"; break; + default: + assert(true); + continue; + } + } + + if (opt_val != opt.second[0]) + { + failed = true; + break; + } + } + if (!failed) { + results.push_back(&preset); + } + } + // find visible without prefix + for (const Preset *preset : results) { + if (preset->is_visible && preset->config.opt_string("printer_model") == model_id) { + return preset; + } + } + // find one visible + for (const Preset *preset : results) { + if (preset->is_visible) { + return preset; + } + } + // find one without prefix + for (const Preset* preset : results) { + if (preset->config.opt_string("printer_model") == model_id) { + return preset; + } + } + if (results.size() != 0) { + return results.front(); + } + return nullptr; +} +} + +bool GUI_App::select_printer_from_connect(const std::string& msg) +{ + // parse message "binary_gcode" + boost::property_tree::ptree ptree; + std::string model_name = UserAccountUtils::get_keyword_from_json(ptree, msg, "printer_model"); + std::string uuid = UserAccountUtils::get_keyword_from_json(ptree, msg, "uuid"); + if (model_name.empty()) { + std::vector compatible_printers; + UserAccountUtils::fill_supported_printer_models_from_json(ptree, compatible_printers); + if (!compatible_printers.empty()) { + model_name = compatible_printers.front(); + } + } + if (model_name.empty()) { + BOOST_LOG_TRIVIAL(error) << "Failed to select printer from Connect. Printer_model is empty."; + return false; + } + std::map> config_options_to_match; + UserAccountUtils::fill_config_options_from_json(ptree, config_options_to_match); + // prevent not having nozzle diameter + if (config_options_to_match.find("nozzle_diameter") == config_options_to_match.end()) { + std::string diameter = UserAccountUtils::get_keyword_from_json(ptree, msg, "nozzle_diameter"); + if (!diameter.empty()) + config_options_to_match["nozzle_diameter"] = {diameter}; + } + // log + BOOST_LOG_TRIVIAL(info) << "Select printer from Connect. Model: " << model_name; + for(const auto& pair :config_options_to_match) { + std::string out; + for(const std::string& val :pair.second) { out += val + ",";} + BOOST_LOG_TRIVIAL(info) << pair.first << ": " << out; + } + // select printer + const Preset* printer_preset = find_preset_by_nozzle_and_options(preset_bundle->printers, model_name, config_options_to_match); + bool is_installed = printer_preset && select_printer_preset(printer_preset); + // notification + std::string out = printer_preset ? + (is_installed ? GUI::format(_L("Installed and selected printer:\n%1%"), printer_preset->name) : + GUI::format(_L("Selected printer:\n%1%"), printer_preset->name)) : + GUI::format(_L("Printer not found:\n%1%"), model_name); + this->plater()->get_notification_manager()->close_notification_of_type(NotificationType::SelectPrinterFromConnect); + this->plater()->get_notification_manager()->push_notification( + NotificationType::SelectPrinterFromConnect + , printer_preset ? NotificationManager::NotificationLevel::ImportantNotificationLevel : NotificationManager::NotificationLevel::WarningNotificationLevel + , out); + plater()->get_user_account()->set_current_printer_uuid_from_connect(uuid); + return printer_preset; +} + +bool GUI_App::select_filament_preset(const Preset* preset, size_t extruder_index) +{ + assert(preset && preset->is_compatible); + + if (!preset->is_visible) { + // To correct update of presets visibility call select_preset for preset_bundle->filaments() + size_t preset_id = preset_bundle->filaments.get_preset_idx_by_name(preset->name); + assert(preset_id != size_t(-1)); + preset_bundle->filaments.select_preset(preset_id); + } + assert(preset->is_visible); + return preset_bundle->extruders_filaments[extruder_index].select_filament(preset->name); +} +void GUI_App::search_and_select_filaments(const std::string& material, bool avoid_abrasive, size_t extruder_index, std::string& out_message) +{ + const Preset* preset = preset_bundle->extruders_filaments[extruder_index].get_selected_preset(); + // selected is ok + if (!preset->is_default && preset->config.has("filament_type") + && (!avoid_abrasive || preset->config.option("filament_abrasive")->values[0] == false) + && preset->config.option("filament_type")->serialize() == material) + { + return; + } + // find installed compatible filament that is QIDI with suitable type and select it + for (const auto& filament : preset_bundle->extruders_filaments[extruder_index]) { + if (filament.is_compatible + && !filament.preset->is_default + && filament.preset->is_visible + && (!filament.preset->vendor || !filament.preset->vendor->templates_profile) + && filament.preset->config.has("filament_type") + && (!avoid_abrasive || filament.preset->config.option("filament_abrasive")->values[0] == false) + && filament.preset->config.option("filament_type")->serialize() == material + && filament.preset->name.compare(0, 9, "QIDIment") == 0 + && select_filament_preset(filament.preset, extruder_index) + ) + { + out_message += /*(extruder_count == 1) + ? GUI::format(_L("Selected Filament:\n%1%"), filament_preset.preset->name) + : */GUI::format(_L("Extruder %1%: Selected filament %2%"), extruder_index + 1, filament.preset->name) + "\n"; + return; + } + } + // find first installed compatible filament with suitable type and select it + for (const auto& filament : preset_bundle->extruders_filaments[extruder_index]) { + if (filament.is_compatible + && !filament.preset->is_default + && filament.preset->is_visible + && (!filament.preset->vendor || !filament.preset->vendor->templates_profile) + && filament.preset->config.has("filament_type") + && (!avoid_abrasive || filament.preset->config.option("filament_abrasive")->values[0] == false) + && filament.preset->config.option("filament_type")->serialize() == material + && select_filament_preset(filament.preset, extruder_index) + ) + { + out_message += /*(extruder_count == 1) + ? GUI::format(_L("Selected Filament:\n%1%"), filament_preset.preset->name) + : */GUI::format(_L("Extruder %1%: Selected filament %2%"), extruder_index + 1, filament.preset->name) + "\n"; + return; + } + } + // find profile to install + // try finding QIDIment + for (const auto& filament : preset_bundle->extruders_filaments[extruder_index]) { + if (filament.is_compatible + && !filament.preset->is_default + && (!filament.preset->vendor || !filament.preset->vendor->templates_profile) + && filament.preset->config.has("filament_type") + && (!avoid_abrasive || filament.preset->config.option("filament_abrasive")->values[0] == false) + && filament.preset->config.option("filament_type")->serialize() == material + && filament.preset->name.compare(0, 9, "QIDIment") == 0 + && select_filament_preset(filament.preset, extruder_index)) + { + out_message += GUI::format(_L("Extruder %1%: Installed and selected filament %2%"), extruder_index + 1, filament.preset->name) + "\n"; + return; + } + } + out_message += GUI::format(_L("Extruder %2%: Failed to find and select filament type: %1%"), material, extruder_index + 1) + "\n"; +} + +void GUI_App::select_filament_from_connect(const std::string& msg) +{ + // parse message + std::vector materials; + std::vector avoid_abrasive; + UserAccountUtils::fill_material_from_json(msg, materials, avoid_abrasive); + if (materials.empty()) { + BOOST_LOG_TRIVIAL(error) << "Failed to select filament from Connect. No material data."; + return; + } + // test if currently selected is same type + size_t extruder_count = preset_bundle->extruders_filaments.size(); + if (extruder_count != materials.size()) { + BOOST_LOG_TRIVIAL(error) << format("Failed to select filament from Connect. Selected printer has %1% extruders while data from Connect contains %2% materials.", extruder_count, materials.size()); + plater()->get_notification_manager()->close_notification_of_type(NotificationType::SelectFilamentFromConnect); + // TRN: Notification text. + plater()->get_notification_manager()->push_notification(NotificationType::SelectFilamentFromConnect, NotificationManager::NotificationLevel::ImportantNotificationLevel, _u8L("Failed to select filament from Connect.")); + return; + } + std::string notification_text; + for (size_t i = 0; i < extruder_count; i++) { + search_and_select_filaments(materials[i], avoid_abrasive.size() > i ? avoid_abrasive[i] : false, i, notification_text); + } + + // When all filaments are selected/intalled, + // then update preset comboboxes on sidebar + sidebar().update_presets(Preset::TYPE_FILAMENT); + // and filaments tab + TabFilament* tab = dynamic_cast(get_tab(Preset::TYPE_FILAMENT)); + tab->select_preset(preset_bundle->extruders_filaments[tab->get_active_extruder()].get_selected_preset_name()); + + if (!notification_text.empty()) { + plater()->get_notification_manager()->close_notification_of_type(NotificationType::SelectFilamentFromConnect); + plater()->get_notification_manager()->push_notification(NotificationType::SelectFilamentFromConnect, NotificationManager::NotificationLevel::ImportantNotificationLevel, notification_text); + } +} + +void GUI_App::handle_connect_request_printer_select(const std::string& msg) +{ + // Here comes code from ConnectWebViewPanel + // It only contains uuid of a printer to be selected + // Lets queue it and wait on result. The result is send via event to plater, where it is send to handle_connect_request_printer_select_inner + boost::property_tree::ptree ptree; + std::string uuid = UserAccountUtils::get_keyword_from_json(ptree, msg, "uuid"); + plater()->get_user_account()->enqueue_printer_data_action(uuid); +} +void GUI_App::handle_connect_request_printer_select_inner(const std::string & msg) +{ + BOOST_LOG_TRIVIAL(debug) << "Handling web request: " << msg; + // return to plater + this->mainframe->select_tab(size_t(0)); + if (!select_printer_from_connect(msg)) { + // If printer was not selected, do not select filament. + return; + } + // TODO: Selecting SLA material + if (Preset::printer_technology(preset_bundle->printers.get_selected_preset().config) != ptFFF) { + return; + } + select_filament_from_connect(msg); +} + +//void GUI_App::show_printer_webview_tab() +//{ +// mainframe->show_printer_webview_tab(preset_bundle->physical_printers.get_selected_printer_config()); +//} + + + +bool LogGui::ignorred_message(const wxString& msg) +{ + for(const wxString& err : std::initializer_list{ wxString("cHRM chunk does not match sRGB"), + wxString("known incorrect sRGB profile") }) { + if (msg.Contains(err)) + return true; + } + return false; +} + +void LogGui::DoLogText(const wxString& msg) +{ + if (ignorred_message(msg)) + return; + wxLogGui::DoLogText(msg); +} + +void LogGui::DoLogRecord(wxLogLevel level, const wxString& msg, const wxLogRecordInfo& info) +{ + if (ignorred_message(msg)) + return; + wxLogGui::DoLogRecord(level, msg, info); +} } // GUI } //Slic3r diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 2c0cebb..8f0c60f 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -7,6 +7,7 @@ #include "ConfigWizard.hpp" #include "OpenGLManager.hpp" #include "libslic3r/Preset.hpp" +#include "I18N.hpp" #include #include @@ -39,6 +40,10 @@ class PrintHostJobQueue; class Model; class AppUpdater; +namespace Search { + class OptionsSearcher; +} + namespace GUI{ class RemovableDriveManager; @@ -54,8 +59,7 @@ class NotificationManager; class Downloader; struct GUI_InitParams; class GalleryDialog; - - +class PresetArchiveDatabase; enum FileType { @@ -115,6 +119,20 @@ static wxString dots("…", wxConvUTF8); #define SUPPORTS_MARKUP #endif + +// A wrapper class to allow ignoring some known warnings +// and not bothering users with redundant messages. +// see https://github.com/qidi3d/QIDISlicer/issues/12920 +class LogGui : public wxLogGui +{ +protected: + void DoLogText(const wxString& msg) override; + void DoLogRecord(wxLogLevel level, const wxString& msg, const wxLogRecordInfo& info) override; + +private: + bool ignorred_message(const wxString& msg); +}; + class GUI_App : public wxApp { public: @@ -137,7 +155,7 @@ private: wxColour m_color_label_sys; wxColour m_color_label_default; wxColour m_color_window_default; -#ifdef _WIN32 +//#ifdef _WIN32 wxColour m_color_highlight_label_default; wxColour m_color_hovered_btn_label; wxColour m_color_default_btn_label; @@ -145,6 +163,7 @@ private: //B10 wxColour m_tap_color_highlight_default; wxColour m_color_selected_btn_bg; +#ifdef _WIN32 bool m_force_colors_update { false }; #endif //B64 @@ -175,17 +194,20 @@ private: //B4 bool m_adding_script_handler{false}; - std::unique_ptr m_removable_drive_manager; - - std::unique_ptr m_imgui; - std::unique_ptr m_printhost_job_queue; - std::unique_ptr m_other_instance_message_handler; - std::unique_ptr m_app_updater; - std::unique_ptr m_single_instance_checker; - std::unique_ptr m_downloader; + std::unique_ptr m_removable_drive_manager; + std::unique_ptr m_imgui; + std::unique_ptr m_printhost_job_queue; + std::unique_ptr m_other_instance_message_handler; + std::unique_ptr m_app_updater; + std::unique_ptr m_single_instance_checker; + std::unique_ptr m_downloader; + std::string m_instance_hash_string; size_t m_instance_hash_int; + Search::OptionsSearcher* m_searcher{ nullptr }; + LogGui* m_log_gui { nullptr }; + public: bool OnInit() override; bool initialized() const { return m_initialized; } @@ -199,6 +221,16 @@ public: bool is_recreating_gui() const { return m_is_recreating_gui; } std::string logo_name() const { return is_editor() ? "QIDISlicer" : "QIDISlicer-gcodeviewer"; } + Search::OptionsSearcher& searcher() noexcept { return *m_searcher; } + void set_searcher(Search::OptionsSearcher* searcher) { m_searcher = searcher; } + void check_and_update_searcher(ConfigOptionMode mode = comExpert); + void jump_to_option(size_t selected); + void jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category); + // jump to option which is represented by composite key : "opt_key;tab_name" + void jump_to_option(const std::string& composite_key); + void update_search_lines(); + void show_search_dialog(); + // To be called after the GUI is fully built up. // Process command line parameters cached in this->init_params, // load configs, STLs etc. @@ -246,7 +278,7 @@ public: std::vector get_mode_palette(); void set_mode_palette(const std::vector &palette); -#ifdef _WIN32 +// #ifdef _WIN32 const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; } const wxColour& get_highlight_default_clr() { return m_color_highlight_default; } //B10 @@ -257,7 +289,7 @@ public: #ifdef _MSW_DARK_MODE void force_menu_update(); #endif //_MSW_DARK_MODE -#endif +// #endif //B64 #if QDT_RELEASE_TO_PUBLIC std::vector get_devices() { return m_devices; }; @@ -274,7 +306,7 @@ public: bool suppress_round_corners() const; wxSize get_min_size(wxWindow* display_win) const; int get_max_font_pt_size(); - float toolbar_icon_scale(const bool is_limited = false) const; + float toolbar_icon_scale(bool& is_custom) const; void set_auto_toolbar_icon_scale(float scale) const; void check_printer_presets(); @@ -303,7 +335,7 @@ public: bool save_mode(const /*ConfigOptionMode*/int mode) ; void update_mode(); - void add_config_menu(wxMenuBar *menu); + wxMenu* get_config_menu(MainFrame* main_frame); bool has_unsaved_preset_changes() const; bool has_current_preset_changes() const; void update_saved_preset_from_current_preset(); @@ -327,6 +359,7 @@ public: // Calls wxLaunchDefaultBrowser if user confirms in dialog. // Add "Rememeber my choice" checkbox to question dialog, when it is forced or a "suppress_hyperlinks" option has empty value bool open_browser_with_warning_dialog(const wxString& url, wxWindow* parent = nullptr, bool force_remember_choice = true, int flags = 0); + bool open_login_browser_with_dialog(const wxString& url, wxWindow* parent = nullptr, int flags = 0); #ifdef __APPLE__ void OSXStoreOpenFiles(const wxArrayString &files) override; // wxWidgets override to get an event on open files. @@ -379,6 +412,7 @@ public: void open_web_page_localized(const std::string &http_address); bool may_switch_to_SLA_preset(const wxString& caption); bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME); + void update_wizard_login_page(); void show_desktop_integration_dialog(); void show_downloader_registration_dialog(); @@ -413,6 +447,29 @@ public: //y3 void setExitHost(std::set exit_host) { m_exit_host = exit_host; }; std::set getExitHost() { return m_exit_host; }; + void request_login(bool show_user_info = false) {} + bool check_login() { return false; } + void get_login_info() {} + bool is_user_login() { return true; } + + void request_user_login(int online_login) {} + void request_user_logout() {} + int request_user_unbind(std::string dev_id) { return 0; } + bool select_printer_from_connect(const std::string& cmd); + void select_filament_from_connect(const std::string& cmd); + void handle_connect_request_printer_select(const std::string& cmd); + void handle_connect_request_printer_select_inner(const std::string& cmd); + // void show_printer_webview_tab(); + // return true if preset vas invisible and we have to installed it to make it selectable + bool select_printer_preset(const Preset* printer_preset); + bool select_filament_preset(const Preset* filament_preset, size_t extruder_index); + void search_and_select_filaments(const std::string& material, bool avoid_abrasive, size_t extruder_index, std::string& out_message); + void handle_script_message(std::string msg) {} + void request_model_download(std::string import_json) {} + void download_project(std::string project_id) {} + void request_project_download(std::string project_id) {} + void request_open_project(std::string project_id) {} + void request_remove_project(std::string project_id) {} private: bool on_init_inner(); @@ -420,6 +477,7 @@ private: // returns old config path to copy from if such exists, // returns an empty string if such config path does not exists or if it cannot be loaded. std::string check_older_app_config(Semver current_version, bool backup); + void legacy_app_config_vendor_check(); void window_pos_save(wxTopLevelWindow* window, const std::string &name); void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false); void window_pos_sanitize(wxTopLevelWindow* window); @@ -428,18 +486,24 @@ private: bool config_wizard_startup(); // Returns true if the configuration is fine. // Returns true if the configuration is not compatible and the user decided to rather close the slicer instead of reconfiguring. - bool check_updates(const bool verbose); + bool check_updates(const bool invoked_automatically); void on_version_read(wxCommandEvent& evt); // if the data from version file are already downloaded, shows dialogs to start download of new version of app void app_updater(bool from_user); // inititate read of version file online in separate thread void app_version_check(bool from_user); - bool m_datadir_redefined { false }; - bool m_wifi_config_dialog_shown { false }; //y3 std::set m_exit_host; + + bool m_wifi_config_dialog_was_declined { false }; + // change to vector of items when adding more items that require update + //wxMenuItem* m_login_config_menu_item { nullptr }; + std::map< ConfigMenuIDs, wxMenuItem*> m_config_menu_updatable_items; + + ConfigWizard* m_config_wizard {nullptr}; + }; DECLARE_APP(GUI_App) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index f0be9dd..ca461d5 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -23,6 +23,33 @@ #include "slic3r/Utils/MacDarkMode.hpp" #endif +// ---------------------------------------------------------------------------- +// MenuWithSeparators +// ---------------------------------------------------------------------------- + +void MenuWithSeparators::DestroySeparators() +{ + if (m_separator_frst) { + Destroy(m_separator_frst); + m_separator_frst = nullptr; + } + + if (m_separator_scnd) { + Destroy(m_separator_scnd); + m_separator_scnd = nullptr; + } +} + +void MenuWithSeparators::SetFirstSeparator() +{ + m_separator_frst = this->AppendSeparator(); +} + +void MenuWithSeparators::SetSecondSeparator() +{ + m_separator_scnd = this->AppendSeparator(); +} + namespace Slic3r { namespace GUI @@ -494,6 +521,7 @@ wxString MenuFactory::get_repaire_result_message( return msg; } + void MenuFactory::append_menu_item_delete(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), @@ -542,7 +570,7 @@ static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType 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) @@ -553,11 +581,11 @@ static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType 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); - } + 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); @@ -572,7 +600,7 @@ static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType 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(); const std::string icon_name = is_submenu_item ? "" : ADD_VOLUME_MENU_ITEMS[int(type)].second; @@ -587,6 +615,7 @@ void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, 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(MenuType menu_type) { wxMenu* menu = menu_type == mtObjectFFF ? &m_object_menu : menu_type == mtObjectSLA ? &m_sla_object_menu : nullptr; @@ -1049,7 +1078,7 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu) return false; const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects); if (volume == nullptr) - return false; + return false; return volume->is_text(); }; @@ -1108,6 +1137,7 @@ void MenuFactory::append_menu_item_edit_svg(wxMenu *menu) }; append_menu_item(menu, wxID_ANY, name, description, open_svg, icon, nullptr, can_edit_svg, m_parent); } + MenuFactory::MenuFactory() { for (int i = 0; i < mtCount; i++) { @@ -1321,6 +1351,7 @@ wxMenu *MenuFactory::svg_part_menu() append_mutable_part_menu_items(&m_svg_part_menu); return &m_svg_part_menu; } + wxMenu* MenuFactory::instance_menu() { return &m_instance_menu; @@ -1464,18 +1495,24 @@ static void update_menu_item_def_colors(T* item) void MenuFactory::sys_color_changed() { - for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) { - sys_color_changed_menu(dynamic_cast(menu));// msw_rescale_menu updates just icons, so use it + for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) + sys_color_changed(dynamic_cast(menu)); +} + +void MenuFactory::sys_color_changed(wxMenu* menu) +{ + sys_color_changed_menu(menu);// msw_rescale_menu updates just icons, so use it #ifdef _WIN32 - // but under MSW we have to update item's bachground color - for (wxMenuItem* item : menu->GetMenuItems()) - update_menu_item_def_colors(item); + // but under MSW we have to update item's bachground color + for (wxMenuItem* item : menu->GetMenuItems()) + update_menu_item_def_colors(item); #endif - } } void MenuFactory::sys_color_changed(wxMenuBar* menubar) { + if (!menubar) + return; for (size_t id = 0; id < menubar->GetMenuCount(); id++) { wxMenu* menu = menubar->GetMenu(id); sys_color_changed_menu(menu); diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index cf65439..26abc13 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -13,6 +13,30 @@ class wxMenu; class wxMenuItem; +// ---------------------------------------------------------------------------- +// MenuWithSeparators +// ---------------------------------------------------------------------------- + +class MenuWithSeparators : public wxMenu +{ +public: + MenuWithSeparators(const wxString& title, long style = 0) + : wxMenu(title, style) {} + + MenuWithSeparators(long style = 0) + : wxMenu(style) {} + + ~MenuWithSeparators() {} + + void DestroySeparators(); + void SetFirstSeparator(); + void SetSecondSeparator(); + +private: + wxMenuItem* m_separator_frst{ nullptr }; // use like separator before settings item + wxMenuItem* m_separator_scnd{ nullptr }; // use like separator between settings items +}; + namespace Slic3r { enum class ModelVolumeType : int; @@ -39,6 +63,7 @@ public: static wxString get_repaire_result_message(const std::vector& succes_models, const std::vector>& failed_models); + MenuFactory(); ~MenuFactory() = default; @@ -48,6 +73,7 @@ public: void update_default_menu(); void sys_color_changed(); + static void sys_color_changed(wxMenu* menu); static void sys_color_changed(wxMenuBar* menu_bar); wxMenu* default_menu(); diff --git a/src/slic3r/GUI/GUI_Geometry.cpp b/src/slic3r/GUI/GUI_Geometry.cpp index b0ed0e0..a643479 100644 --- a/src/slic3r/GUI/GUI_Geometry.cpp +++ b/src/slic3r/GUI/GUI_Geometry.cpp @@ -1,9 +1 @@ -#include "libslic3r/libslic3r.h" #include "GUI_Geometry.hpp" - -namespace Slic3r { -namespace GUI { - - -} // namespace Slic3r -} // namespace GUI diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 0e4c628..3f97970 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -1,7 +1,8 @@ #include "libslic3r/Technologies.hpp" #include "GUI_Init.hpp" -#include "libslic3r/AppConfig.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Utils/DirectoriesUtils.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" @@ -18,7 +19,10 @@ #include #include - +#include +#include +#include +#include #if __APPLE__ #include #endif // __APPLE__ @@ -26,7 +30,6 @@ namespace Slic3r { namespace GUI { - const std::vector> OpenGLVersions::core = { {3,2}, {3,3}, {4,0}, {4,1}, {4,2}, {4,3}, {4,4}, {4,5}, {4,6} }; int GUI_Run(GUI_InitParams ¶ms) @@ -42,6 +45,11 @@ int GUI_Run(GUI_InitParams ¶ms) signal(SIGCHLD, SIG_DFL); #endif // __APPLE__ +#ifdef SLIC3R_LOG_TO_FILE + auto sink = boost::log::add_file_log(get_default_datadir() + "/slicer.log"); + sink->locked_backend()->auto_flush(); + boost::log::add_console_log(); +#endif // SLIC3R_LOG_TO_FILE try { GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) { diff --git a/src/slic3r/GUI/GUI_Init.hpp b/src/slic3r/GUI/GUI_Init.hpp index 4cf38ed..8702772 100644 --- a/src/slic3r/GUI/GUI_Init.hpp +++ b/src/slic3r/GUI/GUI_Init.hpp @@ -10,7 +10,6 @@ namespace GUI { struct OpenGLVersions { - static const std::vector> core; }; @@ -26,15 +25,16 @@ struct GUI_InitParams DynamicPrintConfig extra_config; std::vector input_files; - bool start_as_gcodeviewer; - bool start_downloader; - bool delete_after_load; + bool start_as_gcodeviewer; + bool start_downloader; + bool delete_after_load; std::string download_url; -#if ENABLE_GL_CORE_PROFILE - std::pair opengl_version; - bool opengl_debug; +#if !SLIC3R_OPENGL_ES + std::pair opengl_version; + bool opengl_debug; bool opengl_compatibiity_profile; -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES + bool opengl_aa; }; int GUI_Run(GUI_InitParams ¶ms); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 736b584..ab547d8 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -5,7 +5,7 @@ #include "wxExtensions.hpp" #ifdef __WXOSX__ -#include "../libslic3r/PrintConfig.hpp" +#include "libslic3r/PrintConfig.hpp" #endif class wxBoxSizer; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d1641b7..75012f6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/TextConfiguration.hpp" +#include "libslic3r/BuildVolume.hpp" // IWYU pragma: keep #include "GUI_ObjectList.hpp" #include "GUI_Factories.hpp" #include "GUI_ObjectManipulation.hpp" @@ -29,6 +30,7 @@ #include #include #include +#include // IWYU pragma: keep #include "slic3r/Utils/FixModelByWin10.hpp" @@ -220,6 +222,15 @@ ObjectList::ObjectList(wxWindow* parent) : set_tooltip_for_item(this->get_mouse_position_in_control()); event.Skip(); }); + + GetMainWindow()->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { + m_mouse_left_down = true; + event.Skip(); + }); + GetMainWindow()->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& event) { + m_mouse_left_down = false; + event.Skip(); + }); #endif //__WXMSW__ Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &ObjectList::OnContextMenu, this); @@ -1204,6 +1215,15 @@ void ObjectList::key_event(wxKeyEvent& event) void ObjectList::OnBeginDrag(wxDataViewEvent &event) { +#ifdef __WXMSW__ + if (!m_mouse_left_down) { + event.Veto(); + return; + } + // Invalidate LeftDown flag emmidiately to avoid its unexpected using next time. + m_mouse_left_down = false; +#endif // __WXMSW__ + if (m_is_editing_started) m_is_editing_started = false; #ifdef __WXGTK__ @@ -1541,7 +1561,6 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false void ObjectList::load_from_files(const wxArrayString& input_files, ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery) { - wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().mainframe, wxPD_AUTO_HIDE); @@ -1797,21 +1816,23 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const std::string &n ModelVolume* new_volume = new_object->add_volume(mesh); new_object->sort_volumes(wxGetApp().app_config->get_bool("order_volumes")); new_volume->name = name; + // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_object->invalidate_bounding_box(); - auto bb = mesh.bounding_box(); - new_object->translate(-bb.center()); - new_object->instances[0]->set_offset( - center ? to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) : - bb.center()); + + auto bb = mesh.bounding_box(); + new_object->translate(-bb.center()); + new_object->instances[0]->set_offset( + center ? to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) : + bb.center()); new_object->ensure_on_bed(); #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - + paste_objects_into_list({model.objects.size() - 1}); #ifdef _DEBUG @@ -4548,8 +4569,8 @@ void ObjectList::rename_item() if (new_name.IsEmpty()) return; - if (Plater::has_illegal_filename_characters(new_name)) { - Plater::show_illegal_characters_warning(this); + if (has_illegal_characters(new_name)) { + show_illegal_characters_warning(this); return; } @@ -4666,7 +4687,7 @@ void ObjectList::fix_through_winsdk() // Show info notification wxString msg = MenuFactory::get_repaire_result_message(succes_models, failed_models); - plater->get_notification_manager()->push_notification(NotificationType::RepairFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, boost::nowide::narrow(msg)); + plater->get_notification_manager()->push_notification(NotificationType::RepairFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg)); } void ObjectList::simplify() @@ -4761,7 +4782,7 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) const auto renderer = dynamic_cast(GetColumn(colName)->GetRenderer()); if (renderer->WasCanceled()) - wxTheApp->CallAfter([this]{ Plater::show_illegal_characters_warning(this); }); + wxTheApp->CallAfter([this]{ show_illegal_characters_warning(this); }); #ifdef __WXMSW__ // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index e86d16f..d783c00 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -18,7 +18,6 @@ class wxBoxSizer; class wxBitmapComboBox; class wxMenuItem; -class MenuWithSeparators; namespace Slic3r { class ConfigOptionsGroup; @@ -111,6 +110,9 @@ private: int m_selected_layers_range_idx {-1}; Clipboard m_clipboard; +#ifdef __WXMSW__ + bool m_mouse_left_down{ false }; +#endif struct dragged_item_data { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 20b36fc..f935e04 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -22,6 +22,7 @@ #include "slic3r/Utils/FixModelByWin10.hpp" #include "Widgets/CheckBox.hpp" + // For special mirroring in manipulation gizmo #include "Gizmos/GLGizmosManager.hpp" #include "Gizmos/GLGizmoEmboss.hpp" @@ -1197,6 +1198,7 @@ void ObjectManipulation::sys_color_changed() wxGetApp().UpdateDarkUI(m_word_local_combo); wxGetApp().UpdateDarkUI(m_check_inch); #endif + CheckBox::SysColorChanged(m_check_inch); for (ManipulationEditor* editor : m_editors) editor->sys_color_changed(this); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 43b07f4..9419b0b 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -11,6 +11,7 @@ #include "Widgets/ComboBox.hpp" #include "Widgets/TextInput.hpp" + #ifdef __WXOSX__ class wxBitmapComboBox; #else @@ -21,10 +22,10 @@ class LockButton; class wxStaticBitmap; namespace Slic3r { -namespace GUI { + namespace GUI { #ifdef _WIN32 -class BitmapComboBox; + class BitmapComboBox; #endif #if 1 diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 6bf8e35..e9defa8 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -123,7 +123,7 @@ bool ObjectSettings::update_settings_list() optgroup->label_width = 15; optgroup->sidetext_width = 5; - optgroup->m_on_change = [this, config](const t_config_option_key& opt_id, const boost::any& value) { + optgroup->on_change = [this, config](const t_config_option_key& opt_id, const boost::any& value) { this->update_config_values(config); wxGetApp().obj_list()->changed_object(); }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index a22338a..31fd2b4 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -4,15 +4,20 @@ #include "GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI.hpp" +#include "GUI_Init.hpp" #include "I18N.hpp" #include "3DScene.hpp" #include "BackgroundSlicingProcess.hpp" #include "OpenGLManager.hpp" #include "GLCanvas3D.hpp" #include "libslic3r/PresetBundle.hpp" -#include "DoubleSlider.hpp" +#include "DoubleSliderForGcode.hpp" +#include "DoubleSliderForLayers.hpp" +#include "ExtruderSequenceDialog.hpp" #include "Plater.hpp" +#include "Tab.hpp" #include "MainFrame.hpp" +#include "MsgDialog.hpp" #include "format.hpp" #include @@ -24,6 +29,7 @@ #include #include #include +#include // this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421 #include "libslic3r/Print.hpp" @@ -55,7 +61,8 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; - m_canvas_widget = OpenGLManager::create_wxglcanvas(*this); + const GUI_InitParams* const init_params = wxGetApp().init_params; + m_canvas_widget = OpenGLManager::create_wxglcanvas(*this, (init_params != nullptr) ? init_params->opengl_aa : false); if (m_canvas_widget == nullptr) return false; @@ -183,8 +190,8 @@ Preview::Preview( void Preview::set_layers_slider_values_range(int bottom, int top) { - m_layers_slider->SetHigherValue(std::min(top, m_layers_slider->GetMaxValue())); - m_layers_slider->SetLowerValue(std::max(bottom, m_layers_slider->GetMinValue())); + m_layers_slider->SetSelectionSpan(std::min(top, m_layers_slider->GetMaxPos()), + std::max(bottom, m_layers_slider->GetMinPos())); } bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) @@ -199,7 +206,8 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) SetBackgroundColour(GetParent()->GetBackgroundColour()); #endif // _WIN32 - m_canvas_widget = OpenGLManager::create_wxglcanvas(*this); + const GUI_InitParams* const init_params = wxGetApp().init_params; + m_canvas_widget = OpenGLManager::create_wxglcanvas(*this, (init_params != nullptr) ? init_params->opengl_aa : false); if (m_canvas_widget == nullptr) return false; @@ -209,32 +217,16 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_canvas->set_config(m_config); m_canvas->set_model(model); m_canvas->set_process(m_process); - m_canvas->enable_legend_texture(true); + m_canvas->show_legend(true); m_canvas->enable_dynamic_background(true); - m_layers_slider_sizer = create_layers_slider_sizer(); - - wxGetApp().UpdateDarkUI(m_bottom_toolbar_panel = new wxPanel(this)); + create_sliders(); m_left_sizer = new wxBoxSizer(wxVERTICAL); m_left_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); - wxBoxSizer* right_sizer = new wxBoxSizer(wxVERTICAL); - right_sizer->Add(m_layers_slider_sizer, 1, wxEXPAND, 0); - - m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); - m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); - - wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); - bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0); - m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer); - - m_left_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0); - m_left_sizer->Hide(m_bottom_toolbar_panel); - wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL); main_sizer->Add(m_left_sizer, 1, wxALL | wxEXPAND, 0); - main_sizer->Add(right_sizer, 0, wxALL | wxEXPAND, 0); SetSizer(main_sizer); SetMinSize(GetSize()); @@ -295,142 +287,255 @@ void Preview::load_print(bool keep_z_range) Layout(); } -void Preview::reload_print(bool keep_volumes) +void Preview::reload_print() { -#ifdef __linux__ - // We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955. - // So we are applying a workaround here: a delayed release of OpenGL vertex buffers. if (!IsShown()) - { - m_volumes_cleanup_required = !keep_volumes; return; - } -#endif /* __linux__ */ - if ( -#ifdef __linux__ - m_volumes_cleanup_required || -#endif /* __linux__ */ - !keep_volumes) - { - m_canvas->reset_volumes(); - m_loaded = false; -#ifdef __linux__ - m_volumes_cleanup_required = false; -#endif /* __linux__ */ - } - load_print(); -} - -void Preview::refresh_print() -{ m_loaded = false; - - if (!IsShown()) - return; - - load_print(true); + load_print(); } void Preview::msw_rescale() { - // rescale slider - if (m_layers_slider != nullptr) m_layers_slider->msw_rescale(); - if (m_moves_slider != nullptr) m_moves_slider->msw_rescale(); - + m_layers_slider->SetEmUnit(wxGetApp().em_unit()); + m_moves_slider->SetEmUnit(wxGetApp().em_unit()); // rescale warning legend on the canvas get_canvas3d()->msw_rescale(); // rescale legend - refresh_print(); + reload_print(); } -void Preview::sys_color_changed() +void Preview::render_sliders(GLCanvas3D& canvas) { -#ifdef _WIN32 - wxWindowUpdateLocker noUpdates(this); - wxGetApp().UpdateAllStaticTextDarkUI(m_bottom_toolbar_panel); -#endif // _WIN32 + const Size cnv_size = canvas.get_canvas_size(); + const int canvas_width = cnv_size.get_width(); + const int canvas_height = cnv_size.get_height(); + const float extra_scale = cnv_size.get_scale_factor(); - if (m_layers_slider != nullptr) - m_layers_slider->sys_color_changed(); + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); +#if ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC + // When the application is run as GCodeViewer the collapse toolbar is enabled but invisible, as it is renderer + // outside of the screen + const bool is_collapse_btn_shown = wxGetApp().is_editor() ? collapse_toolbar.is_enabled() : false; +#else + const bool is_collapse_btn_shown = collapse_toolbar.is_enabled(); +#endif // ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC + + if (m_layers_slider) + m_layers_slider->Render(canvas_width, canvas_height, extra_scale, is_collapse_btn_shown ? collapse_toolbar.get_height() : 0.f); + if (m_moves_slider) + m_moves_slider->Render(canvas_width, canvas_height, extra_scale); } -void Preview::jump_layers_slider(wxKeyEvent& evt) +float Preview::get_moves_slider_height() const { - if (m_layers_slider) m_layers_slider->OnChar(evt); + if (m_moves_slider && m_moves_slider->IsShown()) + return m_moves_slider->GetHeight(); + return 0.0f; } -void Preview::move_layers_slider(wxKeyEvent& evt) +float Preview::get_layers_slider_width() const { - if (m_layers_slider != nullptr) m_layers_slider->OnKeyDown(evt); -} - -void Preview::edit_layers_slider(wxKeyEvent& evt) -{ - if (m_layers_slider != nullptr) m_layers_slider->OnChar(evt); + if (m_layers_slider && m_layers_slider->IsShown()) + return m_layers_slider->GetWidth(); + return 0.0f; } void Preview::bind_event_handlers() { Bind(wxEVT_SIZE, &Preview::on_size, this); - m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); } void Preview::unbind_event_handlers() { Unbind(wxEVT_SIZE, &Preview::on_size, this); - m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); -} - -void Preview::move_moves_slider(wxKeyEvent& evt) -{ - if (m_moves_slider != nullptr) m_moves_slider->OnKeyDown(evt); } void Preview::hide_layers_slider() { - m_layers_slider_sizer->Hide((size_t)0); - Layout(); + m_layers_slider->Hide(); } void Preview::on_size(wxSizeEvent& evt) { evt.Skip(); + m_layers_slider->force_ruler_update(); Refresh(); } -wxBoxSizer* Preview::create_layers_slider_sizer() +/* To avoid get an empty string from wxTextEntryDialog + * Let disable OK button, if TextCtrl is empty + * */ +static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, double max = -1.0) { - wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); - m_layers_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100); + GUI::wxGetApp().UpdateDlgDarkUI(dlg); + + // detect TextCtrl and OK button + wxWindowList& dlg_items = dlg->GetChildren(); + for (auto item : dlg_items) { + if (wxTextCtrl* textctrl = dynamic_cast(item)) { + textctrl->SetInsertionPointEnd(); + + wxButton* btn_OK = static_cast(dlg->FindWindowById(wxID_OK)); + btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { + evt.Enable(!textctrl->IsEmpty()); + }, btn_OK->GetId()); + + break; + } + } +} + +void Preview::create_sliders() +{ + // Layers Slider + + m_layers_slider = std::make_unique(0, 0, 0, 100, wxGetApp().is_editor()); + m_layers_slider->SetEmUnit(wxGetApp().em_unit()); + m_layers_slider->set_imgui_wrapper(wxGetApp().imgui()); + m_layers_slider->show_estimated_times(wxGetApp().app_config->get_bool("show_estimated_times_in_dbl_slider")); + m_layers_slider->show_ruler(wxGetApp().app_config->get_bool("show_ruler_in_dbl_slider"), wxGetApp().app_config->get_bool("show_ruler_bg_in_dbl_slider")); m_layers_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA, - wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects")); - m_layers_slider->enable_action_icon(wxGetApp().is_editor()); + wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects")); - sizer->Add(m_layers_slider, 0, wxEXPAND, 0); + m_layers_slider->set_callback_on_thumb_move( [this]() -> void { Preview::on_layers_slider_scroll_changed(); } ); - // sizer, m_canvas_widget - m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_layers_slider_from_canvas, this); - m_canvas_widget->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) { - if (event.GetKeyCode() == WXK_SHIFT) - m_layers_slider->UseDefaultColors(true); - event.Skip(); + m_layers_slider->set_callback_on_change_app_config([](const std::string& key, const std::string& val) -> void { + wxGetApp().app_config->set(key, val); + }); + + if (wxGetApp().is_editor()) { + m_layers_slider->set_callback_on_ticks_changed([this]() -> void { + Model& model = wxGetApp().plater()->model(); + model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues(); + m_schedule_background_process(); + + m_keep_current_preview_type = false; + reload_print(); }); - m_layers_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_layers_slider_scroll_changed, this); - - Bind(DoubleSlider::wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { - Model& model = wxGetApp().plater()->model(); - model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues(); - m_schedule_background_process(); - - m_keep_current_preview_type = false; - reload_print(false); + m_layers_slider->set_callback_on_check_gcode([this](CustomGCode::Type type) -> void { + if (type == ColorChange && m_layers_slider->gcode(ColorChange).empty()) + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyColorChangeCode); }); - return sizer; + m_layers_slider->set_callback_on_empty_auto_color_change([]() -> void { + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::EmptyAutoColorChange); + }); + + m_layers_slider->set_callback_on_get_extruder_colors([]() -> std::vector { + return wxGetApp().plater()->get_extruder_color_strings_from_plater_config(); + }); + + m_layers_slider->set_callback_on_get_print([]() -> const Print& { + return GUI::wxGetApp().plater()->fff_print(); + }); + + m_layers_slider->set_callback_on_get_custom_code([](const std::string& code_in, double height) -> std::string + { + wxString msg_text = _L("Enter custom G-code used on current layer") + ":"; + wxString msg_header = format_wxstr(_L("Custom G-code on current layer (%1% mm)."), height); + + // get custom gcode + wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, + wxTextEntryDialogStyle | wxTE_MULTILINE); + upgrade_text_entry_dialog(&dlg); + + bool valid = true; + std::string value; + do { + if (dlg.ShowModal() != wxID_OK) + return ""; + + value = into_u8(dlg.GetValue()); + valid = Tab::validate_custom_gcode("Custom G-code", value); + } while (!valid); + return value; + }); + + m_layers_slider->set_callback_on_get_pause_print_msg([](const std::string& msg_in, double height) -> std::string + { + wxString msg_text = _L("Enter short message shown on Printer display when a print is paused") + ":"; + wxString msg_header = format_wxstr(_L("Message for pause print on current layer (%1% mm)."), height); + + // get custom gcode + wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), + wxTextEntryDialogStyle); + upgrade_text_entry_dialog(&dlg); + + if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) + return ""; + + return into_u8(dlg.GetValue()); + }); + + m_layers_slider->set_callback_on_get_new_color([](const std::string& color) -> std::string + { + wxColour clr(color); + if (!clr.IsOk()) + clr = wxColour(0, 0, 0); // Don't set alfa to transparence + + auto data = new wxColourData(); + data->SetChooseFull(1); + data->SetColour(clr); + + wxColourDialog dialog(GUI::wxGetApp().GetTopWindow(), data); + dialog.CenterOnParent(); + if (dialog.ShowModal() == wxID_OK) + return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); + return ""; + }); + + m_layers_slider->set_callback_on_show_info_msg([this](const std::string& message, int btns_flag) -> int + { + GUI::MessageDialog msg(this, from_u8(message), _L("Notice"), btns_flag); + int ret = msg.ShowModal(); + return ret == wxID_YES ? wxYES : + ret == wxID_NO ? wxNO : + ret == wxID_CANCEL ? wxCANCEL : -1; + }); + + m_layers_slider->set_callback_on_show_warning_msg([this](const std::string& message, int btns_flag) -> int + { + GUI::WarningDialog msg(this, from_u8(message), _L("Warning"), btns_flag); + int ret = msg.ShowModal(); + return ret == wxID_YES ? wxYES : + ret == wxID_NO ? wxNO : + ret == wxID_CANCEL ? wxCANCEL : -1; + }); + + m_layers_slider->set_callback_on_get_extruders_cnt([]() -> int + { + return GUI::wxGetApp().extruders_edited_cnt(); + }); + + m_layers_slider->set_callback_on_get_extruders_sequence([](DoubleSlider::ExtrudersSequence& extruders_sequence) -> bool + { + GUI::ExtruderSequenceDialog dlg(extruders_sequence); + if (dlg.ShowModal() != wxID_OK) + return false; + extruders_sequence = dlg.GetValue(); + return true; + }); + } + + // Move Gcode Slider + + m_moves_slider = std::make_unique(0, 0, 0, 100); + m_moves_slider->SetEmUnit(wxGetApp().em_unit()); + + m_moves_slider->set_callback_on_thumb_move([this]() ->void { on_moves_slider_scroll_changed(); }); + + // m_canvas_widget + m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_sliders_from_canvas, this); + m_canvas_widget->Bind(EVT_GLCANVAS_SLIDERS_MANIPULATION, &Preview::update_sliders_from_canvas, this); + + // Hide sliders from the very begibing. Visibility will be set later + m_layers_slider->Hide(); + m_moves_slider->Hide(); } // Find an index of a value in a sorted vector, which is in . @@ -469,7 +574,7 @@ void Preview::check_layers_slider_values(std::vector& ticks_f ticks_from_model.erase(std::remove_if(ticks_from_model.begin(), ticks_from_model.end(), [layers_z](CustomGCode::Item val) { - auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.print_z - DoubleSlider::epsilon()); + auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.print_z - CustomGCode::epsilon()); return it == layers_z.end(); }), ticks_from_model.end()); @@ -480,18 +585,17 @@ void Preview::check_layers_slider_values(std::vector& ticks_f void Preview::update_layers_slider(const std::vector& layers_z, bool keep_z_range) { // Save the initial slider span. - double z_low = m_layers_slider->GetLowerValueD(); - double z_high = m_layers_slider->GetHigherValueD(); - bool was_empty = m_layers_slider->GetMaxValue() == 0; + double z_low = m_layers_slider->GetLowerValue(); + double z_high = m_layers_slider->GetHigherValue(); + bool was_empty = m_layers_slider->GetMaxPos() == 0; bool force_sliders_full_range = was_empty; - if (!keep_z_range) - { - bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_layers_slider->GetMaxValueD()) > DoubleSlider::epsilon()/*1e-6*/; + if (!keep_z_range) { + bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_layers_slider->GetMaxValue()) > CustomGCode::epsilon()/*1e-6*/; force_sliders_full_range |= span_changed; } - bool snap_to_min = force_sliders_full_range || m_layers_slider->is_lower_at_min(); - bool snap_to_max = force_sliders_full_range || m_layers_slider->is_higher_at_max(); + bool snap_to_min = force_sliders_full_range || m_layers_slider->IsLowerAtMin(); + bool snap_to_max = force_sliders_full_range || m_layers_slider->IsHigherAtMax(); // Detect and set manipulation mode for double slider update_layers_slider_mode(); @@ -502,26 +606,30 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee ticks_info_from_model = plater->model().custom_gcode_per_print_z; else { ticks_info_from_model.mode = CustomGCode::Mode::SingleExtruder; - ticks_info_from_model.gcodes = m_canvas->get_custom_gcode_per_print_z(); + ticks_info_from_model.gcodes = m_gcode_result->custom_gcode_per_print_z; } check_layers_slider_values(ticks_info_from_model.gcodes, layers_z); //first of all update extruder colors to avoid crash, when we are switching printer preset from MM to SM - m_layers_slider->SetExtruderColors(plater->get_extruder_colors_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result)); + m_layers_slider->SetExtruderColors(plater->get_extruder_color_strings_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result)); m_layers_slider->SetSliderValues(layers_z); - assert(m_layers_slider->GetMinValue() == 0); - m_layers_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1); + m_layers_slider->force_ruler_update(); + assert(m_layers_slider->GetMinPos() == 0); + + m_layers_slider->Freeze(); + + m_layers_slider->SetMaxPos(layers_z.empty() ? 0 : layers_z.size() - 1); int idx_low = 0; - int idx_high = m_layers_slider->GetMaxValue(); + int idx_high = m_layers_slider->GetMaxPos(); if (!layers_z.empty()) { if (!snap_to_min) { - int idx_new = find_close_layer_idx(layers_z, z_low, DoubleSlider::epsilon()/*1e-6*/); + int idx_new = find_close_layer_idx(layers_z, z_low, CustomGCode::epsilon()/*1e-6*/); if (idx_new != -1) idx_low = idx_new; } if (!snap_to_max) { - int idx_new = find_close_layer_idx(layers_z, z_high, DoubleSlider::epsilon()/*1e-6*/); + int idx_new = find_close_layer_idx(layers_z, z_high, CustomGCode::epsilon()/*1e-6*/); if (idx_new != -1) idx_high = idx_new; } @@ -533,11 +641,11 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects"); m_layers_slider->SetDrawMode(sla_print_technology, sequential_print); if (sla_print_technology) - m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times); - else { - auto print_mode_stat = m_gcode_result->print_statistics.modes.front(); - m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time); - } + m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times_running_total); + else + m_layers_slider->SetLayersTimes(m_canvas->get_gcode_layers_times_cache(), m_gcode_result->print_statistics.modes.front().time); + + m_layers_slider->Thaw(); // check if ticks_info_from_model contains ColorChange g-code bool color_change_already_exists = false; @@ -547,10 +655,20 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee break; } + auto get_print_obj_idxs = [plater]() ->std::string { + if (plater->printer_technology() == ptSLA) + return "sla"; + const Print& print = GUI::wxGetApp().plater()->fff_print(); + std::string idxs; + for (auto object : print.objects()) + idxs += std::to_string(object->id().id) + "_"; + return idxs; + }; + // Suggest the auto color change, if model looks like sign if (!color_change_already_exists && wxGetApp().app_config->get_bool("allow_auto_color_change") && - m_layers_slider->IsNewPrint()) + m_layers_slider->is_new_print(get_print_obj_idxs())) { const Print& print = wxGetApp().plater()->fff_print(); @@ -605,9 +723,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee break; } } - - m_layers_slider_sizer->Show((size_t)0); - Layout(); + m_layers_slider->Show(); } void Preview::update_layers_slider_mode() @@ -666,72 +782,115 @@ void Preview::update_layers_slider_mode() void Preview::reset_layers_slider() { - m_layers_slider->SetHigherValue(0); - m_layers_slider->SetLowerValue(0); + m_layers_slider->SetSelectionSpan(0, 0); } -void Preview::update_layers_slider_from_canvas(wxKeyEvent& event) +void Preview::update_sliders_from_canvas(wxKeyEvent& event) { - if (event.HasModifiers()) { + const auto key = event.GetKeyCode(); + + const bool can_edit = wxGetApp().is_editor(); + + if (can_edit && (key == WXK_NUMPAD_ADD || key == '+')) + m_layers_slider->add_current_tick(); + else if (can_edit && (key == WXK_NUMPAD_SUBTRACT || key == WXK_DELETE || key == WXK_BACK || key == '-')) + m_layers_slider->delete_current_tick(); + else if (key == 'G' || key == 'g') + m_layers_slider->jump_to_value(); + else if (key == WXK_LEFT || key == WXK_RIGHT || key == WXK_UP || key == WXK_DOWN) { + int delta = 1; + // accelerators + int accelerator = 0; + if (wxGetKeyState(WXK_SHIFT)) + accelerator += 5; + if (wxGetKeyState(WXK_CONTROL)) + accelerator += 5; + if (accelerator > 0) + delta *= accelerator; + + if (key == WXK_LEFT || key == WXK_RIGHT) + m_moves_slider->move_current_thumb(delta * (key == WXK_LEFT ? 1 : -1)); + else if (key == WXK_UP || key == WXK_DOWN) + m_layers_slider->move_current_thumb(delta * (key == WXK_DOWN ? 1 : -1)); + } + + else if (event.HasModifiers()) { event.Skip(); return; } - const auto key = event.GetKeyCode(); - - if (key == 'S' || key == 'W') { - const int new_pos = key == 'W' ? m_layers_slider->GetHigherValue() + 1 : m_layers_slider->GetHigherValue() - 1; - m_layers_slider->SetHigherValue(new_pos); - if (event.ShiftDown() || m_layers_slider->is_one_layer()) m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue()); + else if (key == 'S' || key == 'W') { + const int new_pos = key == 'W' ? m_layers_slider->GetHigherPos() + 1 : m_layers_slider->GetHigherPos() - 1; + m_layers_slider->SetHigherPos(new_pos); } else if (key == 'A' || key == 'D') { - const int new_pos = key == 'D' ? m_moves_slider->GetHigherValue() + 1 : m_moves_slider->GetHigherValue() - 1; - m_moves_slider->SetHigherValue(new_pos); - if (event.ShiftDown() || m_moves_slider->is_one_layer()) m_moves_slider->SetLowerValue(m_moves_slider->GetHigherValue()); + const int new_pos = key == 'D' ? m_moves_slider->GetHigherPos() + 1 : m_moves_slider->GetHigherPos() - 1; + m_moves_slider->SetHigherPos(new_pos); } else if (key == 'X') m_layers_slider->ChangeOneLayerLock(); - else if (key == WXK_SHIFT) - m_layers_slider->UseDefaultColors(false); else event.Skip(); } -void Preview::update_moves_slider() +void Preview::update_moves_slider(std::optional visible_range_min, std::optional visible_range_max) { - const GCodeViewer::SequentialView& view = m_canvas->get_gcode_sequential_view(); - // this should not be needed, but it is here to try to prevent rambling crashes on Mac Asan - if (view.endpoints.last < view.endpoints.first) + if (m_gcode_result->moves.empty()) return; - assert(view.endpoints.first <= view.current.first && view.current.first <= view.endpoints.last); - assert(view.endpoints.first <= view.current.last && view.current.last <= view.endpoints.last); + const libvgcode::Interval& range = m_canvas->get_gcode_view_enabled_range(); + uint32_t last_gcode_id = m_canvas->get_gcode_vertex_at(range[0]).gcode_id; + std::optional gcode_id_min = visible_range_min.has_value() ? + std::optional{ m_canvas->get_gcode_vertex_at(*visible_range_min).gcode_id } : std::nullopt; + std::optional gcode_id_max = visible_range_max.has_value() ? + std::optional{ m_canvas->get_gcode_vertex_at(*visible_range_max).gcode_id } : std::nullopt; - std::vector values; - values.reserve(view.endpoints.last - view.endpoints.first + 1); - std::vector alternate_values; - alternate_values.reserve(view.endpoints.last - view.endpoints.first + 1); - unsigned int last_gcode_id = view.gcode_ids[view.endpoints.first]; - for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i) { - if (i > view.endpoints.first) { + const size_t range_size = range[1] - range[0] + 1; + std::vector values; + values.reserve(range_size); + std::vector alternate_values; + alternate_values.reserve(range_size); + + std::optional visible_range_min_id; + std::optional visible_range_max_id; + uint32_t counter = 0; + + for (size_t i = range[0]; i <= range[1]; ++i) { + const uint32_t gcode_id = m_canvas->get_gcode_vertex_at(i).gcode_id; + bool skip = false; + if (i > range[0]) { // skip consecutive moves with same gcode id (resulting from processing G2 and G3 lines) - if (last_gcode_id == view.gcode_ids[i]) { - values.back() = static_cast(i + 1); - alternate_values.back() = static_cast(view.gcode_ids[i]); - continue; + if (last_gcode_id == gcode_id) { + values.back() = i + 1; + skip = true; } else - last_gcode_id = view.gcode_ids[i]; + last_gcode_id = gcode_id; } - values.emplace_back(static_cast(i + 1)); - alternate_values.emplace_back(static_cast(view.gcode_ids[i])); + if (!skip) { + values.emplace_back(i + 1); + alternate_values.emplace_back(gcode_id); + if (gcode_id_min.has_value() && alternate_values.back() == *gcode_id_min) + visible_range_min_id = counter; + else if (gcode_id_max.has_value() && alternate_values.back() == *gcode_id_max) + visible_range_max_id = counter; + ++counter; + } } + const int span_min_id = visible_range_min_id.has_value() ? *visible_range_min_id : 0; + const int span_max_id = visible_range_max_id.has_value() ? *visible_range_max_id : static_cast(values.size()) - 1; + m_moves_slider->SetSliderValues(values); m_moves_slider->SetSliderAlternateValues(alternate_values); - m_moves_slider->SetMaxValue(int(values.size()) - 1); - m_moves_slider->SetSelectionSpan(values.front() - 1 - view.endpoints.first, values.back() - 1 - view.endpoints.first); + + m_moves_slider->Freeze(); + m_moves_slider->SetMaxPos(static_cast(values.size()) - 1); + m_moves_slider->SetSelectionSpan(span_min_id, span_max_id); + m_moves_slider->Thaw(); + + m_moves_slider->ShowLowerThumb(get_app_config()->get("seq_top_layer_only") == "0"); } void Preview::enable_moves_slider(bool enable) @@ -739,7 +898,6 @@ void Preview::enable_moves_slider(bool enable) bool render_as_disabled = !enable; if (m_moves_slider != nullptr && m_moves_slider->is_rendering_as_disabled() != render_as_disabled) { m_moves_slider->set_render_as_disabled(render_as_disabled); - m_moves_slider->Refresh(); } } @@ -773,35 +931,24 @@ void Preview::load_print_as_fff(bool keep_z_range) } if (wxGetApp().is_editor() && !has_layers) { + m_canvas->reset_gcode_layers_times_cache(); hide_layers_slider(); - m_left_sizer->Hide(m_bottom_toolbar_panel); - m_left_sizer->Layout(); - Refresh(); + m_moves_slider->Hide(); m_canvas_widget->Refresh(); return; } - GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); + libvgcode::EViewType gcode_view_type = m_canvas->get_gcode_view_type(); + const bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); + const bool is_pregcode_preview = !gcode_preview_data_valid && wxGetApp().is_editor(); - // Collect colors per extruder. - std::vector colors; - std::vector color_print_values = {}; - // set color print values, if it si selected "ColorPrint" view type - if (gcode_view_type == GCodeViewer::EViewType::ColorPrint) { - colors = wxGetApp().plater()->get_colors_for_color_print(m_gcode_result); - - if (!gcode_preview_data_valid) { - if (wxGetApp().is_editor()) - color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; - else - color_print_values = m_canvas->get_custom_gcode_per_print_z(); - colors.push_back("#808080"); // gray color for pause print or custom G-code - } - } - else if (gcode_preview_data_valid || gcode_view_type == GCodeViewer::EViewType::Tool) { - colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result); - color_print_values.clear(); + const std::vector tool_colors = wxGetApp().plater()->get_extruder_color_strings_from_plater_config(m_gcode_result); + const std::vector& color_print_values = wxGetApp().is_editor() ? + wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_gcode_result->custom_gcode_per_print_z; + std::vector color_print_colors; + if (!color_print_values.empty()) { + color_print_colors = wxGetApp().plater()->get_color_strings_for_color_print(m_gcode_result); + color_print_colors.push_back("#808080"); // gray color for pause print or custom G-code } std::vector zs; @@ -810,46 +957,39 @@ void Preview::load_print_as_fff(bool keep_z_range) m_canvas->set_selected_extruder(0); if (gcode_preview_data_valid) { // Load the real G-code preview. - m_canvas->load_gcode_preview(*m_gcode_result, colors); - m_left_sizer->Layout(); - Refresh(); + m_canvas->load_gcode_preview(*m_gcode_result, tool_colors, color_print_colors); + // the view type may have been changed by the call m_canvas->load_gcode_preview() + gcode_view_type = m_canvas->get_gcode_view_type(); zs = m_canvas->get_gcode_layers_zs(); - if (!zs.empty()) - m_left_sizer->Show(m_bottom_toolbar_panel); m_loaded = true; } - else if (wxGetApp().is_editor()) { + else if (is_pregcode_preview) { // Load the initial preview based on slices, not the final G-code. - m_canvas->load_preview(colors, color_print_values); - m_left_sizer->Hide(m_bottom_toolbar_panel); - m_left_sizer->Layout(); - Refresh(); - zs = m_canvas->get_volumes_print_zs(true); - } - else { - m_left_sizer->Hide(m_bottom_toolbar_panel); - m_left_sizer->Layout(); - Refresh(); + m_canvas->load_preview(tool_colors, color_print_colors, color_print_values); + // the view type has been changed by the call m_canvas->load_gcode_preview() + if (gcode_view_type == libvgcode::EViewType::ColorPrint && !color_print_values.empty()) + m_canvas->set_gcode_view_type(gcode_view_type); + zs = m_canvas->get_gcode_layers_zs(); } + m_moves_slider->Show(gcode_preview_data_valid && !zs.empty()); if (!zs.empty() && !m_keep_current_preview_type) { unsigned int number_extruders = wxGetApp().is_editor() ? - (unsigned int)print->extruders().size() : - m_canvas->get_gcode_extruders_count(); - //B34 - std::vector gcodes = wxGetApp().is_editor() ? - wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : - m_canvas->get_custom_gcode_per_print_z(); - const bool contains_color_gcodes = std::any_of(std::begin(gcodes), std::end(gcodes), + (unsigned int)print->extruders().size() : m_canvas->get_gcode_extruders_count(); + const bool contains_color_gcodes = std::any_of(std::begin(color_print_values), std::end(color_print_values), [](auto const& item) { return item.type == CustomGCode::Type::ColorChange || item.type == CustomGCode::Type::ToolChange; }); - const GCodeViewer::EViewType choice = contains_color_gcodes ? - GCodeViewer::EViewType::ColorPrint : - (number_extruders > 1) ? GCodeViewer::EViewType::Tool : GCodeViewer::EViewType::FeatureType; + const libvgcode::EViewType choice = contains_color_gcodes ? + libvgcode::EViewType::ColorPrint : + (number_extruders > 1) ? libvgcode::EViewType::Tool : libvgcode::EViewType::FeatureType; if (choice != gcode_view_type) { - m_canvas->set_gcode_view_preview_type(choice); + const bool gcode_view_type_cache_load = m_canvas->is_gcode_view_type_cache_load_enabled(); + if (gcode_view_type_cache_load) + m_canvas->enable_gcode_view_type_cache_load(false); + m_canvas->set_gcode_view_type(choice); + if (gcode_view_type_cache_load) + m_canvas->enable_gcode_view_type_cache_load(true); if (wxGetApp().is_gcode_viewer()) m_keep_current_preview_type = true; - refresh_print(); } } @@ -857,7 +997,8 @@ void Preview::load_print_as_fff(bool keep_z_range) // all layers filtered out hide_layers_slider(); m_canvas_widget->Refresh(); - } else + } + else update_layers_slider(zs, keep_z_range); } } @@ -891,9 +1032,7 @@ void Preview::load_print_as_sla() if (IsShown()) { m_canvas->load_sla_preview(); - m_left_sizer->Hide(m_bottom_toolbar_panel); - m_left_sizer->Layout(); - Refresh(); + m_moves_slider->Hide(); if (n_layers > 0) update_layers_slider(zs); @@ -902,27 +1041,29 @@ void Preview::load_print_as_sla() } } -void Preview::on_layers_slider_scroll_changed(wxCommandEvent& event) +void Preview::on_layers_slider_scroll_changed() { if (IsShown()) { PrinterTechnology tech = m_process->current_printer_technology(); if (tech == ptFFF) { - m_canvas->set_volumes_z_range({ m_layers_slider->GetLowerValueD(), m_layers_slider->GetHigherValueD() }); - m_canvas->set_toolpaths_z_range({ static_cast(m_layers_slider->GetLowerValue()), static_cast(m_layers_slider->GetHigherValue()) }); + m_canvas->set_volumes_z_range({ m_layers_slider->GetLowerValue(), m_layers_slider->GetHigherValue() }); + m_canvas->set_toolpaths_z_range({ static_cast(m_layers_slider->GetLowerPos()), static_cast(m_layers_slider->GetHigherPos()) }); m_canvas->set_as_dirty(); } else if (tech == ptSLA) { - m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_layers_slider->GetLowerValueD())); - m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_layers_slider->GetHigherValueD())); + m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_layers_slider->GetLowerValue())); + m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_layers_slider->GetHigherValue())); + m_canvas->set_layer_slider_index(m_layers_slider->GetHigherPos()); m_canvas->render(); } } } -void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event) +void Preview::on_moves_slider_scroll_changed() { - m_canvas->update_gcode_sequential_view_current(static_cast(m_moves_slider->GetLowerValueD() - 1.0), static_cast(m_moves_slider->GetHigherValueD() - 1.0)); - m_canvas->render(); + m_canvas->update_gcode_sequential_view_current(static_cast(m_moves_slider->GetLowerValue() - 1), static_cast(m_moves_slider->GetHigherValue() - 1)); + m_canvas->set_as_dirty(); + m_canvas->request_extra_frame(); } } // namespace GUI diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 7c5b111..8ba8902 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -16,6 +16,11 @@ class wxComboBox; class wxComboCtrl; class wxCheckBox; +namespace DoubleSlider { + class DSForGcode; + class DSForLayers; +}; + namespace Slic3r { class DynamicPrintConfig; @@ -23,10 +28,6 @@ class Print; class BackgroundSlicingProcess; class Model; -namespace DoubleSlider { - class Control; -}; - namespace GUI { class GLCanvas3D; @@ -78,19 +79,11 @@ class Preview : public wxPanel wxGLCanvas* m_canvas_widget { nullptr }; GLCanvas3D* m_canvas { nullptr }; wxBoxSizer* m_left_sizer { nullptr }; - wxBoxSizer* m_layers_slider_sizer { nullptr }; - wxPanel* m_bottom_toolbar_panel { nullptr }; DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; GCodeProcessorResult* m_gcode_result; -#ifdef __linux__ - // We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955. - // So we are applying a workaround here. - bool m_volumes_cleanup_required { false }; -#endif /* __linux__ */ - // Calling this function object forces Plater::schedule_background_process. std::function m_schedule_background_process; @@ -99,8 +92,8 @@ class Preview : public wxPanel bool m_loaded { false }; - DoubleSlider::Control* m_layers_slider{ nullptr }; - DoubleSlider::Control* m_moves_slider{ nullptr }; + std::unique_ptr m_layers_slider{ nullptr }; + std::unique_ptr m_moves_slider { nullptr }; public: enum class OptionType : unsigned int @@ -134,20 +127,18 @@ public: void load_gcode_shells(); void load_print(bool keep_z_range = false); - void reload_print(bool keep_volumes = false); - void refresh_print(); + void reload_print(); void msw_rescale(); - void sys_color_changed(); - void jump_layers_slider(wxKeyEvent& evt); - void move_layers_slider(wxKeyEvent& evt); - void edit_layers_slider(wxKeyEvent& evt); + + void render_sliders(GLCanvas3D& canvas); + float get_layers_slider_width() const; + float get_moves_slider_height() const; bool is_loaded() const { return m_loaded; } - void update_moves_slider(); + void update_moves_slider(std::optional visible_range_min = std::nullopt, std::optional visible_range_max = std::nullopt); void enable_moves_slider(bool enable); - void move_moves_slider(wxKeyEvent& evt); void hide_layers_slider(); void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; } @@ -163,20 +154,20 @@ private: void on_size(wxSizeEvent& evt); // Create/Update/Reset double slider on 3dPreview - wxBoxSizer* create_layers_slider_sizer(); + void create_sliders(); void check_layers_slider_values(std::vector& ticks_from_model, const std::vector& layers_z); void reset_layers_slider(); void update_layers_slider(const std::vector& layers_z, bool keep_z_range = false); void update_layers_slider_mode(); // update vertical DoubleSlider after keyDown in canvas - void update_layers_slider_from_canvas(wxKeyEvent& event); + void update_sliders_from_canvas(wxKeyEvent& event); void load_print_as_fff(bool keep_z_range = false); void load_print_as_sla(); - void on_layers_slider_scroll_changed(wxCommandEvent& event); - void on_moves_slider_scroll_changed(wxCommandEvent& event); + void on_layers_slider_scroll_changed(); + void on_moves_slider_scroll_changed(); }; } // namespace GUI diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 0022d97..935a0c5 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -207,7 +207,6 @@ private: int m_new_font_point_size; - // check if new scale is differ from previous bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; } diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index fe9be31..6238af6 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include "GUI.hpp" @@ -22,7 +21,6 @@ #include "format.hpp" #include "wxExtensions.hpp" #include "I18N.hpp" -#include "Notebook.hpp" #include "3DScene.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index c1edcc2..2864e83 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1,22 +1,27 @@ - #include "GLGizmoCut.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include #include +#include #include +#include #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/GUI_Factories.hpp" #include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/Field.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/FixModelByWin10.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif #include "imgui/imgui_internal.h" #include "slic3r/GUI/MsgDialog.hpp" @@ -219,9 +224,9 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, m_axis_names = { "X", "Y", "Z" }; m_part_orientation_names = { - {"none", _L("Keep orientation")}, - {"on_cut", _L("Place on cut")}, - {"flip", _L("Flip upside down")}, + {"none", _u8L("Keep orientation")}, + {"on_cut", _u8L("Place on cut")}, + {"flip", _u8L("Flip upside down")}, }; m_labels_map = { @@ -519,7 +524,7 @@ bool GLGizmoCut3D::render_cut_mode_combo() { ImGui::AlignTextToFramePadding(); int selection_idx = int(m_mode); - const bool is_changed = m_imgui->combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width); + const bool is_changed = ImGuiPureWrap::combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width); if (is_changed) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change cut mode"), UndoRedo::SnapshotType::GizmoAction); @@ -533,7 +538,7 @@ bool GLGizmoCut3D::render_cut_mode_combo() bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, int& selection_idx) { ImGui::AlignTextToFramePadding(); - const bool is_changed = m_imgui->combo(label, lines, selection_idx, 0, m_label_width, m_control_width); + const bool is_changed = ImGuiPureWrap::combo(label, lines, selection_idx, 0, m_label_width, m_control_width); //if (is_changed) // update_connector_shape(); @@ -544,7 +549,7 @@ bool GLGizmoCut3D::render_combo(const std::string& label, const std::vectortext(label); + ImGuiPureWrap::text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width); @@ -555,7 +560,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); - m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + ImGuiPureWrap::text(m_imperial_units ? _u8L("in") : _u8L("mm")); value_in = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); return !is_approx(old_val, value); @@ -589,7 +594,7 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f); const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/min_val : 1.f; - ImGuiWrapper::text(label); + ImGuiPureWrap::text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width * 0.7f); @@ -609,7 +614,7 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v void GLGizmoCut3D::render_move_center_input(int axis) { - m_imgui->text(m_axis_names[axis]+":"); + ImGuiPureWrap::text(m_axis_names[axis]+":"); ImGui::SameLine(); ImGui::PushItemWidth(0.3f*m_control_width); @@ -669,7 +674,7 @@ bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::s ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) - m_imgui->tooltip(tooltip.c_str(), ImGui::GetFontSize() * 20.0f); + ImGuiPureWrap::tooltip(tooltip.c_str(), ImGui::GetFontSize() * 20.0f); ImGui::PopStyleVar(); @@ -1173,16 +1178,16 @@ bool GLGizmoCut3D::on_init() m_shortcut_key = WXK_CONTROL_C; // initiate info shortcuts - const wxString ctrl = GUI::shortkey_ctrl_prefix(); - const wxString alt = GUI::shortkey_alt_prefix(); - const wxString shift = "Shift+"; + const std::string ctrl = GUI::shortkey_ctrl_prefix(); + const std::string alt = GUI::shortkey_alt_prefix(); + const std::string shift = "Shift+"; - m_shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add connector"))); - m_shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove connector"))); - m_shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move connector"))); - m_shortcuts.push_back(std::make_pair(shift + _L("Left click"), _L("Add connector to selection"))); - m_shortcuts.push_back(std::make_pair(alt + _L("Left click"), _L("Remove connector from selection"))); - m_shortcuts.push_back(std::make_pair(ctrl + "A", _L("Select all connectors"))); + m_shortcuts.push_back(std::make_pair(_u8L("Left click"), _u8L("Add connector"))); + m_shortcuts.push_back(std::make_pair(_u8L("Right click"), _u8L("Remove connector"))); + m_shortcuts.push_back(std::make_pair(_u8L("Drag"), _u8L("Move connector"))); + m_shortcuts.push_back(std::make_pair(shift + _u8L("Left click"), _u8L("Add connector to selection"))); + m_shortcuts.push_back(std::make_pair(alt + _u8L("Left click"), _u8L("Remove connector from selection"))); + m_shortcuts.push_back(std::make_pair(ctrl + "A", _u8L("Select all connectors"))); return true; } @@ -1888,7 +1893,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor // split to parts for (int id = int(volumes.size())-1; id >= 0; id--) - if (volumes[id]->is_splittable()) + if (volumes[id]->is_splittable() && volumes[id]->is_model_part()) volumes[id]->split(1); m_parts.clear(); @@ -2164,18 +2169,18 @@ void GLGizmoCut3D::on_render() void GLGizmoCut3D::render_debug_input_window(float x) { return; - m_imgui->begin(wxString("DEBUG")); + ImGuiPureWrap::begin("DEBUG"); - m_imgui->end(); + ImGuiPureWrap::end(); /* static bool hide_clipped = false; static bool fill_cut = false; static float contour_width = 0.4f; - m_imgui->checkbox(_L("Hide cut plane and grabbers"), m_hide_cut_plane); - if (m_imgui->checkbox("hide_clipped", hide_clipped) && !hide_clipped) + ImGuiPureWrap::checkbox(_L("Hide cut plane and grabbers"), m_hide_cut_plane); + if (ImGuiPureWrap::checkbox("hide_clipped", hide_clipped) && !hide_clipped) m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); - m_imgui->checkbox("fill_cut", fill_cut); + ImGuiPureWrap::checkbox("fill_cut", fill_cut); m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); if (auto oc = m_c->object_clipper()) oc->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, double(contour_width)); @@ -2186,14 +2191,14 @@ void GLGizmoCut3D::render_debug_input_window(float x) ImGui::Separator(); - if (m_imgui->checkbox(("Render cut plane as disc"), m_cut_plane_as_circle)) + if (ImGuiPureWrap::checkbox(("Render cut plane as disc"), m_cut_plane_as_circle)) m_plane.reset(); ImGui::PushItemWidth(0.5f * m_label_width); if (m_imgui->slider_float("cut_plane_radius_koef", &m_cut_plane_radius_koef, 1.f, 2.f)) m_plane.reset(); - m_imgui->end(); + ImGuiPureWrap::end(); } void GLGizmoCut3D::adjust_window_position(float x, float y, float bottom_limit) @@ -2231,12 +2236,15 @@ void GLGizmoCut3D::select_all_connectors() void GLGizmoCut3D::render_shortcuts() { - if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) + std::wstring btn_label; + btn_label = m_show_shortcuts ? ImGui::CollapseBtn : ImGui::ExpandBtn; + + if (ImGuiPureWrap::button("? " + boost::nowide::narrow(btn_label))) m_show_shortcuts = !m_show_shortcuts; if (m_shortcut_label_width < 0.f) { for (const auto& shortcut : m_shortcuts) { - const float width = m_imgui->calc_text_size(shortcut.first).x; + const float width = ImGuiPureWrap::calc_text_size(shortcut.first).x; if (m_shortcut_label_width < width) m_shortcut_label_width = width; } @@ -2245,9 +2253,9 @@ void GLGizmoCut3D::render_shortcuts() if (m_show_shortcuts) for (const auto&shortcut : m_shortcuts ){ - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, shortcut.first); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, shortcut.first); ImGui::SameLine(m_shortcut_label_width); - m_imgui->text(shortcut.second); + ImGuiPureWrap::text(shortcut.second); } } @@ -2270,13 +2278,13 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) ImGui::Separator(); // WIP : Auto : Need to implement - // m_imgui->text(_L("Mode")); + // ImGuiPureWrap::text(_L("Mode")); // render_connect_mode_radio_button(CutConnectorMode::Auto); // render_connect_mode_radio_button(CutConnectorMode::Manual); ImGui::AlignTextToFramePadding(); - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, m_labels_map["Connectors"]); - + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, m_labels_map["Connectors"]); + m_imgui->disabled_begin(connectors.empty()); ImGui::SameLine(m_label_width); const wxString act_name = _L("Remove connectors"); @@ -2288,7 +2296,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) render_flip_plane_button(m_connectors_editing && connectors.empty()); - m_imgui->text(m_labels_map["Type"]); + ImGuiPureWrap::text(m_labels_map["Type"]); bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); type_changed |= render_connect_type_radio_button(CutConnectorType::Snap); @@ -2334,6 +2342,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].z_angle = m_connector_angle; }); + if (m_connector_type == CutConnectorType::Snap) { render_snap_specific_input(_u8L("Bulge"), _L("Bulge proportion related to radius"), m_snap_bulge_proportion, 0.15f, 5.f, 100.f * m_snap_space_proportion); render_snap_specific_input(_u8L("Space"), _L("Space proportion related to radius"), m_snap_space_proportion, 0.3f, 10.f, 50.f); @@ -2341,14 +2350,14 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) ImGui::Separator(); - if (m_imgui->button(_L("Confirm connectors"))) { + if (ImGuiPureWrap::button(_u8L("Confirm connectors"))) { unselect_all_connectors(); set_connectors_editing(false); } ImGui::SameLine(m_label_width + 1.15f * m_control_width); - if (m_imgui->button(_L("Cancel"))) { + if (ImGuiPureWrap::button(_u8L("Cancel"))) { reset_connectors(); set_connectors_editing(false); } @@ -2357,17 +2366,17 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) void GLGizmoCut3D::render_build_size() { double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; - wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); + std::string unit_str = " " + (m_imperial_units ? _u8L("in") : _u8L("mm")); Vec3d tbb_sz = m_transformed_bounding_box.size(); - wxString size = "X: " + double_to_string(tbb_sz.x() * koef, 2) + unit_str + - ", Y: " + double_to_string(tbb_sz.y() * koef, 2) + unit_str + - ", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str; + std::string size = "X: " + into_u8(double_to_string(tbb_sz.x() * koef, 2)) + unit_str + + ", Y: " + into_u8(double_to_string(tbb_sz.y() * koef, 2)) + unit_str + + ", Z: " + into_u8(double_to_string(tbb_sz.z() * koef, 2)) + unit_str; ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Build Volume")); + ImGuiPureWrap::text(_u8L("Build Volume")); ImGui::SameLine(); - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, size); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, size); } void GLGizmoCut3D::reset_cut_plane() @@ -2469,7 +2478,7 @@ void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); m_imgui->disabled_begin(disable_pred); - if (m_imgui->button(_L("Flip cut plane"))) + if (ImGuiPureWrap::button(_u8L("Flip cut plane"))) flip_cut_plane(); m_imgui->disabled_end(); @@ -2500,7 +2509,7 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) pos.x += size; pos.y += 1.25f * radius; ImGui::GetCurrentWindow()->DrawList->AddNgonFilled(pos, radius, color, 6); - ImGuiWrapper::text(" "); + ImGuiPureWrap::text(" "); } void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) @@ -2547,7 +2556,7 @@ bool GLGizmoCut3D::render_angle_input(const std::string& label, float& in_val, c { bool is_changed{ false }; - ImGuiWrapper::text(label); + ImGuiPureWrap::text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width * 0.7f); @@ -2565,7 +2574,7 @@ bool GLGizmoCut3D::render_angle_input(const std::string& label, float& in_val, c Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Edited"), label), UndoRedo::SnapshotType::GizmoAction); m_imgui->get_last_slider_status().invalidate_snapshot(); if (m_mode == size_t(CutMode::cutTongueAndGroove)) - m_groove_editing = true; + m_groove_editing = true; } in_val = deg2rad(val); is_changed = true; @@ -2600,7 +2609,7 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in void GLGizmoCut3D::render_snap_specific_input(const std::string& label, const wxString& tooltip, float& in_val, const float& init_val, const float min_val, const float max_val) { - ImGuiWrapper::text(label); + ImGuiPureWrap::text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width * 0.7f); @@ -2630,16 +2639,23 @@ void GLGizmoCut3D::render_snap_specific_input(const std::string& label, const wx } } +static std::string get_string_from_wchar(const wchar_t& icon) +{ + std::wstring tmp; + tmp += icon; + return boost::nowide::narrow(tmp); +} + void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) { // if (m_mode == size_t(CutMode::cutPlanar)) { CutMode mode = CutMode(m_mode); if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) { ImGui::AlignTextToFramePadding(); - ImGuiWrapper::text(wxString(ImGui::InfoMarkerSmall)); + ImGuiPureWrap::text(get_string_from_wchar(ImGui::InfoMarkerSmall)); ImGui::SameLine(); - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, - get_wraped_wxString(_L("Hold SHIFT key to draw a cut line"), 40)); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, + into_u8(get_wraped_wxString(_L("Hold SHIFT key to draw a cut line"), 40))); ImGui::Separator(); const bool has_connectors = !connectors.empty(); @@ -2652,15 +2668,15 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) render_build_size(); ImGui::AlignTextToFramePadding(); - ImGuiWrapper::text(_L("Cut position") + ": "); + ImGuiPureWrap::text(_u8L("Cut position") + ": "); ImGui::SameLine(); render_move_center_input(Z); ImGui::SameLine(); const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); m_imgui->disabled_begin(is_cut_plane_init); - wxString act_name = _L("Reset cutting plane"); - if (render_reset_button("cut_plane", into_u8(act_name))) { + std::string act_name = _u8L("Reset cutting plane"); + if (render_reset_button("cut_plane", act_name)) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); reset_cut_plane(); } @@ -2672,15 +2688,15 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) add_vertical_scaled_interval(0.75f); m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); - if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) + if (ImGuiPureWrap::button(has_connectors ? _u8L("Edit connectors") : _u8L("Add connectors"))) set_connectors_editing(true); m_imgui->disabled_end(); ImGui::SameLine(1.5f * m_control_width); m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); - act_name = _L("Reset cut"); - if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) { + act_name = _u8L("Reset cut"); + if (ImGuiPureWrap::button(act_name, _u8L("Reset cutting plane and remove connectors"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); reset_cut_plane(); reset_connectors(); @@ -2690,7 +2706,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) else if (mode == CutMode::cutTongueAndGroove) { m_is_slider_editing_done = false; ImGui::Separator(); - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, m_labels_map["Groove"] + ": "); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, m_labels_map["Groove"] + ": "); render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); render_groove_angle_input(m_labels_map["Flap Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f); @@ -2703,7 +2719,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImVec2 label_size; for (const auto& item : m_part_orientation_names) { - const ImVec2 text_size = ImGuiWrapper::calc_text_size(item.second); + const ImVec2 text_size = ImGuiPureWrap::calc_text_size(item.second); if (label_size.x < text_size.x) label_size.x = text_size.x; if (label_size.y < text_size.y) @@ -2712,46 +2728,46 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) const float h_shift = label_size.x + m_imgui->scaled(3.f); const float marker_size = label_size.y; - auto render_part_name = [this, marker_size, has_connectors](const wxString& name, bool& keep_part, const ImU32& color) { + auto render_part_name = [this, marker_size, has_connectors](const std::string& name, bool& keep_part, const ImU32& color) { bool keep = true; add_horizontal_shift(m_imgui->scaled(1.2f)); - m_imgui->checkbox((m_keep_as_parts ? _L("Part") : _L("Object")) + " " + name, has_connectors ? keep : keep_part); + ImGuiPureWrap::checkbox((m_keep_as_parts ? _u8L("Part") : _u8L("Object")) + " " + name, has_connectors ? keep : keep_part); render_color_marker(marker_size, color); }; - auto render_part_actions = [this, h_shift] (const wxString& suffix, const bool& keep_part, bool& place_on_cut_part, bool& rotate_part) + auto render_part_actions = [this, h_shift] (const std::string& suffix, const bool& keep_part, bool& place_on_cut_part, bool& rotate_part) { float shift = m_imgui->scaled(1.2f); if (suffix == "##lower") shift += h_shift; m_imgui->disabled_begin(!keep_part || m_keep_as_parts); add_horizontal_shift(shift); - if (m_imgui->radio_button(m_part_orientation_names.at("none") + suffix, !place_on_cut_part && !rotate_part)) { + if (ImGuiPureWrap::radio_button(m_part_orientation_names.at("none") + suffix, !place_on_cut_part && !rotate_part)) { rotate_part = false; place_on_cut_part = false; } add_horizontal_shift(shift); - if (m_imgui->radio_button(m_part_orientation_names.at("on_cut") + suffix, place_on_cut_part)) { + if (ImGuiPureWrap::radio_button(m_part_orientation_names.at("on_cut") + suffix, place_on_cut_part)) { place_on_cut_part = !place_on_cut_part; rotate_part = false; } add_horizontal_shift(shift); - if (m_imgui->radio_button(m_part_orientation_names.at("flip") + suffix, rotate_part)) { + if (ImGuiPureWrap::radio_button(m_part_orientation_names.at("flip") + suffix, rotate_part)) { rotate_part = !rotate_part; place_on_cut_part = false; } m_imgui->disabled_end(); }; - ImGuiWrapper::text(_L("Cut result") + ": "); + ImGuiPureWrap::text(_u8L("Cut result") + ": "); add_vertical_scaled_interval(0.5f); m_imgui->disabled_begin(has_connectors || m_keep_as_parts); - render_part_name("A", m_keep_upper, m_imgui->to_ImU32(UPPER_PART_COLOR)); + render_part_name("A", m_keep_upper, ImGuiPSWrap::to_ImU32(UPPER_PART_COLOR)); ImGui::SameLine(h_shift + ImGui::GetCurrentWindow()->WindowPadding.x); - render_part_name("B", m_keep_lower, m_imgui->to_ImU32(LOWER_PART_COLOR)); + render_part_name("B", m_keep_lower, ImGuiPSWrap::to_ImU32(LOWER_PART_COLOR)); m_imgui->disabled_end(); add_vertical_scaled_interval(0.5f); @@ -2765,18 +2781,18 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) add_vertical_scaled_interval(0.75f); m_imgui->disabled_begin(has_connectors || m_part_selection.valid() || mode == CutMode::cutTongueAndGroove); - ImGuiWrapper::text(_L("Cut into") + ":"); + ImGuiPureWrap::text(_u8L("Cut into") + ":"); if (m_part_selection.valid()) m_keep_as_parts = false; add_horizontal_scaled_interval(1.2f); // TRN CutGizmo: RadioButton Cut into ... - if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts)) + if (ImGuiPureWrap::radio_button(_u8L("Objects"), !m_keep_as_parts)) m_keep_as_parts = false; ImGui::SameLine(); // TRN CutGizmo: RadioButton Cut into ... - if (m_imgui->radio_button(_L("Parts"), m_keep_as_parts)) + if (ImGuiPureWrap::radio_button(_u8L("Parts"), m_keep_as_parts)) m_keep_as_parts = true; if (m_keep_as_parts) { @@ -2790,7 +2806,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::Separator(); m_imgui->disabled_begin(!can_perform_cut()); - if(m_imgui->button(_L("Perform cut"))) + if(ImGuiPureWrap::button(_u8L("Perform cut"))) perform_cut(m_parent.get_selection()); m_imgui->disabled_end(); } @@ -2882,7 +2898,7 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) if (m_label_width == 0.f) { for (const auto& item : m_labels_map) { - const float width = ImGuiWrapper::calc_text_size(item.second).x; + const float width = ImGuiPureWrap::calc_text_size(item.second).x; if (m_label_width < width) m_label_width = width; } @@ -2892,29 +2908,31 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { + const std::string warning_marker = get_string_from_wchar(ImGui::WarningMarkerSmall); + if (! m_invalid_connectors_idxs.empty()) { - wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":"; + std::string out = warning_marker + _u8L("Invalid connectors detected") + ":"; if (m_info_stats.outside_cut_contour > size_t(0)) - out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour), + out += "\n - " + GUI::format(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour), m_info_stats.outside_cut_contour); if (m_info_stats.outside_bb > size_t(0)) - out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of object", "%1$d connectors are out of object", m_info_stats.outside_bb), + out += "\n - " + GUI::format(_L_PLURAL("%1$d connector is out of object", "%1$d connectors are out of object", m_info_stats.outside_bb), m_info_stats.outside_bb); if (m_info_stats.is_overlap) - out += "\n - " + _L("Some connectors are overlapped"); - m_imgui->text(out); + out += "\n - " + _u8L("Some connectors are overlapped"); + ImGuiPureWrap::text(out); } if (!m_keep_upper && !m_keep_lower) - m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Select at least one object to keep after cutting.")); + ImGuiPureWrap::text(warning_marker + _u8L("Select at least one object to keep after cutting.")); if (!has_valid_contour()) - m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane is placed out of object")); + ImGuiPureWrap::text(warning_marker + _u8L("Cut plane is placed out of object")); else if (!has_valid_groove()) - m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane with groove is invalid")); + ImGuiPureWrap::text(warning_marker + _u8L("Cut plane with groove is invalid")); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) { - m_imgui->begin(get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); // adjust window position to avoid overlap the view toolbar adjust_window_position(x, y, bottom_limit); @@ -2930,7 +2948,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) render_input_window_warning(); - m_imgui->end(); + ImGuiPureWrap::end(); if (!m_connectors_editing) // connectors mode render_debug_input_window(x); @@ -3127,7 +3145,7 @@ void GLGizmoCut3D::render_connectors() else if (!looking_forward) pos += 0.05 * m_clp_normal; - const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * rotation_transform(-connector.z_angle * Vec3d::UnitZ()) * scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); @@ -3270,23 +3288,26 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects) 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]); + 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 QIDISlicer team in which scenario this issue happened.\n" - "Thank you.", names)).ShowModal(); -} + "Some connectors may be missing in slicing result.\n" + "Please report to QIDISlicer team in which scenario this issue happened.\n" + "Thank you.", names)).ShowModal(); + } + if (is_windows10() && !err_objects_idxs.empty()) { auto dlg = WarningDialog(plater, _L("Open edges or errors were detected after the cut.\n" "Do you want to fix them by Windows repair algorithm?"), @@ -3351,6 +3372,7 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects) } } } + void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id) { for (ModelObject* obj : model.objects) @@ -3415,7 +3437,9 @@ void GLGizmoCut3D::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_rotation_m) : cut.perform_with_plane(); + check_objects_after_cut(new_objects); + // save cut_id to post update synchronization const CutObjectBase cut_id = cut_mo->cut_id; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 675eb06..926789b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -202,7 +202,7 @@ class GLGizmoCut3D : public GLGizmoBase PartSelection m_part_selection; bool m_show_shortcuts{ false }; - std::vector> m_shortcuts; + std::vector> m_shortcuts; enum class CutMode { cutPlanar @@ -234,7 +234,7 @@ class GLGizmoCut3D : public GLGizmoBase std::vector m_axis_names; - std::map m_part_orientation_names; + std::map m_part_orientation_names; std::map m_labels_map; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 7eb7702..1c87719 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -80,29 +80,26 @@ const std::string rotation_snapshot_name = L("Text rotate"); // TRN - Title in Undo/Redo stack after move with text along emboss axe - From surface const std::string move_snapshot_name = L("Text move"); // NOTE: Translation is made in "m_parent.do_translate()" + template struct Limit { // Limitation for view slider range in GUI MinMax gui; // Real limits for setting exacts values MinMax values; }; - // Variable keep limits for variables static const struct Limits { MinMax emboss{0.01, 1e4}; // in mm MinMax size_in_mm{0.1f, 1000.f}; // in mm - Limit boldness{{-.5f, .5f}, {-5e5f, 5e5f}}; // in font points + Limit boldness{{-.1f, .1f}, {-5e5f, 5e5f}}; // in font points Limit skew{{-1.f, 1.f}, {-100.f, 100.f}}; // ration without unit MinMax char_gap{-20000, 20000}; // in font points MinMax line_gap{-20000, 20000}; // in font points // distance text object from surface MinMax angle{-180.f, 180.f}; // in degrees - - } limits; - /// /// Prepare data for emboss /// @@ -115,12 +112,11 @@ static const struct Limits /// Base data for emboss text std::unique_ptr create_emboss_data_base( const std::string& text, - StyleManager &style_manager, - TextLinesModel &text_lines, - const Selection &selection, - ModelVolumeType type, - std::shared_ptr> &cancel); -CreateVolumeParams create_input(GLCanvas3D &canvas, const StyleManager::Style &style, RaycastManager &raycaster, ModelVolumeType volume_type); + StyleManager& style_manager, + TextLinesModel& text_lines, + const Selection& selection, + ModelVolumeType type, + std::shared_ptr>& cancel); /// /// Move window for edit emboss text near to embossed object @@ -134,7 +130,22 @@ struct TextDataBase : public DataBase TextConfiguration &&text_configuration, const EmbossProjection& projection); // Create shape from text + font configuration EmbossShape &create_shape() override; + + /// + /// Write text property into volume + /// + /// Place to write Text emboss data void write(ModelVolume &volume) const override; + + /// + /// Used only with text for embossing per glyph. + /// Create text lines only for new added volume to object + /// otherwise textline is already setted before + /// + /// Embossed volume final transformation in object + /// Volumes to be sliced to text lines + /// True on succes otherwise False(Per glyph shoud be disabled) + bool create_text_lines(const Transform3d &tr, const ModelVolumePtrs &vols) override; private: // Keep pointer on Data of font (glyph shapes) @@ -352,10 +363,8 @@ bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous if (!init_create(volume_type)) return false; - // NOTE: change style manager - be carefull with order changes - DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), volume_type, m_job_cancel); - CreateVolumeParams input = create_input(m_parent, m_style_manager.get_style(), m_raycast_manager, volume_type); - return start_create_volume(input, std::move(base), mouse_pos); + CreateVolumeParams input = create_input(volume_type); + return start_create_volume(input, mouse_pos); } // Designed for create volume without information of mouse in scene @@ -364,12 +373,23 @@ bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type) if (!init_create(volume_type)) return false; - // NOTE: change style manager - be carefull with order changes - DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), volume_type, m_job_cancel); - CreateVolumeParams input = create_input(m_parent, m_style_manager.get_style(), m_raycast_manager, volume_type); - return start_create_volume_without_position(input, std::move(base)); - } + CreateVolumeParams input = create_input(volume_type); + return start_create_volume_without_position(input); +} +CreateVolumeParams GLGizmoEmboss::create_input(ModelVolumeType volume_type) +{ + // NOTE: change style manager - be carefull with order changes + DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, + m_parent.get_selection(), volume_type, m_job_cancel); + + const StyleManager::Style &style = m_style_manager.get_style(); + auto gizmo = static_cast(GLGizmosManager::Emboss); + const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); + Plater *plater = wxGetApp().plater(); + return CreateVolumeParams{std::move(base), m_parent, plater->get_camera(), plater->build_volume(), + plater->get_ui_job_worker(), volume_type, m_raycast_manager, gizmo, gl_volume, style.distance, style.angle}; +} void GLGizmoEmboss::on_shortcut_key() { set_volume_by_selection(); @@ -381,7 +401,7 @@ void GLGizmoEmboss::on_shortcut_key() { } else { // shortcut is pressed when text is selected soo start edit it. - auto &mng = m_parent.get_gizmos_manager(); + auto& mng = m_parent.get_gizmos_manager(); if (mng.get_current_type() != GLGizmosManager::Emboss) mng.open_gizmo(GLGizmosManager::Emboss); } @@ -411,7 +431,9 @@ bool GLGizmoEmboss::re_emboss(const ModelVolume &text_volume, std::shared_ptrvolumes; @@ -466,15 +488,16 @@ bool GLGizmoEmboss::do_mirror(size_t axis) Vec3d scale = Vec3d::Ones(); scale[axis] = -1.; - Transform3d tr = m_volume->get_matrix(); + + Transform3d tr = m_volume->get_matrix(); if (es.has_value()) { const std::optional &fix_tr = es->fix_3mf_tr; if (fix_tr.has_value()) tr = tr * (fix_tr->inverse()); } - // mirror - tr = tr * Eigen::Scaling(scale); + // mirror + tr = tr * Eigen::Scaling(scale); if (is_per_glyph) { // init textlines before mirroring on mirrored text volume transformation @@ -509,22 +532,25 @@ bool GLGizmoEmboss::init_create(ModelVolumeType volume_type) BOOST_LOG_TRIVIAL(error) << "Can't create text. Gizmo is not activabled."; return false; } - + // Check can't be inside is_activable() cause crash // steps to reproduce: start App -> key 't' -> key 'delete' if (wxGetApp().obj_list()->has_selected_cut_object()) { BOOST_LOG_TRIVIAL(error) << "Can't create text on cut object"; - return false; + return false; } m_style_manager.discard_style_changes(); + // It should be already free when create new volume + assert(!m_text_lines.is_init()); + m_text_lines.reset(); // remove not current text lines + // set default text m_text = _u8L("Embossed text"); return true; } - bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) { if (mouse_event.Moving()) return false; @@ -572,14 +598,13 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) calculate_scale(); // Recalculate angle for GUI - if (!m_keep_up) { + if (!m_keep_up) { const Selection &selection = m_parent.get_selection(); const GLVolume *gl_volume = get_selected_gl_volume(selection); assert(gl_volume != nullptr); assert(m_style_manager.is_active_font()); if (gl_volume == nullptr || !m_style_manager.is_active_font()) return res; - m_style_manager.get_style().angle = calc_angle(selection); } @@ -721,6 +746,7 @@ void GLGizmoEmboss::volume_transformation_changed() init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size()); bool use_surface = es.projection.use_surface; + // Update surface by new position if (use_surface || per_glyph) process(); @@ -739,6 +765,7 @@ bool GLGizmoEmboss::wants_enter_leave_snapshots() const { return true; } std::string GLGizmoEmboss::get_gizmo_entering_text() const { return _u8L("Enter emboss gizmo"); } std::string GLGizmoEmboss::get_gizmo_leaving_text() const { return _u8L("Leave emboss gizmo"); } std::string GLGizmoEmboss::get_action_snapshot_name() const { return _u8L("Embossing actions"); } + bool GLGizmoEmboss::on_init() { m_rotate_gizmo.init(); @@ -850,7 +877,7 @@ static void draw_mouse_offset(const std::optional &offset) ImVec2 p1 = ImGui::GetMousePos(); ImVec2 p2(p1.x + offset->x(), p1.y + offset->y()); //B18 - ImU32 color = ImGui::GetColorU32(ImGuiWrapper::COL_BLUE_LIGHT); + ImU32 color = ImGui::GetColorU32(ImGuiPureWrap::COL_BLUE_LIGHT); float thickness = 3.f; draw_list->AddLine(p1, p2, color, thickness); } @@ -885,6 +912,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) ::GuiCfg cfg = create_gui_configuration(); cfg.screen_scale = screen_scale; cfg.main_toolbar_height = main_toolbar_height; + GuiCfg gui_cfg{std::move(cfg)}; m_gui_cfg = std::make_unique(std::move(gui_cfg)); // set position near toolbar @@ -910,7 +938,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) ImVec4(1.f, .3f, .3f, .75f) ); // Warning color const float radius = 16.f; - ImGuiWrapper::draw_cross_hair(center, radius, color); + ImGuiPureWrap::draw_cross_hair(center, radius, color); } #ifdef SHOW_FINE_POSITION @@ -960,7 +988,6 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) close(); } - void GLGizmoEmboss::on_set_state() { // enable / disable bed from picking @@ -988,11 +1015,10 @@ void GLGizmoEmboss::on_set_state() // change position of just opened emboss window if (m_allow_open_near_volume) { m_set_window_offset = calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); - } else { + } else { m_set_window_offset = (m_gui_cfg != nullptr) ? - ImGuiWrapper::change_window_position(on_get_name().c_str(), false) : ImVec2(-1, -1); + ImGuiPureWrap::change_window_position(on_get_name().c_str(), false) : ImVec2(-1, -1); } - } } @@ -1013,15 +1039,11 @@ void GLGizmoEmboss::on_stop_dragging() // apply rotation m_parent.do_rotate(rotation_snapshot_name); - - m_rotate_start_angle.reset(); - volume_transformation_changed(); } void GLGizmoEmboss::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); } - EmbossStyles GLGizmoEmboss::create_default_styles() { wxFontEnumerator::InvalidateCache(); @@ -1091,7 +1113,8 @@ EmbossStyles GLGizmoEmboss::create_default_styles() return styles; } -namespace{ +namespace { + /// /// Check installed fonts whether optional face name exist in installed fonts /// @@ -1136,8 +1159,13 @@ std::optional get_installed_face_name(const std::optional } return {}; // not installed } + void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines) -{ +{ + text_lines.reset(); + if (selection.is_empty()) + return; + const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) return; @@ -1147,6 +1175,7 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* if (mv_ptr == nullptr) return; const ModelVolume &mv = *mv_ptr; + if (mv.is_the_only_one_part()) return; @@ -1154,6 +1183,7 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* if (!es_opt.has_value()) return; const EmbossShape &es = *es_opt; + const std::optional &tc_opt = mv.text_configuration; if (!tc_opt.has_value()) return; @@ -1175,8 +1205,6 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* mv_trafo = mv_trafo * (es.fix_3mf_tr->inverse()); text_lines.init(mv_trafo, volumes, style_manager, count_lines); } - - } void GLGizmoEmboss::reinit_text_lines(unsigned count_lines) { @@ -1186,7 +1214,7 @@ void GLGizmoEmboss::reinit_text_lines(unsigned count_lines) { void GLGizmoEmboss::set_volume_by_selection() { const Selection &selection = m_parent.get_selection(); - const GLVolume *gl_volume = get_selected_gl_volume(selection); + const GLVolume *gl_volume = get_selected_gl_volume(selection); if (gl_volume == nullptr) return reset_volume(); @@ -1206,17 +1234,18 @@ void GLGizmoEmboss::set_volume_by_selection() // 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(); + ImGuiPureWrap::left_inputs(); // Is selected volume text volume? - const std::optional& tc_opt = volume->text_configuration; - if (!tc_opt.has_value()) + const std::optional &tc_opt = volume->text_configuration; + if (!tc_opt.has_value()) return reset_volume(); // Emboss shape must be setted assert(volume->emboss_shape.has_value()); if (!volume->emboss_shape.has_value()) return; + const TextConfiguration &tc = *tc_opt; const EmbossStyle &style = tc.style; @@ -1225,7 +1254,7 @@ void GLGizmoEmboss::set_volume_by_selection() wxFont wx_font; // load wxFont from same OS when font name is installed if (style.type == WxFontUtils::get_current_type() && installed_name.has_value()) - wx_font = WxFontUtils::load_wxFont(style.path); + wx_font = WxFontUtils::load_wxFont(style.path); // Flag that is selected same font bool is_exact_font = true; @@ -1244,7 +1273,7 @@ void GLGizmoEmboss::set_volume_by_selection() assert(wx_font.IsOk()); // Load style to style manager - const auto& styles = m_style_manager.get_styles(); + const auto &styles = m_style_manager.get_styles(); auto has_same_name = [&name = style.name](const StyleManager::Style &style_item) { return style_item.name == name; }; StyleManager::Style style_{style}; // copy @@ -1310,6 +1339,7 @@ void GLGizmoEmboss::reset_volume() m_volume = nullptr; m_volume_id.id = 0; + m_text_lines.reset(); // No more need of current notification remove_notification_not_valid_font(); @@ -1318,7 +1348,7 @@ void GLGizmoEmboss::reset_volume() void GLGizmoEmboss::calculate_scale() { Transform3d to_world = m_parent.get_selection().get_first_volume()->world_matrix(); auto to_world_linear = to_world.linear(); - auto calc = [&to_world_linear](const Vec3d &axe, std::optional& scale)->bool { + 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.)) { @@ -1333,6 +1363,7 @@ void GLGizmoEmboss::calculate_scale() { }; bool exist_change = calc(Vec3d::UnitY(), m_scale_height); + exist_change |= calc(Vec3d::UnitX(), m_scale_width); exist_change |= calc(Vec3d::UnitZ(), m_scale_depth); // Change of scale has to change font imgui font size @@ -1344,8 +1375,7 @@ namespace { bool is_text_empty(std::string_view text) { return text.empty() || text.find_first_not_of(" \n\t\r") == std::string::npos; } } // namespace -bool GLGizmoEmboss::process(bool make_snapshot) -{ +bool GLGizmoEmboss::process(bool make_snapshot, std::optional volume_transformation) { // no volume is selected -> selection from right panel assert(m_volume != nullptr); if (m_volume == nullptr) return false; @@ -1355,17 +1385,24 @@ bool GLGizmoEmboss::process(bool make_snapshot) // exist loaded font file? if (!m_style_manager.is_active_font()) return false; - + const Selection& selection = m_parent.get_selection(); DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, selection, m_volume->type(), m_job_cancel); - DataUpdate data{std::move(base), m_volume->id(), make_snapshot}; + DataUpdate data{std::move(base), m_volume->id(), make_snapshot, volume_transformation}; // check valid count of text lines assert(data.base->text_lines.empty() || data.base->text_lines.size() == get_count_lines(m_text)); if (!start_update_volume(std::move(data), *m_volume, selection, m_raycast_manager)) - return false; + return false; + wxGetApp().plater()->clear_before_change_volume(*m_volume, _u8L("Custom supports, seams and multimaterial painting were " + "removed after embossed text changed.")); + + // When we changed geometry and removed custom supports, seams, and multimaterial painting + // we have to update info about the object to remove information about custom supports, + // seams, and multimaterial painting from the right panel. + wxGetApp().obj_list()->update_info_items(selection.get_object_idx()); // notification is removed befor object is changed by job remove_notification_not_valid_font(); @@ -1374,18 +1411,38 @@ bool GLGizmoEmboss::process(bool make_snapshot) void GLGizmoEmboss::close() { - // remove volume when text is empty if (m_volume != nullptr && - m_volume->text_configuration.has_value() && - is_text_empty(m_text)) { - Plater &p = *wxGetApp().plater(); - // is the text object? - if (m_volume->is_the_only_one_part()) { - // delete whole object - p.remove(m_parent.get_selection().get_object_idx()); - } else { - // delete text volume - p.remove_selected(); + m_volume->text_configuration.has_value() ){ + + // remove volume when text is empty + if (is_text_empty(m_text)) { + Plater &p = *wxGetApp().plater(); + // is the text object? + if (m_volume->is_the_only_one_part()) { + // delete whole object + p.remove(m_parent.get_selection().get_object_idx()); + } else { + // delete text volume + p.remove_selected(); + } + } + + // Fix phanthom transformation + // appear when right click into scene during edit Rotation in input (click "Edit" button) + const GLVolume *gl_volume_ptr = m_parent.get_selection().get_first_volume(); + if (gl_volume_ptr != nullptr) { + const Transform3d &v_tr = m_volume->get_matrix(); + const Transform3d &gl_v_tr = gl_volume_ptr->get_volume_transformation().get_matrix(); + + const Matrix3d &v_rot = v_tr.linear(); + const Matrix3d &gl_v_rot = gl_v_tr.linear(); + const Vec3d &v_move = v_tr.translation(); + const Vec3d &gl_v_move = gl_v_tr.translation(); + if (!is_approx(v_rot, gl_v_rot)) { + m_parent.do_rotate(rotation_snapshot_name); + } else if (!is_approx(v_move, gl_v_move)){ + m_parent.do_move(move_snapshot_name); + } } } @@ -1447,7 +1504,7 @@ void GLGizmoEmboss::draw_window() #ifdef SHOW_WX_FONT_DESCRIPTOR if (is_selected_style) - m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, m_style_manager.get_style().path); + m_imgui->text_colored(ImGuiPureWrap::COL_GREY_DARK, m_style_manager.get_style().path); #endif // SHOW_WX_FONT_DESCRIPTOR #ifdef SHOW_CONTAIN_3MF_FIX @@ -1455,7 +1512,7 @@ void GLGizmoEmboss::draw_window() m_volume->text_configuration.has_value() && m_volume->text_configuration->fix_3mf_tr.has_value()) { ImGui::SameLine(); - m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ".3mf"); + m_imgui->text_colored(ImGuiPureWrap::COL_GREY_DARK, ".3mf"); if (ImGui::IsItemHovered()) { Transform3d &fix = *m_volume->text_configuration->fix_3mf_tr; std::stringstream ss; @@ -1533,7 +1590,6 @@ void GLGizmoEmboss::draw_text_input() warning_tool_tip += "\n"; warning_tool_tip += t; }; - if (is_text_empty(m_text)) append_warning(_u8L("Embossed text cannot contain only white spaces.")); if (m_text_contain_unknown_glyph) @@ -1579,7 +1635,6 @@ void GLGizmoEmboss::draw_text_input() if (!warning_tool_tip.empty()) { // Multiline input has hidden window for scrolling const ImGuiWindow *input = ImGui::GetCurrentWindow()->DC.ChildWindows.front(); - const ImGuiStyle &style = ImGui::GetStyle(); float scrollbar_width = (input->ScrollbarY) ? style.ScrollbarSize : 0.f; float scrollbar_height = (input->ScrollbarX) ? style.ScrollbarSize : 0.f; @@ -1603,11 +1658,10 @@ void GLGizmoEmboss::draw_text_input() // IMPROVE: only extend not clear // Extend font ranges if (!range_text.empty() && - !ImGuiWrapper::contain_all_glyphs(imgui_font, range_text) ) + !ImGuiPureWrap::contain_all_glyphs(imgui_font, range_text) ) m_style_manager.clear_imgui_font(); } - // create texture for visualization font face void GLGizmoEmboss::init_font_name_texture() { Timer t("init_font_name_texture"); @@ -1664,9 +1718,9 @@ void GLGizmoEmboss::draw_font_list_line() const std::string& font_text = m_gui_cfg->translations.font; if (exist_change_in_font || !exist_stored_style) //B18 - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, font_text); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, font_text); else - ImGuiWrapper::text(font_text); + ImGuiPureWrap::text(font_text); ImGui::SameLine(m_gui_cfg->input_offset); @@ -1840,7 +1894,7 @@ void GLGizmoEmboss::draw_font_list() m_face_names->texture_id = 0; // Remove value from search input - ImGuiWrapper::left_inputs(); + ImGuiPureWrap::left_inputs(); m_face_names->search.clear(); } @@ -1885,7 +1939,7 @@ void GLGizmoEmboss::draw_model_type() 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()); + ImGuiPureWrap::text_colored(color, title.c_str()); } else { ImGui::Text("%s", title.c_str()); } @@ -1960,15 +2014,16 @@ void GLGizmoEmboss::draw_style_rename_popup() { const std::string &old_name = m_style_manager.get_stored_style()->name; std::string text_in_popup = GUI::format(_L("Rename style(%1%) for embossing text"), old_name) + ": "; ImGui::Text("%s", text_in_popup.c_str()); - + bool is_unique = (new_name == old_name) || // could be same as before rename m_style_manager.is_unique_style_name(new_name); + bool allow_change = false; //B18 if (new_name.empty()) { - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Name can't be empty.")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Name can't be empty.")); }else if (!is_unique) { - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Name has to be unique.")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Name has to be unique.")); } else { ImGui::NewLine(); allow_change = true; @@ -1977,7 +2032,7 @@ void GLGizmoEmboss::draw_style_rename_popup() { bool store = false; ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue; if (ImGui::InputText("##rename style", &new_name, flags) && allow_change) store = true; - if (m_imgui->button(_L("OK"), ImVec2(0.f, 0.f), allow_change)) store = true; + if (m_imgui->button(_u8L("OK"), ImVec2(0.f, 0.f), allow_change)) store = true; ImGui::SameLine(); if (ImGui::Button(_u8L("Cancel").c_str())) { new_name = old_name; @@ -2015,7 +2070,7 @@ void GLGizmoEmboss::draw_style_rename_button() else ImGui::SetTooltip("%s", _u8L("Can't rename temporary style.").c_str()); } if (ImGui::BeginPopupModal(popup_id, 0, ImGuiWindowFlags_AlwaysAutoResize)) { - m_imgui->disable_background_fadeout_animation(); + ImGuiPureWrap::disable_background_fadeout_animation(); draw_style_rename_popup(); ImGui::EndPopup(); } @@ -2044,15 +2099,13 @@ void GLGizmoEmboss::draw_style_save_as_popup() { // use name inside of volume configuration as temporary new name std::string &new_name = m_volume->text_configuration->style.name; - bool is_unique = m_style_manager.is_unique_style_name(new_name); - bool allow_change = false; //B18 if (new_name.empty()) { - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Name can't be empty.")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Name can't be empty.")); }else if (!is_unique) { - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Name has to be unique.")); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Name has to be unique.")); } else { ImGui::NewLine(); allow_change = true; @@ -2063,7 +2116,7 @@ void GLGizmoEmboss::draw_style_save_as_popup() { if (ImGui::InputText("##save as style", &new_name, flags)) save_style = true; - if (m_imgui->button(_L("OK"), ImVec2(0.f, 0.f), allow_change)) + if (m_imgui->button(_u8L("OK"), ImVec2(0.f, 0.f), allow_change)) save_style = true; ImGui::SameLine(); @@ -2094,7 +2147,7 @@ void GLGizmoEmboss::draw_style_add_button() ImGui::SameLine(); if (draw_button(m_icons, IconType::add, !can_add)) { if (!m_style_manager.exist_stored_style()) { - m_style_manager.store_styles_to_app_config(wxGetApp().app_config); + m_style_manager.store_styles_to_app_config(); } else { ImGui::OpenPopup(popup_id); } @@ -2109,7 +2162,7 @@ void GLGizmoEmboss::draw_style_add_button() } if (ImGui::BeginPopupModal(popup_id, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - m_imgui->disable_background_fadeout_animation(); + ImGuiPureWrap::disable_background_fadeout_animation(); draw_style_save_as_popup(); ImGui::EndPopup(); } @@ -2160,7 +2213,7 @@ void GLGizmoEmboss::draw_delete_style_button() { break; } if (exist_change) - m_style_manager.store_styles_to_app_config(wxGetApp().app_config); + m_style_manager.store_styles_to_app_config(false); } if (ImGui::IsItemHovered()) { @@ -2174,8 +2227,11 @@ void GLGizmoEmboss::draw_delete_style_button() { } namespace { -// FIX IT: It should not change volume position before successfull change volume by process -void fix_transformation(const StyleManager::Style &from, const StyleManager::Style &to, GLCanvas3D &canvas) { +// When during change style is need to change transformation it calculate it +std::optional fix_transformation( + const StyleManager::Style &from, const StyleManager::Style &to, GLCanvas3D &canvas +) { + bool exist_transformation = false; // fix Z rotation when exists difference in styles const std::optional &f_angle_opt = from.angle; const std::optional &t_angle_opt = to.angle; @@ -2183,9 +2239,9 @@ void fix_transformation(const StyleManager::Style &from, const StyleManager::Sty // fix rotation float f_angle = f_angle_opt.value_or(.0f); float t_angle = t_angle_opt.value_or(.0f); + // Rotate only UI do_local_z_rotate(canvas.get_selection(), t_angle - f_angle); - std::string no_snapshot; - canvas.do_rotate(no_snapshot); + exist_transformation = true; } // fix distance (Z move) when exists difference in styles @@ -2194,10 +2250,33 @@ void fix_transformation(const StyleManager::Style &from, const StyleManager::Sty if (!is_approx(f_move_opt, t_move_opt)) { float f_move = f_move_opt.value_or(.0f); float t_move = t_move_opt.value_or(.0f); + // Move only UI do_local_z_move(canvas.get_selection(), t_move - f_move); - std::string no_snapshot; - canvas.do_move(no_snapshot); + exist_transformation = true; } + + if (!exist_transformation) + return std::nullopt; + + const GLVolume* gl_volume = canvas.get_selection().get_first_volume(); + assert(gl_volume != nullptr); + if (gl_volume == nullptr) + return std::nullopt; + + const Transform3d &tr = gl_volume->get_volume_transformation().get_matrix(); + + // exist fix matrix made by store to .3mf + const ModelVolume* volume = get_model_volume(*gl_volume, canvas.get_model()->objects); + assert(volume != nullptr); + if (volume == nullptr) + return tr; + 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()) + return tr * emboss_shape->fix_3mf_tr->inverse(); + + // no fix matrix just return transformation + return tr; } } // namesapce @@ -2217,8 +2296,8 @@ void GLGizmoEmboss::draw_style_list() { if (trunc_name.empty()) { // generate trunc name std::string current_name = current_style.name; - ImGuiWrapper::escape_double_hash(current_name); - trunc_name = ImGuiWrapper::trunc(current_name, max_style_name_width); + ImGuiPureWrap::escape_double_hash(current_name); + trunc_name = ImGuiPureWrap::trunc(current_name, max_style_name_width); } std::string title = _u8L("Style"); @@ -2226,7 +2305,7 @@ void GLGizmoEmboss::draw_style_list() { ImGui::Text("%s", title.c_str()); else //B18 - ImGui::TextColored(ImGuiWrapper::COL_BLUE_LIGHT, "%s", title.c_str()); + ImGui::TextColored(ImGuiPureWrap::COL_BLUE_LIGHT, "%s", title.c_str()); ImGui::SetNextItemWidth(m_gui_cfg->input_width); auto add_text_modify = [&is_modified](const std::string& name) { @@ -2306,8 +2385,7 @@ void GLGizmoEmboss::draw_style_list() { StyleManager::Style cur_s = current_style; // copy StyleManager::Style new_s = style; // copy if (m_style_manager.load_style(*selected_style_index)) { - ::fix_transformation(cur_s, new_s, m_parent); - process(); + process(true, ::fix_transformation(cur_s, new_s, m_parent)); } else { wxString title = _L("Not valid style."); wxString message = GUI::format_wxstr(_L("Style \"%1%\" can't be used and will be removed from a list."), style.name); @@ -2451,9 +2529,9 @@ bool GLGizmoEmboss::revertible(const std::string &name, bool changed = exist_change(value, default_value); if (changed || default_value == nullptr) //B18 - ImGuiWrapper::text_colored(ImGuiWrapper::COL_BLUE_LIGHT, name); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, name); else - ImGuiWrapper::text(name); + ImGuiPureWrap::text(name); // render revert changes button if (changed) { @@ -2462,8 +2540,10 @@ bool GLGizmoEmboss::revertible(const std::string &name, ImGui::SameLine(undo_offset); // change cursor postion if (draw_button(m_icons, IconType::undo)) { value = *default_value; + // !! Fix to detect change of value after revert of float-slider m_imgui->get_last_slider_status().deactivated_after_edit = true; + return true; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", undo_tooltip.c_str()); @@ -2471,7 +2551,6 @@ bool GLGizmoEmboss::revertible(const std::string &name, } return draw(); } - // May be move to ImGuiWrapper template bool imgui_input(const char *label, T *v, T step, T step_fast, const char *format, ImGuiInputTextFlags flags); template<> bool imgui_input(const char *label, float *v, float step, float step_fast, const char *format, ImGuiInputTextFlags flags) @@ -2496,14 +2575,14 @@ bool GLGizmoEmboss::rev_input(const std::string &name, T &value, const T *defaul } template -bool GLGizmoEmboss::rev_input_mm(const std::string &name, +bool GLGizmoEmboss::rev_input_mm(const std::string &name, T &value, const T *default_value_ptr, - const std::string &undo_tooltip, + const std::string &undo_tooltip, T step, T step_fast, - const char *format, - bool use_inch, + const char *format, + bool use_inch, const std::optional &scale) const { // _variable which temporary keep value @@ -2591,7 +2670,6 @@ void GLGizmoEmboss::draw_height(bool use_inch) process(); } - void GLGizmoEmboss::draw_depth(bool use_inch) { double &value = m_style_manager.get_style().projection.depth; @@ -2606,38 +2684,15 @@ void GLGizmoEmboss::draw_depth(bool use_inch) // only different value need process if(!is_approx(value, m_volume->emboss_shape->projection.depth)) - process(); -} -} - -bool GLGizmoEmboss::rev_slider(const std::string &name, - std::optional& value, - const std::optional *default_value, - const std::string &undo_tooltip, - int v_min, - int v_max, - const std::string& format, - const wxString &tooltip) const -{ - auto draw_slider_optional_int = [&]() -> bool { - float slider_offset = m_gui_cfg->advanced_input_offset; - float slider_width = m_gui_cfg->input_width; - ImGui::SameLine(slider_offset); - ImGui::SetNextItemWidth(slider_width); - return m_imgui->slider_optional_int( ("##" + name).c_str(), value, - v_min, v_max, format.c_str(), 1.f, false, tooltip); - }; - float undo_offset = ImGui::GetStyle().FramePadding.x; - return revertible(name, value, default_value, - undo_tooltip, undo_offset, draw_slider_optional_int); + process(); + } } bool GLGizmoEmboss::rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, const std::string &undo_tooltip, - float v_min, - float v_max, + const MinMax &min_max, const std::string& format, const wxString &tooltip) const { @@ -2647,7 +2702,7 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, ImGui::SameLine(slider_offset); ImGui::SetNextItemWidth(slider_width); return m_imgui->slider_optional_float(("##" + name).c_str(), value, - v_min, v_max, format.c_str(), 1.f, false, tooltip); + min_max.min, min_max.max, format.c_str(), 1.f, false, tooltip); }; float undo_offset = ImGui::GetStyle().FramePadding.x; return revertible(name, value, default_value, @@ -2658,8 +2713,7 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, float &value, const float *default_value, const std::string &undo_tooltip, - float v_min, - float v_max, + const MinMax &min_max, const std::string &format, const wxString &tooltip) const { @@ -2668,7 +2722,7 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, float slider_width = m_gui_cfg->input_width; ImGui::SameLine(slider_offset); ImGui::SetNextItemWidth(slider_width); - return m_imgui->slider_float("##" + name, &value, v_min, v_max, + return m_imgui->slider_float("##" + name, &value, min_max.min, min_max.max, format.c_str(), 1.f, false, tooltip); }; float undo_offset = ImGui::GetStyle().FramePadding.x; @@ -2676,7 +2730,6 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, undo_tooltip, undo_offset, draw_slider_float); } - void GLGizmoEmboss::draw_advanced() { const auto &ff = m_style_manager.get_font_file_with_cache(); @@ -2701,301 +2754,37 @@ void GLGizmoEmboss::draw_advanced() unsigned int collection = current_prop.collection_number.value_or(0); ff_property += ", collect=" + std::to_string(collection+1) + "/" + std::to_string(font_file->infos.size()); } - m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ff_property); + m_imgui->text_colored(ImGuiPureWrap::COL_GREY_DARK, ff_property); #endif // SHOW_FONT_FILE_PROPERTY - auto &tr = m_gui_cfg->translations; - - const StyleManager::Style *stored_style = nullptr; - if (m_style_manager.exist_stored_style()) - stored_style = m_style_manager.get_stored_style(); - - bool is_the_only_one_part = m_volume->is_the_only_one_part(); - bool can_use_surface = (m_volume->emboss_shape->projection.use_surface)? true : // already used surface must have option to uncheck - !is_the_only_one_part; - m_imgui->disabled_begin(!can_use_surface); - const bool *def_use_surface = stored_style ? - &stored_style->projection.use_surface : nullptr; - StyleManager::Style ¤t_style = m_style_manager.get_style(); - bool &use_surface = current_style.projection.use_surface; - if (rev_checkbox(tr.use_surface, use_surface, def_use_surface, - _u8L("Revert using of model surface."))) { - if (use_surface) - // when using surface distance is not used - current_style.distance.reset(); - process(); - } - m_imgui->disabled_end(); // !can_use_surface - - bool &per_glyph = font_prop.per_glyph; - bool can_use_per_glyph = (per_glyph) ? true : // already used surface must have option to uncheck - !is_the_only_one_part; - m_imgui->disabled_begin(!can_use_per_glyph); - const bool *def_per_glyph = stored_style ? &stored_style->prop.per_glyph : nullptr; - if (rev_checkbox(tr.per_glyph, per_glyph, def_per_glyph, - _u8L("Revert Transformation per glyph."))) { - if (per_glyph && !m_text_lines.is_init()) - reinit_text_lines(); - process(); - } else if (ImGui::IsItemHovered()) { - if (per_glyph) { - ImGui::SetTooltip("%s", _u8L("Set global orientation for whole text.").c_str()); - } else { - ImGui::SetTooltip("%s", _u8L("Set position and orientation per glyph.").c_str()); - if (!m_text_lines.is_init()) - reinit_text_lines(); - } - } else if (!per_glyph && m_text_lines.is_init()) - m_text_lines.reset(); - m_imgui->disabled_end(); // !can_use_per_glyph - - auto draw_align = [&align = font_prop.align, input_offset = m_gui_cfg->advanced_input_offset, &icons = m_icons]() { - bool is_change = false; - ImGui::SameLine(input_offset); - if (align.first==FontProp::HorizontalAlign::left) draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_left)) { align.first=FontProp::HorizontalAlign::left; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Left", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.first==FontProp::HorizontalAlign::center) draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_center)) { align.first=FontProp::HorizontalAlign::center; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Center", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.first==FontProp::HorizontalAlign::right) draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_right)) { align.first=FontProp::HorizontalAlign::right; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Right", "Alignment"), "Alignment").c_str()); - - ImGui::SameLine(); - if (align.second==FontProp::VerticalAlign::top) draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_top)) { align.second=FontProp::VerticalAlign::top; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Top", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.second==FontProp::VerticalAlign::center) draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_center)) { align.second=FontProp::VerticalAlign::center; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Middle", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.second==FontProp::VerticalAlign::bottom) draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_bottom)) { align.second=FontProp::VerticalAlign::bottom; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Bottom", "Alignment"), "Alignment").c_str()); - return is_change; - }; - const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr; - float undo_offset = ImGui::GetStyle().FramePadding.x; - if (revertible(tr.alignment, font_prop.align, def_align, _u8L("Revert alignment."), undo_offset, draw_align)) { - if (font_prop.per_glyph) - reinit_text_lines(m_text_lines.get_lines().size()); - // TODO: move with text in finalize to not change position - process(); - } - - // TRN EmbossGizmo: font units - std::string units = _u8L("points"); - std::string units_fmt = "%.0f " + units; - - // input gap between characters - auto def_char_gap = stored_style ? - &stored_style->prop.char_gap : nullptr; - - bool exist_change = false; - int half_ascent = font_info.ascent / 2; - int min_char_gap = -half_ascent; - int max_char_gap = half_ascent; - FontProp ¤t_prop = current_style.prop; - if (rev_slider(tr.char_gap, current_prop.char_gap, def_char_gap, _u8L("Revert gap between characters"), - min_char_gap, max_char_gap, units_fmt, _L("Distance between characters"))){ - // Condition prevent recalculation when insertint out of limits value by imgui input - const std::optional &volume_char_gap = m_volume->text_configuration->style.prop.char_gap; - if (!apply(current_prop.char_gap, limits.char_gap) || - !volume_char_gap.has_value() || volume_char_gap != current_prop.char_gap) { - // char gap is stored inside of imgui font atlas - m_style_manager.clear_imgui_font(); - exist_change = true; - } - } - bool last_change = false; - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - - // input gap between lines - bool is_multiline = get_count_lines(m_volume->text_configuration->text) > 1; // TODO: cache count lines - m_imgui->disabled_begin(!is_multiline); - auto def_line_gap = stored_style ? - &stored_style->prop.line_gap : nullptr; - int min_line_gap = -half_ascent; - int max_line_gap = half_ascent; - if (rev_slider(tr.line_gap, current_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"), - min_line_gap, max_line_gap, units_fmt, _L("Distance between lines"))){ - // Condition prevent recalculation when insertint out of limits value by imgui input - const std::optional &volume_line_gap = m_volume->text_configuration->style.prop.line_gap; - if (!apply(current_prop.line_gap, limits.line_gap) || - !volume_line_gap.has_value() || volume_line_gap != current_prop.line_gap) { - // line gap is planed to be stored inside of imgui font atlas - m_style_manager.clear_imgui_font(); - if (font_prop.per_glyph) - reinit_text_lines(m_text_lines.get_lines().size()); - exist_change = true; - } - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - m_imgui->disabled_end(); // !is_multiline - - // input boldness - auto def_boldness = stored_style ? - &stored_style->prop.boldness : nullptr; - int min_boldness = static_cast(font_info.ascent * limits.boldness.gui.min); - int max_boldness = static_cast(font_info.ascent * limits.boldness.gui.max); - if (rev_slider(tr.boldness, current_prop.boldness, def_boldness, _u8L("Undo boldness"), - min_boldness, max_boldness, units_fmt, _L("Tiny / Wide glyphs"))){ - const std::optional &volume_boldness = m_volume->text_configuration->style.prop.boldness; - if (!apply(current_prop.boldness, limits.boldness.values) || - !volume_boldness.has_value() || volume_boldness != current_prop.boldness) - exist_change = true; - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - - // input italic - auto def_skew = stored_style ? - &stored_style->prop.skew : nullptr; - if (rev_slider(tr.skew_ration, current_prop.skew, def_skew, _u8L("Undo letter's skew"), - limits.skew.gui.min, limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ - const std::optional &volume_skew = m_volume->text_configuration->style.prop.skew; - if (!apply(current_prop.skew, limits.skew.values) || - !volume_skew.has_value() ||volume_skew != current_prop.skew) - exist_change = true; - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - - // input surface distance - bool allowe_surface_distance = !use_surface && !m_volume->is_the_only_one_part(); - std::optional &distance = current_style.distance; - float prev_distance = distance.value_or(.0f); - float min_distance = static_cast(-2 * current_style.projection.depth); - float max_distance = static_cast(2 * current_style.projection.depth); - auto def_distance = stored_style ? - &stored_style->distance : nullptr; - m_imgui->disabled_begin(!allowe_surface_distance); - - bool use_inch = wxGetApp().app_config->get_bool("use_inches"); - const std::string undo_move_tooltip = _u8L("Undo translation"); - const wxString move_tooltip = _L("Distance of the center of the text to the model surface."); - bool is_moved = false; - if (use_inch) { - std::optional distance_inch; - if (distance.has_value()) distance_inch = (*distance * ObjectManipulation::mm_to_in); - std::optional def_distance_inch; - if (def_distance != nullptr) { - if (def_distance->has_value()) def_distance_inch = ObjectManipulation::mm_to_in * (*(*def_distance)); - def_distance = &def_distance_inch; - } - min_distance *= ObjectManipulation::mm_to_in; - max_distance *= ObjectManipulation::mm_to_in; - if (rev_slider(tr.from_surface, distance_inch, def_distance, undo_move_tooltip, min_distance, max_distance, "%.3f in", move_tooltip)) { - if (distance_inch.has_value()) { - distance = *distance_inch * ObjectManipulation::in_to_mm; - } else { - distance.reset(); - } - is_moved = true; - } - } else { - if (rev_slider(tr.from_surface, distance, def_distance, undo_move_tooltip, - min_distance, max_distance, "%.2f mm", move_tooltip)) is_moved = true; - } - - if (is_moved){ - if (font_prop.per_glyph){ - process(false); - } else { - do_local_z_move(m_parent.get_selection(), distance.value_or(.0f) - prev_distance); - } - } - // Apply move to model(backend) - if (m_imgui->get_last_slider_status().deactivated_after_edit) { - m_parent.do_move(move_snapshot_name); - if (font_prop.per_glyph) - process(); - } - m_imgui->disabled_end(); // allowe_surface_distance - - // 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 = current_style.angle.value_or(0.f); - float angle_deg = static_cast(-angle * 180 / M_PI); - float def_angle_deg_val = - (!stored_style || !stored_style->angle.has_value()) ? - 0.f : (*stored_style->angle * -180 / M_PI); - float* def_angle_deg = stored_style ? - &def_angle_deg_val : nullptr; - if (rev_slider(tr.rotation, angle_deg, def_angle_deg, _u8L("Undo rotation"), - limits.angle.min, limits.angle.max, u8"%.2f °", - _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 - const Selection &selection = m_parent.get_selection(); - const GLVolume *gl_volume = get_selected_gl_volume(selection); - assert(gl_volume != nullptr); - assert(m_style_manager.is_active_font()); - if (m_style_manager.is_active_font() && gl_volume != nullptr) - m_style_manager.get_style().angle = calc_angle(selection); - - if (font_prop.per_glyph) - reinit_text_lines(m_text_lines.get_lines().size()); - - // recalculate for surface cut - if (use_surface || font_prop.per_glyph) - process(false); - } - - // Apply rotation on model (backend) - if (m_imgui->get_last_slider_status().deactivated_after_edit) { - m_parent.do_rotate(rotation_snapshot_name); - - // recalculate for surface cut - if (use_surface || font_prop.per_glyph) - process(); - } - - // 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_bold : IconType::unlock_bold, IconState::activable); - const IconManager::Icon &icon_disable = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::disabled); - if (button(icon, icon_hover, icon_disable)) - m_keep_up = !m_keep_up; - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", (m_keep_up? - _u8L("Unlock the text's rotation when moving text along the object's surface."): - _u8L("Lock the text's rotation when moving text along the object's surface.") - ).c_str()); - } + draw_use_surface(); + draw_per_glyph(); + draw_align(); + draw_char_gap(); + draw_line_gap(); + draw_boldness(); + draw_skew(); + draw_surface_distance(); + draw_rotation(); // when more collection add selector if (ff.font_file->infos.size() > 1) { - ImGui::Text("%s", tr.collection.c_str()); + ImGui::Text("%s", m_gui_cfg->translations.collection.c_str()); ImGui::SameLine(m_gui_cfg->advanced_input_offset); ImGui::SetNextItemWidth(m_gui_cfg->input_width); - unsigned int selected = current_prop.collection_number.value_or(0); + std::optional &collection_number = m_style_manager.get_font_prop().collection_number; + + unsigned int selected = collection_number.value_or(0); if (ImGui::BeginCombo("## Font collection", std::to_string(selected).c_str())) { for (unsigned int i = 0; i < ff.font_file->infos.size(); ++i) { ImGui::PushID(1 << (10 + i)); bool is_selected = (i == selected); if (ImGui::Selectable(std::to_string(i).c_str(), is_selected)) { - if (i == 0) current_prop.collection_number.reset(); - else current_prop.collection_number = i; - exist_change = true; - last_change = true; + if (i == 0) collection_number.reset(); + else collection_number = i; + + m_style_manager.clear_glyphs_cache(); + process(); } ImGui::PopID(); } @@ -3005,18 +2794,9 @@ void GLGizmoEmboss::draw_advanced() } } - if (exist_change || last_change) { - m_style_manager.clear_glyphs_cache(); - if (font_prop.per_glyph) - reinit_text_lines(); - else - m_text_lines.reset(); - process(last_change); - } - if (ImGui::Button(_u8L("Set text to face camera").c_str())) { assert(get_selected_volume(m_parent.get_selection()) == m_volume); - const Camera &cam = wxGetApp().plater()->get_camera(); + 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(); @@ -3025,6 +2805,7 @@ void GLGizmoEmboss::draw_advanced() } //ImGui::SameLine(); if (ImGui::Button("Re-emboss")) GLGizmoEmboss::re_emboss(*m_volume); + #ifdef ALLOW_DEBUG_MODE ImGui::Text("family = %s", (current_prop.family.has_value() ? current_prop.family->c_str() : @@ -3044,6 +2825,471 @@ void GLGizmoEmboss::draw_advanced() #endif // ALLOW_DEBUG_MODE } +void GLGizmoEmboss::draw_use_surface(){ + bool 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(); + + m_imgui->disabled_begin(!can_use_surface); + const std::string &tr_use_surface = m_gui_cfg->translations.use_surface; + bool &use_surface = m_style_manager.get_style().projection.use_surface; + const bool *def_use_surface = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->projection.use_surface : nullptr; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert using of model surface."); + if (rev_checkbox(tr_use_surface, use_surface, def_use_surface, undo_tooltip)) { + if (use_surface) + // when using surface distance is not used + m_style_manager.get_style().distance.reset(); + process(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", _u8L("If checked,\n\ +model surface under the text's shape is shift in the embossing direction,\n\ +otherwise text is flat and you have to deal with distance from surface.").c_str()); + } + m_imgui->disabled_end(); // !can_use_surface +} + +void GLGizmoEmboss::draw_per_glyph() { + bool can_use_per_glyph = (m_volume->text_configuration->style.prop.per_glyph) ? + true : // already used surface must have option to uncheck + !m_volume->is_the_only_one_part(); + + m_imgui->disabled_begin(!can_use_per_glyph); + const std::string &tr_per_glyph = m_gui_cfg->translations.per_glyph; + bool &per_glyph = m_style_manager.get_font_prop().per_glyph; + const bool *def_per_glyph = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.per_glyph : nullptr; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert Transformation per glyph."); + if (rev_checkbox(tr_per_glyph, per_glyph, def_per_glyph, undo_tooltip)) { + if (per_glyph && !m_text_lines.is_init()) + reinit_text_lines(); // text line has to be initialized before process + process(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", _u8L("If checked,\n\ +each letter(glyph) has an independent orthogonal projection,\n\ +otherwise, the whole text has the same orthogonal projection.").c_str()); + if (!per_glyph && !m_text_lines.is_init()) + reinit_text_lines(); // create text line preview on hover + } else if (!per_glyph && m_text_lines.is_init()) + m_text_lines.reset(); // remove text line preview when not hovered + m_imgui->disabled_end(); // !can_use_per_glyph +} + +void GLGizmoEmboss::draw_align() +{ + FontProp::Align &align = m_style_manager.get_font_prop().align; + auto draw_fnc = [&align, input_offset = m_gui_cfg->advanced_input_offset, &icons = m_icons]() { + struct HAlignItem{FontProp::HorizontalAlign align; IconType icon; std::string tooltip;}; + static const std::array h_aligns{{ + {FontProp::HorizontalAlign::left, IconType::align_horizontal_left, _CTX_utf8(L_CONTEXT("Left", "Alignment"), "Alignment")}, + {FontProp::HorizontalAlign::center,IconType::align_horizontal_center,_CTX_utf8(L_CONTEXT("Center","Alignment"), "Alignment")}, + {FontProp::HorizontalAlign::right, IconType::align_horizontal_right, _CTX_utf8(L_CONTEXT("Right", "Alignment"), "Alignment")} + }}; + struct VAlignItem{FontProp::VerticalAlign align; IconType icon; std::string tooltip;}; + static const std::array v_aligns{{ + {FontProp::VerticalAlign::top, IconType::align_vertical_top, _CTX_utf8(L_CONTEXT("Top", "Alignment"), "Alignment")}, + {FontProp::VerticalAlign::center, IconType::align_vertical_center, _CTX_utf8(L_CONTEXT("Middle", "Alignment"), "Alignment")}, + {FontProp::VerticalAlign::bottom, IconType::align_vertical_bottom, _CTX_utf8(L_CONTEXT("Bottom", "Alignment"), "Alignment")} + }}; + bool is_change = false; + float offset = input_offset; + for (const HAlignItem& h_align : h_aligns){ + ImGui::SameLine(offset); offset = .0f; + if (align.first==h_align.align) draw(get_icon(icons, h_align.icon, IconState::hovered)); + else if (draw_button(icons, h_align.icon)){ align.first=h_align.align; is_change=true; } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", h_align.tooltip.c_str()); + } + + for (const VAlignItem& v_align : v_aligns){ + ImGui::SameLine(); + if (align.second==v_align.align) draw(get_icon(icons, v_align.icon, IconState::hovered)); + else if (draw_button(icons, v_align.icon)){ align.second=v_align.align; is_change=true; } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", v_align.tooltip.c_str()); + } + return is_change; + }; + + const FontProp::Align * def_align = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.align : nullptr; + float undo_offset = ImGui::GetStyle().FramePadding.x; + const std::string &tr_alignment = m_gui_cfg->translations.alignment; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert alignment."); + if (revertible(tr_alignment, align, def_align, undo_tooltip, undo_offset, draw_fnc)) { + // align change origin for per glyph slice + if (m_style_manager.get_font_prop().per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + + // TODO: move with text in finalize to not change position + + process(); + } +} + +void GLGizmoEmboss::draw_char_gap() +{ + // input gap between characters + auto def_char_gap_font_point = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.char_gap : nullptr; + + const FontFile &ff = *m_style_manager.get_font_file_with_cache().font_file; + const FontProp &fp = m_style_manager.get_font_prop(); + const FontFile::Info &font_info = get_font_info(ff, fp); + + const std::string &tr_char_gap = m_gui_cfg->translations.char_gap; + std::optional &char_gap_font_point = m_style_manager.get_font_prop().char_gap; + + double font_point_to_volume_mm = fp.size_in_mm / (double)font_info.unit_per_em; + double font_point_to_world_mm = font_point_to_volume_mm * m_scale_width.value_or(1.f); + double scale = font_point_to_world_mm; + std::string units_fmt = "%.2f mm"; + + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + if (use_inch){ + scale *= ObjectManipulation::mm_to_in; + units_fmt = "%.2f in"; + } + + float char_gap = static_cast(char_gap_font_point.value_or(0) * scale); + float def_char_gap_value = def_char_gap_font_point ? + static_cast(def_char_gap_font_point->value_or(0) * scale) : 0.f; + float *def_char_gap = def_char_gap_font_point ? &def_char_gap_value : nullptr; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert gap between characters"); + float max_char_gap = static_cast(font_info.ascent / 2. * scale); + MinMax min_max_char_gap{-max_char_gap, max_char_gap}; + // TRN - Tooltip above char gap slider + const wxString tooltip = _L("Additional distance between characters"); + bool exist_change = false; + if (rev_slider(tr_char_gap, char_gap, def_char_gap, undo_tooltip, + min_max_char_gap, units_fmt, tooltip)) { + int char_gap_font_point_new = static_cast(std::round(char_gap / scale)); + if (char_gap_font_point_new == char_gap_font_point.value_or(0)) + // appear when emboss job throw error - e.g. no volume (no symbol font) + // -> cause infinit loop call of process() + return; // no change + else if (char_gap_font_point_new == 0) + char_gap_font_point.reset(); + else + char_gap_font_point = char_gap_font_point_new; + + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_char_gap = m_volume->text_configuration->style.prop.char_gap; + apply(char_gap_font_point, limits.char_gap); + + // Condition prevent recalculation when value are same + if (volume_char_gap.value_or(0) != char_gap_font_point.value_or(0)) { + // char gap is stored inside of imgui font atlas + m_style_manager.clear_imgui_font(); + m_style_manager.clear_glyphs_cache(); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_line_gap() { + const FontFile &ff = *m_style_manager.get_font_file_with_cache().font_file; + const FontProp &fp = m_style_manager.get_font_prop(); + const FontFile::Info &font_info = get_font_info(ff, fp); + + double font_point_to_volume_mm = fp.size_in_mm / (double) font_info.unit_per_em; + double font_point_to_world_mm = font_point_to_volume_mm * m_scale_width.value_or(1.f); + double scale = font_point_to_world_mm; + std::string units_fmt = "%.2f mm"; + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + if (use_inch) { + scale *= ObjectManipulation::mm_to_in; + units_fmt = "%.2f in"; + } + + std::optional &line_gap_font_point = m_style_manager.get_font_prop().line_gap; + auto def_line_gap_font_point = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.line_gap : nullptr; + float def_line_gap_value = def_line_gap_font_point ? + static_cast(def_line_gap_font_point->value_or(0) * scale) : 0.f; + float *def_line_gap = def_line_gap_font_point ? &def_line_gap_value : nullptr; + float max_line_gap = static_cast(font_info.ascent * scale); + MinMax min_max_line_gap{-max_line_gap, max_line_gap}; + + // input gap between lines + bool is_multiline = get_count_lines(m_volume->text_configuration->text) > 1; + // TODO: cache count lines + + m_imgui->disabled_begin(!is_multiline); + + const std::string& tr_line_gap = m_gui_cfg->translations.line_gap; + float line_gap = static_cast(fp.line_gap.value_or(0) * scale); + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert gap between lines"); + // TRN - Tooltip above line gap slider + const wxString tooltip = _L("Additional distance between lines"); + + bool exist_change = false; + if (rev_slider(tr_line_gap, line_gap, def_line_gap, undo_tooltip, + min_max_line_gap, units_fmt, tooltip)) { + int line_gap_font_point_new = static_cast(std::round(line_gap / scale)); + if (line_gap_font_point_new == line_gap_font_point.value_or(0)) + // appear when emboss job throw error - e.g. no volume (no symbol font) + // -> cause infinit loop call of process() + return; // no change + else if (line_gap_font_point_new == 0) + line_gap_font_point.reset(); + else + line_gap_font_point = line_gap_font_point_new; + + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_line_gap = m_volume->text_configuration->style.prop.line_gap; + apply(line_gap_font_point, limits.line_gap); + // Condition prevent recalculation when insert same value. e.g. over limits + if (volume_line_gap.value_or(0) != line_gap_font_point.value_or(0)) { + // line gap is planed to be stored inside of imgui font atlas + m_style_manager.clear_imgui_font(); + + // different line gap need to reinitialize text lines + if (fp.per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + m_imgui->disabled_end(); // !is_multiline + + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_boldness(){ + const std::string& tr_boldness = m_gui_cfg->translations.boldness; + + const FontFile &ff = *m_style_manager.get_font_file_with_cache().font_file; + const FontProp &fp = m_style_manager.get_font_prop(); + const FontFile::Info &font_info = get_font_info(ff, fp); + + double font_point_to_volume_mm = fp.size_in_mm / (double) font_info.unit_per_em; + double font_point_to_world_mm = font_point_to_volume_mm * m_scale_width.value_or(1.f); + double scale = font_point_to_world_mm; + std::string units_fmt = "%.2f mm"; + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + if (use_inch) { + scale *= ObjectManipulation::mm_to_in; + units_fmt = "%.2f in"; + } + + std::optional &boldness_font_point = m_style_manager.get_font_prop().boldness; + float boldness = boldness_font_point.value_or(0.f) * scale; + + auto def_boldness_font_point = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.boldness : nullptr; + float def_boldness_value = def_boldness_font_point ? + static_cast(def_boldness_font_point->value_or(0) * scale) : 0.f; + float *def_boldness = def_boldness_font_point ? &def_boldness_value : nullptr; + + float min_boldness = static_cast((double)font_info.ascent * limits.boldness.gui.min * scale); + float max_boldness = static_cast((double)font_info.ascent * limits.boldness.gui.max * scale); + MinMax min_max_boldness{min_boldness, max_boldness}; + + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Undo boldness"); + // TRN - Tooltip above line gap slider + const wxString tooltip = _L("Tiny / Wide glyphs"); + + bool exist_change = false; + if (rev_slider(tr_boldness, boldness, def_boldness, undo_tooltip, + min_max_boldness, units_fmt, tooltip)){ + float boldness_font_point_new = static_cast(std::round(boldness / scale)); + if (is_approx(boldness_font_point_new, boldness_font_point.value_or(0.f))) + // appear when emboss job throw error - e.g. no volume (no symbol font) + // -> cause infinit loop call of process() + return; // no change + else if (is_approx(boldness_font_point_new, 0.f)) + boldness_font_point.reset(); + else + boldness_font_point = boldness_font_point_new; + + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_boldness = m_volume->text_configuration->style.prop.boldness; + apply(boldness_font_point, limits.boldness.values); + + if (!is_approx(boldness_font_point.value_or(0.f), volume_boldness.value_or(0.f))) { + // glyph shape is modified by boldness + m_style_manager.clear_glyphs_cache(); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_skew() +{ + const std::string tr_skew = m_gui_cfg->translations.skew_ration; + std::optional &skew = m_style_manager.get_font_prop().skew; + auto def_skew = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.skew : nullptr; + + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Undo letter's skew"); + // TRN - Tooltip above skew ration slider + const wxString tooltip = _L("Italic strength ratio"); + bool exist_change = false; + if (rev_slider(tr_skew, skew, def_skew, undo_tooltip, + limits.skew.gui, "%.2f", tooltip)){ + apply(skew, limits.skew.values); + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_skew = m_volume->text_configuration->style.prop.skew; + if (!is_approx(skew.value_or(0.f), volume_skew.value_or(0.f))) { + // glyph shape is modified by skew + m_style_manager.clear_glyphs_cache(); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_surface_distance() +{ + const StyleManager::Style ¤t_style = m_style_manager.get_style(); + const EmbossProjection &projection = current_style.projection; + // input surface distance + bool allowe_surface_distance = + !projection.use_surface && + !m_volume->is_the_only_one_part(); + + std::optional &distance = m_style_manager.get_style().distance; + float prev_distance = distance.value_or(.0f); + float min_distance = static_cast(-2 * projection.depth); + float max_distance = static_cast(2 * projection.depth); + MinMax min_max_distance{min_distance, max_distance}; + auto def_distance = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->distance : + nullptr; + m_imgui->disabled_begin(!allowe_surface_distance); + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + const std::string undo_move_tooltip = _u8L("Undo translation"); + const wxString move_tooltip = _L("Distance of the center of the text to the model surface."); + const std::string &tr_from_surface = m_gui_cfg->translations.from_surface; + bool is_moved = false; + if (use_inch) { + std::optional distance_inch; + if (distance.has_value()) distance_inch = (*distance * ObjectManipulation::mm_to_in); + std::optional def_distance_inch; + if (def_distance != nullptr) { + if (def_distance->has_value()) def_distance_inch = ObjectManipulation::mm_to_in * (*(*def_distance)); + def_distance = &def_distance_inch; + } + min_max_distance.min *= ObjectManipulation::mm_to_in; + min_max_distance.max *= ObjectManipulation::mm_to_in; + if (rev_slider(tr_from_surface, distance_inch, def_distance, undo_move_tooltip, min_max_distance, "%.3f in", move_tooltip)) { + if (distance_inch.has_value()) { + distance = *distance_inch * ObjectManipulation::in_to_mm; + } else { + distance.reset(); + } + is_moved = true; + } + } else { + if (rev_slider(tr_from_surface, distance, def_distance, undo_move_tooltip, + min_max_distance, "%.2f mm", move_tooltip)) is_moved = true; + } + + if (is_moved){ + if (current_style.prop.per_glyph) { + process(false); + } else { + do_local_z_move(m_parent.get_selection(), distance.value_or(.0f) - prev_distance); + } + } + + // Apply move to model(backend) + if (m_imgui->get_last_slider_status().deactivated_after_edit) { + m_parent.do_move(move_snapshot_name); + if (current_style.prop.per_glyph) + process(); + } + + m_imgui->disabled_end(); // allowe_surface_distance +} + +void GLGizmoEmboss::draw_rotation() { + const std::string &tr_rotation = m_gui_cfg->translations.rotation; + + // 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_style_manager.get_style().angle.value_or(0.f); + float angle_deg = static_cast(-angle * 180 / M_PI); + const auto def_angle_rad = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->angle : nullptr; + float def_angle_deg_val = def_angle_rad ? + static_cast(def_angle_rad->value_or(0.f) * -180 / M_PI) : 0.f; + float *def_angle_deg = def_angle_rad ? &def_angle_deg_val : nullptr; + const std::string undo_tooltip = _u8L("Undo rotation"); + // TRN - Tooltip above rotation slider + const wxString tooltip = _L("Rotate text Clock-wise."); + bool exist_change = false; + if (rev_slider(tr_rotation, angle_deg, def_angle_deg, undo_tooltip, + limits.angle, u8"%.2f °", tooltip)) { + // 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; + if (!is_approx(diff_angle, 0.)) { + do_local_z_rotate(m_parent.get_selection(), diff_angle); + + // calc angle after rotation + const Selection &selection = m_parent.get_selection(); + const GLVolume *gl_volume = get_selected_gl_volume(selection); + assert(gl_volume != nullptr); + assert(m_style_manager.is_active_font()); + if (m_style_manager.is_active_font() && gl_volume != nullptr) + m_style_manager.get_style().angle = calc_angle(selection); + + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (is_last_change || exist_change) { + bool per_glyph = m_style_manager.get_font_prop().per_glyph; + if (per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + + // recalculate for surface cut + if (m_style_manager.get_style().projection.use_surface || per_glyph) { + std::string no_snapshot; + m_parent.do_rotate(is_last_change? rotation_snapshot_name : no_snapshot); + process(is_last_change); + } else if (is_last_change){ + 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_bold : IconType::unlock_bold, IconState::activable); + const IconManager::Icon &icon_disable = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::disabled); + if (button(icon, icon_hover, icon_disable)) + m_keep_up = !m_keep_up; + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", (m_keep_up? + _u8L("Unlock the text's rotation when moving text along the object's surface."): + _u8L("Lock the text's rotation when moving text along the object's surface.") + ).c_str()); + } +} + void GLGizmoEmboss::set_minimal_window_size(bool is_advance_edit_style) { ImVec2 window_size = ImGui::GetWindowSize(); @@ -3055,7 +3301,7 @@ void GLGizmoEmboss::set_minimal_window_size(bool is_advance_edit_style) const ImVec2 &min_win_size = get_minimal_window_size(); ImVec2 new_window_size(0.f, min_win_size.y + diff_y); ImGui::SetWindowSize(new_window_size, ImGuiCond_Always); - m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), true); + m_set_window_offset = ImGuiPureWrap::change_window_position(on_get_name().c_str(), true); } ImVec2 GLGizmoEmboss::get_minimal_window_size() const @@ -3071,6 +3317,7 @@ ImVec2 GLGizmoEmboss::get_minimal_window_size() const // Can change type of volume if (!m_volume->is_the_only_one_part()) res.y += m_gui_cfg->height_of_volume_type_selector; + return res; } @@ -3167,7 +3414,6 @@ bool GLGizmoEmboss::choose_true_type_file() } #endif // ALLOW_ADD_FONT_BY_FILE - void GLGizmoEmboss::create_notification_not_valid_font( const TextConfiguration &tc) { @@ -3330,6 +3576,25 @@ void TextDataBase::write(ModelVolume &volume) const DataBase::write(volume); volume.text_configuration = m_text_configuration; // copy assert(volume.emboss_shape.has_value()); + + // Fix for object: stored attribute that volume is embossed per glyph when it is object + if (m_text_configuration.style.prop.per_glyph && volume.is_the_only_one_part()) + volume.text_configuration->style.prop.per_glyph = false; +} + +bool TextDataBase::create_text_lines(const Transform3d &tr, const ModelVolumePtrs &vols) { + // only when per glyph is on + if (!m_text_configuration.style.prop.per_glyph) + return false; + + unsigned count_lines = get_count_lines(m_text_configuration.text); + text_lines = Slic3r::Emboss::create_text_lines( + tr, vols, + *m_font_file.font_file, m_text_configuration.style.prop, count_lines); + if (text_lines.empty()) + m_text_configuration.style.prop.per_glyph = false; + + return !text_lines.empty(); } std::unique_ptr create_emboss_data_base(const std::string &text, @@ -3353,18 +3618,12 @@ std::unique_ptr create_emboss_data_base(const std::string return {}; // no active font in style, should never happend !!! } - const StyleManager::Style &style = style_manager.get_style(); + StyleManager::Style style = style_manager.get_style(); // copy // actualize font path - during changes in gui it could be corrupted // volume must store valid path assert(style_manager.get_wx_font().IsOk()); assert(style.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0); - if (style.prop.per_glyph) { - if (!text_lines.is_init()) - init_text_lines(text_lines, selection, style_manager); - } else - text_lines.reset(); - bool is_outside = (type == ModelVolumeType::MODEL_PART); // Cancel previous Job, when it is in process @@ -3374,9 +3633,11 @@ std::unique_ptr create_emboss_data_base(const std::string cancel->store(true); // create new shared ptr to cancel new job cancel = std::make_shared>(false); + DataBase base(volume_name, cancel); base.is_outside = is_outside; - base.text_lines = text_lines.get_lines(); + if (style.prop.per_glyph) // lines are already created when hover checkbox per_glyph + base.text_lines = text_lines.get_lines(); base.from_surface = style.distance; FontFileWithCache &font = style_manager.get_font_file_with_cache(); @@ -3384,15 +3645,6 @@ std::unique_ptr create_emboss_data_base(const std::string return std::make_unique(std::move(base), font, std::move(tc), style.projection); } -CreateVolumeParams create_input(GLCanvas3D &canvas, const StyleManager::Style &style, RaycastManager& raycaster, ModelVolumeType volume_type) -{ - auto gizmo = static_cast(GLGizmosManager::Emboss); - const GLVolume *gl_volume = get_first_hovered_gl_volume(canvas); - Plater *plater = wxGetApp().plater(); - return CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), - plater->get_ui_job_worker(), volume_type, raycaster, gizmo, gl_volume, style.distance, style.angle}; -} - ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) { const Selection::IndicesList indices = selection.get_volume_idxs(); @@ -3417,7 +3669,7 @@ std::string concat(std::vector data) { for (const auto &d : data) ss << d.c_str() << ", "; return ss.str(); - } +} boost::filesystem::path get_fontlist_cache_path(){ return boost::filesystem::path(data_dir()) / "cache" / "fonts.cereal"; @@ -3453,7 +3705,7 @@ bool load(Facenames &facenames) { } boost::nowide::ifstream file(path_str, std::ios::binary); cereal::BinaryInputArchive archive(file); - + FacenamesSerializer data; try { archive(data); @@ -3477,7 +3729,7 @@ void init_truncated_names(Facenames &face_names, float max_width) { for (FaceName &face : face_names.faces) { std::string name_str(face.wx_name.ToUTF8().data()); - face.name_truncated = ImGuiWrapper::trunc(name_str, max_width); + face.name_truncated = ImGuiPureWrap::trunc(name_str, max_width); } face_names.has_truncated_names = true; } @@ -3497,6 +3749,7 @@ void init_face_names(Facenames &face_names) load(face_names); face_names.has_truncated_names = false; } + using namespace std::chrono; steady_clock::time_point enumerate_start = steady_clock::now(); ScopeGuard sg([&enumerate_start, &face_names = face_names]() { @@ -3517,7 +3770,7 @@ void init_face_names(Facenames &face_names) BOOST_LOG_TRIVIAL(info) << "Same FontNames hash, cache is used. " << "For clear cache delete file: " << get_fontlist_cache_path().string(); return; -} + } BOOST_LOG_TRIVIAL(info) << ((face_names.hash == 0) ? "FontName list is generate from scratch." : @@ -3727,7 +3980,7 @@ GuiCfg create_gui_configuration() // TRN - Input label. Be short as possible // Keep vector from bottom to top of text aligned with printer Y axis tr.keep_up = _u8L("Keep up"); - + // TRN - Input label. Be short as possible. // Some Font file contain multiple fonts inside and // this is numerical selector of font inside font collections diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 6954e15..552ad32 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -26,6 +26,9 @@ namespace Slic3r{ class AppConfig; class GLVolume; enum class ModelVolumeType : int; + namespace GUI::Emboss { + struct CreateVolumeParams; + } } namespace Slic3r::GUI { @@ -43,6 +46,7 @@ public: /// Object part / Negative volume / Modifier /// Define position of new volume bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); + /// /// Create new text without given position /// @@ -70,6 +74,7 @@ public: /// Way to stop re_emboss job /// True on success otherwise False static bool re_emboss(const ModelVolume &text, std::shared_ptr> job_cancel = nullptr); + protected: bool on_init() override; std::string on_get_name() const override; @@ -99,6 +104,7 @@ protected: std::string get_gizmo_entering_text() const override; std::string get_gizmo_leaving_text() const override; std::string get_action_snapshot_name() const override; + private: void volume_transformation_changing(); void volume_transformation_changed(); @@ -111,7 +117,7 @@ private: void reset_volume(); // create volume from text - main functionality - bool process(bool make_snapshot = true); + bool process(bool make_snapshot = true, std::optional volume_transformation = std::nullopt); void close(); void draw_window(); void draw_text_input(); @@ -129,7 +135,6 @@ private: void draw_height(bool use_inch); void draw_depth(bool use_inch); - // call after set m_style_manager.get_style().prop.size_in_mm bool set_height(); @@ -137,6 +142,17 @@ private: bool draw_bold_button(); void draw_advanced(); + void draw_use_surface(); + void draw_per_glyph(); + void draw_align(); + void draw_char_gap(); + void draw_line_gap(); + void draw_boldness(); + void draw_skew(); + void draw_rotation(); + + void draw_surface_distance(); + bool select_facename(const wxString& facename); template bool rev_input_mm(const std::string &name, T &value, const T *default_value, @@ -150,12 +166,10 @@ private: template bool rev_input(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, T step, T step_fast, const char *format, ImGuiInputTextFlags flags = 0) const; bool rev_checkbox(const std::string &name, bool &value, const bool* default_value, const std::string &undo_tooltip) const; - bool rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, - const std::string &undo_tooltip, int v_min, int v_max, const std::string &format, const wxString &tooltip) const; bool rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, - const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const; + const std::string &undo_tooltip, const MinMax& min_max, const std::string &format, const wxString &tooltip) const; bool rev_slider(const std::string &name, float &value, const float *default_value, - const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const; + const std::string &undo_tooltip, const MinMax& min_max, const std::string &format, const wxString &tooltip) const; template bool revertible(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, float undo_offset, Draw draw) const; @@ -173,7 +187,11 @@ private: void create_notification_not_valid_font(const TextConfiguration& tc); void create_notification_not_valid_font(const std::string& text); void remove_notification_not_valid_font(); - + + // initialize data for create volume in job + Emboss::CreateVolumeParams create_input(ModelVolumeType volume_type); + // Emboss::CreateVolumeParams create_input(GLCanvas3D &canvas, const StyleManager::Style &style, RaycastManager &raycaster, ModelVolumeType volume_type); + struct GuiCfg; std::unique_ptr m_gui_cfg; @@ -232,6 +250,7 @@ private: // For text on scaled objects std::optional m_scale_height; std::optional m_scale_depth; + std::optional m_scale_width; void calculate_scale(); // drawing icons diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 28e66fb..ca94919 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -44,35 +44,35 @@ bool GLGizmoFdmSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; - m_desc["autopaint"] = _L("Automatic painting"); + m_desc["autopaint"] = _u8L("Automatic painting"); // TRN GizmoFdmSupports : message line during the waiting for autogenerated supports - m_desc["painting"] = _L("painting") + dots; - m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size"] = _L("Brush size") + ": "; - m_desc["cursor_type"] = _L("Brush shape") + ": "; - m_desc["enforce_caption"] = _L("Left mouse button") + ": "; - m_desc["enforce"] = _L("Enforce supports"); - m_desc["block_caption"] = _L("Right mouse button") + ": "; - m_desc["block"] = _L("Block supports"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all selection"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Triangles"); - m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); - m_desc["enforce_button"] = _L("Enforce"); - m_desc["cancel"] = _L("Cancel"); + m_desc["painting"] = _u8L("painting") + "…"; + m_desc["clipping_of_view"] = _u8L("Clipping of view") + ": "; + m_desc["reset_direction"] = _u8L("Reset direction"); + m_desc["cursor_size"] = _u8L("Brush size") + ": "; + m_desc["cursor_type"] = _u8L("Brush shape") + ": "; + m_desc["enforce_caption"] = _u8L("Left mouse button") + ": "; + m_desc["enforce"] = _u8L("Enforce supports"); + m_desc["block_caption"] = _u8L("Right mouse button") + ": "; + m_desc["block"] = _u8L("Block supports"); + m_desc["remove_caption"] = _u8L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _u8L("Remove selection"); + m_desc["remove_all"] = _u8L("Remove all selection"); + m_desc["circle"] = _u8L("Circle"); + m_desc["sphere"] = _u8L("Sphere"); + m_desc["pointer"] = _u8L("Triangles"); + m_desc["highlight_by_angle"] = _u8L("Highlight overhang by angle"); + m_desc["enforce_button"] = _u8L("Enforce"); + m_desc["cancel"] = _u8L("Cancel"); - m_desc["tool_type"] = _L("Tool type") + ": "; - m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_smart_fill"] = _L("Smart fill"); + m_desc["tool_type"] = _u8L("Tool type") + ": "; + m_desc["tool_brush"] = _u8L("Brush"); + m_desc["tool_smart_fill"] = _u8L("Smart fill"); - m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["smart_fill_angle"] = _u8L("Smart fill angle"); - m_desc["split_triangles"] = _L("Split triangles"); - m_desc["on_overhangs_only"] = _L("On overhangs only"); + m_desc["split_triangles"] = _u8L("Split triangles"); + m_desc["on_overhangs_only"] = _u8L("On overhangs only"); return true; } @@ -101,46 +101,46 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float approx_height = m_imgui->scaled(25.f); y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); + const float autoset_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); - const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_pointer = ImGuiPureWrap::calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); - const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x; - const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x; + const float button_width = ImGuiPureWrap::calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float button_enforce_width = ImGuiPureWrap::calc_text_size(m_desc.at("enforce_button")).x; + const float button_cancel_width = ImGuiPureWrap::calc_text_size(m_desc.at("cancel")).x; const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f); const float minimal_slider_width = m_imgui->scaled(4.f); - const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); - const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_left = ImGuiPureWrap::calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); + const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); - const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); - const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + const float on_overhangs_only_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); - total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); + caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x); } total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); - const float slider_icon_width = m_imgui->get_slider_icon_size().x; + const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); @@ -150,11 +150,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { + auto draw_text_with_caption = [&caption_max](const std::string& caption, const std::string& text) { //B18 - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, caption); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, caption); ImGui::SameLine(caption_max); - m_imgui->text(text); + ImGuiPureWrap::text(text); }; for (const auto &t : std::array{"enforce", "block", "remove"}) @@ -163,9 +163,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); if (waiting_for_autogenerated_supports) { - m_imgui->text(m_desc.at("painting")); + ImGuiPureWrap::text(m_desc.at("painting")); } else { - bool generate = m_imgui->button(m_desc.at("autopaint")); + bool generate = ImGuiPureWrap::button(m_desc.at("autopaint")); if (generate) auto_generate(); } @@ -173,7 +173,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l float position_before_text_y = ImGui::GetCursorPos().y; ImGui::AlignTextToFramePadding(); - m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); + ImGuiPureWrap::text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); ImGui::AlignTextToFramePadding(); float position_after_text_y = ImGui::GetCursorPos().y; std::string format_str = std::string("%.f") + I18N::translate_utf8("°", @@ -181,7 +181,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); - float slider_height = m_imgui->get_slider_float_height(); + float slider_height = ImGuiPureWrap::get_slider_float_height(); // Makes slider to be aligned to bottom of the multi-line text. float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height); ImGui::SetCursorPosY(slider_start_position_y); @@ -205,13 +205,13 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); ImGui::NewLine(); ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); - if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { + if (ImGuiPureWrap::button(m_desc["enforce_button"], buttons_width, 0.f)) { select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } ImGui::SameLine(window_width - buttons_width); - if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { + if (ImGuiPureWrap::button(m_desc["cancel"], buttons_width, 0.f)) { m_highlight_by_angle_threshold_deg = 0.f; m_parent.use_slope(false); } @@ -221,80 +221,80 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["tool_type"]); + ImGuiPureWrap::text(m_desc["tool_type"]); float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; ImGui::SameLine(tool_type_offset); ImGui::PushItemWidth(tool_type_radio_brush); - if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) + if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) m_tool_type = ToolType::BRUSH; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); - if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) + if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) m_tool_type = ToolType::SMART_FILL; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); - m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + ImGuiPureWrap::checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); + ImGuiPureWrap::tooltip(GUI::format(_u8L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); ImGui::Separator(); if (m_tool_type == ToolType::BRUSH) { - m_imgui->text(m_desc.at("cursor_type")); + ImGuiPureWrap::text(m_desc.at("cursor_type")); ImGui::NewLine(); float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; ImGui::SameLine(cursor_type_offset); ImGui::PushItemWidth(cursor_type_radio_sphere); - if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + if (ImGuiPureWrap::radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); - if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + if (ImGuiPureWrap::radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_pointer); - if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) + if (ImGuiPureWrap::radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) m_cursor_type = TriangleSelector::CursorType::POINTER; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints only one facet."), max_tooltip_width); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_size")); + ImGuiPureWrap::text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); - m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); + ImGuiPureWrap::checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); m_imgui->disabled_end(); } else { assert(m_tool_type == ToolType::SMART_FILL); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["smart_fill_angle"] + ":"); + ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":"); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); @@ -308,10 +308,10 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); + ImGuiPureWrap::text(m_desc.at("clipping_of_view")); } else { - if (m_imgui->button(m_desc.at("reset_direction"))) { + if (ImGuiPureWrap::button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ m_c->object_clipper()->set_position_by_ratio(-1., false); }); @@ -321,11 +321,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l auto clp_dist = float(m_c->object_clipper()->get_position()); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, from_u8(GUI::shortkey_ctrl_prefix()) + _L("Mouse wheel"))) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); ImGui::Separator(); - if (m_imgui->button(m_desc.at("remove_all"))) { + if (ImGuiPureWrap::button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); ModelObject *mo = m_c->selection_info()->model_object(); int idx = -1; @@ -341,7 +341,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_parent.set_as_dirty(); } - m_imgui->end(); + ImGuiPureWrap::end(); } @@ -371,7 +371,7 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) const indexed_triangle_set &its = mv->mesh().its; for (const stl_triangle_vertex_indices &face : its.indices) { if (its_face_normal(its, face).dot(down) > dot_limit) { - m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); + m_triangle_selectors[mesh_id]->set_facet(idx, block ? TriangleStateType::BLOCKER : TriangleStateType::ENFORCER); m_triangle_selectors.back()->request_update_render_data(); } ++ idx; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index ee77573..8339933 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -41,7 +41,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; + std::map m_desc; bool waiting_for_autogenerated_supports = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 0a42173..661eaf3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -26,18 +26,18 @@ GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filenam bool GLGizmoHollow::on_init() { m_shortcut_key = WXK_CONTROL_H; - m_desc["enable"] = _(L("Hollow this object")); - m_desc["preview"] = _(L("Preview hollowed and drilled model")); - m_desc["offset"] = _(L("Offset")) + ": "; - m_desc["quality"] = _(L("Quality")) + ": "; - m_desc["closing_distance"] = _(L("Closing distance")) + ": "; - m_desc["hole_diameter"] = _(L("Hole diameter")) + ": "; - m_desc["hole_depth"] = _(L("Hole depth")) + ": "; - m_desc["remove_selected"] = _(L("Remove selected holes")); - m_desc["remove_all"] = _(L("Remove all holes")); - m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; - m_desc["reset_direction"] = _(L("Reset direction")); - m_desc["show_supports"] = _(L("Show supports")); + m_desc["enable"] = _u8L("Hollow this object"); + m_desc["preview"] = _u8L("Preview hollowed and drilled model"); + m_desc["offset"] = _u8L("Offset") + ": "; + m_desc["quality"] = _u8L("Quality") + ": "; + m_desc["closing_distance"] = _u8L("Closing distance") + ": "; + m_desc["hole_diameter"] = _u8L("Hole diameter") + ": "; + m_desc["hole_depth"] = _u8L("Hole depth") + ": "; + m_desc["remove_selected"] = _u8L("Remove selected holes"); + m_desc["remove_all"] = _u8L("Remove all holes"); + m_desc["clipping_of_view"] = _u8L("Clipping of view")+ ": "; + m_desc["reset_direction"] = _u8L("Reset direction"); + m_desc["show_supports"] = _u8L("Show supports"); return true; } @@ -554,40 +554,40 @@ void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit) double closing_d_max = opts[2].second->max; ConfigOptionMode closing_d_mode = opts[2].second->mode; - m_desc["offset"] = _(opts[0].second->label) + ":"; - m_desc["quality"] = _(opts[1].second->label) + ":"; - m_desc["closing_distance"] = _(opts[2].second->label) + ":"; + m_desc["offset"] = into_u8(_(opts[0].second->label)) + ":"; + m_desc["quality"] = into_u8(_(opts[1].second->label)) + ":"; + m_desc["closing_distance"] = into_u8(_(opts[2].second->label)) + ":"; RENDER_AGAIN: const float approx_height = m_imgui->scaled(20.0f); y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f); + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f); const float settings_sliders_left = - std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x, - m_imgui->calc_text_size(m_desc.at("quality")).x, - m_imgui->calc_text_size(m_desc.at("closing_distance")).x, - m_imgui->calc_text_size(m_desc.at("hole_diameter")).x, - m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left); + std::max(std::max({ImGuiPureWrap::calc_text_size(m_desc.at("offset")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("quality")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("closing_distance")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("hole_diameter")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left); - const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f); + const float diameter_slider_left = settings_sliders_left; //ImGuiPureWrap::calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); - const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x; + const float button_preview_width = ImGuiPureWrap::calc_button_size(m_desc.at("preview")).x; float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); window_width = std::max(window_width, button_preview_width); m_imgui->disabled_begin(!is_input_enabled()); - if (m_imgui->button(m_desc["preview"])) + if (ImGuiPureWrap::button(m_desc["preview"])) reslice_until_step(slaposDrillHoles); bool config_changed = false; @@ -597,7 +597,7 @@ RENDER_AGAIN: { auto opts = get_config_options({"hollowing_enable"}); m_enable_hollowing = static_cast(opts[0].first)->value; - if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { + if (ImGuiPureWrap::checkbox(m_desc["enable"], m_enable_hollowing)) { mo->config.set("hollowing_enable", m_enable_hollowing); wxGetApp().obj_list()->update_and_show_object_settings_item(); config_changed = true; @@ -609,8 +609,8 @@ RENDER_AGAIN: m_imgui->disabled_begin(!is_input_enabled() || !m_enable_hollowing); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("offset")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGuiPureWrap::text(m_desc.at("offset")); + ImGui::SameLine(settings_sliders_left, ImGuiPureWrap::get_item_spacing().x); ImGui::PushItemWidth(window_width - settings_sliders_left); m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); @@ -620,8 +620,8 @@ RENDER_AGAIN: if (current_mode >= quality_mode) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("quality")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGuiPureWrap::text(m_desc.at("quality")); + ImGui::SameLine(settings_sliders_left, ImGuiPureWrap::get_item_spacing().x); m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); slider_clicked |= m_imgui->get_last_slider_status().clicked; @@ -631,8 +631,8 @@ RENDER_AGAIN: if (current_mode >= closing_d_mode) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("closing_distance")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGuiPureWrap::text(m_desc.at("closing_distance")); + ImGui::SameLine(settings_sliders_left, ImGuiPureWrap::get_item_spacing().x); m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip)); slider_clicked |= m_imgui->get_last_slider_status().clicked; @@ -676,8 +676,8 @@ RENDER_AGAIN: m_imgui->disabled_begin(!is_input_enabled()); - m_imgui->text(m_desc.at("hole_diameter")); - ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); + ImGuiPureWrap::text(m_desc.at("hole_diameter")); + ImGui::SameLine(diameter_slider_left, ImGuiPureWrap::get_item_spacing().x); ImGui::PushItemWidth(window_width - diameter_slider_left); float diam = 2.f * m_new_hole_radius; m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false); @@ -693,8 +693,8 @@ RENDER_AGAIN: ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["hole_depth"]); - ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); + ImGuiPureWrap::text(m_desc["hole_depth"]); + ImGui::SameLine(diameter_slider_left, ImGuiPureWrap::get_item_spacing().x); m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); m_imgui->disabled_end(); @@ -742,11 +742,11 @@ RENDER_AGAIN: } m_imgui->disabled_begin(!is_input_enabled() || m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); + remove_selected = ImGuiPureWrap::button(m_desc.at("remove_selected")); m_imgui->disabled_end(); m_imgui->disabled_begin(!is_input_enabled() || mo->sla_drain_holes.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); + remove_all = ImGuiPureWrap::button(m_desc.at("remove_all")); m_imgui->disabled_end(); // Following is rendered in both editing and non-editing mode: @@ -754,17 +754,17 @@ RENDER_AGAIN: m_imgui->disabled_begin(!is_input_enabled()); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); + ImGuiPureWrap::text(m_desc.at("clipping_of_view")); } else { - if (m_imgui->button(m_desc.at("reset_direction"))) { + if (ImGuiPureWrap::button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGui::SameLine(settings_sliders_left, ImGuiPureWrap::get_item_spacing().x); ImGui::PushItemWidth(window_width - settings_sliders_left); float clp_dist = m_c->object_clipper()->get_position(); if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) @@ -773,13 +773,13 @@ RENDER_AGAIN: // make sure supports are shown/hidden as appropriate ImGui::Separator(); bool show_sups = are_sla_supports_shown(); - if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { + if (ImGuiPureWrap::checkbox(m_desc["show_supports"], show_sups)) { show_sla_supports(show_sups); force_refresh = true; } m_imgui->disabled_end(); - m_imgui->end(); + ImGuiPureWrap::end(); if (remove_selected || remove_all) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 025e70b..3757981 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -74,7 +74,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; + std::map m_desc; GLSelectionRectangle m_selection_rectangle; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 41eeb57..4a2d1dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -8,6 +8,10 @@ #include "libslic3r/PresetBundle.hpp" #include "libslic3r/MeasureUtils.hpp" +#include +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif #include #include @@ -1164,24 +1168,29 @@ void GLGizmoMeasure::render_dimensioning() const Transform3d ss_to_ndc_matrix = TransformHelper::ndc_to_ss_matrix_inverse(viewport); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES shader->stop_using(); +#if SLIC3R_OPENGL_ES + shader = wxGetApp().get_shader("dashed_lines"); +#else shader = wxGetApp().get_shader("dashed_thick_lines"); +#endif // SLIC3R_OPENGL_ES if (shader == nullptr) return; shader->start_using(); shader->set_uniform("projection_matrix", Transform3d::Identity()); - const std::array& viewport = camera.get_viewport(); shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); shader->set_uniform("width", 1.0f); shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES } else -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(2.0f)); +#endif // !SLIC3R_OPENGL_ES // stem shader->set_uniform("view_model_matrix", overlap ? @@ -1190,8 +1199,9 @@ void GLGizmoMeasure::render_dimensioning() m_dimensioning.line.set_color(ColorRGBA::WHITE()); m_dimensioning.line.render(); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES shader->stop_using(); shader = wxGetApp().get_shader("flat"); @@ -1199,10 +1209,11 @@ void GLGizmoMeasure::render_dimensioning() return; shader->start_using(); +#if !SLIC3R_OPENGL_ES } else -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(1.0f)); +#endif // !SLIC3R_OPENGL_ES // arrow 1 shader->set_uniform("view_model_matrix", overlap ? @@ -1224,14 +1235,14 @@ void GLGizmoMeasure::render_dimensioning() static double edit_value = 0.0; const Vec2d label_position = 0.5 * (v1ss + v2ss); - m_imgui->set_next_window_pos(label_position.x(), viewport[3] - label_position.y(), ImGuiCond_Always, 0.0f, 1.0f); - m_imgui->set_next_window_bg_alpha(0.0f); + ImGuiPureWrap::set_next_window_pos(label_position.x(), viewport[3] - label_position.y(), ImGuiCond_Always, 0.0f, 1.0f); + ImGuiPureWrap::set_next_window_bg_alpha(0.0f); if (!m_editing_distance) { ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 1.0f, 1.0f }); - m_imgui->begin(std::string("distance"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); + ImGuiPureWrap::begin(std::string("distance"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); ImGui::AlignTextToFramePadding(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -1240,16 +1251,16 @@ void GLGizmoMeasure::render_dimensioning() ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); const ImGuiStyle& style = ImGui::GetStyle(); draw_list->AddRectFilled({ pos.x - style.FramePadding.x, pos.y + style.FramePadding.y }, { pos.x + txt_size.x + 2.0f * style.FramePadding.x , pos.y + txt_size.y + 2.0f * style.FramePadding.y }, - ImGuiWrapper::to_ImU32(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f))); + ImGuiPSWrap::to_ImU32(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f))); ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); - m_imgui->text(txt); + ImGuiPureWrap::text(txt); ImGui::SameLine(); - if (m_imgui->image_button(ImGui::SliderFloatEditBtnIcon, _L("Edit to scale"))) { + if (m_imgui->image_button(ImGui::SliderFloatEditBtnIcon, _u8L("Edit to scale"))) { m_editing_distance = true; edit_value = curr_value; m_imgui->requires_extra_frame(); } - m_imgui->end(); + ImGuiPureWrap::end(); ImGui::PopStyleVar(3); } @@ -1351,7 +1362,7 @@ void GLGizmoMeasure::render_dimensioning() action_exit(); }; - m_imgui->disable_background_fadeout_animation(); + ImGuiPureWrap::disable_background_fadeout_animation(); ImGui::PushItemWidth(value_str_width); if (ImGui::InputDouble("##distance", &edit_value, 0.0f, 0.0f, "%.3f")) { } @@ -1370,10 +1381,10 @@ void GLGizmoMeasure::render_dimensioning() action_exit(); ImGui::SameLine(); - if (m_imgui->button(_CTX(L_CONTEXT("Scale", "Verb"), "Verb"))) + if (ImGuiPureWrap::button(_CTX_utf8(L_CONTEXT("Scale", "Verb"), "Verb"))) action_scale(edit_value, curr_value); ImGui::SameLine(); - if (m_imgui->button(_L("Cancel"))) + if (ImGuiPureWrap::button(_u8L("Cancel"))) action_exit(); ImGui::EndPopup(); } @@ -1458,11 +1469,16 @@ void GLGizmoMeasure::render_dimensioning() } const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES shader->stop_using(); +#if SLIC3R_OPENGL_ES + shader = wxGetApp().get_shader("dashed_lines"); +#else shader = wxGetApp().get_shader("dashed_thick_lines"); +#endif // SLIC3R_OPENGL_ES if (shader == nullptr) return; @@ -1472,18 +1488,20 @@ void GLGizmoMeasure::render_dimensioning() shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); shader->set_uniform("width", 1.0f); shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES } else -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glLineWidth(2.0f)); + glsafe(::glLineWidth(2.0f)); +#endif // !SLIC3R_OPENGL_ES // arc shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center)); m_dimensioning.arc.render(); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES shader->stop_using(); shader = wxGetApp().get_shader("flat"); @@ -1491,10 +1509,11 @@ void GLGizmoMeasure::render_dimensioning() return; shader->start_using(); +#if !SLIC3R_OPENGL_ES } else -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glLineWidth(1.0f)); + glsafe(::glLineWidth(1.0f)); +#endif // !SLIC3R_OPENGL_ES // arrows auto render_arrow = [this, shader, &camera, &normal, ¢er, &e1_unit, draw_radius, step, resolution](unsigned int endpoint_id) { @@ -1546,10 +1565,10 @@ void GLGizmoMeasure::render_dimensioning() const Vec2d label_position_ss = TransformHelper::world_to_ss(label_position_world, camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(), viewport); - m_imgui->set_next_window_pos(label_position_ss.x(), viewport[3] - label_position_ss.y(), ImGuiCond_Always, 0.0f, 1.0f); - m_imgui->set_next_window_bg_alpha(0.0f); + ImGuiPureWrap::set_next_window_pos(label_position_ss.x(), viewport[3] - label_position_ss.y(), ImGuiCond_Always, 0.0f, 1.0f); + ImGuiPureWrap::set_next_window_bg_alpha(0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - m_imgui->begin(wxString("##angle"), ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGuiPureWrap::begin("##angle", ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); ImGui::AlignTextToFramePadding(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -1558,10 +1577,10 @@ void GLGizmoMeasure::render_dimensioning() ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); const ImGuiStyle& style = ImGui::GetStyle(); draw_list->AddRectFilled({ pos.x - style.FramePadding.x, pos.y + style.FramePadding.y }, { pos.x + txt_size.x + 2.0f * style.FramePadding.x , pos.y + txt_size.y + 2.0f * style.FramePadding.y }, - ImGuiWrapper::to_ImU32(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f))); + ImGuiPSWrap::to_ImU32(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f))); ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); - m_imgui->text(txt); - m_imgui->end(); + ImGuiPureWrap::text(txt); + ImGuiPureWrap::end(); ImGui::PopStyleVar(); }; @@ -1699,9 +1718,9 @@ static void add_row_to_table(std::function col_1 = nullptr, std::fun col_2(); } -static void add_strings_row_to_table(ImGuiWrapper& imgui, const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) +static void add_strings_row_to_table(const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { - add_row_to_table([&]() { imgui.text_colored(col_1_color, col_1); }, [&]() { imgui.text_colored(col_2_color, col_2); }); + add_row_to_table([&]() { ImGuiPureWrap::text_colored(col_1_color, col_1); }, [&]() { ImGuiPureWrap::text_colored(col_2_color, col_2); }); }; #if ENABLE_MEASURE_GIZMO_DEBUG @@ -1710,27 +1729,27 @@ void GLGizmoMeasure::render_debug_dialog() //B18 auto add_feature_data = [this](const SelectedFeatures::Item& item) { const std::string text = (item.source == item.feature) ? surface_feature_type_as_string(item.feature->get_type()) : point_on_feature_type_as_string(item.source->get_type(), m_hover_id); - add_strings_row_to_table(*m_imgui, "Type", ImGuiWrapper::COL_BLUE_LIGHT, text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "Type", ImGuiPureWrap::COL_BLUE_LIGHT, text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); switch (item.feature->get_type()) { case Measure::SurfaceFeatureType::Point: { - add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(item.feature->get_point()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(item.feature->get_point()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); break; } case Measure::SurfaceFeatureType::Edge: { auto [from, to] = item.feature->get_edge(); - add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(from), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(to), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(from), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(to), ImGui::GetStyleColorVec4(ImGuiCol_Text)); break; } case Measure::SurfaceFeatureType::Plane: { auto [idx, normal, origin] = item.feature->get_plane(); - add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(origin), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_BLUE_LIGHT, format_double(idx), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(origin), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiPureWrap::COL_BLUE_LIGHT, format_double(idx), ImGui::GetStyleColorVec4(ImGuiCol_Text)); break; } case Measure::SurfaceFeatureType::Circle: @@ -1738,18 +1757,18 @@ void GLGizmoMeasure::render_debug_dialog() auto [center, radius, normal] = item.feature->get_circle(); const Vec3d on_circle = center + radius * Measure::get_orthogonal(normal, true); radius = (on_circle - center).norm(); - add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(center), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); - add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_BLUE_LIGHT, format_double(radius), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(center), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiPureWrap::COL_BLUE_LIGHT, format_double(radius), ImGui::GetStyleColorVec4(ImGuiCol_Text)); break; } } std::optional extra_point = item.feature->get_extra_point(); if (extra_point.has_value()) - add_strings_row_to_table(*m_imgui, "m_pt3", ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(*extra_point), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt3", ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(*extra_point), ImGui::GetStyleColorVec4(ImGuiCol_Text)); }; - m_imgui->begin("Measure tool debug", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin("Measure tool debug", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); if (ImGui::BeginTable("Mode", 2)) { std::string txt; switch (m_mode) @@ -1758,39 +1777,39 @@ void GLGizmoMeasure::render_debug_dialog() case EMode::PointSelection: { txt = "Point selection"; break; } default: { assert(false); break; } } - add_strings_row_to_table(*m_imgui, "Mode", ImGuiWrapper::COL_BLUE_LIGHT, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "Mode", ImGuiPureWrap::COL_BLUE_LIGHT, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); ImGui::EndTable(); } ImGui::Separator(); if (ImGui::BeginTable("Hover", 2)) { - add_strings_row_to_table(*m_imgui, "Hover id", ImGuiWrapper::COL_BLUE_LIGHT, std::to_string(m_hover_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "Hover id", ImGuiPureWrap::COL_BLUE_LIGHT, std::to_string(m_hover_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); const std::string txt = m_curr_feature.has_value() ? surface_feature_type_as_string(m_curr_feature->get_type()) : "None"; - add_strings_row_to_table(*m_imgui, "Current feature", ImGuiWrapper::COL_BLUE_LIGHT, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "Current feature", ImGuiPureWrap::COL_BLUE_LIGHT, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); ImGui::EndTable(); } ImGui::Separator(); if (!m_selected_features.first.feature.has_value() && !m_selected_features.second.feature.has_value()) - m_imgui->text("Empty selection"); + ImGuiPureWrap::text("Empty selection"); else { const ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersH; if (m_selected_features.first.feature.has_value()) { - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Selection 1"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Selection 1"); if (ImGui::BeginTable("Selection 1", 2, flags)) { add_feature_data(m_selected_features.first); ImGui::EndTable(); } } if (m_selected_features.second.feature.has_value()) { - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Selection 2"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Selection 2"); if (ImGui::BeginTable("Selection 2", 2, flags)) { add_feature_data(m_selected_features.second); ImGui::EndTable(); } } } - m_imgui->end(); + ImGuiPureWrap::end(); } #endif // ENABLE_MEASURE_GIZMO_DEBUG @@ -1806,7 +1825,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit if (m_editing_distance) return; - m_imgui->begin(get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); // adjust window position to avoid overlap the view toolbar const float win_h = ImGui::GetWindowHeight(); @@ -1824,8 +1843,8 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit if (ImGui::BeginTable("Commands", 2)) { unsigned int row_count = 1; add_row_to_table( - [this]() { - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Left mouse button")); + []() { + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Left mouse button")); }, [this]() { std::string text; @@ -1939,37 +1958,37 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit assert(!text.empty()); - m_imgui->text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), text); + ImGuiPureWrap::text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), text); ImGui::SameLine(); const ImVec2 pos = ImGui::GetCursorScreenPos(); const float rect_size = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + rect_size, pos.y + rect_size), ImGuiWrapper::to_ImU32(color)); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + rect_size, pos.y + rect_size), ImGuiPSWrap::to_ImU32(color)); ImGui::Dummy(ImVec2(rect_size, rect_size)); } ); //B18 if (m_mode == EMode::FeatureSelection && m_hover_id != -1) { - add_strings_row_to_table(*m_imgui, "Shift", ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Enable point selection"), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Shift", ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Enable point selection"), ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++row_count; } if (m_selected_features.first.feature.has_value()) { - add_strings_row_to_table(*m_imgui, "Delete", ImGuiWrapper::COL_BLUE_LIGHT, _u8L("Restart selection"), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table("Delete", ImGuiPureWrap::COL_BLUE_LIGHT, _u8L("Restart selection"), ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++row_count; } if (m_selected_features.first.feature.has_value() || m_selected_features.second.feature.has_value()) { add_row_to_table( - [this]() { - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, "Esc"); + []() { + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, "Esc"); }, [this]() { - m_imgui->text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), _u8L("Unselect")); + ImGuiPureWrap::text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), _u8L("Unselect")); ImGui::SameLine(); const ImVec2 pos = ImGui::GetCursorScreenPos(); const float rect_size = ImGui::GetTextLineHeight(); const ColorRGBA color = m_selected_features.second.feature.has_value() ? SELECTED_2ND_COLOR : SELECTED_1ST_COLOR; - ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + rect_size, pos.y + rect_size), ImGuiWrapper::to_ImU32(color)); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + rect_size, pos.y + rect_size), ImGuiPSWrap::to_ImU32(color)); ImGui::Dummy(ImVec2(rect_size, rect_size)); } ); @@ -1979,7 +1998,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit // add dummy rows to keep dialog size fixed for (unsigned int i = row_count; i < 4; ++i) { - add_strings_row_to_table(*m_imgui, " ", ImGuiWrapper::COL_BLUE_LIGHT, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(" ", ImGuiPureWrap::COL_BLUE_LIGHT, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); } ImGui::EndTable(); @@ -2015,15 +2034,15 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit return text; }; - add_strings_row_to_table(*m_imgui, _u8L("Selection") + " 1:", ImGuiWrapper::to_ImVec4(SELECTED_1ST_COLOR), format_item_text(m_selected_features.first), - ImGuiWrapper::to_ImVec4(SELECTED_1ST_COLOR)); - add_strings_row_to_table(*m_imgui, _u8L("Selection") + " 2:", ImGuiWrapper::to_ImVec4(SELECTED_2ND_COLOR), format_item_text(m_selected_features.second), - ImGuiWrapper::to_ImVec4(SELECTED_2ND_COLOR)); + add_strings_row_to_table(_u8L("Selection") + " 1:", ImGuiPSWrap::to_ImVec4(SELECTED_1ST_COLOR), format_item_text(m_selected_features.first), + ImGuiPSWrap::to_ImVec4(SELECTED_1ST_COLOR)); + add_strings_row_to_table(_u8L("Selection") + " 2:", ImGuiPSWrap::to_ImVec4(SELECTED_2ND_COLOR), format_item_text(m_selected_features.second), + ImGuiPSWrap::to_ImVec4(SELECTED_2ND_COLOR)); ImGui::EndTable(); } m_imgui->disabled_begin(!m_selected_features.first.feature.has_value()); - if (m_imgui->button(_L("Restart selection"))) { + if (ImGuiPureWrap::button(_u8L("Restart selection"))) { m_selected_features.reset(); m_selected_sphere_raycasters.clear(); m_imgui->set_requires_extra_frame(); @@ -2033,11 +2052,11 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit auto add_measure_row_to_table = [this](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - m_imgui->text_colored(col_1_color, col_1); + ImGuiPureWrap::text_colored(col_1_color, col_1); ImGui::TableSetColumnIndex(1); - m_imgui->text_colored(col_2_color, col_2); + ImGuiPureWrap::text_colored(col_2_color, col_2); ImGui::TableSetColumnIndex(2); - if (m_imgui->image_button(ImGui::ClipboardBtnIcon, _L("Copy to clipboard"))) { + if (m_imgui->image_button(ImGui::ClipboardBtnIcon, _u8L("Copy to clipboard"))) { wxTheClipboard->Open(); wxTheClipboard->SetData(new wxTextDataObject(col_1 + ": " + col_2)); wxTheClipboard->Close(); @@ -2045,7 +2064,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit }; ImGui::Separator(); - m_imgui->text(_u8L("Measure")); + ImGuiPureWrap::text(_u8L("Measure")); const unsigned int max_measure_row_count = 2; unsigned int measure_row_count = 0; @@ -2054,7 +2073,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit const Measure::MeasurementResult& measure = m_measurement_result; if (measure.angle.has_value()) { ImGui::PushID("ClipboardAngle"); - add_measure_row_to_table(_u8L("Angle"), ImGuiWrapper::COL_BLUE_LIGHT, format_double(Geometry::rad2deg(measure.angle->angle)) + "°", + add_measure_row_to_table(_u8L("Angle"), ImGuiPureWrap::COL_BLUE_LIGHT, format_double(Geometry::rad2deg(measure.angle->angle)) + "°", ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++measure_row_count; ImGui::PopID(); @@ -2068,7 +2087,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit if (use_inches) distance = ObjectManipulation::mm_to_in * distance; ImGui::PushID("ClipboardDistanceInfinite"); - add_measure_row_to_table(show_strict ? _u8L("Perpendicular distance") : _u8L("Distance"), ImGuiWrapper::COL_BLUE_LIGHT, format_double(distance) + units, + add_measure_row_to_table(show_strict ? _u8L("Perpendicular distance") : _u8L("Distance"), ImGuiPureWrap::COL_BLUE_LIGHT, format_double(distance) + units, ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++measure_row_count; ImGui::PopID(); @@ -2078,7 +2097,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit if (use_inches) distance = ObjectManipulation::mm_to_in * distance; ImGui::PushID("ClipboardDistanceStrict"); - add_measure_row_to_table(_u8L("Direct distance"), ImGuiWrapper::COL_BLUE_LIGHT, format_double(distance) + units, + add_measure_row_to_table(_u8L("Direct distance"), ImGuiPureWrap::COL_BLUE_LIGHT, format_double(distance) + units, ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++measure_row_count; ImGui::PopID(); @@ -2088,7 +2107,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit if (use_inches) distance = ObjectManipulation::mm_to_in * distance; ImGui::PushID("ClipboardDistanceXYZ"); - add_measure_row_to_table(_u8L("Distance XYZ"), ImGuiWrapper::COL_BLUE_LIGHT, format_vec3(distance), + add_measure_row_to_table(_u8L("Distance XYZ"), ImGuiPureWrap::COL_BLUE_LIGHT, format_vec3(distance), ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++measure_row_count; ImGui::PopID(); @@ -2097,7 +2116,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit // add dummy rows to keep dialog size fixed for (unsigned int i = measure_row_count; i < max_measure_row_count; ++i) { - add_strings_row_to_table(*m_imgui, " ", ImGuiWrapper::COL_BLUE_LIGHT, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(" ", ImGuiPureWrap::COL_BLUE_LIGHT, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); } ImGui::EndTable(); } @@ -2110,7 +2129,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit m_imgui->set_requires_extra_frame(); } - m_imgui->end(); + ImGuiPureWrap::end(); } void GLGizmoMeasure::on_register_raycasters_for_picking() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 14a127e..1441285 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -57,14 +57,6 @@ bool GLGizmoMmuSegmentation::on_is_activable() const return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1; } -std::vector get_extruders_colors() -{ - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - std::vector ret; - decode_colors(colors, ret); - return ret; -} - static std::vector get_extruders_names() { size_t extruders_count = wxGetApp().extruders_edited_cnt(); @@ -93,7 +85,7 @@ static std::vector get_extruder_id_for_volumes(const ModelObject &model_obj void GLGizmoMmuSegmentation::init_extruders_data() { m_original_extruders_names = get_extruders_names(); - m_original_extruders_colors = get_extruders_colors(); + m_original_extruders_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); m_modified_extruders_colors = m_original_extruders_colors; m_first_selected_extruder_idx = 0; m_second_selected_extruder_idx = 1; @@ -103,28 +95,28 @@ bool GLGizmoMmuSegmentation::on_init() { m_shortcut_key = WXK_CONTROL_N; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["cursor_size"] = _L("Brush size") + ": "; - m_desc["cursor_type"] = _L("Brush shape"); - m_desc["first_color_caption"] = _L("Left mouse button") + ": "; - m_desc["first_color"] = _L("First color"); - m_desc["second_color_caption"] = _L("Right mouse button") + ": "; - m_desc["second_color"] = _L("Second color"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove painted color"); - m_desc["remove_all"] = _L("Clear all"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Triangles"); + m_desc["reset_direction"] = _u8L("Reset direction"); + m_desc["clipping_of_view"] = _u8L("Clipping of view") + ": "; + m_desc["cursor_size"] = _u8L("Brush size") + ": "; + m_desc["cursor_type"] = _u8L("Brush shape"); + m_desc["first_color_caption"] = _u8L("Left mouse button") + ": "; + m_desc["first_color"] = _u8L("First color"); + m_desc["second_color_caption"] = _u8L("Right mouse button") + ": "; + m_desc["second_color"] = _u8L("Second color"); + m_desc["remove_caption"] = _u8L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _u8L("Remove painted color"); + m_desc["remove_all"] = _u8L("Clear all"); + m_desc["circle"] = _u8L("Circle"); + m_desc["sphere"] = _u8L("Sphere"); + m_desc["pointer"] = _u8L("Triangles"); - m_desc["tool_type"] = _L("Tool type"); - m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_smart_fill"] = _L("Smart fill"); - m_desc["tool_bucket_fill"] = _L("Bucket fill"); + m_desc["tool_type"] = _u8L("Tool type"); + m_desc["tool_brush"] = _u8L("Brush"); + m_desc["tool_smart_fill"] = _u8L("Smart fill"); + m_desc["tool_bucket_fill"] = _u8L("Bucket fill"); - m_desc["smart_fill_angle"] = _L("Smart fill angle"); - m_desc["split_triangles"] = _L("Split triangles"); + m_desc["smart_fill_angle"] = _u8L("Smart fill angle"); + m_desc["split_triangles"] = _u8L("Split triangles"); init_extruders_data(); @@ -155,7 +147,7 @@ void GLGizmoMmuSegmentation::data_changed(bool is_serializing) ModelObject *model_object = m_c->selection_info()->model_object(); if (int prev_extruders_count = int(m_original_extruders_colors.size()); - prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) { + prev_extruders_count != wxGetApp().extruders_edited_cnt() || wxGetApp().plater()->get_extruder_colors_from_plater_config() != m_original_extruders_colors) { if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)) show_notification_extruders_limit_exceeded(); @@ -232,7 +224,7 @@ static void render_extruders_combo(const std::string& label, ImGui::SameLine(); ImGuiStyle &style = ImGui::GetStyle(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), ImGuiWrapper::to_ImU32(extruders_colors[extruder_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), ImGuiPSWrap::to_ImU32(extruders_colors[extruder_idx])); ImGui::GetWindowDrawList()->AddRect(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(start_position.x + height + height / 2 + style.FramePadding.x, start_position.y)); @@ -250,7 +242,7 @@ static void render_extruders_combo(const std::string& label, ImVec2 p = ImGui::GetCursorScreenPos(); float height = ImGui::GetTextLineHeight(); - ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), ImGuiWrapper::to_ImU32(extruders_colors[selection_idx])); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), ImGuiPSWrap::to_ImU32(extruders_colors[selection_idx])); ImGui::GetWindowDrawList()->AddRect(p, ImVec2(p.x + height + height / 2, p.y + height), IM_COL32_BLACK); ImGui::SetCursorScreenPos(ImVec2(p.x + height + height / 2 + style.FramePadding.x, p.y)); @@ -268,44 +260,44 @@ 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); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_pointer = ImGuiPureWrap::calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float button_width = ImGuiPureWrap::calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float buttons_width = m_imgui->scaled(0.5f); const float minimal_slider_width = m_imgui->scaled(4.f); const float color_button_width = m_imgui->scaled(1.75f); - const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x, - m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f); + const float combo_label_width = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("first_color")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f); - const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); - const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"first_color", "second_color", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); - total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); + caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x); } total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); - const float slider_icon_width = m_imgui->get_slider_icon_size().x; + const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); @@ -314,10 +306,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); - auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, caption); + auto draw_text_with_caption = [&caption_max](const std::string &caption, const std::string& text) { + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, caption); ImGui::SameLine(caption_max); - m_imgui->text(text); + ImGuiPureWrap::text(text); }; for (const auto &t : std::array{"first_color", "second_color", "remove"}) @@ -326,47 +318,47 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::Separator(); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("first_color")); + ImGuiPureWrap::text(m_desc.at("first_color")); ImGui::SameLine(combo_label_width); ImGui::PushItemWidth(window_width - combo_label_width - color_button_width); render_extruders_combo("##first_color_combo", m_original_extruders_names, m_original_extruders_colors, m_first_selected_extruder_idx); ImGui::SameLine(); const ColorRGBA& select_first_color = m_modified_extruders_colors[m_first_selected_extruder_idx]; - ImVec4 first_color = ImGuiWrapper::to_ImVec4(select_first_color); + ImVec4 first_color = ImGuiPSWrap::to_ImVec4(select_first_color); const std::string first_label = into_u8(m_desc.at("first_color")) + "##color_picker"; if (ImGui::ColorEdit4(first_label.c_str(), (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel, // TRN Means "current color" _u8L("Current").c_str(), // TRN Means "original color" _u8L("Original").c_str())) - m_modified_extruders_colors[m_first_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(first_color); + m_modified_extruders_colors[m_first_selected_extruder_idx] = ImGuiPSWrap::from_ImVec4(first_color); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("second_color")); + ImGuiPureWrap::text(m_desc.at("second_color")); ImGui::SameLine(combo_label_width); ImGui::PushItemWidth(window_width - combo_label_width - color_button_width); render_extruders_combo("##second_color_combo", m_original_extruders_names, m_original_extruders_colors, m_second_selected_extruder_idx); ImGui::SameLine(); const ColorRGBA& select_second_color = m_modified_extruders_colors[m_second_selected_extruder_idx]; - ImVec4 second_color = ImGuiWrapper::to_ImVec4(select_second_color); + ImVec4 second_color = ImGuiPSWrap::to_ImVec4(select_second_color); const std::string second_label = into_u8(m_desc.at("second_color")) + "##color_picker"; if (ImGui::ColorEdit4(second_label.c_str(), (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel, _u8L("Current").c_str(), _u8L("Original").c_str())) - m_modified_extruders_colors[m_second_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(second_color); + m_modified_extruders_colors[m_second_selected_extruder_idx] = ImGuiPSWrap::from_ImVec4(second_color); const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::Separator(); - m_imgui->text(m_desc.at("tool_type")); + ImGuiPureWrap::text(m_desc.at("tool_type")); ImGui::NewLine(); float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(1.5f)) / 2.f; ImGui::SameLine(tool_type_offset); ImGui::PushItemWidth(tool_type_radio_brush); - if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) { + if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) { m_tool_type = ToolType::BRUSH; for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); @@ -375,11 +367,11 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); - if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) { + if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) { m_tool_type = ToolType::SMART_FILL; for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); @@ -388,11 +380,11 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_bucket_fill); - if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) { + if (ImGuiPureWrap::radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) { m_tool_type = ToolType::BUCKET_FILL; for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); @@ -401,60 +393,60 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints neighboring facets that have the same color."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets that have the same color."), max_tooltip_width); ImGui::Separator(); if(m_tool_type == ToolType::BRUSH) { - m_imgui->text(m_desc.at("cursor_type")); + ImGuiPureWrap::text(m_desc.at("cursor_type")); ImGui::NewLine(); float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; ImGui::SameLine(cursor_type_offset); ImGui::PushItemWidth(cursor_type_radio_sphere); - if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + if (ImGuiPureWrap::radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); - if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + if (ImGuiPureWrap::radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); ImGui::PushItemWidth(cursor_type_radio_pointer); - if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) + if (ImGuiPureWrap::radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) m_cursor_type = TriangleSelector::CursorType::POINTER; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints only one facet."), max_tooltip_width); m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_size")); + ImGuiPureWrap::text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); - m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); + ImGuiPureWrap::checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width); m_imgui->disabled_end(); ImGui::Separator(); } else if(m_tool_type == ToolType::SMART_FILL) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["smart_fill_angle"] + ":"); + ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":"); std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); @@ -470,9 +462,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); + ImGuiPureWrap::text(m_desc.at("clipping_of_view")); } else { - if (m_imgui->button(m_desc.at("reset_direction"))) { + if (ImGuiPureWrap::button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -480,11 +472,11 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott auto clp_dist = float(m_c->object_clipper()->get_position()); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, from_u8(GUI::shortkey_ctrl_prefix()) + _L("Mouse wheel"))) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); ImGui::Separator(); - if (m_imgui->button(m_desc.at("remove_all"))) { + if (ImGuiPureWrap::button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); ModelObject * mo = m_c->selection_info()->model_object(); @@ -500,7 +492,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_parent.set_as_dirty(); } - m_imgui->end(); + ImGuiPureWrap::end(); } void GLGizmoMmuSegmentation::update_model_object() const @@ -525,7 +517,7 @@ void GLGizmoMmuSegmentation::update_model_object() const void GLGizmoMmuSegmentation::init_model_triangle_selectors() { const int extruders_count = wxGetApp().extruders_edited_cnt(); - const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelObject *mo = m_c->selection_info()->model_object(); m_triangle_selectors.clear(); // Don't continue when extruders colors are not initialized @@ -539,7 +531,7 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors() // This mesh does not account for the possible Z up SLA offset. const TriangleMesh *mesh = &mv->mesh(); - size_t extruder_idx = get_extruder_color_idx(*mv, extruders_count); + const size_t extruder_idx = ModelVolume::get_extruder_color_idx(*mv, extruders_count); m_triangle_selectors.emplace_back(std::make_unique(*mesh, m_modified_extruders_colors, m_original_extruders_colors[extruder_idx])); // Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize(). m_triangle_selectors.back()->deserialize(mv->mm_segmentation_facets.get_data(), false); @@ -555,7 +547,7 @@ void GLGizmoMmuSegmentation::update_from_model_object() // Extruder colors need to be reloaded before calling init_model_triangle_selectors to render painted triangles // using colors from loaded 3MF and not from printer profile in Slicer. if (int prev_extruders_count = int(m_original_extruders_colors.size()); - prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) + prev_extruders_count != wxGetApp().extruders_edited_cnt() || wxGetApp().plater()->get_extruder_colors_from_plater_config() != m_original_extruders_colors) this->init_extruders_data(); this->init_model_triangle_selectors(); @@ -588,6 +580,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper* imgui, const Transform3d& matri auto *shader = wxGetApp().get_current_shader(); if (!shader) return; + assert(shader->get_name() == "mm_gouraud"); for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx) { @@ -658,12 +651,16 @@ void GLMmSegmentationGizmo3DScene::release_geometry() { glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id)); triangle_indices_VBO_id = 0; } -#if ENABLE_GL_CORE_PROFILE - if (this->vertices_VAO_id > 0) { - glsafe(::glDeleteVertexArrays(1, &this->vertices_VAO_id)); - this->vertices_VAO_id = 0; +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + if (this->vertices_VAO_id > 0) { + glsafe(::glDeleteVertexArrays(1, &this->vertices_VAO_id)); + this->vertices_VAO_id = 0; + } +#if !SLIC3R_OPENGL_ES } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES this->clear(); } @@ -672,10 +669,13 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const { assert(triangle_indices_idx < this->triangle_indices_VBO_ids.size()); assert(this->triangle_indices_sizes.size() == this->triangle_indices_VBO_ids.size()); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES assert(this->vertices_VAO_id != 0); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES assert(this->vertices_VBO_id != 0); assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0); @@ -683,11 +683,14 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const if (shader == nullptr) return; -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(this->vertices_VAO_id)); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES // the following binding is needed to set the vertex attributes -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id)); const GLint position_id = shader->get_attrib_location("v_position"); if (position_id != -1) { @@ -707,25 +710,34 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const glsafe(::glDisableVertexAttribArray(position_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES } void GLMmSegmentationGizmo3DScene::finalize_vertices() { -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES assert(this->vertices_VAO_id == 0); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES assert(this->vertices_VBO_id == 0); if (!this->vertices.empty()) { -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) { +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glGenVertexArrays(1, &this->vertices_VAO_id)); glsafe(::glBindVertexArray(this->vertices_VAO_id)); +#if !SLIC3R_OPENGL_ES } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES glsafe(::glGenBuffers(1, &this->vertices_VBO_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id)); @@ -733,10 +745,13 @@ void GLMmSegmentationGizmo3DScene::finalize_vertices() glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); this->vertices.clear(); -#if ENABLE_GL_CORE_PROFILE - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) - glsafe(::glBindVertexArray(0)); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + glsafe(::glBindVertexArray(0)); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES } } @@ -745,7 +760,7 @@ void GLMmSegmentationGizmo3DScene::finalize_triangle_indices() assert(std::all_of(triangle_indices_VBO_ids.cbegin(), triangle_indices_VBO_ids.cend(), [](const auto &ti_VBO_id) { return ti_VBO_id == 0; })); assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size()); - for (size_t buffer_idx = 0; buffer_idx < this->triangle_indices.size(); ++buffer_idx) + for (size_t buffer_idx = 0; buffer_idx < this->triangle_indices.size(); ++buffer_idx) { if (!this->triangle_indices[buffer_idx].empty()) { glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx])); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx])); @@ -753,6 +768,7 @@ void GLMmSegmentationGizmo3DScene::finalize_triangle_indices() glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); this->triangle_indices[buffer_idx].clear(); } + } } } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 1d53aed..d520ea2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -57,9 +57,7 @@ public: // IDs of the Vertex Array Objects, into which the geometry has been loaded. // Zero if the VBOs are not sent to GPU yet. -#if ENABLE_GL_CORE_PROFILE unsigned int vertices_VAO_id{ 0 }; -#endif // ENABLE_GL_CORE_PROFILE unsigned int vertices_VBO_id{ 0 }; std::vector triangle_indices_VBO_ids; }; @@ -70,6 +68,7 @@ public: // Plus 1 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the indices above colors.size() are allocated for seed fill. explicit TriangleSelectorMmGui(const TriangleMesh& mesh, const std::vector& colors, const ColorRGBA& default_volume_color) : TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(2 * (colors.size() + 1)) {} + ~TriangleSelectorMmGui() override = default; void render(ImGuiWrapper* imgui, const Transform3d& matrix) override; @@ -107,8 +106,8 @@ protected: ColorRGBA get_cursor_sphere_left_button_color() const override; ColorRGBA get_cursor_sphere_right_button_color() const override; - EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_first_selected_extruder_idx + 1); } - EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType(m_second_selected_extruder_idx + 1); } + TriangleStateType get_left_button_state_type() const override { return TriangleStateType(m_first_selected_extruder_idx + 1); } + TriangleStateType get_right_button_state_type() const override { return TriangleStateType(m_second_selected_extruder_idx + 1); } void on_render_input_window(float x, float y, float bottom_limit) override; std::string on_get_name() const override; @@ -146,18 +145,9 @@ 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; + std::map m_desc; }; -std::vector get_extruders_colors(); - -inline size_t get_extruder_color_idx(const ModelVolume &model_volume, const int extruders_count) -{ - if (const int extruder_id = model_volume.extruder_id(); extruder_id <= 0 || extruder_id > extruders_count) - return 0; - else - return extruder_id - 1; -} } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index d3538a7..f013c1f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -130,10 +130,10 @@ void GLGizmoMove3D::on_render() m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset }; m_grabbers[2].color = AXES_COLOR[2]; -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); +#endif // !SLIC3R_OPENGL_ES auto render_grabber_connection = [this, &zero](unsigned int id) { if (m_grabbers[id].enabled) { @@ -162,22 +162,26 @@ void GLGizmoMove3D::on_render() }; if (m_hover_id == -1) { -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES // draw axes for (unsigned int i = 0; i < 3; ++i) { @@ -192,23 +196,27 @@ void GLGizmoMove3D::on_render() } else { // draw axis -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.5f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif /// !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.5f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES render_grabber_connection(m_hover_id); shader->stop_using(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index fee49cd..22c23a3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -154,103 +154,115 @@ void GLGizmoPainterBase::render_cursor_circle() const float cnv_inv_height = 1.0f / cnv_height; const Vec2d center = m_parent.get_local_mouse_position(); -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES const float zoom = float(wxGetApp().plater()->get_camera().get_zoom()); const float radius = m_cursor_radius * zoom; -#else - const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom()); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(1.5f)); +#endif // !SLIC3R_OPENGL_ES + glsafe(::glDisable(GL_DEPTH_TEST)); -#if !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES - glsafe(::glPushAttrib(GL_ENABLE_BIT)); - glsafe(::glLineStipple(4, 0xAAAA)); - glsafe(::glEnable(GL_LINE_STIPPLE)); -#endif // !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES +#if !SLIC3R_OPENGL_ES + if (!OpenGLManager::get_gl_info().is_core_profile()) { + glsafe(::glPushAttrib(GL_ENABLE_BIT)); + glsafe(::glLineStipple(4, 0xAAAA)); + glsafe(::glEnable(GL_LINE_STIPPLE)); + } +#endif // !SLIC3R_OPENGL_ES -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - if (!m_circle.is_initialized() || std::abs(m_old_cursor_radius - radius) > EPSILON) { - m_old_cursor_radius = radius; - m_circle.reset(); -#else if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) { m_old_cursor_radius = radius; m_old_center = center; m_circle.reset(); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES GLModel::Geometry init_data; -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - const unsigned int StepsCount = (unsigned int)(2 * (4 + int(252 * (zoom - 1.0f) / (250.0f - 1.0f)))); - const float StepSize = 2.0f * float(PI) / float(StepsCount); - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2 }; -#else - static const unsigned int StepsCount = 32; - static const float StepSize = 2.0f * float(PI) / float(StepsCount); - init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES + unsigned int steps_count = 0; +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + steps_count = (unsigned int)(2 * (4 + int(252 * (zoom - 1.0f) / (250.0f - 1.0f)))); + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2 }; +#if !SLIC3R_OPENGL_ES + } + else { + steps_count = 32; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; + } +#endif // !SLIC3R_OPENGL_ES + const float step_size = 2.0f * float(PI) / float(steps_count); init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; - init_data.reserve_vertices(StepsCount); - init_data.reserve_indices(StepsCount); + init_data.reserve_vertices(steps_count); + init_data.reserve_indices(steps_count); // vertices + indices - for (unsigned int i = 0; i < StepsCount; ++i) { -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - if (i % 2 != 0) continue; + for (unsigned int i = 0; i < steps_count; ++i) { +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + if (i % 2 != 0) continue; - const float angle_i = float(i) * StepSize; - const unsigned int j = (i + 1) % StepsCount; - const float angle_j = float(j) * StepSize; - const Vec2d v_i(::cos(angle_i), ::sin(angle_i)); - const Vec2d v_j(::cos(angle_j), ::sin(angle_j)); - init_data.add_vertex(Vec2f(v_i.x(), v_i.y())); - init_data.add_vertex(Vec2f(v_j.x(), v_j.y())); - const size_t vcount = init_data.vertices_count(); - init_data.add_line(vcount - 2, vcount - 1); -#else - const float angle = float(i) * StepSize; - init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f), - -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f))); - init_data.add_index(i); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES + const float angle_i = float(i) * step_size; + const unsigned int j = (i + 1) % steps_count; + const float angle_j = float(j) * step_size; + const Vec2d v_i(::cos(angle_i), ::sin(angle_i)); + const Vec2d v_j(::cos(angle_j), ::sin(angle_j)); + init_data.add_vertex(Vec2f(v_i.x(), v_i.y())); + init_data.add_vertex(Vec2f(v_j.x(), v_j.y())); + const size_t vcount = init_data.vertices_count(); + init_data.add_line(vcount - 2, vcount - 1); +#if !SLIC3R_OPENGL_ES + } + else { + const float angle = float(i) * step_size; + init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f), + -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f))); + init_data.add_index(i); + } +#endif // !SLIC3R_OPENGL_ES } m_circle.init_from(std::move(init_data)); } -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - const Transform3d view_model_matrix = Geometry::translation_transform(Vec3d(2.0f * (center.x() * cnv_inv_width - 0.5f), -2.0f * (center.y() * cnv_inv_height - 0.5f), 0.0)) * - Geometry::scale_transform(Vec3d(2.0f * radius * cnv_inv_width, 2.0f * radius * cnv_inv_height, 1.0f)); - shader->set_uniform("view_model_matrix", view_model_matrix); -#else - shader->set_uniform("view_model_matrix", Transform3d::Identity()); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const Transform3d view_model_matrix = Geometry::translation_transform(Vec3d(2.0f * (center.x() * cnv_inv_width - 0.5f), -2.0f * (center.y() * cnv_inv_height - 0.5f), 0.0)) * + Geometry::scale_transform(Vec3d(2.0f * radius * cnv_inv_width, 2.0f * radius * cnv_inv_height, 1.0f)); + shader->set_uniform("view_model_matrix", view_model_matrix); +#if !SLIC3R_OPENGL_ES + } + else + shader->set_uniform("view_model_matrix", Transform3d::Identity()); +#endif // !SLIC3R_OPENGL_ES shader->set_uniform("projection_matrix", Transform3d::Identity()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES m_circle.render(); shader->stop_using(); } -#if !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES - glsafe(::glPopAttrib()); -#endif // !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES +#if !SLIC3R_OPENGL_ES + if (!OpenGLManager::get_gl_info().is_core_profile()) + glsafe(::glPopAttrib()); +#endif // !SLIC3R_OPENGL_ES glsafe(::glEnable(GL_DEPTH_TEST)); } @@ -487,7 +499,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_triangle_selectors.empty()) return false; - EnforcerBlockerType new_state = EnforcerBlockerType::NONE; + TriangleStateType new_state = TriangleStateType::NONE; if (! shift_down) { if (action == SLAGizmoEventType::Dragging) new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type(); @@ -921,16 +933,16 @@ void TriangleSelectorGUI::update_render_data() static const float offset = 0.001f; for (const Triangle &tr : m_triangles) { - if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) + if (!tr.valid() || tr.is_split() || (tr.get_state() == TriangleStateType::NONE && !tr.is_selected_by_seed_fill())) continue; int tr_state = int(tr.get_state()); - GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : - iva_blockers_data; - int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : - blc_cnt; + GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : + tr.get_state() == TriangleStateType::ENFORCER ? iva_enforcers_data : + iva_blockers_data; + int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : + tr.get_state() == TriangleStateType::ENFORCER ? enf_cnt : + blc_cnt; const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v; const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v; const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 9c0bc18..2698153 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -116,8 +116,8 @@ protected: virtual ColorRGBA get_cursor_sphere_left_button_color() const { return { 0.0f, 0.0f, 1.0f, 0.25f }; } virtual ColorRGBA get_cursor_sphere_right_button_color() const { return { 1.0f, 0.0f, 0.0f, 0.25f }; } - virtual EnforcerBlockerType get_left_button_state_type() const { return EnforcerBlockerType::ENFORCER; } - virtual EnforcerBlockerType get_right_button_state_type() const { return EnforcerBlockerType::BLOCKER; } + virtual TriangleStateType get_left_button_state_type() const { return TriangleStateType::ENFORCER; } + virtual TriangleStateType get_right_button_state_type() const { return TriangleStateType::BLOCKER; } float m_cursor_radius = 2.f; static constexpr float CursorRadiusMin = 0.4f; // cannot be zero @@ -150,9 +150,7 @@ protected: float m_highlight_by_angle_threshold_deg = 0.f; GLModel m_circle; -#if !ENABLE_GL_CORE_PROFILE Vec2d m_old_center{ Vec2d::Zero() }; -#endif // !ENABLE_GL_CORE_PROFILE float m_old_cursor_radius{ 0.0f }; static constexpr float SmartFillAngleMin = 0.0f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index be7ddf3..1e30911 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -149,16 +149,16 @@ void GLGizmoRotate::on_render() m_grabbers.front().matrix = local_transform(selection); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); +#endif // !SLIC3R_OPENGL_ES -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); @@ -166,12 +166,16 @@ void GLGizmoRotate::on_render() const Transform3d view_model_matrix = camera.get_view_matrix() * m_grabbers.front().matrix; shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES const bool radius_changed = std::abs(m_old_radius - m_radius) > EPSILON; m_old_radius = m_radius; @@ -204,7 +208,6 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_center = sphere.first; m_radius = Offset + sphere.second; m_orient_matrix = box_trafo; - m_orient_matrix.translation() = m_center; m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; @@ -626,9 +629,8 @@ void GLGizmoRotate3D::on_unregister_raycasters_for_picking() GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, State & state, const Alignment &alignment) - : m_imgui{imgui} { - imgui->begin(_L("Optimize orientation"), ImGuiWindowFlags_NoMove | + ImGuiPureWrap::begin(_u8L("Optimize orientation"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); @@ -675,7 +677,7 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, ImGui::Separator(); - auto btn_txt = _L("Apply"); + auto btn_txt = _u8L("Apply"); auto btn_txt_sz = ImGui::CalcTextSize(btn_txt.c_str()); ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y}; ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x); @@ -683,7 +685,7 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, if (!wxGetApp().plater()->get_ui_job_worker().is_idle()) imgui->disabled_begin(true); - if ( imgui->button(btn_txt) ) { + if ( ImGuiPureWrap::button(btn_txt) ) { replace_job(wxGetApp().plater()->get_ui_job_worker(), std::make_unique()); } @@ -693,7 +695,7 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow() { - m_imgui->end(); + ImGuiPureWrap::end(); } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index bbc32ec..15b932c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -173,8 +173,6 @@ private: class RotoptimzeWindow { - ImGuiWrapper *m_imgui = nullptr; - public: struct State { float accuracy = 1.f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 24841c3..dbacc21 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -27,12 +27,12 @@ #include // detection of change DPI #include +#include #include #include // measure enumeration of fonts #include // save for svg #include -#include using namespace Slic3r; using namespace Slic3r::Emboss; @@ -58,6 +58,7 @@ const std::string rotation_snapshot_name = L("SVG rotate"); // TRN - Title in Undo/Redo stack after move with SVG along emboss axe - From surface const std::string move_snapshot_name = L("SVG move"); // NOTE: Translation is made in "m_parent.do_translate()" + // Variable keep limits for variables const struct Limits { @@ -117,15 +118,6 @@ std::string get_file_name(const std::string &file_path); /// Name for volume std::string volume_name(const EmbossShape& shape); -/// -/// Create input for volume creation -/// -/// parent of gizmo -/// Keep scene -/// Type of volume to be created -/// Params -CreateVolumeParams create_input(GLCanvas3D &canvas, RaycastManager &raycaster, ModelVolumeType volume_type); - enum class IconType : unsigned { reset_value, reset_value_hover, @@ -197,33 +189,40 @@ struct GLGizmoSVG::GuiCfg: public ::GuiCfg{}; bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) { - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - 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); + CreateVolumeParams input = create_input(volume_type); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume(input, mouse_pos); } bool GLGizmoSVG::create_volume(ModelVolumeType volume_type) { - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - 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)); + CreateVolumeParams input = create_input(volume_type); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume_without_position(input); } bool GLGizmoSVG::create_volume(std::string_view svg_file, ModelVolumeType volume_type){ - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - 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)); + CreateVolumeParams input = create_input(volume_type, svg_file); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume_without_position(input); } bool GLGizmoSVG::create_volume(std::string_view svg_file, const Vec2d &mouse_pos, ModelVolumeType volume_type) { - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - 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); + CreateVolumeParams input = create_input(volume_type, svg_file); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume(input, mouse_pos); +} + +CreateVolumeParams GLGizmoSVG::create_input(ModelVolumeType volume_type, std::string_view svg_filepath) { + // Be carefull it must be before call create_emboss_data_base + const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); + // NOTE: During selection of file it could change hovered volume + DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type, svg_filepath); + auto gizmo = static_cast(GLGizmosManager::Svg); + Plater *plater = wxGetApp().plater(); + return CreateVolumeParams{std::move(base), m_parent, plater->get_camera(), plater->build_volume(), + plater->get_ui_job_worker(), volume_type, m_raycast_manager, gizmo, gl_volume}; } bool GLGizmoSVG::is_svg(const ModelVolume &volume) { @@ -330,6 +329,37 @@ void GLGizmoSVG::volume_transformation_changed() calculate_scale(); } +void GLGizmoSVG::on_mouse_confirm_edit(const wxMouseEvent &mouse_event) { + // Fix phanthom transformation + // appear when mouse click into scene during edit Rotation in input (click "Edit" button) + // this must happen just before unselect selection (to find current volume) + static bool was_dragging = true; + if ((mouse_event.LeftUp() || mouse_event.RightUp()) && + m_parent.get_first_hover_volume_idx() < 0 && + !was_dragging && + m_volume != nullptr && + m_volume->is_svg() ) { + // current volume + const GLVolume *gl_volume_ptr = m_parent.get_selection().get_first_volume(); + assert(gl_volume_ptr->geometry_id.first == m_volume->id().id); + if (gl_volume_ptr != nullptr) { + const Transform3d &v_tr = m_volume->get_matrix(); + const Transform3d &gl_v_tr = gl_volume_ptr->get_volume_transformation().get_matrix(); + + const Matrix3d &v_rot = v_tr.linear(); + const Matrix3d &gl_v_rot = gl_v_tr.linear(); + const Vec3d &v_move = v_tr.translation(); + const Vec3d &gl_v_move = gl_v_tr.translation(); + if (!is_approx(v_rot, gl_v_rot)) { + m_parent.do_rotate(rotation_snapshot_name); + } else if (!is_approx(v_move, gl_v_move)) { + m_parent.do_move(move_snapshot_name); + } + } + } + was_dragging = mouse_event.Dragging(); +} + bool GLGizmoSVG::on_mouse(const wxMouseEvent &mouse_event) { // not selected volume @@ -339,7 +369,7 @@ bool GLGizmoSVG::on_mouse(const wxMouseEvent &mouse_event) if (on_mouse_for_rotation(mouse_event)) return true; if (on_mouse_for_translate(mouse_event)) return true; - + on_mouse_confirm_edit(mouse_event); return false; } @@ -485,7 +515,7 @@ void GLGizmoSVG::on_render_input_window(float x, float y, float bottom_limit) ImVec4(1.f, .3f, .3f, .75f) ); // Warning color const float radius = 16.f; - ImGuiWrapper::draw_cross_hair(center, radius, color); + ImGuiPureWrap::draw_cross_hair(center, radius, color); } // check if is set window offset @@ -524,7 +554,7 @@ void GLGizmoSVG::on_set_state() set_volume_by_selection(); m_set_window_offset = (m_gui_cfg != nullptr) ? - ImGuiWrapper::change_window_position(on_get_name().c_str(), false) : ImVec2(-1, -1); + ImGuiPureWrap::change_window_position(on_get_name().c_str(), false) : ImVec2(-1, -1); } } @@ -1166,7 +1196,7 @@ void GLGizmoSVG::set_volume_by_selection() // 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(); + ImGuiPureWrap::left_inputs(); // is valid svg volume? if (!is_svg(*volume)) @@ -1436,7 +1466,7 @@ void GLGizmoSVG::draw_filename(){ // 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); + m_filename_preview = ImGuiPureWrap::trunc(m_filename_preview, m_gui_cfg->input_width); } if (!m_shape_warnings.empty()){ @@ -1460,7 +1490,7 @@ void GLGizmoSVG::draw_filename(){ ImGui::Text("%s", m_filename_preview.c_str()); bool is_hovered = ImGui::IsItemHovered(); ImGui::SameLine(); - m_imgui->text_colored(ImGuiWrapper::COL_GREY_LIGHT, ".svg"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_GREY_LIGHT, ".svg"); ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing is_hovered |= ImGui::IsItemHovered(); @@ -1508,7 +1538,7 @@ void GLGizmoSVG::draw_filename(){ if (m_volume->emboss_shape->svg_file->path.empty()){ draw(get_icon(m_icons, IconType::bake_inactive)); ImGui::SameLine(); - m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, forget_path.c_str()); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_GREY_DARK, forget_path.c_str()); } else { draw(get_icon(m_icons, IconType::bake)); ImGui::SameLine(); @@ -1564,21 +1594,18 @@ void GLGizmoSVG::draw_filename(){ 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); + std::string out_path_str(into_u8(dlg.GetPath())); + boost::nowide::ofstream stream(out_path_str); 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 = out_path_str; 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"; + BOOST_LOG_TRIVIAL(error) << "Opening file: \"" << out_path_str << "\" Failed"; } } @@ -1621,7 +1648,7 @@ void GLGizmoSVG::draw_filename(){ void GLGizmoSVG::draw_depth() { - ImGuiWrapper::text(m_gui_cfg->translations.depth); + ImGuiPureWrap::text(m_gui_cfg->translations.depth); ImGui::SameLine(m_gui_cfg->input_offset); ImGui::SetNextItemWidth(m_gui_cfg->input_width); @@ -1657,7 +1684,7 @@ void GLGizmoSVG::draw_depth() void GLGizmoSVG::draw_size() { - ImGuiWrapper::text(m_gui_cfg->translations.size); + ImGuiPureWrap::text(m_gui_cfg->translations.size); if (ImGui::IsItemHovered()){ size_t count_points = 0; for (const auto &s : m_volume_shape.shapes_with_ids) @@ -1689,6 +1716,7 @@ void GLGizmoSVG::draw_size() std::optional new_relative_scale; bool make_snap = false; + if (m_keep_ratio) { std::stringstream ss; ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? "in" : "mm"); @@ -1794,6 +1822,7 @@ void GLGizmoSVG::draw_size() process(false); } } + if (make_snap) process(); // make undo/redo snap-shot } @@ -1805,7 +1834,7 @@ void GLGizmoSVG::draw_use_surface() m_imgui->disabled_begin(!can_use_surface); ScopeGuard sc([imgui = m_imgui]() { imgui->disabled_end(); }); - ImGuiWrapper::text(m_gui_cfg->translations.use_surface); + ImGuiPureWrap::text(m_gui_cfg->translations.use_surface); ImGui::SameLine(m_gui_cfg->input_offset); if (ImGui::Checkbox("##useSurface", &m_volume_shape.projection.use_surface)) @@ -1825,7 +1854,7 @@ void GLGizmoSVG::draw_distance() m_imgui->disabled_begin(!allowe_surface_distance); ScopeGuard sg([imgui = m_imgui]() { imgui->disabled_end(); }); - ImGuiWrapper::text(m_gui_cfg->translations.distance); + ImGuiPureWrap::text(m_gui_cfg->translations.distance); ImGui::SameLine(m_gui_cfg->input_offset); ImGui::SetNextItemWidth(m_gui_cfg->input_width); @@ -1849,7 +1878,6 @@ void GLGizmoSVG::draw_distance() if (m_imgui->slider_optional_float("##distance", m_distance, min_distance, max_distance, "%.2f mm", 1.f, false, move_tooltip)) 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()) { @@ -1868,7 +1896,7 @@ void GLGizmoSVG::draw_distance() void GLGizmoSVG::draw_rotation() { - ImGuiWrapper::text(m_gui_cfg->translations.rotation); + ImGuiPureWrap::text(m_gui_cfg->translations.rotation); ImGui::SameLine(m_gui_cfg->input_offset); ImGui::SetNextItemWidth(m_gui_cfg->input_width); @@ -1878,21 +1906,18 @@ void GLGizmoSVG::draw_rotation() // 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."))){ + if (m_imgui->slider_float("##angle", &angle_deg, limits.angle.min, limits.angle.max, u8"%.2f °", 1.f, false, _L("Rotate 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); + if (!is_approx(diff_angle, 0.)) { + 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(); + // calc angle after rotation + m_angle = calc_angle(m_parent.get_selection()); + } } bool is_stop_sliding = m_imgui->get_last_slider_status().deactivated_after_edit; @@ -1902,18 +1927,20 @@ void GLGizmoSVG::draw_rotation() 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(); is_reseted = true; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Reset rotation").c_str()); } // Apply rotation on model (backend) - if (is_stop_sliding || is_reseted) - m_parent.do_rotate(rotation_snapshot_name); + if (is_stop_sliding || is_reseted) { + m_parent.do_rotate(rotation_snapshot_name); + + // recalculate for surface cut + if (m_volume->emboss_shape->projection.use_surface) + process(); + } + // Keep up - lock button icon if (!m_volume->is_the_only_one_part()) { ImGui::SameLine(m_gui_cfg->lock_offset); @@ -1972,7 +1999,7 @@ void GLGizmoSVG::draw_model_type() 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()); + ImGuiPureWrap::text_colored(color, title.c_str()); } else { ImGui::Text("%s", title.c_str()); } @@ -2082,15 +2109,6 @@ std::string volume_name(const EmbossShape &shape) return "SVG shape"; } -CreateVolumeParams create_input(GLCanvas3D &canvas, RaycastManager& raycaster, ModelVolumeType volume_type) -{ - auto gizmo = static_cast(GLGizmosManager::Svg); - const GLVolume *gl_volume = get_first_hovered_gl_volume(canvas); - Plater *plater = wxGetApp().plater(); - return CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), - plater->get_ui_job_worker(), volume_type, raycaster, gizmo, gl_volume}; -} - GuiCfg create_gui_configuration() { GuiCfg cfg; // initialize by default values; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 103051c..24d8c73 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -24,6 +24,9 @@ namespace Slic3r{ class ModelVolume; enum class ModelVolumeType : int; +namespace GUI::Emboss { +struct CreateVolumeParams; +} } namespace Slic3r::GUI { @@ -129,9 +132,12 @@ private: // process mouse event bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); bool on_mouse_for_translate(const wxMouseEvent &mouse_event); + void on_mouse_confirm_edit(const wxMouseEvent &mouse_event); void volume_transformation_changed(); + Emboss::CreateVolumeParams create_input(ModelVolumeType volume_type, std::string_view svg_filepath = ""); + struct GuiCfg; std::unique_ptr m_gui_cfg; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1a7251d..7b1940e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -160,31 +160,35 @@ void GLGizmoScale3D::on_render() update_render_data(); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); +#endif // !SLIC3R_OPENGL_ES const float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); if (m_hover_id == -1) { // draw connections -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES if (m_grabbers[0].enabled && m_grabbers[1].enabled) render_grabbers_connection(0, 1, m_grabbers[0].color); if (m_grabbers[2].enabled && m_grabbers[3].enabled) @@ -203,22 +207,26 @@ void GLGizmoScale3D::on_render() } else if ((m_hover_id == 0 || m_hover_id == 1) && m_grabbers[0].enabled && m_grabbers[1].enabled) { // draw connections -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES render_grabbers_connection(0, 1, m_grabbers[0].color); shader->stop_using(); } @@ -234,22 +242,26 @@ void GLGizmoScale3D::on_render() } else if ((m_hover_id == 2 || m_hover_id == 3) && m_grabbers[2].enabled && m_grabbers[3].enabled) { // draw connections -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES render_grabbers_connection(2, 3, m_grabbers[2].color); shader->stop_using(); } @@ -265,22 +277,26 @@ void GLGizmoScale3D::on_render() } else if ((m_hover_id == 4 || m_hover_id == 5) && m_grabbers[4].enabled && m_grabbers[5].enabled) { // draw connections -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES render_grabbers_connection(4, 5, m_grabbers[4].color); shader->stop_using(); } @@ -296,22 +312,26 @@ void GLGizmoScale3D::on_render() } else if (m_hover_id >= 6) { // draw connections -#if ENABLE_GL_CORE_PROFILE - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#if SLIC3R_OPENGL_ES + GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines"); #else - GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#endif // SLIC3R_OPENGL_ES if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#if ENABLE_GL_CORE_PROFILE - const std::array& viewport = camera.get_viewport(); - shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); - shader->set_uniform("width", 0.25f); - shader->set_uniform("gap_size", 0.0f); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES render_grabbers_connection(6, 7, m_drag_color); render_grabbers_connection(7, 8, m_drag_color); render_grabbers_connection(8, 9, m_drag_color); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 213728f..63f4beb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -28,19 +28,19 @@ bool GLGizmoSeam::on_init() { m_shortcut_key = WXK_CONTROL_P; - m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size"] = _L("Brush size") + ": "; - m_desc["cursor_type"] = _L("Brush shape") + ": "; - m_desc["enforce_caption"] = _L("Left mouse button") + ": "; - m_desc["enforce"] = _L("Enforce seam"); - m_desc["block_caption"] = _L("Right mouse button") + ": "; - m_desc["block"] = _L("Block seam"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all selection"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); + m_desc["clipping_of_view"] = _u8L("Clipping of view") + ": "; + m_desc["reset_direction"] = _u8L("Reset direction"); + m_desc["cursor_size"] = _u8L("Brush size") + ": "; + m_desc["cursor_type"] = _u8L("Brush shape") + ": "; + m_desc["enforce_caption"] = _u8L("Left mouse button") + ": "; + m_desc["enforce"] = _u8L("Enforce seam"); + m_desc["block_caption"] = _u8L("Right mouse button") + ": "; + m_desc["block"] = _u8L("Block seam"); + m_desc["remove_caption"] = _u8L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _u8L("Remove selection"); + m_desc["remove_all"] = _u8L("Remove all selection"); + m_desc["circle"] = _u8L("Circle"); + m_desc["sphere"] = _u8L("Sphere"); return true; } @@ -77,42 +77,42 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) const float approx_height = m_imgui->scaled(12.5f); y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float cursor_size_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc["cursor_type"]).x + m_imgui->scaled(1.f); - const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_left = ImGuiPureWrap::calc_text_size(m_desc["cursor_type"]).x + m_imgui->scaled(1.f); + const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float button_width = ImGuiPureWrap::calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); float caption_max = 0.f; float total_text_max = 0.f; for (const auto &t : std::array{"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); - total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); + caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x); } total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left); - const float slider_icon_width = m_imgui->get_slider_icon_size().x; + const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_sphere + cursor_type_radio_circle); //B18 - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, caption); + auto draw_text_with_caption = [&caption_max](const std::string& caption, const std::string& text) { + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, caption); ImGui::SameLine(caption_max); - m_imgui->text(text); + ImGuiPureWrap::text(text); }; for (const auto &t : std::array{"enforce", "block", "remove"}) @@ -123,38 +123,38 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_size")); + ImGuiPureWrap::text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_type")); + ImGuiPureWrap::text(m_desc.at("cursor_type")); float cursor_type_offset = cursor_type_radio_left + (window_width - cursor_type_radio_left - cursor_type_radio_sphere - cursor_type_radio_circle + m_imgui->scaled(0.5f)) / 2.f; ImGui::SameLine(cursor_type_offset); ImGui::PushItemWidth(cursor_type_radio_sphere); - if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + if (ImGuiPureWrap::radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) m_cursor_type = TriangleSelector::CursorType::SPHERE; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); ImGui::PushItemWidth(cursor_type_radio_circle); - if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + if (ImGuiPureWrap::radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) m_cursor_type = TriangleSelector::CursorType::CIRCLE; if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); + ImGuiPureWrap::tooltip(_u8L("Ignores facets facing away from the camera."), max_tooltip_width); ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); + ImGuiPureWrap::text(m_desc.at("clipping_of_view")); } else { - if (m_imgui->button(m_desc.at("reset_direction"))) { + if (ImGuiPureWrap::button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ m_c->object_clipper()->set_position_by_ratio(-1., false); }); @@ -164,11 +164,11 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) auto clp_dist = float(m_c->object_clipper()->get_position()); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, from_u8(GUI::shortkey_ctrl_prefix()) + _L("Mouse wheel"))) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); ImGui::Separator(); - if (m_imgui->button(m_desc.at("remove_all"))) { + if (ImGuiPureWrap::button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); ModelObject *mo = m_c->selection_info()->model_object(); int idx = -1; @@ -183,7 +183,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) m_parent.set_as_dirty(); } - m_imgui->end(); + ImGuiPureWrap::end(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index c1a6d7d..3d94c1b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -37,7 +37,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; + std::map m_desc; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index c686de3..8c3e0a8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -64,7 +64,6 @@ static std::set get_selected_volume_ids(const Selection &selection) return result; } - static std::string create_volumes_name(const std::set& ids, const Selection &selection){ assert(!ids.empty()); std::string name; @@ -259,15 +258,15 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi bool is_multipart = (m_volume_ids.size() > 1); int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; - m_imgui->begin(on_get_name(), flag); + ImGuiPureWrap::begin(on_get_name(), flag); //B18 - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, tr_mesh_name + ":"); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, tr_mesh_name + ":"); ImGui::SameLine(m_gui_cfg->top_left_width); - m_imgui->text(m_volumes_name); - m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, tr_triangles + ":"); + ImGuiPureWrap::text(m_volumes_name); + ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_BLUE_LIGHT, tr_triangles + ":"); ImGui::SameLine(m_gui_cfg->top_left_width); - m_imgui->text(std::to_string(m_original_triangle_count)); + ImGuiPureWrap::text(std::to_string(m_original_triangle_count)); ImGui::Separator(); @@ -341,7 +340,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe); m_imgui->disabled_begin(is_cancelling); - if (m_imgui->button(_L("Close"))) { + if (ImGuiPureWrap::button(_u8L("Close"))) { close(); } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling) ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str()); @@ -350,7 +349,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(); m_imgui->disabled_begin(is_worker_running || ! is_result_ready); - if (m_imgui->button(_L("Apply"))) { + if (ImGuiPureWrap::button(_u8L("Apply"))) { apply_simplify(); } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running) ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str()); @@ -364,7 +363,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImVec2 progress_size(m_gui_cfg->input_width, 0.f); ImGui::ProgressBar(progress / 100., progress_size, progress_text.c_str()); } - m_imgui->end(); + ImGuiPureWrap::end(); if (start_process) process(); } @@ -577,16 +576,16 @@ void GLGizmoSimplify::on_set_state() void GLGizmoSimplify::create_gui_cfg() { if (m_gui_cfg.has_value()) return; - int space_size = m_imgui->calc_text_size(std::string_view{":MM"}).x; + int space_size = ImGuiPureWrap::calc_text_size(std::string_view{":MM"}).x; GuiCfg cfg; - cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x, - m_imgui->calc_text_size(tr_triangles).x) + cfg.top_left_width = std::max(ImGuiPureWrap::calc_text_size(tr_mesh_name).x, + ImGuiPureWrap::calc_text_size(tr_triangles).x) + space_size; const float radio_size = ImGui::GetFrameHeight(); cfg.bottom_left_width = - std::max(m_imgui->calc_text_size(tr_detail_level).x, - m_imgui->calc_text_size(tr_decimate_ratio).x) + + std::max(ImGuiPureWrap::calc_text_size(tr_detail_level).x, + ImGuiPureWrap::calc_text_size(tr_decimate_ratio).x) + space_size + radio_size; cfg.input_width = cfg.bottom_left_width * 1.5; @@ -675,7 +674,7 @@ void GLGizmoSimplify::update_model(const State::Data &data) auto color = glmodel.get_color(); // when not reset it keeps old shape glmodel.reset(); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3E3 }; init_data.reserve_vertices(3 * its.indices.size()); @@ -698,7 +697,7 @@ void GLGizmoSimplify::update_model(const State::Data &data) glmodel.init_from(std::move(init_data)); #else glmodel.init_from(its); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES glmodel.set_color(color); m_triangle_count += its.indices.size(); @@ -735,11 +734,7 @@ void GLGizmoSimplify::on_render() const Transform3d trafo_matrix = selected_volume->world_matrix(); auto* gouraud_shader = wxGetApp().get_shader("gouraud_light"); -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES bool depth_test_enabled = ::glIsEnabled(GL_DEPTH_TEST); -#else - glsafe(::glPushAttrib(GL_DEPTH_TEST)); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES glsafe(::glEnable(GL_DEPTH_TEST)); gouraud_shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); @@ -753,37 +748,31 @@ void GLGizmoSimplify::on_render() gouraud_shader->stop_using(); if (m_show_wireframe) { -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES auto* contour_shader = wxGetApp().get_shader("wireframe"); #else auto *contour_shader = wxGetApp().get_shader("mm_contour"); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES contour_shader->start_using(); contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); contour_shader->set_uniform("view_model_matrix", view_model_matrix); contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); const ColorRGBA color = glmodel.get_color(); glmodel.set_color(ColorRGBA::WHITE()); -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth(1.0f)); -#if !ENABLE_OPENGL_ES glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); -#endif // !ENABLE_OPENGL_ES +#endif // !SLIC3R_OPENGL_ES glmodel.render(); -#if !ENABLE_OPENGL_ES +#if !SLIC3R_OPENGL_ES glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); -#endif // !ENABLE_OPENGL_ES +#endif // !SLIC3R_OPENGL_ES glmodel.set_color(color); contour_shader->stop_using(); } -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES if (depth_test_enabled) glsafe(::glEnable(GL_DEPTH_TEST)); -#else - glsafe(::glPopAttrib()); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 8d2ec99..e6e70eb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -33,18 +33,18 @@ bool GLGizmoSlaSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; - m_desc["head_diameter"] = _L("Head diameter") + ": "; - m_desc["lock_supports"] = _L("Lock supports under new islands"); - m_desc["remove_selected"] = _L("Remove selected points"); - m_desc["remove_all"] = _L("Remove all points"); - m_desc["apply_changes"] = _L("Apply changes"); - m_desc["discard_changes"] = _L("Discard changes"); - m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; - m_desc["points_density"] = _L("Support points density") + ": "; - m_desc["auto_generate"] = _L("Auto-generate points"); - m_desc["manual_editing"] = _L("Manual editing"); - m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; - m_desc["reset_direction"] = _L("Reset direction"); + m_desc["head_diameter"] = _u8L("Head diameter") + ": "; + m_desc["lock_supports"] = _u8L("Lock supports under new islands"); + m_desc["remove_selected"] = _u8L("Remove selected points"); + m_desc["remove_all"] = _u8L("Remove all points"); + m_desc["apply_changes"] = _u8L("Apply changes"); + m_desc["discard_changes"] = _u8L("Discard changes"); + m_desc["minimal_distance"] = _u8L("Minimal points distance") + ": "; + m_desc["points_density"] = _u8L("Support points density") + ": "; + m_desc["auto_generate"] = _u8L("Auto-generate points"); + m_desc["manual_editing"] = _u8L("Manual editing"); + m_desc["clipping_of_view"] = _u8L("Clipping of view")+ ": "; + m_desc["reset_direction"] = _u8L("Reset direction"); return true; } @@ -561,12 +561,12 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l bool first_run = true; // This is a hack to redraw the button when all points are removed, // so it is not delayed until the background process finishes. RENDER_AGAIN: - //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + //ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f)); //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); //ImGui::SetNextWindowSize(ImVec2(window_size)); - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // adjust window position to avoid overlap the view toolbar float win_h = ImGui::GetWindowHeight(); @@ -583,12 +583,12 @@ RENDER_AGAIN: // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float settings_sliders_left = std::max(m_imgui->calc_text_size(m_desc.at("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f); - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f); + const float settings_sliders_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("minimal_distance")).x, ImGuiPureWrap::calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float diameter_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); - const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f); - const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f); + const float buttons_width_approx = ImGuiPureWrap::calc_text_size(m_desc.at("apply_changes")).x + ImGuiPureWrap::calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f); + const float lock_supports_width_approx = ImGuiPureWrap::calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f); float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left); window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx); @@ -604,7 +604,7 @@ RENDER_AGAIN: m_new_point_head_diameter = diameter_upper_cap; ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("head_diameter")); + ImGuiPureWrap::text(m_desc.at("head_diameter")); ImGui::SameLine(diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left); @@ -639,25 +639,25 @@ RENDER_AGAIN: } bool changed = m_lock_unique_islands; - m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands); + ImGuiPureWrap::checkbox(m_desc.at("lock_supports"), m_lock_unique_islands); force_refresh |= changed != m_lock_unique_islands; m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); + remove_selected = ImGuiPureWrap::button(m_desc.at("remove_selected")); m_imgui->disabled_end(); m_imgui->disabled_begin(m_editing_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); + remove_all = ImGuiPureWrap::button(m_desc.at("remove_all")); m_imgui->disabled_end(); - m_imgui->text(" "); // vertical gap + ImGuiPureWrap::text(" "); // vertical gap - if (m_imgui->button(m_desc.at("apply_changes"))) { + if (ImGuiPureWrap::button(m_desc.at("apply_changes"))) { editing_mode_apply_changes(); force_refresh = true; } ImGui::SameLine(); - bool discard_changes = m_imgui->button(m_desc.at("discard_changes")); + bool discard_changes = ImGuiPureWrap::button(m_desc.at("discard_changes")); if (discard_changes) { editing_mode_discard_changes(); force_refresh = true; @@ -667,7 +667,7 @@ RENDER_AGAIN: m_imgui->disabled_begin(!is_input_enabled()); ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("minimal_distance")); + ImGuiPureWrap::text(m_desc.at("minimal_distance")); ImGui::SameLine(settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left); @@ -681,7 +681,7 @@ RENDER_AGAIN: bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("points_density")); + ImGuiPureWrap::text(m_desc.at("points_density")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%"); @@ -706,23 +706,23 @@ RENDER_AGAIN: wxGetApp().obj_list()->update_and_show_object_settings_item(); } - bool generate = m_imgui->button(m_desc.at("auto_generate")); + bool generate = ImGuiPureWrap::button(m_desc.at("auto_generate")); if (generate) auto_generate(); ImGui::Separator(); - if (m_imgui->button(m_desc.at("manual_editing"))) + if (ImGuiPureWrap::button(m_desc.at("manual_editing"))) switch_to_editing_mode(); m_imgui->disabled_end(); m_imgui->disabled_begin(!is_input_enabled() || m_normal_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); + remove_all = ImGuiPureWrap::button(m_desc.at("remove_all")); m_imgui->disabled_end(); - // m_imgui->text(""); - // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : + // ImGuiPureWrap::text(""); + // ImGuiPureWrap::text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); @@ -734,10 +734,10 @@ RENDER_AGAIN: ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); + ImGuiPureWrap::text(m_desc.at("clipping_of_view")); } else { - if (m_imgui->button(m_desc.at("reset_direction"))) { + if (ImGuiPureWrap::button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ m_c->object_clipper()->set_position_by_ratio(-1., false); }); @@ -750,7 +750,7 @@ RENDER_AGAIN: if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); - if (m_imgui->button("?")) { + if (ImGuiPureWrap::button("?")) { wxGetApp().CallAfter([]() { SlaGizmoHelpDialog help_dlg; help_dlg.ShowModal(); @@ -759,7 +759,7 @@ RENDER_AGAIN: m_imgui->disabled_end(); - m_imgui->end(); + ImGuiPureWrap::end(); if (remove_selected || remove_all) { force_refresh = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 1c02b6a..7bd1ab4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -104,7 +104,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; + std::map m_desc; GLSelectionRectangle m_selection_rectangle; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 02f30d3..9c5bce4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -221,19 +221,11 @@ void InstancesHider::render_cut() const else clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES bool depth_test_enabled = ::glIsEnabled(GL_DEPTH_TEST); -#else - glsafe(::glPushAttrib(GL_DEPTH_TEST)); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES glsafe(::glDisable(GL_DEPTH_TEST)); clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES if (depth_test_enabled) glsafe(::glEnable(GL_DEPTH_TEST)); -#else - glsafe(::glPopAttrib()); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES ++clipper_id; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 794747b..1c903e5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -34,7 +34,7 @@ namespace Slic3r { namespace GUI { -const float GLGizmosManager::Default_Icons_Size = 64; +const float GLGizmosManager::Default_Icons_Size = 52;// 64; GLGizmosManager::GLGizmosManager(GLCanvas3D& parent) : m_parent(parent) @@ -66,11 +66,12 @@ GLGizmosManager::EType GLGizmosManager::get_gizmo_from_mouse(const Vec2d &mouse_ float icons_size = m_layout.scaled_icons_size(); float border = m_layout.scaled_border(); float stride_y = m_layout.scaled_stride_y(); + float gap_x = m_layout.scaled_gap_x(); float top_y = 0.5f * (cnv_h - height) + border; // is mouse horizontally in the area? - if ((border <= (float) mouse_pos(0) && - ((float) mouse_pos(0) <= border + icons_size))) { + if (border <= (float) mouse_pos(0) && + (float) mouse_pos(0) <= (border+gap_x + icons_size)) { // which icon is it on? size_t from_top = (size_t) ((float) mouse_pos(1) - top_y) / stride_y; // is it really on the icon or already past the border? @@ -803,7 +804,9 @@ void GLGizmosManager::do_render_overlay() const render_background(top_x, top_y, top_x + width, top_y - height, border_w, border_h); - top_x += border_w; + const float margin_w = border_w + m_layout.scaled_gap_x() * inv_cnv_w; + + top_x += margin_w; top_y -= border_h; const float icons_size_x = 2.0f * m_layout.scaled_icons_size() * inv_cnv_w; @@ -827,10 +830,16 @@ void GLGizmosManager::do_render_overlay() const float current_y = FLT_MAX; for (size_t idx : selectable_idxs) { GLGizmoBase* gizmo = m_gizmos[idx].get(); + + if (m_current == idx) + render_background(top_x - margin_w, top_y + border_h, top_x + icons_size_x + margin_w, top_y - icons_size_y - border_h, border_w, border_h); + const unsigned int sprite_id = gizmo->get_sprite_id(); // higlighted state needs to be decided first so its highlighting in every other state + // const int icon_idx = (m_highlight.first == idx ? (m_highlight.second ? 4 : 5) : (m_current == idx) ? /*2*/1 : ((m_hover == idx) ? 1 : (gizmo->is_activable() ? 0 : 3))); + //y14 const int icon_idx = (m_highlight.first == idx ? (m_highlight.second ? 4 : 5) : (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable() ? 0 : 3))); - + const float u_left = u_offset + icon_idx * du; const float u_right = u_left + du - u_offset; const float v_top = v_offset + sprite_id * dv; @@ -851,12 +860,12 @@ void GLGizmosManager::do_render_overlay() const float GLGizmosManager::get_scaled_total_height() const { - return m_layout.scale * (2.0f * m_layout.border + (float)get_selectable_idxs().size() * m_layout.stride_y() - m_layout.gap_y); + return 2.0f * m_layout.scaled_border() + (float)get_selectable_idxs().size() * m_layout.scaled_stride_y() - m_layout.scaled_gap_y(); } float GLGizmosManager::get_scaled_total_width() const { - return 2.0f * m_layout.scaled_border() + m_layout.scaled_icons_size(); + return 2.0f * m_layout.scaled_border() + m_layout.scaled_icons_size() + m_layout.scaled_gap_x(); } GLGizmoBase* GLGizmosManager::get_current() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 509a433..632c455 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -90,14 +90,16 @@ private: float scale{ 1.0f }; float icons_size{ Default_Icons_Size }; float border{ 5.0f }; - float gap_y{ 5.0f }; + float gap_x{ 15.0f }; + float gap_y{ 15.0f }; float stride_y() const { return icons_size + gap_y;} - float scaled_icons_size() const { return scale * icons_size; } - float scaled_border() const { return scale * border; } - float scaled_gap_y() const { return scale * gap_y; } - float scaled_stride_y() const { return scale * stride_y(); } + float scaled_icons_size() const { return int(scale * icons_size); } + float scaled_border() const { return int(scale * border); } + float scaled_gap_x() const { return int(scale * gap_x); } + float scaled_gap_y() const { return int(scale * gap_y); } + float scaled_stride_y() const { return scaled_icons_size() + scaled_gap_y(); } }; GLCanvas3D& m_parent; @@ -227,6 +229,8 @@ public: // To end highlight set gizmo = undefined void set_highlight(EType gizmo, bool highlight_shown) { m_highlight = std::pair(gizmo, highlight_shown); } bool get_highlight_state() const { return m_highlight.second; } + float get_scaled_total_height() const; + float get_scaled_total_width() const; private: bool gizmo_event(SLAGizmoEventType action, @@ -238,10 +242,6 @@ private: void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const; void do_render_overlay() const; - - float get_scaled_total_height() const; - float get_scaled_total_width() const; - bool generate_icons_texture(); void update_hover_state(const EType &type); diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index 824671c..7296ba7 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -1,5 +1,5 @@ #include "HintNotification.hpp" -#include "ImGuiWrapper.hpp" +#include "ImGuiPureWrap.hpp" #include "format.hpp" #include "I18N.hpp" #include "GUI_ObjectList.hpp" @@ -420,7 +420,7 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) std::string opt = dict["hypertext_settings_opt"]; Preset::Type type = static_cast(std::atoi(dict["hypertext_settings_type"].c_str())); std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]); - HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } }; + HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().jump_to_option(opt, type, category); } }; m_loaded_hints.emplace_back(hint_data); // open preferences } else if(dict["hypertext_type"] == "preferences") { @@ -733,7 +733,7 @@ void NotificationManager::HintNotification::init() m_state = EState::Shown; } -void NotificationManager::HintNotification::set_next_window_size(ImGuiWrapper& imgui) +void NotificationManager::HintNotification::set_next_window_size() { /* m_window_height = m_multiline ? @@ -752,7 +752,7 @@ bool NotificationManager::HintNotification::on_text_click() return false; } -void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::HintNotification::render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (!m_has_hint_data) { retrieve_data(); @@ -792,15 +792,15 @@ void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, con last_end = m_endlines[i]; if (m_text1.size() > m_endlines[i]) last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); } } //hyperlink text if (!m_multiline && m_lines_count > 2) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true); + render_hypertext(x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true); } else if (!m_hypertext.empty()) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty()? "": " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); + render_hypertext(x_offset + ImGui::CalcTextSize((line + (line.empty()? "": " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); } // text2 @@ -828,14 +828,14 @@ void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, con last_end = m_endlines2[i]; if (m_text2.size() > m_endlines2[i]) last_end += (m_text2[m_endlines2[i]] == '\n' || m_text2[m_endlines2[i]] == ' ' ? 1 : 0); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); } } } } -void NotificationManager::HintNotification::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) +void NotificationManager::HintNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -866,7 +866,7 @@ void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& im ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); } - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { close(); } @@ -874,7 +874,7 @@ void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& im //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - 2 * m_line_height)) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - 2 * m_line_height)) { close(); } @@ -883,8 +883,8 @@ void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& im //render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_logo(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_preferences_button(imgui, win_pos_x, win_pos_y); + render_logo(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_preferences_button(win_pos_x, win_pos_y); //Y4 /*if (!m_documentation_link.empty() && !wxGetApp().app_config->get_bool("suppress_hyperlinks")) { @@ -893,7 +893,7 @@ void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& im } -void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) +void NotificationManager::HintNotification::render_preferences_button(const float win_pos_x, const float win_pos_y) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); @@ -912,9 +912,9 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp // tooltip long time_now = wxGetLocalTime(); if (m_prefe_hover_time > 0 && m_prefe_hover_time < time_now) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - imgui.text(_u8L("Open Preferences.")); + ImGuiPureWrap::text(_u8L("Open Preferences.")); ImGui::EndTooltip(); ImGui::PopStyleColor(); } @@ -931,7 +931,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp } else { ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f); } - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { wxGetApp().open_preferences("show_hints", "GUI"); } @@ -940,7 +940,7 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp // preferences button is in place of minimize button m_minimize_b_visible = true; } -void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::HintNotification::render_right_arrow_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // Used for debuging @@ -963,14 +963,14 @@ void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapp ImGui::SetCursorPosY(m_close_b_y + m_close_b_w / 4.f * 7.f); else ImGui::SetCursorPosY(m_window_height - button_size.y - m_close_b_w / 4.f); - if (imgui.button(button_text.c_str(), button_size.x * 0.8f, button_size.y * 1.f)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x * 0.8f, button_size.y * 1.f)) { retrieve_data(); } ImGui::PopStyleColor(5); } -void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::HintNotification::render_logo(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { std::string placeholder_text; placeholder_text = ImGui::EjectButton; @@ -979,9 +979,9 @@ void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, con text = ImGui::ClippyMarker; ImGui::SetCursorPosX(button_pic_size.x / 3); ImGui::SetCursorPosY(win_size_y / 2 - button_pic_size.y * 2.f); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } -void NotificationManager::HintNotification::render_documentation_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::HintNotification::render_documentation_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1004,9 +1004,9 @@ void NotificationManager::HintNotification::render_documentation_button(ImGuiWra // tooltip long time_now = wxGetLocalTime(); if (m_docu_hover_time > 0 && m_docu_hover_time < time_now) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - imgui.text(_u8L("Open Documentation in web browser.")); + ImGuiPureWrap::text(_u8L("Open Documentation in web browser.")); ImGui::EndTooltip(); ImGui::PopStyleColor(); } @@ -1020,7 +1020,7 @@ void NotificationManager::HintNotification::render_documentation_button(ImGuiWra ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { open_documentation(); } @@ -1028,7 +1028,7 @@ void NotificationManager::HintNotification::render_documentation_button(ImGuiWra //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.f, win_size.y - 2 * m_line_height)) + if (ImGuiPureWrap::button(" ", m_line_height * 2.f, win_size.y - 2 * m_line_height)) { open_documentation(); } diff --git a/src/slic3r/GUI/HintNotification.hpp b/src/slic3r/GUI/HintNotification.hpp index e5dd90b..d79bb24 100644 --- a/src/slic3r/GUI/HintNotification.hpp +++ b/src/slic3r/GUI/HintNotification.hpp @@ -77,27 +77,26 @@ public: virtual void init() override; void open_next() { retrieve_data(); } protected: - virtual void set_next_window_size(ImGuiWrapper& imgui) override; + virtual void set_next_window_size() override; virtual void count_spaces() override; virtual void count_lines() override; virtual bool on_text_click() override; - virtual void render_text(ImGuiWrapper& imgui, + virtual void render_text(const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + virtual void render_close_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - virtual void 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) override; - virtual void render_minimize_button(ImGuiWrapper& imgui, + virtual void render_minimize_button( const float win_pos_x, const float win_pos_y) override {} - void render_preferences_button(ImGuiWrapper& imgui, + void render_preferences_button( const float win_pos_x, const float win_pos_y); - void render_right_arrow_button(ImGuiWrapper& imgui, + void render_right_arrow_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_documentation_button(ImGuiWrapper& imgui, + void render_documentation_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_logo(ImGuiWrapper& imgui, + void render_logo( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); // recursion counter -1 tells to retrieve same hint as last time diff --git a/src/slic3r/GUI/I18N.hpp b/src/slic3r/GUI/I18N.hpp index f8a7417..3d93c33 100644 --- a/src/slic3r/GUI/I18N.hpp +++ b/src/slic3r/GUI/I18N.hpp @@ -1,3 +1,7 @@ +#include +#include +#include +#include #ifndef _ #define _(s) Slic3r::GUI::I18N::translate((s)) #define _L(s) Slic3r::GUI::I18N::translate((s)) diff --git a/src/slic3r/GUI/IconManager.cpp b/src/slic3r/GUI/IconManager.cpp index e85e710..8343530 100644 --- a/src/slic3r/GUI/IconManager.cpp +++ b/src/slic3r/GUI/IconManager.cpp @@ -28,6 +28,7 @@ static void draw_transparent_icon(const IconManager::Icon &icon); // only help f IconManager::~IconManager() { priv::clear(m_icons); // release opengl texture is made in ~GLTexture() + if (m_id != 0) glsafe(::glDeleteTextures(1, &m_id)); } @@ -164,7 +165,8 @@ IconManager::Icons IconManager::init(const InitTypes &input) NSVGimage *image = parse_file(i.filepath.c_str()); assert(image != nullptr); if (image == nullptr) - return {}; + return {}; + ScopeGuard sg_image([image]() { ::nsvgDelete(image); }); float svg_scale = i.size.y / image->height; @@ -217,6 +219,7 @@ std::vector IconManager::init(const std::vector assert(!file_paths.empty()); assert(size.x >= 1); assert(size.x < 256*16); + // TODO: remove in future if (!m_icons.empty()) { // not first initialization @@ -352,6 +355,7 @@ void priv::draw_transparent_icon(const IconManager::Icon &icon) 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) @@ -372,7 +376,10 @@ void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_ bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover) { // check of hover - float cursor_x = ImGui::GetCursorPosX(); + 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()) { diff --git a/src/slic3r/GUI/IconManager.hpp b/src/slic3r/GUI/IconManager.hpp index 72331a8..24ec3f7 100644 --- a/src/slic3r/GUI/IconManager.hpp +++ b/src/slic3r/GUI/IconManager.hpp @@ -96,6 +96,7 @@ public: private: // keep data stored on GPU GLTexture m_icons_texture; + unsigned int m_id{ 0 }; Icons m_icons; }; diff --git a/src/slic3r/GUI/ImGuiDoubleSlider.cpp b/src/slic3r/GUI/ImGuiDoubleSlider.cpp new file mode 100644 index 0000000..afc309b --- /dev/null +++ b/src/slic3r/GUI/ImGuiDoubleSlider.cpp @@ -0,0 +1,715 @@ + +#include "ImGuiDoubleSlider.hpp" + +#include + +#include "slic3r/GUI/ImGuiPureWrap.hpp" + +namespace DoubleSlider { + +const ImU32 tooltip_bg_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_GREY_LIGHT); +const ImU32 thumb_bg_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_BLUE_LIGHT); +const ImU32 groove_bg_clr = ImGui::ColorConvertFloat4ToU32(ImGuiPureWrap::COL_WINDOW_BACKGROUND); +const ImU32 border_clr = IM_COL32(255, 255, 255, 255); + +static bool behavior(ImGuiID id, const ImRect& region, + const ImS32 v_min, const ImS32 v_max, + ImS32* out_value, ImRect* out_thumb, + ImGuiSliderFlags flags = 0, + bool change_on_mouse_move = false) +{ + ImGuiContext& context = *GImGui; + + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + + const ImVec2 thumb_sz = out_thumb->GetSize(); + ImS32 v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); + const float region_usable_sz = (region.Max[axis] - region.Min[axis]); + const float region_usable_pos_min = region.Min[axis]; + const float region_usable_pos_max = region.Max[axis]; + + const float mouse_abs_pos = context.IO.MousePos[axis]; + float mouse_pos_ratio = (region_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - region_usable_pos_min) / region_usable_sz, 0.0f, 1.0f) : 0.0f; + if (axis == ImGuiAxis_Y) + mouse_pos_ratio = 1.0f - mouse_pos_ratio; + + // Process interacting with the slider + ImS32 v_new = *out_value; + bool value_changed = false; + // wheel behavior + ImRect mouse_wheel_responsive_region; + if (axis == ImGuiAxis_X) + mouse_wheel_responsive_region = ImRect(region.Min - ImVec2(thumb_sz.x / 2, 0), region.Max + ImVec2(thumb_sz.x / 2, 0)); + if (axis == ImGuiAxis_Y) + mouse_wheel_responsive_region = ImRect(region.Min - ImVec2(0, thumb_sz.y), region.Max + ImVec2(0, thumb_sz.y)); + if (ImGui::ItemHoverable(mouse_wheel_responsive_region, id)) { + if (change_on_mouse_move) + v_new = v_min + (ImS32)(v_range * mouse_pos_ratio + 0.5f); + else { + float mw = context.IO.MouseWheel; +#if defined(__APPLE__) + if (mw > 0.f) mw = 1.f; + if (mw < 0.f) mw = -1.f; +#endif + const float accer = context.IO.KeyCtrl || context.IO.KeyShift ? 5.f : 1.f; + v_new = ImClamp(*out_value + (ImS32)(mw * accer), v_min, v_max); + } + } + + // drag behavior + if (context.ActiveId == id) + { + if (context.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (context.IO.MouseReleased[0]) + ImGui::ClearActiveID(); + if (context.IO.MouseDown[0]) + v_new = v_min + (ImS32)(v_range * mouse_pos_ratio + 0.5f); + } + } + + // apply result, output value + if (*out_value != v_new) + { + *out_value = v_new; + value_changed = true; + } + + // Output thumb position so it can be displayed by the caller + const ImS32 v_clamped = (v_min < v_max) ? ImClamp(*out_value, v_min, v_max) : ImClamp(*out_value, v_max, v_min); + float thumb_pos_ratio = v_range != 0 ? ((float)(v_clamped - v_min) / (float)v_range) : 0.0f; + thumb_pos_ratio = axis == ImGuiAxis_Y ? 1.0f - thumb_pos_ratio : thumb_pos_ratio; + const float thumb_pos = region_usable_pos_min + (region_usable_pos_max - region_usable_pos_min) * thumb_pos_ratio; + + ImVec2 new_thumb_center = axis == ImGuiAxis_Y ? ImVec2(out_thumb->GetCenter().x, thumb_pos) : ImVec2(thumb_pos, out_thumb->GetCenter().y); + *out_thumb = ImRect(new_thumb_center - thumb_sz * 0.5f, new_thumb_center + thumb_sz * 0.5f); + + return value_changed; +} + +static bool lclicked_on_thumb(ImGuiID id, const ImRect& region, + const ImS32 v_min, const ImS32 v_max, + const ImRect& thumb, ImGuiSliderFlags flags = 0) +{ + ImGuiContext& context = *GImGui; + + if (context.ActiveId == id && context.ActiveIdSource == ImGuiInputSource_Mouse && + context.IO.MouseReleased[0]) + { + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + + ImS32 v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); + const float region_usable_sz = (region.Max[axis] - region.Min[axis]); + const float region_usable_pos_min = region.Min[axis]; + const float region_usable_pos_max = region.Max[axis]; + + const float mouse_abs_pos = context.IO.MousePos[axis]; + float mouse_pos_ratio = (region_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - region_usable_pos_min) / region_usable_sz, 0.0f, 1.0f) : 0.0f; + if (axis == ImGuiAxis_Y) + mouse_pos_ratio = 1.0f - mouse_pos_ratio; + + ImS32 v_new = v_min + (ImS32)(v_range * mouse_pos_ratio + 0.5f); + + // Output thumb position so it can be displayed by the caller + const ImS32 v_clamped = (v_min < v_max) ? ImClamp(v_new, v_min, v_max) : ImClamp(v_new, v_max, v_min); + float thumb_pos_ratio = v_range != 0 ? ((float)(v_clamped - v_min) / (float)v_range) : 0.0f; + thumb_pos_ratio = axis == ImGuiAxis_Y ? 1.0f - thumb_pos_ratio : thumb_pos_ratio; + const float thumb_pos = region_usable_pos_min + (region_usable_pos_max - region_usable_pos_min) * thumb_pos_ratio; + + ImVec2 new_thumb_center = axis == ImGuiAxis_Y ? ImVec2(thumb.GetCenter().x, thumb_pos) : ImVec2(thumb_pos, thumb.GetCenter().y); + if (thumb.Contains(new_thumb_center)) + return true; + } + + return false; +} + +ImRect ImGuiControl::DrawOptions::groove(const ImVec2& pos, const ImVec2& size, bool is_horizontal) const +{ + ImVec2 groove_start = is_horizontal ? + ImVec2(pos.x + thumb_dummy_sz().x + text_dummy_sz().x, pos.y + size.y - groove_sz().y - dummy_sz().y) : + ImVec2(pos.x + size.x - groove_sz().x - dummy_sz().x, pos.y + text_dummy_sz().y); + ImVec2 groove_size = is_horizontal ? + ImVec2(size.x - 2 * (thumb_dummy_sz().x + text_dummy_sz().x), groove_sz().y) : + ImVec2(groove_sz().x, size.y - 2 * text_dummy_sz().y); + + return ImRect(groove_start, groove_start + groove_size); +} + +ImRect ImGuiControl::DrawOptions::draggable_region(const ImRect& groove, bool is_horizontal) const +{ + ImRect draggable_region = is_horizontal ? + ImRect(groove.Min.x, groove.GetCenter().y, groove.Max.x, groove.GetCenter().y) : + ImRect(groove.GetCenter().x, groove.Min.y, groove.GetCenter().x, groove.Max.y); + draggable_region.Expand(is_horizontal ? + ImVec2(/*thumb_radius()*/0, draggable_region_sz().y) : + ImVec2(draggable_region_sz().x, 0)); + + return draggable_region; +} + +ImRect ImGuiControl::DrawOptions::slider_line(const ImRect& draggable_region, const ImVec2& h_thumb_center, const ImVec2& l_thumb_center, bool is_horizontal) const +{ + ImVec2 mid = draggable_region.GetCenter(); + + ImRect scroll_line = is_horizontal ? + ImRect(ImVec2(l_thumb_center.x, mid.y - groove_sz().y / 2), ImVec2(h_thumb_center.x, mid.y + groove_sz().y / 2)) : + ImRect(ImVec2(mid.x - groove_sz().x / 2, h_thumb_center.y), ImVec2(mid.x + groove_sz().x / 2, l_thumb_center.y)); + + return scroll_line; +} + +ImGuiControl::ImGuiControl( int lowerValue, + int higherValue, + int minValue, + int maxValue, + ImGuiSliderFlags flags, + std::string name, + bool use_lower_thumb) : + m_selection(ssUndef), + m_name(name), + m_lower_pos(lowerValue), + m_higher_pos (higherValue), + m_min_pos(minValue), + m_max_pos(maxValue), + m_flags(flags), + m_draw_lower_thumb(use_lower_thumb) +{ +} + +int ImGuiControl::GetActivePos() const +{ + return m_selection == ssLower ? m_lower_pos : + m_selection == ssHigher ? m_higher_pos : -1; +} + +void ImGuiControl::SetLowerPos(const int lower_pos) +{ + m_selection = ssLower; + m_lower_pos = lower_pos; + correct_lower_pos(); +} + +void ImGuiControl::SetHigherPos(const int higher_pos) +{ + m_selection = ssHigher; + m_higher_pos = higher_pos; + correct_higher_pos(); +} + +void ImGuiControl::SetSelectionSpan(const int lower_pos, const int higher_pos) +{ + m_lower_pos = std::max(lower_pos, m_min_pos); + m_higher_pos = std::max(std::min(higher_pos, m_max_pos), m_lower_pos); + if (m_lower_pos < m_higher_pos) + m_combine_thumbs = false; +} + +void ImGuiControl::SetMaxPos(const int max_pos) +{ + m_max_pos = max_pos; + correct_higher_pos(); +} + +void ImGuiControl::MoveActiveThumb(int delta) +{ + if (m_selection == ssUndef) + m_selection = ssHigher; + + if (m_selection == ssLower) { + m_lower_pos -= delta; + correct_lower_pos(); + } + else if (m_selection == ssHigher) { + m_higher_pos -= delta; + correct_higher_pos(); + } +} + +void ImGuiControl::correct_lower_pos() +{ + if (m_lower_pos < m_min_pos) + m_lower_pos = m_min_pos; + else if (m_lower_pos > m_max_pos) + m_lower_pos = m_max_pos; + + if ((m_lower_pos >= m_higher_pos && m_lower_pos <= m_max_pos) || m_combine_thumbs) { + m_higher_pos = m_lower_pos; + } +} + +void ImGuiControl::correct_higher_pos() +{ + if (m_higher_pos > m_max_pos) + m_higher_pos = m_max_pos; + else if (m_higher_pos < m_min_pos) + m_higher_pos = m_min_pos; + + if ((m_higher_pos <= m_lower_pos && m_higher_pos >= m_min_pos) || m_combine_thumbs) { + m_lower_pos = m_higher_pos; + } +} + +void ImGuiControl::CombineThumbs(bool combine) +{ + m_combine_thumbs = combine; + if (combine) { + m_selection = ssHigher; + correct_higher_pos(); + } + else + ResetPositions(); +} + +void ImGuiControl::ResetPositions() +{ + SetLowerPos(m_min_pos); + SetHigherPos(m_max_pos); + m_selection == ssLower ? correct_lower_pos() : correct_higher_pos(); +} + +std::string ImGuiControl::get_label(int pos) const +{ + if (m_cb_get_label) + return m_cb_get_label(pos); + + if (pos >= m_max_pos || pos < m_min_pos) + return "ErrVal"; + + return std::to_string(pos); +} + +float ImGuiControl::GetPositionInRect(int pos, const ImRect& rect) const +{ + int v_min = m_min_pos; + int v_max = m_max_pos; + + float pos_ratio = (v_max - v_min) != 0 ? ((float)(pos - v_min) / (float)(v_max - v_min)) : 0.0f; + float thumb_pos; + if (is_horizontal()) { + thumb_pos = rect.Min.x + (rect.Max.x - rect.Min.x) * pos_ratio; + } + else { + pos_ratio = 1.0f - pos_ratio; + thumb_pos = rect.Min.y + (rect.Max.y - rect.Min.y) * pos_ratio; + } + return thumb_pos; +} + +ImRect ImGuiControl::GetActiveThumbRect() const +{ + return m_selection == ssLower ? m_regions.lower_thumb : m_regions.higher_thumb; +} + +bool ImGuiControl::IsLClickOnThumb() +{ + if (m_lclick_on_selected_thumb) { + // discard left mouse click at list its value is checked to avoud reuse it on next frame + m_lclick_on_selected_thumb = false; + m_suppress_process_behavior = false; + return true; + } + return false; +} + +bool ImGuiControl::IsLClickOnHoveredPos() +{ + if (m_lclick_on_hovered_pos) { + // Discard left mouse click at hovered tick to avoud reuse it on next frame + m_lclick_on_hovered_pos = false; + return true; + } + return false; +} + +void ImGuiControl::draw_scroll_line(const ImRect& scroll_line, const ImRect& slideable_region) +{ + if (m_cb_draw_scroll_line) + m_cb_draw_scroll_line(scroll_line, slideable_region); + else + ImGui::RenderFrame(scroll_line.Min, scroll_line.Max, thumb_bg_clr, false, m_draw_opts.rounding()); +} + +void ImGuiControl::draw_background(const ImRect& slideable_region) +{ + ImVec2 groove_sz = m_draw_opts.groove_sz() * 0.55f; + auto groove_center = slideable_region.GetCenter(); + ImRect groove = is_horizontal() ? + ImRect(slideable_region.Min.x, groove_center.y - groove_sz.y, slideable_region.Max.x, groove_center.y + groove_sz.y) : + ImRect(groove_center.x - groove_sz.x, slideable_region.Min.y, groove_center.x + groove_sz.x, slideable_region.Max.y); + ImVec2 groove_padding = (is_horizontal() ? ImVec2(2.0f, 2.0f) : ImVec2(3.0f, 4.0f)) * m_draw_opts.scale; + + ImRect bg_rect = groove; + bg_rect.Expand(groove_padding); + + // draw bg of slider + ImGui::RenderFrame(bg_rect.Min, bg_rect.Max, border_clr, false, 0.5 * bg_rect.GetWidth()); + // draw bg of scroll + ImGui::RenderFrame(groove.Min, groove.Max, groove_bg_clr, false, 0.5 * groove.GetWidth()); +} + +void ImGuiControl::draw_label(std::string label, const ImRect& thumb, bool is_mirrored /*= false*/, bool with_border /*= false*/) +{ + if (label.empty() || label == "ErrVal") + return; + + const ImVec2 thumb_center = thumb.GetCenter(); + ImVec2 text_padding = m_draw_opts.text_padding(); + float rounding = m_draw_opts.rounding(); + + float triangle_offset_x = 9.f * m_draw_opts.scale; + float triangle_offset_y = 8.f * m_draw_opts.scale; + + ImVec2 text_content_size = ImGui::CalcTextSize(label.c_str()); + ImVec2 text_size = text_content_size + text_padding * 2; + ImVec2 text_start = is_horizontal() ? + ImVec2(thumb.Max.x + triangle_offset_x, thumb_center.y - text_size.y) : + ImVec2(thumb.Min.x - text_size.x - triangle_offset_x, thumb_center.y - text_size.y) ; + + if (is_mirrored) + text_start = is_horizontal() ? + ImVec2(thumb.Min.x - text_size.x - triangle_offset_x, thumb_center.y - text_size.y) : + ImVec2(thumb.Min.x - text_size.x - triangle_offset_x, thumb_center.y) ; + + ImRect text_rect(text_start, text_start + text_size); + + if (with_border) { + + float rounding_b = 0.75f * rounding; + + ImRect text_rect_b(text_rect); + text_rect_b.Expand(ImVec2(rounding_b, rounding_b)); + + float triangle_offset_x_b = triangle_offset_x + rounding_b; + float triangle_offset_y_b = triangle_offset_y + rounding_b; + + ImVec2 pos_1 = is_horizontal() ? + ImVec2(text_rect_b.Min.x + rounding_b, text_rect_b.Max.y) : + ImVec2(text_rect_b.Max.x - rounding_b, text_rect_b.Max.y); + ImVec2 pos_2 = is_horizontal() ? pos_1 - ImVec2(triangle_offset_x_b, 0.f) : pos_1 - ImVec2(0.f, triangle_offset_y_b); + ImVec2 pos_3 = is_horizontal() ? pos_1 - ImVec2(0.f, triangle_offset_y_b) : pos_1 + ImVec2(triangle_offset_x_b, 0.f); + + if (is_mirrored) { + pos_1 = is_horizontal() ? + ImVec2(text_rect_b.Max.x - rounding_b - 1, text_rect_b.Max.y - 1) : + ImVec2(text_rect_b.Max.x - rounding_b, text_rect_b.Min.y); + pos_2 = is_horizontal() ? pos_1 + ImVec2(triangle_offset_x_b, 0.f) : pos_1 + ImVec2(0.f, triangle_offset_y_b); + pos_3 = is_horizontal() ? pos_1 - ImVec2(0.f, triangle_offset_y_b) : pos_1 + ImVec2(triangle_offset_x_b, 0.f); + } + + ImGui::RenderFrame(text_rect_b.Min, text_rect_b.Max, thumb_bg_clr, true, rounding); + ImGui::GetCurrentWindow()->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, thumb_bg_clr); + } + + ImVec2 pos_1 = is_horizontal() ? + ImVec2(text_rect.Min.x + rounding, text_rect.Max.y) : + ImVec2(text_rect.Max.x - rounding, text_rect.Max.y); + ImVec2 pos_2 = is_horizontal() ? pos_1 - ImVec2(triangle_offset_x, 0.f) : pos_1 - ImVec2(0.f, triangle_offset_y); + ImVec2 pos_3 = is_horizontal() ? pos_1 - ImVec2(0.f, triangle_offset_y) : pos_1 + ImVec2(triangle_offset_x, 0.f); + + if (is_mirrored) { + pos_1 = is_horizontal() ? + ImVec2(text_rect.Max.x - rounding-1, text_rect.Max.y-1) : + ImVec2(text_rect.Max.x - rounding, text_rect.Min.y); + pos_2 = is_horizontal() ? pos_1 + ImVec2(triangle_offset_x, 0.f) : pos_1 + ImVec2(0.f, triangle_offset_y); + pos_3 = is_horizontal() ? pos_1 - ImVec2(0.f, triangle_offset_y) : pos_1 + ImVec2(triangle_offset_x, 0.f); + } + + ImGui::RenderFrame(text_rect.Min, text_rect.Max, tooltip_bg_clr, true, rounding); + ImGui::GetCurrentWindow()->DrawList->AddTriangleFilled(pos_1, pos_2, pos_3, tooltip_bg_clr); + ImGui::RenderText(text_start + text_padding, label.c_str()); +}; + +void ImGuiControl::draw_thumb(const ImVec2& center, bool mark/* = false*/) +{ + const float line_width = 1.5f * m_draw_opts.scale; + const float radius = m_draw_opts.thumb_radius(); + const float line_offset = 0.5f * radius; + const float rounding = 1.5f * m_draw_opts.rounding(); + + const float hexagon_angle = is_horizontal() ? 0.f : IM_PI * 0.5f; + + ImGuiPureWrap::draw_hexagon(center, radius, border_clr, hexagon_angle, rounding); + ImGuiPureWrap::draw_hexagon(center, radius - line_width, thumb_bg_clr, hexagon_angle, rounding); + + if (mark) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + window->DrawList->AddLine(center + ImVec2(-line_offset, 0.0f), center + ImVec2(line_offset, 0.0f), border_clr, line_width); + window->DrawList->AddLine(center + ImVec2(0.0f, -line_offset), center + ImVec2(0.0f, line_offset), border_clr, line_width); + } +} + +void ImGuiControl::apply_regions(int higher_pos, int lower_pos, const ImRect& draggable_region) +{ + ImVec2 mid = draggable_region.GetCenter(); + float thumb_radius = m_draw_opts.thumb_radius(); + + // set slideable region + m_regions.higher_slideable_region = is_horizontal() ? + ImRect(draggable_region.Min + ImVec2(m_draw_lower_thumb ? thumb_radius : 0, 0), draggable_region.Max) : + ImRect(draggable_region.Min, draggable_region.Max - ImVec2(0, m_combine_thumbs ? 0 : thumb_radius)); + m_regions.lower_slideable_region = is_horizontal() ? + ImRect(draggable_region.Min, draggable_region.Max - ImVec2(thumb_radius, 0)) : + ImRect(draggable_region.Min + ImVec2(0, thumb_radius), draggable_region.Max); + + // initialize the thumbs. + float higher_thumb_pos = GetPositionInRect(higher_pos, m_regions.higher_slideable_region); + m_regions.higher_thumb = is_horizontal() ? + ImRect(higher_thumb_pos - thumb_radius, mid.y - thumb_radius, higher_thumb_pos + thumb_radius, mid.y + thumb_radius) : + ImRect(mid.x - thumb_radius, higher_thumb_pos - thumb_radius, mid.x + thumb_radius, higher_thumb_pos + thumb_radius); + + float lower_thumb_pos = GetPositionInRect(lower_pos, m_regions.lower_slideable_region); + m_regions.lower_thumb = is_horizontal() ? + ImRect(lower_thumb_pos - thumb_radius, mid.y - thumb_radius, lower_thumb_pos + thumb_radius, mid.y + thumb_radius) : + ImRect(mid.x - thumb_radius, lower_thumb_pos - thumb_radius, mid.x + thumb_radius, lower_thumb_pos + thumb_radius); +} + +void ImGuiControl::check_and_correct_thumbs(int* higher_pos, int* lower_pos) +{ + if (!m_draw_lower_thumb || m_combine_thumbs) + return; + + const ImVec2 higher_thumb_center = m_regions.higher_thumb.GetCenter(); + const ImVec2 lower_thumb_center = m_regions.lower_thumb.GetCenter(); + const float thumb_radius = m_draw_opts.thumb_radius(); + + const float higher_thumb_center_pos = is_horizontal() ? higher_thumb_center.x : higher_thumb_center.y; + const float lower_thumb_center_pos = is_horizontal() ? lower_thumb_center.x : lower_thumb_center.y; + + if (is_horizontal()) { + if (lower_thumb_center_pos + thumb_radius > higher_thumb_center_pos) { + if (m_selection == ssHigher) { + m_regions.higher_thumb = m_regions.lower_thumb; + m_regions.higher_thumb.TranslateX(thumb_radius); + *lower_pos = *higher_pos; + } + else { + m_regions.lower_thumb = m_regions.higher_thumb; + m_regions.lower_thumb.TranslateX(-thumb_radius); + *higher_pos = *lower_pos; + } + } + } + else { + if (higher_thumb_center_pos + thumb_radius > lower_thumb_center_pos) { + if (m_selection == ssHigher) { + m_regions.lower_thumb = m_regions.higher_thumb; + m_regions.lower_thumb.TranslateY(thumb_radius); + *lower_pos = *higher_pos; + } + else { + m_regions.higher_thumb = m_regions.lower_thumb; + m_regions.higher_thumb.TranslateY(-thumb_radius); + *higher_pos = *lower_pos; + } + } + } +} + +bool ImGuiControl::draw_slider( int* higher_pos, int* lower_pos, + std::string& higher_label, std::string& lower_label, + const ImVec2& pos, const ImVec2& size, float scale) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& context = *GImGui; + const ImGuiID id = window->GetID(m_name.c_str()); + + const ImRect item_size(pos, pos + size); + ImGui::ItemSize(item_size); + + // get slider groove size + ImRect groove = m_draw_opts.groove(pos, size, is_horizontal()); + + // get active(draggable) region. + ImRect draggable_region = m_draw_opts.draggable_region(groove, is_horizontal()); + + if (ImGui::ItemHoverable(draggable_region, id) && context.IO.MouseDown[0]) { + ImGui::SetActiveID(id, window); + ImGui::SetFocusID(id, window); + ImGui::FocusWindow(window); + } + + // set slideable regions and thumbs. + apply_regions(*higher_pos, *lower_pos, draggable_region); + + // select and mark higher thumb by default + if (m_selection == ssUndef) + m_selection = ssHigher; + + // Processing interacting + + if (ImGui::ItemHoverable(m_regions.higher_thumb, id) && context.IO.MouseClicked[0]) + m_selection = ssHigher; + + if (m_draw_lower_thumb && !m_combine_thumbs && + ImGui::ItemHoverable(m_regions.lower_thumb, id) && context.IO.MouseClicked[0]) + m_selection = ssLower; + + { + // detect left click on selected thumb + const ImRect& active_thumb = m_selection == ssHigher ? m_regions.higher_thumb : m_regions.lower_thumb; + if (ImGui::ItemHoverable(active_thumb, id) && context.IO.MouseClicked[0]) { + m_active_thumb = active_thumb; + m_suppress_process_behavior = true; + } + else if (ImGui::ItemHoverable(active_thumb, id) && context.IO.MouseReleased[0]) { + const ImRect& slideable_region = m_selection == ssHigher ? m_regions.higher_slideable_region : m_regions.lower_slideable_region; + if (lclicked_on_thumb(id, slideable_region, m_min_pos, m_max_pos, m_active_thumb, m_flags)) { + m_suppress_process_behavior = true; + m_lclick_on_selected_thumb = true; + } + } + + if (ImGui::ItemHoverable(active_thumb, id) && ImGui::IsMouseDragging(0)) { + // invalidate active thumb clicking + m_active_thumb = ImRect(0.f, 0.f, 0.f, 0.f); + } + + // detect left click on hovered region + if (ImGui::ItemHoverable(m_hovered_region, id) && context.IO.MouseClicked[0]) { + // clear active ID to avoid a process of behavior() + if (context.ActiveId == id && context.ActiveIdSource == ImGuiInputSource_Mouse) + ImGui::ClearActiveID(); + } + else if (ImGui::ItemHoverable(m_hovered_region, id) && context.IO.MouseReleased[0]) { + const ImRect& slideable_region = m_selection == ssHigher ? m_regions.higher_slideable_region : m_regions.lower_slideable_region; + if (lclicked_on_thumb(id, slideable_region, m_min_pos, m_max_pos, m_hovered_region, m_flags)) { + m_lclick_on_hovered_pos = true; + } + } + } + + // update thumb position + bool pos_changed = false; + if (!m_suppress_process_behavior) { + if (m_selection == ssHigher) { + pos_changed = behavior(id, m_regions.higher_slideable_region, m_min_pos, m_max_pos, + higher_pos, &m_regions.higher_thumb, m_flags); + } + else if (m_draw_lower_thumb && !m_combine_thumbs) { + pos_changed = behavior(id, m_regions.lower_slideable_region, m_min_pos, m_max_pos, + lower_pos, &m_regions.lower_thumb, m_flags); + } + + // check thumbs poss and correct them if needed + check_and_correct_thumbs(higher_pos, lower_pos); + } + const ImRect& slideable_region = m_selection == ssHigher ? m_regions.higher_slideable_region : m_regions.lower_slideable_region; + const ImRect& active_thumb = m_selection == ssHigher ? m_regions.higher_thumb : m_regions.lower_thumb; + + bool show_move_label = false; + ImRect mouse_pos_rc = active_thumb; + + std::string move_label = ""; + + auto move_size = item_size; + move_size.Min.x += left_dummy_sz().x; + if (!pos_changed && ImGui::ItemHoverable(move_size, id) && !ImGui::IsMouseDragging(0)) { + auto sl_region = slideable_region; + if (!is_horizontal() && m_draw_opts.has_ruler) + sl_region.Max.x += m_draw_opts.dummy_sz().x; + behavior(id, sl_region, m_min_pos, m_max_pos, + &m_mouse_pos, &mouse_pos_rc, m_flags, true); + show_move_label = true; + move_label = get_label_on_move(m_mouse_pos); + } + + // detect right click on selected thumb + if (ImGui::ItemHoverable(active_thumb, id) && context.IO.MouseClicked[1]) + m_rclick_on_selected_thumb = true; + if ((!ImGui::ItemHoverable(active_thumb, id) && context.IO.MouseClicked[1]) || + context.IO.MouseClicked[0]) + m_rclick_on_selected_thumb = false; + + if (m_suppress_process_behavior && ImGui::ItemHoverable(item_size, id) && ImGui::IsMouseDragging(0)) + m_suppress_process_behavior = false; + + // render slider + + ImVec2 higher_thumb_center = m_regions.higher_thumb.GetCenter(); + ImVec2 lower_thumb_center = m_regions.lower_thumb.GetCenter(); + + ImRect scroll_line = m_draw_opts.slider_line(slideable_region, higher_thumb_center, lower_thumb_center, is_horizontal()); + + if (m_cb_extra_draw) + m_cb_extra_draw(slideable_region); + + // draw background + draw_background(slideable_region); + // draw scroll line + draw_scroll_line(m_combine_thumbs ? groove : scroll_line, slideable_region); + + // draw thumbs with label + draw_thumb(higher_thumb_center, m_selection == ssHigher && m_draw_lower_thumb); + draw_label(higher_label, m_regions.higher_thumb); + + if (m_draw_lower_thumb && !m_combine_thumbs) { + ImVec2 text_size = ImGui::CalcTextSize(lower_label.c_str()) + m_draw_opts.text_padding() * 2.f; + const bool mirror_label = is_horizontal() ? (higher_thumb_center.x - lower_thumb_center.x < text_size.x) : + (lower_thumb_center.y - higher_thumb_center.y < text_size.y); + + draw_thumb(lower_thumb_center, m_selection == ssLower); + draw_label(lower_label, m_regions.lower_thumb, mirror_label); + } + + // draw label on mouse move + if (m_show_move_label) + draw_label(move_label, mouse_pos_rc, false, true); + + return pos_changed; +} + +bool ImGuiControl::render() +{ + bool result = false; + + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + + int windows_flag = ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse; + + ImGuiPureWrap::set_next_window_pos(m_pos.x, m_pos.y, ImGuiCond_Always); + ImGuiPureWrap::begin(m_name, windows_flag); + + float scale = 1.f; + + int higher_pos = m_higher_pos; + int lower_pos = m_lower_pos; + std::string higher_label = get_label(m_higher_pos); + std::string lower_label = get_label(m_lower_pos); + int temp_higher_pos = m_higher_pos; + int temp_lower_pos = m_lower_pos; + + if (draw_slider(&higher_pos, &lower_pos, higher_label, lower_label, m_pos, m_size, scale)) { + if (temp_higher_pos != higher_pos) { + m_higher_pos = higher_pos; + if (m_combine_thumbs) + m_lower_pos = m_higher_pos; + } + if (temp_lower_pos != lower_pos) + m_lower_pos = lower_pos; + result = true; + } + + ImGuiPureWrap::end(); + + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(3); + + return result; +} + +} // DoubleSlider + diff --git a/src/slic3r/GUI/ImGuiDoubleSlider.hpp b/src/slic3r/GUI/ImGuiDoubleSlider.hpp new file mode 100644 index 0000000..cbc8590 --- /dev/null +++ b/src/slic3r/GUI/ImGuiDoubleSlider.hpp @@ -0,0 +1,309 @@ + +#ifndef slic3r_ImGUI_DoubleSlider_hpp_ +#define slic3r_ImGUI_DoubleSlider_hpp_ + +#include "ImGuiPureWrap.hpp" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include +#include +#include +#include + +#include "imgui/imgui_internal.h" + +// this code is borrowed from https://stackoverflow.com/questions/16605967/set-precision-of-stdto-string-when-converting-floating-point-values +template +std::string to_string_with_precision(const T a_value, const int n = 2) +{ + std::ostringstream out; + out.precision(n); + out << std::fixed << a_value; + return std::move(out).str(); +} + +namespace DoubleSlider { + +enum SelectedSlider { + ssUndef, + ssLower, + ssHigher +}; + +class ImGuiControl +{ +public: + ImGuiControl(int lowerValue, + int higherValue, + int minValue, + int maxValue, + ImGuiSliderFlags flags = ImGuiSliderFlags_None, + std::string name = "d_slider", + bool use_lower_thumb = true); + ImGuiControl() {} + ~ImGuiControl() {} + + int GetMinPos() const { return m_min_pos; } + int GetMaxPos() const { return m_max_pos; } + int GetLowerPos() const { return m_lower_pos; } + int GetHigherPos() const { return m_higher_pos; } + int GetActivePos() const; + + // Set low and high slider position. If the span is non-empty, disable the "one layer" mode. + void SetLowerPos (const int lower_pos); + void SetHigherPos(const int higher_pos); + void SetSelectionSpan(const int lower_pos, const int higher_pos); + + void SetMaxPos(const int max_pos); + void CombineThumbs(bool combine); + void ResetPositions(); + + void SetCtrlPos(ImVec2 pos) { m_pos = pos; } + void SetCtrlSize(ImVec2 size) { m_size = size; } + void SetCtrlScale(float scale) { m_draw_opts.scale = scale; } + void Init(const ImVec2& pos, const ImVec2& size, float scale, bool has_ruler = false) { + m_pos = pos; + m_size = size; + m_draw_opts.scale = scale; + m_draw_opts.has_ruler = has_ruler; + } + ImVec2 GetCtrlSize() { return m_size; } + ImVec2 GetCtrlPos() { return m_pos; } + + void Show(bool show) { m_is_shown = show; } + void Hide() { m_is_shown = false; } + bool IsShown() const { return m_is_shown; } + bool IsCombineThumbs() const { return m_combine_thumbs; } + bool IsActiveHigherThumb() const { return m_selection == ssHigher; } + void MoveActiveThumb(int delta); + void ShowLowerThumb(bool show) { m_draw_lower_thumb = show; } + + void ShowLabelOnMouseMove(bool show = true) { m_show_move_label = show; } + ImRect GetGrooveRect() const { return m_draw_opts.groove(m_pos, m_size, is_horizontal()); } + float GetPositionInRect(int pos, const ImRect& rect) const; + ImRect GetActiveThumbRect() const; + + bool IsRClickOnThumb() const { return m_rclick_on_selected_thumb; } + bool IsLClickOnThumb(); + bool IsLClickOnHoveredPos(); + + bool is_horizontal() const { return !(m_flags & ImGuiSliderFlags_Vertical); } + bool render(); + + std::string get_label(int pos) const; + float rounding() const { return m_draw_opts.rounding(); } + ImVec2 left_dummy_sz() const { return m_draw_opts.text_dummy_sz() + m_draw_opts.text_padding(); } + + void SetHoveredRegion(ImRect region) { m_hovered_region = region; } + void InvalidateHoveredRegion() { m_hovered_region = ImRect(0.f, 0.f, 0.f, 0.f); } + + void set_get_label_on_move_cb(std::function cb) { m_cb_get_label_on_move = cb; } + void set_get_label_cb(std::function cb) { m_cb_get_label = cb; } + void set_draw_scroll_line_cb(std::function cb) { m_cb_draw_scroll_line = cb; } + void set_extra_draw_cb(std::function cb) { m_cb_extra_draw = cb; } + +private: + + struct DrawOptions { + float scale { 1.f }; // used for Retina on osx + bool has_ruler { false }; + + ImVec2 dummy_sz() const { return ImVec2(has_ruler ? 48.0f : 24.0f, 16.0f) * scale; } + ImVec2 thumb_dummy_sz() const { return ImVec2(17.0f, 17.0f) * scale; } + ImVec2 groove_sz() const { return ImVec2(4.0f, 4.0f) * scale; } + ImVec2 draggable_region_sz()const { return ImVec2(20.0f, 19.0f) * scale; } + ImVec2 text_dummy_sz() const { return ImVec2(60.0f, 34.0f) * scale; } + ImVec2 text_padding() const { return ImVec2( 5.0f, 2.0f) * scale; } + + float thumb_radius() const { return 10.0f * scale; } + float thumb_border() const { return 2.0f * scale; } + float rounding() const { return 2.0f * scale; } + + ImRect groove(const ImVec2& pos, const ImVec2& size, bool is_horizontal) const; + ImRect draggable_region(const ImRect& groove, bool is_horizontal) const; + ImRect slider_line(const ImRect& draggable_region, const ImVec2& h_thumb_center, const ImVec2& l_thumb_center, bool is_horizontal) const; + }; + + struct Regions { + ImRect higher_slideable_region; + ImRect lower_slideable_region; + ImRect higher_thumb; + ImRect lower_thumb; + }; + + SelectedSlider m_selection; + ImVec2 m_pos; + ImVec2 m_size; + std::string m_name; + ImGuiSliderFlags m_flags{ ImGuiSliderFlags_None }; + bool m_is_shown{ true }; + + int m_min_pos; + int m_max_pos; + int m_lower_pos; + int m_higher_pos; + // slider's position of the mouse cursor + int m_mouse_pos { 0 }; + + bool m_rclick_on_selected_thumb{ false }; + bool m_lclick_on_selected_thumb{ false }; + bool m_lclick_on_hovered_pos { false }; + bool m_suppress_process_behavior{ false }; + ImRect m_active_thumb; + ImRect m_hovered_region; + + bool m_draw_lower_thumb{ true }; + bool m_combine_thumbs { false }; + bool m_show_move_label { false }; + + DrawOptions m_draw_opts; + Regions m_regions; + + std::function m_cb_get_label { nullptr }; + std::function m_cb_get_label_on_move { nullptr }; + std::function m_cb_draw_scroll_line { nullptr }; + std::function m_cb_extra_draw { nullptr }; + + void correct_lower_pos(); + void correct_higher_pos(); + std::string get_label_on_move(int pos) const { return m_cb_get_label_on_move ? m_cb_get_label_on_move(pos) : get_label(pos); } + + void apply_regions(int higher_pos, int lower_pos, const ImRect& draggable_region); + void check_and_correct_thumbs(int* higher_pos, int* lower_pos); + + void draw_scroll_line(const ImRect& scroll_line, const ImRect& slideable_region); + void draw_background(const ImRect& slideable_region); + void draw_label(std::string label, const ImRect& thumb, bool is_mirrored = false, bool with_border = false); + void draw_thumb(const ImVec2& center, bool mark = false); + bool draw_slider(int* higher_pos, int* lower_pos, + std::string& higher_label, std::string& lower_label, + const ImVec2& pos, const ImVec2& size, float scale = 1.0f); +}; + +// VatType = a typ of values, related to the each position in slider +template +class Manager +{ +public: + + void Init( int lowerPos, + int higherPos, + int minPos, + int maxPos, + const std::string& name, + bool is_horizontal) + { + m_ctrl = ImGuiControl( lowerPos, higherPos, + minPos, maxPos, + is_horizontal ? 0 : ImGuiSliderFlags_Vertical, + name, !is_horizontal); + + m_ctrl.set_get_label_cb([this](int pos) {return get_label(pos); }); + }; + + Manager() {} + Manager(int lowerPos, + int higherPos, + int minPos, + int maxPos, + const std::string& name, + bool is_horizontal) + { + Init (lowerPos, higherPos, minPos, maxPos, name, is_horizontal); + } + ~Manager() {} + + int GetMinPos() const { return m_ctrl.GetMinPos(); } + int GetMaxPos() const { return m_ctrl.GetMaxPos(); } + int GetLowerPos() const { return m_ctrl.GetLowerPos(); } + int GetHigherPos()const { return m_ctrl.GetHigherPos(); } + + ValType GetMinValue() { return m_values.empty() ? static_cast(0) : m_values[GetMinPos()]; } + ValType GetMaxValue() { return m_values.empty() ? static_cast(0) : m_values[GetMaxPos()]; } + ValType GetLowerValue() { return m_values.empty() ? static_cast(0) : m_values[GetLowerPos()];} + ValType GetHigherValue() { return m_values.empty() ? static_cast(0) : m_values[GetHigherPos()]; } + + // Set low and high slider position. If the span is non-empty, disable the "one layer" mode. + void SetLowerPos(const int lower_pos) { + m_ctrl.SetLowerPos(lower_pos); + process_thumb_move(); + } + void SetHigherPos(const int higher_pos) { + m_ctrl.SetHigherPos(higher_pos); + process_thumb_move(); + } + void SetSelectionSpan(const int lower_pos, const int higher_pos) { + m_ctrl.SetSelectionSpan(lower_pos, higher_pos); + process_thumb_move(); + } + void SetMaxPos(const int max_pos) { + m_ctrl.SetMaxPos(max_pos); + process_thumb_move(); + } + void Freeze() { + m_allow_process_thumb_move = false; + } + void Thaw() { + m_allow_process_thumb_move = true; + process_thumb_move(); + } + + void SetSliderValues(const std::vector& values) { m_values = values; } + // values used to show thumb labels + void SetSliderAlternateValues(const std::vector& values) { m_alternate_values = values; } + + bool IsLowerAtMin() const { return m_ctrl.GetLowerPos() == m_ctrl.GetMinPos(); } + bool IsHigherAtMax() const { return m_ctrl.GetHigherPos() == m_ctrl.GetMaxPos(); } + + void Show(bool show = true) { m_ctrl.Show(show); } + void Hide() { m_ctrl.Show(false); } + bool IsShown() { return m_ctrl.IsShown(); } + void SetEmUnit(int em_unit) { m_em = em_unit; } + void ShowLowerThumb(bool show) { m_ctrl.ShowLowerThumb(show); } + + float GetWidth() { return m_ctrl.GetCtrlSize().x; } + float GetHeight() { return m_ctrl.GetCtrlSize().y; } + virtual void Render(const int canvas_width, const int canvas_height, float extra_scale = 1.f, float offset = 0.f) = 0; + + void set_callback_on_thumb_move(std::function cb) { m_cb_thumb_move = cb; }; + + void move_current_thumb(const int delta) + { + m_ctrl.MoveActiveThumb(delta); + process_thumb_move(); + } + +protected: + + std::vector m_values; + std::vector m_alternate_values; + + ImGuiControl m_ctrl; + int m_em{ 10 }; + float m_scale{ 1.f }; + + virtual std::string get_label(int pos) const { + if (m_values.empty()) + return std::to_string(pos); + if (pos >= int(m_values.size())) + return "ErrVal"; + return to_string_with_precision(static_cast(m_alternate_values.empty() ? m_values[pos] : m_alternate_values[pos])); + } + + void process_thumb_move() { + if (m_cb_thumb_move && m_allow_process_thumb_move) + m_cb_thumb_move(); + } + +private: + + std::function m_cb_thumb_move { nullptr }; + bool m_allow_process_thumb_move { true }; +}; + +} // DoubleSlider + + +#endif // slic3r_ImGUI_DoubleSlider_hpp_ diff --git a/src/slic3r/GUI/ImGuiPureWrap.cpp b/src/slic3r/GUI/ImGuiPureWrap.cpp new file mode 100644 index 0000000..0452d9c --- /dev/null +++ b/src/slic3r/GUI/ImGuiPureWrap.cpp @@ -0,0 +1,864 @@ + +#include "ImGuiPureWrap.hpp" + +#include +#include + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include +#include +#include +#include +#include +#include + +namespace ImGuiPureWrap { + +void set_display_size(float w, float h) +{ + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(w, h); + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); +} + +ImVec2 calc_text_size(std::string_view text, + bool hide_text_after_double_hash, + float wrap_width) +{ + return ImGui::CalcTextSize(text.data(), text.data() + text.length(), + hide_text_after_double_hash, wrap_width); +} + +ImVec2 calc_text_size(const std::string& text, + bool hide_text_after_double_hash, + float wrap_width) +{ + return ImGui::CalcTextSize(text.c_str(), NULL, hide_text_after_double_hash, wrap_width); +} + +ImVec2 calc_button_size(const std::string &text, const ImVec2 &button_size) +{ + const ImVec2 text_size = calc_text_size(text); + const ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + + return ImGui::CalcItemSize(button_size, text_size.x + style.FramePadding.x * 2.0f, text_size.y + style.FramePadding.y * 2.0f); +} + +ImVec2 calc_button_size(const std::wstring& wtext, const ImVec2& button_size) +{ + const std::string text = boost::nowide::narrow(wtext); + return calc_button_size(text, button_size); +} + +ImVec2 get_item_spacing() +{ + const ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + return style.ItemSpacing; +} + +float get_slider_float_height() +{ + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + return g.FontSize + style.FramePadding.y * 2.0f + style.ItemSpacing.y; +} + +void set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) +{ + ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); + ImGui::SetNextWindowSize(ImVec2(0.0, 0.0)); +} + +void set_next_window_bg_alpha(float alpha) +{ + ImGui::SetNextWindowBgAlpha(alpha); +} + +void set_next_window_size(float x, float y, ImGuiCond cond) +{ + ImGui::SetNextWindowSize(ImVec2(x, y), cond); +} + +bool begin(const std::string &name, int flags) +{ + return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); +} + +bool begin(const std::string& name, bool* close, int flags) +{ + return ImGui::Begin(name.c_str(), close, (ImGuiWindowFlags)flags); +} + +void end() +{ + ImGui::End(); +} + +bool button(const std::string & label_utf8, const std::string& tooltip) +{ + const bool ret = ImGui::Button(label_utf8.c_str()); + + if (!tooltip.empty() && ImGui::IsItemHovered()) { + auto tooltip_utf8 = tooltip; + ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + } + + return ret; +} + +bool button(const std::string& label_utf8, float width, float height) +{ + return ImGui::Button(label_utf8.c_str(), ImVec2(width, height)); +} + +bool button(const std::wstring& wlabel, float width, float height) +{ + const std::string label = boost::nowide::narrow(wlabel); + return button(label, width, height); +} + +bool radio_button(const std::string& label_utf8, bool active) +{ + return ImGui::RadioButton(label_utf8.c_str(), active); +} + +bool draw_radio_button(const std::string& name, float size, bool active, + std::function draw_callback) +{ + ImGuiWindow& window = *ImGui::GetCurrentWindow(); + if (window.SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window.GetID(name.c_str()); + + const ImVec2 pos = window.DC.CursorPos; + const ImRect total_bb(pos, pos + ImVec2(size, size + style.FramePadding.y * 2.0f)); + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + ImGui::MarkItemEdited(id); + + if (hovered) + window.DrawList->AddRect({ pos.x - 1.0f, pos.y - 1.0f }, { pos.x + size + 1.0f, pos.y + size + 1.0f }, ImGui::GetColorU32(ImGuiCol_CheckMark)); + + if (active) + window.DrawList->AddRect(pos, { pos.x + size, pos.y + size }, ImGui::GetColorU32(ImGuiCol_CheckMark)); + + draw_callback(window, pos, size); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window.DC.LastItemStatusFlags); + return pressed; +} + +bool checkbox(const std::string& label_utf8, bool &value) +{ + return ImGui::Checkbox(label_utf8.c_str(), &value); +} + +void text(const char *label) +{ + ImGui::Text("%s", label); +} + +void text(const std::string &label) +{ + text(label.c_str()); +} + +void text(const std::wstring& wlabel) +{ + const std::string label = boost::nowide::narrow(wlabel); + text(label.c_str()); +} + +void text_colored(const ImVec4& color, const char* label) +{ + ImGui::TextColored(color, "%s", label); +} + +void text_colored(const ImVec4& color, const std::string& label) +{ + text_colored(color, label.c_str()); +} + +void text_wrapped(const char *label, float wrap_width) +{ + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + text(label); + ImGui::PopTextWrapPos(); +} + +void text_wrapped(const std::string &label, float wrap_width) +{ + text_wrapped(label.c_str(), wrap_width); +} + +void tooltip(const char *label, float wrap_width) +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 4.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 8.0f, 8.0f }); + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextUnformatted(label); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + ImGui::PopStyleVar(3); +} + +void tooltip(const std::string& label, float wrap_width) +{ + tooltip(label.c_str(), wrap_width); +} + +ImVec2 get_slider_icon_size() +{ + return calc_button_size(std::wstring(&ImGui::SliderFloatEditBtnIcon, 1)); +} + +static bool image_button_ex(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + ImGui::ItemSize(bb); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, ImGui::GetColorU32(bg_col)); + window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, ImGui::GetColorU32(tint_col)); + + return pressed; +} + +bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + ImGui::PushID((void*)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + ImGui::PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; + return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags); +} + +bool combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/) +{ + // this is to force the label to the left of the widget: + const bool hidden_label = boost::starts_with(label, "##"); + if (!label.empty() && !hidden_label) { + text(label); + ImGui::SameLine(label_width); + } + ImGui::PushItemWidth(item_width); + + int selection_out = selection; + bool res = false; + + const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : ""; + if (ImGui::BeginCombo(hidden_label ? label.c_str() : ("##" + label).c_str(), selection_str, flags)) { + for (int i = 0; i < (int)options.size(); i++) { + if (ImGui::Selectable(options[i].c_str(), i == selection)) { + selection_out = i; + res = true; + } + } + + ImGui::EndCombo(); + } + + selection = selection_out; + return res; +} + +void draw_hexagon(const ImVec2& center, float radius, ImU32 col, float start_angle, float rounding) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + + float a_min = start_angle; + float a_max = start_angle + 2.f * IM_PI; + + if (rounding <= 0) { + window->DrawList->PathArcTo(center, radius, a_min, a_max, 6); + } + else { + const float a_delta = IM_PI / 4.f; + radius -= rounding; + + for (int i = 0; i <= 6; i++) { + float a = a_min + ((float)i / (float)6) * (a_max - a_min); + if (a >= 2.f * IM_PI) + a -= 2.f * IM_PI; + ImVec2 pos = ImVec2(center.x + ImCos(a) * radius, center.y + ImSin(a) * radius); + window->DrawList->PathArcTo(pos, rounding, a - a_delta, a + a_delta, 5); + } + } + window->DrawList->PathFillConvex(col); +} + +// Scroll up for one item +void scroll_up() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + float item_size_y = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; + float win_top = window->Scroll.y; + + ImGui::SetScrollY(win_top - item_size_y); +} + +// Scroll down for one item +void scroll_down() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + float item_size_y = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; + float win_top = window->Scroll.y; + + ImGui::SetScrollY(win_top + item_size_y); +} + +void process_mouse_wheel(int& mouse_wheel) +{ + if (mouse_wheel > 0) + scroll_up(); + else if (mouse_wheel < 0) + scroll_down(); + mouse_wheel = 0; +} + +bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool , int , const char**), int& hovered, int& selected, int& mouse_wheel) +{ + bool is_hovered = false; + ImGui::ListBoxHeader("", size); + + int i=0; + const char* item_text; + while (items_getter(is_undo, i, &item_text)) { + ImGui::Selectable(item_text, i < hovered); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", item_text); + hovered = i; + is_hovered = true; + } + + if (ImGui::IsItemClicked()) + selected = i; + i++; + } + + if (is_hovered) + process_mouse_wheel(mouse_wheel); + + ImGui::ListBoxFooter(); + return is_hovered; +} + +void title(const std::string& str) +{ + text(str); + ImGui::Separator(); +} + +bool want_mouse() +{ + return ImGui::GetIO().WantCaptureMouse; +} + +bool want_keyboard() +{ + return ImGui::GetIO().WantCaptureKeyboard; +} + +bool want_text_input() +{ + return ImGui::GetIO().WantTextInput; +} + +bool want_any_input() +{ + const auto io = ImGui::GetIO(); + return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; +} + +void disable_background_fadeout_animation() +{ + GImGui->DimBgRatio = 1.0f; +} + +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 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 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 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); +} + +std::optional 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 left_inputs() { + ImGui::ClearActiveID(); +} + +std::string 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 + float average_letter_width = calc_text_size(std::string_view("n")).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); + text_width = calc_text_size(result_text).x; + if (text_width < allowed_width) { + // increase letter count + while (count_letter < text.length()) { + ++count_letter; + std::string_view 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 std::string(result_text) + tail; +} + +void 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 draw_cross_hair(const ImVec2 &position, float radius, ImU32 color, int num_segments, float thickness) +{ + auto draw_list = ImGui::GetOverlayDrawList(); + draw_list->AddCircle(position, radius, color, num_segments, thickness); + auto dirs = {ImVec2{0, 1}, ImVec2{1, 0}, ImVec2{0, -1}, ImVec2{-1, 0}}; + for (const ImVec2 &dir : dirs) { + ImVec2 start(position.x + dir.x * 0.5 * radius, position.y + dir.y * 0.5 * radius); + ImVec2 end(position.x + dir.x * 1.5 * radius, position.y + dir.y * 1.5 * radius); + draw_list->AddLine(start, end, color, thickness); + } +} + +bool contain_all_glyphs(const ImFont *font, + const std::string &text) +{ + if (font == nullptr) return false; + if (!font->IsLoaded()) return false; + const ImFontConfig *fc = font->ConfigData; + if (fc == nullptr) return false; + if (text.empty()) return true; + return is_chars_in_ranges(fc->GlyphRanges, text.c_str()); +} + +bool is_char_in_ranges(const ImWchar *ranges, + unsigned int letter) +{ + for (const ImWchar *range = ranges; range[0] && range[1]; range += 2) { + ImWchar from = range[0]; + ImWchar to = range[1]; + if (from <= letter && letter <= to) return true; + if (letter < to) return false; // ranges should be sorted + } + return false; +}; + +bool is_chars_in_ranges(const ImWchar *ranges, + const char *chars_ptr) +{ + while (*chars_ptr) { + unsigned int c = 0; + // UTF-8 to 32-bit character need imgui_internal + int c_len = ImTextCharFromUtf8(&c, chars_ptr, NULL); + chars_ptr += c_len; + if (c_len == 0) break; + if (!is_char_in_ranges(ranges, c)) return false; + } + return true; +} + +bool begin_menu(const char* label, bool enabled) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + bool menu_is_open = ImGui::IsPopupOpen(id, ImGuiPopupFlags_None); + + // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) + ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) flags |= ImGuiWindowFlags_ChildWindow; + + // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). + // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. + // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used. + if (g.MenusIdSubmittedThisFrame.contains(id)) { + if (menu_is_open) + menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + else + g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values + return menu_is_open; + } + + // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu + g.MenusIdSubmittedThisFrame.push_back(id); + + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + bool pressed; + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && + (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, + // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). + // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() + popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + float w = label_size.x; + pressed = /*selectable*/ImGui::Selectable(label, menu_is_open, + ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | + (!enabled ? ImGuiSelectableFlags_Disabled : 0), + ImVec2(w, 0.0f)); + ImGui::PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR( + style.ItemSpacing.x * + (-1.0f + + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else { + // Menu inside a menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, ImGui::GetContentRegionAvail().x - min_w); + pressed = /*selectable*/ImGui::Selectable(label, menu_is_open, + ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | + ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), + ImVec2(min_w, 0.0f)); + ImU32 text_col = ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); + ImGui::RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); + } + + const bool hovered = enabled && ImGui::ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) g.NavWindow = backed_nav_window; + + bool want_open = false; + bool want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_toward_other_child_menu = false; + + ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? + g.OpenPopupStack[g.BeginPopupStack.Size].Window : + NULL; + if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) { + // FIXME-DPI: Values should be derived from a master "scale" factor. + ImRect next_window_rect = child_menu_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + // GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] + } + if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) + want_close = true; + + if (!menu_is_open && hovered && pressed) // Click to open + want_open = true; + else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open + want_open = true; + + if (g.NavActivateId == id) { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + ImGui::NavMoveRequestCancel(); + } + } + else { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + ImGui::NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && ImGui::IsPopupOpen(id, ImGuiPopupFlags_None)) ImGui::ClosePopupToLevel(g.BeginPopupStack.Size, true); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + ImGui::OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) ImGui::OpenPopup(label); + + if (menu_is_open) { + ImGui::SetNextWindowPos(popup_pos, + ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos. + menu_is_open = ImGui::BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + else { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + } + + return menu_is_open; +} + +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*/) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), + // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. + ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. + float w = label_size.x; + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + pressed = ImGui::Selectable(label, selected, flags, ImVec2(w, 0.0f)); + ImGui::PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR( + style.ItemSpacing.x * + (-1.0f + + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else { + // Menu item inside a vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + float shortcut_w = shortcut ? ImGui::CalcTextSize(shortcut, NULL).x : 0.0f; + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = std::max(0.0f, ImGui::GetContentRegionAvail().x - min_w); + pressed = /*selectable*/ImGui::Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + + if (icon_size.x != 0 && icon_size.y != 0) { + float selectable_pos_y = pos.y + -0.5f * style.ItemSpacing.y; + float icon_pos_y = selectable_pos_y + (label_size.y + style.ItemSpacing.y - icon_size.y) / 2; + float icon_pos_x = pos.x + window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f; + ImVec2 icon_pos = ImVec2(icon_pos_x, icon_pos_y); + ImGui::RenderFrame(icon_pos, icon_pos + icon_size, icon_color); + } + + if (shortcut_w > 0.0f) { + ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + ImGui::RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + ImGui::PopStyleColor(); + } + if (selected) { + ImGui::RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), + ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + } + + IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; +} + +} // ImGuiPureWrap diff --git a/src/slic3r/GUI/ImGuiPureWrap.hpp b/src/slic3r/GUI/ImGuiPureWrap.hpp new file mode 100644 index 0000000..2ef3bad --- /dev/null +++ b/src/slic3r/GUI/ImGuiPureWrap.hpp @@ -0,0 +1,166 @@ + +#ifndef slic3r_ImGuiPureWrap_hpp_ +#define slic3r_ImGuiPureWrap_hpp_ + +#include +#include +#include +#include +#include +#include +#include + +struct IMGUI_API ImGuiWindow; + +namespace ImGuiPureWrap +{ + void set_display_size(float w, float h); + + /// + /// Extend ImGui::CalcTextSize to use string_view + /// + ImVec2 calc_text_size(std::string_view text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + ImVec2 calc_text_size(const std::string& text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + + ImVec2 calc_button_size(const std::string& text, const ImVec2& button_size = ImVec2(0, 0)); + ImVec2 calc_button_size(const std::wstring& text, const ImVec2& button_size = ImVec2(0, 0)); + + ImVec2 get_slider_icon_size(); + + ImVec2 get_item_spacing(); + float get_slider_float_height(); + void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); + void set_next_window_bg_alpha(float alpha); + void set_next_window_size(float x, float y, ImGuiCond cond); + + bool begin(const std::string &name, int flags = 0); + bool begin(const std::string& name, bool* close, int flags = 0); + void end(); + + void title(const std::string& str); + + bool draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback); + bool button(const std::string &label, const std::string& tooltip = {}); + bool button(const std::string& label, float width, float height); + bool button(const std::wstring& label, float width, float height); + bool radio_button(const std::string &label, bool active); + + bool checkbox(const std::string& label, bool& value); + + // Use selection = -1 to not mark any option as selected + bool combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f); + + void draw_hexagon(const ImVec2& center, float radius, ImU32 col, float start_angle = 0.f, float rounding = 0.f); + + void text(const char* label); + void text(const std::string& label); + void text(const std::wstring& label); + void text_colored(const ImVec4& color, const char* label); + void text_colored(const ImVec4& color, const std::string& label); + void text_wrapped(const char* label, float wrap_width); + void text_wrapped(const std::string& label, float wrap_width); + void tooltip(const char* label, float wrap_width); + void tooltip(const std::string& label, float wrap_width); + + bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0); + + bool want_mouse(); + bool want_keyboard(); + bool want_text_input(); + bool want_any_input(); + + void disable_background_fadeout_animation(); + + bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel); + void scroll_up(); + void scroll_down(); + void process_mouse_wheel(int& mouse_wheel); + + // 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. + 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 + 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 + 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); + + /// + /// 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 + 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. + /// + 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 + 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 + void escape_double_hash(std::string &text); + + /// + /// Draw symbol of cross hair + /// + /// Center of cross hair + /// Circle radius + /// Color of symbol + /// Precission of circle + /// Thickness of Line in symbol + void draw_cross_hair(const ImVec2 &position, + float radius = 16.f, + ImU32 color = ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, .75f)), + int num_segments = 0, + float thickness = 4.f); + + /// + /// Check that font ranges contain all chars in string + /// (rendered Unicodes are stored in GlyphRanges) + /// + /// Contain glyph ranges + /// Vector of character to check + /// True when all glyphs from text are in font ranges + bool contain_all_glyphs(const ImFont *font, const std::string &text); + bool is_chars_in_ranges(const ImWchar *ranges, const char *chars_ptr); + bool is_char_in_ranges(const ImWchar *ranges, unsigned int letter); + + bool begin_menu(const char* label, bool enabled = true); + void end_menu(); + 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); + + const ImVec4 COL_GREY_DARK = { 0.33f, 0.33f, 0.33f, 1.0f }; + const ImVec4 COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; + const ImVec4 COL_ORANGE_DARK = { 0.67f, 0.36f, 0.19f, 1.0f }; + const ImVec4 COL_ORANGE_LIGHT = { 0.923f, 0.504f, 0.264f, 1.0f }; + const ImVec4 COL_BLUE_DARK = {0.017f,0.326f,0.926f,1.0f}; + const ImVec4 COL_BLUE_LIGHT = { 0.27f, 0.47f, 1.0f, 1.0f }; + const ImVec4 COL_WINDOW_BACKGROUND = { 0.13f, 0.13f, 0.13f, 0.8f }; + const ImVec4 COL_BUTTON_BACKGROUND = COL_BLUE_DARK; + const ImVec4 COL_BUTTON_HOVERED = COL_BLUE_LIGHT; + const ImVec4 COL_BUTTON_ACTIVE = COL_BUTTON_HOVERED; + const ImVec4 COL_WHITE_LIGHT = { 1.0f, 1.0f, 1.0f, 1.0f }; +} + +#endif // slic3r_ImGuiPureWrap_hpp_ + diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index c9d5882..7921791 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -41,6 +41,40 @@ // suggest location #include "libslic3r/ClipperUtils.hpp" // Slic3r::intersection + +// Following two sets keeps characters that ImGui tried to render, but they were not in the atlas, +// and ones that we already tried to add into the atlas. +std::set s_missing_chars; +std::set s_fixed_chars; +bool s_font_cjk; + +// This is a free function that ImGui calls when it renders +// a fallback glyph for c. +void imgui_rendered_fallback_glyph(ImWchar c) +{ + if (ImGui::GetIO().Fonts->Fonts[0] == ImGui::GetFont()) { + // Only do this when we are using the default ImGui font. Otherwise this would conflict with + // EmbossStyleManager's font handling and we would load glyphs needlessly. + auto it = s_fixed_chars.find(c); + if (it == s_fixed_chars.end()) { + // This is the first time we are trying to fix this character. + s_missing_chars.emplace(c); + } else { + // We already tried to add this, but it is still not there. There is a chance + // that loading the CJK font would make this available. + if (! s_font_cjk) { + s_font_cjk = true; + s_missing_chars.emplace(c); + s_fixed_chars.erase(it); + } else { + // We did everything we could. The glyph was not available. + // Do not try to add it anymore. + } + } + } +} + + namespace Slic3r { namespace GUI { @@ -68,6 +102,8 @@ static const std::map font_icons = { {ImGui::PlugMarker , "plug" }, {ImGui::DowelMarker , "dowel" }, {ImGui::SnapMarker , "snap" }, + {ImGui::HorizontalHide , "horizontal_hide" }, + {ImGui::HorizontalShow , "horizontal_show" }, }; static const std::map font_icons_large = { @@ -133,6 +169,47 @@ ImGuiWrapper::ImGuiWrapper() init_style(); ImGui::GetIO().IniFilename = nullptr; + + static const ImWchar ranges_latin2[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0100, 0x017F, // Latin Extended-A + 0, + }; + static const ImWchar ranges_turkish[] = { + 0x0020, 0x01FF, // Basic Latin + Latin Supplement + 0x0100, 0x017F, // Latin Extended-A + 0x0180, 0x01FF, // Turkish + 0, + }; + static const ImWchar ranges_vietnamese[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x0102, 0x0103, + 0x0110, 0x0111, + 0x0128, 0x0129, + 0x0168, 0x0169, + 0x01A0, 0x01A1, + 0x01AF, 0x01B0, + 0x1EA0, 0x1EF9, + 0, + }; + + m_lang_glyphs_info.emplace_back("cs", ranges_latin2, false); + m_lang_glyphs_info.emplace_back("pl", ranges_latin2, false); + m_lang_glyphs_info.emplace_back("hu", ranges_latin2, false); + m_lang_glyphs_info.emplace_back("sl", ranges_latin2, false); + m_lang_glyphs_info.emplace_back("ru", ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(), false); // Default + about 400 Cyrillic characters + m_lang_glyphs_info.emplace_back("uk", ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(), false); + m_lang_glyphs_info.emplace_back("be", ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(), false); + m_lang_glyphs_info.emplace_back("tr", ranges_turkish, false); + m_lang_glyphs_info.emplace_back("vi", ranges_vietnamese, false); + m_lang_glyphs_info.emplace_back("ja", ImGui::GetIO().Fonts->GetGlyphRangesJapanese(), true); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + m_lang_glyphs_info.emplace_back("ko", ImGui::GetIO().Fonts->GetGlyphRangesKorean(), true); // Default + Korean characters + m_lang_glyphs_info.emplace_back("zh_TW",ImGui::GetIO().Fonts->GetGlyphRangesChineseFull(), true); // Traditional Chinese: Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs + m_lang_glyphs_info.emplace_back("zh", ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon(), true); // Simplified Chinese: Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese + m_lang_glyphs_info.emplace_back("th", ImGui::GetIO().Fonts->GetGlyphRangesThai(), false); + m_lang_glyphs_info.emplace_back("else", ImGui::GetIO().Fonts->GetGlyphRangesDefault(), false); } ImGuiWrapper::~ImGuiWrapper() @@ -152,75 +229,24 @@ void ImGuiWrapper::set_language(const std::string &language) } const ImWchar *ranges = nullptr; - size_t idx = language.find('_'); - std::string lang = (idx == std::string::npos) ? language : language.substr(0, idx); - static const ImWchar ranges_latin2[] = - { - 0x0020, 0x00FF, // Basic Latin + Latin Supplement - 0x0100, 0x017F, // Latin Extended-A - 0, - }; - static const ImWchar ranges_turkish[] = { - 0x0020, 0x01FF, // Basic Latin + Latin Supplement - 0x0100, 0x017F, // Latin Extended-A - 0x0180, 0x01FF, // Turkish - 0, - }; - static const ImWchar ranges_vietnamese[] = - { - 0x0020, 0x00FF, // Basic Latin - 0x0102, 0x0103, - 0x0110, 0x0111, - 0x0128, 0x0129, - 0x0168, 0x0169, - 0x01A0, 0x01A1, - 0x01AF, 0x01B0, - 0x1EA0, 0x1EF9, - 0, - }; - m_font_cjk = false; - if (lang == "cs" || lang == "pl" || lang == "hu" || lang == "sl") { - ranges = ranges_latin2; - } else if (lang == "ru" || lang == "uk" || lang == "be") { - ranges = ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters - } else if (lang == "tr") { - ranges = ranges_turkish; - } else if (lang == "vi") { - ranges = ranges_vietnamese; - } else if (lang == "ja") { - ranges = ImGui::GetIO().Fonts->GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs - m_font_cjk = true; - } else if (lang == "ko") { - ranges = ImGui::GetIO().Fonts->GetGlyphRangesKorean(); // Default + Korean characters - m_font_cjk = true; - } else if (lang == "zh") { - ranges = (language == "zh_TW") ? - // Traditional Chinese - // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs - ImGui::GetIO().Fonts->GetGlyphRangesChineseFull() : - // Simplified Chinese - // Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese - ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon(); - m_font_cjk = true; - } else if (lang == "th") { - ranges = ImGui::GetIO().Fonts->GetGlyphRangesThai(); // Default + Thai characters - } else { - ranges = ImGui::GetIO().Fonts->GetGlyphRangesDefault(); // Basic Latin, Extended Latin + + // Get glyph ranges for current language, std CLK flag to inform which font files need to be loaded. + for (const auto& [lang_str, lang_ranges, lang_cjk] : m_lang_glyphs_info) { + if (boost::istarts_with(language, lang_str) || lang_str == "else") { + ranges = lang_ranges; + s_font_cjk = lang_cjk; + } } + s_missing_chars.clear(); + s_fixed_chars.clear(); + if (ranges != m_glyph_ranges) { m_glyph_ranges = ranges; destroy_font(); } } -void ImGuiWrapper::set_display_size(float w, float h) -{ - ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2(w, h); - io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); -} - void ImGuiWrapper::set_scaling(float font_size, float scale_style, float scale_both) { font_size *= scale_both; @@ -259,9 +285,9 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; - if (want_mouse()) + if (ImGuiPureWrap::want_mouse()) new_frame(); - return want_mouse(); + return ImGuiPureWrap::want_mouse(); } bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) @@ -308,7 +334,7 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) io.KeyAlt = evt.AltDown(); io.KeySuper = evt.MetaDown(); } - bool ret = want_keyboard() || want_text_input(); + bool ret = ImGuiPureWrap::want_keyboard() || ImGuiPureWrap::want_text_input(); if (ret) new_frame(); return ret; @@ -363,139 +389,25 @@ void ImGuiWrapper::render() ImGui::Render(); render_draw_data(ImGui::GetDrawData()); m_new_frame_open = false; -} -ImVec2 ImGuiWrapper::calc_text_size(std::string_view text, - bool hide_text_after_double_hash, - float wrap_width) -{ - return ImGui::CalcTextSize(text.data(), text.data() + text.length(), - hide_text_after_double_hash, wrap_width); -} - -ImVec2 ImGuiWrapper::calc_text_size(const std::string& text, - bool hide_text_after_double_hash, - float wrap_width) -{ - return ImGui::CalcTextSize(text.c_str(), NULL, hide_text_after_double_hash, wrap_width); -} - -ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, - bool hide_text_after_double_hash, - float wrap_width) -{ - auto text_utf8 = into_u8(text); - ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, hide_text_after_double_hash, wrap_width); - -/*#ifdef __linux__ - size.x *= m_style_scaling; - size.y *= m_style_scaling; -#endif*/ - - return size; -} - -ImVec2 ImGuiWrapper::calc_button_size(const wxString &text, const ImVec2 &button_size) const -{ - const ImVec2 text_size = this->calc_text_size(text); - const ImGuiContext &g = *GImGui; - const ImGuiStyle &style = g.Style; - - return ImGui::CalcItemSize(button_size, text_size.x + style.FramePadding.x * 2.0f, text_size.y + style.FramePadding.y * 2.0f); -} - -ImVec2 ImGuiWrapper::get_item_spacing() const -{ - const ImGuiContext &g = *GImGui; - const ImGuiStyle &style = g.Style; - return style.ItemSpacing; -} - -float ImGuiWrapper::get_slider_float_height() const -{ - const ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - return g.FontSize + style.FramePadding.y * 2.0f + style.ItemSpacing.y; -} - -void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) -{ - ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); - ImGui::SetNextWindowSize(ImVec2(0.0, 0.0)); -} - -void ImGuiWrapper::set_next_window_bg_alpha(float alpha) -{ - ImGui::SetNextWindowBgAlpha(alpha); -} - -void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond) -{ - ImGui::SetNextWindowSize(ImVec2(x, y), cond); -} - -bool ImGuiWrapper::begin(const std::string &name, int flags) -{ - return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); -} - -bool ImGuiWrapper::begin(const wxString &name, int flags) -{ - return begin(into_u8(name), flags); -} - -bool ImGuiWrapper::begin(const std::string& name, bool* close, int flags) -{ - return ImGui::Begin(name.c_str(), close, (ImGuiWindowFlags)flags); -} - -bool ImGuiWrapper::begin(const wxString& name, bool* close, int flags) -{ - return begin(into_u8(name), close, flags); -} - -void ImGuiWrapper::end() -{ - ImGui::End(); -} - -bool ImGuiWrapper::button(const wxString &label, const wxString& tooltip) -{ - auto label_utf8 = into_u8(label); - const bool ret = ImGui::Button(label_utf8.c_str()); - - if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { - auto tooltip_utf8 = into_u8(tooltip); - ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + if (! s_missing_chars.empty()) { + // If there were some characters that ImGui was unable to render, we will destroy current font. + // It will be rebuilt in the next call of new_frame including these. + destroy_font(); + this->set_requires_extra_frame(); } - - return ret; } -bool ImGuiWrapper::button(const wxString& label, float width, float height) -{ - auto label_utf8 = into_u8(label); - return ImGui::Button(label_utf8.c_str(), ImVec2(width, height)); -} - -bool ImGuiWrapper::button(const wxString& label, const ImVec2 &size, bool enable) +bool ImGuiWrapper::button(const std::string& label, const ImVec2 &size, bool enable) { disabled_begin(!enable); - auto label_utf8 = into_u8(label); - bool res = ImGui::Button(label_utf8.c_str(), size); + bool res = ImGui::Button(label.c_str(), size); disabled_end(); return (enable) ? res : false; } - -bool ImGuiWrapper::radio_button(const wxString &label, bool active) -{ - auto label_utf8 = into_u8(label); - return ImGui::RadioButton(label_utf8.c_str(), active); -} - void ImGuiWrapper::draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id) { ImGuiIO& io = ImGui::GetIO(); @@ -505,120 +417,7 @@ void ImGuiWrapper::draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, const ImFontAtlas::CustomRect* const rect = GetTextureCustomRect(icon_id); const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; - window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); -} - -bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active, - std::function draw_callback) -{ - ImGuiWindow& window = *ImGui::GetCurrentWindow(); - if (window.SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window.GetID(name.c_str()); - - const ImVec2 pos = window.DC.CursorPos; - const ImRect total_bb(pos, pos + ImVec2(size, size + style.FramePadding.y * 2.0f)); - ImGui::ItemSize(total_bb, style.FramePadding.y); - if (!ImGui::ItemAdd(total_bb, id)) - return false; - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); - if (pressed) - ImGui::MarkItemEdited(id); - - if (hovered) - window.DrawList->AddRect({ pos.x - 1.0f, pos.y - 1.0f }, { pos.x + size + 1.0f, pos.y + size + 1.0f }, ImGui::GetColorU32(ImGuiCol_CheckMark)); - - if (active) - window.DrawList->AddRect(pos, { pos.x + size, pos.y + size }, ImGui::GetColorU32(ImGuiCol_CheckMark)); - - draw_callback(window, pos, size); - - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window.DC.LastItemStatusFlags); - return pressed; -} - -bool ImGuiWrapper::checkbox(const wxString &label, bool &value) -{ - auto label_utf8 = into_u8(label); - return ImGui::Checkbox(label_utf8.c_str(), &value); -} - -void ImGuiWrapper::text(const char *label) -{ - ImGui::Text("%s", label); -} - -void ImGuiWrapper::text(const std::string &label) -{ - ImGuiWrapper::text(label.c_str()); -} - -void ImGuiWrapper::text(const wxString &label) -{ - auto label_utf8 = into_u8(label); - ImGuiWrapper::text(label_utf8.c_str()); -} - -void ImGuiWrapper::text_colored(const ImVec4& color, const char* label) -{ - ImGui::TextColored(color, "%s", label); -} - -void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label) -{ - ImGuiWrapper::text_colored(color, label.c_str()); -} - -void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label) -{ - auto label_utf8 = into_u8(label); - ImGuiWrapper::text_colored(color, label_utf8.c_str()); -} - -void ImGuiWrapper::text_wrapped(const char *label, float wrap_width) -{ - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); - this->text(label); - ImGui::PopTextWrapPos(); -} - -void ImGuiWrapper::text_wrapped(const std::string &label, float wrap_width) -{ - this->text_wrapped(label.c_str(), wrap_width); -} - -void ImGuiWrapper::text_wrapped(const wxString &label, float wrap_width) -{ - auto label_utf8 = into_u8(label); - this->text_wrapped(label_utf8.c_str(), wrap_width); -} - -void ImGuiWrapper::tooltip(const char *label, float wrap_width) -{ - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 4.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 8.0f, 8.0f }); - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(wrap_width); - ImGui::TextUnformatted(label); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - ImGui::PopStyleVar(3); -} - -void ImGuiWrapper::tooltip(const wxString &label, float wrap_width) -{ - tooltip(label.ToUTF8().data(), wrap_width); -} - -ImVec2 ImGuiWrapper::get_slider_icon_size() const -{ - return this->calc_button_size(std::wstring(&ImGui::SliderFloatEditBtnIcon, 1)); + window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiPSWrap::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); } bool ImGuiWrapper::slider_float(const char* label, float* 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*/) @@ -647,7 +446,7 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float m_last_slider_status.can_take_snapshot = ImGui::IsItemClicked(); if (!tooltip.empty() && ImGui::IsItemHovered()) - this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width); + ImGuiPureWrap::tooltip(into_u8(tooltip).c_str(), max_tooltip_width); if (clamp) *v = std::clamp(*v, v_min, v_max); @@ -673,7 +472,7 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float int frame_padding = style.ItemSpacing.y / 2; // keep same line height for input and slider const ImTextureID tex_id = io.Fonts->TexID; - if (image_button(tex_id, size, uv0, uv1, frame_padding, ImVec4(0.0, 0.0, 0.0, 0.0), ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags_PressedOnClick)) { + if (ImGuiPureWrap::image_button(tex_id, size, uv0, uv1, frame_padding, ImVec4(0.0, 0.0, 0.0, 0.0), ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags_PressedOnClick)) { if (!slider_editing) ImGui::SetKeyboardFocusHere(-1); else @@ -684,7 +483,7 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) - this->tooltip(into_u8(_L("Edit")).c_str(), max_tooltip_width); + ImGuiPureWrap::tooltip(into_u8(_L("Edit")).c_str(), max_tooltip_width); ImGui::PopStyleVar(); } @@ -698,7 +497,7 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); ImGui::SameLine(); - this->text(out_label.c_str()); + ImGuiPureWrap::text(out_label.c_str()); ImGui::PopStyleVar(); } @@ -716,49 +515,7 @@ bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, fl return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); } -static bool image_button_ex(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); - ImGui::ItemSize(bb); - if (!ImGui::ItemAdd(bb, id)) - return false; - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); - - // Render - const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - ImGui::RenderNavHighlight(bb, id); - ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); - if (bg_col.w > 0.0f) - window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, ImGui::GetColorU32(bg_col)); - window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, ImGui::GetColorU32(tint_col)); - - return pressed; -} - -bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (window->SkipItems) - return false; - - // Default to using texture ID as ID. User can still push string/integer prefixes. - ImGui::PushID((void*)(intptr_t)user_texture_id); - const ImGuiID id = window->GetID("#image"); - ImGui::PopID(); - - const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; - return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags); -} - -bool ImGuiWrapper::image_button(const wchar_t icon, const wxString& tooltip) +bool ImGuiWrapper::image_button(const wchar_t icon, const std::string& tooltip, bool highlight_on_hover/* = true*/) { const ImGuiIO& io = ImGui::GetIO(); const ImTextureID tex_id = io.Fonts->TexID; @@ -769,113 +526,18 @@ bool ImGuiWrapper::image_button(const wchar_t icon, const wxString& tooltip) const ImVec2 size = { float(rect->Width), float(rect->Height) }; const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h); const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h); - ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.25f, 0.25f, 0.25f, 1.0f }); - const bool res = image_button(tex_id, size, uv0, uv1); + ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, highlight_on_hover ? 1.0f : 0.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.25f, 0.25f, 0.25f, highlight_on_hover ? 1.0f : 0.0f }); + const bool res = ImGuiPureWrap::image_button(tex_id, size, uv0, uv1); ImGui::PopStyleColor(3); if (!tooltip.empty() && ImGui::IsItemHovered()) - this->tooltip(tooltip, ImGui::GetFontSize() * 20.0f); + ImGuiPureWrap::tooltip(tooltip, ImGui::GetFontSize() * 20.0f); return res; } -bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/) -{ - return combo(into_u8(label), options, selection, flags, label_width, item_width); -} - -bool ImGuiWrapper::combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/) -{ - // this is to force the label to the left of the widget: - const bool hidden_label = boost::starts_with(label, "##"); - if (!label.empty() && !hidden_label) { - text(label); - ImGui::SameLine(label_width); - } - ImGui::PushItemWidth(item_width); - - int selection_out = selection; - bool res = false; - - const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : ""; - if (ImGui::BeginCombo(hidden_label ? label.c_str() : ("##" + label).c_str(), selection_str, flags)) { - for (int i = 0; i < (int)options.size(); i++) { - if (ImGui::Selectable(options[i].c_str(), i == selection)) { - selection_out = i; - res = true; - } - } - - ImGui::EndCombo(); - } - - selection = selection_out; - return res; -} - -// Scroll up for one item -static void scroll_up() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - float item_size_y = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; - float win_top = window->Scroll.y; - - ImGui::SetScrollY(win_top - item_size_y); -} - -// Scroll down for one item -static void scroll_down() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - float item_size_y = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; - float win_top = window->Scroll.y; - - ImGui::SetScrollY(win_top + item_size_y); -} - -static void process_mouse_wheel(int& mouse_wheel) -{ - if (mouse_wheel > 0) - scroll_up(); - else if (mouse_wheel < 0) - scroll_down(); - mouse_wheel = 0; -} - -bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool , int , const char**), int& hovered, int& selected, int& mouse_wheel) -{ - bool is_hovered = false; - ImGui::ListBoxHeader("", size); - - int i=0; - const char* item_text; - while (items_getter(is_undo, i, &item_text)) { - ImGui::Selectable(item_text, i < hovered); - - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", item_text); - hovered = i; - is_hovered = true; - } - - if (ImGui::IsItemClicked()) - selected = i; - i++; - } - - if (is_hovered) - process_mouse_wheel(mouse_wheel); - - ImGui::ListBoxFooter(); - return is_hovered; -} - // It's a copy of IMGui::Selactable function. // But a little beat modified to change a label text. // If item is hovered we should use another color for highlighted letters. @@ -1147,12 +809,12 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co // Process mouse wheel if (mouse_hovered > 0) - process_mouse_wheel(mouse_wheel); + ImGuiPureWrap::process_mouse_wheel(mouse_wheel); // process Up/DownArrows and Enter process_key_down(ImGuiKey_UpArrow, [&hovered_id, mouse_hovered]() { if (mouse_hovered > 0) - scroll_up(); + ImGuiPureWrap::scroll_up(); else { if (hovered_id > 0) --hovered_id; @@ -1162,7 +824,7 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co process_key_down(ImGuiKey_DownArrow, [&hovered_id, mouse_hovered, i]() { if (mouse_hovered > 0) - scroll_down(); + ImGuiPureWrap::scroll_down(); else { if (hovered_id < 0) hovered_id = 0; @@ -1178,10 +840,10 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co ImGui::ListBoxFooter(); - auto check_box = [&edited, this](const wxString& label, bool& check) { + auto check_box = [&edited](const std::string& label, bool& check) { ImGui::SameLine(); bool ch = check; - checkbox(label, ch); + ImGuiPureWrap::checkbox(label, ch); if (ImGui::IsItemClicked()) { check = !check; edited = true; @@ -1191,16 +853,10 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co ImGui::AlignTextToFramePadding(); // add checkboxes for show/hide Categories and Groups - text(_L("Use for search")+":"); - check_box(_L("Category"), view_params.category); + ImGuiPureWrap::text(_u8L("Use for search")+":"); + check_box(_u8L("Category"), view_params.category); if (is_localized) - check_box(_L("Search in English"), view_params.english); -} - -void ImGuiWrapper::title(const std::string& str) -{ - text(str); - ImGui::Separator(); + check_box(_u8L("Search in English"), view_params.english); } void ImGuiWrapper::disabled_begin(bool disabled) @@ -1223,54 +879,28 @@ void ImGuiWrapper::disabled_end() } } -bool ImGuiWrapper::want_mouse() const -{ - return ImGui::GetIO().WantCaptureMouse; -} - -bool ImGuiWrapper::want_keyboard() const -{ - return ImGui::GetIO().WantCaptureKeyboard; -} - -bool ImGuiWrapper::want_text_input() const -{ - return ImGui::GetIO().WantTextInput; -} - -bool ImGuiWrapper::want_any_input() const -{ - const auto io = ImGui::GetIO(); - return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; -} - ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id) { auto item = m_custom_glyph_rects_ids.find(tex_id); return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr; } -void ImGuiWrapper::disable_background_fadeout_animation() -{ - GImGui->DimBgRatio = 1.0f; -} - -ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA& color) +ImU32 ImGuiPSWrap::to_ImU32(const ColorRGBA& color) { return ImGui::GetColorU32({ color.r(), color.g(), color.b(), color.a() }); } -ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGBA& color) +ImVec4 ImGuiPSWrap::to_ImVec4(const ColorRGBA& color) { return { color.r(), color.g(), color.b(), color.a() }; } -ColorRGBA ImGuiWrapper::from_ImU32(const ImU32& color) +ColorRGBA ImGuiPSWrap::from_ImU32(const ImU32& color) { return from_ImVec4(ImGui::ColorConvertU32ToFloat4(color)); } -ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4& color) +ColorRGBA ImGuiPSWrap::from_ImVec4(const ImVec4& color) { return { color.x, color.y, color.z, color.w }; } @@ -1293,58 +923,6 @@ static bool input_optional(std::optional &v, Func& f, std::function& 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, @@ -1397,100 +975,6 @@ bool ImGuiWrapper::slider_optional_int(const char *label, } 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 - float average_letter_width = calc_text_size(std::string_view("n")).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); - text_width = calc_text_size(result_text).x; - if (text_width < allowed_width) { - // increase letter count - while (count_letter < text.length()) { - ++count_letter; - std::string_view 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 std::string(result_text) + 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); -} - ImVec2 ImGuiWrapper::suggest_location(const ImVec2 &dialog_size, const Slic3r::Polygon &interest, const ImVec2 &canvas_size) @@ -1566,7 +1050,7 @@ ImVec2 ImGuiWrapper::suggest_location(const ImVec2 &dialog_size, void ImGuiWrapper::draw( const Polygon &polygon, ImDrawList * draw_list /* = ImGui::GetOverlayDrawList()*/, - ImU32 color /* = ImGui::GetColorU32(COL_ORANGE_LIGHT)*/, + ImU32 color /* = ImGui::GetColorU32(COL_BLUE_LIGHT)*/, float thickness /* = 3.f*/) { // minimal one line consist of 2 points @@ -1583,55 +1067,6 @@ void ImGuiWrapper::draw( } } -void ImGuiWrapper::draw_cross_hair(const ImVec2 &position, float radius, ImU32 color, int num_segments, float thickness) { - auto draw_list = ImGui::GetOverlayDrawList(); - draw_list->AddCircle(position, radius, color, num_segments, thickness); - auto dirs = {ImVec2{0, 1}, ImVec2{1, 0}, ImVec2{0, -1}, ImVec2{-1, 0}}; - for (const ImVec2 &dir : dirs) { - ImVec2 start(position.x + dir.x * 0.5 * radius, position.y + dir.y * 0.5 * radius); - ImVec2 end(position.x + dir.x * 1.5 * radius, position.y + dir.y * 1.5 * radius); - draw_list->AddLine(start, end, color, thickness); - } -} - -bool ImGuiWrapper::contain_all_glyphs(const ImFont *font, - const std::string &text) -{ - if (font == nullptr) return false; - if (!font->IsLoaded()) return false; - const ImFontConfig *fc = font->ConfigData; - if (fc == nullptr) return false; - if (text.empty()) return true; - return is_chars_in_ranges(fc->GlyphRanges, text.c_str()); -} - -bool ImGuiWrapper::is_char_in_ranges(const ImWchar *ranges, - unsigned int letter) -{ - for (const ImWchar *range = ranges; range[0] && range[1]; range += 2) { - ImWchar from = range[0]; - ImWchar to = range[1]; - if (from <= letter && letter <= to) return true; - if (letter < to) return false; // ranges should be sorted - } - return false; -}; - -bool ImGuiWrapper::is_chars_in_ranges(const ImWchar *ranges, - const char *chars_ptr) -{ - while (*chars_ptr) { - unsigned int c = 0; - // UTF-8 to 32-bit character need imgui_internal - int c_len = ImTextCharFromUtf8(&c, chars_ptr, NULL); - chars_ptr += c_len; - if (c_len == 0) break; - if (!is_char_in_ranges(ranges, c)) return false; - } - return true; -} - - #ifdef __APPLE__ static const ImWchar ranges_keyboard_shortcuts[] = { @@ -1685,22 +1120,26 @@ void ImGuiWrapper::init_font(bool compress) io.Fonts->Clear(); // Create ranges of characters from m_glyph_ranges, possibly adding some OS specific special characters. - ImVector ranges; + ImVector ranges; ImFontGlyphRangesBuilder builder; - builder.AddRanges(m_glyph_ranges); + builder.AddRanges(m_glyph_ranges); builder.AddChar(ImWchar(0x2026)); // … - if (m_font_cjk) { - // This is a temporary fix of https://github.com/qidi3d/QIDISlicer/issues/8171. The translation - // contains characters not in the ImGui ranges for simplified Chinese. For now, just add them manually. - // In future, it might be worth to parse the dictionary and add all the necessary characters. + if (s_font_cjk) { builder.AddChar(ImWchar(0x5ED3)); builder.AddChar(ImWchar(0x8F91)); } + // Add the characters that that needed the fallback character. + for (ImWchar c : s_missing_chars) { + builder.AddChar(c); + s_fixed_chars.emplace(c); + } + s_missing_chars.clear(); + #ifdef __APPLE__ - if (m_font_cjk) + if (s_font_cjk) // Apple keyboard shortcuts are only contained in the CJK fonts. builder.AddRanges(ranges_keyboard_shortcuts); #endif @@ -1708,7 +1147,13 @@ 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 - ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + (m_font_cjk ? "NotoSansCJK-Regular.ttc" : "NotoSans-Regular.ttf")).c_str(), m_font_size, nullptr, ranges.Data); + ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "NotoSans-Regular.ttf").c_str(), m_font_size, nullptr, ranges.Data); + if (s_font_cjk) { + ImFontConfig config; + config.MergeMode = true; + io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + "NotoSansCJK-Regular.ttc").c_str(), m_font_size, &config, ranges.Data); + } + if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { @@ -1716,16 +1161,6 @@ void ImGuiWrapper::init_font(bool compress) } } -#ifdef __APPLE__ - ImFontConfig config; - config.MergeMode = true; - if (! m_font_cjk) { - // Apple keyboard shortcuts are only contained in the CJK fonts. - [[maybe_unused]]ImFont *font_cjk = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc").c_str(), m_font_size, &config, ranges_keyboard_shortcuts); - assert(font_cjk != nullptr); - } -#endif - float font_scale = m_font_size/15; int icon_sz = lround(16 * font_scale); // default size of icon is 16 px @@ -1735,6 +1170,11 @@ void ImGuiWrapper::init_font(bool compress) m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); } + const int icon_sz_m = int(1.25 * icon_sz); // default size of medium icon is 20 px + for (auto& icon : font_icons_medium) { + m_custom_glyph_rects_ids[icon.first] = + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz_m, icon_sz_m, 3.0 * font_scale + icon_sz_m); + } for (auto& icon : font_icons_large) { m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); @@ -1771,6 +1211,10 @@ void ImGuiWrapper::init_font(bool compress) load_icon_from_svg(icon, icon_sz); } + for (auto icon : font_icons_medium) { + load_icon_from_svg(icon, icon_sz_m); + } + icon_sz *= 2; // default size of large icon is 32 px for (auto icon : font_icons_large) { load_icon_from_svg(icon, icon_sz); @@ -1783,6 +1227,7 @@ void ImGuiWrapper::init_font(bool compress) // Upload texture to graphics system GLint last_texture; + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); glsafe(::glGenTextures(1, &m_font_texture)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_font_texture)); @@ -1849,55 +1294,55 @@ void ImGuiWrapper::init_style() // Window //B18 style.WindowRounding = 4.0f; - set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROUND); - set_color(ImGuiCol_TitleBgActive, COL_BLUE_LIGHT); + set_color(ImGuiCol_WindowBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); + set_color(ImGuiCol_TitleBgActive, ImGuiPureWrap::COL_BLUE_DARK); // Generics - set_color(ImGuiCol_FrameBg, COL_GREY_DARK); - set_color(ImGuiCol_FrameBgHovered, COL_GREY_LIGHT); - set_color(ImGuiCol_FrameBgActive, COL_GREY_LIGHT); + set_color(ImGuiCol_FrameBg, ImGuiPureWrap::COL_GREY_DARK); + set_color(ImGuiCol_FrameBgHovered, ImGuiPureWrap::COL_GREY_LIGHT); + set_color(ImGuiCol_FrameBgActive, ImGuiPureWrap::COL_GREY_LIGHT); // Text selection //B18 - set_color(ImGuiCol_TextSelectedBg, COL_BLUE_LIGHT); + set_color(ImGuiCol_TextSelectedBg, ImGuiPureWrap::COL_BLUE_DARK); // Buttons - set_color(ImGuiCol_Button, COL_BUTTON_BACKGROUND); - set_color(ImGuiCol_ButtonHovered, COL_BUTTON_HOVERED); - set_color(ImGuiCol_ButtonActive, COL_BUTTON_ACTIVE); + set_color(ImGuiCol_Button, ImGuiPureWrap::COL_BUTTON_BACKGROUND); + set_color(ImGuiCol_ButtonHovered, ImGuiPureWrap::COL_BUTTON_HOVERED); + set_color(ImGuiCol_ButtonActive, ImGuiPureWrap::COL_BUTTON_ACTIVE); // Checkbox //B18 - set_color(ImGuiCol_CheckMark, COL_BLUE_LIGHT); + set_color(ImGuiCol_CheckMark, ImGuiPureWrap::COL_BLUE_LIGHT); // ComboBox items //B18 - set_color(ImGuiCol_Header, COL_BLUE_LIGHT); - set_color(ImGuiCol_HeaderHovered, COL_BLUE_LIGHT); - set_color(ImGuiCol_HeaderActive, COL_BLUE_LIGHT); + set_color(ImGuiCol_Header, ImGuiPureWrap::COL_BLUE_DARK); + set_color(ImGuiCol_HeaderHovered, ImGuiPureWrap::COL_BLUE_LIGHT); + set_color(ImGuiCol_HeaderActive, ImGuiPureWrap::COL_BLUE_LIGHT); // Slider //B18 - set_color(ImGuiCol_SliderGrab, COL_BLUE_LIGHT); - set_color(ImGuiCol_SliderGrabActive, COL_BLUE_LIGHT); + set_color(ImGuiCol_SliderGrab, ImGuiPureWrap::COL_BLUE_DARK); + set_color(ImGuiCol_SliderGrabActive,ImGuiPureWrap::COL_BLUE_LIGHT); // Separator //B18 - set_color(ImGuiCol_Separator, COL_BLUE_LIGHT); + set_color(ImGuiCol_Separator, ImGuiPureWrap::COL_BLUE_LIGHT); // Tabs //B18 - set_color(ImGuiCol_Tab, COL_BLUE_LIGHT); - set_color(ImGuiCol_TabHovered, COL_BLUE_LIGHT); - set_color(ImGuiCol_TabActive, COL_BLUE_LIGHT); - set_color(ImGuiCol_TabUnfocused, COL_GREY_DARK); - set_color(ImGuiCol_TabUnfocusedActive, COL_GREY_LIGHT); + set_color(ImGuiCol_Tab, ImGuiPureWrap::COL_BLUE_DARK); + set_color(ImGuiCol_TabHovered, ImGuiPureWrap::COL_BLUE_LIGHT); + set_color(ImGuiCol_TabActive, ImGuiPureWrap::COL_BLUE_LIGHT); + set_color(ImGuiCol_TabUnfocused, ImGuiPureWrap::COL_GREY_DARK); + set_color(ImGuiCol_TabUnfocusedActive, ImGuiPureWrap::COL_GREY_LIGHT); // Scrollbars //B18 - set_color(ImGuiCol_ScrollbarGrab, COL_BLUE_LIGHT); - set_color(ImGuiCol_ScrollbarGrabHovered, COL_BLUE_LIGHT); - set_color(ImGuiCol_ScrollbarGrabActive, COL_BLUE_LIGHT); + set_color(ImGuiCol_ScrollbarGrab, ImGuiPureWrap::COL_BLUE_LIGHT); + set_color(ImGuiCol_ScrollbarGrabHovered,ImGuiPureWrap::COL_BLUE_LIGHT); + set_color(ImGuiCol_ScrollbarGrabActive, ImGuiPureWrap::COL_BLUE_LIGHT); } void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) @@ -1922,14 +1367,15 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) shader->start_using(); -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES // Backup GL state GLenum last_active_texture; glsafe(::glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture)); GLuint last_program; glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program)); GLuint last_texture; glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture)); GLuint last_array_buffer; glsafe(::glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer)); GLuint last_vertex_array_object = 0; - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) +#endif // !SLIC3R_OPENGL_ES glsafe(::glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object)); GLint last_viewport[4]; glsafe(::glGetIntegerv(GL_VIEWPORT, last_viewport)); GLint last_scissor_box[4]; glsafe(::glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box)); @@ -1954,25 +1400,6 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_STENCIL_TEST)); glsafe(::glEnable(GL_SCISSOR_TEST)); -#else - // We are using the OpenGL fixed pipeline to make the example code simpler to read! - // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. - GLint last_texture; glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); - GLint last_polygon_mode[2]; glsafe(::glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode)); - GLint last_viewport[4]; glsafe(::glGetIntegerv(GL_VIEWPORT, last_viewport)); - GLint last_scissor_box[4]; glsafe(::glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box)); - GLint last_texture_env_mode; glsafe(::glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &last_texture_env_mode)); - glsafe(::glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_STENCIL_TEST)); - glsafe(::glEnable(GL_SCISSOR_TEST)); - glsafe(::glEnable(GL_TEXTURE_2D)); - glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); - glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. @@ -2004,13 +1431,15 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert); const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx); -#if ENABLE_GL_CORE_PROFILE GLuint vao_id = 0; - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) { +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES glsafe(::glGenVertexArrays(1, &vao_id)); glsafe(::glBindVertexArray(vao_id)); +#if !SLIC3R_OPENGL_ES } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES GLuint vbo_id; glsafe(::glGenBuffers(1, &vbo_id)); @@ -2072,17 +1501,22 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) glsafe(::glDeleteBuffers(1, &ibo_id)); glsafe(::glDeleteBuffers(1, &vbo_id)); -#if ENABLE_GL_CORE_PROFILE - if (vao_id > 0) - glsafe(::glDeleteVertexArrays(1, &vao_id)); -#endif // ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) { +#endif // !SLIC3R_OPENGL_ES + if (vao_id > 0) + glsafe(::glDeleteVertexArrays(1, &vao_id)); +#if !SLIC3R_OPENGL_ES + } +#endif // !SLIC3R_OPENGL_ES } -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES // Restore modified GL state glsafe(::glBindTexture(GL_TEXTURE_2D, last_texture)); glsafe(::glActiveTexture(last_active_texture)); - if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#if !SLIC3R_OPENGL_ES + if (OpenGLManager::get_gl_info().is_core_profile()) +#endif // !SLIC3R_OPENGL_ES glsafe(::glBindVertexArray(last_vertex_array_object)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer)); glsafe(::glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha)); @@ -2094,16 +1528,6 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) if (last_enable_scissor_test) glsafe(::glEnable(GL_SCISSOR_TEST)); else glsafe(::glDisable(GL_SCISSOR_TEST)); glsafe(::glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3])); glsafe(::glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3])); -#else - // Restore modified state - glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_texture_env_mode)); - glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture)); - glsafe(::glPopAttrib()); - glsafe(::glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); - glsafe(::glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]))); - glsafe(::glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3])); - glsafe(::glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3])); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES shader->stop_using(); @@ -2124,6 +1548,13 @@ void ImGuiWrapper::destroy_font() io.Fonts->TexID = 0; glsafe(::glDeleteTextures(1, &m_font_texture)); m_font_texture = 0; + + // We have destroyed current font, including all characters that we may have added dynamically. + // Move move all characters that we already added into the list of missing chars again, + // so they are all added at once. + for (ImWchar c : s_fixed_chars) + s_missing_chars.emplace(c); + s_fixed_chars.clear(); } } @@ -2163,5 +1594,6 @@ void ImGuiWrapper::clipboard_set(void* /* user_data */, const char* text) } + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 1fefa63..eae6dcd 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -1,11 +1,7 @@ #ifndef slic3r_ImGuiWrapper_hpp_ #define slic3r_ImGuiWrapper_hpp_ -#include -#include -#include - -#include +#include "ImGuiPureWrap.hpp" #include @@ -22,6 +18,7 @@ struct OptionViewParameters; class wxString; class wxMouseEvent; class wxKeyEvent; +struct ImRect; struct IMGUI_API ImGuiWindow; @@ -30,9 +27,9 @@ namespace GUI { class ImGuiWrapper { + std::vector> m_lang_glyphs_info; // language prefix, ranges, whether it needs CLK font const ImWchar* m_glyph_ranges{ nullptr }; // Chinese, Japanese, Korean - bool m_font_cjk{ false }; float m_font_size{ 18.0 }; unsigned m_font_texture{ 0 }; float m_style_scaling{ 1.0 }; @@ -60,7 +57,6 @@ public: ~ImGuiWrapper(); void set_language(const std::string &language); - void set_display_size(float w, float h); void set_scaling(float font_size, float scale_style, float scale_both); bool update_mouse_data(wxMouseEvent &evt); bool update_key_data(wxKeyEvent &evt); @@ -74,121 +70,30 @@ 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); } - /// - /// Extend ImGui::CalcTextSize to use string_view - /// - static ImVec2 calc_text_size(std::string_view text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); - static ImVec2 calc_text_size(const std::string& text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); - static ImVec2 calc_text_size(const wxString &text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); - ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const; - ImVec2 get_item_spacing() const; - float get_slider_float_height() const; const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; } LastSliderStatus& get_last_slider_status() { return m_last_slider_status; } - void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); - void set_next_window_bg_alpha(float alpha); - void set_next_window_size(float x, float y, ImGuiCond cond); - - bool begin(const std::string &name, int flags = 0); - bool begin(const wxString &name, int flags = 0); - bool begin(const std::string& name, bool* close, int flags = 0); - bool begin(const wxString& name, bool* close, int flags = 0); - void end(); - - bool button(const wxString &label, const wxString& tooltip = {}); - bool button(const wxString& label, float width, float height); - bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f) - bool radio_button(const wxString &label, bool active); + bool button(const std::string& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f) void draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id); - bool draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback); - bool checkbox(const wxString &label, bool &value); - static void text(const char *label); - static void text(const std::string &label); - static void text(const wxString &label); - static void text_colored(const ImVec4& color, const char* label); - static void text_colored(const ImVec4& color, const std::string& label); - static void text_colored(const ImVec4& color, const wxString& label); - void text_wrapped(const char *label, float wrap_width); - void text_wrapped(const std::string &label, float wrap_width); - void text_wrapped(const wxString &label, float wrap_width); - void tooltip(const char *label, float wrap_width); - void tooltip(const wxString &label, float wrap_width); // Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true). - ImVec2 get_slider_icon_size() const; bool slider_float(const char* label, float* 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); bool slider_float(const std::string& label, float* 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); bool slider_float(const wxString& label, float* 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); - bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0); - bool image_button(const wchar_t icon, const wxString& tooltip = L""); - - // Use selection = -1 to not mark any option as selected - bool combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f); - bool combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f); - bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel); + bool image_button(const wchar_t icon, const std::string& tooltip = {}, bool highlight_on_hover = true); void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str, Search::OptionViewParameters& view_params, int& selected, bool& edited, int& mouse_wheel, bool is_localized); - void title(const std::string& str); void disabled_begin(bool disabled); void disabled_end(); - bool want_mouse() const; - bool want_keyboard() const; - 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); - /// /// Suggest loacation of dialog window, /// dependent on actual visible thing on platter @@ -214,45 +119,13 @@ public: //B18 static void draw(const Polygon &polygon, ImDrawList * draw_list = ImGui::GetOverlayDrawList(), - ImU32 color = ImGui::GetColorU32(COL_BLUE_LIGHT), + ImU32 color = ImGui::GetColorU32(ImGuiPureWrap::COL_BLUE_LIGHT), float thickness = 3.f); - /// - /// Draw symbol of cross hair - /// - /// Center of cross hair - /// Circle radius - /// Color of symbol - /// Precission of circle - /// Thickness of Line in symbol - static void draw_cross_hair(const ImVec2 &position, - float radius = 16.f, - ImU32 color = ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, .75f)), - int num_segments = 0, - float thickness = 4.f); - - /// - /// Check that font ranges contain all chars in string - /// (rendered Unicodes are stored in GlyphRanges) - /// - /// Contain glyph ranges - /// Vector of character to check - /// True when all glyphs from text are in font ranges - static bool contain_all_glyphs(const ImFont *font, const std::string &text); - static bool is_chars_in_ranges(const ImWchar *ranges, const char *chars_ptr); - static bool is_char_in_ranges(const ImWchar *ranges, unsigned int letter); - bool requires_extra_frame() const { return m_requires_extra_frame; } void set_requires_extra_frame() { m_requires_extra_frame = true; } void reset_requires_extra_frame() { m_requires_extra_frame = false; } - void disable_background_fadeout_animation(); - - static ImU32 to_ImU32(const ColorRGBA& color); - static ImVec4 to_ImVec4(const ColorRGBA& color); - static ColorRGBA from_ImU32(const ImU32& color); - static ColorRGBA from_ImVec4(const ImVec4& color); - ImFontAtlasCustomRect* GetTextureCustomRect(const wchar_t& tex_id); static const ImVec4 COL_GREY_DARK; @@ -282,6 +155,13 @@ private: LastSliderStatus m_last_slider_status; }; +namespace ImGuiPSWrap +{ + ImU32 to_ImU32(const ColorRGBA& color); + ImVec4 to_ImVec4(const ColorRGBA& color); + ColorRGBA from_ImU32(const ImU32& color); + ColorRGBA from_ImVec4(const ImVec4& color); +} } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index 84d721f..ea9d5fa 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -65,18 +65,29 @@ namespace instance_check_internal //if (argc < 2) // return ret; std::vector arguments { argv[0] }; + bool send_if_url = false; + bool has_url = false; for (int i = 1; i < argc; ++i) { const std::string token = argv[i]; + if (token.find("qidislicer://") == 0) { + BOOST_LOG_TRIVIAL(info) << "url found: " << token; + has_url = true; + } // Processing of boolean command line arguments shall match DynamicConfig::read_cli(). if (token == "--single-instance") ret.should_send = true; else if (token == "--no-single-instance") ret.should_send = false; + else if (token == "--single-instance-on-url") + send_if_url = true; else arguments.emplace_back(token); } + if (send_if_url && has_url) { + ret.should_send = true; + } ret.cl_string = escape_strings_cstyle(arguments); - BOOST_LOG_TRIVIAL(debug) << "single instance: " << + BOOST_LOG_TRIVIAL(info) << "single instance: " << (ret.should_send.has_value() ? (*ret.should_send ? "true" : "false") : "undefined") << ". other params: " << ret.cl_string; return ret; @@ -115,11 +126,14 @@ namespace instance_check_internal other_instance_hash_major = PtrToUint(handle); other_instance_hash_major = other_instance_hash_major << 32; other_instance_hash += other_instance_hash_major; + handle = GetProp(hwnd, L"Instance_Is_Maximized"); + const bool maximized = PtrToUint(handle) == 1; + if(my_instance_hash == other_instance_hash) { BOOST_LOG_TRIVIAL(debug) << "win enum - found correct instance"; l_qidi_slicer_hwnd = hwnd; - ShowWindow(hwnd, SW_SHOWMAXIMIZED); + ShowWindow(hwnd, maximized ? SW_SHOWMAXIMIZED : SW_SHOW); SetForegroundWindow(hwnd); return false; } @@ -374,6 +388,7 @@ namespace GUI { wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent); wxDEFINE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent); +wxDEFINE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent); wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent); void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler) @@ -406,6 +421,7 @@ void OtherInstanceMessageHandler::shutdown(MainFrame* main_frame) HWND hwnd = main_frame->GetHandle(); RemoveProp(hwnd, L"Instance_Hash_Minor"); RemoveProp(hwnd, L"Instance_Hash_Major"); + RemoveProp(hwnd, L"Instance_Is_Maximized"); #endif //_WIN32 #if __APPLE__ //delete macos implementation @@ -435,14 +451,30 @@ void OtherInstanceMessageHandler::init_windows_properties(MainFrame* main_frame, { size_t minor_hash = instance_hash & 0xFFFFFFFF; size_t major_hash = (instance_hash & 0xFFFFFFFF00000000) >> 32; + size_t is_maximized = main_frame->IsMaximized() ? 1 : 0; HWND hwnd = main_frame->GetHandle(); HANDLE handle_minor = UIntToPtr(minor_hash); HANDLE handle_major = UIntToPtr(major_hash); + HANDLE handle_is_maximized = UIntToPtr(is_maximized); SetProp(hwnd, L"Instance_Hash_Minor", handle_minor); SetProp(hwnd, L"Instance_Hash_Major", handle_major); + SetProp(hwnd, L"Instance_Is_Maximized", handle_is_maximized); //BOOST_LOG_TRIVIAL(debug) << "window properties initialized " << instance_hash << " (" << minor_hash << " & "<< major_hash; } +void OtherInstanceMessageHandler::update_windows_properties(MainFrame* main_frame) +{ + if (m_initialized) { + // dlete old value of "Instance_Is_Maximized" property + HWND hwnd = main_frame->GetHandle(); + RemoveProp(hwnd, L"Instance_Is_Maximized"); + // set new value for "Instance_Is_Maximized" property + size_t is_maximized = main_frame->IsMaximized() ? 1 : 0; + HANDLE handle_is_maximized = UIntToPtr(is_maximized); + SetProp(hwnd, L"Instance_Is_Maximized", handle_is_maximized); + } +} + #if 0 void OtherInstanceMessageHandler::print_window_info(HWND hwnd) @@ -516,6 +548,9 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message) else if (it->rfind("qidislicer://open?file=", 0) == 0) #endif downloads.emplace_back(*it); + else if (it->rfind("qidislicer://login", 0) == 0) { + wxPostEvent(m_callback_evt_handler, LoginOtherInstanceEvent(GUI::EVT_LOGIN_OTHER_INSTANCE, std::string(*it))); + } } if (! paths.empty()) { //wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here? diff --git a/src/slic3r/GUI/InstanceCheck.hpp b/src/slic3r/GUI/InstanceCheck.hpp index a2fdf16..322cb01 100644 --- a/src/slic3r/GUI/InstanceCheck.hpp +++ b/src/slic3r/GUI/InstanceCheck.hpp @@ -44,8 +44,10 @@ class MainFrame; using LoadFromOtherInstanceEvent = Event>; using StartDownloadOtherInstanceEvent = Event>; +using LoginOtherInstanceEvent = Event; wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent); wxDECLARE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent); +wxDECLARE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent); using InstanceGoToFrontEvent = SimpleEvent; wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent); @@ -75,6 +77,7 @@ public: #endif //__APPLE__ #ifdef _WIN32 static void init_windows_properties(MainFrame* main_frame, size_t instance_hash); + void update_windows_properties(MainFrame* main_frame); #endif //WIN32 private: bool m_initialized { false }; diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp index 57c9a62..19e3f9b 100644 --- a/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.cpp @@ -1,6 +1,11 @@ +#include +#include #include #include "BoostThreadWorker.hpp" +#include "libslic3r/Thread.hpp" +#include "slic3r/GUI/Jobs/ProgressIndicator.hpp" +#include "slic3r/GUI/Jobs/ThreadSafeQueue.hpp" namespace Slic3r { namespace GUI { @@ -39,9 +44,10 @@ void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner) void BoostThreadWorker::run() { bool stop = false; + while (!stop) { m_input_queue - .consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) { + .consume_one(BlockingWait{0}, [this, &stop](JobEntry &e) { if (!e.job) stop = true; else { @@ -56,7 +62,6 @@ void BoostThreadWorker::run() e.canceled = m_canceled.load(); m_output_queue.push(std::move(e)); // finalization message } - m_running.store(false); }); }; } @@ -77,9 +82,12 @@ std::future BoostThreadWorker::call_on_main_thread(std::function } BoostThreadWorker::BoostThreadWorker(std::shared_ptr pri, - boost::thread::attributes &attribs, - const char * name) - : m_progress(std::move(pri)), m_name{name} + boost::thread::attributes &attribs, + const char *name) + : m_progress(std::move(pri)) + , m_input_queue{m_running} + , m_output_queue{m_running} + , m_name{name} { if (m_progress) m_progress->set_cancel_callback([this](){ cancel(); }); diff --git a/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp index 2fe39c0..551b9e5 100644 --- a/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/BoostThreadWorker.hpp @@ -2,13 +2,26 @@ #define BOOSTTHREADWORKER_HPP #include - -#include "Worker.hpp" - #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Worker.hpp" #include "ThreadSafeQueue.hpp" +#include "slic3r/GUI/Jobs/Job.hpp" + +namespace Slic3r { +class ProgressIndicator; +} // namespace Slic3r namespace Slic3r { namespace GUI { @@ -63,8 +76,58 @@ class BoostThreadWorker : public Worker, private Job::Ctl void deliver(BoostThreadWorker &runner); }; - using JobQueue = ThreadSafeQueueSPSC; - using MessageQueue = ThreadSafeQueueSPSC; + // The m_running state flag needs special attention. Previously, it was set simply in the run() + // method whenever a new job was taken from the input queue and unset after the finalize message + // was pushed into the output queue. This was not correct. It must not be possible to consume + // the finalize message before the flag gets unset, these two operations must be done atomically + // So the underlying queues are here extended to support handling of this m_running flag. + template + class RawQueue: public std::deque { + std::atomic *m_running_ptr; + + public: + using std::deque::deque; + explicit RawQueue(std::atomic &rflag): m_running_ptr{&rflag} {} + + void set_running() { m_running_ptr->store(true); } + void set_stopped() { m_running_ptr->store(false); } + }; + + // The running flag is set if a job is popped from the queue + template + class RawJobQueue: public RawQueue { + public: + using RawQueue::RawQueue; + void pop_front() + { + RawQueue::pop_front(); + this->set_running(); + } + }; + + // The running flag is unset when the finalize message is pushed into the queue + template + class RawMsgQueue: public RawQueue { + public: + using RawQueue::RawQueue; + void push_back(El &&entry) + { + this->emplace_back(std::move(entry)); + } + + template + auto & emplace_back(EArgs&&...args) + { + auto &el = RawQueue::emplace_back(std::forward(args)...); + if (el.get_type() == WorkerMessage::Finalize) + this->set_stopped(); + + return el; + } + }; + + using JobQueue = ThreadSafeQueueSPSC; + using MessageQueue = ThreadSafeQueueSPSC; boost::thread m_thread; std::atomic m_running{false}, m_canceled{false}; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 5eeb95e..146d0a3 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -98,6 +98,7 @@ struct DataCreateObject // Define which gizmo open on the success GLGizmosManager::EType gizmo; + // additionl rotation around Z axe, given by style settings std::optional angle = {}; }; @@ -164,6 +165,7 @@ bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); template static ExPolygons create_shape(DataBase &input, Fnc was_canceled); + // create sure that emboss object is bigger than source object [in mm] constexpr float safe_extension = 1.0f; @@ -191,7 +193,7 @@ TriangleMesh create_default_mesh(); /// New mesh data /// Text configuration, ... /// Transformation of volume -void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr); +void final_update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr); /// /// Update name in right panel @@ -280,7 +282,6 @@ void CreateVolumeJob::process(Ctl &ctl) { throw std::runtime_error("Bad input data for EmbossCreateVolumeJob."); m_result = create_mesh(*m_input.base, was_canceled(ctl, *m_input.base), ctl); } - void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { if (!::finalize(canceled, eptr, *m_input.base)) return; @@ -328,6 +329,7 @@ void CreateObjectJob::process(Ctl &ctl) 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 @@ -344,9 +346,8 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) if (m_result.empty()) return create_message("Can't create empty object."); - GUI_App &app = wxGetApp(); - Plater *plater = app.plater(); - + GUI_App &app = wxGetApp(); + Plater *plater = app.plater(); plater->take_snapshot(_L("Add Emboss text object")); Model& model = plater->model(); @@ -409,7 +410,7 @@ 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); + ::final_update_volume(std::move(m_result), m_input); } void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base) @@ -435,14 +436,13 @@ void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const Da const ObjectList *obj_list = app.obj_list(); if (obj_list != nullptr) update_name_in_list(*obj_list, *volume); -} + } ModelObject *object = volume->get_object(); assert(object != nullptr); if (object == nullptr) return; - Plater *plater = app.plater(); if (plater->printer_technology() == ptSLA) sla::reproject_points_and_holes(object); @@ -492,7 +492,7 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) // 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); + ::final_update_volume(std::move(m_result), m_input, &m_input.transform); } namespace { @@ -534,11 +534,11 @@ const GLVolume *find_closest( /// /// Start job for add object with text into scene /// -/// Contain worker, build shape, gizmo -/// Define params for create volume +/// Contain worker, build shape, gizmo, +/// emboss_data is moved out soo it can't be const /// Screen coordinat, where to create new object laying on bed /// True when can add job to worker otherwise FALSE -bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor); +bool start_create_object_job(CreateVolumeParams &input, const Vec2d &coor); /// /// Start job to create volume on the surface of object @@ -549,7 +549,7 @@ bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss /// True .. try to create volume without screen_coor, /// False .. /// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way -bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &screen_coor, bool try_no_coor); +bool start_create_volume_on_surface_job(CreateVolumeParams &input, const Vec2d &screen_coor, bool try_no_coor); } // namespace @@ -564,25 +564,25 @@ SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &text_vo return ::create_sources(volumes, text_volume.id().id); } -bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos) +bool start_create_volume(CreateVolumeParams &input, const Vec2d &mouse_pos) { - if (data == nullptr) + if (input.data == nullptr) return false; if (!check(input)) return false; if (input.gl_volume == nullptr) // object is not under mouse position soo create object on plater - return ::start_create_object_job(input, std::move(data), mouse_pos); + return ::start_create_object_job(input, mouse_pos); bool try_no_coor = true; - return ::start_create_volume_on_surface_job(input, std::move(data), mouse_pos, try_no_coor); + return ::start_create_volume_on_surface_job(input, mouse_pos, try_no_coor); } -bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data) +bool start_create_volume_without_position(CreateVolumeParams &input) { - assert(data != nullptr); - if (data == nullptr) + assert(input.data != nullptr); + if (input.data == nullptr) return false; if (!check(input)) return false; @@ -600,17 +600,17 @@ bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr 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); + return ::start_create_object_job(input, 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); + return ::start_create_object_job(input, screen_center); bool try_no_coor = false; - return ::start_create_volume_on_surface_job(input, std::move(data), coor, try_no_coor); + return ::start_create_volume_on_surface_job(input, coor, try_no_coor); } #ifdef EXECUTE_UPDATE_ON_MAIN_THREAD @@ -678,6 +678,7 @@ bool start_update_volume(DataUpdate &&data, const ModelVolume &volume, const Sel } } // namespace Slic3r::GUI::Emboss + //////////////////////////// /// private namespace implementation namespace { @@ -708,14 +709,16 @@ bool check(GLGizmosManager::EType gizmo) { assert(gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg); return gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg; - } +} + bool check(const CreateVolumeParams &input) { bool res = is_valid(input.volume_type); auto gizmo_type = static_cast(input.gizmo); res &= ::check(gizmo_type); - return res; + return res; } + bool check(const DataCreateVolume &input, bool is_main_thread) { bool check_fontfile = false; @@ -726,7 +729,7 @@ bool check(const DataCreateVolume &input, bool is_main_thread) res &= check(input.gizmo); assert(!input.base->shape.projection.use_surface); res &= !input.base->shape.projection.use_surface; - return res; + return res; } bool check(const DataCreateObject &input) { @@ -830,32 +833,26 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w { // 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 bbs = 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.; - 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 < input.text_lines.size(); ++text_line_index) { const BoundingBoxes &line_bbs = bbs[text_line_index]; const TextLine &line = input.text_lines[text_line_index]; PolygonPoints samples = sample_slice(line, line_bbs, shape.scale); - std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + std::vector angles = calculate_angles(line_bbs, samples, line.polygon); for (size_t i = 0; i < line_bbs.size(); ++i) { const BoundingBox &letter_bb = line_bbs[i]; @@ -918,7 +915,6 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w return TriangleMesh(std::move(result)); } - template TriangleMesh try_create_mesh(DataBase &input, const Fnc& was_canceled) { @@ -996,18 +992,18 @@ void update_name_in_list(const ObjectList& object_list, const ModelVolume& volum 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; @@ -1015,13 +1011,13 @@ void update_name_in_list(const ObjectList& object_list, const ModelVolume& volum object_list.update_name_in_list(object_index, volume_index); } -void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr) +void final_update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr) { // for sure that some object will be created - if (mesh.its.empty()) + if (mesh.its.empty()) return create_message("Empty mesh can't be created."); - Plater *plater = wxGetApp().plater(); + Plater *plater = wxGetApp().plater(); // Check gizmo is still open otherwise job should be canceled assert(plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss || plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Svg); @@ -1030,15 +1026,21 @@ void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3 // 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); + 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) { + if (data.trmat.has_value()) { + assert(tr == nullptr); + tr = &(*data.trmat); + } + + if (tr != nullptr) { volume->set_transformation(*tr); } else { // apply fix matrix made by store to .3mf @@ -1047,7 +1049,6 @@ void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3 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); } @@ -1064,14 +1065,14 @@ void create_volume(TriangleMesh &&mesh, GLCanvas3D *canvas = plater->canvas3D(); ModelObjectPtrs &objects = plater->model().objects; - ModelObject *obj = nullptr; - size_t object_idx = 0; + 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) { + if (o->id() == object_id) { obj = o; break; - } + } } // Parent object for text volume was propably removed. @@ -1090,6 +1091,7 @@ void create_volume(TriangleMesh &&mesh, 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); @@ -1099,14 +1101,13 @@ void create_volume(TriangleMesh &&mesh, 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 + volume->name = data.volume_name; // copy if (trmat.has_value()) { volume->set_transformation(*trmat); @@ -1139,7 +1140,7 @@ void create_volume(TriangleMesh &&mesh, // 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); + wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection); if (!sel.IsEmpty()) obj_list->select_item(sel.front()); @@ -1161,7 +1162,7 @@ OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const 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; + double projection_size = max_z - min_z; Matrix3d transformation_for_vector = tr.linear(); // Projection must be negative value. // System of text coordinate @@ -1187,7 +1188,6 @@ OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform 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, std::function was_canceled) { assert(!sources.empty()); BoundingBox bb = get_extents(shapes); @@ -1213,8 +1213,8 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor biggest_count = its.vertices.size(); biggest = &s; } - size_t source_index = &s - &sources.front(); - size_t its_index = itss.size(); + 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)); } @@ -1224,17 +1224,17 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor Transform3d tr_inv = biggest->tr.inverse(); Transform3d cut_projection_tr = tr_inv * tr; - size_t itss_index = s_to_itss[biggest - &sources.front()]; + 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) + if (&s == biggest) continue; - Transform3d tr = s.tr * tr_inv; - bool fix_reflected = true; + 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); @@ -1242,8 +1242,8 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor } // tr_inv = transformation of mesh inverted - Transform3d emboss_tr = cut_projection_tr.inverse(); - BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); + 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) / @@ -1284,7 +1284,6 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2, std::function was_canceled) { - // Precalculate bounding boxes of glyphs // Separate lines of text to vector of Bounds const EmbossShape &es = input1.create_shape(); @@ -1295,10 +1294,6 @@ TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &in assert(get_count_lines(es.shapes_with_ids) == input1.text_lines.size()); size_t count_lines = input1.text_lines.size(); std::vector bbs = 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; @@ -1306,7 +1301,7 @@ TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &in const BoundingBoxes &line_bbs = bbs[text_line_index]; const TextLine &line = input1.text_lines[text_line_index]; PolygonPoints samples = sample_slice(line, line_bbs, es.scale); - std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + std::vector angles = calculate_angles(line_bbs, samples, line.polygon); for (size_t i = 0; i < line_bbs.size(); ++i) { const BoundingBox &glyph_bb = line_bbs[i]; @@ -1348,7 +1343,6 @@ TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &in return TriangleMesh(std::move(result)); } - // input can't be const - cache of font template TriangleMesh cut_surface(DataBase& input1, const SurfaceVolumeData& input2, const Fnc& was_canceled) @@ -1490,67 +1484,85 @@ const GLVolume *find_closest( return closest; } -bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor) +bool start_create_object_job(CreateVolumeParams &input, const Vec2d &coor) { const Pointfs &bed_shape = input.build_volume.bed_shape(); auto gizmo_type = static_cast(input.gizmo); - DataCreateObject data{std::move(emboss_data), coor, input.camera, bed_shape, gizmo_type, input.angle}; + DataCreateObject data{std::move(input.data), coor, input.camera, bed_shape, gizmo_type, input.angle}; // Fix: adding text on print bed with style containing use_surface if (data.base->shape.projection.use_surface) // Til the print bed is flat using surface for Object is useless data.base->shape.projection.use_surface = false; - auto job = std::make_unique(std::move(data)); + + auto job = std::make_unique(std::move(data)); return queue_job(input.worker, std::move(job)); } -bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &screen_coor, bool try_no_coor) +namespace { +// for creation volume +ModelVolumePtrs prepare_volumes_to_slice(const ModelObject &mo) { + const ModelVolumePtrs &volumes = mo.volumes; + ModelVolumePtrs result; + result.reserve(volumes.size()); + for (ModelVolume *volume : volumes) { + // only part could be surface for volumes + if (!volume->is_model_part()) + continue; + + result.push_back(volume); + } + return result; +} +} // namespace + +bool start_create_volume_on_surface_job(CreateVolumeParams &input, const Vec2d &screen_coor, bool try_no_coor) { - auto on_bad_state = [&input, try_no_coor](DataBasePtr data_, const ModelObject *object = nullptr) { + auto on_bad_state = [&input, try_no_coor](const ModelObject *object = nullptr) { if (try_no_coor) { // Can't create on coordinate try to create somewhere - return start_create_volume_without_position(input, std::move(data_)); + return start_create_volume_without_position(input); } else { // 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; + if (input.data->shape.projection.use_surface) + input.data->shape.projection.use_surface = false; if (object == nullptr) return false; auto gizmo_type = static_cast(input.gizmo); - return start_create_volume_job(input.worker, *object, {}, std::move(data_), input.volume_type, gizmo_type); + return start_create_volume_job(input.worker, *object, {}, std::move(input.data), input.volume_type, gizmo_type); } }; assert(input.gl_volume != nullptr); if (input.gl_volume == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); const Model *model = input.canvas.get_model(); assert(model != nullptr); if (model == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); const ModelObjectPtrs &objects = model->objects; const ModelVolume *volume = get_model_volume(*input.gl_volume, objects); assert(volume != nullptr); if (volume == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); const ModelInstance *instance = get_model_instance(*input.gl_volume, objects); assert(instance != nullptr); if (instance == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); const ModelObject *object = volume->get_object(); assert(object != nullptr); if (object == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); auto cond = RaycastManager::AllowVolumes({volume->id().id}); RaycastManager::Meshes meshes = create_meshes(input.canvas, cond); @@ -1563,19 +1575,23 @@ bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr d if (!hit.has_value()) // When model is broken. It could appear that hit miss the object. // So add part near by in simmilar manner as right panel do - return on_bad_state(std::move(data), object); + return on_bad_state(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); + auto gizmo_type = static_cast(input.gizmo); + + // Create text lines for Per Glyph projection when needed + input.data->create_text_lines(transform, prepare_volumes_to_slice(*object)); + // 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); + return start_create_volume_job(input.worker, *object, transform, std::move(input.data), input.volume_type, gizmo_type); } void create_message(const std::string &message) { show_error(nullptr, message.c_str()); } -} // namespace \ No newline at end of file +} // namespace diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 1707d42..bbb1007 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -8,8 +8,10 @@ #include // ExPolygonsWithIds #include "libslic3r/Point.hpp" // Transform3d #include "libslic3r/ObjectID.hpp" + #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/TextLines.hpp" + #include "Job.hpp" // forward declarations @@ -59,6 +61,14 @@ public: // False (engraved).. move into object (NEGATIVE_VOLUME) bool is_outside = true; + /// + /// Used only with text for embossing per glyph + /// + /// Embossed volume final transformation in world + /// Volumes to be sliced to text lines + /// True on succes otherwise False(Per glyph shoud be disabled) + virtual bool create_text_lines(const Transform3d& tr, const ModelVolumePtrs &vols) { return false; } + // Define per letter projection on one text line // [optional] It is not used when empty Slic3r::Emboss::TextLines text_lines = {}; @@ -95,7 +105,6 @@ struct DataCreateVolume : public DataBase // new created volume transformation Transform3d trmat; }; - using DataBasePtr = std::unique_ptr; /// @@ -105,10 +114,16 @@ 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; + + // Transformation of volume after update volume shape + // NOTE: Add for style change, because it change rotation and distance from surface + std::optional trmat; }; /// @@ -175,7 +190,7 @@ struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData{}; class UpdateSurfaceVolumeJob : public Job { UpdateSurfaceVolumeData m_input; - TriangleMesh m_result; + TriangleMesh m_result; public: // move params to private variable @@ -196,6 +211,10 @@ SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume) /// struct CreateVolumeParams { + // base input data for job + // When nullptr there is some issue with creation params ... + DataBasePtr data; + GLCanvas3D &canvas; // Direction of ray into scene @@ -229,22 +248,15 @@ struct CreateVolumeParams /// /// Create new volume on position of mouse cursor /// -/// canvas + camera + bed shape + -/// Shape of emboss -/// New created volume type -/// Knows object in scene -/// Define which gizmo open on the success - enum GLGizmosManager::EType -/// Define position where to create volume -/// Wanted additionl move in Z(emboss) direction of new created volume -/// Wanted additionl rotation around Z of new created volume +/// Cantain all needed data for start creation job /// True on success otherwise False -bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos); +bool start_create_volume(CreateVolumeParams &input, const Vec2d &mouse_pos); /// /// Same as previous function but without mouse position /// Need to suggest position or put near the selection /// -bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data); +bool start_create_volume_without_position(CreateVolumeParams &input); /// /// Start job for update embossed volume @@ -255,6 +267,7 @@ bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr /// Could cast ray to scene /// True when start job otherwise false bool start_update_volume(DataUpdate &&data, const ModelVolume &volume, const Selection &selection, RaycastManager &raycaster); + } // namespace Slic3r::GUI #endif // slic3r_EmbossJob_hpp_ diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 9e60afa..ecf5fec 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -101,8 +101,8 @@ void SLAImportJob::prepare() { reset(); - auto path = p->import_dlg->get_path(); - auto nm = wxFileName(path); + const std::string path = p->import_dlg->get_path(); + auto nm = wxFileName(from_u8(path)); p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); if (p->path.empty()) { p->err = _u8L("The file does not exist."); diff --git a/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp index 3ee1672..57bb4e2 100644 --- a/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp +++ b/src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp @@ -15,11 +15,6 @@ 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. @@ -31,10 +26,22 @@ class ThreadSafeQueueSPSC std::queue> m_queue; mutable std::mutex m_mutex; std::condition_variable m_cond_var; + public: + // Forward arguments to the underlying queue + template + ThreadSafeQueueSPSC(Qargs &&...qargs) + : m_queue{Container{std::forward(qargs)...}} {} + + ThreadSafeQueueSPSC(const ThreadSafeQueueSPSC&) = delete; + ThreadSafeQueueSPSC(ThreadSafeQueueSPSC&&) = delete; + ThreadSafeQueueSPSC& operator=(const ThreadSafeQueueSPSC&) = delete; + ThreadSafeQueueSPSC& operator=(ThreadSafeQueueSPSC &&) = delete; + // Consume one element, block if the queue is empty. - template bool consume_one(const BlockingWait &blkw, Fn &&fn) + template + bool consume_one(const BlockingWait &blkw, Fn &&fn) { static_assert(!std::is_reference_v, ""); static_assert(std::is_default_constructible_v, ""); @@ -59,13 +66,10 @@ public: 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; } @@ -92,7 +96,8 @@ public: } // Push element into the queue. - template void push(TArgs&&...el) + template + void push(TArgs&&...el) { std::lock_guard lk{m_mutex}; m_queue.emplace(std::forward(el)...); diff --git a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp new file mode 100644 index 0000000..a387485 --- /dev/null +++ b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp @@ -0,0 +1,796 @@ + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "LibVGCodeWrapper.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Color.hpp" +#include "libslic3r/CustomGCode.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/GCode/WipeTower.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "../../src/libvgcode/include/GCodeInputData.hpp" +#include "../../src/libvgcode/include/PathVertex.hpp" +#include "libvgcode/include/Types.hpp" + +namespace libvgcode { +class Viewer; + +Vec3 convert(const Slic3r::Vec3f& v) +{ + return { v.x(), v.y(), v.z() }; +} + +Slic3r::Vec3f convert(const Vec3& v) +{ + return { v[0], v[1], v[2] }; +} + +Mat4x4 convert(const Slic3r::Matrix4f& m) +{ + Mat4x4 ret; + std::memcpy(ret.data(), m.data(), 16 * sizeof(float)); + return ret; +} + +Slic3r::ColorRGBA convert(const Color& c) +{ + static const float inv_255 = 1.0f / 255.0f; + return { c[0] * inv_255, c[1] * inv_255, c[2] * inv_255, 1.0f }; +} + +Color convert(const Slic3r::ColorRGBA& c) +{ + return { static_cast(c.r() * 255.0f), static_cast(c.g() * 255.0f), static_cast(c.b() * 255.0f) }; +} + +Color convert(const std::string& color_str) +{ + Slic3r::ColorRGBA color_rgba; + return decode_color(color_str, color_rgba) ? convert(color_rgba) : DUMMY_COLOR; +} + +Slic3r::GCodeExtrusionRole convert(EGCodeExtrusionRole role) +{ + switch (role) + { + case EGCodeExtrusionRole::None: { return Slic3r::GCodeExtrusionRole::None; } + case EGCodeExtrusionRole::Perimeter: { return Slic3r::GCodeExtrusionRole::Perimeter; } + case EGCodeExtrusionRole::ExternalPerimeter: { return Slic3r::GCodeExtrusionRole::ExternalPerimeter; } + case EGCodeExtrusionRole::OverhangPerimeter: { return Slic3r::GCodeExtrusionRole::OverhangPerimeter; } + case EGCodeExtrusionRole::InternalInfill: { return Slic3r::GCodeExtrusionRole::InternalInfill; } + case EGCodeExtrusionRole::SolidInfill: { return Slic3r::GCodeExtrusionRole::SolidInfill; } + case EGCodeExtrusionRole::TopSolidInfill: { return Slic3r::GCodeExtrusionRole::TopSolidInfill; } + case EGCodeExtrusionRole::Ironing: { return Slic3r::GCodeExtrusionRole::Ironing; } + case EGCodeExtrusionRole::BridgeInfill: { return Slic3r::GCodeExtrusionRole::BridgeInfill; } + case EGCodeExtrusionRole::GapFill: { return Slic3r::GCodeExtrusionRole::GapFill; } + case EGCodeExtrusionRole::Skirt: { return Slic3r::GCodeExtrusionRole::Skirt; } + case EGCodeExtrusionRole::SupportMaterial: { return Slic3r::GCodeExtrusionRole::SupportMaterial; } + case EGCodeExtrusionRole::SupportMaterialInterface: { return Slic3r::GCodeExtrusionRole::SupportMaterialInterface; } + case EGCodeExtrusionRole::WipeTower: { return Slic3r::GCodeExtrusionRole::WipeTower; } + case EGCodeExtrusionRole::Custom: { return Slic3r::GCodeExtrusionRole::Custom; } + default: { return Slic3r::GCodeExtrusionRole::None; } + } +} + +EGCodeExtrusionRole convert(Slic3r::GCodeExtrusionRole role) +{ + switch (role) + { + case Slic3r::GCodeExtrusionRole::None: { return EGCodeExtrusionRole::None; } + case Slic3r::GCodeExtrusionRole::Perimeter: { return EGCodeExtrusionRole::Perimeter; } + case Slic3r::GCodeExtrusionRole::ExternalPerimeter: { return EGCodeExtrusionRole::ExternalPerimeter; } + case Slic3r::GCodeExtrusionRole::OverhangPerimeter: { return EGCodeExtrusionRole::OverhangPerimeter; } + case Slic3r::GCodeExtrusionRole::InternalInfill: { return EGCodeExtrusionRole::InternalInfill; } + case Slic3r::GCodeExtrusionRole::SolidInfill: { return EGCodeExtrusionRole::SolidInfill; } + case Slic3r::GCodeExtrusionRole::TopSolidInfill: { return EGCodeExtrusionRole::TopSolidInfill; } + case Slic3r::GCodeExtrusionRole::Ironing: { return EGCodeExtrusionRole::Ironing; } + case Slic3r::GCodeExtrusionRole::BridgeInfill: { return EGCodeExtrusionRole::BridgeInfill; } + case Slic3r::GCodeExtrusionRole::GapFill: { return EGCodeExtrusionRole::GapFill; } + case Slic3r::GCodeExtrusionRole::Skirt: { return EGCodeExtrusionRole::Skirt; } + case Slic3r::GCodeExtrusionRole::SupportMaterial: { return EGCodeExtrusionRole::SupportMaterial; } + case Slic3r::GCodeExtrusionRole::SupportMaterialInterface: { return EGCodeExtrusionRole::SupportMaterialInterface; } + case Slic3r::GCodeExtrusionRole::WipeTower: { return EGCodeExtrusionRole::WipeTower; } + case Slic3r::GCodeExtrusionRole::Custom: { return EGCodeExtrusionRole::Custom; } + default: { return EGCodeExtrusionRole::None; } + } +} + +EMoveType convert(Slic3r::EMoveType type) +{ + switch (type) + { + case Slic3r::EMoveType::Noop: { return EMoveType::Noop; } + case Slic3r::EMoveType::Retract: { return EMoveType::Retract; } + case Slic3r::EMoveType::Unretract: { return EMoveType::Unretract; } + case Slic3r::EMoveType::Seam: { return EMoveType::Seam; } + case Slic3r::EMoveType::Tool_change: { return EMoveType::ToolChange; } + case Slic3r::EMoveType::Color_change: { return EMoveType::ColorChange; } + case Slic3r::EMoveType::Pause_Print: { return EMoveType::PausePrint; } + case Slic3r::EMoveType::Custom_GCode: { return EMoveType::CustomGCode; } + case Slic3r::EMoveType::Travel: { return EMoveType::Travel; } + case Slic3r::EMoveType::Wipe: { return EMoveType::Wipe; } + case Slic3r::EMoveType::Extrude: { return EMoveType::Extrude; } + default: { return EMoveType::COUNT; } + } +} + +EOptionType convert(const Slic3r::GUI::Preview::OptionType& type) +{ + switch (type) + { + case Slic3r::GUI::Preview::OptionType::Travel: { return EOptionType::Travels; } + case Slic3r::GUI::Preview::OptionType::Wipe: { return EOptionType::Wipes; } + case Slic3r::GUI::Preview::OptionType::Retractions: { return EOptionType::Retractions; } + case Slic3r::GUI::Preview::OptionType::Unretractions: { return EOptionType::Unretractions; } + case Slic3r::GUI::Preview::OptionType::Seams: { return EOptionType::Seams; } + case Slic3r::GUI::Preview::OptionType::ToolChanges: { return EOptionType::ToolChanges; } + case Slic3r::GUI::Preview::OptionType::ColorChanges: { return EOptionType::ColorChanges; } + case Slic3r::GUI::Preview::OptionType::PausePrints: { return EOptionType::PausePrints; } + case Slic3r::GUI::Preview::OptionType::CustomGCodes: { return EOptionType::CustomGCodes; } +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + case Slic3r::GUI::Preview::OptionType::CenterOfGravity: { return EOptionType::CenterOfGravity; } + case Slic3r::GUI::Preview::OptionType::ToolMarker: { return EOptionType::ToolMarker; } +#else + case Slic3r::GUI::Preview::OptionType::CenterOfGravity: { return EOptionType::COUNT; } + case Slic3r::GUI::Preview::OptionType::ToolMarker: { return EOptionType::COUNT; } +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + default: { return EOptionType::COUNT; } + } +} + +ETimeMode convert(const Slic3r::PrintEstimatedStatistics::ETimeMode& mode) +{ + switch (mode) + { + case Slic3r::PrintEstimatedStatistics::ETimeMode::Normal: { return ETimeMode::Normal; } + case Slic3r::PrintEstimatedStatistics::ETimeMode::Stealth: { return ETimeMode::Stealth; } + default: { return ETimeMode::COUNT; } + } +} + +Slic3r::PrintEstimatedStatistics::ETimeMode convert(const ETimeMode& mode) +{ + switch (mode) + { + case ETimeMode::Normal: { return Slic3r::PrintEstimatedStatistics::ETimeMode::Normal; } + case ETimeMode::Stealth: { return Slic3r::PrintEstimatedStatistics::ETimeMode::Stealth; } + default: { return Slic3r::PrintEstimatedStatistics::ETimeMode::Count; } + } +} + +GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors, const Viewer& viewer) +{ + GCodeInputData ret; + + // collect tool colors + ret.tools_colors.reserve(str_tool_colors.size()); + for (const std::string& color : str_tool_colors) { + ret.tools_colors.emplace_back(convert(color)); + } + + // collect color print colors + const std::vector& str_colors = str_color_print_colors.empty() ? str_tool_colors : str_color_print_colors; + ret.color_print_colors.reserve(str_colors.size()); + for (const std::string& color : str_colors) { + ret.color_print_colors.emplace_back(convert(color)); + } + + const std::vector& moves = result.moves; + ret.vertices.reserve(2 * moves.size()); + for (size_t i = 1; i < moves.size(); ++i) { + const Slic3r::GCodeProcessorResult::MoveVertex& curr = moves[i]; + const Slic3r::GCodeProcessorResult::MoveVertex& prev = moves[i - 1]; + const EMoveType curr_type = convert(curr.type); + const EOptionType option_type = move_type_to_option(curr_type); + if (option_type == EOptionType::COUNT || option_type == EOptionType::Travels || option_type == EOptionType::Wipes) { + if (ret.vertices.empty() || prev.type != curr.type || prev.extrusion_role != curr.extrusion_role) { + // to allow libvgcode to properly detect the start/end of a path we need to add a 'phantom' vertex + // equal to the current one with the exception of the position, which should match the previous move position, + // and the times, which are set to zero +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + const libvgcode::PathVertex vertex = { convert(prev.position), curr.height, curr.width, curr.feedrate, prev.actual_feedrate, + curr.mm3_per_mm, curr.fan_speed, curr.temperature, 0.0f, convert(curr.extrusion_role), curr_type, + static_cast(curr.gcode_id), static_cast(curr.layer_id), + static_cast(curr.extruder_id), static_cast(curr.cp_color_id), { 0.0f, 0.0f } }; +#else + const libvgcode::PathVertex vertex = { convert(prev.position), curr.height, curr.width, curr.feedrate, prev.actual_feedrate, + curr.mm3_per_mm, curr.fan_speed, curr.temperature, convert(curr.extrusion_role), curr_type, + static_cast(curr.gcode_id), static_cast(curr.layer_id), + static_cast(curr.extruder_id), static_cast(curr.cp_color_id), { 0.0f, 0.0f } }; +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + ret.vertices.emplace_back(vertex); + } + } + +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + const libvgcode::PathVertex vertex = { convert(curr.position), curr.height, curr.width, curr.feedrate, curr.actual_feedrate, + curr.mm3_per_mm, curr.fan_speed, curr.temperature, + result.filament_densities[curr.extruder_id] * curr.mm3_per_mm * (curr.position - prev.position).norm(), + convert(curr.extrusion_role), curr_type, static_cast(curr.gcode_id), static_cast(curr.layer_id), + static_cast(curr.extruder_id), static_cast(curr.cp_color_id), curr.time }; +#else + const libvgcode::PathVertex vertex = { convert(curr.position), curr.height, curr.width, curr.feedrate, curr.actual_feedrate, + curr.mm3_per_mm, curr.fan_speed, curr.temperature, convert(curr.extrusion_role), curr_type, + static_cast(curr.gcode_id), static_cast(curr.layer_id), + static_cast(curr.extruder_id), static_cast(curr.cp_color_id), curr.time }; +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + ret.vertices.emplace_back(vertex); + } + ret.vertices.shrink_to_fit(); + + ret.spiral_vase_mode = result.spiral_vase_mode; + + return ret; +} + +static void convert_lines_to_vertices(const Slic3r::Lines& lines, const std::vector& widths, const std::vector& heights, + float top_z, size_t layer_id, size_t extruder_id, size_t color_id, EGCodeExtrusionRole extrusion_role, bool closed, std::vector& vertices) +{ + if (lines.empty()) + return; + + // loop once more in case of closed loops + const size_t lines_end = closed ? (lines.size() + 1) : lines.size(); + for (size_t ii = 0; ii < lines_end; ++ii) { + const size_t i = (ii == lines.size()) ? 0 : ii; + const Slic3r::Line& line = lines[i]; + // first segment of the polyline + if (ii == 0) { + // add a dummy vertex at the start, to separate the current line from the others + const Slic3r::Vec2f a = unscale(line.a).cast(); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + libvgcode::PathVertex vertex = { convert(Slic3r::Vec3f(a.x(), a.y(), top_z)), heights[i], widths[i], 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, extrusion_role, EMoveType::Noop, 0, static_cast(layer_id), + static_cast(extruder_id), static_cast(color_id), { 0.0f, 0.0f } }; +#else + libvgcode::PathVertex vertex = { convert(Slic3r::Vec3f(a.x(), a.y(), top_z)), heights[i], widths[i], 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, extrusion_role, EMoveType::Noop, 0, static_cast(layer_id), + static_cast(extruder_id), static_cast(color_id), { 0.0f, 0.0f } }; +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + vertices.emplace_back(vertex); + // add the starting vertex of the segment + vertex.type = EMoveType::Extrude; + vertices.emplace_back(vertex); + } + // add the ending vertex of the segment + const Slic3r::Vec2f b = unscale(line.b).cast(); +#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS + const libvgcode::PathVertex vertex = { convert(Slic3r::Vec3f(b.x(), b.y(), top_z)), heights[i], widths[i], 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, extrusion_role, EMoveType::Extrude, 0, static_cast(layer_id), + static_cast(extruder_id), static_cast(color_id), { 0.0f, 0.0f } }; +#else + const libvgcode::PathVertex vertex = { convert(Slic3r::Vec3f(b.x(), b.y(), top_z)), heights[i], widths[i], 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, extrusion_role, EMoveType::Extrude, 0, static_cast(layer_id), + static_cast(extruder_id), static_cast(color_id), { 0.0f, 0.0f } }; +#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS + vertices.emplace_back(vertex); + } +} + +static void convert_to_vertices(const Slic3r::ExtrusionPath& extrusion_path, float print_z, size_t layer_id, size_t extruder_id, size_t color_id, + EGCodeExtrusionRole extrusion_role, const Slic3r::Point& shift, std::vector& vertices) +{ + Slic3r::Polyline polyline = extrusion_path.polyline; + polyline.remove_duplicate_points(); + polyline.translate(shift); + const Slic3r::Lines lines = polyline.lines(); + std::vector widths(lines.size(), extrusion_path.width()); + std::vector heights(lines.size(), extrusion_path.height()); + convert_lines_to_vertices(lines, widths, heights, print_z, layer_id, extruder_id, color_id, extrusion_role, false, vertices); +} + +static void convert_to_vertices(const Slic3r::ExtrusionMultiPath& extrusion_multi_path, float print_z, size_t layer_id, size_t extruder_id, + size_t color_id, EGCodeExtrusionRole extrusion_role, const Slic3r::Point& shift, std::vector& vertices) +{ + Slic3r::Lines lines; + std::vector widths; + std::vector heights; + for (const Slic3r::ExtrusionPath& extrusion_path : extrusion_multi_path.paths) { + Slic3r::Polyline polyline = extrusion_path.polyline; + polyline.remove_duplicate_points(); + polyline.translate(shift); + const Slic3r::Lines lines_this = polyline.lines(); + append(lines, lines_this); + widths.insert(widths.end(), lines_this.size(), extrusion_path.width()); + heights.insert(heights.end(), lines_this.size(), extrusion_path.height()); + } + convert_lines_to_vertices(lines, widths, heights, print_z, layer_id, extruder_id, color_id, extrusion_role, false, vertices); +} + +static void convert_to_vertices(const Slic3r::ExtrusionLoop& extrusion_loop, float print_z, size_t layer_id, size_t extruder_id, size_t color_id, + EGCodeExtrusionRole extrusion_role, const Slic3r::Point& shift, std::vector& vertices) +{ + Slic3r::Lines lines; + std::vector widths; + std::vector heights; + for (const Slic3r::ExtrusionPath& extrusion_path : extrusion_loop.paths) { + Slic3r::Polyline polyline = extrusion_path.polyline; + polyline.remove_duplicate_points(); + polyline.translate(shift); + const Slic3r::Lines lines_this = polyline.lines(); + append(lines, lines_this); + widths.insert(widths.end(), lines_this.size(), extrusion_path.width()); + heights.insert(heights.end(), lines_this.size(), extrusion_path.height()); + } + convert_lines_to_vertices(lines, widths, heights, print_z, layer_id, extruder_id, color_id, extrusion_role, true, vertices); +} + +// forward declaration +static void convert_to_vertices(const Slic3r::ExtrusionEntityCollection& extrusion_entity_collection, float print_z, size_t layer_id, + size_t extruder_id, size_t color_id, EGCodeExtrusionRole extrusion_role, const Slic3r::Point& shift, std::vector& vertices); + +static void convert_to_vertices(const Slic3r::ExtrusionEntity& extrusion_entity, float print_z, size_t layer_id, size_t extruder_id, size_t color_id, + EGCodeExtrusionRole extrusion_role, const Slic3r::Point& shift, std::vector& vertices) +{ + auto* extrusion_path = dynamic_cast(&extrusion_entity); + if (extrusion_path != nullptr) + convert_to_vertices(*extrusion_path, print_z, layer_id, extruder_id, color_id, extrusion_role, shift, vertices); + else { + auto* extrusion_loop = dynamic_cast(&extrusion_entity); + if (extrusion_loop != nullptr) + convert_to_vertices(*extrusion_loop, print_z, layer_id, extruder_id, color_id, extrusion_role, shift, vertices); + else { + auto* extrusion_multi_path = dynamic_cast(&extrusion_entity); + if (extrusion_multi_path != nullptr) + convert_to_vertices(*extrusion_multi_path, print_z, layer_id, extruder_id, color_id, extrusion_role, shift, vertices); + else { + auto* extrusion_entity_collection = dynamic_cast(&extrusion_entity); + if (extrusion_entity_collection != nullptr) + convert_to_vertices(*extrusion_entity_collection, print_z, layer_id, extruder_id, color_id, extrusion_role, shift, vertices); + else + throw Slic3r::RuntimeError("Found unexpected extrusion_entity type"); + } + } + } +} + +static void convert_to_vertices(const Slic3r::ExtrusionEntityCollection& extrusion_entity_collection, float print_z, size_t layer_id, + size_t extruder_id, size_t color_id, EGCodeExtrusionRole extrusion_role, const Slic3r::Point& shift, std::vector& vertices) +{ + for (const Slic3r::ExtrusionEntity* extrusion_entity : extrusion_entity_collection.entities) { + if (extrusion_entity != nullptr) + convert_to_vertices(*extrusion_entity, print_z, layer_id, extruder_id, color_id, extrusion_role, shift, vertices); + } +} + +struct VerticesData +{ + std::vector vertices; + std::vector layers_zs; +}; + +static void convert_brim_skirt_to_vertices(const Slic3r::Print& print, std::vector& vertices_data) +{ + vertices_data.emplace_back(VerticesData()); + VerticesData& data = vertices_data.back(); + + // number of skirt layers + size_t total_layer_count = 0; + for (const Slic3r::PrintObject* print_object : print.objects()) { + total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); + } + size_t skirt_height = print.has_infinite_skirt() ? total_layer_count : std::min(print.config().skirt_height.value, total_layer_count); + if (skirt_height == 0 && print.has_brim()) + skirt_height = 1; + + // Get first skirt_height layers. + //FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature. + // This is not critical as this is just an initial preview. + const Slic3r::PrintObject* highest_object = *std::max_element(print.objects().begin(), print.objects().end(), + [](auto l, auto r) { return l->layers().size() < r->layers().size(); }); + data.layers_zs.reserve(skirt_height * 2); + for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++i) { + data.layers_zs.emplace_back(float(highest_object->layers()[i]->print_z)); + } + // Only add skirt for the raft layers. + for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++i) { + data.layers_zs.emplace_back(float(highest_object->support_layers()[i]->print_z)); + } + Slic3r::sort_remove_duplicates(data.layers_zs); + skirt_height = std::min(skirt_height, data.layers_zs.size()); + data.layers_zs.erase(data.layers_zs.begin() + skirt_height, data.layers_zs.end()); + + for (size_t i = 0; i < skirt_height; ++i) { + if (i == 0) + convert_to_vertices(print.brim(), data.layers_zs[i], i, 0, 0, EGCodeExtrusionRole::Skirt, Slic3r::Point(0, 0), data.vertices); + convert_to_vertices(print.skirt(), data.layers_zs[i], i, 0, 0, EGCodeExtrusionRole::Skirt, Slic3r::Point(0, 0), data.vertices); + } +} + +class WipeTowerHelper +{ +public: + WipeTowerHelper(const Slic3r::Print& print) : m_print(print) { + const Slic3r::PrintConfig& config = m_print.config(); + const Slic3r::WipeTowerData& wipe_tower_data = m_print.wipe_tower_data(); + if (wipe_tower_data.priming && config.single_extruder_multi_material_priming) { + for (size_t i = 0; i < wipe_tower_data.priming.get()->size(); ++i) { + m_priming.emplace_back(wipe_tower_data.priming.get()->at(i)); + } + } + if (wipe_tower_data.final_purge) + m_final.emplace_back(*wipe_tower_data.final_purge.get()); + + m_angle = config.wipe_tower_rotation_angle.value / 180.0f * PI; + m_position = Slic3r::Vec2f(config.wipe_tower_x.value, config.wipe_tower_y.value); + m_layers_count = wipe_tower_data.tool_changes.size() + (m_priming.empty() ? 0 : 1); + } + + const std::vector& tool_change(size_t idx) { + const auto& tool_changes = m_print.wipe_tower_data().tool_changes; + return m_priming.empty() ? + ((idx == tool_changes.size()) ? m_final : tool_changes[idx]) : + ((idx == 0) ? m_priming : (idx == tool_changes.size() + 1) ? m_final : tool_changes[idx - 1]); + } + + float get_angle() const { return m_angle; } + const Slic3r::Vec2f& get_position() const { return m_position; } + size_t get_layers_count() { return m_layers_count; } + +private: + const Slic3r::Print& m_print; + std::vector m_priming; + std::vector m_final; + Slic3r::Vec2f m_position{ Slic3r::Vec2f::Zero() }; + float m_angle{ 0.0f }; + size_t m_layers_count{ 0 }; +}; + +static void convert_wipe_tower_to_vertices(const Slic3r::Print& print, const std::vector& str_tool_colors, + std::vector& vertices_data) +{ + vertices_data.emplace_back(VerticesData()); + VerticesData& data = vertices_data.back(); + + WipeTowerHelper wipe_tower_helper(print); + const float angle = wipe_tower_helper.get_angle(); + const Slic3r::Vec2f& position = wipe_tower_helper.get_position(); + + for (size_t item = 0; item < wipe_tower_helper.get_layers_count(); ++item) { + const std::vector& layer = wipe_tower_helper.tool_change(item); + for (const Slic3r::WipeTower::ToolChangeResult& extrusions : layer) { + data.layers_zs.emplace_back(extrusions.print_z); + for (size_t i = 1; i < extrusions.extrusions.size(); /*no increment*/) { + const Slic3r::WipeTower::Extrusion& e = extrusions.extrusions[i]; + if (e.width == 0.0f) { + ++i; + continue; + } + size_t j = i + 1; + if (str_tool_colors.empty()) + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.0f; ++j); + else + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.0f; ++j); + + const size_t n_lines = j - i; + Slic3r::Lines lines; + std::vector widths; + std::vector heights; + lines.reserve(n_lines); + widths.reserve(n_lines); + heights.assign(n_lines, extrusions.layer_height); + Slic3r::WipeTower::Extrusion e_prev = extrusions.extrusions[i - 1]; + + if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation + e_prev.pos = Eigen::Rotation2Df(angle) * e_prev.pos; + e_prev.pos += position; + } + + for (; i < j; ++i) { + Slic3r::WipeTower::Extrusion ee = extrusions.extrusions[i]; + assert(ee.width > 0.0f); + if (!extrusions.priming) { + ee.pos = Eigen::Rotation2Df(angle) * ee.pos; + ee.pos += position; + } + lines.emplace_back(Slic3r::Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Slic3r::Point::new_scale(ee.pos.x(), ee.pos.y())); + widths.emplace_back(ee.width); + e_prev = ee; + } + + convert_lines_to_vertices(lines, widths, heights, extrusions.print_z, item, static_cast(e.tool), 0, + EGCodeExtrusionRole::WipeTower, lines.front().a == lines.back().b, data.vertices); + } + } + } + + Slic3r::sort_remove_duplicates(data.layers_zs); +} + +class ObjectHelper +{ +public: + ObjectHelper(const std::vector& color_print_values, size_t tool_colors_count, size_t color_print_colors_count, size_t extruders_count) + : m_color_print_values(color_print_values) + , m_tool_colors_count(tool_colors_count) + , m_color_print_colors_count(color_print_colors_count) + , m_extruders_count(extruders_count) { + } + + uint8_t color_id(float print_z, size_t extruder_id) const { + if (!m_color_print_values.empty()) + return color_print_color_id(double(print_z), extruder_id); + else { + if (m_tool_colors_count > 0) + return std::min(m_tool_colors_count - 1, static_cast(extruder_id)); + else + return 0; + } + } + +private: + const std::vector& m_color_print_values; + size_t m_tool_colors_count{ 0 }; + size_t m_color_print_colors_count{ 0 }; + size_t m_extruders_count{ 0 }; + + uint8_t color_print_color_id(double print_z, size_t extruder_id) const { + auto it = std::find_if(m_color_print_values.begin(), m_color_print_values.end(), + [print_z](const Slic3r::CustomGCode::Item& code) { + return std::fabs(code.print_z - print_z) < EPSILON; + }); + if (it != m_color_print_values.end()) { + Slic3r::CustomGCode::Type type = it->type; + // pause print or custom Gcode + if (type == Slic3r::CustomGCode::PausePrint || (type != Slic3r::CustomGCode::ColorChange && type != Slic3r::CustomGCode::Template)) + return static_cast(m_color_print_colors_count - 1); // last color item is a gray color for pause print or custom G-code + switch (it->type) { + // change color for current extruder + case Slic3r::CustomGCode::ColorChange: { + const int c = color_change_color_id(it, extruder_id); + if (c >= 0) + return static_cast(c); + break; + } + // change tool (extruder) + case Slic3r::CustomGCode::ToolChange: { return tool_change_color_id(it, extruder_id); } + default: { break; } + } + } + + const Slic3r::CustomGCode::Item value{ print_z + EPSILON, Slic3r::CustomGCode::Custom, 0, "" }; + it = std::lower_bound(m_color_print_values.begin(), m_color_print_values.end(), value); + while (it != m_color_print_values.begin()) { + --it; + switch (it->type) { + // change color for current extruder + case Slic3r::CustomGCode::ColorChange: { + const int c = color_change_color_id(it, extruder_id); + if (c >= 0) + return static_cast(c); + break; + } + // change tool (extruder) + case Slic3r::CustomGCode::ToolChange: { return tool_change_color_id(it, extruder_id); } + default: { break; } + } + } + + return std::min(m_extruders_count - 1, static_cast(extruder_id)); + } + + int color_change_color_id(std::vector::const_iterator it, size_t extruder_id) const { + if (m_extruders_count == 1) + return m600_color_id(it); + + auto it_n = it; + bool is_tool_change = false; + while (it_n != m_color_print_values.begin()) { + --it_n; + if (it_n->type == Slic3r::CustomGCode::ToolChange) { + is_tool_change = true; + if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == static_cast(extruder_id + 1))) + return m600_color_id(it); + break; + } + } + if (!is_tool_change && it->extruder == static_cast(extruder_id + 1)) + return m600_color_id(it); + + return -1; + } + + uint8_t tool_change_color_id(std::vector::const_iterator it, size_t extruder_id) const { + const int current_extruder = it->extruder == 0 ? static_cast(extruder_id + 1) : it->extruder; + if (m_tool_colors_count == m_extruders_count + 1) // there is no one "M600" + return std::min(m_extruders_count - 1, std::max(current_extruder - 1, 0)); + + auto it_n = it; + while (it_n != m_color_print_values.begin()) { + --it_n; + if (it_n->type == Slic3r::CustomGCode::ColorChange && it_n->extruder == current_extruder) + return m600_color_id(it_n); + } + + return std::min(m_extruders_count - 1, std::max(current_extruder - 1, 0)); + } + + int m600_color_id(std::vector::const_iterator it) const { + int shift = 0; + while (it != m_color_print_values.begin()) { + --it; + if (it->type == Slic3r::CustomGCode::ColorChange) + ++shift; + } + return static_cast(m_extruders_count) + shift; + } +}; + +static void convert_object_to_vertices(const Slic3r::PrintObject& object, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors, const std::vector& color_print_values, + size_t extruders_count, VerticesData& data) +{ + const bool has_perimeters = object.is_step_done(Slic3r::posPerimeters); + const bool has_infill = object.is_step_done(Slic3r::posInfill); + const bool has_support = object.is_step_done(Slic3r::posSupportMaterial); + + // order layers by print_z + std::vector layers; + if (has_perimeters || has_infill) { + layers.reserve(layers.size() + object.layers().size()); + std::copy(object.layers().begin(), object.layers().end(), std::back_inserter(layers)); + } + if (has_support) { + layers.reserve(layers.size() + object.support_layers().size()); + std::copy(object.support_layers().begin(), object.support_layers().end(), std::back_inserter(layers)); + } + std::sort(layers.begin(), layers.end(), [](const Slic3r::Layer* l1, const Slic3r::Layer* l2) { return l1->print_z < l2->print_z; }); + + ObjectHelper object_helper(color_print_values, str_tool_colors.size(), str_color_print_colors.size(), extruders_count); + + data.layers_zs.reserve(layers.size()); + for (const Slic3r::Layer* layer : layers) { + data.layers_zs.emplace_back(static_cast(layer->print_z)); + } + + Slic3r::sort_remove_duplicates(data.layers_zs); + + for (const Slic3r::Layer* layer : layers) { + const size_t old_vertices_count = data.vertices.size(); + const float layer_z = static_cast(layer->print_z); + const auto it = std::find(data.layers_zs.begin(), data.layers_zs.end(), layer_z); + assert(it != data.layers_zs.end()); + const size_t layer_id = (it != data.layers_zs.end()) ? std::distance(data.layers_zs.begin(), it) : 0; + for (const Slic3r::PrintInstance& instance : object.instances()) { + const Slic3r::Point& copy = instance.shift; + for (const Slic3r::LayerRegion* layerm : layer->regions()) { + if (layerm->slices().empty()) + continue; + const Slic3r::PrintRegionConfig& cfg = layerm->region().config(); + if (has_perimeters) { + const size_t extruder_id = static_cast(std::max(cfg.perimeter_extruder.value - 1, 0)); + convert_to_vertices(layerm->perimeters(), layer_z, layer_id, extruder_id, + object_helper.color_id(layer_z, extruder_id), EGCodeExtrusionRole::ExternalPerimeter, + copy, data.vertices); + } + if (has_infill) { + for (const Slic3r::ExtrusionEntity* ee : layerm->fills()) { + // fill represents infill extrusions of a single island. + const auto& fill = *dynamic_cast(ee); + if (!fill.entities.empty()) { + const bool is_solid_infill = fill.entities.front()->role().is_solid_infill(); + const size_t extruder_id = is_solid_infill ? + static_cast(std::max(cfg.solid_infill_extruder.value - 1, 0)) : + static_cast(std::max(cfg.infill_extruder.value - 1, 0)); + convert_to_vertices(fill, layer_z, layer_id, extruder_id, + object_helper.color_id(layer_z, extruder_id), + is_solid_infill ? EGCodeExtrusionRole::SolidInfill : EGCodeExtrusionRole::InternalInfill, + copy, data.vertices); + } + } + } + } + if (has_support) { + const Slic3r::SupportLayer* support_layer = dynamic_cast(layer); + if (support_layer == nullptr) + continue; + const Slic3r::PrintObjectConfig& cfg = support_layer->object()->config(); + for (const Slic3r::ExtrusionEntity* extrusion_entity : support_layer->support_fills.entities) { + const bool is_support_material = extrusion_entity->role() == Slic3r::ExtrusionRole::SupportMaterial; + const size_t extruder_id = is_support_material ? + static_cast(std::max(cfg.support_material_extruder.value - 1, 0)) : + static_cast(std::max(cfg.support_material_interface_extruder.value - 1, 0)); + convert_to_vertices(*extrusion_entity, layer_z, layer_id, + extruder_id, object_helper.color_id(layer_z, extruder_id), + is_support_material ? EGCodeExtrusionRole::SupportMaterial : EGCodeExtrusionRole::SupportMaterialInterface, + copy, data.vertices); + } + } + } + // filter out empty layers + const size_t new_vertices_count = data.vertices.size(); + if (new_vertices_count == old_vertices_count) + data.layers_zs.erase(data.layers_zs.begin() + layer_id); + } +} + +static void convert_objects_to_vertices(const Slic3r::SpanOfConstPtrs& objects, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors, const std::vector& color_print_values, size_t extruders_count, + std::vector& data) +{ + // extract vertices and layers zs object by object + data.reserve(data.size() + objects.size()); + for (size_t i = 0; i < objects.size(); ++i) { + data.emplace_back(VerticesData()); + convert_object_to_vertices(*objects[i], str_tool_colors, str_color_print_colors, color_print_values, extruders_count, data.back()); + } +} + +// mapping from Slic3r::Print to libvgcode::GCodeInputData +GCodeInputData convert(const Slic3r::Print& print, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors, const std::vector& color_print_values, + size_t extruders_count) +{ + GCodeInputData ret; + std::vector data; + if (print.is_step_done(Slic3r::psSkirtBrim) && (print.has_skirt() || print.has_brim())) + // extract vertices and layers zs from skirt/brim + convert_brim_skirt_to_vertices(print, data); + if (!print.wipe_tower_data().tool_changes.empty() && print.is_step_done(Slic3r::psWipeTower)) + // extract vertices and layers zs from wipe tower + convert_wipe_tower_to_vertices(print, str_tool_colors, data); + // extract vertices and layers zs from objects + convert_objects_to_vertices(print.objects(), str_tool_colors, str_color_print_colors, color_print_values, extruders_count, data); + + // collect layers zs + std::vector layers; + for (const VerticesData& d : data) { + layers.reserve(layers.size() + d.layers_zs.size()); + std::copy(d.layers_zs.begin(), d.layers_zs.end(), std::back_inserter(layers)); + } + Slic3r::sort_remove_duplicates(layers); + + // Now we need to copy the vertices into ret.vertices to be consumed by the preliminary G-code preview. + // We need to collect vertices in the first layer for all objects, push them into the output vector + // and then do the same for all the layers. The algorithm relies on the fact that the vertices from + // lower layers are always placed after vertices from the higher layer. + std::vector vert_indices(data.size(), 0); + for (size_t layer_id = 0; layer_id < layers.size(); ++layer_id) { + const float layer_z = layers[layer_id]; + for (size_t obj_idx = 0; obj_idx < data.size(); ++obj_idx) { + // d contains PathVertices for one object. Let's stuff everything below this layer_z into ret.vertices. + const size_t start_idx = vert_indices[obj_idx]; + size_t idx = start_idx; + while (idx < data[obj_idx].vertices.size() && data[obj_idx].vertices[idx].position[2] <= layer_z) + ++idx; + // We have found a vertex above current layer_z. Let's copy the vertices into the output + // and remember where to start when we process another layer. + ret.vertices.insert(ret.vertices.end(), + data[obj_idx].vertices.begin() + start_idx, + data[obj_idx].vertices.begin() + idx); + vert_indices[obj_idx] = idx; + } + } + + + // collect tool colors + ret.tools_colors.reserve(str_tool_colors.size()); + for (const std::string& color : str_tool_colors) { + ret.tools_colors.emplace_back(convert(color)); + } + + // collect color print colors + const std::vector& str_colors = str_color_print_colors.empty() ? str_tool_colors : str_color_print_colors; + ret.color_print_colors.reserve(str_colors.size()); + for (const std::string& color : str_colors) { + ret.color_print_colors.emplace_back(convert(color)); + } + + return ret; +} + +} // namespace libvgcode + diff --git a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.hpp b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.hpp new file mode 100644 index 0000000..be93b10 --- /dev/null +++ b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.hpp @@ -0,0 +1,80 @@ + +#ifndef slic3r_LibVGCodeWrapper_hpp_ +#define slic3r_LibVGCodeWrapper_hpp_ + +#include <../../src/libvgcode/include/Viewer.hpp> +#include <../../src/libvgcode/include/PathVertex.hpp> +#include <../../src/libvgcode/include/GCodeInputData.hpp> +#include <../../src/libvgcode/include/ColorRange.hpp> +#include +#include +#include +#include + +#include "../../src/libvgcode/include/Types.hpp" +#include "libslic3r/Color.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "slic3r/GUI/GUI_Preview.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Point.hpp" + + +namespace Slic3r { +class Print; + +namespace CustomGCode { +struct Item; +} // namespace CustomGCode +} // namespace Slic3r + +namespace libvgcode { +class Viewer; + +// mapping from Slic3r::Vec3f to libvgcode::Vec3 +extern Vec3 convert(const Slic3r::Vec3f& v); + +// mapping from libvgcode::Vec3 to Slic3r::Vec3f +extern Slic3r::Vec3f convert(const Vec3& v); + +// mapping from Slic3r::Matrix4f to libvgcode::Mat4x4 +extern Mat4x4 convert(const Slic3r::Matrix4f& m); + +// mapping from libvgcode::Color to Slic3r::ColorRGBA +extern Slic3r::ColorRGBA convert(const Color& c); + +// mapping from Slic3r::ColorRGBA to libvgcode::Color +extern Color convert(const Slic3r::ColorRGBA& c); + +// mapping from encoded color to libvgcode::Color +extern Color convert(const std::string& color_str); + +// mapping from libvgcode::EGCodeExtrusionRole to Slic3r::GCodeExtrusionRole +extern Slic3r::GCodeExtrusionRole convert(EGCodeExtrusionRole role); + +// mapping from Slic3r::GCodeExtrusionRole to libvgcode::EGCodeExtrusionRole +extern EGCodeExtrusionRole convert(Slic3r::GCodeExtrusionRole role); + +// mapping from Slic3r::EMoveType to libvgcode::EMoveType +extern EMoveType convert(Slic3r::EMoveType type); + +// mapping from Slic3r::GUI::Preview::OptionType to libvgcode::EOptionType +extern EOptionType convert(const Slic3r::GUI::Preview::OptionType& type); + +// mapping from Slic3r::PrintEstimatedStatistics::ETimeMode to libvgcode::ETimeMode +extern ETimeMode convert(const Slic3r::PrintEstimatedStatistics::ETimeMode& mode); + +// mapping from libvgcode::ETimeMode to Slic3r::PrintEstimatedStatistics::ETimeMode +extern Slic3r::PrintEstimatedStatistics::ETimeMode convert(const ETimeMode& mode); + +// mapping from Slic3r::GCodeProcessorResult to libvgcode::GCodeInputData +extern GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors, const Viewer& viewer); + +// mapping from Slic3r::Print to libvgcode::GCodeInputData +extern GCodeInputData convert(const Slic3r::Print& print, const std::vector& str_tool_colors, + const std::vector& str_color_print_colors, const std::vector& color_print_values, + size_t extruders_count); + +} // namespace libvgcode + +#endif // slic3r_LibVGCodeWrapper_hpp_ diff --git a/src/slic3r/GUI/LoginDialog.cpp b/src/slic3r/GUI/LoginDialog.cpp new file mode 100644 index 0000000..4a27cd4 --- /dev/null +++ b/src/slic3r/GUI/LoginDialog.cpp @@ -0,0 +1,95 @@ +#include "LoginDialog.hpp" + +#include "GUI_App.hpp" +#include "wxExtensions.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "format.hpp" + +namespace Slic3r { +namespace GUI { + +LoginDialog::LoginDialog(wxWindow* parent, UserAccount* user_account) + // TRN: This is the dialog title. + : DPIDialog(parent, wxID_ANY, ("QIDI Account"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + , p_user_account(user_account) +{ + const int em = wxGetApp().em_unit(); + bool logged = p_user_account->is_logged(); + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + // sizer with black border + wxStaticBoxSizer* static_box_sizer = new wxStaticBoxSizer(wxVERTICAL, this, ("Log into your QIDI Account")); + static_box_sizer->SetMinSize(wxSize(em * 30, em * 15)); + // avatar + boost::filesystem::path path = p_user_account->get_avatar_path(logged); + ScalableBitmap logo(this, path, wxSize(em * 10, em * 10)); + m_avatar_bitmap = new wxStaticBitmap(this, wxID_ANY, logo.bmp(), wxDefaultPosition, wxDefaultSize); + static_box_sizer->Add(m_avatar_bitmap, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); + // username + const wxString username = GUI::format_wxstr("%1%", logged ? from_u8(p_user_account->get_username()) : ("Anonymous")); + m_username_label = new wxStaticText(this, wxID_ANY, username, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); + m_username_label->SetFont(m_username_label->GetFont().Bold()); + static_box_sizer->Add(m_username_label, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5); + // login button + m_login_button_id = NewControlId(); + m_login_button = new wxButton(this, m_login_button_id, logged ? ("Log out") : ("Log in")); + static_box_sizer->Add(m_login_button, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); + // TODO: why is m_login_button always hovered? + main_sizer->Add(static_box_sizer, 1, wxEXPAND | wxALL, 10); + // continue button + m_continue_button = new wxButton(this, wxID_OK, logged ? ("Continue") : ("Continue without QIDI Account")); + main_sizer->Add(m_continue_button, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); + + SetSizerAndFit(main_sizer); + + m_login_button->Bind(wxEVT_BUTTON, [user_account = p_user_account](wxCommandEvent& event) { + if (!user_account->is_logged()) + user_account->do_login(); + else + user_account->do_logout(); + }); + + wxGetApp().UpdateDlgDarkUI(this); + SetFocus(); +} + +LoginDialog::~LoginDialog() +{ +} + +void LoginDialog::update_account() +{ + bool logged = p_user_account->is_logged(); + + const wxString username = GUI::format_wxstr("%1%", logged ? from_u8(p_user_account->get_username()) : ("Anonymous")); + m_username_label->SetLabel(username); + + boost::filesystem::path path = p_user_account->get_avatar_path(logged); + if (boost::filesystem::exists(path)) { + const int em = wxGetApp().em_unit(); + ScalableBitmap logo(this, path, wxSize(em * 10, em * 10)); + m_avatar_bitmap->SetBitmap(logo.bmp()); + } + + m_login_button->SetLabel(logged ? ("Log out") : ("Log in")); + m_continue_button->SetLabel(logged ? ("Continue") : ("Continue without QIDI Account")); + // TODO: resize correctly m_continue_button + //m_continue_button->Fit(); + + Fit(); + Refresh(); +} + +void LoginDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + + SetFont(wxGetApp().normal_font()); + + const int em = em_unit(); + msw_buttons_rescale(this, em, { wxID_OK, m_login_button_id}); + + Fit(); + Refresh(); + +} +}}// Slicer::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/LoginDialog.hpp b/src/slic3r/GUI/LoginDialog.hpp new file mode 100644 index 0000000..debd0c4 --- /dev/null +++ b/src/slic3r/GUI/LoginDialog.hpp @@ -0,0 +1,38 @@ +#ifndef slic3r_LoginDialog_hpp_ +#define slic3r_LoginDialog_hpp_ + +#include "UserAccount.hpp" + +#include "GUI_Utils.hpp" + +#include +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +class RemovableDriveManager; +class LoginDialog : public DPIDialog +{ +public: + LoginDialog(wxWindow* parent, UserAccount* user_account); + ~LoginDialog(); + + void update_account(); +private: + UserAccount* p_user_account; + + wxStaticText* m_username_label; + wxStaticBitmap* m_avatar_bitmap; + wxButton* m_login_button; + int m_login_button_id{ wxID_ANY }; + wxButton* m_continue_button; +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override {} +}; + +}} // Slicer::GUI +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ba1e636..37d9331 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -9,9 +9,13 @@ #include #include #include +#include //#include #include #include +#if wxUSE_SECRETSTORE +#include +#endif #include #include @@ -23,7 +27,6 @@ #include "libslic3r/PresetBundle.hpp" #include "Tab.hpp" -#include "ProgressStatusBar.hpp" #include "3DScene.hpp" #include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" @@ -36,6 +39,7 @@ #include "Plater.hpp" #include "../Utils/Process.hpp" #include "format.hpp" +#include "slic3r/GUI/InstanceCheck.hpp" #include #include @@ -43,12 +47,14 @@ #include "GUI_App.hpp" #include "UnsavedChangesDialog.hpp" #include "MsgDialog.hpp" -#include "Notebook.hpp" +#include "TopBar.hpp" #include "GUI_Factories.hpp" #include "GUI_ObjectList.hpp" #include "GalleryDialog.hpp" #include "NotificationManager.hpp" #include "Preferences.hpp" +#include "WebViewDialog.hpp" +#include "UserAccount.hpp" #ifdef _WIN32 #include @@ -171,14 +177,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // Load the icon either from the exe, or from the ico file. SetIcon(main_frame_icon(wxGetApp().get_app_mode())); - // initialize status bar -// m_statusbar = std::make_shared(this); -// m_statusbar->set_font(GUI::wxGetApp().normal_font()); -// if (wxGetApp().is_editor()) -// m_statusbar->embed(this); -// m_statusbar->set_status_text(_L("Version") + " " + -// SLIC3R_VERSION + " - " + -// _L("Remember to check for updates at https://github.com/qidi3d/QIDISlicer/releases")); + wxGetApp().set_searcher(&m_searcher); // initialize tabpanel and menubar init_tabpanel(); @@ -187,19 +186,34 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S else init_menubar_as_editor(); +#ifndef __APPLE__ + std::vector& entries_cache = accelerator_entries_cache(); + assert(entries_cache.size() + 6 < 100); + wxAcceleratorEntry entries[100]; + + int id = 0; + //for (const auto* entry : entries_cache) + // entries[id++].Set(entry->GetFlags(), entry->GetKeyCode(), entry->GetMenuItem()->GetId()); + #if _WIN32 // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad - wxAcceleratorEntry entries[6]; - entries[0].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1); - entries[1].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2); - entries[2].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3); - entries[3].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4); - entries[4].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5); - entries[5].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6); - wxAcceleratorTable accel(6, entries); - SetAcceleratorTable(accel); + entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1); + entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2); + entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3); + entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4); + entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5); + entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6); #endif // _WIN32 + wxAcceleratorTable accel(id, entries); + SetAcceleratorTable(accel); + + // clear cache with wxAcceleratorEntry, because it's no need anymore + for (auto entry : entries_cache) + delete entry; + entries_cache.clear(); +#endif + // set default tooltip timer in msec // SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values // (SetAutoPop is not available on GTK.) @@ -266,22 +280,37 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S //FIXME it seems this method is not called on application start-up, at least not on Windows. Why? // The same applies to wxEVT_CREATE, it is not being called on startup on Windows. Bind(wxEVT_ACTIVATE, [this](wxActivateEvent& event) { - if (m_plater != nullptr && event.GetActive()) - m_plater->on_activate(); + if (m_plater != nullptr) + m_plater->on_activate(event.GetActive()); event.Skip(); }); + Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { + event.Skip(); +#ifdef _WIN32 + // Update window property to mainframe so other instances can indentify it. + wxGetApp().other_instance_message_handler()->update_windows_properties(this); +#endif //WIN32 + if (m_layout == ESettingsLayout::Dlg || m_layout == ESettingsLayout::Old) { + if (m_layout == ESettingsLayout::Old) + m_tabpanel->UpdateSearchSizeAndPosition(); + else + m_tmp_top_bar->UpdateSearchSizeAndPosition(); + } + }); + + Bind(wxEVT_MOVE, [](wxMoveEvent& event) { // OSX specific issue: // When we move application between Retina and non-Retina displays, The legend on a canvas doesn't redraw // So, redraw explicitly canvas, when application is moved //FIXME maybe this is useful for __WXGTK3__ as well? #if __APPLE__ - Bind(wxEVT_MOVE, [](wxMoveEvent& event) { wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); +#endif + wxGetApp().searcher().update_dialog_position(); event.Skip(); }); -#endif wxGetApp().persist_window_geometry(this, true); wxGetApp().persist_window_geometry(&m_settings_dialog, true); @@ -289,137 +318,24 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S update_ui_from_settings(); // FIXME (?) if (m_plater != nullptr) { +#if ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC + // When the application is run as GCodeViewer the collapse toolbar is set as enabled, but rendered outside of the screen + m_plater->get_collapse_toolbar().set_enabled(wxGetApp().is_gcode_viewer() ? + true : wxGetApp().app_config->get_bool("show_collapse_button")); +#else m_plater->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get_bool("show_collapse_button")); +#endif // ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC m_plater->show_action_buttons(true); preferences_dialog = new PreferencesDialog(this); } - // bind events from DiffDlg - - bind_diff_dialog(); -} - -void MainFrame::bind_diff_dialog() -{ - auto get_tab = [](Preset::Type type) { - Tab* null_tab = nullptr; - for (Tab* tab : wxGetApp().tabs_list) - if (tab->type() == type) - return tab; - return null_tab; - }; - - auto transfer = [this, get_tab](Preset::Type type) { - get_tab(type)->transfer_options(diff_dialog.get_left_preset_name(type), - diff_dialog.get_right_preset_name(type), - diff_dialog.get_selected_options(type)); - }; - - auto update_presets = [this, get_tab](Preset::Type type) { - get_tab(type)->update_preset_choice(); - m_plater->sidebar().update_presets(type); - }; - - auto process_options = [this](std::function process) { - const Preset::Type diff_dlg_type = diff_dialog.view_type(); - if (diff_dlg_type == Preset::TYPE_INVALID) { - for (const Preset::Type& type : diff_dialog.types_list() ) - process(type); - } - else - process(diff_dlg_type); - }; - - diff_dialog.Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); }); - - diff_dialog.Bind(EVT_DIFF_DIALOG_UPDATE_PRESETS,[process_options, update_presets](SimpleEvent&) { process_options(update_presets); }); -} - - -#ifdef _MSW_DARK_MODE -static wxString pref() { return " [ "; } -static wxString suff() { return " ] "; } -static void append_tab_menu_items_to_menubar(wxMenuBar* bar, PrinterTechnology pt, bool is_mainframe_menu) -{ - if (is_mainframe_menu) - bar->Append(new wxMenu(), pref() + _L("Plater") + suff()); - for (const wxString& title : { is_mainframe_menu ? _L("Print Settings") : pref() + _L("Print Settings") + suff(), - pt == ptSLA ? _L("Material Settings") : _L("Filament Settings"), - _L("Printer Settings") }) - bar->Append(new wxMenu(), title); -} - -// update markers for selected/unselected menu items -static void update_marker_for_tabs_menu(wxMenuBar* bar, const wxString& title, bool is_mainframe_menu) -{ - if (!bar) - return; - size_t items_cnt = bar->GetMenuCount(); - for (size_t id = items_cnt - (is_mainframe_menu ? 4 : 3); id < items_cnt; id++) { - wxString label = bar->GetMenuLabel(id); - if (label.First(pref()) == 0) { - if (label == pref() + title + suff()) - return; - label.Remove(size_t(0), pref().Len()); - label.RemoveLast(suff().Len()); - bar->SetMenuLabel(id, label); - break; - } + if (wxGetApp().is_editor()) { + // jump to found option from SearchDialog + Bind(wxCUSTOMEVT_JUMP_TO_OPTION, [](wxCommandEvent& evt) { wxGetApp().jump_to_option(evt.GetInt()); }); } - if (int id = bar->FindMenu(title); id != wxNOT_FOUND) - bar->SetMenuLabel(id, pref() + title + suff()); } -static void add_tabs_as_menu(wxMenuBar* bar, MainFrame* main_frame, wxWindow* bar_parent) -{ - PrinterTechnology pt = main_frame->plater() ? main_frame->plater()->printer_technology() : ptFFF; - - bool is_mainframe_menu = bar_parent == main_frame; - if (!is_mainframe_menu) - append_tab_menu_items_to_menubar(bar, pt, is_mainframe_menu); - - bar_parent->Bind(wxEVT_MENU_OPEN, [main_frame, bar, is_mainframe_menu](wxMenuEvent& event) { - wxMenu* const menu = event.GetMenu(); - if (!menu || menu->GetMenuItemCount() > 0) { - // If we are here it means that we open regular menu and not a tab used as a menu - event.Skip(); // event.Skip() is verry important to next processing of the wxEVT_UPDATE_UI by this menu items. - // If wxEVT_MENU_OPEN will not be pocessed in next event queue then MenuItems of this menu will never caught wxEVT_UPDATE_UI - // and, as a result, "check/radio value" will not be updated - return; - } - - // update tab selection - - const wxString& title = menu->GetTitle(); - if (title == _L("Plater")) - main_frame->select_tab(size_t(0)); - else if (title == _L("Print Settings")) - main_frame->select_tab(wxGetApp().get_tab(main_frame->plater()->printer_technology() == ptFFF ? Preset::TYPE_PRINT : Preset::TYPE_SLA_PRINT)); - else if (title == _L("Filament Settings")) - main_frame->select_tab(wxGetApp().get_tab(Preset::TYPE_FILAMENT)); - else if (title == _L("Material Settings")) - main_frame->select_tab(wxGetApp().get_tab(Preset::TYPE_SLA_MATERIAL)); - else if (title == _L("Printer Settings")) - main_frame->select_tab(wxGetApp().get_tab(Preset::TYPE_PRINTER)); - - // update markers for selected/unselected menu items - update_marker_for_tabs_menu(bar, title, is_mainframe_menu); - }); -} - -void MainFrame::show_tabs_menu(bool show) -{ - if (show) - append_tab_menu_items_to_menubar(m_menubar, plater() ? plater()->printer_technology() : ptFFF, true); - else - while (m_menubar->GetMenuCount() >= 8) { - if (wxMenu* menu = m_menubar->Remove(7)) - delete menu; - } -} -#endif // _MSW_DARK_MODE - void MainFrame::update_layout() { auto restore_to_creation = [this]() { @@ -453,6 +369,7 @@ void MainFrame::update_layout() m_settings_dialog.Close(); m_tabpanel->Hide(); + m_tmp_top_bar->Hide(); m_plater->Hide(); Layout(); @@ -460,7 +377,7 @@ void MainFrame::update_layout() ESettingsLayout layout = wxGetApp().is_gcode_viewer() ? ESettingsLayout::GCodeViewer : (wxGetApp().app_config->get_bool("old_settings_layout_mode") ? ESettingsLayout::Old : - wxGetApp().app_config->get_bool("new_settings_layout_mode") ? ( wxGetApp().tabs_as_menu() ? ESettingsLayout::Old : ESettingsLayout::New) : + // wxGetApp().app_config->get_bool("new_settings_layout_mode") ? ( wxGetApp().tabs_as_menu() ? ESettingsLayout::Old : ESettingsLayout::New) : wxGetApp().app_config->get_bool("dlg_settings_layout_mode") ? ESettingsLayout::Dlg : ESettingsLayout::Old); if (m_layout == layout) @@ -485,7 +402,6 @@ void MainFrame::update_layout() layout == ESettingsLayout::Dlg ? State::toDlg : State::noUpdate; #endif //__WXMSW__ - ESettingsLayout old_layout = m_layout; m_layout = layout; // From the very beginning the Print settings should be selected @@ -501,71 +417,47 @@ void MainFrame::update_layout() case ESettingsLayout::Old: { m_plater->Reparent(m_tabpanel); -#ifdef _MSW_DARK_MODE m_plater->Layout(); - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"), true); - else -#endif - m_tabpanel->InsertPage(0, m_plater, _L("Plater")); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 1); m_plater->Show(); - m_tabpanel->Show(); - // update Tabs - if (old_layout == ESettingsLayout::Dlg) - if (int sel = m_tabpanel->GetSelection(); sel != wxNOT_FOUND) - m_tabpanel->SetSelection(sel+1);// call SetSelection to correct layout after switching from Dlg to Old mode -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) - show_tabs_menu(true); -#endif - break; - } - case ESettingsLayout::New: - { - m_main_sizer->Add(m_plater, 1, wxEXPAND); - m_tabpanel->Hide(); - m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); - m_plater_page = new wxPanel(m_tabpanel); -#ifdef _MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"), true); - else -#endif - m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ - m_plater->Show(); + m_tabpanel->ShowFull(); + m_tmp_top_bar->Hide(); break; } case ESettingsLayout::Dlg: { - m_main_sizer->Add(m_plater, 1, wxEXPAND); - m_tabpanel->Reparent(&m_settings_dialog); - m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 2); - m_tabpanel->Show(); + const int sel = m_tabpanel->GetSelection(); + + m_plater->Reparent(this); + m_main_sizer->Add(m_tmp_top_bar, 0, wxEXPAND | wxTOP, 1); + m_main_sizer->Add(m_plater, 1, wxEXPAND | wxTOP, 1); + m_plater->Layout(); + m_tmp_top_bar->ShowFull(); m_plater->Show(); -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) - show_tabs_menu(false); -#endif + m_tabpanel->Reparent(&m_settings_dialog); + m_tabpanel->SetSelection(sel > 0 ? (sel - 1) : 0); + m_tabpanel->ShowJustMode(); + m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 2); + m_settings_dialog.Layout(); break; } case ESettingsLayout::GCodeViewer: { m_main_sizer->Add(m_plater, 1, wxEXPAND); m_plater->set_default_bed_shape(); +#if ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC + m_plater->get_collapse_toolbar().set_enabled(true); +#else m_plater->get_collapse_toolbar().set_enabled(false); +#endif // !ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC m_plater->collapse_sidebar(true); m_plater->Show(); break; } } -#ifdef _MSW_DARK_MODE - // Sizer with buttons for mode changing - m_plater->sidebar().show_mode_sizer(wxGetApp().tabs_as_menu() || m_layout != ESettingsLayout::Old); -#endif - #ifdef __WXMSW__ if (update_scaling_state != State::noUpdate) { @@ -596,24 +488,11 @@ void MainFrame::update_layout() } #endif //__WXMSW__ -//#ifdef __APPLE__ -// // Using SetMinSize() on Mac messes up the window position in some cases -// // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 -// // So, if we haven't possibility to set MinSize() for the MainFrame, -// // set the MinSize() as a half of regular for the m_plater and m_tabpanel, when settings layout is in slNew mode -// // Otherwise, MainFrame will be maximized by height -// if (m_layout == ESettingsLayout::New) { -// wxSize size = wxGetApp().get_min_size(); -// size.SetHeight(int(0.5 * size.GetHeight())); -// m_plater->SetMinSize(size); -// m_tabpanel->SetMinSize(size); -// } -//#endif + if (m_layout == ESettingsLayout::Old) + m_tabpanel->InsertNewPage(0, m_plater, _L("Plater"), "", true); + + update_topbars(); -#ifdef __APPLE__ - m_plater->sidebar().change_top_border_for_mode_sizer(m_layout != ESettingsLayout::Old); -#endif - Layout(); Thaw(); } @@ -736,29 +615,23 @@ void MainFrame::init_tabpanel() { wxGetApp().update_ui_colours_from_appconfig(); + set_callbacks_for_topbar_menus(); + + if (wxGetApp().is_editor()) { + m_tmp_top_bar = new TopBar(this, &m_bar_menus, [this]() -> void { select_tab(); }); + m_tmp_top_bar->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + m_tmp_top_bar->Hide(); + } + // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 // with multiple high resolution displays connected. -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) { - m_tabpanel = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); -// wxGetApp().UpdateDarkUI(m_tabpanel); - } - else - m_tabpanel = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME, true); -#else - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); -#endif + m_tabpanel = new TopBar(this, &m_bar_menus); - wxGetApp().UpdateDarkUI(m_tabpanel); m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); -#ifdef __WXMSW__ m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { -#else - m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { -#endif //B45 m_printer_view->SetPauseThread(true); if (int old_selection = e.GetOldSelection(); @@ -769,23 +642,32 @@ void MainFrame::init_tabpanel() } wxWindow* panel = m_tabpanel->GetCurrentPage(); + size_t current_selected_tab = m_tabpanel->GetSelection(); Tab* tab = dynamic_cast(panel); - // There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology - if (panel == nullptr || (tab != nullptr && !tab->supports_printer_technology(m_plater->printer_technology()))) - return; + if (tab != nullptr) + { + // There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology + //if (panel == nullptr || (tab != nullptr && !tab->supports_printer_technology(m_plater->printer_technology()))) + // return; - auto& tabs_list = wxGetApp().tabs_list; - if (tab && std::find(tabs_list.begin(), tabs_list.end(), tab) != tabs_list.end()) { - // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered - // before the MainFrame is fully set up. - tab->OnActivate(); - m_last_selected_tab = m_tabpanel->GetSelection(); -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) - tab->SetFocus(); -#endif + // temporary fix - WebViewPanel is not inheriting from Tab -> would jump to select Plater + //if (panel && !tab) + // return; + + auto& tabs_list = wxGetApp().tabs_list; + if (tab && std::find(tabs_list.begin(), tabs_list.end(), tab) != tabs_list.end()) { + // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered + // before the MainFrame is fully set up. + tab->OnActivate(); + m_last_selected_tab = m_tabpanel->GetSelection(); + select_tab(tab); + //} else if (m_tabpanel->GetSelection() != 0) { + // m_last_selected_tab = m_tabpanel->GetSelection(); + } } + else if (current_selected_tab == 4 || current_selected_tab == 5) + select_tab(current_selected_tab); else select_tab(size_t(0)); // select Plater }); @@ -806,8 +688,11 @@ void MainFrame::init_tabpanel() // Show a correct number of filament fields. // nozzle_diameter is undefined when SLA printer is selected if (full_config.has("nozzle_diameter")) { - m_plater->on_extruders_change(full_config.option("nozzle_diameter")->values.size()); + m_plater->sidebar().set_extruders_count(full_config.option("nozzle_diameter")->values.size()); } + + if (wxGetApp().is_editor()) + m_tmp_top_bar->SetSettingsButtonTooltip(GetTooltipForSettingsButton(m_plater->printer_technology())); } } @@ -883,7 +768,7 @@ void MainFrame::create_preset_tabs() #endif #ifdef _MSW_DARK_MODE if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->AddPage(m_printer_view, _L("Device"), "tab_monitor_active"); + m_tabpanel->AddPage(m_printer_view, _L("Device"), "tab_monitor_active"); else #endif m_tabpanel->AddPage(m_printer_view, _L("Device")); @@ -900,7 +785,7 @@ void MainFrame::create_preset_tabs() #endif #ifdef _MSW_DARK_MODE if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->AddPage(m_guide_view, _L("Guide"), "userguide"); + m_tabpanel->AddPage(m_guide_view, _L("Guide"), "userguide"); else #endif m_tabpanel->AddPage(m_guide_view, _L("Guide")); @@ -910,28 +795,136 @@ void MainFrame::create_preset_tabs() wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_btns_enabling(); wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_PRINTER); }); + m_connect_webview = new ConnectWebViewPanel(m_tabpanel); + m_printer_webview = new PrinterWebViewPanel(m_tabpanel, L""); + // new created tabs have to be hidden by default + m_connect_webview->Hide(); + m_printer_webview->Hide(); - //m_printer_view->SetDeleteHandler([this](wxCommandEvent &event) { - // PresetBundle &preset_bundle = *wxGetApp().preset_bundle; +} - // const std::string &printer_name = preset_bundle.physical_printers.get_selected_full_printer_name(); - // if (printer_name.empty()) - // return false; +void MainFrame::add_connect_webview_tab() +{ + if (m_connect_webview_added) { + m_connect_webview->resend_config(); + return; + } + // parameters of InsertNewPage (to prevent ambigous overloaded function) + // insert "Connect" tab to position next to "Printer" tab + // order of tabs: Plater - Print Settings - Filaments - Printers - QIDI Connect - QIDI Link - // wxString msg; - // //if (!note_string.IsEmpty()) - // // msg += note_string + "\n"; - // msg += format_wxstr(_L("Are you sure you want to delete \"%1%\" printer?"), printer_name); + int n = m_tabpanel->FindPage(wxGetApp().get_tab(Preset::TYPE_PRINTER)) + 1; + wxWindow* page = m_connect_webview; + const wxString text(L"QIDI Connect"); + const std::string bmp_name = ""; + bool bSelect = false; + m_tabpanel->InsertNewPage(n, page, text, bmp_name, bSelect); + m_connect_webview->load_default_url_delayed(); + m_connect_webview_added = true; +} +void MainFrame::remove_connect_webview_tab() +{ + if (!m_connect_webview_added) { + return; + } + int n = m_tabpanel->FindPage(m_connect_webview); + if (m_tabpanel->GetSelection() == n) + m_tabpanel->SetSelection(0); + m_tabpanel->RemovePage(size_t(n)); + m_connect_webview_added = false; + m_connect_webview->logout(); +} - // if (MessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES) - // return false; +void MainFrame::show_printer_webview_tab(DynamicPrintConfig* dpc) +{ +#if 0 + // if physical printer is selected + if (dpc && dpc->option>("host_type")->value != htQIDIConnect) { + std::string url = dpc->opt_string("print_host"); - // preset_bundle.physical_printers.delete_selected_printer(); - //}); + if (url.find("http://") != 0 && url.find("https://") != 0) { + url = "http://" + url; + } - // #if defined(__WIN32__) - // m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, &MainFrame::OnTabPanelSelectionChanged, this); - // #endif + // set password / api key + if (dynamic_cast*>(dpc->option("printhost_authorization_type"))->value == AuthorizationType::atKeyPassword) { + set_printer_webview_api_key(dpc->opt_string("printhost_apikey")); + } + else { + set_printer_webview_credentials(dpc->opt_string("printhost_user"), dpc->opt_string("printhost_password")); + } + // add printer or change url + if (get_printer_webview_tab_added()) { + set_printer_webview_tab_url(from_u8(url)); + } + else { + add_printer_webview_tab(from_u8(url)); + } + } + // if physical printer isn't selected, so delete page from TopBar + else { + if (m_tabpanel->GetPageText(m_tabpanel->GetSelection()) == _L("Physical Printer")) + select_tab(size_t(0)); + remove_printer_webview_tab(); + } +#endif +} + +void MainFrame::add_printer_webview_tab(const wxString& url) +{ + if (m_printer_webview_added) { + set_printer_webview_tab_url(url); + return; + } + m_printer_webview_added = true; + // add as the last (rightmost) panel + m_tabpanel->AddNewPage(m_printer_webview, _L("Physical Printer"), ""); + m_printer_webview->set_default_url(url); + m_printer_webview->load_default_url_delayed(); +} +void MainFrame::remove_printer_webview_tab() +{ + if (!m_printer_webview_added) { + return; + } + m_printer_webview_added = false; + m_printer_webview->Hide(); + m_tabpanel->RemovePage(m_tabpanel->FindPage(m_printer_webview)); +} +void MainFrame::set_printer_webview_tab_url(const wxString& url) +{ + if (!m_printer_webview_added) { + add_printer_webview_tab(url); + return; + } + // TODO: this will reset already filled credential when bundle loaded, + // what's the reason of clearing credentials here? + //m_printer_webview->clear(); + m_printer_webview->set_default_url(url); + + if (m_tabpanel->GetSelection() == m_tabpanel->FindPage(m_printer_webview)) { + m_printer_webview->load_url(url); + } else { + m_printer_webview->load_default_url_delayed(); + } +} + +void MainFrame::set_printer_webview_api_key(const std::string& key) +{ + m_printer_webview->set_api_key(key); +} +void MainFrame::set_printer_webview_credentials(const std::string& usr, const std::string& psk) +{ + m_printer_webview->set_credentials(usr, psk); +} + +void Slic3r::GUI::MainFrame::refresh_account_menu(bool avatar/* = false */) +{ + // Update User name in TopBar + m_bar_menus.UpdateAccountState(avatar); + + m_tabpanel->GetTopBarItemsCtrl()->UpdateAccountButton(avatar); + m_tmp_top_bar->GetTopBarItemsCtrl()->UpdateAccountButton(avatar); } void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*/) @@ -941,12 +934,7 @@ void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""* const auto printer_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); if (panel->supports_printer_technology(printer_tech)) -#ifdef _MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->AddPage(panel, panel->title(), bmp_name); - else -#endif - m_tabpanel->AddPage(panel, panel->title()); + m_tabpanel->AddNewPage(panel, panel->title(), bmp_name); } bool MainFrame::is_active_and_shown_tab(Tab* tab) @@ -958,9 +946,6 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab) if (m_layout == ESettingsLayout::Dlg) return m_settings_dialog.IsShown(); - - if (m_layout == ESettingsLayout::New) - return m_main_sizer->IsShown(m_tabpanel); return true; } @@ -1089,7 +1074,6 @@ bool MainFrame::can_change_view() const switch (m_layout) { default: { return false; } - case ESettingsLayout::New: { return m_plater->IsShown(); } case ESettingsLayout::Dlg: { return true; } case ESettingsLayout::Old: { int page_id = m_tabpanel->GetSelection(); @@ -1129,10 +1113,10 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) wxGetApp().update_fonts(this); this->SetFont(this->normal_font()); -#ifdef _MSW_DARK_MODE - // update common mode sizer - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->Rescale(); +#ifdef _WIN32 + if (m_tmp_top_bar && m_tmp_top_bar->IsShown()) + m_tmp_top_bar->Rescale(); + m_tabpanel->Rescale(); #endif // update Plater @@ -1143,6 +1127,10 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); + wxGetApp().searcher().dlg_msw_rescale(); + + return; // #ysFIXME - delete_after_testing - It looks like next code is no need any more + // Workarounds for correct Window rendering after rescale /* Even if Window is maximized during moving, @@ -1171,41 +1159,45 @@ void MainFrame::on_sys_color_changed() // update label colors in respect to the system mode wxGetApp().init_ui_colours(); + + if (wxGetApp().is_gcode_viewer()) { + MenuFactory::sys_color_changed(m_menubar); + return; + } + // but if there are some ui colors in appconfig, they have to be applied wxGetApp().update_ui_colours_from_appconfig(); #ifdef __WXMSW__ wxGetApp().UpdateDarkUI(m_tabpanel); -#ifdef _MSW_DARK_MODE - // update common mode sizer - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->OnColorsChanged(); -#endif + wxGetApp().UpdateDarkUI(m_tmp_top_bar); #endif + m_tabpanel->OnColorsChanged(); + m_tmp_top_bar->OnColorsChanged(); // update Plater wxGetApp().plater()->sys_color_changed(); // update Tabs - for (auto tab : wxGetApp().tabs_list) + for (Tab* tab : wxGetApp().tabs_list) tab->sys_color_changed(); + if (m_connect_webview) + m_connect_webview->sys_color_changed(); + if (m_printer_webview) + m_printer_webview->sys_color_changed(); + MenuFactory::sys_color_changed(m_menubar); this->Refresh(); + + wxGetApp().searcher().dlg_sys_color_changed(); } void MainFrame::update_mode_markers() { -#ifdef __WXMSW__ -#ifdef _MSW_DARK_MODE // update markers in common mode sizer - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->UpdateModeMarkers(); -#endif -#endif - - // update mode markers on side_bar - wxGetApp().sidebar().update_mode_markers(); + m_tmp_top_bar->UpdateModeMarkers(); + m_tabpanel->UpdateModeMarkers(); // update mode markers in tabs for (auto tab : wxGetApp().tabs_list) @@ -1245,26 +1237,27 @@ static void init_macos_application_menu(wxMenuBar* menu_bar, MainFrame* main_fra } } #endif // __APPLE__ + static wxMenu* generate_help_menu() { wxMenu* helpMenu = new wxMenu(); //B6 append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Website"), SLIC3R_APP_NAME), wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME), - [](wxCommandEvent&) { wxLaunchDefaultBrowser("https://www.qidi3d.com", 0); }); + [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.qidi3d.com/slicerweb"); }); // TRN Item from "Help" menu - //append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&Quick Start"), SLIC3R_APP_NAME), - // wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME), - // [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://www.qidi3d.com", nullptr, false); }); +// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&Quick Start"), SLIC3R_APP_NAME), +// wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME), +// [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://help.qidi3d.com/article/first-print-with-qidislicer_1753", nullptr, false); }); // TRN Item from "Help" menu - //append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("Sample &G-codes and Models"), SLIC3R_APP_NAME), - // wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME), - // [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://www.qidi3d.com", nullptr, false); }); - //helpMenu->AppendSeparator(); - // append_menu_item(helpMenu, wxID_ANY, _L("QIDI 3D &Drivers"), _L("Open the QIDI3D drivers download page in your browser"), - // [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.qidi3d.com/downloads"); }); - // append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"), - // [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/qidi3d/QIDISlicer/releases", nullptr, false); }); +// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("Sample &G-codes and Models"), SLIC3R_APP_NAME), +// wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME), +// [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://help.qidi3d.com/article/sample-g-codes_529630", nullptr, false); }); +// helpMenu->AppendSeparator(); +// append_menu_item(helpMenu, wxID_ANY, _L("QIDI 3D &Drivers"), _L("Open the QIDI3D drivers download page in your browser"), +// [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.qidi3d.com/downloads"); }); +// append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"), +// [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/qidi3d/QIDISlicer/releases", nullptr, false); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); @@ -1279,20 +1272,17 @@ static wxMenu* generate_help_menu() append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"), [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); // append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), -// [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/QIDITECH/QIDISlicer", nullptr, false); }); - if (wxGetApp().is_editor()) - append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), - [](wxCommandEvent&) { Slic3r::GUI::about(); }); - else - append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), GCODEVIEWER_APP_NAME), _L("Show about dialog"), - [](wxCommandEvent&) { Slic3r::GUI::about(); }); +// [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/qidi3d/slic3r/issues/new", nullptr, false); }); +#ifndef __APPLE__ + append_about_menu_item(helpMenu); +#endif // __APPLE__ //B6 // append_menu_item(helpMenu, wxID_ANY, _L("Show Tip of the Day") -// #if 0//debug -// + "\tCtrl+Shift+T" -// #endif -// ,_L("Opens Tip of the day notification in bottom right corner or shows another tip if already opened."), -// [](wxCommandEvent&) { wxGetApp().plater()->get_notification_manager()->push_hint_notification(false); }); +//#if 0//debug +// + "\tCtrl+Shift+T" +//#endif +// ,_L("Opens Tip of the day notification in bottom right corner or shows another tip if already opened."), +// [](wxCommandEvent&) { wxGetApp().plater()->get_notification_manager()->push_hint_notification(false); }); helpMenu->AppendSeparator(); append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"), [](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); @@ -1442,10 +1432,6 @@ void MainFrame::init_menubar_as_editor() append_menu_item(export_menu, wxID_ANY, _L("Export Plate as STL/OBJ &Including Supports") + dots, _L("Export current plate as STL/OBJ including supports"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl_obj(true); }, "export_plater", nullptr, [this](){return can_export_supports(); }, this); -// Deprecating AMF export. Let's wait for user feedback. -// append_menu_item(export_menu, wxID_ANY, _L("Export Plate as &AMF") + dots, _L("Export current plate as AMF"), -// [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater", nullptr, -// [this](){return can_export_model(); }, this); export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _L("Export &Toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, @@ -1470,6 +1456,7 @@ void MainFrame::init_menubar_as_editor() [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->convert_gcode_to_ascii(); }, "convert_file", nullptr, []() { return true; }, this); append_submenu(fileMenu, convert_menu, wxID_ANY, _L("&Convert"), ""); + append_menu_item(fileMenu, wxID_ANY, _L("Ejec&t SD Card / Flash Drive") + dots + "\tCtrl+T", _L("Eject SD card / Flash drive after the G-code was exported to it."), [this](wxCommandEvent&) { if (m_plater) m_plater->eject_drive(); }, "eject_sd", nullptr, [this]() {return can_eject(); }, this); @@ -1549,7 +1536,7 @@ void MainFrame::init_menubar_as_editor() editMenu->AppendSeparator(); append_menu_item(editMenu, wxID_ANY, _L("Searc&h") + "\tCtrl+F", - _L("Search in settings"), [this](wxCommandEvent&) { m_plater->search(m_plater->IsShown()); }, + _L("Search in settings"), [](wxCommandEvent&) { wxGetApp().show_search_dialog(); }, "search", nullptr, []() {return true; }, this); } @@ -1573,12 +1560,20 @@ void MainFrame::init_menubar_as_editor() [this/*, tab_offset*/](wxCommandEvent&) { select_tab(3); }, "printer", nullptr, []() {return true; }, this); m_changeable_menu_items.push_back(item_printer_tab); + wxMenuItem* item_device_tab = append_menu_item(windowMenu, wxID_HIGHEST + 5, _L("Device Page") + "\tCtrl+5", _L("Show the Device page"), + [this/*, tab_offset*/](wxCommandEvent&) { select_tab(4); }, "tab_monitor_active", nullptr, + []() {return true; }, this); + m_changeable_menu_items.push_back(item_device_tab); + wxMenuItem* item_guide_tab = append_menu_item(windowMenu, wxID_HIGHEST + 6, _L("Guide Page") + "\tCtrl+6", _L("Show the Guide page"), + [this/*, tab_offset*/](wxCommandEvent&) { select_tab(5); }, "userguide", nullptr, + []() {return true; }, this); + m_changeable_menu_items.push_back(item_guide_tab); if (m_plater) { windowMenu->AppendSeparator(); - append_menu_item(windowMenu, wxID_HIGHEST + 5, _L("3&D") + "\tCtrl+5", _L("Show the 3D editing view"), + append_menu_item(windowMenu, wxID_HIGHEST + 7, _L("3&D") + "\tCtrl+7", _L("Show the 3D editing view"), [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu", nullptr, [this](){return can_change_view(); }, this); - append_menu_item(windowMenu, wxID_HIGHEST + 6, _L("Pre&view") + "\tCtrl+6", _L("Show the 3D slices preview"), + append_menu_item(windowMenu, wxID_HIGHEST + 8, _L("Pre&view") + "\tCtrl+8", _L("Show the 3D slices preview"), [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu", nullptr, [this](){return can_change_view(); }, this); } @@ -1672,6 +1667,26 @@ void MainFrame::init_menubar_as_editor() "", nullptr, [this]() { return m_plater->is_view3D_shown(); }, this); } +#if 0//ndef__APPLE__ + // append menus for Menu button from TopBar + + m_bar_menus.AppendMenuItem(fileMenu, _L("&File")); + if (editMenu) + m_bar_menus.AppendMenuItem(editMenu, _L("&Edit")); + + m_bar_menus.AppendMenuSeparaorItem(); + + m_bar_menus.AppendMenuItem(windowMenu, _L("&Window")); + if (viewMenu) + m_bar_menus.AppendMenuItem(viewMenu, _L("&View")); + + m_bar_menus.AppendMenuItem(wxGetApp().get_config_menu(this), _L("&Configuration")); + + m_bar_menus.AppendMenuSeparaorItem(); + + m_bar_menus.AppendMenuItem(helpMenu, _L("&Help")); + +#else // menubar // assign menubar to frame after appending items, otherwise special items // will not be handled correctly @@ -1682,29 +1697,19 @@ void MainFrame::init_menubar_as_editor() m_menubar->Append(windowMenu, _L("&Window")); if (viewMenu) m_menubar->Append(viewMenu, _L("&View")); // Add additional menus from C++ - wxGetApp().add_config_menu(m_menubar); + m_menubar->Append(wxGetApp().get_config_menu(this), _L("&Configuration")); m_menubar->Append(helpMenu, _L("&Help")); //B34 m_menubar->Append(calibrationMenu, _L("&Calibration")); -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) { - // Add separator - m_menubar->Append(new wxMenu(), " "); - add_tabs_as_menu(m_menubar, this, this); - } -#endif SetMenuBar(m_menubar); -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) - m_menubar->EnableTop(6, false); -#endif - #ifdef __APPLE__ init_macos_application_menu(m_menubar, this); #endif // __APPLE__ +#endif + if (plater()->printer_technology() == ptSLA) update_menubar(); } @@ -1789,7 +1794,7 @@ void MainFrame::init_menubar_as_gcodeviewer() m_menubar->Append(fileMenu, _L("&File")); if (viewMenu != nullptr) m_menubar->Append(viewMenu, _L("&View")); // Add additional menus from C++ - wxGetApp().add_config_menu(m_menubar); + m_menubar->Append(wxGetApp().get_config_menu(this), _L("&Configuration")); m_menubar->Append(helpMenu, _L("&Help")); SetMenuBar(m_menubar); @@ -1868,6 +1873,50 @@ void MainFrame::export_config() if (dlg.ShowModal() == wxID_OK) file = dlg.GetPath(); if (!file.IsEmpty()) { +//#if wxUSE_SECRETSTORE +// bool passwords_to_plain = false; +// bool passwords_dialog_shown = false; +//#endif +// // callback function thats going to be passed to preset bundle (so preset bundle doesnt have to include WX secret lib) +// std::function load_password = [&](const std::string& printer_id, const std::string& opt, std::string& out_psswd)->bool{ +// out_psswd = std::string(); +//#if wxUSE_SECRETSTORE +// // First password prompts user with dialog +// if (!passwords_dialog_shown) { +// wxString msg = _L("Some of the exported printers contain passwords, which are stored in the system password store." +// " Do you want to include the passwords in the plain text form in the exported file?"); +// MessageDialog dlg_psswd(this, msg, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); +// if (dlg_psswd.ShowModal() == wxID_YES) +// passwords_to_plain = true; +// passwords_dialog_shown = true; +// } +// if (!passwords_to_plain) +// return false; +// wxSecretStore store = wxSecretStore::GetDefault(); +// wxString errmsg; +// if (!store.IsOk(&errmsg)) { +// std::string msg = GUI::format("%1% (%2%).", _u8L("Failed to load credentials from the system password store."), errmsg); +// BOOST_LOG_TRIVIAL(error) << msg; +// show_error(nullptr, msg); +// // Do not try again. System store is not reachable. +// passwords_to_plain = false; +// return false; +// } +// const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, printer_id, opt); +// wxString username; +// wxSecretValue password; +// if (!store.Load(service, username, password)) { +// std::string msg = GUI::format(_u8L("Failed to load credentials from the system password store for printer %1%."), printer_id); +// BOOST_LOG_TRIVIAL(error) << msg; +// show_error(nullptr, msg); +// return false; +// } +// out_psswd = into_u8(password.GetAsString()); +// return true; +//#else +// return false; +//#endif // wxUSE_SECRETSTORE +// }; wxGetApp().app_config->update_config_dir(get_dir_name(file)); m_last_config = file; config.save(file.ToUTF8().data()); @@ -1882,7 +1931,7 @@ void MainFrame::load_config_file() wxFileDialog dlg(this, _L("Select configuration to load:"), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), "config.ini", "INI files (*.ini, *.gcode, *.bgcode)|*.ini;*.INI;*.gcode;*.g;*.bgcode;*.bgc", wxFD_OPEN | wxFD_FILE_MUST_EXIST); - wxString file; + wxString file; if (dlg.ShowModal() == wxID_OK) file = dlg.GetPath(); if (! file.IsEmpty() && this->load_config_file(file.ToUTF8().data())) { @@ -1899,6 +1948,7 @@ void MainFrame::load_config_file() msg_dlg.set_caption(wxString(SLIC3R_APP_NAME " - ") + _L("Attention!")); msg_dlg.ShowModal(); } + wxGetApp().app_config->update_config_dir(get_dir_name(file)); m_last_config = file; } @@ -1942,9 +1992,54 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) file = dlg.GetPath(); if (!file.IsEmpty()) { // Export the config bundle. +#if wxUSE_SECRETSTORE + bool passwords_to_plain = false; + bool passwords_dialog_shown = false; +#endif + // callback function thats going to be passed to preset bundle (so preset bundle doesnt have to include WX secret lib) + std::function load_password = [&](const std::string& printer_id, const std::string& opt, std::string& out_psswd)->bool{ + out_psswd = std::string(); +#if wxUSE_SECRETSTORE + // First password prompts user with dialog + if (!passwords_dialog_shown) { + wxString msg = _L("Some of the exported printers contain passwords, which are stored in the system password store." + " Do you want to include the passwords in the plain text form in the exported file?"); + MessageDialog dlg_psswd(this, msg, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); + if (dlg_psswd.ShowModal() == wxID_YES) + passwords_to_plain = true; + passwords_dialog_shown = true; + } + if (!passwords_to_plain) + return false; + wxSecretStore store = wxSecretStore::GetDefault(); + wxString errmsg; + if (!store.IsOk(&errmsg)) { + std::string msg = GUI::format("%1% (%2%).", _u8L("Failed to load credentials from the system password store."), errmsg); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + // Do not try again. System store is not reachable. + passwords_to_plain = false; + return false; + } + const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, printer_id, opt); + wxString username; + wxSecretValue password; + if (!store.Load(service, username, password)) { + std::string msg = GUI::format(_u8L("Failed to load credentials from the system password store for printer %1%."), printer_id); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return false; + } + out_psswd = into_u8(password.GetAsString()); + return true; +#else + return false; +#endif // wxUSE_SECRETSTORE + }; + wxGetApp().app_config->update_config_dir(get_dir_name(file)); try { - wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data(), false, export_physical_printers); + wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data(), false, export_physical_printers, load_password); } catch (const std::exception &ex) { show_error(this, ex.what()); } @@ -2028,6 +2123,13 @@ void MainFrame::load_config(const DynamicPrintConfig& config) #endif } +void MainFrame::update_search_lines(const std::string search_line) +{ + wxString search = from_u8(search_line); + m_tabpanel ->UpdateSearch(search); + m_tmp_top_bar->UpdateSearch(search); +} + void MainFrame::select_tab(Tab* tab) { if (!tab) @@ -2038,20 +2140,10 @@ void MainFrame::select_tab(Tab* tab) select_tab(size_t(page_idx)); } -//B45 -// void MainFrame::OnTabPanelSelectionChanged(wxCommandEvent &event) -// { - -// m_printer_view->PauseButton(); -// event.Skip(); -// } - - - -//B4 -//B45 void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { + if (!wxGetApp().is_editor()) + return; bool tabpanel_was_hidden = false; // Controls on page are created on active page of active tab now. @@ -2101,58 +2193,12 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) //B64 m_printer_view->SetPauseThread(false); - - //y3 - //if (const DynamicPrintConfig *cfg = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); cfg) { - // const PhysicalPrinter &pp = preset_bundle.physical_printers.get_selected_printer(); - // wxString host = pp.config.opt_string("print_host"); - // //B55 - // //B45 - // if (host.empty()) { - // tem_host = ""; - // host = wxString::Format("file://%s/web/qidi/missing_connection.html", from_u8(resources_dir())); - // } else { - // //B55 - // const auto opt = cfg->option>("host_type"); - // const auto host_type = opt != nullptr ? opt->value : htOctoPrint; - // if (!host.Lower().starts_with("http")) - // host = wxString::Format("http://%s", host); - // if (host_type == htMoonraker) { - // if (!host.Lower().ends_with("10088")) - // host = wxString::Format("%s:10088", host); - // } - // } - // //B55 - // if (tem_host != host) { - // //B45 - // m_printer_view->load_url(host); - // tem_host = host; - // } - //} else { - // tem_host = ""; - // wxString url = wxString::Format("file://%s/web/qidi/missing_connection.html", from_u8(resources_dir())); - // m_printer_view->load_url(url); - //} m_printer_view->Layout(); } // B30 - if (m_tabpanel->GetSelection() != (int) new_selection && m_tabpanel->GetSelection() < 4) + if (m_tabpanel->GetSelection() != (int) new_selection) m_tabpanel->SetSelection(new_selection); - //#ifdef __APPLE__ - // if (count == 0 && m_tabpanel->GetSelection() != (int) new_selection) { - // m_tabpanel->SetSelection(new_selection); - // count++; - // } - //#endif -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) { - if (Tab* cur_tab = dynamic_cast(m_tabpanel->GetPage(new_selection))) - update_marker_for_tabs_menu((m_layout == ESettingsLayout::Old ? m_menubar : m_settings_dialog.menubar()), cur_tab->title(), m_layout == ESettingsLayout::Old); - else if (tab == 0 && m_layout == ESettingsLayout::Old) - m_plater->get_current_canvas3D()->render(); - } -#endif if (tab == 0 && m_layout == ESettingsLayout::Old) m_plater->canvas3D()->render(); else if (was_hidden) { @@ -2166,9 +2212,6 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) if (tab==0) { if (m_settings_dialog.IsShown()) this->SetFocus(); - // plater should be focused for correct navigation inside search window - if (m_plater->canvas3D()->is_search_pressed()) - m_plater->SetFocus(); return; } // Show/Activate Settings Dialog @@ -2196,26 +2239,11 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) if (m_settings_dialog.IsIconized()) m_settings_dialog.Iconize(false); } - else if (m_layout == ESettingsLayout::New) { - m_main_sizer->Show(m_plater, tab == 0); - tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel); - select(tabpanel_was_hidden); - m_main_sizer->Show(m_tabpanel, tab != 0); - - // plater should be focused for correct navigation inside search window - if (tab == 0) - m_plater->SetFocus(); - Layout(); - } else { select(false); -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu() && tab == 0) - m_plater->SetFocus(); -#endif } - // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning + // When we run application in ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, // which are used for update TreeCtrl and "revert_buttons". // So, force the call of this function for Tabs, if tab panel was hidden @@ -2263,24 +2291,6 @@ void MainFrame::on_presets_changed(SimpleEvent &event) } } -// #ys_FIXME_to_delete -void MainFrame::on_value_changed(wxCommandEvent& event) -{ - auto *tab = dynamic_cast(event.GetEventObject()); - wxASSERT(tab != nullptr); - if (tab == nullptr) - return; - - auto opt_key = event.GetString(); - if (m_plater) { - m_plater->on_config_change(*tab->get_config()); // propagate config change events to the plater - if (opt_key == "extruders_count") { - auto value = event.GetInt(); - m_plater->on_extruders_change(value); - } - } -} - void MainFrame::on_config_changed(DynamicPrintConfig* config) const { if (m_plater) @@ -2304,8 +2314,12 @@ void MainFrame::add_to_recent_projects(const wxString& filename) void MainFrame::technology_changed() { - // update menu titles PrinterTechnology pt = plater()->printer_technology(); + m_tmp_top_bar->SetSettingsButtonTooltip(GetTooltipForSettingsButton(pt)); + + if (!m_menubar) + return; + // update menu titles if (int id = m_menubar->FindMenu(pt == ptFFF ? _L("Material Settings") : _L("Filament Settings")); id != wxNOT_FOUND) m_menubar->SetMenuLabel(id , pt == ptSLA ? _L("Material Settings") : _L("Filament Settings")); @@ -2325,6 +2339,8 @@ void MainFrame::update_ui_from_settings() // m_plater->sidebar().show_export(bp_on); // m_plater->sidebar().Layout(); + update_topbars(); + if (m_plater) m_plater->update_ui_from_settings(); for (auto tab: wxGetApp().tabs_list) @@ -2358,7 +2374,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) if (wxGetApp().is_gcode_viewer()) return; - // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -2379,15 +2394,19 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) case '2': { m_main_frame->select_tab(1); break; } case '3': { m_main_frame->select_tab(2); break; } case '4': { m_main_frame->select_tab(3); break; } + case '5': { m_main_frame->select_tab(4); break; } + case '6': { m_main_frame->select_tab(5); break; } #ifdef __APPLE__ case 'f': #else /* __APPLE__ */ case WXK_CONTROL_F: #endif /* __APPLE__ */ - case 'F': { m_main_frame->plater()->search(false); break; } + case 'F': { wxGetApp().show_search_dialog(); break; } default:break; } } + + evt.Skip(); }; if (evt.IsShown()) { @@ -2403,14 +2422,11 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) //just hide the Frame on closing this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& evt) { this->Hide(); }); -#ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) { - // menubar - m_menubar = new wxMenuBar(); - add_tabs_as_menu(m_menubar, mainframe, this); - this->SetMenuBar(m_menubar); - } -#endif + this->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { + event.Skip(); + if (m_tabpanel) + m_tabpanel->UpdateSearchSizeAndPosition(); + }); // initialize layout auto sizer = new wxBoxSizer(wxVERTICAL); @@ -2428,6 +2444,11 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) SetSize(GetMinSize()); #endif Layout(); + + Bind(wxEVT_MOVE, [](wxMoveEvent& event) { + wxGetApp().searcher().update_dialog_position(); + event.Skip(); + }); } void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) @@ -2435,22 +2456,22 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) if (wxGetApp().is_gcode_viewer()) return; - const int& em = em_unit(); - const wxSize& size = wxSize(85 * em, 50 * em); +// #ysFIXME - delete_after_testing +// const int& em = em_unit(); +// const wxSize& size = wxSize(85 * em, 50 * em); -#ifdef _MSW_DARK_MODE - // update common mode sizer - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->Rescale(); +#ifdef _WIN32 + m_tabpanel->Rescale(); #endif // update Tabs for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); - SetMinSize(size); - Fit(); - Refresh(); +// #ysFIXME - delete_after_testing +// SetMinSize(size); +// Fit(); +// Refresh(); } diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 7474603..ccbee8c 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -17,6 +17,9 @@ #include "GUI_Utils.hpp" #include "Event.hpp" #include "UnsavedChangesDialog.hpp" +#include "Search.hpp" + +#include "TopBarMenus.hpp" //B4 #include "PrinterWebView.hpp" // B28 @@ -24,13 +27,11 @@ //B34 #include "calib_dlg.hpp" -class wxBookCtrlBase; +class TopBar; class wxProgressDialog; namespace Slic3r { -class ProgressStatusBar; - namespace GUI { @@ -40,7 +41,8 @@ class Plater; class MainFrame; class PreferencesDialog; class GalleryDialog; - +class ConnectWebViewPanel; +class PrinterWebViewPanel; enum QuickSlice { @@ -63,13 +65,13 @@ struct PresetTab { class SettingsDialog : public DPIFrame//DPIDialog { - wxBookCtrlBase* m_tabpanel { nullptr }; + TopBar* m_tabpanel { nullptr }; MainFrame* m_main_frame { nullptr }; wxMenuBar* m_menubar{ nullptr }; public: SettingsDialog(MainFrame* mainframe); ~SettingsDialog() = default; - void set_tabpanel(wxBookCtrlBase* tabpanel) { m_tabpanel = tabpanel; } + void set_tabpanel(TopBar* tabpanel) { m_tabpanel = tabpanel; } wxMenuBar* menubar() { return m_menubar; } protected: @@ -84,20 +86,23 @@ class MainFrame : public DPIFrame wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; wxMenuBar* m_menubar{ nullptr }; + TopBarMenus m_bar_menus; -#if 0 - wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now -#endif wxMenuItem* m_menu_item_reslice_now { nullptr }; wxSizer* m_main_sizer{ nullptr }; size_t m_last_selected_tab; + Search::OptionsSearcher m_searcher; + + ConnectWebViewPanel* m_connect_webview{ nullptr }; + bool m_connect_webview_added{ false }; + PrinterWebViewPanel* m_printer_webview{ nullptr }; + bool m_printer_webview_added{ false }; std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const; std::string get_dir_name(const wxString &full_name) const; void on_presets_changed(SimpleEvent&); - void on_value_changed(wxCommandEvent&); bool can_start_new_project() const; bool can_export_model() const; @@ -114,7 +119,6 @@ class MainFrame : public DPIFrame bool can_delete() const; bool can_delete_all() const; bool can_reslice() const; - void bind_diff_dialog(); // MenuBar items changeable in respect to printer technology enum MenuItems @@ -123,6 +127,7 @@ class MainFrame : public DPIFrame miSend, // Send G-code Send to print miMaterialTab, // Filament Settings Material Settings miPrinterTab, // Different bitmap for Printer Settings + miLogin, }; // vector of a MenuBar items changeable in respect to printer technology @@ -149,7 +154,6 @@ public: MainFrame(const int font_point_size); ~MainFrame() = default; - void update_layout(); void update_mode_markers(); @@ -161,6 +165,8 @@ public: void update_title(); + void set_callbacks_for_topbar_menus(); + void update_topbars(); void init_tabpanel(); void create_preset_tabs(); void add_created_tab(Tab* panel, const std::string& bmp_name = ""); @@ -173,9 +179,6 @@ public: void update_menubar(); // Open item in menu by menu and item name (in actual language) void open_menubar_item(const wxString& menu_name,const wxString& item_name); -#ifdef _WIN32 - void show_tabs_menu(bool show); -#endif void update_ui_from_settings(); bool is_loaded() const { return m_loaded; } bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); } @@ -191,13 +194,11 @@ public: void export_configbundle(bool export_physical_printers = false); void load_configbundle(wxString file = wxEmptyString); void load_config(const DynamicPrintConfig& config); + void update_search_lines(const std::string search_line); // Select tab in m_tabpanel // When tab == -1, will be selected last selected tab void select_tab(Tab* tab); void select_tab(size_t tab = size_t(-1)); - //B45 - //void OnTabPanelSelectionChanged(wxCommandEvent &event); - void select_view(const std::string& direction); // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; @@ -210,8 +211,21 @@ public: void add_to_recent_projects(const wxString& filename); void technology_changed(); - PrintHostQueueDialog *printhost_queue_dlg() { return m_printhost_queue_dlg; } + void add_connect_webview_tab(); + void remove_connect_webview_tab(); + void show_printer_webview_tab(DynamicPrintConfig* dpc); + + void add_printer_webview_tab(const wxString& url); + void remove_printer_webview_tab(); + void set_printer_webview_tab_url(const wxString& url); + bool get_printer_webview_tab_added() const { return m_printer_webview_added; } + void set_printer_webview_api_key(const std::string& key); + void set_printer_webview_credentials(const std::string& usr, const std::string& psk); + + void refresh_account_menu(bool avatar = false); + + PrintHostQueueDialog *printhost_queue_dlg() { return m_printhost_queue_dlg; } Plater *m_plater{nullptr}; FRF_Calibration_Dlg *m_frf_calib_dlg{nullptr}; @@ -226,18 +240,16 @@ public: //B45 PresetCollection *m_collection{nullptr}; - - wxBookCtrlBase * m_tabpanel{nullptr}; + TopBar* m_tmp_top_bar { nullptr }; + TopBar* m_tabpanel { nullptr }; SettingsDialog m_settings_dialog; DiffPresetDialog diff_dialog; wxWindow* m_plater_page{ nullptr }; // wxProgressDialog* m_progress_dialog { nullptr }; PreferencesDialog* preferences_dialog { nullptr }; PrintHostQueueDialog* m_printhost_queue_dlg; -// std::shared_ptr m_statusbar; GalleryDialog* m_gallery_dialog{ nullptr }; - - + #ifdef __APPLE__ std::unique_ptr m_taskbar_icon; #endif // __APPLE__ diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index 1ce6717..3325dff 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -446,11 +446,11 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const Size cnv_size = canvas.get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(0.5f * (float)cnv_size.get_width(), 0.5f * (float)cnv_size.get_height(), ImGuiCond_Always, 0.5f, 0.5f); + ImGuiPureWrap::set_next_window_pos(0.5f * (float)cnv_size.get_width(), 0.5f * (float)cnv_size.get_height(), ImGuiCond_Always, 0.5f, 0.5f); static ImVec2 last_win_size(0.0f, 0.0f); bool shown = true; - if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) { + if (ImGuiPureWrap::begin(_u8L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) { if (shown) { ImVec2 win_size = ImGui::GetWindowSize(); if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) { @@ -461,12 +461,12 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator); - imgui.text_colored(color, _L("Device:")); + ImGuiPureWrap::text_colored(color, _u8L("Device:")); ImGui::SameLine(); - imgui.text(m_device_str); + ImGuiPureWrap::text(m_device_str); ImGui::Separator(); - imgui.text_colored(color, _L("Speed:")); + ImGuiPureWrap::text_colored(color, _u8L("Speed:")); float translation_scale = float(params_copy.translation.scale) / float(Params::DefaultTranslationScale); if (imgui.slider_float(_L("Translation"), &translation_scale, float(Params::MinTranslationScale), float(Params::MaxTranslationScale), "%.1f")) { @@ -487,7 +487,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } ImGui::Separator(); - imgui.text_colored(color, _L("Deadzone:")); + ImGuiPureWrap::text_colored(color, _u8L("Deadzone:")); float translation_deadzone = (float)params_copy.translation.deadzone; if (imgui.slider_float(_L("Translation") + "/" + _L("Zoom"), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) { @@ -502,10 +502,10 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } ImGui::Separator(); - imgui.text_colored(color, _L("Options:")); + ImGuiPureWrap::text_colored(color, _u8L("Options:")); bool swap_yz = params_copy.swap_yz; - if (imgui.checkbox(_L("Swap Y/Z axes"), swap_yz)) { + if (ImGuiPureWrap::checkbox(_u8L("Swap Y/Z axes"), swap_yz)) { params_copy.swap_yz = swap_yz; params_changed = true; } @@ -513,14 +513,14 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); ImGui::Separator(); - imgui.text_colored(color, "DEBUG:"); - imgui.text_colored(color, "Vectors:"); + ImGuiPureWrap::text_colored(color, "DEBUG:"); + ImGuiPureWrap::text_colored(color, "Vectors:"); Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast(); Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast(); ImGui::InputFloat3("Translation##2", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); - imgui.text_colored(color, "Queue size:"); + ImGuiPureWrap::text_colored(color, "Queue size:"); int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) }; ImGui::InputInt2("Current", input_queue_size_current, ImGuiInputTextFlags_ReadOnly); @@ -534,13 +534,13 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } ImGui::Separator(); - imgui.text_colored(color, "Camera:"); + ImGuiPureWrap::text_colored(color, "Camera:"); Vec3f target = wxGetApp().plater()->get_camera().get_target().cast(); ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); - if (imgui.button(_L("Close"))) { + if (ImGuiPureWrap::button(_u8L("Close"))) { // the user clicked on the [Close] button m_settings_dialog_closed_by_user = true; canvas.set_as_dirty(); @@ -553,7 +553,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } } - imgui.end(); + ImGuiPureWrap::end(); if (params_changed) { // Synchronize front end parameters to back end. @@ -971,12 +971,12 @@ bool Mouse3DController::connect_device() if (m_device != nullptr) { wchar_t buffer[1024]; hid_get_manufacturer_string(m_device, buffer, 1024); - m_device_str = boost::nowide::narrow(buffer); + m_device_str = into_u8(buffer); // #3479 seems to show that sometimes an extra whitespace is added, so we remove it boost::algorithm::trim(m_device_str); hid_get_product_string(m_device, buffer, 1024); - m_device_str += "/" + boost::nowide::narrow(buffer); + m_device_str += "/" + into_u8(buffer); // #3479 seems to show that sometimes an extra whitespace is added, so we remove it boost::algorithm::trim(m_device_str); diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 8aad678..86327e2 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -26,7 +26,7 @@ #include "Widgets/CheckBox.hpp" //Y -#include "libslic3r/AppConfig.cpp" +#include "libslic3r/AppConfig.hpp" namespace Slic3r { namespace GUI { @@ -135,7 +135,6 @@ void MsgDialog::finalize() this->CenterOnParent(); } - // Text shown as HTML, so that mouse selection and Ctrl-V to copy will work. static void add_msg_content(MsgDialog* parent, wxBoxSizer* content_sizer, const HtmlContent& content) { @@ -234,7 +233,7 @@ static void add_msg_content(MsgDialog* parent, wxBoxSizer* content_sizer, const content.on_link_clicked(into_u8(event.GetLinkInfo().GetHref())); } else - wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref(), parent, false); + wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref(), parent, false); event.Skip(false); }); @@ -264,13 +263,15 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_ create(m_content, monospaced_font ? 48 : 84); } -ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, const std::function &on_link_clicked) +ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, const t_link_clicked& on_link_clicked) : MsgDialog(parent, wxString::Format(_L("%s error"), SLIC3R_APP_NAME), wxString::Format(_L("%s has encountered an error"), SLIC3R_APP_NAME), wxOK) , m_content(HtmlContent{ msg, false, true, on_link_clicked }) { create(m_content, 84); } + + HtmlCapableRichMessageDialog::HtmlCapableRichMessageDialog(wxWindow *parent, const wxString &msg, const wxString &caption, @@ -278,6 +279,8 @@ HtmlCapableRichMessageDialog::HtmlCapableRichMessageDialog(wxWindow const std::function &on_link_clicked) : RichMessageDialogBase(parent, HtmlContent{msg, false, true, on_link_clicked}, caption, style) {} + + // WarningDialog WarningDialog::WarningDialog(wxWindow *parent, @@ -326,14 +329,16 @@ RichMessageDialogBase::RichMessageDialogBase(wxWindow* parent, const HtmlContent #else m_checkBox = new wxCheckBox(this, wxID_ANY, m_checkBoxText); #endif + 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(); + finalize(); } + int RichMessageDialogBase::ShowModal() { if (m_checkBoxText.IsEmpty()) @@ -347,6 +352,7 @@ int RichMessageDialogBase::ShowModal() return wxDialog::ShowModal(); } + // InfoDialog InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg, bool is_marked_msg/* = false*/, long style/* = wxOK | wxICON_INFORMATION*/) diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index fbbe40d..9d6f717 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -20,13 +20,16 @@ namespace Slic3r { namespace GUI { +using t_link_clicked = std::function; + struct HtmlContent { - wxString msg{ wxEmptyString }; - bool is_monospaced_font{ false }; - bool is_marked_msg{ false }; - std::function on_link_clicked{ nullptr }; + wxString msg{ wxEmptyString }; + bool is_monospaced_font{ false }; + bool is_marked_msg{ false }; + t_link_clicked on_link_clicked{ nullptr }; }; + // A message / query dialog with a bitmap on the left and any content on the right // with buttons underneath. struct MsgDialog : wxDialog @@ -70,7 +73,7 @@ public: // If monospaced_font is true, the error message is displayed using html
tags, // so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser. ErrorDialog(wxWindow *parent, const wxString &msg, bool courier_font); - ErrorDialog(wxWindow *parent, const wxString &msg, const std::function& on_link_clicked); + ErrorDialog(wxWindow *parent, const wxString &msg, const t_link_clicked& on_link_clicked); ErrorDialog(ErrorDialog &&) = delete; ErrorDialog(const ErrorDialog &) = delete; ErrorDialog &operator=(ErrorDialog &&) = delete; @@ -105,6 +108,7 @@ wxString get_wraped_wxString(const wxString& text_in, size_t line_len = 80); // Generic rich message dialog, used intead of wxRichMessageDialog class RichMessageDialogBase : public MsgDialog { + // Using CheckBox causes some weird sizer-related issues on Linux and macOS. To get around the problem before // we find a better fix, we will fallback to wxCheckBox in this dialog. This makes little difference for most dialogs, // We currently only use this class as a base for HtmlCapableRichMessageDialog on Linux and macOS. The normal @@ -114,6 +118,7 @@ class RichMessageDialogBase : public MsgDialog #else wxCheckBox* m_checkBox{ nullptr }; #endif + wxString m_checkBoxText; bool m_checkBoxValue{ false }; @@ -246,6 +251,7 @@ private: m_ok, m_cancel, m_help; + HtmlContent m_content; }; @@ -283,7 +289,9 @@ public: MessageDialog &operator=(const MessageDialog &) = delete; virtual ~MessageDialog() = default; }; + using RichMessageDialog = RichMessageDialogBase; + #else // just a wrapper for wxStaticLine to use the same code on all platforms class StaticLine : public wxStaticLine @@ -334,6 +342,7 @@ public: private: HtmlContent m_content; }; + // Generic info dialog, used for displaying exceptions class InfoDialog : public MsgDialog { @@ -346,6 +355,7 @@ public: virtual ~InfoDialog() = default; void set_caption(const wxString& caption) { this->SetTitle(caption); } + private: wxString msg; }; diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp index 4a03abf..720f973 100644 --- a/src/slic3r/GUI/Notebook.cpp +++ b/src/slic3r/GUI/Notebook.cpp @@ -10,7 +10,7 @@ wxDEFINE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent); -ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent, bool add_mode_buttons/* = false*/) : +ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent) : wxControl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL) { #ifdef __WINDOWS__ @@ -27,12 +27,6 @@ ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent, bool add_mode_buttons/* = fal m_buttons_sizer = new wxFlexGridSizer(1, m_btn_margin, m_btn_margin); m_sizer->Add(m_buttons_sizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxBOTTOM, m_btn_margin); - if (add_mode_buttons) { - m_mode_sizer = new ModeSizer(this, m_btn_margin); - m_sizer->AddStretchSpacer(20); - m_sizer->Add(m_mode_sizer, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxBOTTOM, m_btn_margin); - } - this->Bind(wxEVT_PAINT, &ButtonsListCtrl::OnPaint, this); } @@ -66,23 +60,6 @@ void ButtonsListCtrl::OnPaint(wxPaintEvent&) dc.DrawRectangle(pos.x, pos.y + size.y, size.x, sz.y - size.y); } - // highlight selected mode button - - if (m_mode_sizer) { - const std::vector& mode_btns = m_mode_sizer->get_btns(); - for (int idx = 0; idx < int(mode_btns.size()); idx++) { - ModeButton* btn = mode_btns[idx]; - btn->SetBackgroundColour(btn->is_selected() ? selected_btn_bg : default_btn_bg); - - //wxPoint pos = btn->GetPosition(); - //wxSize size = btn->GetSize(); - //const wxColour& clr = btn->is_selected() ? btn_marker_color : default_btn_bg; - //dc.SetPen(clr); - //dc.SetBrush(clr); - //dc.DrawRectangle(pos.x, pos.y + size.y, size.x, sz.y - size.y); - } - } - // Draw orange bottom line dc.SetPen(btn_marker_color); @@ -90,11 +67,6 @@ void ButtonsListCtrl::OnPaint(wxPaintEvent&) dc.DrawRectangle(1, sz.y - m_line_margin, sz.x, m_line_margin); } -void ButtonsListCtrl::UpdateMode() -{ - m_mode_sizer->SetMode(Slic3r::GUI::wxGetApp().get_mode()); -} - void ButtonsListCtrl::Rescale() { int em = em_unit(this); @@ -111,16 +83,9 @@ void ButtonsListCtrl::OnColorsChanged() for (ScalableButton* btn : m_pageButtons) btn->sys_color_changed(); - m_mode_sizer->sys_color_changed(); - m_sizer->Layout(); } -void ButtonsListCtrl::UpdateModeMarkers() -{ - m_mode_sizer->update_mode_markers(); -} - void ButtonsListCtrl::SetSelection(int sel) { if (m_selection == sel) diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp index c945be3..d6e97d5 100644 --- a/src/slic3r/GUI/Notebook.hpp +++ b/src/slic3r/GUI/Notebook.hpp @@ -5,7 +5,6 @@ #include -class ModeSizer; class ScalableButton; // custom message the ButtonsListCtrl sends to its parent (Notebook) to notify a selection change: @@ -14,15 +13,13 @@ wxDECLARE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent); class ButtonsListCtrl : public wxControl { public: - ButtonsListCtrl(wxWindow* parent, bool add_mode_buttons = false); + ButtonsListCtrl(wxWindow* parent); ~ButtonsListCtrl() {} void OnPaint(wxPaintEvent&); void SetSelection(int sel); - void UpdateMode(); void Rescale(); void OnColorsChanged(); - void UpdateModeMarkers(); bool InsertPage(size_t n, const wxString& text, bool bSelect = false, const std::string& bmp_name = ""); void RemovePage(size_t n); bool SetPageImage(size_t n, const std::string& bmp_name) const; @@ -37,7 +34,6 @@ private: int m_selection {-1}; int m_btn_margin; int m_line_margin; - ModeSizer* m_mode_sizer {nullptr}; }; class Notebook: public wxBookCtrlBase @@ -47,24 +43,22 @@ public: wxWindowID winid = wxID_ANY, const wxPoint & pos = wxDefaultPosition, const wxSize & size = wxDefaultSize, - long style = 0, - bool add_mode_buttons = false) + long style = 0) { Init(); - Create(parent, winid, pos, size, style, add_mode_buttons); + Create(parent, winid, pos, size, style); } bool Create(wxWindow * parent, wxWindowID winid = wxID_ANY, const wxPoint & pos = wxDefaultPosition, const wxSize & size = wxDefaultSize, - long style = 0, - bool add_mode_buttons = false) + long style = 0) { if (!wxBookCtrlBase::Create(parent, winid, pos, size, style | wxBK_TOP)) return false; - m_bookctrl = new ButtonsListCtrl(this, add_mode_buttons); + m_bookctrl = new ButtonsListCtrl(this); wxSizer* mainSizer = new wxBoxSizer(IsVertical() ? wxVERTICAL : wxHORIZONTAL); @@ -237,11 +231,6 @@ public: ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast(m_bookctrl); } - void UpdateMode() - { - GetBtnsListCtrl()->UpdateMode(); - } - void Rescale() { GetBtnsListCtrl()->Rescale(); @@ -252,11 +241,6 @@ public: GetBtnsListCtrl()->OnColorsChanged(); } - void UpdateModeMarkers() - { - GetBtnsListCtrl()->UpdateModeMarkers(); - } - void OnNavigationKey(wxNavigationKeyEvent& event) { if (event.IsWindowChange()) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index baebea3..fbc8f70 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -2,12 +2,12 @@ #include "HintNotification.hpp" #include "GUI.hpp" -#include "ImGuiWrapper.hpp" +#include "ImGuiPureWrap.hpp" #include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" #include "ObjectDataViewModel.hpp" #include "libslic3r/Config.hpp" -#include "../Utils/PrintHost.hpp" +#include "slic3r/Utils/PrintHost.hpp" #include "libslic3r/Config.hpp" #include "format.hpp" @@ -157,7 +157,6 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init } Size cnv_size = canvas.get_canvas_size(); - ImGuiWrapper& imgui = *wxGetApp().imgui(); ImVec2 mouse_pos = ImGui::GetMousePos(); float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); bool fading_pop = false; @@ -165,14 +164,22 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init if (m_line_height != ImGui::CalcTextSize("A").y) init(); - set_next_window_size(imgui); + set_next_window_size(); // top y of window m_top_y = initial_y + m_window_height; ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - right_gap, 1.0f * (float)cnv_size.get_height() - m_top_y); - imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); - imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); + + if (wxGetApp().plater()->is_preview_shown()) { + if (Preview* preview = dynamic_cast(canvas.get_wxglcanvas_parent())) { + win_pos.y -= preview->get_moves_slider_height(); + win_pos.x -= preview->get_layers_slider_width(); + } + } + + ImGuiPureWrap::set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); + ImGuiPureWrap::set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); // find if hovered FIXME: do it only in update state? @@ -209,17 +216,17 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoFocusOnAppearing; - if (imgui.begin(name, flags)) { + if (ImGuiPureWrap::begin(name, flags)) { ImVec2 win_size = ImGui::GetWindowSize(); - render_left_sign(imgui); - render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); - render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + render_left_sign(); + render_text(win_size.x, win_size.y, win_pos.x, win_pos.y); + render_close_button(win_size.x, win_size.y, win_pos.x, win_pos.y); m_minimize_b_visible = false; if (m_multiline && m_lines_count > 3) - render_minimize_button(imgui, win_pos.x, win_pos.y); + render_minimize_button(win_pos.x, win_pos.y); } - imgui.end(); + ImGuiPureWrap::end(); if (bgrnd_color_pop) ImGui::PopStyleColor(); @@ -434,7 +441,7 @@ void NotificationManager::PopNotification::init() if (m_state == EState::Unknown) m_state = EState::Shown; } -void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) +void NotificationManager::PopNotification::set_next_window_size() { m_window_height = m_multiline ? std::max(m_lines_count, m_normal_lines_count) * m_line_height : @@ -442,7 +449,7 @@ void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& im m_window_height += 1 * m_line_height; // top and bottom } -void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::PopNotification::render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { float x_offset = m_left_indentation; int last_end = 0; @@ -472,15 +479,15 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons last_end = m_endlines[i]; if (m_text1.size() > m_endlines[i]) last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); } } //hyperlink text if (!m_multiline && m_lines_count > m_normal_lines_count) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_normal_lines_count -1) *shift_y, "[" + _u8L("More") + "]", true); + render_hypertext(x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_normal_lines_count -1) *shift_y, "[" + _u8L("More") + "]", true); } else if (!m_hypertext.empty()) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty() ? "" : " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); + render_hypertext(x_offset + ImGui::CalcTextSize((line + (line.empty() ? "" : " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); } // text2 @@ -502,13 +509,13 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons last_end = m_endlines2[i]; if (m_text2.size() > m_endlines2[i]) last_end += (m_text2[m_endlines2[i]] == '\n' || m_text2[m_endlines2[i]] == ' ' ? 1 : 0); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); } } } } -void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more) +void NotificationManager::PopNotification::render_hypertext(const float text_x, const float text_y, const std::string text, bool more) { //invisible button ImVec2 part_size = ImGui::CalcTextSize(text.c_str()); @@ -517,12 +524,12 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); - if (imgui.button(" ", part_size.x + 6, part_size.y + 10)) + if (ImGuiPureWrap::button(" ", part_size.x + 6, part_size.y + 10)) { if (more) { on_more_hypertext_click(); - set_next_window_size(imgui); + set_next_window_size(); } else if (on_text_click()) { close(); @@ -541,7 +548,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, push_style_color(ImGuiCol_Text, blue_color, m_state == EState::FadingOut, m_current_fade_opacity); ImGui::SetCursorPosX(text_x); ImGui::SetCursorPosY(text_y); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); ImGui::PopStyleColor(); //underline @@ -553,7 +560,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, } -void NotificationManager::PopNotification::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) +void NotificationManager::PopNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -577,7 +584,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { close(); } @@ -585,30 +592,30 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0))) { close(); } ImGui::PopStyleColor(5); } -void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) +void NotificationManager::PopNotification::render_left_sign() { if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { std::string text; text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } else if (m_data.level == NotificationLevel::PrintInfoNotificationLevel || m_data.level == NotificationLevel::PrintInfoShortNotificationLevel) { std::wstring text; text = ImGui::InfoMarker; ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text); } } -void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) +void NotificationManager::PopNotification::render_minimize_button(const float win_pos_x, const float win_pos_y) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); @@ -630,7 +637,7 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(m_window_width - m_line_height * 1.8f); ImGui::SetCursorPosY(m_window_height - button_size.y - 5); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { m_multiline = false; } @@ -756,11 +763,11 @@ void NotificationManager::ExportFinishedNotification::count_spaces() m_window_width = m_line_height * 25; } -void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ExportFinishedNotification::render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (m_eject_pending) { - return PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + return PopNotification::render_text(win_size_x, win_size_y, win_pos_x, win_pos_y); } float x_offset = m_left_indentation; std::string fulltext = m_text1 + m_hypertext; //+ m_text2; @@ -779,24 +786,24 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(starting_y + i * shift_y); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); //hyperlink text if ( i == 0 && !m_eject_pending) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); + render_hypertext(x_offset + ImGui::CalcTextSize(line.c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); } } } } -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) +void NotificationManager::ExportFinishedNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - PopNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + PopNotification::render_close_button(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); + render_eject_button(win_size_x, win_size_y, win_pos_x, win_pos_y); } -void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ExportFinishedNotification::render_eject_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -818,9 +825,9 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW // tooltip long time_now = wxGetLocalTime(); if (m_hover_time > 0 && m_hover_time < time_now) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - imgui.text(_u8L("Eject drive") + " " + GUI::shortkey_ctrl_prefix() + "T"); + ImGuiPureWrap::text(_u8L("Eject drive") + " " + GUI::shortkey_ctrl_prefix() + "T"); ImGui::EndTooltip(); ImGui::PopStyleColor(); // somehow the tooltip wont show if the render doesnt run twice @@ -842,7 +849,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) @@ -853,7 +860,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + if (ImGuiPureWrap::button(" ", m_line_height * 2.f, win_size.y)) { assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) @@ -906,7 +913,7 @@ void NotificationManager::ProgressBarNotification::init() m_state = EState::NotFading; } -void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ProgressBarNotification::render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // hypertext is not rendered at all. If it is needed, it needs to be added here. // m_endlines should have endline for each line and then for hypertext thus m_endlines[1] should always be in m_text1 @@ -917,14 +924,14 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img // two lines text (what doesnt fit, wont show), one line bar ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(m_line_height / 4); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGuiPureWrap::text(m_text1.substr(0, m_endlines[0]).c_str()); ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(m_line_height + m_line_height / 4); std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); if (m_has_cancel_button) - render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } else { assert(m_text1.size() >= m_endlines[0]); if (m_endlines[0] > m_text1.size()) @@ -932,15 +939,15 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img //one line text, one line bar ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(/*win_size_y / 2 - win_size_y / 6 -*/ m_line_height / 4); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGuiPureWrap::text(m_text1.substr(0, m_endlines[0]).c_str()); if (m_has_cancel_button) - render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } } -void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ProgressBarNotification::render_bar(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); @@ -958,20 +965,20 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu text = stream.str(); ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } } //------ProgressBarWithCancelNotification---------------- -void NotificationManager::ProgressBarWithCancelNotification::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) +void NotificationManager::ProgressBarWithCancelNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (m_percentage < 0.f || m_percentage >= 1.f) - render_close_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_close_button_inner(win_size_x, win_size_y, win_pos_x, win_pos_y); else - render_cancel_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button_inner(win_size_x, win_size_y, win_pos_x, win_pos_y); } -void NotificationManager::ProgressBarWithCancelNotification::render_close_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ProgressBarWithCancelNotification::render_close_button_inner(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -995,7 +1002,7 @@ void NotificationManager::ProgressBarWithCancelNotification::render_close_button ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { close(); } @@ -1003,7 +1010,7 @@ void NotificationManager::ProgressBarWithCancelNotification::render_close_button //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) { close(); } @@ -1011,7 +1018,7 @@ void NotificationManager::ProgressBarWithCancelNotification::render_close_button } -void NotificationManager::ProgressBarWithCancelNotification::render_cancel_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ProgressBarWithCancelNotification::render_cancel_button_inner(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1035,7 +1042,7 @@ void NotificationManager::ProgressBarWithCancelNotification::render_cancel_butto ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { on_cancel_button(); } @@ -1043,7 +1050,7 @@ void NotificationManager::ProgressBarWithCancelNotification::render_cancel_butto //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) { on_cancel_button(); } @@ -1060,9 +1067,9 @@ void NotificationManager::ProgressBarWithCancelNotification::on_cancel_button() } } -void NotificationManager::ProgressBarWithCancelNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ProgressBarWithCancelNotification::render_bar(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); std::string text; if (m_percentage < 0.f) { text = _u8L("ERROR"); @@ -1073,21 +1080,21 @@ void NotificationManager::ProgressBarWithCancelNotification::render_bar(ImGuiWra } ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } //------URLDownloadNotification---------------- -void NotificationManager::URLDownloadNotification::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) +void NotificationManager::URLDownloadNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (m_percentage < 0.f || m_percentage >= 1.f) { - render_close_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_close_button_inner(win_size_x, win_size_y, win_pos_x, win_pos_y); if (m_percentage >= 1.f) - render_open_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_open_button_inner(win_size_x, win_size_y, win_pos_x, win_pos_y); } else - render_pause_cancel_buttons_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_pause_cancel_buttons_inner(win_size_x, win_size_y, win_pos_x, win_pos_y); } -void NotificationManager::URLDownloadNotification::render_close_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::URLDownloadNotification::render_close_button_inner(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1111,7 +1118,7 @@ void NotificationManager::URLDownloadNotification::render_close_button_inner(ImG ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { close(); } @@ -1119,7 +1126,7 @@ void NotificationManager::URLDownloadNotification::render_close_button_inner(ImG //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) { close(); } @@ -1127,13 +1134,12 @@ void NotificationManager::URLDownloadNotification::render_close_button_inner(ImG } -void NotificationManager::URLDownloadNotification::render_pause_cancel_buttons_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::URLDownloadNotification::render_pause_cancel_buttons_inner(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - - render_cancel_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_pause_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button_inner(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_pause_button_inner(win_size_x, win_size_y, win_pos_x, win_pos_y); } -void NotificationManager::URLDownloadNotification::render_pause_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::URLDownloadNotification::render_pause_button_inner(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1153,11 +1159,11 @@ void NotificationManager::URLDownloadNotification::render_pause_button_inner(ImG button_text = (m_download_paused ? ImGui::PlayHoverButton : ImGui::PauseHoverButton); } - ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str()); + ImVec2 button_pic_size = ImGui::CalcTextSize(into_u8(button_text).c_str()); ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text, button_size.x, button_size.y)) { trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused); } @@ -1165,14 +1171,14 @@ void NotificationManager::URLDownloadNotification::render_pause_button_inner(ImG //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + if (ImGuiPureWrap::button(" ", m_line_height * 2.f, win_size.y)) { trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused); } ImGui::PopStyleColor(5); } -void NotificationManager::URLDownloadNotification::render_open_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::URLDownloadNotification::render_open_button_inner(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1192,11 +1198,11 @@ void NotificationManager::URLDownloadNotification::render_open_button_inner(ImGu button_text = ImGui::OpenHoverButton; } - ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str()); + ImVec2 button_pic_size = ImGui::CalcTextSize(into_u8(button_text).c_str()); ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder); } @@ -1204,14 +1210,14 @@ void NotificationManager::URLDownloadNotification::render_open_button_inner(ImGu //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + if (ImGuiPureWrap::button(" ", m_line_height * 2.f, win_size.y)) { trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder); } ImGui::PopStyleColor(5); } -void NotificationManager::URLDownloadNotification::render_cancel_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::URLDownloadNotification::render_cancel_button_inner(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1235,7 +1241,7 @@ void NotificationManager::URLDownloadNotification::render_cancel_button_inner(Im ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled); } @@ -1243,7 +1249,7 @@ void NotificationManager::URLDownloadNotification::render_cancel_button_inner(Im //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) { trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled); } @@ -1259,7 +1265,7 @@ void NotificationManager::URLDownloadNotification::trigger_user_action_callback( } -void NotificationManager::URLDownloadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::URLDownloadNotification::render_bar(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { auto shorten_to_line = [this](const std::string& text, bool dots) -> std::string { std::string line = text; @@ -1275,7 +1281,7 @@ void NotificationManager::URLDownloadNotification::render_bar(ImGuiWrapper& imgu return line; }; - ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); std::string text; if (m_percentage < 0.f) { text = _u8L("ERROR") + ": " + m_error_message; @@ -1288,7 +1294,7 @@ void NotificationManager::URLDownloadNotification::render_bar(ImGuiWrapper& imgu } ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); - imgui.text(shorten_to_line(text, true).c_str()); + ImGuiPureWrap::text(shorten_to_line(text, true).c_str()); } void NotificationManager::URLDownloadNotification::count_spaces() @@ -1432,7 +1438,7 @@ void NotificationManager::PrintHostUploadNotification::complete_with_warning() init(); } -void NotificationManager::PrintHostUploadNotification::render_text(ImGuiWrapper& imgui,const float win_size_x, const float win_size_y,const float win_pos_x, const float win_pos_y) +void NotificationManager::PrintHostUploadNotification::render_text(const float win_size_x, const float win_size_y,const float win_pos_x, const float win_pos_y) { // If not completed, the text rendering is very similar to progressbar notification except it doesnt use m_multiline to decide. // If completed, whole text is part of m_text_1 and is rendered by PopNotification function. @@ -1447,15 +1453,15 @@ void NotificationManager::PrintHostUploadNotification::render_text(ImGuiWrapper& // two lines text (what doesnt fit, wont show), one line bar ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(m_line_height / 4); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGuiPureWrap::text(m_text1.substr(0, m_endlines[0]).c_str()); ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(m_line_height + m_line_height / 4); std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); // uncomment only if close and stop button should be next to each other //if (m_has_cancel_button) - // render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + // render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } else { assert(m_text1.size() >= m_endlines[0]); @@ -1464,21 +1470,21 @@ void NotificationManager::PrintHostUploadNotification::render_text(ImGuiWrapper& //one line text, one line bar ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(/*win_size_y / 2 - win_size_y / 6 -*/ m_line_height / 4); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGuiPureWrap::text(m_text1.substr(0, m_endlines[0]).c_str()); if (m_has_cancel_button) - render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } } else - PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + PopNotification::render_text(win_size_x, win_size_y, win_pos_x, win_pos_y); } -void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::PrintHostUploadNotification::render_bar(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { std::string text; switch (m_uj_state) { case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS: { - ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); float uploaded = m_file_size * m_percentage; std::stringstream stream; stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; @@ -1535,35 +1541,35 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& break; } - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } -void NotificationManager::PrintHostUploadNotification::render_left_sign(ImGuiWrapper& imgui) +void NotificationManager::PrintHostUploadNotification::render_left_sign() { if (m_uj_state == UploadJobState::PB_ERROR) { std::string text; text = ImGui::ErrorMarker; ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } else if (m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) { std::string text; text = ImGui::WarningMarker; ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } } -void NotificationManager::PrintHostUploadNotification::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) +void NotificationManager::PrintHostUploadNotification::render_close_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (m_has_cancel_button) - render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); else - ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_close_button(win_size_x, win_size_y, win_pos_x, win_pos_y); } -void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::PrintHostUploadNotification::render_cancel_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1585,9 +1591,9 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu // tooltip long time_now = wxGetLocalTime(); if (m_hover_time > 0 && m_hover_time < time_now) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); + ImGuiPureWrap::text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); ImGui::EndTooltip(); ImGui::PopStyleColor(); } @@ -1598,7 +1604,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { wxGetApp().printhost_job_queue().cancel(m_job_id - 1); } @@ -1606,7 +1612,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) { wxGetApp().printhost_job_queue().cancel(m_job_id - 1); } @@ -1634,9 +1640,9 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu // tooltip long time_now = wxGetLocalTime(); if (m_hover_time > 0 && m_hover_time < time_now) { - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiPureWrap::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); - imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); + ImGuiPureWrap::text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); ImGui::EndTooltip(); ImGui::PopStyleColor(); } @@ -1650,7 +1656,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { wxGetApp().printhost_job_queue().cancel(m_job_id - 1); } @@ -1658,7 +1664,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + if (ImGuiPureWrap::button(" ", m_line_height * 2.f, win_size.y)) { wxGetApp().printhost_job_queue().cancel(m_job_id - 1); } @@ -1711,7 +1717,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty } // Uncomment to have different icon for every type of info, otherwise it will have standart cube with i. /* -void NotificationManager::UpdatedItemsInfoNotification::render_left_sign(ImGuiWrapper& imgui) +void NotificationManager::UpdatedItemsInfoNotification::render_left_sign() { std::string text; InfoItemType type = (m_types_and_counts.empty() ? InfoItemType::CustomSupports : m_types_and_counts[0].first); @@ -1725,7 +1731,7 @@ void NotificationManager::UpdatedItemsInfoNotification::render_left_sign(ImGuiWr } ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); - imgui.text(text.c_str()); + ImGuiPureWrap::text(text.c_str()); } */ //------SlicingProgressNotification @@ -1864,41 +1870,41 @@ bool NotificationManager::SlicingProgressNotification::update_state(bool paused set_progress_state(SlicingProgressState::SP_NO_SLICING); return ret; } -void NotificationManager::SlicingProgressNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::SlicingProgressNotification::render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (m_sp_state == SlicingProgressState::SP_PROGRESS /*|| (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed)*/) { - ProgressBarNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_text(win_size_x, win_size_y, win_pos_x, win_pos_y); /* // enable for hypertext during slicing (correct call of export_enabled needed) if (m_multiline) { // two lines text, one line bar ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(m_line_height / 4); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGuiPureWrap::text(m_text1.substr(0, m_endlines[0]).c_str()); ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(m_line_height + m_line_height / 4); std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); - render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height + m_line_height / 4, m_hypertext); + render_hypertext(m_left_indentation + text_size.x + 4, m_line_height + m_line_height / 4, m_hypertext); } if (m_has_cancel_button) - render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } else { //one line text, one line bar ImGui::SetCursorPosX(m_left_indentation); ImGui::SetCursorPosY(m_line_height / 4); std::string line = m_text1.substr(0, m_endlines[0]); - imgui.text(line.c_str()); + ImGuiPureWrap::text(line.c_str()); if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); - render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height / 4, m_hypertext); + render_hypertext(m_left_indentation + text_size.x + 4, m_line_height / 4, m_hypertext); } if (m_has_cancel_button) - render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } */ } else if (m_sp_state == SlicingProgressState::SP_COMPLETED && m_sidebar_collapsed) { @@ -1914,32 +1920,32 @@ void NotificationManager::SlicingProgressNotification::render_text(ImGuiWrapper& cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(cursor_y); - imgui.text(m_print_info.c_str()); + ImGuiPureWrap::text(m_print_info.c_str()); cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; } ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(cursor_y); - imgui.text(m_text1.c_str()); + ImGuiPureWrap::text(m_text1.c_str()); if (m_sidebar_collapsed) - render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); + render_hypertext(x_offset + text1_size.x + 4, cursor_y, m_hypertext); } else { - PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + PopNotification::render_text(win_size_x, win_size_y, win_pos_x, win_pos_y); } } -void NotificationManager::SlicingProgressNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::SlicingProgressNotification::render_bar(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { if (m_sp_state != SlicingProgressState::SP_PROGRESS) { return; } - ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } -void NotificationManager::SlicingProgressNotification::render_hypertext(ImGuiWrapper& imgui,const float text_x, const float text_y, const std::string text, bool more) +void NotificationManager::SlicingProgressNotification::render_hypertext(const float text_x, const float text_y, const std::string text, bool more) { if (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed) return; - ProgressBarNotification::render_hypertext(imgui, text_x, text_y, text, more); + ProgressBarNotification::render_hypertext(text_x, text_y, text, more); } -void NotificationManager::SlicingProgressNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::SlicingProgressNotification::render_cancel_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -1963,7 +1969,7 @@ void NotificationManager::SlicingProgressNotification::render_cancel_button(ImGu ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { on_cancel_button(); } @@ -1971,7 +1977,7 @@ void NotificationManager::SlicingProgressNotification::render_cancel_button(ImGu //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) { on_cancel_button(); } @@ -1982,11 +1988,11 @@ void NotificationManager::SlicingProgressNotification::render_cancel_button(ImGu ImGui::PopStyleColor(); } -void NotificationManager::SlicingProgressNotification::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) +void NotificationManager::SlicingProgressNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // Do not render close button while showing progress - cancel button is rendered instead if (m_sp_state != SlicingProgressState::SP_PROGRESS) { - ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_close_button(win_size_x, win_size_y, win_pos_x, win_pos_y); } } //------ProgressIndicatorNotification------- @@ -2061,7 +2067,7 @@ bool NotificationManager::ProgressIndicatorNotification::update_state(bool pause return ret; } -void NotificationManager::ProgressIndicatorNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +void NotificationManager::ProgressIndicatorNotification::render_cancel_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); ImVec2 win_pos(win_pos_x, win_pos_y); @@ -2085,7 +2091,7 @@ void NotificationManager::ProgressIndicatorNotification::render_cancel_button(Im ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + if (ImGuiPureWrap::button(button_text.c_str(), button_size.x, button_size.y)) { on_cancel_button(); } @@ -2093,18 +2099,18 @@ void NotificationManager::ProgressIndicatorNotification::render_cancel_button(Im //invisible large button ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); ImGui::SetCursorPosY(0); - if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + if (ImGuiPureWrap::button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) { on_cancel_button(); } ImGui::PopStyleColor(5); } -void NotificationManager::ProgressIndicatorNotification::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) +void NotificationManager::ProgressIndicatorNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { // Do not render close button while showing progress - cancel button is rendered instead if (m_percentage >= 1.0f) { - ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + ProgressBarNotification::render_close_button(win_size_x, win_size_y, win_pos_x, win_pos_y); } } //------NotificationManager-------- diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 7e8616c..571c626 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -32,7 +32,6 @@ wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClicke using CancelFn = std::function; class GLCanvas3D; -class ImGuiWrapper; enum class InfoItemType; enum class NotificationType @@ -125,7 +124,18 @@ enum class NotificationType // MacOS specific - PS comes forward even when downloader is not allowed URLNotRegistered, // Config file was detected during startup, open wifi config dialog via hypertext - WifiConfigFileDetected + WifiConfigFileDetected, + // Info abouty successful login or logout + UserAccountID, + // When in Connect tab "set as current" is selected and selected presets in plater changes + SelectPrinterFromConnect, + SelectFilamentFromConnect, + // Debug notification for connect communication + QIDIConnectPrinters, + // Notification that bed temperatures for the used filaments differ significantly. + BedTemperaturesDiffer, + // Notification that shrinkage compensations for the used filaments differ. + ShrinkageCompensationsDiffer, }; class NotificationManager @@ -359,21 +369,17 @@ private: // Call after every size change virtual void init(); // Calculetes correct size but not se it in imgui! - virtual void set_next_window_size(ImGuiWrapper& imgui); - virtual void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + virtual void set_next_window_size(); + virtual void render_text(const float win_size_x, const float win_size_y, const float win_pos_x , const float win_pos_y); - virtual void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + virtual void render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x , const float win_pos_y); - virtual void render_hypertext(ImGuiWrapper& imgui, - const float text_x, const float text_y, + virtual void render_hypertext(const float text_x, const float text_y, const std::string text, bool more = false); // Left sign could be error or warning sign - virtual void render_left_sign(ImGuiWrapper& imgui); - virtual void render_minimize_button(ImGuiWrapper& imgui, - const float win_pos_x, const float win_pos_y); + virtual void render_left_sign(); + virtual void render_minimize_button(const float win_pos_x, const float win_pos_y); // Hypertext action, returns true if notification should close. // Action is stored in NotificationData::callback as std::function virtual bool on_text_click(); @@ -476,18 +482,15 @@ private: float get_percentage() const { return m_percentage; } protected: virtual void init() override; - virtual void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + virtual void render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - virtual void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + virtual void render_bar( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) ; - virtual void render_cancel_button(ImGuiWrapper& imgui, + virtual void render_cancel_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) {} - void render_minimize_button(ImGuiWrapper& imgui, - const float win_pos_x, const float win_pos_y) override {} + void render_minimize_button(const float win_pos_x, const float win_pos_y) override {} float m_percentage {0.0f}; //B64 int m_waittime{0}; @@ -510,17 +513,13 @@ private: void set_cancel_callback(std::function cancel_callback) { m_cancel_callback = cancel_callback; } protected: - void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_close_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_close_button_inner(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_close_button_inner( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_cancel_button_inner(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_cancel_button_inner( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_bar( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; void on_cancel_button(); @@ -553,26 +552,20 @@ private: void set_error_message(const std::string& message) { m_error_message = message; } bool compare_text(const std::string& text) const override { return false; }; protected: - void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_close_button_inner(ImGuiWrapper& imgui, + void render_close_button_inner( const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + void render_pause_cancel_buttons_inner( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_pause_cancel_buttons_inner(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_open_button_inner( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_open_button_inner(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_cancel_button_inner( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_cancel_button_inner(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_pause_button_inner( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_pause_button_inner(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); - void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_bar( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; void trigger_user_action_callback(DownloaderUserAction action); @@ -627,19 +620,17 @@ private: void init() override; void count_spaces() override; bool push_background_color() override; - virtual void render_text(ImGuiWrapper& imgui, + virtual void render_text( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_bar( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - virtual void render_close_button(ImGuiWrapper& imgui, + virtual void render_close_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_cancel_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_cancel_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_left_sign(ImGuiWrapper& imgui) override; + void render_left_sign() override; void generate_text(); void on_more_hypertext_click() override { ProgressBarNotification::on_more_hypertext_click(); m_more_hypertext_used = true; } @@ -701,18 +692,14 @@ private: void set_export_possible(bool b) { m_export_possible = b; } protected: void init() override; - void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; + void render_bar( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_cancel_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_cancel_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_hypertext(ImGuiWrapper& imgui, - const float text_x, const float text_y, + void render_hypertext( const float text_x, const float text_y, const std::string text, bool more = false) override ; void on_cancel_button(); @@ -759,11 +746,9 @@ private: CancelFn m_cancel_callback { nullptr }; ProgressIndicatorState m_progress_state { ProgressIndicatorState::PIS_HIDDEN }; - void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_cancel_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_cancel_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; void on_cancel_button() { if (m_cancel_callback) m_cancel_callback(); } }; @@ -787,17 +772,14 @@ private: protected: // Reserves space on right for more buttons void count_spaces() override; - void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; // Renders also button to open directory with exported path and eject removable media - void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_eject_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, + void render_eject_button( const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override + void render_minimize_button(const float win_pos_x, const float win_pos_y) override { m_minimize_b_visible = false; } bool on_text_click() override; void on_eject_click(); @@ -823,7 +805,7 @@ private: PopNotification::close(); } protected: - //void render_left_sign(ImGuiWrapper& imgui) override; + //void render_left_sign() override; std::vector> m_types_and_counts; }; diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 6367c8c..e02ae02 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -246,9 +246,7 @@ void OG_CustomCtrl::OnMotion(wxMouseEvent& event) wxString language = wxGetApp().app_config->get("translation_language"); - //B21 - //const bool suppress_hyperlinks = get_app_config()->get_bool("suppress_hyperlinks"); - const bool suppress_hyperlinks = true; + const bool suppress_hyperlinks = get_app_config()->get_bool("suppress_hyperlinks"); for (CtrlLine& line : ctrl_lines) { line.is_focused = is_point_in_rect(pos, line.rect_label); @@ -334,6 +332,7 @@ void OG_CustomCtrl::OnLeftDown(wxMouseEvent& event) event.Skip(); return; } + if (opt_idx < line.rects_edit_icon.size() && is_point_in_rect(pos, line.rects_edit_icon[opt_idx])) { if (Field* field = opt_group->get_field(opt_key)) field->on_edit_value(); @@ -579,11 +578,11 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) } wxCoord h_pos = draw_mode_bmp(dc, v_pos); + Field* field = ctrl->opt_group->get_field(og_line.get_options().front().opt_id); - //B21 - //const bool suppress_hyperlinks = get_app_config()->get_bool("suppress_hyperlinks"); - const bool suppress_hyperlinks = true; + + const bool suppress_hyperlinks = get_app_config()->get_bool("suppress_hyperlinks"); if (draw_just_act_buttons) { if (field) { const wxPoint pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap(), field->undo_bitmap(), field->blink()); @@ -594,7 +593,6 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) return; } - if (og_line.near_label_widget_win) h_pos += og_line.near_label_widget_win->GetSize().x + ctrl->m_h_gap; diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 668c3b7..ffe9338 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -267,7 +267,7 @@ class ObjectDataViewModel :public wxDataViewModel { std::vector m_objects; std::vector m_volume_bmps; - std::vector m_text_volume_bmps; + std::vector m_text_volume_bmps; std::vector m_svg_volume_bmps; std::map m_info_bmps; wxBitmapBundle m_empty_bmp; diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index d85f046..de0ce6f 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -2,9 +2,9 @@ #include "OpenGLManager.hpp" #include "GUI.hpp" -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES #include "GUI_Init.hpp" -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES #include "I18N.hpp" #include "3DScene.hpp" #include "format.hpp" @@ -123,8 +123,13 @@ void OpenGLManager::GLInfo::detect() const float* max_anisotropy = const_cast(&m_max_anisotropy); glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); } + if (!GLEW_ARB_compatibility) *const_cast(&m_core_profile) = true; + + int* samples = const_cast(&m_samples); + glsafe(::glGetIntegerv(GL_SAMPLES, samples)); + *const_cast(&m_detected) = true; } @@ -139,16 +144,16 @@ static Semver parse_version_string(const std::string& version) if (tokens.empty()) return Semver::invalid(); -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES const std::string version_container = (tokens.size() > 1 && boost::istarts_with(tokens[1], "ES")) ? tokens[2] : tokens[0]; -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES std::vector numbers; -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES boost::split(numbers, version_container, boost::is_any_of("."), boost::token_compress_on); #else boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES unsigned int gl_major = 0; unsigned int gl_minor = 0; @@ -196,22 +201,23 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const out << h2_start << "OpenGL installation" << h2_end << line_end; out << b_start << "GL version: " << b_end << m_version << " (" << m_version_string << ")" << line_end; -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES out << b_start << "Profile: " << b_end << (is_core_profile() ? "Core" : "Compatibility") << line_end; -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES out << b_start << "Vendor: " << b_end << m_vendor << line_end; out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; out << b_start << "Textures compression: " << b_end << (are_compressed_textures_supported() ? "Enabled" : "Disabled") << line_end; + out << b_start << "Mutisampling: " << b_end << (can_multisample() ? "Enabled (" + std::to_string(m_samples) + " samples)" : "Disabled") << line_end; { -#if ENABLE_GL_CORE_PROFILE - std::vector extensions_list = get_extensions_list(); -#else +#if SLIC3R_OPENGL_ES const std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, ""); std::vector extensions_list; boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_on); -#endif // ENABLE_GL_CORE_PROFILE +#else + std::vector extensions_list = get_extensions_list(); +#endif // SLIC3R_OPENGL_ES if (!extensions_list.empty()) { if (for_github) @@ -232,7 +238,7 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const return out.str(); } -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES std::vector OpenGLManager::GLInfo::get_extensions_list() const { std::vector ret; @@ -255,7 +261,7 @@ std::vector OpenGLManager::GLInfo::get_extensions_list() const return ret; } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES OpenGLManager::GLInfo OpenGLManager::s_gl_info; bool OpenGLManager::s_compressed_textures_supported = false; @@ -285,7 +291,7 @@ OpenGLManager::~OpenGLManager() #endif //__APPLE__ } -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES #ifdef _WIN32 static void APIENTRY CustomGLDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char* message, const void* userParam) #else @@ -329,26 +335,22 @@ static void CustomGLDebugOutput(GLenum source, GLenum type, unsigned int id, GLe out += "]:\n"; std::cout << out << "(" << id << "): " << message << "\n\n"; } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES bool OpenGLManager::init_gl() { if (!m_gl_initialized) { -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - glewExperimental = true; -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - GLenum err = glewInit(); + glewExperimental = true; + GLenum err = glewInit(); if (err != GLEW_OK) { BOOST_LOG_TRIVIAL(error) << "Unable to init glew library: " << glewGetErrorString(err); return false; } -#if ENABLE_GL_CORE_PROFILE do { // glewInit() generates an OpenGL GL_INVALID_ENUM error err = ::glGetError(); } while (err != GL_NO_ERROR); -#endif // ENABLE_GL_CORE_PROFILE m_gl_initialized = true; @@ -364,27 +366,22 @@ bool OpenGLManager::init_gl() else s_framebuffers_type = EFramebufferType::Unknown; -#if ENABLE_OPENGL_ES - bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0); -#elif ENABLE_GL_CORE_PROFILE - const bool valid_version = s_gl_info.is_version_greater_or_equal_to(3, 2); +#if SLIC3R_OPENGL_ES + bool valid_version = s_gl_info.is_version_greater_or_equal_to(3, 0); #else - bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0); -#endif // ENABLE_OPENGL_ES + const bool valid_version = s_gl_info.is_version_greater_or_equal_to(3, 2); +#endif // SLIC3R_OPENGL_ES if (!valid_version) { // Complain about the OpenGL version. wxString message = format_wxstr( -#if ENABLE_OPENGL_ES - _L("QIDISlicer requires OpenGL ES 2.0 capable graphics driver to run correctly, \n" - "while OpenGL version %s, render %s, vendor %s was detected."), s_gl_info.get_version_string(), s_gl_info.get_renderer(), s_gl_info.get_vendor()); -#elif ENABLE_GL_CORE_PROFILE - _L("QIDISlicer requires OpenGL %s capable graphics driver to run correctly, \n" - "while OpenGL version %s, render %s, vendor %s was detected."), (s_gl_info.is_core_profile() ? "3.3" : "2.0"), s_gl_info.get_version_string(), s_gl_info.get_renderer(), s_gl_info.get_vendor()); +#if SLIC3R_OPENGL_ES + _L("QIDISlicer requires OpenGL ES 3.0 capable graphics driver to run correctly, \n" + "while OpenGL version %s, renderer %s, vendor %s was detected."), s_gl_info.get_version_string(), s_gl_info.get_renderer(), s_gl_info.get_vendor()); #else - _L("QIDISlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" - "while OpenGL version %s, render %s, vendor %s was detected."), s_gl_info.get_version_string(), s_gl_info.get_renderer(), s_gl_info.get_vendor()); -#endif // ENABLE_OPENGL_ES + _L("QIDISlicer requires OpenGL 3.2 capable graphics driver to run correctly,\n" + "while OpenGL version %s, renderer %s, vendor %s was detected."), s_gl_info.get_version_string(), s_gl_info.get_renderer(), s_gl_info.get_vendor()); +#endif // SLIC3R_OPENGL_ES message += "\n"; message += _L("You may need to update your graphics card driver."); #ifdef _WIN32 @@ -401,7 +398,7 @@ bool OpenGLManager::init_gl() wxString message = format_wxstr(_L("Unable to load the following shaders:\n%s"), error); wxMessageBox(message, wxString("QIDISlicer - ") + _L("Error loading shaders"), wxOK | wxICON_ERROR); } -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES if (m_debug_enabled && s_gl_info.is_version_greater_or_equal_to(4, 3) && GLEW_KHR_debug) { ::glEnable(GL_DEBUG_OUTPUT); ::glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); @@ -409,7 +406,7 @@ bool OpenGLManager::init_gl() ::glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); std::cout << "Enabled OpenGL debug output\n"; } -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES } #ifdef _WIN32 @@ -435,19 +432,19 @@ bool OpenGLManager::init_gl() return true; } -#if ENABLE_GL_CORE_PROFILE +#if SLIC3R_OPENGL_ES +wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) +#else wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version, bool enable_compatibility_profile, bool enable_debug) -#else -wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) -#endif // ENABLE_GL_CORE_PROFILE +#endif // SLIC3R_OPENGL_ES { if (m_context == nullptr) { -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES wxGLContextAttrs attrs; attrs.PlatformDefaults().ES2().MajorVersion(2).EndList(); m_context = new wxGLContext(&canvas, nullptr, &attrs); -#elif ENABLE_GL_CORE_PROFILE +#else m_debug_enabled = enable_debug; const int gl_major = required_opengl_version.first; @@ -517,9 +514,7 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) // if no valid context was created use the default one m_context = new wxGLContext(&canvas, nullptr, &attrs); } -#else - m_context = new wxGLContext(&canvas); -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES #ifdef __APPLE__ // Part of hack to remove crash when closing the application on OSX 10.9.5 when building against newer wxWidgets @@ -531,75 +526,39 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) return m_context; } -wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent) +wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent, bool enable_auto_aa_samples) { -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES wxGLAttributes attribList; - attribList.PlatformDefaults().RGBA().DoubleBuffer().MinRGBA(8, 8, 8, 8).Depth(24).SampleBuffers(1).Samplers(4).EndList(); + s_multisample = EMultisampleState::Disabled; + // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, + // at least on some platforms. + if (platform_flavor() != PlatformFlavor::LinuxOnChromium) { + for (int i = enable_auto_aa_samples ? 16 : 4; i >= 4; i /= 2) { + attribList.Reset(); + attribList.PlatformDefaults().RGBA().DoubleBuffer().MinRGBA(8, 8, 8, 8).Depth(24).SampleBuffers(1).Samplers(i); #ifdef __APPLE__ - // on MAC the method RGBA() has no effect - attribList.SetNeedsARB(true); + // on MAC the method RGBA() has no effect + attribList.SetNeedsARB(true); #endif // __APPLE__ -#else - int attribList[] = { - WX_GL_RGBA, - WX_GL_DOUBLEBUFFER, - // RGB channels each should be allocated with 8 bit depth. One should almost certainly get these bit depths by default. - WX_GL_MIN_RED, 8, - WX_GL_MIN_GREEN, 8, - WX_GL_MIN_BLUE, 8, - // Requesting an 8 bit alpha channel. Interestingly, the NVIDIA drivers would most likely work with some alpha plane, but glReadPixels would not return - // the alpha channel on NVIDIA if not requested when the GL context is created. - WX_GL_MIN_ALPHA, 8, - WX_GL_DEPTH_SIZE, 24, - WX_GL_SAMPLE_BUFFERS, GL_TRUE, - WX_GL_SAMPLES, 4, - 0 - }; -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - - if (s_multisample == EMultisampleState::Unknown) { - detect_multisample(attribList); -// // debug output -// std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; + attribList.EndList(); + if (wxGLCanvas::IsDisplaySupported(attribList)) { + s_multisample = EMultisampleState::Enabled; + break; + } + } } - if (!can_multisample()) -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - { + if (s_multisample != EMultisampleState::Enabled) { attribList.Reset(); - attribList.PlatformDefaults().RGBA().DoubleBuffer().MinRGBA(8, 8, 8, 8).Depth(24).EndList(); + attribList.PlatformDefaults().RGBA().DoubleBuffer().MinRGBA(8, 8, 8, 8).Depth(24); #ifdef __APPLE__ // on MAC the method RGBA() has no effect attribList.SetNeedsARB(true); #endif // __APPLE__ + attribList.EndList(); } return new wxGLCanvas(&parent, attribList, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); -#else - attribList[12] = 0; - - return new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES -} - -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES -void OpenGLManager::detect_multisample(const wxGLAttributes& attribList) -#else -void OpenGLManager::detect_multisample(int* attribList) -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES -{ - int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; - bool enable_multisample = wxVersion >= 30003; - s_multisample = - enable_multisample && - // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, - // at least on some platforms. - platform_flavor() != PlatformFlavor::LinuxOnChromium && - wxGLCanvas::IsDisplaySupported(attribList) - ? EMultisampleState::Enabled : EMultisampleState::Disabled; - // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows - // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample"); } } // namespace GUI diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index cd941aa..36ad3ea 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -6,9 +6,7 @@ class wxWindow; class wxGLCanvas; class wxGLContext; -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES class wxGLAttributes; -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES namespace Slic3r { namespace GUI { @@ -30,6 +28,7 @@ public: bool m_core_profile{ false }; int m_max_tex_size{ 0 }; float m_max_anisotropy{ 0.0f }; + int m_samples{ 0 }; std::string m_version_string; Semver m_version = Semver::invalid(); @@ -53,11 +52,11 @@ public: bool is_mesa() const; bool is_es() const { -#if ENABLE_OPENGL_ES +#if SLIC3R_OPENGL_ES return true; #else return false; -#endif // ENABLE_OPENGL_ES +#endif // SLIC3R_OPENGL_ES } int get_max_tex_size() const; @@ -70,9 +69,9 @@ public: // Otherwise HTML formatted for the system info dialog. std::string to_string(bool for_github) const; -#if ENABLE_GL_CORE_PROFILE +#if !SLIC3R_OPENGL_ES std::vector get_extensions_list() const; -#endif // ENABLE_GL_CORE_PROFILE +#endif // !SLIC3R_OPENGL_ES private: void detect() const; @@ -116,11 +115,11 @@ public: ~OpenGLManager(); bool init_gl(); -#if ENABLE_GL_CORE_PROFILE - wxGLContext* init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version, bool enable_compatibility_profile, bool enable_debug); -#else +#if SLIC3R_OPENGL_ES wxGLContext* init_glcontext(wxGLCanvas& canvas); -#endif // ENABLE_GL_CORE_PROFILE +#else + wxGLContext* init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version, bool enable_compatibility_profile, bool enable_debug); +#endif // SLIC3R_OPENGL_ES GLShaderProgram* get_shader(const std::string& shader_name) { return m_shaders_manager.get_shader(shader_name); } GLShaderProgram* get_current_shader() { return m_shaders_manager.get_current_shader(); } @@ -129,16 +128,9 @@ public: static bool can_multisample() { return s_multisample == EMultisampleState::Enabled; } static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); } static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } - static wxGLCanvas* create_wxglcanvas(wxWindow& parent); + static wxGLCanvas* create_wxglcanvas(wxWindow& parent, bool enable_auto_aa_samples); static const GLInfo& get_gl_info() { return s_gl_info; } static bool force_power_of_two_textures() { return s_force_power_of_two_textures; } - -private: -#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES - static void detect_multisample(const wxGLAttributes& attribList); -#else - static void detect_multisample(int* attribList); -#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES }; } // namespace GUI diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index c5f60c7..5c6680a 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -1,10 +1,8 @@ #include "OptionsGroup.hpp" -#include "ConfigExceptions.hpp" -#include "Plater.hpp" +#include "Search.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" #include "OG_CustomCtrl.hpp" -#include "MsgDialog.hpp" #include "format.hpp" #include @@ -12,6 +10,7 @@ #include #include #include +#include "slic3r/GUI/Search.hpp" #include "libslic3r/Exception.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/AppConfig.hpp" @@ -71,7 +70,8 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co m_fields.emplace(id, SpinCtrl::Create(this->ctrl_parent(), opt, id)); break; case coEnum: - m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); + case coEnums: + m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); break; case coPoints: m_fields.emplace(id, PointCtrl::Create(this->ctrl_parent(), opt, id)); @@ -103,6 +103,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co }; field->set_edit_tooltip(_L("Edit Custom G-code")); } + field->m_back_to_initial_value = [this](std::string opt_id) { if (!m_disabled) this->back_to_initial_value(opt_id); @@ -125,6 +126,135 @@ OptionsGroup::OptionsGroup( wxWindow* _parent, const wxString& title, { } +// opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) +void OptionsGroup::change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) +{ + 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); + return; + } + + const ConfigOptionDef* opt_def = config.def()->get(opt_key); + switch (opt_def->type) { + case coFloatOrPercent: { + 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) + config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent)); + break; } + case coPercent: + config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast(value))); + break; + case coFloat: { + double& val = config.opt_float(opt_key); + val = boost::any_cast(value); + 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) + ConfigOptionFloatsOrPercents* vec_new = new ConfigOptionFloatsOrPercents({ {val, percent} }); + config.option(opt_key)->set_at(vec_new, opt_index, opt_index); + break; + } + case coPercents: { + ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast(value) }; + config.option(opt_key)->set_at(vec_new, 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); + break; + } + case coString: + config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast(value))); + break; + case coStrings: { + if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "gcode_substitutions") { + config.option(opt_key)->values = + boost::any_cast>(value); + } + else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0) { + std::string str = boost::any_cast(value); + std::vector values{}; + if (!str.empty()) { + if (str.back() == ';') str.pop_back(); + // Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values. + // Currently used for the post_process config value only. + boost::split(values, str, boost::is_any_of(";")); + if (values.size() == 1 && values[0] == "") + values.resize(0); + } + 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); + } + } + break; + case coBool: + 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); + break; } + case coInt: { + //config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast(value))); + int& val_new = config.opt_int(opt_key); + val_new = 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); + } + break; + case coEnum: { + auto* opt = opt_def->default_value.get()->clone(); + opt->setInt(boost::any_cast(value)); + config.set_key_value(opt_key, opt); + } + break; + case coEnums: { + ConfigOptionEnumsGeneric* vec_new = new ConfigOptionEnumsGeneric(1, boost::any_cast(value));; + config.option(opt_key)->set_at(vec_new, opt_index, 0); + break; } + case coPoints: { + if (opt_key == "bed_shape") { + 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); + } + break; + case coNone: + break; + default: + break; + } + } + catch (const std::exception& e) + { + wxLogError(format_wxstr("Internal error when changing value for %1%: %2%", opt_key, e.what())); + } +} + Option::Option(const ConfigOptionDef& _opt, t_config_option_key id) : opt(_opt), opt_id(id) { if (!opt.tooltip.empty()) { @@ -133,7 +263,9 @@ Option::Option(const ConfigOptionDef& _opt, t_config_option_key id) : opt(_opt), tooltip = _L("Unavailable for this method.") + "\n"; tooltip += _(opt.tooltip); - edit_tooltip(tooltip); + // edit tooltip : change Slic3r to SLIC3R_APP_KEY + // Temporary workaround for localization + tooltip.Replace("Slic3r", SLIC3R_APP_KEY, true); opt.tooltip = into_u8(tooltip); } @@ -558,8 +690,8 @@ void OptionsGroup::clear_fields_except_of(const std::vector left_fi } void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) { - if (m_on_change != nullptr) - m_on_change(opt_id, value); + if (on_change != nullptr) + on_change(opt_id, value); } Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/) @@ -572,8 +704,10 @@ Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index std::pair pair(opt_key, opt_index); m_opt_map.emplace(opt_id, pair); - if (m_use_custom_ctrl) // fill group and category values just for options from Settings Tab - wxGetApp().sidebar().get_searcher().add_key(opt_id, static_cast(this->config_type()), title, this->config_category()); + if (m_use_custom_ctrl) { + // fill group and category values just for options from Settings Tab + wxGetApp().searcher().add_key(opt_id, static_cast(this->config_type()), title, this->config_category()); + } return Option(*m_config->def()->get(opt_key), opt_id); } @@ -601,18 +735,18 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key) { - if (m_get_initial_config == nullptr) + if (get_initial_config == nullptr) return; - back_to_config_value(m_get_initial_config(), opt_key); + back_to_config_value(get_initial_config(), opt_key); } void ConfigOptionsGroup::back_to_sys_value(const std::string& opt_key) { - if (m_get_sys_config == nullptr) + if (get_sys_config == nullptr) return; if (!have_sys_config()) return; - back_to_config_value(m_get_sys_config(), opt_key); + back_to_config_value(get_sys_config(), opt_key); } void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key) @@ -654,8 +788,8 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, void ConfigOptionsGroup::on_kill_focus(const std::string& opt_key) { - if (m_fill_empty_value) - m_fill_empty_value(opt_key); + if (fill_empty_value) + fill_empty_value(opt_key); else reload_config(); } @@ -681,7 +815,7 @@ void ConfigOptionsGroup::Hide() void ConfigOptionsGroup::Show(const bool show) { - sizer->ShowItems(show); + if (sizer) sizer->ShowItems(show); #if 0//#ifdef __WXGTK__ m_panel->Show(show); m_grid_sizer->Show(show); @@ -927,8 +1061,8 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config } break; case coString: { - ret = from_u8(config.opt_string(opt_key)); - break; + ret = from_u8(config.opt_string(opt_key)); + break; } case coStrings: if (opt_key == "compatible_printers" || opt_key == "compatible_prints" || opt_key == "gcode_substitutions") { @@ -966,6 +1100,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config case coEnum: ret = config.option(opt_key)->getInt(); break; + case coEnums: + ret = config.option(opt_key)->getInts()[idx]; + break; case coPoints: //Y20 //B52 if (opt_key == "bed_shape" || opt_key == "bed_exclude_area") @@ -1014,7 +1151,7 @@ std::pair ConfigOptionsGroup::get_custom_ctrl_with_blinki void ConfigOptionsGroup::change_opt_value(const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { - Slic3r::GUI::change_opt_value(const_cast(*m_config), opt_key, value, opt_index); + OptionsGroup::change_opt_value(const_cast(*m_config), opt_key, value, opt_index); if (m_modelconfig) m_modelconfig->touch(); } @@ -1032,9 +1169,7 @@ wxString OptionsGroup::get_url(const std::string& path_end) bool OptionsGroup::launch_browser(const std::string& path_end) { - //B21 - //return wxGetApp().open_browser_with_warning_dialog(OptionsGroup::get_url(path_end), wxGetApp().mainframe->m_tabpanel); - return false; + return wxGetApp().open_browser_with_warning_dialog(OptionsGroup::get_url(path_end), wxGetApp().tab_panel()); } // list of options, which doesn't have a related filed diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 65e63b6..125f225 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -111,13 +111,15 @@ public: OG_CustomCtrl* custom_ctrl{ nullptr }; int ctrl_horiz_alignment{ wxALIGN_LEFT}; column_t extra_column {nullptr}; - t_change m_on_change { nullptr }; + t_change on_change { nullptr }; + t_change m_on_change { nullptr }; // To be called when the field loses focus, to assign a new initial value to the field. // Used by the relative position / rotation / scale manipulation fields of the Object Manipulation UI. - t_kill_focus m_fill_empty_value { nullptr }; - std::function m_get_initial_config{ nullptr }; - std::function m_get_sys_config{ nullptr }; - std::function have_sys_config{ nullptr }; + t_kill_focus fill_empty_value { nullptr }; + + std::function get_initial_config{ nullptr }; + std::function get_sys_config { nullptr }; + std::function have_sys_config { nullptr }; std::function rescale_extra_column_item { nullptr }; std::function rescale_near_label_widget { nullptr }; @@ -247,6 +249,9 @@ public: static wxString get_url(const std::string& path_end); static bool launch_browser(const std::string& path_end); static bool is_option_without_field(const std::string& opt_key); + + // Change option value in config + static void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); }; class ConfigOptionsGroup: public OptionsGroup { diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index ce78c71..f13d492 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,10 @@ #include #include #include +#if wxUSE_SECRETSTORE +#include +#endif +#include #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" @@ -28,6 +33,7 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" +#include "../Utils/ServiceConfig.hpp" #include "RemovableDriveManager.hpp" #include "BitmapCache.hpp" #include "BonjourDialog.hpp" @@ -746,11 +752,20 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) msg_wingow.ShowModal(); return; } - if (m_exit_host.find(now_host) != m_exit_host.end()) { - MessageDialog msg_wingow(nullptr, _L("A device with the same host (IP or URL) already exists, please re-enter."), "", - wxICON_WARNING | wxOK); - msg_wingow.ShowModal(); - return; + if (now_host.find(":") != std::string::npos) + { + size_t pos = now_host.find(":"); + now_host = now_host.substr(0, pos); + } + for(auto exit_host : m_exit_host) + { + if(exit_host.find(now_host) != std::string::npos) + { + MessageDialog msg_wingow(nullptr, _L("A device with the same host (IP or URL) already exists, please re-enter."), "", + wxICON_WARNING | wxOK); + msg_wingow.ShowModal(); + return; + } } wxString printer_name = m_printer_name->GetValue(); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 63fd904..4ce72c0 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -62,7 +62,6 @@ class PhysicalPrinterDialog : public DPIDialog PhysicalPrinter m_printer; wxString m_default_name; DynamicPrintConfig* m_config { nullptr }; - ::TextInput* m_printer_name { nullptr }; std::vector m_presets; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index adb0326..ede48f4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1,3 +1,4 @@ + #include "Plater.hpp" #include "slic3r/GUI/Jobs/UIThreadWorker.hpp" @@ -16,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -37,7 +40,12 @@ #include #endif +#if wxUSE_SECRETSTORE +#include +#endif + #include + #include "libslic3r/libslic3r.h" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" @@ -68,6 +76,7 @@ #include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" #include "GUI_Geometry.hpp" +#include "GUI_Utils.hpp" #include "GUI_Factories.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -83,6 +92,7 @@ #include "Tab.hpp" //#include "Jobs/ArrangeJob.hpp" #include "Jobs/ArrangeJob2.hpp" +#include "ConfigWizardWebViewPage.hpp" //#include "Jobs/FillBedJob.hpp" #include "Jobs/RotoptimizeJob.hpp" @@ -96,7 +106,7 @@ #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" #include "../Utils/PrintHost.hpp" -#include "../Utils/FixModelByWin10.hpp" + #include "../Utils/UndoRedo.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Utils/Process.hpp" @@ -110,7 +120,12 @@ #include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file #include "Gizmos/GLGizmoCut.hpp" #include "FileArchiveDialog.hpp" - +#include "UserAccount.hpp" +#include "UserAccountUtils.hpp" +#include "DesktopIntegrationDialog.hpp" +#include "WebViewDialog.hpp" +#include "ConfigWizardWebViewPage.hpp" +#include "PresetArchiveDatabase.hpp" //B34 #include "Gizmos/GLGizmoEmboss.hpp" @@ -126,6 +141,7 @@ #include "libslic3r/Platform.hpp" #include "Widgets/CheckBox.hpp" + using boost::optional; namespace fs = boost::filesystem; using Slic3r::_3DScene; @@ -140,8 +156,6 @@ static const std::pair THUMBNAIL_SIZE_SEND = {128, 1 namespace Slic3r { namespace GUI { -// Trigger Plater::schedule_background_process(). -wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); // BackgroundSlicingProcess updates UI with slicing progress: Status bar / progress bar has to be updated, possibly scene has to be refreshed, // see PrintBase::SlicingStatus for the content of the message. wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); @@ -151,1582 +165,6 @@ wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, SlicingProcessCompletedEvent); wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); - -bool Plater::has_illegal_filename_characters(const wxString& wxs_name) -{ - std::string name = into_u8(wxs_name); - return has_illegal_filename_characters(name); -} - -bool Plater::has_illegal_filename_characters(const std::string& name) -{ - const char* illegal_characters = "<>:/\\|?*\""; - for (size_t i = 0; i < std::strlen(illegal_characters); i++) - if (name.find_first_of(illegal_characters[i]) != std::string::npos) - return true; - - return false; -} - -void Plater::show_illegal_characters_warning(wxWindow* parent) -{ - show_error(parent, _L("The provided name is not valid;") + "\n" + - _L("the following characters are not allowed:") + " <>:/\\|?*\""); -} - -// Sidebar widgets - -// struct InfoBox : public wxStaticBox -// { -// InfoBox(wxWindow *parent, const wxString &label) : -// wxStaticBox(parent, wxID_ANY, label) -// { -// SetFont(GUI::small_font().Bold()); -// } -// }; - -class ObjectInfo : public wxStaticBoxSizer -{ - std::string m_warning_icon_name{ "exclamation" }; -public: - ObjectInfo(wxWindow *parent); - - wxStaticBitmap *manifold_warning_icon; - wxStaticBitmap *info_icon; - wxStaticText *info_size; - wxStaticText *info_volume; - wxStaticText *info_facets; - wxStaticText *info_manifold; - - wxStaticText *label_volume; - std::vector sla_hidden_items; - - bool showing_manifold_warning_icon; - void show_sizer(bool show); - void update_warning_icon(const std::string& warning_icon_name); -}; - -ObjectInfo::ObjectInfo(wxWindow *parent) : - wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Info")), wxVERTICAL) -{ - GetStaticBox()->SetFont(wxGetApp().bold_font()); - wxGetApp().UpdateDarkUI(GetStaticBox()); - - auto *grid_sizer = new wxFlexGridSizer(4, 5, 15); - grid_sizer->SetFlexibleDirection(wxHORIZONTAL); - - auto init_info_label = [parent, grid_sizer](wxStaticText **info_label, wxString text_label, wxSizer* sizer_with_icon=nullptr) { - auto *text = new wxStaticText(parent, wxID_ANY, text_label + ":"); - text->SetFont(wxGetApp().small_font()); - *info_label = new wxStaticText(parent, wxID_ANY, ""); - (*info_label)->SetFont(wxGetApp().small_font()); - grid_sizer->Add(text, 0); - if (sizer_with_icon) { - sizer_with_icon->Insert(0, *info_label, 0); - grid_sizer->Add(sizer_with_icon, 0, wxEXPAND); - } - else - grid_sizer->Add(*info_label, 0); - return text; - }; - - init_info_label(&info_size, _L("Size")); - - info_icon = new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("info")); - info_icon->SetToolTip(_L("For a multipart object, this value isn't accurate.\n" - "It doesn't take account of intersections and negative volumes.")); - auto* volume_info_sizer = new wxBoxSizer(wxHORIZONTAL); - volume_info_sizer->Add(info_icon, 0, wxLEFT, 10); - label_volume = init_info_label(&info_volume, _L("Volume"), volume_info_sizer); - - init_info_label(&info_facets, _L("Facets")); -// label_materials = init_info_label(&info_materials, _L("Materials")); - Add(grid_sizer, 0, wxEXPAND); - - info_manifold = new wxStaticText(parent, wxID_ANY, ""); - info_manifold->SetFont(wxGetApp().small_font()); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle(m_warning_icon_name)); - auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); - sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); - sizer_manifold->Add(info_manifold, 0, wxLEFT, 2); - Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4); - - sla_hidden_items = { label_volume, info_volume, /*label_materials, info_materials*/ }; - - // Fixes layout issues on plater, short BitmapComboBoxes with some Windows scaling, see GH issue #7414. - this->Show(false); -} - -void ObjectInfo::show_sizer(bool show) -{ - Show(show); - if (show) - manifold_warning_icon->Show(showing_manifold_warning_icon && show); -} - -void ObjectInfo::update_warning_icon(const std::string& warning_icon_name) -{ - if ((showing_manifold_warning_icon = !warning_icon_name.empty())) { - m_warning_icon_name = warning_icon_name; - manifold_warning_icon->SetBitmap(*get_bmp_bundle(m_warning_icon_name)); - } -} - -enum SlicedInfoIdx -{ - siFilament_g, - siFilament_m, - siFilament_mm3, - siMateril_unit, - siCost, - siEstimatedTime, - siWTNumbetOfToolchanges, - - siCount -}; - -class SlicedInfo : public wxStaticBoxSizer -{ -public: - SlicedInfo(wxWindow *parent); - void SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label=""); - -private: - std::vector> info_vec; -}; - -SlicedInfo::SlicedInfo(wxWindow *parent) : - wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _L("Sliced Info")), wxVERTICAL) -{ - GetStaticBox()->SetFont(wxGetApp().bold_font()); - wxGetApp().UpdateDarkUI(GetStaticBox()); - - auto *grid_sizer = new wxFlexGridSizer(2, 5, 15); - grid_sizer->SetFlexibleDirection(wxVERTICAL); - - info_vec.reserve(siCount); - - auto init_info_label = [this, parent, grid_sizer](wxString text_label) { - auto *text = new wxStaticText(parent, wxID_ANY, text_label); - text->SetFont(wxGetApp().small_font()); - auto info_label = new wxStaticText(parent, wxID_ANY, "N/A"); - info_label->SetFont(wxGetApp().small_font()); - grid_sizer->Add(text, 0); - grid_sizer->Add(info_label, 0); - info_vec.push_back(std::pair(text, info_label)); - }; - - init_info_label(_L("Used Filament (g)")); - init_info_label(_L("Used Filament (m)")); - init_info_label(_L("Used Filament (mm³)")); - init_info_label(_L("Used Material (unit)")); - init_info_label(_L("Cost (money)")); - init_info_label(_L("Estimated printing time")); - init_info_label(_L("Number of tool changes")); - - Add(grid_sizer, 0, wxEXPAND); - this->Show(false); -} - -void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/) -{ - const bool show = text != "N/A"; - if (show) - info_vec[idx].second->SetLabelText(text); - if (!new_label.IsEmpty()) - info_vec[idx].first->SetLabelText(new_label); - info_vec[idx].first->Show(show); - info_vec[idx].second->Show(show); -} - -// Frequently changed parameters - -class FreqChangedParams : public OG_Settings -{ - double m_brim_width = 0.0; - wxButton* m_wiping_dialog_button{ nullptr }; - wxSizer* m_sizer {nullptr}; - -//Y26 - std::shared_ptr m_og_filament; - std::shared_ptr m_og_sla; - std::vector m_empty_buttons; -public: - FreqChangedParams(wxWindow* parent); - ~FreqChangedParams() {} - - wxButton* get_wiping_dialog_button() { return m_wiping_dialog_button; } - wxSizer* get_sizer() override; - ConfigOptionsGroup* get_og(const bool is_fff); -//Y26 - ConfigOptionsGroup* get_og_filament(); - void Show(const bool is_fff) override; - - void msw_rescale(); - void sys_color_changed(); -}; - -void FreqChangedParams::msw_rescale() -{ - m_og->msw_rescale(); -//Y26 - m_og_filament->msw_rescale(); - m_og_sla->msw_rescale(); -} - -void FreqChangedParams::sys_color_changed() -{ - m_og->sys_color_changed(); -//Y26 - m_og_filament->sys_color_changed(); - m_og_sla->sys_color_changed(); - - for (auto btn: m_empty_buttons) - btn->sys_color_changed(); - - wxGetApp().UpdateDarkUI(m_wiping_dialog_button, true); -} - -FreqChangedParams::FreqChangedParams(wxWindow* parent) : - OG_Settings(parent, false) -{ - DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; - - // Frequently changed parameters for FFF_technology - m_og->set_config(config); - m_og->hide_labels(); - - m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { - Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); - if (!tab_print) return; - - if (opt_key == "fill_density") { - tab_print->update_dirty(); - tab_print->reload_config(); - tab_print->update(); - } - else - { - DynamicPrintConfig new_conf = *config; - if (opt_key == "brim") { - double new_val; - double brim_width = config->opt_float("brim_width"); - if (boost::any_cast(value) == true) - { - new_val = m_brim_width == 0.0 ? 5 : - m_brim_width < 0.0 ? m_brim_width * (-1) : - m_brim_width; - } - else { - m_brim_width = brim_width * (-1); - new_val = 0; - } - new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val)); - } - else { - assert(opt_key == "support"); - const wxString& selection = boost::any_cast(value); - PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); - - auto support_material = selection == _("None") ? false : true; - new_conf.set_key_value("support_material", new ConfigOptionBool(support_material)); - - if (selection == _("Everywhere")) { - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); - if (printer_technology == ptFFF) - new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true)); - } else if (selection == _("Support on build plate only")) { - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); - if (printer_technology == ptFFF) - new_conf.set_key_value("support_material_auto", new ConfigOptionBool(true)); - } else if (selection == _("For support enforcers only")) { - assert(printer_technology == ptFFF); - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); - new_conf.set_key_value("support_material_auto", new ConfigOptionBool(false)); - } - } - tab_print->load_config(new_conf); - } - }; - - - Line line = Line { "", "" }; - - ConfigOptionDef support_def; - support_def.label = L("Supports"); - support_def.type = coStrings; - support_def.tooltip = L("Select what kind of support do you need"); - support_def.set_enum_labels(ConfigOptionDef::GUIType::select_close, { - L("None"), - L("Support on build plate only"), - L("For support enforcers only"), - L("Everywhere") - }); - support_def.set_default_value(new ConfigOptionStrings{ "None" }); - Option option = Option(support_def, "support"); - option.opt.full_width = true; - line.append_option(option); - - /* Not a best solution, but - * Temporary workaround for right border alignment - */ - auto empty_widget = [this] (wxWindow* parent) { - auto sizer = new wxBoxSizer(wxHORIZONTAL); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent", wxEmptyString, - wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); - sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); - m_empty_buttons.push_back(btn); - return sizer; - }; - line.append_widget(empty_widget); - - m_og->append_line(line); - - - line = Line { "", "" }; - - option = m_og->get_option("fill_density"); - option.opt.label = L("Infill"); - option.opt.width = 8; - option.opt.sidetext = " "; - line.append_option(option); - - m_brim_width = config->opt_float("brim_width"); - ConfigOptionDef def; - def.label = L("Brim"); - def.type = coBool; - def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); - def.gui_type = ConfigOptionDef::GUIType::undefined; - def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }); - option = Option(def, "brim"); - option.opt.sidetext = ""; - line.append_option(option); - - auto wiping_dialog_btn = [this](wxWindow* parent) { - m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _L("Purging volumes") + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); - wxGetApp().SetWindowVariantForButton(m_wiping_dialog_button); - wxGetApp().UpdateDarkUI(m_wiping_dialog_button, true); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); - m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) - { - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - DynamicPrintConfig& project_config = preset_bundle->project_config; - const bool use_custom_matrix = (project_config.option("wiping_volumes_use_custom_matrix"))->value; - const std::vector &init_matrix = (project_config.option("wiping_volumes_matrix"))->values; - - const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); - - // Extract the relevant config options, even values from possibly modified presets. - const double default_purge = static_cast(preset_bundle->printers.get_edited_preset().config.option("multimaterial_purging"))->value; - std::vector filament_purging_multipliers = preset_bundle->get_config_options_for_current_filaments("filament_purge_multiplier"); - - WipingDialog dlg(parent, cast(init_matrix), extruder_colours, default_purge, filament_purging_multipliers, use_custom_matrix); - - if (dlg.ShowModal() == wxID_OK) { - std::vector matrix = dlg.get_matrix(); - (project_config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(), matrix.end()); - (project_config.option("wiping_volumes_use_custom_matrix"))->value = dlg.get_use_custom_matrix(); - // Update Project dirty state, update application title bar. - wxGetApp().plater()->update_project_dirty_from_presets(); - wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); - } - })); - - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent", wxEmptyString, - wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); - sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, - int(0.3 * wxGetApp().em_unit())); - m_empty_buttons.push_back(btn); - - return sizer; - }; - line.append_widget(wiping_dialog_btn); - m_og->append_line(line); - - m_og->activate(); - - Choice* choice = dynamic_cast(m_og->get_field("support")); - choice->suppress_scroll(); - - // Frequently changed parameters for SLA_technology - m_og_sla = std::make_shared(parent, ""); - m_og_sla->hide_labels(); - DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_og_sla->set_config(config_sla); - - m_og_sla->m_on_change = [config_sla](t_config_option_key opt_key, boost::any value) { - Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); - if (!tab) return; - - DynamicPrintConfig new_conf = *config_sla; - if (opt_key == "pad") { - const wxString& selection = boost::any_cast(value); - - const bool pad_enable = selection == _("None") ? false : true; - new_conf.set_key_value("pad_enable", new ConfigOptionBool(pad_enable)); - - if (selection == _("Below object")) - new_conf.set_key_value("pad_around_object", new ConfigOptionBool(false)); - else if (selection == _("Around object")) - new_conf.set_key_value("pad_around_object", new ConfigOptionBool(true)); - } - else - { - assert(opt_key == "support"); - const wxString& selection = boost::any_cast(value); - - const bool supports_enable = selection == _("None") ? false : true; - new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); - - std::string treetype = get_sla_suptree_prefix(new_conf); - - if (selection == _("Everywhere")) { - new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(false)); - new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); - } - else if (selection == _("Support on build plate only")) { - new_conf.set_key_value(treetype + "support_buildplate_only", new ConfigOptionBool(true)); - new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(false)); - } - else if (selection == _("For support enforcers only")) { - new_conf.set_key_value("support_enforcers_only", new ConfigOptionBool(true)); - } - } - - tab->load_config(new_conf); - tab->update_dirty(); - }; - - line = Line{ "", "" }; - - ConfigOptionDef support_def_sla = support_def; - support_def_sla.set_default_value(new ConfigOptionStrings{ "None" }); - option = Option(support_def_sla, "support"); - option.opt.full_width = true; - line.append_option(option); - line.append_widget(empty_widget); - m_og_sla->append_line(line); - - line = Line{ "", "" }; - - ConfigOptionDef pad_def; - pad_def.label = L("Pad"); - pad_def.type = coStrings; - pad_def.tooltip = L("Select what kind of pad do you need"); - pad_def.set_enum_labels(ConfigOptionDef::GUIType::select_close, { - L("None"), - L("Below object"), - L("Around object") - }); - pad_def.set_default_value(new ConfigOptionStrings{ "Below object" }); - option = Option(pad_def, "pad"); - option.opt.full_width = true; - line.append_option(option); - line.append_widget(empty_widget); - - m_og_sla->append_line(line); - - m_og_sla->activate(); - choice = dynamic_cast(m_og_sla->get_field("support")); - choice->suppress_scroll(); - choice = dynamic_cast(m_og_sla->get_field("pad")); - choice->suppress_scroll(); - - //Y26 - m_og_filament = std::make_shared(parent, ""); - DynamicPrintConfig* filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config; - - m_og_filament->set_config(filament_config); - m_og_filament->hide_labels(); - - m_og_filament->m_on_change = [filament_config, this](t_config_option_key opt_key, boost::any value) { - Tab* tab_filament = wxGetApp().get_tab(Preset::TYPE_FILAMENT); - if (!tab_filament) return; - - if (opt_key == "seal_print") { - tab_filament->update_dirty(); - tab_filament->reload_config(); - tab_filament->update(); - } - }; - - line = Line { "", "" }; - - option = m_og_filament->get_option("seal_print"); - option.opt.label = L("Seal"); - line.append_option(option); - line.append_widget(empty_widget); - - m_og_filament->append_line(line); - m_og_filament->activate(); - - m_sizer = new wxBoxSizer(wxVERTICAL); - m_sizer->Add(m_og->sizer, 0, wxEXPAND); -//Y26 - m_sizer->Add(m_og_filament->sizer, 0, wxEXPAND); - m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND); -} - - -wxSizer* FreqChangedParams::get_sizer() -{ - return m_sizer; -} - -void FreqChangedParams::Show(const bool is_fff) -{ - const bool is_wdb_shown = m_wiping_dialog_button->IsShown(); - m_og->Show(is_fff); -//Y26 - m_og_filament->Show(is_fff); - m_og_sla->Show(!is_fff); - - // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden - if (is_fff && !is_wdb_shown) - m_wiping_dialog_button->Hide(); -} - -ConfigOptionsGroup* FreqChangedParams::get_og(const bool is_fff) -{ - return is_fff ? m_og.get() : m_og_sla.get(); -} - -//Y26 -ConfigOptionsGroup* FreqChangedParams::get_og_filament() -{ - return m_og_filament.get(); -} - -// Sidebar / private - -enum class ActionButtonType : int { - abReslice, - abExport, - abSendGCode -}; - -struct Sidebar::priv -{ - Plater *plater; - - wxScrolledWindow *scrolled; - wxPanel* presets_panel; // Used for MSW better layouts - - ModeSizer *mode_sizer {nullptr}; - wxFlexGridSizer *sizer_presets; - PlaterPresetComboBox *combo_print; - std::vector combos_filament; - wxBoxSizer *sizer_filaments; - PlaterPresetComboBox *combo_sla_print; - PlaterPresetComboBox *combo_sla_material; - PlaterPresetComboBox *combo_printer; - - wxBoxSizer *sizer_params; - FreqChangedParams *frequently_changed_parameters{ nullptr }; - ObjectList *object_list{ nullptr }; - ObjectManipulation *object_manipulation{ nullptr }; - ObjectSettings *object_settings{ nullptr }; - ObjectLayers *object_layers{ nullptr }; - ObjectInfo *object_info; - SlicedInfo *sliced_info; - - wxButton *btn_export_gcode; - wxButton *btn_reslice; - ScalableButton *btn_send_gcode; - //ScalableButton *btn_eject_device; - ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) - - bool is_collapsed {false}; - Search::OptionsSearcher searcher; - - priv(Plater *plater) : plater(plater) {} - ~priv(); - - void show_preset_comboboxes(); - -#ifdef _WIN32 - wxString btn_reslice_tip; - void show_rich_tip(const wxString& tooltip, wxButton* btn); - void hide_rich_tip(wxButton* btn); -#endif -}; - -Sidebar::priv::~priv() -{ - delete object_manipulation; - delete object_settings; - delete frequently_changed_parameters; - delete object_layers; -} - -void Sidebar::priv::show_preset_comboboxes() -{ - const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; - - for (size_t i = 0; i < 4; ++i) - sizer_presets->Show(i, !showSLA); - - for (size_t i = 4; i < 8; ++i) - sizer_presets->Show(i, showSLA); - - frequently_changed_parameters->Show(!showSLA); - - scrolled->GetParent()->Layout(); - scrolled->Refresh(); -} - -#ifdef _WIN32 -using wxRichToolTipPopup = wxCustomBackgroundWindow; -static wxRichToolTipPopup* get_rtt_popup(wxButton* btn) -{ - auto children = btn->GetChildren(); - for (auto child : children) - if (child->IsShown()) - return dynamic_cast(child); - return nullptr; -} - -// Help function to find and check if some combobox is dropped down and then dismiss it -static bool found_and_dismiss_shown_dropdown(wxWindow* win) -{ - auto children = win->GetChildren(); - if (children.IsEmpty()) { - if (auto dd = dynamic_cast(win); dd && dd->IsShown()) { - dd->CallDismissAndNotify(); - return true; - } - } - - for (auto child : children) { - if (found_and_dismiss_shown_dropdown(child)) - return true; - } - return false; -} -void Sidebar::priv::show_rich_tip(const wxString& tooltip, wxButton* btn) -{ - if (tooltip.IsEmpty()) - return; - // Currently state (propably wxWidgets issue) : - // When second wxPopupTransientWindow is popped up, then first wxPopupTransientWindow doesn't receive EVT_DISMISS and stay on the top. - // New comboboxes use wxPopupTransientWindow as DropDown now - // That is why DropDown stay on top, when we show rich tooltip for btn. - // (see https://github.com/prusa3d/PrusaSlicer/issues/11988) - - // So, check the combo boxes and close them if necessary before showing the rich tip. - found_and_dismiss_shown_dropdown(scrolled); - wxRichToolTip tip(tooltip, ""); - tip.SetIcon(wxICON_NONE); - tip.SetTipKind(wxTipKind_BottomRight); - tip.SetTitleFont(wxGetApp().normal_font()); - tip.SetBackgroundColour(wxGetApp().get_window_default_clr()); - - tip.ShowFor(btn); - // Every call of the ShowFor() creates new RichToolTip and show it. - // Every one else are hidden. - // So, set a text color just for the shown rich tooltip - if (wxRichToolTipPopup* popup = get_rtt_popup(btn)) { - auto children = popup->GetChildren(); - for (auto child : children) { - child->SetForegroundColour(wxGetApp().get_label_clr_default()); - // we neen just first text line for out rich tooltip - return; - } - } -} - -void Sidebar::priv::hide_rich_tip(wxButton* btn) -{ - if (wxRichToolTipPopup* popup = get_rtt_popup(btn)) - popup->Dismiss(); -} -#endif - -// Sidebar / public - -Sidebar::Sidebar(Plater *parent) - : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(42 * wxGetApp().em_unit(), -1)), p(new priv(parent)) -{ - SetFont(wxGetApp().normal_font()); - p->scrolled = new wxScrolledWindow(this); -// p->scrolled->SetScrollbars(0, 100, 1, 2); // ys_DELETE_after_testing. pixelsPerUnitY = 100 from https://github.com/prusa3d/PrusaSlicer/commit/8f019e5fa992eac2c9a1e84311c990a943f80b01, - // but this cause the bad layout of the sidebar, when all infoboxes appear. - // As a result we can see the empty block at the bottom of the sidebar - // But if we set this value to 5, layout will be better - p->scrolled->SetScrollRate(0, 5); - -#ifndef __APPLE__ -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(this); - wxGetApp().UpdateDarkUI(p->scrolled); -#else -// SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#endif -#endif - - // Sizer in the scrolled area - auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); - p->scrolled->SetSizer(scrolled_sizer); - - // Sizer with buttons for mode changing - p->mode_sizer = new ModeSizer(p->scrolled, int(0.5 * wxGetApp().em_unit())); - - // The preset chooser - p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); - p->sizer_presets->AddGrowableCol(0, 1); - p->sizer_presets->SetFlexibleDirection(wxBOTH); - - bool is_msw = false; -#ifdef __WINDOWS__ - p->scrolled->SetDoubleBuffered(true); - - p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - wxGetApp().UpdateDarkUI(p->presets_panel); - p->presets_panel->SetSizer(p->sizer_presets); - - is_msw = true; -#else - p->presets_panel = p->scrolled; -#endif //__WINDOWS__ - - p->sizer_filaments = new wxBoxSizer(wxVERTICAL); - - const int margin_5 = int(0.5 * wxGetApp().em_unit());// 5; - - auto init_combo = [this, margin_5](PlaterPresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { - auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + ":"); - text->SetFont(wxGetApp().small_font()); - *combo = new PlaterPresetComboBox(p->presets_panel, preset_type); - - auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); - combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); - if ((*combo)->edit_btn) - combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT, - int(0.3*wxGetApp().em_unit())); - - auto *sizer_presets = this->p->sizer_presets; - auto *sizer_filaments = this->p->sizer_filaments; - // Hide controls, which will be shown/hidden in respect to the printer technology - text->Show(preset_type == Preset::TYPE_PRINTER); - sizer_presets->Add(text, 0, wxALIGN_LEFT | wxEXPAND | wxRIGHT, 4); - if (! filament) { - combo_and_btn_sizer->ShowItems(preset_type == Preset::TYPE_PRINTER); - sizer_presets->Add(combo_and_btn_sizer, 0, wxEXPAND | -#ifdef __WXGTK3__ - wxRIGHT, margin_5); -#else - wxBOTTOM, 1); - (void)margin_5; // supress unused capture warning -#endif // __WXGTK3__ - } else { - sizer_filaments->Add(combo_and_btn_sizer, 0, wxEXPAND | -#ifdef __WXGTK3__ - wxRIGHT, margin_5); -#else - wxBOTTOM, 1); -#endif // __WXGTK3__ - (*combo)->set_extruder_idx(0); - sizer_filaments->ShowItems(false); - sizer_presets->Add(sizer_filaments, 1, wxEXPAND); - } - }; - - p->combos_filament.push_back(nullptr); - init_combo(&p->combo_print, _L("Print settings"), Preset::TYPE_PRINT, false); - init_combo(&p->combos_filament[0], _L("Filament"), Preset::TYPE_FILAMENT, true); - init_combo(&p->combo_sla_print, _L("SLA print settings"), Preset::TYPE_SLA_PRINT, false); - init_combo(&p->combo_sla_material, _L("SLA material"), Preset::TYPE_SLA_MATERIAL, false); - init_combo(&p->combo_printer, _L("Printer"), Preset::TYPE_PRINTER, false); - - p->sizer_params = new wxBoxSizer(wxVERTICAL); - - // Frequently changed parameters - p->frequently_changed_parameters = new FreqChangedParams(p->scrolled); - p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM -#ifdef __WXGTK3__ - | wxRIGHT -#endif // __WXGTK3__ - , wxOSX ? 1 : margin_5); - - // Object List - p->object_list = new ObjectList(p->scrolled); - p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND); - - // Object Manipulations - p->object_manipulation = new ObjectManipulation(p->scrolled); - p->object_manipulation->Hide(); - p->sizer_params->Add(p->object_manipulation->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); - - // Frequently Object Settings - p->object_settings = new ObjectSettings(p->scrolled); - p->object_settings->Hide(); - p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); - - // Object Layers - p->object_layers = new ObjectLayers(p->scrolled); - p->object_layers->Hide(); - p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); - - // Info boxes - p->object_info = new ObjectInfo(p->scrolled); - p->sliced_info = new SlicedInfo(p->scrolled); - - // Sizer in the scrolled area - if (p->mode_sizer) - scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL); - - int size_margin = wxGTK3 ? wxLEFT | wxRIGHT : wxLEFT; - - is_msw ? - scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | size_margin, margin_5) : - scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | size_margin, margin_5); - scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | size_margin, margin_5); - scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | size_margin, margin_5); - scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | size_margin, margin_5); - - // Buttons underneath the scrolled area - - // rescalable bitmap buttons "Send to printer" and "Remove device" -//Y14 - auto init_scalable_btn = [this](ScalableButton** btn, const std::string& icon_name, wxString label, wxString tooltip = wxEmptyString) - { -#ifdef __APPLE__ - int bmp_px_cnt = 16; -#else - int bmp_px_cnt = 32; -#endif //__APPLE__ - ScalableBitmap bmp = ScalableBitmap(this, icon_name, bmp_px_cnt); - *btn = new ScalableButton(this, wxID_ANY, bmp, label, wxBU_EXACTFIT); - (*btn)->SetFont(wxGetApp().bold_font()); - wxGetApp().SetWindowVariantForButton((*btn)); - -#ifdef _WIN32 - (*btn)->Bind(wxEVT_ENTER_WINDOW, [tooltip, btn, this](wxMouseEvent& event) { - p->show_rich_tip(tooltip, *btn); - event.Skip(); - }); - (*btn)->Bind(wxEVT_LEAVE_WINDOW, [btn, this](wxMouseEvent& event) { - p->hide_rich_tip(*btn); - event.Skip(); - }); -#else - (*btn)->SetToolTip(tooltip); -#endif // _WIN32 - (*btn)->Hide(); - }; - - init_scalable_btn(&p->btn_send_gcode, "export_gcode", _L("Send to printer"), _L("Send to printer") + " " + GUI::shortkey_ctrl_prefix() + "Shift+G"); -// init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device ") + GUI::shortkey_ctrl_prefix() + "T"); - init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export"), _L("Export to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U"); -//Y14 - // regular buttons "Slice now" and "Export G-code" - -// const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4; -#ifdef _WIN32 - const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight(); -#else - const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight() + 4; -#endif - auto init_btn = [this](wxButton **btn, wxString label, const int button_height) { - *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, - wxSize(-1, button_height), wxBU_EXACTFIT); - wxGetApp().SetWindowVariantForButton((*btn)); - (*btn)->SetFont(wxGetApp().bold_font()); - wxGetApp().UpdateDarkUI((*btn), true); - }; - - init_btn(&p->btn_export_gcode, _L("Export G-code") + dots , scaled_height); - init_btn(&p->btn_reslice , _L("Slice now") , scaled_height); - - enable_buttons(false); - - auto *btns_sizer = new wxBoxSizer(wxHORIZONTAL); - - auto* complect_btns_sizer = new wxBoxSizer(wxHORIZONTAL); - complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND); - complect_btns_sizer->Add(p->btn_send_gcode, 0, wxLEFT, margin_5); - complect_btns_sizer->Add(p->btn_export_gcode_removable, 0, wxLEFT, margin_5); -// complect_btns_sizer->Add(p->btn_eject_device); - - - btns_sizer->Add(p->btn_reslice, 1, wxEXPAND | wxTOP | wxBOTTOM, margin_5); - btns_sizer->Add(complect_btns_sizer, 1, wxEXPAND | wxTOP | wxBOTTOM, margin_5); - - auto *sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(p->scrolled, 1, wxEXPAND); - sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, margin_5); - SetSizer(sizer); - - // Events - p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(false); }); - p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) - { - if (p->plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) - return; - - const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); - if (export_gcode_after_slicing) - p->plater->export_gcode(true); - else - p->plater->reslice(); - p->plater->select_view_3D("Preview"); - }); - -#ifdef _WIN32 - p->btn_reslice->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& event) { - p->show_rich_tip(p->btn_reslice_tip, p->btn_reslice); - event.Skip(); - }); - p->btn_reslice->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { - p->hide_rich_tip(p->btn_reslice); - event.Skip(); - }); -#endif // _WIN32 - - p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); -// p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); - p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); }); -} - -Sidebar::~Sidebar() {} - -void Sidebar::init_filament_combo(PlaterPresetComboBox** combo, const int extr_idx) -{ - *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); - (*combo)->set_extruder_idx(extr_idx); - - auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); - combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); - combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, - int(0.3*wxGetApp().em_unit())); - - this->p->sizer_filaments->Add(combo_and_btn_sizer, 1, wxEXPAND | -#ifdef __WXGTK3__ - wxRIGHT, int(0.5 * wxGetApp().em_unit())); -#else - wxBOTTOM, 1); -#endif // __WXGTK3__ -} - -void Sidebar::remove_unused_filament_combos(const size_t current_extruder_count) -{ - if (current_extruder_count >= p->combos_filament.size()) - return; - auto sizer_filaments = this->p->sizer_filaments; - while (p->combos_filament.size() > current_extruder_count) { - const int last = p->combos_filament.size() - 1; - sizer_filaments->Remove(last); - (*p->combos_filament[last]).Destroy(); - p->combos_filament.pop_back(); - } -} - -void Sidebar::update_all_preset_comboboxes() -{ - PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); - - // Update the print choosers to only contain the compatible presets, update the dirty flags. - if (print_tech == ptFFF) - p->combo_print->update(); - else { - p->combo_sla_print->update(); - p->combo_sla_material->update(); - } - // Update the printer choosers, update the dirty flags. - p->combo_printer->update(); - // Update the filament choosers to only contain the compatible presets, update the color preview, - // update the dirty flags. - if (print_tech == ptFFF) { - for (PlaterPresetComboBox* cb : p->combos_filament) - cb->update(); - } -} - -void Sidebar::update_presets(Preset::Type preset_type) -{ - PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); - - switch (preset_type) { - case Preset::TYPE_FILAMENT: - { - const size_t extruder_cnt = print_tech != ptFFF ? 1 : - dynamic_cast(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); - const size_t filament_cnt = p->combos_filament.size() > extruder_cnt ? extruder_cnt : p->combos_filament.size(); - - for (size_t i = 0; i < filament_cnt; i++) - p->combos_filament[i]->update(); - - break; - } - - case Preset::TYPE_PRINT: - p->combo_print->update(); - break; - - case Preset::TYPE_SLA_PRINT: - p->combo_sla_print->update(); - break; - - case Preset::TYPE_SLA_MATERIAL: - p->combo_sla_material->update(); - break; - - case Preset::TYPE_PRINTER: - { - update_all_preset_comboboxes(); -#if 1 // #ysFIXME_delete_after_test_of >> it looks like CallAfter() is no need [issue with disapearing of comboboxes are not reproducible] - p->show_preset_comboboxes(); -#else - // CallAfter is really needed here to correct layout of the preset comboboxes, - // when printer technology is changed during a project loading AND/OR switching the application mode. - // Otherwise, some of comboboxes are invisible - CallAfter([this]() { p->show_preset_comboboxes(); }); -#endif - break; - } - - default: break; - } - - // Synchronize config.ini with the current selections. - wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); -} - -void Sidebar::update_mode_sizer() const -{ - if (p->mode_sizer) - p->mode_sizer->SetMode(m_mode); -} - -void Sidebar::change_top_border_for_mode_sizer(bool increase_border) -{ - if (p->mode_sizer) { - p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0); - p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0); - } -} - -void Sidebar::update_reslice_btn_tooltip() const -{ - wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]"; - if (m_mode != comSimple) - tooltip += wxString("\n") + _L("Hold Shift to Slice & Export G-code"); -#ifdef _WIN32 - p->btn_reslice_tip = tooltip; -#else - p->btn_reslice->SetToolTip(tooltip); -#endif -} - -void Sidebar::msw_rescale() -{ - SetMinSize(wxSize(42 * wxGetApp().em_unit(), -1)); - - for (PlaterPresetComboBox* combo : std::vector { p->combo_print, - p->combo_sla_print, - p->combo_sla_material, - p->combo_printer } ) - combo->msw_rescale(); - for (PlaterPresetComboBox* combo : p->combos_filament) - combo->msw_rescale(); - - p->frequently_changed_parameters->msw_rescale(); - p->object_list->msw_rescale(); - p->object_manipulation->msw_rescale(); - p->object_layers->msw_rescale(); - -#ifdef _WIN32 - const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight(); -#else - const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight() + 4; -#endif - p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height)); - p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height)); - - p->scrolled->Layout(); - - p->searcher.dlg_msw_rescale(); -} - -void Sidebar::sys_color_changed() -{ -#ifdef _WIN32 - wxWindowUpdateLocker noUpdates(this); - - for (wxWindow* win : std::vector{ this, p->sliced_info->GetStaticBox(), p->object_info->GetStaticBox(), p->btn_reslice, p->btn_export_gcode }) - wxGetApp().UpdateDarkUI(win); - for (wxWindow* win : std::vector{ p->scrolled, p->presets_panel }) - wxGetApp().UpdateAllStaticTextDarkUI(win); - for (wxWindow* btn : std::vector{ p->btn_reslice, p->btn_export_gcode }) - wxGetApp().UpdateDarkUI(btn, true); - - if (p->mode_sizer) - p->mode_sizer->sys_color_changed(); - p->frequently_changed_parameters->sys_color_changed(); - p->object_settings->sys_color_changed(); -#endif - - for (PlaterPresetComboBox* combo : std::vector{ p->combo_print, - p->combo_sla_print, - p->combo_sla_material, - p->combo_printer }) - combo->sys_color_changed(); - for (PlaterPresetComboBox* combo : p->combos_filament) - combo->sys_color_changed(); - - p->object_list->sys_color_changed(); - p->object_manipulation->sys_color_changed(); - p->object_layers->sys_color_changed(); - - // btn...->msw_rescale() updates icon on button, so use it - p->btn_send_gcode->sys_color_changed(); -// p->btn_eject_device->msw_rescale(); - p->btn_export_gcode_removable->sys_color_changed(); - - p->scrolled->Layout(); - p->scrolled->Refresh(); - - p->searcher.dlg_sys_color_changed(); -} - -void Sidebar::update_mode_markers() -{ - if (p->mode_sizer) - p->mode_sizer->update_mode_markers(); -} - -void Sidebar::search() -{ - p->searcher.search(); -} - -void Sidebar::jump_to_option(const std::string& composite_key) -{ - const auto separator_pos = composite_key.find(";"); - const std::string opt_key = composite_key.substr(0, separator_pos); - const std::string tab_name = composite_key.substr(separator_pos + 1, composite_key.length()); - - for (Tab* tab : wxGetApp().tabs_list) { - if (tab->name() == tab_name) { - check_and_update_searcher(true); - - // Regularly searcher is sorted in respect to the options labels, - // so resort searcher before get an option - p->searcher.sort_options_by_key(); - const Search::Option& opt = p->searcher.get_option(opt_key, tab->type()); - tab->activate_option(opt_key, boost::nowide::narrow(opt.category)); - - // Revert sort of searcher back - p->searcher.sort_options_by_label(); - break; - } - } -} -void Sidebar::jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category) -{ - //const Search::Option& opt = p->searcher.get_option(opt_key, type); - wxGetApp().get_tab(type)->activate_option(opt_key, category); -} - -void Sidebar::jump_to_option(size_t selected) -{ - const Search::Option& opt = p->searcher.get_option(selected); - if (opt.type == Preset::TYPE_PREFERENCES) - wxGetApp().open_preferences(opt.opt_key(), boost::nowide::narrow(opt.group)); - else - wxGetApp().get_tab(opt.type)->activate_option(opt.opt_key(), boost::nowide::narrow(opt.category)); -} - -ObjectManipulation* Sidebar::obj_manipul() -{ - return p->object_manipulation; -} - -ObjectList* Sidebar::obj_list() -{ - return p->object_list; -} - -ObjectSettings* Sidebar::obj_settings() -{ - return p->object_settings; -} - -ObjectLayers* Sidebar::obj_layers() -{ - return p->object_layers; -} - -wxScrolledWindow* Sidebar::scrolled_panel() -{ - return p->scrolled; -} - -wxPanel* Sidebar::presets_panel() -{ - return p->presets_panel; -} - -ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff) -{ - return p->frequently_changed_parameters->get_og(is_fff); -} - -//Y26 -ConfigOptionsGroup* Sidebar::og_filament_chng_params() -{ - return p->frequently_changed_parameters->get_og_filament(); -} - -wxButton* Sidebar::get_wiping_dialog_button() -{ - return p->frequently_changed_parameters->get_wiping_dialog_button(); -} - -void Sidebar::update_objects_list_extruder_column(size_t extruders_count) -{ - p->object_list->update_objects_list_extruder_column(extruders_count); -} - -void Sidebar::show_info_sizer() -{ - Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - ModelObjectPtrs objects = p->plater->model().objects; - const int obj_idx = selection.get_object_idx(); - const int inst_idx = selection.get_instance_idx(); - - if (m_mode < comExpert || objects.empty() || obj_idx < 0 || int(objects.size()) <= obj_idx || - inst_idx < 0 || int(objects[obj_idx]->instances.size()) <= inst_idx || - 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())) { - p->object_info->Show(false); - return; - } - - const ModelObject* model_object = objects[obj_idx]; - - bool imperial_units = wxGetApp().app_config->get_bool("use_inches"); - double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f; - - ModelVolume* vol = nullptr; - Transform3d t; - if (selection.is_single_volume()) { - std::vector obj_idxs, vol_idxs; - wxGetApp().obj_list()->get_selection_indexes(obj_idxs, vol_idxs); - if (vol_idxs.size() != 1) - // Case when this fuction is called between update selection in ObjectList and on Canvas - // Like after try to delete last solid part in object, the object is selected in ObjectLIst when just a part is still selected on Canvas - // see https://github.com/qidi3d/QIDISlicer/issues/7408 - return; - vol = model_object->volumes[vol_idxs[0]]; - t = model_object->instances[inst_idx]->get_matrix() * vol->get_matrix(); - } - - Vec3d size = vol ? vol->mesh().transformed_bounding_box(t).size() : model_object->instance_bounding_box(inst_idx).size(); - p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f", size(0)*koef, size(1)*koef, size(2)*koef)); -// p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count()))); - - const TriangleMeshStats& stats = vol ? vol->mesh().stats() : model_object->get_object_stl_stats(); - - double volume_val = stats.volume; - if (vol) - volume_val *= std::fabs(t.matrix().block(0, 0, 3, 3).determinant()); - - p->object_info->info_volume->SetLabel(wxString::Format("%.2f", volume_val * pow(koef,3))); - p->object_info->info_facets->SetLabel(format_wxstr(_L_PLURAL("%1% (%2$d shell)", "%1% (%2$d shells)", stats.number_of_parts), - static_cast(model_object->facets_count()), stats.number_of_parts)); - - wxString info_manifold_label; - auto mesh_errors = obj_list()->get_mesh_errors_info(&info_manifold_label); - wxString tooltip = mesh_errors.tooltip; - p->object_info->update_warning_icon(mesh_errors.warning_icon_name); - p->object_info->info_manifold->SetLabel(info_manifold_label); - p->object_info->info_manifold->SetToolTip(tooltip); - p->object_info->manifold_warning_icon->SetToolTip(tooltip); - - p->object_info->show_sizer(true); - if (vol || model_object->volumes.size() == 1) - p->object_info->info_icon->Hide(); - - if (p->plater->printer_technology() == ptSLA) { - for (auto item: p->object_info->sla_hidden_items) - item->Show(false); - } -} - -void Sidebar::update_sliced_info_sizer() -{ - if (p->sliced_info->IsShown(size_t(0))) - { - if (p->plater->printer_technology() == ptSLA) - { - const SLAPrintStatistics& ps = p->plater->sla_print().print_statistics(); - wxString new_label = _L("Used Material (ml)") + ":"; - const bool is_supports = ps.support_used_material > 0.0; - if (is_supports) - new_label += format_wxstr("\n - %s\n - %s", _L_PLURAL("object", "objects", p->plater->model().objects.size()), _L("supports and pad")); - - wxString info_text = is_supports ? - wxString::Format("%.2f \n%.2f \n%.2f", (ps.objects_used_material + ps.support_used_material) / 1000, - ps.objects_used_material / 1000, - ps.support_used_material / 1000) : - wxString::Format("%.2f", (ps.objects_used_material + ps.support_used_material) / 1000); - p->sliced_info->SetTextAndShow(siMateril_unit, info_text, new_label); - - wxString str_total_cost = "N/A"; - - DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_SLA_MATERIAL)->get_config(); - if (cfg->option("bottle_cost")->getFloat() > 0.0 && - cfg->option("bottle_volume")->getFloat() > 0.0) - { - double material_cost = cfg->option("bottle_cost")->getFloat() / - cfg->option("bottle_volume")->getFloat(); - str_total_cost = wxString::Format("%.3f", material_cost*(ps.objects_used_material + ps.support_used_material) / 1000); - } - p->sliced_info->SetTextAndShow(siCost, str_total_cost, "Cost"); - - wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : from_u8(short_time_ui(get_time_dhms(float(ps.estimated_print_time)))); - p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":"); - - p->plater->get_notification_manager()->set_slicing_complete_print_time(_u8L("Estimated printing time") + ": " + boost::nowide::narrow(t_est), p->plater->is_sidebar_collapsed()); - - // Hide non-SLA sliced info parameters - p->sliced_info->SetTextAndShow(siFilament_m, "N/A"); - p->sliced_info->SetTextAndShow(siFilament_mm3, "N/A"); - p->sliced_info->SetTextAndShow(siFilament_g, "N/A"); - p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, "N/A"); - } - else - { - const PrintStatistics& ps = p->plater->fff_print().print_statistics(); - const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; - - bool imperial_units = wxGetApp().app_config->get_bool("use_inches"); - double koef = imperial_units ? ObjectManipulation::in_to_mm : 1000.0; - - wxString new_label = imperial_units ? _L("Used Filament (in)") : _L("Used Filament (m)"); - if (is_wipe_tower) - new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower")); - - wxString info_text = is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / koef, - (ps.total_used_filament - ps.total_wipe_tower_filament) / koef, - ps.total_wipe_tower_filament / koef) : - wxString::Format("%.2f", ps.total_used_filament / koef); - p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); - - koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f; - new_label = imperial_units ? _L("Used Filament (in³)") : _L("Used Filament (mm³)"); - info_text = wxString::Format("%.2f", imperial_units ? ps.total_extruded_volume * koef : ps.total_extruded_volume); - p->sliced_info->SetTextAndShow(siFilament_mm3, info_text, new_label); - - if (ps.total_weight == 0.0) - p->sliced_info->SetTextAndShow(siFilament_g, "N/A"); - else { - new_label = _L("Used Filament (g)"); - info_text = wxString::Format("%.2f", ps.total_weight); - - if (ps.filament_stats.size() > 1) - new_label += ":"; - - const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments; - for (const auto& [filament_id, filament_vol] : ps.filament_stats) { - assert(filament_id < extruders_filaments.size()); - if (const Preset* preset = extruders_filaments[filament_id].get_selected_preset()) { - double filament_weight; - if (ps.filament_stats.size() == 1) - filament_weight = ps.total_weight; - else { - double filament_density = preset->config.opt_float("filament_density", 0); - filament_weight = filament_vol * filament_density/* *2.4052f*/ * 0.001; // assumes 1.75mm filament diameter; - - new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament_id + 1); - info_text += wxString::Format("\n%.2f", filament_weight); - } - - double spool_weight = preset->config.opt_float("filament_spool_weight", 0); - if (spool_weight != 0.0) { - new_label += "\n " + _L("(including spool)"); - info_text += wxString::Format(" (%.2f)\n", filament_weight + spool_weight); - } - } - } - - p->sliced_info->SetTextAndShow(siFilament_g, info_text, new_label); - } - - new_label = _L("Cost"); - if (is_wipe_tower) - new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower")); - - info_text = ps.total_cost == 0.0 ? "N/A" : - is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost, - (ps.total_cost - ps.total_wipe_tower_cost), - ps.total_wipe_tower_cost) : - wxString::Format("%.2f", ps.total_cost); - p->sliced_info->SetTextAndShow(siCost, info_text, new_label); - - if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") - p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); - else { - info_text = ""; - new_label = _L("Estimated printing time") + ":"; - if (ps.estimated_normal_print_time != "N/A") { - new_label += format_wxstr("\n - %1%", _L("normal mode")); - info_text += format_wxstr("\n%1%", short_time_ui(ps.estimated_normal_print_time)); - - p->plater->get_notification_manager()->set_slicing_complete_print_time(_u8L("Estimated printing time") + ": " + ps.estimated_normal_print_time, p->plater->is_sidebar_collapsed()); - - } - if (ps.estimated_silent_print_time != "N/A") { - new_label += format_wxstr("\n - %1%", _L("stealth mode")); - info_text += format_wxstr("\n%1%", short_time_ui(ps.estimated_silent_print_time)); - } - p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); - } - - p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, ps.total_toolchanges > 0 ? wxString::Format("%.d", ps.total_toolchanges) : "N/A"); - - // Hide non-FFF sliced info parameters - p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); - } - } - - Layout(); -} - -void Sidebar::show_sliced_info_sizer(const bool show) -{ - wxWindowUpdateLocker freeze_guard(this); - - p->sliced_info->Show(show); - if (show) - update_sliced_info_sizer(); - - Layout(); - p->scrolled->Refresh(); -} - -void Sidebar::enable_buttons(bool enable) -{ - p->btn_reslice->Enable(enable); - p->btn_export_gcode->Enable(enable); - p->btn_send_gcode->Enable(enable); -// p->btn_eject_device->Enable(enable); - p->btn_export_gcode_removable->Enable(enable); -} - -//Y5 -void Sidebar::enable_export_buttons(bool enable) -{ - p->btn_export_gcode->Enable(enable); - p->btn_send_gcode->Enable(enable); -// p->btn_eject_device->Enable(enable); - p->btn_export_gcode_removable->Enable(enable); -} - -bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } -bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } -bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } -bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); } -//bool Sidebar::show_eject(bool show) const { return p->btn_eject_device->Show(show); } -//bool Sidebar::get_eject_shown() const { return p->btn_eject_device->IsShown(); } - -bool Sidebar::is_multifilament() -{ - return p->combos_filament.size() > 1; -} - -void Sidebar::check_and_update_searcher(bool respect_mode /*= false*/) -{ - std::vector search_inputs{}; - - auto& tabs_list = wxGetApp().tabs_list; - auto print_tech = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); - for (auto tab : tabs_list) - if (tab->supports_printer_technology(print_tech)) - search_inputs.emplace_back(Search::InputInfo{ tab->get_config(), tab->type() }); - - p->searcher.check_and_update(wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(), - respect_mode ? m_mode : comExpert, search_inputs); -} - -void Sidebar::update_mode() -{ - m_mode = wxGetApp().get_mode(); - - update_reslice_btn_tooltip(); - update_mode_sizer(); - - wxWindowUpdateLocker noUpdates(this); - - if (m_mode == comSimple) - p->object_manipulation->set_coordinates_type(ECoordinatesType::World); - - p->object_list->get_sizer()->Show(m_mode > comSimple); - - p->object_list->unselect_objects(); - p->object_list->update_selections(); - - Layout(); -} - -bool Sidebar::is_collapsed() { return p->is_collapsed; } - -void Sidebar::collapse(bool collapse) -{ - p->is_collapsed = collapse; - - this->Show(!collapse); - p->plater->Layout(); - - // save collapsing state to the AppConfig - if (wxGetApp().is_editor()) - wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0"); -} - -#ifdef _MSW_DARK_MODE -void Sidebar::show_mode_sizer(bool show) -{ - p->mode_sizer->Show(show); -} -#endif - -void Sidebar::update_ui_from_settings() -{ - p->object_manipulation->update_ui_from_settings(); - show_info_sizer(); - update_sliced_info_sizer(); - // update Cut gizmo, if it's open - p->plater->canvas3D()->update_gizmos_on_off_state(); - p->plater->set_current_canvas_as_dirty(); - p->plater->get_current_canvas3D()->request_extra_frame(); - p->object_list->apply_volumes_order(); -} - -std::vector& Sidebar::combos_filament() -{ - return p->combos_filament; -} - -Search::OptionsSearcher& Sidebar::get_searcher() -{ - return p->searcher; -} - -std::string& Sidebar::get_search_line() -{ - // update searcher before show imGui search dialog on the plater, if printer technology or mode was changed - check_and_update_searcher(true); - return p->searcher.search_string(); -} - // Plater::DropTarget class PlaterDropTarget : public wxFileDropTarget @@ -1765,6 +203,7 @@ bool emboss_svg(Plater& plater, const wxString &svg_file, const Vec2d& mouse_dro return svg->create_volume(svg_file_str, mouse_drop_position, ModelVolumeType::MODEL_PART); } } + bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { #ifdef WIN32 @@ -1776,6 +215,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi m_mainframe.select_tab(size_t(0)); if (wxGetApp().is_editor()) m_plater.select_view_3D("3D"); + // When only one .svg file is dropped on scene if (filenames.size() == 1) { const wxString &filename = filenames.Last(); @@ -1834,9 +274,14 @@ struct Plater::priv GLToolbar collapse_toolbar; Preview *preview; std::unique_ptr notification_manager; + std::unique_ptr user_account; + std::unique_ptr preset_archive_database; + // Login dialog needs to be kept somewhere. + // It is created inside evt Bind. But it might be closed from another event. + LoginWebViewDialog* login_dialog { nullptr }; ProjectDirtyStateManager dirty_state; - + BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; @@ -1866,6 +311,7 @@ struct Plater::priv static const std::regex pattern_any_amf; static const std::regex pattern_qidi; static const std::regex pattern_zip; + static const std::regex pattern_printRequest; priv(Plater *q, MainFrame *main_frame); ~priv(); @@ -1944,13 +390,14 @@ struct Plater::priv bool is_legend_shown() const { return (current_panel == preview) && preview->get_canvas3d()->is_legend_shown(); } void show_legend(bool show) { if (current_panel == preview) preview->get_canvas3d()->show_legend(show); } - bool is_sidebar_collapsed() const { return sidebar->is_collapsed(); } + bool is_sidebar_collapsed() const { return sidebar->is_collapsed; } void collapse_sidebar(bool collapse); bool is_view3D_layers_editing_enabled() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_layers_editing_enabled(); } void set_current_canvas_as_dirty(); GLCanvas3D* get_current_canvas3D(); + void render_sliders(GLCanvas3D& canvas); void unbind_canvas_event_handlers(); void reset_canvas_volumes(); @@ -1958,8 +405,7 @@ struct Plater::priv bool init_collapse_toolbar(); void set_preview_layers_slider_values_range(int bottom, int top); - - void update_preview_moves_slider(); + void update_preview_moves_slider(std::optional visible_range_min = std::nullopt, std::optional visible_range_max = std::nullopt); void enable_preview_moves_slider(bool enable); void reset_gcode_toolpaths(); @@ -1968,7 +414,6 @@ struct Plater::priv void apply_free_camera_correction(bool apply = true); void update_ui_from_settings(); void update_main_toolbar_tooltips(); -// std::shared_ptr statusbar(); bool get_config_bool(const std::string &key) const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config, bool used_inches = false); @@ -2055,7 +500,6 @@ struct Plater::priv void reload_all_from_disk(); void set_current_panel(wxPanel* panel); - void on_select_preset(wxCommandEvent&); void on_slicing_update(SlicingStatusEvent&); void on_slicing_completed(wxCommandEvent&); void on_process_completed(SlicingProcessCompletedEvent&); @@ -2087,18 +531,18 @@ struct Plater::priv void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); void show_action_buttons(const bool is_ready_to_slice) const; - + bool can_show_upload_to_connect() const; // Set the bed shape to a single closed 2D polygon(array of two element arrays), // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. //B52 - void set_bed_shape(const Pointfs & shape, - const double max_print_height, - const std::string &custom_texture, - const std::string &custom_model, - const Pointfs & exclude_bed_shape, - bool force_as_custom = false); + void set_bed_shape(const Pointfs& shape, + const double max_print_height, + const std::string& custom_texture, + const std::string& custom_model, + const Pointfs& exclude_bed_shape, + bool force_as_custom = false); bool can_delete() const; bool can_delete_all() const; @@ -2171,6 +615,7 @@ const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::ica const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase); const std::regex Plater::priv::pattern_qidi(".*qidi", std::regex::icase); const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase); +const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase); Plater::priv::priv(Plater *q, MainFrame *main_frame) : q(q) @@ -2190,6 +635,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , notification_manager(std::make_unique(q)) + , user_account(std::make_unique(q, wxGetApp().app_config, wxGetApp().get_instance_hash_string())) + , preset_archive_database(std::make_unique(wxGetApp().app_config, q)) , m_worker{q, std::make_unique(notification_manager.get()), "ui_worker"} , m_sla_import_dlg{new SLAImportDialog{q}} , delayed_scene_refresh(false) @@ -2197,14 +644,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , collapse_toolbar(GLToolbar::Normal, "Collapse") , m_project_filename(wxEmptyString) { - background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); background_process.set_gcode_result(&gcode_result); background_process.set_thumbnail_cb([this](const ThumbnailsParams& params) { return this->generate_thumbnails(params, Camera::EType::Ortho); }); background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); - background_process.set_export_began_event(EVT_EXPORT_BEGAN); + background_process.set_export_began_event(EVT_EXPORT_BEGAN); // Default printer technology for default config. background_process.select_technology(this->printer_technology); // Register progress callback from the Print class to the Plater. @@ -2219,10 +665,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D = new View3D(q, bed, &model, config, &background_process); preview = new Preview(q, bed, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); }); -#ifdef __APPLE__ // set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size view_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size); -#endif // __APPLE__ panels.push_back(view3D); panels.push_back(preview); @@ -2250,11 +694,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) if (wxGetApp().is_editor()) { // Preset change event - sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this); - sidebar->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); }); - sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); - // jump to found option from SearchDialog - q->Bind(wxCUSTOMEVT_JUMP_TO_OPTION, [this](wxCommandEvent& evt) { sidebar->jump_to_option(evt.GetInt()); }); + this->q->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); }); + this->q->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); } wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas(); @@ -2278,9 +719,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_RESET_SKEW, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MIRRORED, [this](SimpleEvent&) { update(); }); - view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event& evt) { this->sidebar->enable_buttons(evt.data); }); //Y5 view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_EXPORT_BUTTONS, [this](Event& evt) { this->sidebar->enable_export_buttons(evt.data); }); + view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event& evt) { this->sidebar->enable_buttons(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED, &priv::on_3dcanvas_mouse_dragging_started, this); view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); @@ -2317,12 +758,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { this->q->collapse_sidebar(!this->q->is_sidebar_collapsed()); }); } - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_JUMP_TO, [this](wxKeyEvent& evt) { preview->jump_layers_slider(evt); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_SLIDERS, [this](wxKeyEvent& evt) { - preview->move_layers_slider(evt); - preview->move_moves_slider(evt); - }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_layers_slider(evt); }); + if (wxGetApp().is_gcode_viewer()) preview->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { this->q->reload_gcode_from_disk(); }); @@ -2339,8 +775,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) q->Layout(); set_current_panel(wxGetApp().is_editor() ? static_cast(view3D) : static_cast(preview)); - if (wxGetApp().is_gcode_viewer()) - preview->hide_layers_slider(); // updates camera type from .ini file camera.enable_update_config_on_type_change(true); @@ -2368,7 +802,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) if (wxGetApp().is_editor()) { this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); - this->q->Bind(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, [](PresetUpdateAvailableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); + this->q->Bind(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, [](PresetUpdateAvailableClickedEvent&) { + GUI_App &app = wxGetApp(); + app.get_preset_updater()->on_update_notification_confirm(app.plater()->get_preset_archive_database()->get_selected_archive_repositories()); + }); this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { if (evt.data.second) { q->show_action_buttons(); @@ -2390,6 +827,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // Close notification ExportingFinished but only if last export was to removable notification_manager->device_ejected(); }); + this->q->Bind(EVT_REMOVABLE_DRIVE_ADDED, [this](wxCommandEvent& evt) { if (!fs::exists(fs::path(evt.GetString().utf8_string()) / "qidi_printer_settings.ini")) return; @@ -2407,6 +845,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) } }); + // Start the background thread and register this window as a target for update events. wxGetApp().removable_drive_manager()->init(this->q); #ifdef _WIN32 @@ -2421,7 +860,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // Reset the "dirty project" flag. m_undo_redo_stack_main.mark_current_as_saved(); dirty_state.update_from_undo_redo_stack(false); - + this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent& evt) { BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event."; wxArrayString input_files; @@ -2431,19 +870,186 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) wxGetApp().mainframe->Raise(); this->q->load_files(input_files); }); - - this->q->Bind(EVT_START_DOWNLOAD_OTHER_INSTANCE, [](StartDownloadOtherInstanceEvent& evt) { - BOOST_LOG_TRIVIAL(trace) << "Received url from other instance event."; - wxGetApp().mainframe->Raise(); - for (size_t i = 0; i < evt.data.size(); ++i) { - wxGetApp().start_download(evt.data[i]); - } - - }); - - this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) { + this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent&) { bring_instance_forward(); }); + // Downloader and USerAccount Events doesnt need to be binded in viewer. + // Not binding Account events prevents it from loging in. + if (wxGetApp().is_editor()) { + this->q->Bind(EVT_START_DOWNLOAD_OTHER_INSTANCE, [](StartDownloadOtherInstanceEvent& evt) { + BOOST_LOG_TRIVIAL(trace) << "Received url from other instance event."; + wxGetApp().mainframe->Raise(); + for (size_t i = 0; i < evt.data.size(); ++i) { + wxGetApp().start_download(evt.data[i]); + } + }); + this->q->Bind(EVT_LOGIN_OTHER_INSTANCE, [this](LoginOtherInstanceEvent& evt) { + BOOST_LOG_TRIVIAL(trace) << "Received login from other instance event."; + user_account->on_login_code_recieved(evt.data); + }); + this->q->Bind(EVT_LOGIN_VIA_WIZARD, [this](Event &evt) { + BOOST_LOG_TRIVIAL(trace) << "Received login from wizard."; + user_account->on_login_code_recieved(evt.data); + }); + this->q->Bind(EVT_OPEN_QIDIAUTH, [this](OpenQIDIAuthEvent& evt) { + BOOST_LOG_TRIVIAL(info) << "open login browser: " << evt.data.first; + std::string dialog_msg; + login_dialog = new LoginWebViewDialog(this->q, dialog_msg, evt.data.first, this->q); + if (login_dialog->ShowModal() == wxID_OK) { + user_account->on_login_code_recieved(dialog_msg); + } + if (login_dialog != nullptr) { + this->q->RemoveChild(login_dialog); + login_dialog->Destroy(); + login_dialog = nullptr; + } + }); + + auto open_external_login = [this](wxCommandEvent& evt){ + DownloaderUtils::Worker::perform_url_register(); +#if defined(__linux__) + // Remove all desktop files registering qidislicer:// url done by previous versions. + DesktopIntegrationDialog::undo_downloader_registration_rigid(); +#if defined(SLIC3R_DESKTOP_INTEGRATION) + if (DownloaderUtils::Worker::perform_registration_linux) + DesktopIntegrationDialog::perform_downloader_desktop_integration(); +#endif // SLIC3R_DESKTOP_INTEGRATION +#endif // __linux__ + std::string service; + if (evt.GetString().Find(L"accounts.google.com") != wxString::npos) { + service = "google"; + } else if (evt.GetString().Find(L"appleid.apple.com") != wxString::npos) { + service = "apple"; + } else if (evt.GetString().Find(L"facebook.com") != wxString::npos) { + service = "facebook"; + } + wxString url = user_account->get_login_redirect_url(service); + wxGetApp().open_login_browser_with_dialog(into_u8(url)); + }; + + this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, open_external_login); + this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN, open_external_login); + + this->q->Bind(EVT_UA_LOGGEDOUT, [this](UserAccountSuccessEvent& evt) { + user_account->clear(); + std::string text = _u8L("Logged out from QIDI Account."); + this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); + this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); + this->main_frame->remove_connect_webview_tab(); + this->main_frame->refresh_account_menu(true); + // Update sidebar printer status + sidebar->update_printer_presets_combobox(); + wxGetApp().update_wizard_login_page(); + this->show_action_buttons(this->ready_to_slice); + }); + + // this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) { + // if (login_dialog != nullptr) { + // login_dialog->EndModal(wxID_CANCEL); + // } + // // There are multiple handlers and we want to notify all + // evt.Skip(); + // std::string who = user_account->get_username(); + // std::string username; + // if (user_account->on_user_id_success(evt.data, username)) { + // // Do not show notification on refresh. + // if (who != username) { + // std::string text = format(_u8L("Logged to QIDI Account as %1%."), username); + // // login notification + // this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); + // // show connect tab + // this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); + // } + // this->main_frame->add_connect_webview_tab(); + // // Update User name in TopBar + // this->main_frame->refresh_account_menu(); + // wxGetApp().update_wizard_login_page(); + // this->show_action_buttons(this->ready_to_slice); + + // } else { + // // data were corrupt and username was not retrieved + // // procced as if EVT_UA_RESET was recieved + // BOOST_LOG_TRIVIAL(error) << "Reseting QIDI Account communication. Recieved data were corrupt."; + // user_account->clear(); + // this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); + // this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to QIDI Account.")); + // this->main_frame->remove_connect_webview_tab(); + // // Update User name in TopBar + // this->main_frame->refresh_account_menu(true); + // // Update sidebar printer status + // sidebar->update_printer_presets_combobox(); + // } + + // }); + // this->q->Bind(EVT_UA_RESET, [this](UserAccountFailEvent& evt) { + // BOOST_LOG_TRIVIAL(error) << "Reseting QIDI Account communication. Error message: " << evt.data; + // user_account->clear(); + // this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); + // this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to QIDI Account.")); + // this->main_frame->remove_connect_webview_tab(); + // // Update User name in TopBar + // this->main_frame->refresh_account_menu(true); + // // Update sidebar printer status + // sidebar->update_printer_presets_combobox(); + // }); + // this->q->Bind(EVT_UA_FAIL, [this](UserAccountFailEvent& evt) { + // BOOST_LOG_TRIVIAL(error) << "Failed communication with QIDI Account: " << evt.data; + // user_account->on_communication_fail(); + // }); + // this->q->Bind(EVT_UA_QIDICONNECT_STATUS_SUCCESS, [this](UserAccountSuccessEvent& evt) { + // std::string text; + // bool printers_changed = false; + // if (user_account->on_connect_printers_success(evt.data, wxGetApp().app_config, printers_changed)) { + // if (printers_changed) { + // sidebar->update_printer_presets_combobox(); + // } + // } else { + // // message was corrupt, procceed like EVT_UA_FAIL + // user_account->on_communication_fail(); + // } + // }); + // this->q->Bind(EVT_UA_QIDICONNECT_PRINTER_MODELS_SUCCESS, [this](UserAccountSuccessEvent& evt) { + // std::string text; + // bool printers_changed = false; + // if (user_account->on_connect_uiid_map_success(evt.data, wxGetApp().app_config, printers_changed)) { + // if (printers_changed) { + // sidebar->update_printer_presets_combobox(); + // } + // } + // else { + // // message was corrupt, procceed like EVT_UA_FAIL + // user_account->on_communication_fail(); + // } + // }); + // this->q->Bind(EVT_UA_AVATAR_SUCCESS, [this](UserAccountSuccessEvent& evt) { + // boost::filesystem::path path = user_account->get_avatar_path(true); + // FILE* file; + // file = boost::nowide::fopen(path.generic_string().c_str(), "wb"); + // if (file == NULL) { + // BOOST_LOG_TRIVIAL(error) << "Failed to create file to store avatar picture at: " << path; + // return; + // } + // fwrite(evt.data.c_str(), 1, evt.data.size(), file); + // fclose(file); + // this->main_frame->refresh_account_menu(true); + // }); + // this->q->Bind(EVT_UA_QIDICONNECT_PRINTER_DATA_SUCCESS, [this](UserAccountSuccessEvent& evt) { + // this->user_account->set_current_printer_data(evt.data); + // wxGetApp().handle_connect_request_printer_select_inner(evt.data); + // }); + // this->q->Bind(EVT_UA_QIDICONNECT_PRINTER_DATA_FAIL, [this](UserAccountFailEvent& evt) { + // BOOST_LOG_TRIVIAL(error) << "Failed communication with QIDI Account: " << evt.data; + // user_account->on_communication_fail(); + // std::string msg = _u8L("Failed to select printer from QIDI Connect."); + // this->notification_manager->close_notification_of_type(NotificationType::SelectFilamentFromConnect); + // this->notification_manager->push_notification(NotificationType::SelectFilamentFromConnect, NotificationManager::NotificationLevel::WarningNotificationLevel, msg); + // }); + + this->q->Bind(EVT_UA_REFRESH_TIME, [this](UserAccountTimeEvent& evt) { + this->user_account->set_refresh_time(evt.data); + }); + } + wxGetApp().other_instance_message_handler()->init(this->q); // collapse sidebar according to saved value @@ -2529,6 +1135,7 @@ void Plater::priv::collapse_sidebar(bool collapse) new_tooltip += " [Shift+Tab]"; int id = collapse_toolbar.get_item_id("collapse_sidebar"); collapse_toolbar.set_tooltip(id, new_tooltip); + collapse_toolbar.set_enabled(collapse || wxGetApp().app_config->get_bool("show_collapse_button")); notification_manager->set_sidebar_collapsed(collapse); } @@ -2549,6 +1156,10 @@ void Plater::priv::update_ui_from_settings() preview->get_canvas3d()->update_ui_from_settings(); sidebar->update_ui_from_settings(); + // update Cut gizmo, if it's open + q->canvas3D()->update_gizmos_on_off_state(); + q->set_current_canvas_as_dirty(); + q->get_current_canvas3D()->request_extra_frame(); } // Called after the print technology was changed. @@ -2558,12 +1169,6 @@ void Plater::priv::update_main_toolbar_tooltips() view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar(); } -//std::shared_ptr Plater::priv::statusbar() -//{ -// return nullptr; -// return main_frame->m_statusbar; -//} - bool Plater::priv::get_config_bool(const std::string &key) const { return wxGetApp().app_config->get_bool(key); @@ -2572,7 +1177,6 @@ bool Plater::priv::get_config_bool(const std::string &key) const void Plater::notify_about_installed_presets() { const auto& names = wxGetApp().preset_bundle->tmp_installed_presets; - // show notification about temporarily installed presets if (!names.empty()) { std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on the active instance of QIDISlicer", @@ -2652,6 +1256,14 @@ std::vector Plater::priv::load_files(const std::vector& input_ const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf); const bool type_qidi = std::regex_match(path.string(), pattern_qidi); + const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest); + + if (type_printRequest && printer_technology != ptSLA) { + Slic3r::GUI::show_info(nullptr, + _L("PrintRequest can only be loaded if an SLA printer is selected."), + _L("Error!")); + continue; + } Slic3r::Model model; bool is_project_file = type_qidi; @@ -2703,7 +1315,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (! config_substitutions.empty()) show_substitutions_info(config_substitutions.substitutions, filename.string()); - this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; + if (load_config) + this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; } if (load_config) { @@ -2721,6 +1334,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ msg_dlg.set_caption(wxString(SLIC3R_APP_NAME " - ") + _L("Attention!")); msg_dlg.ShowModal(); } + Preset::normalize(config); PresetBundle* preset_bundle = wxGetApp().preset_bundle; preset_bundle->load_config_model(filename.string(), std::move(config)); @@ -2822,12 +1436,12 @@ std::vector Plater::priv::load_files(const std::vector& input_ convert_model_if(model, answer_convert_from_imperial_units == wxID_YES); } - if (model.looks_like_multipart_object()) { + if (!type_printRequest && model.looks_like_multipart_object()) { if (answer_consider_as_multi_part_objects == wxOK_DEFAULT) { RichMessageDialog dlg(q, _L( - "This file contains several objects positioned at multiple heights.\n" - "Instead of considering them as multiple objects, should \n" - "the file be loaded as a single object having multiple parts?") + "\n", + "This file contains several objects positioned at multiple heights.\n" + "Instead of considering them as multiple objects, should \n" + "the file be loaded as a single object having multiple parts?") + "\n", _L("Multi-part object detected"), wxICON_QUESTION | wxYES_NO); dlg.ShowCheckBox(_L("Apply to all objects being loaded.")); int answer = dlg.ShowModal(); @@ -2861,6 +1475,57 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (!model_object->instances.empty()) model_object->ensure_on_bed(is_project_file); + if (type_printRequest) { + for (ModelInstance* obj_instance : model_object->instances) { + obj_instance->set_offset(obj_instance->get_offset() + Slic3r::to_3d(this->bed.build_volume().bed_center(), -model_object->origin_translation(2))); + } + } + } + if (type_printRequest) { + assert(model.materials.size()); + + for (const auto& material : model.materials) { + std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, + Preset::remove_suffix_modified(material.first)); + Preset* prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); + if (!prst) { //did not find compatible profile + // try find alias of material comaptible with another print profile - if exists, use the print profile + auto& prints = wxGetApp().preset_bundle->sla_prints; + std::string edited_print_name = prints.get_edited_preset().name; + bool found = false; + for (auto it = prints.begin(); it != prints.end(); ++it) + { + if (it->name != edited_print_name) { + BOOST_LOG_TRIVIAL(error) << it->name; + wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(it->name, false); + preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, + Preset::remove_suffix_modified(material.first)); + prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); + if (prst) { + found = true; + break; + } + } + } + if (!found) { + // return to original print profile + wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(edited_print_name, false); + std::string notif_text = GUI::format(_L("Material preset was not loaded:\n - %1%"), preset_name); + q->get_notification_manager()->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, notif_text); + break; + } + } + + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + if (preset_bundle->sla_materials.get_selected_preset_name() != preset_name) { + preset_bundle->sla_materials.select_preset_by_name(preset_name, false, true); + preset_bundle->tmp_installed_presets = { preset_name }; + q->notify_about_installed_presets(); + wxGetApp().load_current_presets(false);// For this case we shouldn't check printer_presets + } + break; + } } if (one_by_one) { @@ -2898,7 +1563,6 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (load_model && !in_temp) { wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().make_preferred().string()); // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames... - // statusbar()->set_status_text(_L("Loaded")); } // automatic selection of added objects @@ -3389,9 +2053,14 @@ void Plater::priv::process_validation_warning(const std::vector& wa if (warnings.empty()) notification_manager->close_notification_of_type(NotificationType::ValidateWarning); - for (std::string text : warnings) { - std::string hypertext = ""; - std::function action_fn = [](wxEvtHandler*){ return false; }; + // Always close warnings BedTemperaturesDiffer and ShrinkageCompensationsDiffer before next processing. + notification_manager->close_notification_of_type(NotificationType::BedTemperaturesDiffer); + notification_manager->close_notification_of_type(NotificationType::ShrinkageCompensationsDiffer); + + for (std::string text : warnings) { + std::string hypertext = ""; + NotificationType notification_type = NotificationType::ValidateWarning; + std::function action_fn = [](wxEvtHandler*){ return false; }; if (text == "_SUPPORTS_OFF") { text = _u8L("An object has custom support enforcers which will not be used " @@ -3407,12 +2076,17 @@ void Plater::priv::process_validation_warning(const std::vector& wa print_tab->on_value_change("support_material_auto", config.opt_bool("support_material_auto")); return true; }; + } else if (text == "_BED_TEMPS_DIFFER") { + text = _u8L("Bed temperatures for the used filaments differ significantly."); + notification_type = NotificationType::BedTemperaturesDiffer; + } else if (text == "_FILAMENT_SHRINKAGE_DIFFER") { + text = _u8L("Filament shrinkage will not be used because filament shrinkage " + "for the used filaments differs significantly."); + notification_type = NotificationType::ShrinkageCompensationsDiffer; } - if (text == "_BED_TEMPS_DIFFER") - text = _u8L("Bed temperatures for the used filaments differ significantly."); notification_manager->push_notification( - NotificationType::ValidateWarning, + notification_type, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("WARNING:") + "\n" + text, hypertext, action_fn ); @@ -3431,6 +2105,23 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(); if (full_config.has("binary_gcode")) // needed for SLA full_config.set("binary_gcode", bool(full_config.opt_bool("binary_gcode") & wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"))); + + const Preset &selected_printer = wxGetApp().preset_bundle->printers.get_selected_preset(); + std::string printer_model_serialized = full_config.option("printer_model")->serialize(); + std::string vendor_repo_prefix; + if (selected_printer.vendor) { + vendor_repo_prefix = selected_printer.vendor->repo_prefix; + } else if (std::string inherits = selected_printer.inherits(); !inherits.empty()) { + const Preset *parent = wxGetApp().preset_bundle->printers.find_preset(inherits); + if (parent && parent->vendor) { + vendor_repo_prefix = parent->vendor->repo_prefix; + } + } + if (printer_model_serialized.find(vendor_repo_prefix) == 0) { + printer_model_serialized = printer_model_serialized.substr(vendor_repo_prefix.size()); + boost::trim_left(printer_model_serialized); + full_config.set("printer_model", printer_model_serialized); + } // If the update_background_process() was not called by the timer, kill the timer, // so the update_restart_background_process() will not be called again in vain. background_process_timer.Stop(); @@ -3548,27 +2239,24 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool { // Validation of the background data failed. const wxString invalid_str = _L("Invalid data"); - for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport}) + for (auto btn : {ActionButtonType::Reslice, ActionButtonType::SendGCode, ActionButtonType::Export}) sidebar->set_btn_label(btn, invalid_str); process_completed_with_error = true; } else { // Background data is valid. -// if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || -// (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) -// this->statusbar()->set_status_text(_L("Ready to slice")); if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) notification_manager->set_slicing_progress_hidden(); - sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); - sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); + sidebar->set_btn_label(ActionButtonType::Export, _(label_btn_export)); + sidebar->set_btn_label(ActionButtonType::SendGCode, _(label_btn_send)); dirty_state.update_from_preview(); const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? _L("Slicing") + dots : _L("Slice now"); - sidebar->set_btn_label(ActionButtonType::abReslice, slice_string); + sidebar->set_btn_label(ActionButtonType::Reslice, slice_string); if (background_process.finished()) show_action_buttons(false); @@ -3598,10 +2286,6 @@ bool Plater::priv::restart_background_process(unsigned int state) (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { // The print is valid and it can be started. if (this->background_process.start()) { -// this->statusbar()->set_cancel_callback([this]() { -// this->statusbar()->set_status_text(_L("Cancelling")); -// this->background_process.stop(); -// }); if (!show_warning_dialog) on_slicing_began(); return true; @@ -3797,6 +2481,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const // update new name in ObjectList sidebar->obj_list()->update_name_in_list(object_idx, volume_idx); + sidebar->obj_list()->update_item_error_icon(object_idx, volume_idx); sla::reproject_points_and_holes(old_model_object); @@ -4211,7 +2896,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) q->reslice(); } // keeps current gcode preview, if any - preview->reload_print(true); + preview->reload_print(); } preview->set_as_dirty(); @@ -4225,74 +2910,6 @@ void Plater::priv::set_current_panel(wxPanel* panel) current_panel->SetFocusFromKbd(); } -void Plater::priv::on_select_preset(wxCommandEvent &evt) -{ - PlaterPresetComboBox* combo = static_cast(evt.GetEventObject()); - Preset::Type preset_type = combo->get_type(); - - // see https://github.com/qidi3d/QIDISlicer/issues/3889 - // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender"), - // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. - // So, use GetSelection() from event parameter - int selection = evt.GetSelection(); - - auto idx = combo->get_extruder_idx(); - - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, - //! but the OSX version derived from wxOwnerDrawnCombo. - //! So, to get selected string we do - //! combo->GetString(combo->GetSelection()) - //! instead of - //! combo->GetStringSelection().ToUTF8().data()); - - std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, - Preset::remove_suffix_modified(into_u8(combo->GetString(selection))), idx); - - std::string last_selected_ph_printer_name = combo->get_selected_ph_printer_name(); - - bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); - // TODO: ? - if (preset_type == Preset::TYPE_FILAMENT) { - wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); - - TabFilament* tab = dynamic_cast(wxGetApp().get_tab(Preset::TYPE_FILAMENT)); - if (tab && combo->get_extruder_idx() == tab->get_active_extruder() && !tab->select_preset(preset_name)) { - // revert previously selection - const std::string& old_name = wxGetApp().preset_bundle->filaments.get_edited_preset().name; - wxGetApp().preset_bundle->set_filament_preset(idx, old_name); - } - else - // Synchronize config.ini with the current selections. - wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); - combo->update(); - } - else if (select_preset) { - wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); - wxGetApp().get_tab(preset_type)->select_preset(preset_name, false, last_selected_ph_printer_name); - } - - if (preset_type != Preset::TYPE_PRINTER || select_preset) { - // update plater with new config - q->on_config_change(wxGetApp().preset_bundle->full_config()); - } - if (preset_type == Preset::TYPE_PRINTER) { - /* Settings list can be changed after printer preset changing, so - * update all settings items for all item had it. - * Furthermore, Layers editing is implemented only for FFF printers - * and for SLA presets they should be deleted - */ - wxGetApp().obj_list()->update_object_list_by_printer_technology(); - } - -#ifdef __WXMSW__ - // From the Win 2004 preset combobox lose a focus after change the preset selection - // and that is why the up/down arrow doesn't work properly - // (see https://github.com/qidi3d/QIDISlicer/issues/5531 ). - // So, set the focus to the combobox explicitly - combo->SetFocus(); -#endif -} - void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) { if (evt.status.percent >= -1) { @@ -4326,11 +2943,11 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) const std::string message_dial = GUI::format("%1% %2% %3%" , _L_PLURAL("You are using template filament preset.", "You are using template filament presets.", templ_cnt) , _u8L("Please note that template presets are not customized for specific printer and should only be used as a starting point for creating your own user presets.") - , "https://qidi3d.com/" + , "https://help.qidi3d.com/" ); BOOST_LOG_TRIVIAL(warning) << message_notif; - notification_manager->push_slicing_warning_notification(message_notif, false, 0, 0, "https://qidi3d.com/", - [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://qidi3d.com/"); return false; } + notification_manager->push_slicing_warning_notification(message_notif, false, 0, 0, "https://help.qidi3d.com/", + [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://help.qidi3d.com/article/template-filaments_467599"); return false; } ); add_warning({ PrintStateBase::WarningLevel::CRITICAL, true, message_dial, 0}, 0); } @@ -4484,8 +3101,6 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) // At this point of time the thread should be either finished or canceled, // so the following call just confirms, that the produced data were consumed. this->background_process.stop(); -// this->statusbar()->reset_cancel_callback(); -// this->statusbar()->stop_busy(); notification_manager->set_slicing_progress_export_possible(); // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. @@ -4506,18 +3121,16 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) } } else notification_manager->push_slicing_error_notification(message.first); -// this->statusbar()->set_status_text(from_u8(message.first)); if (evt.invalidate_plater()) { const wxString invalid_str = _L("Invalid data"); - for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) + for (auto btn : { ActionButtonType::Reslice, ActionButtonType::SendGCode, ActionButtonType::Export }) sidebar->set_btn_label(btn, invalid_str); process_completed_with_error = true; } has_error = true; } if (evt.cancelled()) { -// this->statusbar()->set_status_text(_L("Cancelled")); this->notification_manager->set_slicing_progress_canceled(_u8L("Slicing Cancelled.")); } @@ -4540,7 +3153,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) if (evt.cancelled()) { if (wxGetApp().get_mode() == comSimple) - sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); + sidebar->set_btn_label(ActionButtonType::Reslice, "Slice now"); show_action_buttons(true); } else { if(wxGetApp().get_mode() == comSimple) { @@ -4781,6 +3394,12 @@ GLCanvas3D* Plater::priv::get_current_canvas3D() return (current_panel == view3D) ? view3D->get_canvas3d() : ((current_panel == preview) ? preview->get_canvas3d() : nullptr); } +void Plater::priv::render_sliders(GLCanvas3D& canvas) +{ + if (current_panel == preview) + preview->render_sliders(canvas); +} + void Plater::priv::unbind_canvas_event_handlers() { if (view3D != nullptr) @@ -4801,8 +3420,8 @@ void Plater::priv::reset_canvas_volumes() bool Plater::priv::init_view_toolbar() { - if (wxGetApp().is_gcode_viewer()) - return true; + //if (wxGetApp().is_gcode_viewer()) + // return true; if (view_toolbar.get_items_count() > 0) // already initialized @@ -4820,15 +3439,18 @@ bool Plater::priv::init_view_toolbar() view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom); + //y15 view_toolbar.set_border(5.0f); view_toolbar.set_gap_size(1.0f); GLToolbarItem::Data item; + unsigned int sprite_id = 0; + item.name = "3D"; item.icon_filename = "editor.svg"; item.tooltip = _u8L("3D editor view") + " [" + GUI::shortkey_ctrl_prefix() + "5]"; - item.sprite_id = 0; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; if (!view_toolbar.add_item(item)) return false; @@ -4836,21 +3458,25 @@ bool Plater::priv::init_view_toolbar() item.name = "Preview"; item.icon_filename = "preview.svg"; item.tooltip = _u8L("Preview") + " [" + GUI::shortkey_ctrl_prefix() + "6]"; - item.sprite_id = 1; + item.sprite_id = sprite_id++; item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; if (!view_toolbar.add_item(item)) return false; + if (!view_toolbar.generate_icons_texture()) + return false; + view_toolbar.select_item("3D"); - view_toolbar.set_enabled(true); + if (wxGetApp().is_editor()) + view_toolbar.set_enabled(true); return true; } bool Plater::priv::init_collapse_toolbar() { - if (wxGetApp().is_gcode_viewer()) - return true; + //if (wxGetApp().is_gcode_viewer()) + // return true; if (collapse_toolbar.get_items_count() > 0) // already initialized @@ -4869,6 +3495,7 @@ bool Plater::priv::init_collapse_toolbar() collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + //y15 collapse_toolbar.set_border(5.0f); collapse_toolbar.set_separator_size(5); collapse_toolbar.set_gap_size(2); @@ -4885,9 +3512,13 @@ bool Plater::priv::init_collapse_toolbar() if (!collapse_toolbar.add_item(item)) return false; + if (!collapse_toolbar.generate_icons_texture()) + return false; + // Now "collapse" sidebar to current state. This is done so the tooltip // is updated before the toolbar is first used. - wxGetApp().plater()->collapse_sidebar(wxGetApp().plater()->is_sidebar_collapsed()); + if (wxGetApp().is_editor()) + wxGetApp().plater()->collapse_sidebar(wxGetApp().plater()->is_sidebar_collapsed()); return true; } @@ -4896,9 +3527,9 @@ void Plater::priv::set_preview_layers_slider_values_range(int bottom, int top) preview->set_layers_slider_values_range(bottom, top); } -void Plater::priv::update_preview_moves_slider() +void Plater::priv::update_preview_moves_slider(std::optional visible_range_min, std::optional visible_range_max) { - preview->update_moves_slider(); + preview->update_moves_slider(visible_range_min, visible_range_max); } void Plater::priv::enable_preview_moves_slider(bool enable) @@ -4978,13 +3609,14 @@ bool Plater::priv::can_reload_from_disk() const return !paths.empty(); } + //B52 -void Plater::priv::set_bed_shape(const Pointfs & shape, - const double max_print_height, - const std::string &custom_texture, - const std::string &custom_model, - const Pointfs & exclude_bed_shape, - bool force_as_custom) +void Plater::priv::set_bed_shape(const Pointfs& shape, + const double max_print_height, + const std::string& custom_texture, + const std::string& custom_model, + const Pointfs& exclude_bed_shape, + bool force_as_custom) { bool new_shape = bed.set_shape(shape, max_print_height, custom_texture, custom_model, exclude_bed_shape, force_as_custom); @@ -5101,6 +3733,26 @@ bool Plater::priv::can_layers_editing() const return layers_height_allowed(); } +bool Plater::priv::can_show_upload_to_connect() const +{ + if (!user_account->is_logged()) { + return false; + } + const Preset& selected_printer = wxGetApp().preset_bundle->printers.get_selected_preset(); + std::string vendor_id; + if (selected_printer.vendor ) { + vendor_id = selected_printer.vendor->id; + } else if (std::string inherits = selected_printer.inherits(); !inherits.empty()) { + const Preset* parent = wxGetApp().preset_bundle->printers.find_preset(inherits); + if (parent && parent->vendor) { + vendor_id = parent->vendor->id; + } + } + // Upload to Connect should show only for qidi printers + // Some vendors might have prefixed name due to repository id. + return vendor_id.find("QIDI") != std::string::npos; +} + void Plater::priv::show_action_buttons(const bool ready_to_slice_) const { // Cache this value, so that the callbacks from the RemovableDriveManager may repeat that value when calling show_action_buttons(). @@ -5111,7 +3763,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); const auto print_host_opt = selected_printer_config ? selected_printer_config->option("print_host") : nullptr; const bool send_gcode_shown = print_host_opt != nullptr && !print_host_opt->value.empty(); - + const bool connect_gcode_shown = print_host_opt == nullptr && can_show_upload_to_connect(); auto m_devices = wxGetApp().get_devices(); const bool link_has_machine = m_devices.size() > 0; @@ -5121,8 +3773,8 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const RemovableDriveManager::RemovableDrivesStatus removable_media_status = wxGetApp().removable_drive_manager()->status(); if (sidebar->show_reslice(false) | sidebar->show_export(true) | sidebar->show_send(send_gcode_shown | link_has_machine) | + // sidebar->show_connect(connect_gcode_shown) | sidebar->show_export_removable(removable_media_status.has_removable_drives)) -// sidebar->show_eject(removable_media_status.has_eject)) sidebar->Layout(); } else @@ -5133,8 +3785,8 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const if (sidebar->show_reslice(ready_to_slice) | sidebar->show_export(!ready_to_slice) | sidebar->show_send((send_gcode_shown | link_has_machine) && !ready_to_slice) | + sidebar->show_connect(connect_gcode_shown && !ready_to_slice) | sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives)) -// sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject)) sidebar->Layout(); } } @@ -5422,16 +4074,6 @@ void Plater::priv::bring_instance_forward() const } } -void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const -{ - switch (btn_type) - { - case ActionButtonType::abReslice: p->btn_reslice->SetLabelText(label); break; - case ActionButtonType::abExport: p->btn_export_gcode->SetLabelText(label); break; - case ActionButtonType::abSendGCode: /*p->btn_send_gcode->SetLabelText(label);*/ break; - } -} - // Plater / Public Plater::Plater(wxWindow *parent, MainFrame *main_frame) @@ -5441,6 +4083,8 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame) // Initialization performed in the private c-tor } +Plater::~Plater() = default; + bool Plater::is_project_dirty() const { return p->is_project_dirty(); } bool Plater::is_presets_dirty() const { return p->is_presets_dirty(); } void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); } @@ -6118,9 +4762,6 @@ void Plater::add_num_text(std::string num, Vec2d posotion) emboss->create_volume(volume_type, posotion, num); } - - - void Plater::import_zip_archive() { wxString input_file; @@ -6170,7 +4811,7 @@ void Plater::load_gcode(const wxString& filename) // cleanup view before to start loading/processing p->gcode_result.reset(); reset_gcode_toolpaths(); - p->preview->reload_print(false); + p->preview->reload_print(); p->get_current_canvas3D()->render(); wxBusyCursor wait; @@ -6179,7 +4820,11 @@ void Plater::load_gcode(const wxString& filename) GCodeProcessor processor; try { - processor.process_file(filename.ToUTF8().data()); + p->notification_manager->push_download_progress_notification("Loading...", []() { return false; }); + processor.process_file(filename.ToUTF8().data(), [this](float value) { + p->notification_manager->set_download_progress_percentage(value); + p->get_current_canvas3D()->render(); + }); } catch (const std::exception& ex) { @@ -6191,7 +4836,7 @@ void Plater::load_gcode(const wxString& filename) // show results try { - p->preview->reload_print(false); + p->preview->reload_print(); } catch (const std::exception&) { @@ -6199,7 +4844,7 @@ void Plater::load_gcode(const wxString& filename) p->gcode_result.reset(); reset_gcode_toolpaths(); set_default_bed_shape(); - p->preview->reload_print(false); + p->preview->reload_print(); p->get_current_canvas3D()->render(); MessageDialog(this, _L("The selected file") + ":\n" + filename + "\n" + _L("does not contain valid gcode."), wxString(GCODEVIEWER_APP_NAME) + " - " + _L("Error while loading .gcode file"), wxOK | wxICON_WARNING | wxCENTRE).ShowModal(); @@ -6409,9 +5054,10 @@ void Plater::convert_gcode_to_binary() _L("Convert G-code file to binary format"), wxICON_ERROR | wxOK); msg_dlg.ShowModal(); } -void Plater::refresh_print() + +void Plater::reload_print() { - p->preview->refresh_print(); + p->preview->reload_print(); } std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); } @@ -6606,7 +5252,7 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) if (size != stat.m_uncomp_size) // size must fit continue; wxString wname = boost::nowide::widen(stat.m_filename); - std::string name = boost::nowide::narrow(wname); + std::string name = into_u8(wname); fs::path archive_path(name); std::string extra(1024, 0); @@ -6643,7 +5289,7 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { // TRN: First argument = path to file, second argument = error description - wxString error_log = GUI::format_wxstr(_L("Failed to unzip file to %1%: %2% "), final_path.string(), mz_zip_get_error_string(mz_zip_get_last_error(&archive))); + wxString error_log = GUI::format_wxstr(_L("Failed to unzip file to %1%: %2%"), final_path.string(), mz_zip_get_error_string(mz_zip_get_last_error(&archive))); BOOST_LOG_TRIVIAL(error) << error_log; show_error(nullptr, error_log); break; @@ -6916,7 +5562,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect) bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*=false*/) { - const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|qidi|step|stp|zip)", std::regex::icase); + const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|qidi|step|stp|zip|printRequest)", std::regex::icase); const std::regex pattern_gcode_drop(".*[.](gcode|g|bgcode|bgc)", std::regex::icase); std::vector paths; @@ -7365,6 +6011,8 @@ void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& ne w.wait_for_idle(); } + + static wxString check_binary_vs_ascii_gcode_extension(PrinterTechnology pt, const std::string& ext, bool binary_output) { wxString err_out; @@ -7420,6 +6068,7 @@ static void alert_when_exporting_binary_gcode(bool binary_output, const std::str } } } + void Plater::export_gcode(bool prefer_removable) { if (p->model.objects.empty()) @@ -7475,10 +6124,11 @@ void Plater::export_gcode(bool prefer_removable) ); if (dlg.ShowModal() == wxID_OK) { output_path = into_path(dlg.GetPath()); + auto check_for_error = [this](const boost::filesystem::path& path, wxString& err_out) -> bool { const std::string filename = path.filename().string(); const std::string ext = boost::algorithm::to_lower_copy(path.extension().string()); - if (has_illegal_filename_characters(filename)) { + if (has_illegal_characters(filename)) { err_out = _L("The provided file name is not valid.") + "\n" + _L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\""; return true; @@ -7493,8 +6143,9 @@ void Plater::export_gcode(bool prefer_removable) wxString error_str; if (check_for_error(output_path, error_str)) { - ErrorDialog(this, error_str, [this](const std::string& key) -> void { sidebar().jump_to_option(key); }).ShowModal(); - output_path.clear(); + const t_link_clicked on_link_clicked = [](const std::string& key) -> void { wxGetApp().jump_to_option(key); }; + ErrorDialog(this, error_str, on_link_clicked).ShowModal(); + output_path.clear(); } else if (printer_technology() == ptFFF) { bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); @@ -7570,6 +6221,8 @@ void Plater::export_stl_obj(bool extended, bool selection_only) TriangleMesh vols_mesh(mesh); mesh = TriangleMesh(); for (const ModelInstance* i : mo.instances) { + if (!i->is_printable()) + continue; TriangleMesh m = vols_mesh; m.transform(i->get_matrix(), true); mesh.merge(m); @@ -7585,8 +6238,10 @@ void Plater::export_stl_obj(bool extended, bool selection_only) const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); - if (!object || !object->get_mesh_to_print() || object->get_mesh_to_print()->empty()) - mesh = mesh_to_export_fff(mo, instance_id); + if (!object || !object->get_mesh_to_print() || object->get_mesh_to_print()->empty()) { + if (!extended) + mesh = mesh_to_export_fff(mo, instance_id); + } else { const Transform3d mesh_trafo_inv = object->trafo().inverse(); const bool is_left_handed = object->is_left_handed(); @@ -7682,28 +6337,6 @@ void Plater::export_stl_obj(bool extended, bool selection_only) Slic3r::store_stl(path_u8.c_str(), &mesh, true); else if (path.Lower().EndsWith(".obj")) Slic3r::store_obj(path_u8.c_str(), &mesh); -// p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); -} - -void Plater::export_amf() -{ - if (p->model.objects.empty()) { return; } - - wxString path = p->get_export_file(FT_AMF); - if (path.empty()) { return; } - const std::string path_u8 = into_u8(path); - - wxBusyCursor wait; - bool export_config = true; - DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); - bool full_pathnames = wxGetApp().app_config->get_bool("export_sources_full_pathnames"); - if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { - // Success -// p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path)); - } else { - // Failure -// p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path)); - } } namespace { @@ -7808,13 +6441,6 @@ void publish(Model &model) { } } } -//B61 -ThumbnailData Plater::get_thumbnailldate() { - ThumbnailData thumbnail_data; - ThumbnailsParams thumbnail_params = {{}, false, true, true, true}; - p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, thumbnail_params, Camera::EType::Ortho); - return thumbnail_data; -} //B64 ThumbnailData Plater::get_thumbnailldate_send() { @@ -7846,6 +6472,7 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path) // take care about private data stored into .3mf // modify model publish(p->model); + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); wxBusyCursor wait; @@ -7866,13 +6493,11 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path) } if (ret) { // Success -// p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); BOOST_LOG_TRIVIAL(info) << "3MF file exported to " << path; p->set_project_filename(path); } else { // Failure -// p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); const wxString what = GUI::format_wxstr("%1%: %2%", _L("Unable to save file") , path_u8); show_error(this, what); } @@ -7955,10 +6580,10 @@ void Plater::reslice() if (p->background_process.running()) { if (wxGetApp().get_mode() == comSimple) - p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slicing") + dots); + p->sidebar->set_btn_label(ActionButtonType::Reslice, _L("Slicing") + dots); else { - p->sidebar->set_btn_label(ActionButtonType::abReslice, _L("Slice now")); + p->sidebar->set_btn_label(ActionButtonType::Reslice, _L("Slice now")); p->show_action_buttons(false); } } @@ -7970,7 +6595,7 @@ void Plater::reslice() if (clean_gcode_toolpaths) reset_gcode_toolpaths(); - p->preview->reload_print(!clean_gcode_toolpaths); + p->preview->reload_print(); } void Plater::reslice_until_step_inner(int step, const ModelObject &object, bool postpone_error_messages) @@ -8009,6 +6634,119 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject & this->reslice_until_step_inner(SLAPrintObjectStep(step), object, postpone_error_messages); } +namespace { +bool load_secret(const std::string& id, const std::string& opt, std::string& usr, std::string& psswd) +{ +#if wxUSE_SECRETSTORE + wxSecretStore store = wxSecretStore::GetDefault(); + wxString errmsg; + if (!store.IsOk(&errmsg)) { + std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return false; + } + const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, id, opt); + wxString username; + wxSecretValue password; + if (!store.Load(service, username, password)) { + std::string msg(_u8L("Failed to load credentials from the system password store.")); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return false; + } + usr = into_u8(username); + psswd = into_u8(password.GetAsString()); + return true; +#else + BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot load password from the system store."; + return false; +#endif // wxUSE_SECRETSTORE +} +} +void Plater::connect_gcode() +{ + assert(p->user_account->is_logged()); + std::string dialog_msg; + { + PrinterPickWebViewDialog dialog(this, dialog_msg); + if (dialog.ShowModal() != wxID_OK) { + return; + } + } + if (dialog_msg.empty()) { + show_error(this, _L("Failed to select a printer.")); + return; + } + BOOST_LOG_TRIVIAL(debug) << "Message from Printer pick webview: " << dialog_msg; + +/* +{ + set_ready: boolean, // uzivatel potvrdil ze je tiskarne ready a muze se tisknout, pouziva se pro tisknout ted a odlozeny tisk + position: -1 | 0, // -1 = posledni ve fronte, 0 = prvni ve fronte + wait_until: number | undefined, // timestamp pro odlozeny tisk + file_name: string, // tady budeme predavat jak se uzivatel rozhodl soubor pojmenovat, kdyz ho neprejmenuje, tak vratime to stejne co nam predtim posle slicer + printer_uuid: string // uuid vybrane tiskarny +} +*/ + const Preset* selected_printer_preset = &wxGetApp().preset_bundle->printers.get_selected_preset(); + + boost::property_tree::ptree ptree; + const std::string filename = UserAccountUtils::get_keyword_from_json(ptree, dialog_msg, "filename"); + const std::string team_id = UserAccountUtils::get_keyword_from_json(ptree, dialog_msg, "team_id"); + + std::string data_subtree = UserAccountUtils::get_print_data_from_json(dialog_msg, "data"); + if (filename.empty() || team_id.empty() || data_subtree.empty()) { + std::string msg = _u8L("Failed to read response from QIDI Connect server. Upload is cancelled."); + BOOST_LOG_TRIVIAL(error) << msg; + BOOST_LOG_TRIVIAL(error) << "Response: " << dialog_msg; + show_error(this, msg); + return; + } + + + PhysicalPrinter ph_printer("connect_temp_printer", wxGetApp().preset_bundle->physical_printers.default_config(), *selected_printer_preset); + ph_printer.config.set_key_value("host_type", new ConfigOptionEnum(htQIDIConnectNew)); + // use existing structures to pass data + ph_printer.config.opt_string("printhost_apikey") = team_id; + DynamicPrintConfig* physical_printer_config = &ph_printer.config; + + PrintHostJob upload_job(physical_printer_config); + assert(!upload_job.empty()); + + upload_job.upload_data.data_json = data_subtree; + upload_job.upload_data.upload_path = boost::filesystem::path(filename); + + p->export_gcode(fs::path(), false, std::move(upload_job)); + +} + +std::string Plater::get_upload_filename() +{ + // Obtain default output path + fs::path default_output_file; + try { + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) + return {}; + default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); + } + catch (const Slic3r::PlaceholderParserError& ex) { + // Show the error with monospaced font. + show_error(this, ex.what(), true); + return {}; + } + catch (const std::exception& ex) { + show_error(this, ex.what(), false); + return {}; + } + default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); + + return default_output_file.filename().string(); +} + void Plater::send_gcode() { // if physical_printer is selected, send gcode for this printer @@ -8021,22 +6759,7 @@ void Plater::send_gcode() // Obtain default output path fs::path default_output_file; - try { - // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. - // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. - unsigned int state = this->p->update_restart_background_process(false, false); - if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) - return; - default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); - } catch (const Slic3r::PlaceholderParserError& ex) { - // Show the error with monospaced font. - show_error(this, ex.what(), true); - return; - } catch (const std::exception& ex) { - show_error(this, ex.what(), false); - return; - } - default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); + default_output_file = get_upload_filename(); // Repetier specific: Query the server for the list of file groups. wxArrayString groups; @@ -8060,11 +6783,10 @@ void Plater::send_gcode() } else { only_link = true; } - + max_send_number = std::stoi(wxGetApp().app_config->get("max_send")); //B61 PrintHostSendDialog dlg(default_output_file, PrintHostPostUploadAction::StartPrint, groups, storage_paths, storage_names, this, (this->fff_print().print_statistics()), only_link); - if (dlg.ShowModal() == wxID_OK) { if (printer_technology() == ptFFF) { const std::string ext = boost::algorithm::to_lower_copy(dlg.filename().extension().string()); @@ -8072,10 +6794,11 @@ void Plater::send_gcode() wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); const wxString error_str = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, binary_output); if (! error_str.IsEmpty()) { - ErrorDialog(this, error_str, t_kill_focus([](const std::string &key) -> void { wxGetApp().sidebar().jump_to_option(key); })) + ErrorDialog(this, error_str, t_kill_focus([](const std::string &key) -> void { wxGetApp().jump_to_option(key); })) .ShowModal(); return; } + bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode"); bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported"); alert_when_exporting_binary_gcode(supports_binary && uses_binary, @@ -8087,11 +6810,14 @@ void Plater::send_gcode() auto checkbox_status = dlg.checkbox_states(); auto checkbox_net_status = dlg.checkbox_net_states(); - int count = 0; - + if (max_send_number != std::stoi(wxGetApp().app_config->get("max_send"))) { + float i = (std::stoi(wxGetApp().app_config->get("max_send")) * 1.0) / max_send_number; + UploadCount *= i; + max_send_number = std::stoi(wxGetApp().app_config->get("max_send")); + } std::chrono::system_clock::time_point curr_time = std::chrono::system_clock::now(); - auto diff = std::chrono::duration_cast(curr_time - m_time_p); + //auto diff = std::chrono::duration_cast(curr_time - m_time_p); for (int i = 0; i < pppd.size(); i++) { if (checkbox_status[i]) { auto preset_data = pppd[i]; @@ -8105,30 +6831,35 @@ void Plater::send_gcode() upload_job.upload_data.group = dlg.group(); upload_job.upload_data.storage = dlg.storage(); upload_job.create_time = std::chrono::system_clock::now(); - if (diff.count()<0) - upload_job.sendinginterval = count / std::stoi(wxGetApp().app_config->get("max_send")) * - std::stoi(wxGetApp().app_config->get("sending_interval")) * 60 - - diff.count()+4; - else - upload_job.sendinginterval = count / std::stoi(wxGetApp().app_config->get("max_send")) * - std::stoi(wxGetApp().app_config->get("sending_interval")) * 60; + + //if (diff.count() < 0) + // upload_job.sendinginterval = UploadCount / std::stoi(wxGetApp().app_config->get("max_send")) * + // std::stoi(wxGetApp().app_config->get("sending_interval")) * 60 - + // diff.count(); + //else + // upload_job.sendinginterval = UploadCount / std::stoi(wxGetApp().app_config->get("max_send")) * + // std::stoi(wxGetApp().app_config->get("sending_interval")) * 60; + if (UploadCount != 0 && UploadCount % std::stoi(wxGetApp().app_config->get("max_send")) == 0) { + m_sending_interval += std::stoi(wxGetApp().app_config->get("sending_interval")) * 60; + } + upload_job.sendinginterval = m_sending_interval; // Show "Is printer clean" dialog for QIDIConnect - Upload and print. if (std::string(upload_job.printhost->get_name()) == "QIDIConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) { GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL); - if (dlg.ShowModal() != wxID_OK) + if (dlg.ShowModal() != wxID_OK) return; } - std::chrono::seconds seconds_to_add(upload_job.sendinginterval); + //std::chrono::seconds seconds_to_add(upload_job.sendinginterval); - m_time_p = upload_job.create_time + seconds_to_add; + //m_time_p = upload_job.create_time + seconds_to_add; p->export_gcode(fs::path(), false, std::move(upload_job)); - count++; + UploadCount++; } } #if QDT_RELEASE_TO_PUBLIC @@ -8137,23 +6868,28 @@ void Plater::send_gcode() if (checkbox_net_status[i]) { auto device = m_devices[i]; PrintHostJob upload_job(device.url,device.local_ip); - if (upload_job.empty()) + if (upload_job.empty()) return; upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.post_action = dlg.post_action(); upload_job.upload_data.group = dlg.group(); upload_job.upload_data.storage = dlg.storage(); upload_job.create_time = std::chrono::system_clock::now(); - if (diff.count() < 0) - upload_job.sendinginterval = count / std::stoi(wxGetApp().app_config->get("max_send")) * - std::stoi(wxGetApp().app_config->get("sending_interval")) * 60 - - diff.count()+4; - else - upload_job.sendinginterval = count / std::stoi(wxGetApp().app_config->get("max_send")) * - std::stoi(wxGetApp().app_config->get("sending_interval")) * 60; + //if (diff.count() < 0) + // upload_job.sendinginterval = UploadCount / std::stoi(wxGetApp().app_config->get("max_send")) * + // std::stoi(wxGetApp().app_config->get("sending_interval")) * 60 - + // diff.count()+4; + //else + // upload_job.sendinginterval = UploadCount / std::stoi(wxGetApp().app_config->get("max_send")) * + // std::stoi(wxGetApp().app_config->get("sending_interval")) * 60; + + if (UploadCount != 0 && UploadCount % std::stoi(wxGetApp().app_config->get("max_send")) == 0) { + m_sending_interval += std::stoi(wxGetApp().app_config->get("sending_interval")) * 60; + } + upload_job.sendinginterval = m_sending_interval; // Show "Is printer clean" dialog for QIDIConnect - Upload and print. - if (std::string(upload_job.printhost->get_name()) == "QIDIConnect" && + if (std::string(upload_job.printhost->get_name()) == "QIDIConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) { GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL); @@ -8161,11 +6897,11 @@ void Plater::send_gcode() return; } - std::chrono::seconds seconds_to_add(upload_job.sendinginterval); + //std::chrono::seconds seconds_to_add(upload_job.sendinginterval); - m_time_p = upload_job.create_time + seconds_to_add; + //m_time_p = upload_job.create_time + seconds_to_add; p->export_gcode(fs::path(), false, std::move(upload_job)); - count++; + UploadCount++; } } #endif @@ -8233,48 +6969,6 @@ void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& ou out_text = ""; } -bool Plater::search_string_getter(int idx, const char** label, const char** tooltip) -{ - const Search::OptionsSearcher& search_list = p->sidebar->get_searcher(); - - if (0 <= idx && (size_t)idx < search_list.size()) { - search_list[idx].get_marked_label_and_tooltip(label, tooltip); - return true; - } - - return false; -} - -void Plater::on_extruders_change(size_t num_extruders) -{ - auto& choices = sidebar().combos_filament(); - - if (num_extruders == choices.size()) - return; - - dynamic_cast(wxGetApp().get_tab(Preset::TYPE_FILAMENT))->update_extruder_combobox(); - - wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/); - - size_t i = choices.size(); - while ( i < num_extruders ) - { - PlaterPresetComboBox* choice/*{ nullptr }*/; - sidebar().init_filament_combo(&choice, i); - choices.push_back(choice); - - // initialize selection - choice->update(); - ++i; - } - - // remove unused choices if any - sidebar().remove_unused_filament_combos(num_extruders); - - sidebar().Layout(); - sidebar().scrolled_panel()->Refresh(); -} - bool Plater::update_filament_colors_in_full_config() { // There is a case, when we use filament_color instead of extruder_color (when extruder_color == ""). @@ -8320,6 +7014,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) p->reset_gcode_toolpaths(); p->view3D->get_canvas3d()->reset_sequential_print_clearance(); p->view3D->get_canvas3d()->set_sla_view_type(GLCanvas3D::ESLAViewType::Original); + p->preview->get_canvas3d()->reset_volumes(); } //B52 else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model" || @@ -8372,34 +7067,34 @@ void Plater::set_bed_shape() const auto exclude_area = p->config->option("bed_exclude_area")->values; Pointfs tem_shape = bed_shape; - tem_shape.push_back({0, 0}); - for (const auto &point : exclude_area) { - tem_shape.push_back({point.x(), point.y()}); + tem_shape.push_back({ 0, 0 }); + for (const auto& point : exclude_area) { + tem_shape.push_back({ point.x(), point.y() }); } - tem_shape.push_back({0, 0}); + tem_shape.push_back({ 0, 0 }); set_bed_shape(tem_shape, p->config->option("max_print_height")->value, p->config->option("bed_custom_texture")->value, - p->config->option("bed_custom_model")->value, exclude_area); + p->config->option("bed_custom_model")->value, exclude_area); } - //B52 - void Plater::set_bed_shape(const Pointfs & shape, - const double max_print_height, - const std::string &custom_texture, - const std::string &custom_model, - const Pointfs & exclude_bed_shape, - bool force_as_custom) const +//B52 +void Plater::set_bed_shape(const Pointfs& shape, + const double max_print_height, + const std::string& custom_texture, + const std::string& custom_model, + const Pointfs& exclude_bed_shape, + bool force_as_custom) const { - p->set_bed_shape(shape, max_print_height, custom_texture, custom_model, exclude_bed_shape, force_as_custom); + p->set_bed_shape(shape, max_print_height, custom_texture, custom_model, exclude_bed_shape, force_as_custom); } //B52 void Plater::set_default_bed_shape() const { - set_bed_shape({{0.0, 0.0}, {200.0, 0.0}, {200.0, 200.0}, {0.0, 200.0}}, 0.0, {}, {}, { + set_bed_shape({ {0.0, 0.0}, {200.0, 0.0}, {200.0, 200.0}, {0.0, 200.0} }, 0.0, {}, {}, { {0.0, 0.0} - }, true); + }, true); } void Plater::force_filament_colors_update() @@ -8455,13 +7150,15 @@ void Plater::force_print_bed_update() p->config->opt_string("printer_model", true) = "\x01\x00\x01"; } -void Plater::on_activate() +void Plater::on_activate(bool active) { - this->p->show_delayed_error_message(); + if (active) { + this->p->show_delayed_error_message(); + } } // Get vector of extruder colors considering filament color, if extruder color is undefined. -std::vector Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result) const +std::vector Plater::get_extruder_color_strings_from_plater_config(const GCodeProcessorResult* const result) const { if (wxGetApp().is_gcode_viewer() && result != nullptr) return result->extruder_colors; @@ -8487,9 +7184,9 @@ std::vector Plater::get_extruder_colors_from_plater_config(const GC /* Get vector of colors used for rendering of a Preview scene in "Color print" mode * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z */ -std::vector Plater::get_colors_for_color_print(const GCodeProcessorResult* const result) const +std::vector Plater::get_color_strings_for_color_print(const GCodeProcessorResult* const result) const { - std::vector colors = get_extruder_colors_from_plater_config(result); + std::vector colors = get_extruder_color_strings_from_plater_config(result); colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size()); if (wxGetApp().is_gcode_viewer() && result != nullptr) { @@ -8508,6 +7205,22 @@ std::vector Plater::get_colors_for_color_print(const GCodeProcessor return colors; } +std::vector Plater::get_extruder_colors_from_plater_config() const +{ + std::vector colors = get_extruder_color_strings_from_plater_config(); + std::vector ret; + decode_colors(colors, ret); + return ret; +} + +std::vector Plater::get_colors_for_color_print() const +{ + std::vector colors = get_color_strings_for_color_print(); + std::vector ret; + decode_colors(colors, ret); + return ret; +} + wxString Plater::get_project_filename(const wxString& extension) const { return p->get_project_filename(extension); @@ -8553,6 +7266,11 @@ GLCanvas3D* Plater::get_current_canvas3D() return p->get_current_canvas3D(); } +void Plater::render_sliders(GLCanvas3D& canvas) +{ + p->render_sliders(canvas); +} + static std::string concat_strings(const std::set &strings, const std::string &delim = "\n") { @@ -8669,6 +7387,20 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology) return ret; } +void Plater::clear_before_change_volume(ModelVolume &mv, const std::string ¬ification_msg) { + // When we change the geometry of the volume, we remove any custom supports/seams/multi-material painting. + if (const bool paint_removed = !mv.supported_facets.empty() || !mv.seam_facets.empty() || !mv.mm_segmentation_facets.empty(); paint_removed) { + mv.supported_facets.reset(); + mv.seam_facets.reset(); + mv.mm_segmentation_facets.reset(); + + get_notification_manager()->push_notification( + NotificationType::CustomSupportsAndSeamRemovedAfterRepair, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + notification_msg); + } +} + void Plater::clear_before_change_mesh(int obj_idx, const std::string ¬ification_msg) { ModelObject* mo = model().objects[obj_idx]; @@ -8709,7 +7441,7 @@ void Plater::changed_mesh(int obj_idx) { ModelObject* mo = model().objects[obj_idx]; if (p->printer_technology == ptSLA) - sla::reproject_points_and_holes(mo); + sla::reproject_points_and_holes(mo); update(); p->object_list_changed(); p->schedule_background_process(); @@ -8718,6 +7450,7 @@ void Plater::changed_mesh(int obj_idx) 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); @@ -8822,29 +7555,6 @@ void Plater::paste_from_clipboard() p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } -void Plater::search(bool plater_is_active) -{ - if (plater_is_active) { - if (is_preview_shown()) - return; - // plater should be focused for correct navigation inside search window - this->SetFocus(); - - wxKeyEvent evt; -#ifdef __APPLE__ - evt.m_keyCode = 'f'; -#else /* __APPLE__ */ - evt.m_keyCode = WXK_CONTROL_F; -#endif /* __APPLE__ */ - evt.SetControlDown(true); - canvas3D()->on_char(evt); - } - else { - p->sidebar->check_and_update_searcher(true); - p->sidebar->get_searcher().show_dialog(); - } -} - void Plater::msw_rescale() { p->preview->msw_rescale(); @@ -8859,7 +7569,6 @@ void Plater::msw_rescale() void Plater::sys_color_changed() { - p->preview->sys_color_changed(); p->sidebar->sys_color_changed(); p->menus.sys_color_changed(); @@ -8941,9 +7650,9 @@ void Plater::set_preview_layers_slider_values_range(int bottom, int top) p->set_preview_layers_slider_values_range(bottom, top); } -void Plater::update_preview_moves_slider() +void Plater::update_preview_moves_slider(std::optional visible_range_min, std::optional visible_range_max) { - p->update_preview_moves_slider(); + p->update_preview_moves_slider(visible_range_min, visible_range_max); } void Plater::enable_preview_moves_slider(bool enable) @@ -8976,6 +7685,44 @@ const NotificationManager * Plater::get_notification_manager() const return p->notification_manager.get(); } +PresetArchiveDatabase* Plater::get_preset_archive_database() +{ + return p->preset_archive_database.get(); +} + +const PresetArchiveDatabase* Plater::get_preset_archive_database() const +{ + return p->preset_archive_database.get(); +} + +UserAccount* Plater::get_user_account() +{ + return p->user_account.get(); +} + +const UserAccount* Plater::get_user_account() const +{ + return p->user_account.get(); +} + +void Plater::toggle_remember_user_account_session() +{ + if (p->user_account) + p->user_account->toggle_remember_session(); +} + +void Plater::act_with_user_account() +{ + std::string current_user_token = wxGetApp().app_config->get("user_token"); + if (current_user_token.empty()) + wxGetApp().ShowUserLogin(true); + else + { + + wxGetApp().SetOnlineLogin(false); + } +} + void Plater::init_notification_manager() { p->init_notification_manager(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e4a6eb7..4ece1d2 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -1,3 +1,4 @@ + #ifndef slic3r_Plater_hpp_ #define slic3r_Plater_hpp_ @@ -7,24 +8,15 @@ #include +#include "Sidebar.hpp" #include "Selection.hpp" -#include "libslic3r/enum_bitmask.hpp" -#include "libslic3r/Preset.hpp" -#include "libslic3r/BoundingBox.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "Jobs/Job.hpp" #include "Jobs/Worker.hpp" -#include "Search.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" -#include "libslic3r/GCode.hpp" -// #include "libslic3r/Gcode/GCodeWriter.hpp" -#include "libslic3r/PrintConfig.hpp" - -class wxButton; -class ScalableButton; -class wxScrolledWindow; class wxString; namespace Slic3r { @@ -49,95 +41,16 @@ namespace UndoRedo { namespace GUI { +wxDECLARE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); + class MainFrame; -class ConfigOptionsGroup; -class ObjectManipulation; -class ObjectSettings; -class ObjectLayers; -class ObjectList; class GLCanvas3D; class Mouse3DController; class NotificationManager; struct Camera; class GLToolbar; -class PlaterPresetComboBox; - -using t_optgroups = std::vector >; - -class Plater; -enum class ActionButtonType : int; - -class Sidebar : public wxPanel -{ - ConfigOptionMode m_mode{ConfigOptionMode::comSimple}; -public: - Sidebar(Plater *parent); - Sidebar(Sidebar &&) = delete; - Sidebar(const Sidebar &) = delete; - Sidebar &operator=(Sidebar &&) = delete; - Sidebar &operator=(const Sidebar &) = delete; - ~Sidebar(); - - void init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx); - void remove_unused_filament_combos(const size_t current_extruder_count); - void update_all_preset_comboboxes(); - void update_presets(Slic3r::Preset::Type preset_type); - void update_mode_sizer() const; - void change_top_border_for_mode_sizer(bool increase_border); - void update_reslice_btn_tooltip() const; - void msw_rescale(); - void sys_color_changed(); - void update_mode_markers(); - void search(); - void jump_to_option(size_t selected); - void jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category); - // jump to option which is represented by composite key : "opt_key;tab_name" - void jump_to_option(const std::string& composite_key); - - ObjectManipulation* obj_manipul(); - ObjectList* obj_list(); - ObjectSettings* obj_settings(); - ObjectLayers* obj_layers(); - wxScrolledWindow* scrolled_panel(); - wxPanel* presets_panel(); - - ConfigOptionsGroup* og_freq_chng_params(const bool is_fff); -//Y26 - ConfigOptionsGroup* og_filament_chng_params(); - wxButton* get_wiping_dialog_button(); - void update_objects_list_extruder_column(size_t extruders_count); - void show_info_sizer(); - void show_sliced_info_sizer(const bool show); - void update_sliced_info_sizer(); - void enable_buttons(bool enable); - //Y5 - void enable_export_buttons(bool enable); - void set_btn_label(const ActionButtonType btn_type, const wxString& label) const; - bool show_reslice(bool show) const; - bool show_export(bool show) const; - bool show_send(bool show) const; - bool show_eject(bool show)const; - bool show_export_removable(bool show) const; - bool get_eject_shown() const; - bool is_multifilament(); - void update_mode(); - bool is_collapsed(); - void collapse(bool collapse); - void check_and_update_searcher(bool respect_mode = false); - void update_ui_from_settings(); - -#ifdef _MSW_DARK_MODE - void show_mode_sizer(bool show); -#endif - - std::vector& combos_filament(); - Search::OptionsSearcher& get_searcher(); - std::string& get_search_line(); - -private: - struct priv; - std::unique_ptr p; -}; +class UserAccount; +class PresetArchiveDatabase; class Plater: public wxPanel { @@ -149,7 +62,7 @@ public: Plater(const Plater &) = delete; Plater &operator=(Plater &&) = delete; Plater &operator=(const Plater &) = delete; - ~Plater() = default; + ~Plater(); bool is_project_dirty() const; bool is_presets_dirty() const; @@ -199,7 +112,7 @@ public: void reload_gcode_from_disk(); void convert_gcode_to_ascii(); void convert_gcode_to_binary(); - void refresh_print(); + void reload_print(); std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); // To be called when providing a list of files to the GUI slic3r on command line. @@ -299,7 +212,6 @@ public: ThumbnailData get_thumbnailldate_send(); void export_gcode(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); - void export_amf(); bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); void reload_from_disk(); void replace_with_stl(); @@ -310,6 +222,7 @@ public: void reslice_FFF_until_step(PrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false); void reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false); + void clear_before_change_volume(ModelVolume &mv, const std::string ¬ification_msg); void clear_before_change_mesh(int obj_idx, const std::string ¬ification_msg); void changed_mesh(int obj_idx); @@ -320,7 +233,10 @@ public: bool is_background_process_update_scheduled() const; void suppress_background_process(const bool stop_background_process) ; void send_gcode(); + // void send_gcode_inner(DynamicPrintConfig* physical_printer_config); void eject_drive(); + void connect_gcode(); + std::string get_upload_filename(); void take_snapshot(const std::string &snapshot_name); void take_snapshot(const wxString &snapshot_name); @@ -333,7 +249,6 @@ public: void redo_to(int selection); bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); void undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text); - bool search_string_getter(int idx, const char** label, const char** tooltip); // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; void clear_undo_redo_stack_main(); @@ -341,16 +256,18 @@ public: void enter_gizmos_stack(); void leave_gizmos_stack(); - void on_extruders_change(size_t extruders_count); bool update_filament_colors_in_full_config(); void on_config_change(const DynamicPrintConfig &config); void force_filament_colors_update(); void force_filament_cb_update(); void force_print_bed_update(); // On activating the parent window. - void on_activate(); - std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; - std::vector get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const; + void on_activate(bool active); + + std::vector get_extruder_color_strings_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_color_strings_for_color_print(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_extruder_colors_from_plater_config() const; + std::vector get_colors_for_color_print() const; void update_menus(); void show_action_buttons(const bool is_ready_to_slice) const; @@ -367,6 +284,8 @@ public: GLCanvas3D* canvas3D(); const GLCanvas3D * canvas3D() const; GLCanvas3D* get_current_canvas3D(); + + void render_sliders(GLCanvas3D& canvas); void arrange(); void arrange(Worker &w, bool selected); @@ -381,7 +300,6 @@ public: void copy_selection_to_clipboard(); void paste_from_clipboard(); - void search(bool plater_is_active); void mirror(Axis axis); void split_object(); void split_volume(); @@ -433,7 +351,7 @@ public: void set_preview_layers_slider_values_range(int bottom, int top); - void update_preview_moves_slider(); + void update_preview_moves_slider(std::optional visible_range_min = std::nullopt, std::optional visible_range_max = std::nullopt); void enable_preview_moves_slider(bool enable); void reset_gcode_toolpaths(); @@ -442,18 +360,27 @@ public: const Mouse3DController& get_mouse3d_controller() const; Mouse3DController& get_mouse3d_controller(); - void set_bed_shape() const; + void set_bed_shape() const; //B52 - void set_bed_shape(const Pointfs & shape, - const double max_print_height, - const std::string &custom_texture, - const std::string &custom_model, - const Pointfs & exclude_bed_shape, - bool force_as_custom = false) const; + void set_bed_shape(const Pointfs& shape, + const double max_print_height, + const std::string& custom_texture, + const std::string& custom_model, + const Pointfs& exclude_bed_shape, + bool force_as_custom = false) const; void set_default_bed_shape() const; - NotificationManager * get_notification_manager(); - const NotificationManager * get_notification_manager() const; + NotificationManager* get_notification_manager(); + const NotificationManager* get_notification_manager() const; + + PresetArchiveDatabase* get_preset_archive_database(); + const PresetArchiveDatabase* get_preset_archive_database() const; + + UserAccount* get_user_account(); + const UserAccount* get_user_account() const; + + void toggle_remember_user_account_session(); + void act_with_user_account(); void init_notification_manager(); @@ -522,9 +449,11 @@ public: wxMenu* layer_menu(); wxMenu* multi_selection_menu(); - static bool has_illegal_filename_characters(const wxString& name); - static bool has_illegal_filename_characters(const std::string& name); - static void show_illegal_characters_warning(wxWindow* parent); + void resetUploadCount() + { + UploadCount = 0; + m_sending_interval = 0; + }; private: void reslice_until_step_inner(int step, const ModelObject &object, bool postpone_error_messages); @@ -547,6 +476,9 @@ private: //B64 std::thread m_sendThread; std::chrono::system_clock::time_point m_time_p; + int UploadCount = 0; + int max_send_number = 1; + int m_sending_interval = 0; }; class SuppressBackgroundProcessingUpdate diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index eac45c9..fce4f41 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1,3 +1,4 @@ + #include "Preferences.hpp" #include "OptionsGroup.hpp" #include "GUI_App.hpp" @@ -12,6 +13,7 @@ #include "OG_CustomCtrl.hpp" #include "GLCanvas3D.hpp" #include "ConfigWizard.hpp" +#include "Search.hpp" #include "Widgets/SpinInput.hpp" @@ -126,8 +128,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin downloader->set_path_name(app_config->get("url_downloader_dest")); downloader->allow(!app_config->has("downloader_url_registered") || app_config->get_bool("downloader_url_registered")); - for (const std::string& opt_key : {"suppress_hyperlinks", "downloader_url_registered"}) + for (const std::string& opt_key : {"suppress_hyperlinks", "downloader_url_registered", "show_login_button"}) m_optgroup_other->set_value(opt_key, app_config->get_bool(opt_key)); + // by default "Log in" button is visible + if (!app_config->has("show_login_button")) + m_optgroup_other->set_value("show_login_button", true); for (const std::string& opt_key : { "default_action_on_close_application" ,"default_action_on_new_project" @@ -146,6 +151,9 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin update_color(color_pickres[mode], palette[mode]); } + // invalidate this flag before show preferences + m_settings_layout_changed = false; + this->ShowModal(); } @@ -183,7 +191,7 @@ static void activate_options_tab(std::shared_ptr optgroup) optgroup->parent()->Layout(); // apply sercher - wxGetApp().sidebar().get_searcher().append_preferences_options(optgroup->get_lines()); + wxGetApp().searcher().append_preferences_options(optgroup->get_lines()); } static void append_bool_option( std::shared_ptr optgroup, @@ -202,7 +210,7 @@ static void append_bool_option( std::shared_ptr optgroup, optgroup->append_single_option_line(option); // fill data to the Search Dialog - wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); + wxGetApp().searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); } template @@ -225,17 +233,18 @@ static void append_enum_option( std::shared_ptr optgroup, optgroup->append_single_option_line(option); // fill data to the Search Dialog - wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); + wxGetApp().searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); } static void append_preferences_option_to_searcher(std::shared_ptr optgroup, const std::string& opt_key, const wxString& label) { + Search::OptionsSearcher& searcher = wxGetApp().searcher(); // fill data to the Search Dialog - wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); + searcher.add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); // apply sercher - wxGetApp().sidebar().get_searcher().append_preferences_option(Line(opt_key, label, "")); + searcher.append_preferences_option(Line(opt_key, label, "")); } void PreferencesDialog::build() @@ -264,7 +273,7 @@ void PreferencesDialog::build() // Add "General" tab m_optgroup_general = create_options_tab(L("General"), tabs); - m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_optgroup_general->on_change = [this](t_config_option_key opt_key, boost::any value) { if (auto it = m_values.find(opt_key); it != m_values.end()) { m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected return; @@ -448,7 +457,7 @@ void PreferencesDialog::build() // Add "Camera" tab m_optgroup_camera = create_options_tab(L("Camera"), tabs); - m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_optgroup_camera->on_change = [this](t_config_option_key opt_key, boost::any value) { if (auto it = m_values.find(opt_key);it != m_values.end()) { m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected return; @@ -475,7 +484,7 @@ void PreferencesDialog::build() // Add "GUI" tab m_optgroup_gui = create_options_tab(L("GUI"), tabs); - m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_optgroup_gui->on_change = [this](t_config_option_key opt_key, boost::any value) { if (opt_key == "notify_release") { int val_int = boost::any_cast(value); for (const auto& item : s_keys_map_NotifyReleaseMode) { @@ -492,15 +501,6 @@ void PreferencesDialog::build() wxGetApp().plater()->get_current_canvas3D()->render(); return; } - if (opt_key == "tabs_as_menu") { - bool disable_new_layout = boost::any_cast(value); - m_rb_new_settings_layout_mode->Show(!disable_new_layout); - if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) { - m_rb_new_settings_layout_mode->SetValue(false); - m_rb_old_settings_layout_mode->SetValue(true); - } - refresh_og(m_optgroup_gui); - } if (auto it = m_values.find(opt_key); it != m_values.end()) { m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected @@ -554,13 +554,6 @@ void PreferencesDialog::build() L("If enabled, related notification will be shown, when sliced object looks like a logo or a sign."), app_config->get_bool("allow_auto_color_change")); -#ifdef _MSW_DARK_MODE - append_bool_option(m_optgroup_gui, "tabs_as_menu", - L("Set settings tabs as menu items"), - L("If enabled, Settings Tabs will be placed as menu items. If disabled, old UI will be used."), - app_config->get_bool("tabs_as_menu")); -#endif - m_optgroup_gui->append_separator(); /* append_bool_option(m_optgroup_gui, "suppress_round_corners", @@ -607,7 +600,7 @@ void PreferencesDialog::build() create_settings_mode_color_widget(); m_optgroup_other = create_options_tab(_L("Other"), tabs); - m_optgroup_other->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_optgroup_other->on_change = [this](t_config_option_key opt_key, boost::any value) { if (auto it = m_values.find(opt_key); it != m_values.end() && opt_key != "url_downloader_dest") { m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected @@ -632,6 +625,11 @@ void PreferencesDialog::build() //L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. " // "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."), app_config->get_bool("suppress_hyperlinks")); + + append_bool_option(m_optgroup_other, "show_login_button", + L("Show \"Log in\" button in application top bar"), + L("If enabled, QIDISlicer will show up \"Log in\" button in application top bar."), + app_config->get_bool("show_login_button")); append_bool_option(m_optgroup_other, "downloader_url_registered", L("Allow downloads from Printables.com"), @@ -646,7 +644,7 @@ void PreferencesDialog::build() #if ENABLE_ENVIRONMENT_MAP // Add "Render" tab m_optgroup_render = create_options_tab(L("Render"), tabs); - m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_optgroup_render->on_change = [this](t_config_option_key opt_key, boost::any value) { if (auto it = m_values.find(opt_key); it != m_values.end()) { m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected return; @@ -666,7 +664,7 @@ void PreferencesDialog::build() #ifdef _WIN32 // Add "Dark Mode" tab m_optgroup_dark_mode = create_options_tab(_L("Dark mode"), tabs); - m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_optgroup_dark_mode->on_change = [this](t_config_option_key opt_key, boost::any value) { if (auto it = m_values.find(opt_key); it != m_values.end()) { m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected return; @@ -751,12 +749,12 @@ void PreferencesDialog::accept(wxEvent&) if (!downloader->on_finish()) return; #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - if( downloader->get_perform_registration_linux()) + if(DownloaderUtils::Worker::perform_registration_linux) DesktopIntegrationDialog::perform_downloader_desktop_integration(); #endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) } - std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled", "font_pt_size", "suppress_round_corners" }; + std::vector options_to_recreate_GUI = { "no_defaults", "sys_menu_enabled", "font_pt_size", "suppress_round_corners" }; for (const std::string& option : options_to_recreate_GUI) { if (m_values.find(option) != m_values.end()) { @@ -785,9 +783,7 @@ void PreferencesDialog::accept(wxEvent&) if (auto it = m_values.find("seq_top_layer_only"); it != m_values.end()) m_seq_top_layer_only_changed = app_config->get("seq_top_layer_only") != it->second; - m_settings_layout_changed = false; for (const std::string& key : { "old_settings_layout_mode", - "new_settings_layout_mode", "dlg_settings_layout_mode" }) { auto it = m_values.find(key); @@ -865,11 +861,6 @@ void PreferencesDialog::revert(wxEvent&) m_settings_layout_changed = false; continue; } - if (key == "new_settings_layout_mode") { - m_rb_new_settings_layout_mode->SetValue(app_config->get_bool(key)); - m_settings_layout_changed = false; - continue; - } if (key == "dlg_settings_layout_mode") { m_rb_dlg_settings_layout_mode->SetValue(app_config->get_bool(key)); m_settings_layout_changed = false; @@ -887,11 +878,6 @@ void PreferencesDialog::revert(wxEvent&) if (opt_group->set_value(key, app_config->get_bool(key))) break; } - if (key == "tabs_as_menu") { - m_rb_new_settings_layout_mode->Show(!app_config->get_bool(key)); - refresh_og(m_optgroup_gui); - continue; - } } clear_cache(); @@ -1016,7 +1002,6 @@ void PreferencesDialog::create_settings_mode_widget() auto app_config = get_app_config(); std::vector choices = { _L("Old regular layout with the tab bar"), - _L("New layout, access via settings button in the top menu"), _L("Settings in non-modal window") }; int id = -1; auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) { @@ -1025,25 +1010,13 @@ void PreferencesDialog::create_settings_mode_widget() (*rb)->SetValue(select); (*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; - m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; - m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0"; + m_values["dlg_settings_layout_mode"] = (id == 1) ? "1" : "0"; }); }; add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get_bool("old_settings_layout_mode")); - add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get_bool("new_settings_layout_mode")); add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get_bool("dlg_settings_layout_mode")); -#ifdef _MSW_DARK_MODE - if (app_config->get_bool("tabs_as_menu")) { - m_rb_new_settings_layout_mode->Hide(); - if (m_rb_new_settings_layout_mode->GetValue()) { - m_rb_new_settings_layout_mode->SetValue(false); - m_rb_old_settings_layout_mode->SetValue(true); - } - } -#endif - std::string opt_key = "settings_layout_mode"; m_blinkers[opt_key] = new BlinkingBitmap(parent); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 765447f..91d8bcc 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -1,3 +1,4 @@ + #ifndef slic3r_Preferences_hpp_ #define slic3r_Preferences_hpp_ @@ -48,7 +49,6 @@ class PreferencesDialog : public DPIDialog wxSizer* m_icon_size_sizer {nullptr}; wxSlider* m_icon_size_slider {nullptr}; wxRadioButton* m_rb_old_settings_layout_mode {nullptr}; - wxRadioButton* m_rb_new_settings_layout_mode {nullptr}; wxRadioButton* m_rb_dlg_settings_layout_mode {nullptr}; wxColourPickerCtrl* m_sys_colour {nullptr}; diff --git a/src/slic3r/GUI/PresetArchiveDatabase.cpp b/src/slic3r/GUI/PresetArchiveDatabase.cpp new file mode 100644 index 0000000..d017253 --- /dev/null +++ b/src/slic3r/GUI/PresetArchiveDatabase.cpp @@ -0,0 +1,921 @@ +#include "PresetArchiveDatabase.hpp" + +#include "slic3r/Utils/Http.hpp" +#include "slic3r/Utils/ServiceConfig.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/UserAccount.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/miniz_extension.hpp" + +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pt = boost::property_tree; +namespace fs = boost::filesystem; +namespace Slic3r { +namespace GUI { + +static const char* TMP_EXTENSION = ".download"; + +namespace { +bool unzip_repository(const fs::path& source_path, const fs::path& target_path) +{ + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + if (!open_zip_reader(&archive, source_path.string())) { + BOOST_LOG_TRIVIAL(error) << "Couldn't open zipped Archive source. " << source_path; + return false; + } + size_t num_files = mz_zip_reader_get_num_files(&archive); + + for (size_t i = 0; i < num_files; ++i) { + mz_zip_archive_file_stat file_stat; + if (!mz_zip_reader_file_stat(&archive, i, &file_stat)) { + BOOST_LOG_TRIVIAL(error) << "Failed to get file stat for file #" << i << " in the zip archive. Ending Unzipping."; + close_zip_reader(&archive); + return false; + } + fs::path extracted_path = target_path / file_stat.m_filename; + if (file_stat.m_is_directory) { + // Create directory if it doesn't exist + fs::create_directories(extracted_path); + continue; + } + // Create parent directory if it doesn't exist + fs::create_directories(extracted_path.parent_path()); + // Extract file + if (!mz_zip_reader_extract_to_file(&archive, i, extracted_path.string().c_str(), 0)) { + BOOST_LOG_TRIVIAL(error) << "Failed to extract file #" << i << " from the zip archive. Ending Unzipping."; + close_zip_reader(&archive); + return false; + } + } + close_zip_reader(&archive); + return true; +} + +bool extract_repository_header(const pt::ptree& ptree, ArchiveRepository::RepositoryManifest& data) +{ + // mandatory atributes + if (const auto name = ptree.get_optional("name"); name){ + data.name = *name; + } else { + BOOST_LOG_TRIVIAL(error) << "Failed to find \"name\" parameter in source manifest. Source is invalid."; + return false; + } + if (const auto id = ptree.get_optional("id"); id) { + data.id = *id; + } + else { + BOOST_LOG_TRIVIAL(error) << "Failed to find \"id\" parameter in source manifest. Source is invalid."; + return false; + } + if (const auto url = ptree.get_optional("url"); url) { + data.url = *url; + } + else { + BOOST_LOG_TRIVIAL(error) << "Failed to find \"url\" parameter in source manifest. Source is invalid."; + return false; + } + // optional atributes + if (const auto index_url = ptree.get_optional("index_url"); index_url) { + data.index_url = *index_url; + } + if (const auto description = ptree.get_optional("description"); description) { + data.description = *description; + } + if (const auto visibility = ptree.get_optional("visibility"); visibility) { + data.visibility = *visibility; + } + return true; +} + +void delete_path_recursive(const fs::path& path) +{ + try { + boost::system::error_code ec; + if (fs::exists(path, ec) && !ec) { + for (fs::directory_iterator it(path); it != fs::directory_iterator(); ++it) { + const fs::path subpath = it->path(); + if (fs::is_directory(subpath)) { + delete_path_recursive(subpath); + } else { + fs::remove(subpath); + } + } + fs::remove(path); + } + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to delete files at: " << path; + } +} + +bool extract_local_archive_repository( ArchiveRepository::RepositoryManifest& manifest_data) +{ + assert(!manifest_data.tmp_path.empty()); + assert(!manifest_data.source_path.empty()); + // Delete previous data before unzip. + // We have unique path in temp set for whole run of slicer and in it folder for each repo. + delete_path_recursive(manifest_data.tmp_path); + fs::create_directories(manifest_data.tmp_path); + // Unzip repository zip to unique path in temp directory. + if (!unzip_repository(manifest_data.source_path, manifest_data.tmp_path)) { + return false; + } + // Read the manifest file. + fs::path manifest_path = manifest_data.tmp_path / "manifest.json"; + try + { + pt::ptree ptree; + pt::read_json(manifest_path.string(), ptree); + if (!extract_repository_header(ptree, manifest_data)) { + BOOST_LOG_TRIVIAL(error) << "Failed to load source " << manifest_data.tmp_path; + return false; + } + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to read source manifest JSON " << manifest_path << ". reason: " << e.what(); + return false; + } + return true; +} + +void deserialize_string(const std::string& opt, std::vector& result) +{ + std::string val; + for (size_t i = 0; i < opt.length(); i++) { + if (std::isspace(opt[i])) { + continue; + } + if (opt[i] != ';') { + val += opt[i]; + } + else { + result.emplace_back(std::move(val)); + } + } + if (!val.empty()) { + result.emplace_back(std::move(val)); + } +} + +std::string escape_string(const std::string& unescaped) +{ + std::string ret_val; + CURL* curl = curl_easy_init(); + if (curl) { + char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size()); + if (decoded) { + ret_val = std::string(decoded); + curl_free(decoded); + } + curl_easy_cleanup(curl); + } + return ret_val; +} +std::string escape_path_by_element(const std::string& path_string) +{ + const boost::filesystem::path path(path_string); + std::string ret_val = escape_string(path.filename().string()); + boost::filesystem::path parent(path.parent_path()); + while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path. + { + ret_val = escape_string(parent.filename().string()) + "/" + ret_val; + parent = parent.parent_path(); + } + return ret_val; +} + +void add_authorization_header(Http& http) +{ + const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token(); + if (!access_token.empty()) { + http.header("Authorization", "Bearer " + access_token); + } +} + +} + +bool OnlineArchiveRepository::get_file_inner(const std::string& url, const fs::path& target_path) const +{ + + bool res = false; + fs::path tmp_path = target_path; + tmp_path += format(".%1%%2%", get_current_pid(), TMP_EXTENSION); + BOOST_LOG_TRIVIAL(info) << format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`", + url, + target_path.string(), + tmp_path.string()); + + auto http = Http::get(url); + add_authorization_header(http); + http + .timeout_max(30) + .on_progress([](Http::Progress, bool& cancel) { + //if (cancel) { cancel = true; } + }) + .on_error([&](std::string body, std::string error, unsigned http_status) { + BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%", + url, + http_status, + body); + }) + .on_complete([&](std::string body, unsigned /* http_status */) { + if (body.empty()) { + return; + } + fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(body.c_str(), body.size()); + file.close(); + fs::rename(tmp_path, target_path); + res = true; + }) + .perform_sync(); + + return res; +} + +bool OnlineArchiveRepository::get_archive(const fs::path& target_path) const +{ + //y15 + return get_file_inner(m_data.index_url.empty() ? m_data.url + "vendor_indices.zip" : m_data.index_url + "vendor_indices.zip", target_path); +} + +bool OnlineArchiveRepository::get_file(const std::string& source_subpath, const fs::path& target_path, const std::string& repository_id) const +{ + if (repository_id != m_data.id) { + BOOST_LOG_TRIVIAL(error) << "Error getting file " << source_subpath << ". The repository_id was not matching."; + return false; + } + const std::string escaped_source_subpath = escape_path_by_element(source_subpath); + return get_file_inner(m_data.url + escaped_source_subpath, target_path); +} + +bool OnlineArchiveRepository::get_ini_no_id(const std::string& source_subpath, const fs::path& target_path) const +{ + const std::string escaped_source_subpath = escape_path_by_element(source_subpath); + return get_file_inner(m_data.url + escaped_source_subpath, target_path); +} + +bool LocalArchiveRepository::get_file_inner(const fs::path& source_path, const fs::path& target_path) const +{ + BOOST_LOG_TRIVIAL(debug) << format("Copying %1% to %2%", source_path, target_path); + std::string error_message; + CopyFileResult cfr = Slic3r::copy_file(source_path.string(), target_path.string(), error_message, false); + if (cfr != CopyFileResult::SUCCESS) { + BOOST_LOG_TRIVIAL(error) << "Copying of " << source_path << " to " << target_path << " has failed (" << cfr << "): " << error_message; + // remove target file, even if it was there before + boost::system::error_code ec; + if (fs::exists(target_path, ec) && !ec) { + ec.clear(); + fs::remove(target_path, ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << format("Failed to delete file: %1%", ec.message()); + } + } + return false; + } + // Permissions should be copied from the source file by copy_file(). We are not sure about the source + // permissions, let's rewrite them with 644. + static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; + fs::permissions(target_path, perms); + + return true; +} + +bool LocalArchiveRepository::get_file(const std::string& source_subpath, const fs::path& target_path, const std::string& repository_id) const +{ + if (repository_id != m_data.id) { + BOOST_LOG_TRIVIAL(error) << "Error getting file " << source_subpath << ". The repository_id was not matching."; + return false; + } + return get_file_inner(m_data.tmp_path / source_subpath, target_path); +} +bool LocalArchiveRepository::get_ini_no_id(const std::string& source_subpath, const fs::path& target_path) const +{ + return get_file_inner(m_data.tmp_path / source_subpath, target_path); +} +bool LocalArchiveRepository::get_archive(const fs::path& target_path) const +{ + fs::path source_path = fs::path(m_data.tmp_path) / "vendor_indices.zip"; + return get_file_inner(std::move(source_path), target_path); +} + +void LocalArchiveRepository::do_extract() +{ + RepositoryManifest new_manifest; + new_manifest.source_path = this->get_manifest().source_path; + new_manifest.tmp_path = this->get_manifest().tmp_path; + m_extracted = extract_local_archive_repository(new_manifest); + set_manifest(std::move(new_manifest)); +} + +//-------------------------------------PresetArchiveDatabase------------------------------------------------------------------------------------------------------------------------- + +PresetArchiveDatabase::PresetArchiveDatabase(AppConfig* app_config, wxEvtHandler* evt_handler) + : p_evt_handler(evt_handler) +{ + // + boost::system::error_code ec; + m_unq_tmp_path = fs::temp_directory_path() / fs::unique_path(); + fs::create_directories(m_unq_tmp_path, ec); + assert(!ec); + + load_app_manifest_json(); +} + +bool PresetArchiveDatabase::set_selected_repositories(const std::vector& selected_uuids, std::string& msg) +{ + // First re-extract locals, this will set is_extracted flag + extract_local_archives(); + // Check if some uuids leads to the same id (online vs local conflict) + std::map used_set; + for (const std::string& uuid : selected_uuids) { + std::string id; + std::string name; + for (const auto& archive : m_archive_repositories) { + if (archive->get_uuid() != uuid) { + continue; + } + id = archive->get_manifest().id; + name = archive->get_manifest().name; + if (!archive->is_extracted()) { + // non existent local repo since start selected + msg = GUI::format( + _L("Cannot select local source from path: %1%. It was not extracted."), + archive->get_manifest().source_path + ); + return false; + } + break; + } + assert(!id.empty()); + if (auto it = used_set.find(id); it != used_set.end()) { + msg = GUI::format(_L("Cannot select two sources with the same id: %1% and %2%"), it->second, name); + return false; + } + used_set.emplace(id, name); + } + // deselect all first + for (auto& pair : m_selected_repositories_uuid) { + pair.second = false; + } + for (const std::string& uuid : selected_uuids) { + m_selected_repositories_uuid[uuid] = true; + } + save_app_manifest_json(); + return true; +} +bool PresetArchiveDatabase::extract_archives_with_check(std::string &msg) +{ + extract_local_archives(); + for (const std::pair& pair : m_selected_repositories_uuid) { + if (!pair.second) { + continue; + } + const std::string uuid = pair.first; + auto compare_repo = [&uuid](const std::unique_ptr &repo) { + return repo->get_uuid() == uuid; + }; + + const auto& archives_it =std::find_if(m_archive_repositories.begin(), m_archive_repositories.end(), compare_repo); + assert(archives_it != m_archive_repositories.end()); + if (!archives_it->get()->is_extracted()) { + // non existent local repo since start selected + msg += std::string(msg.empty() ? "" : "\n") + archives_it->get()->get_manifest().source_path.string(); + } + } + return msg.empty(); +} +void PresetArchiveDatabase::set_installed_printer_repositories(const std::vector &used_ids) +{ + // set all uuids as not having installed printer + m_has_installed_printer_repositories_uuid.clear(); + for (const auto &archive : m_archive_repositories) { + m_has_installed_printer_repositories_uuid.emplace(archive->get_uuid(), false); + } + // set correct repos as having installed printer + for (const std::string &used_id : used_ids) { + // find archive with id and is used + std::vector selected_uuid; + std::vector unselected_uuid; + for (const auto &archive : m_archive_repositories) { + if (archive->get_manifest().id != used_id) { + continue; + } + const std::string uuid = archive->get_uuid(); + if (m_selected_repositories_uuid[uuid]) { + selected_uuid.emplace_back(uuid); + } else { + unselected_uuid.emplace_back(uuid); + } + } + + if (selected_uuid.empty() && unselected_uuid.empty()) { + // there is id in used_ids that is not in m_archive_repositories - BAD + assert(true); + continue; + } else if (selected_uuid.size() == 1){ + // regular case + m_has_installed_printer_repositories_uuid[selected_uuid.front()] = true; + } else if (selected_uuid.size() > 1) { + // this should not happen, only one repo of same id should be selected (online / local conflict) + assert(true); + // select first one to solve the conflict + m_has_installed_printer_repositories_uuid[selected_uuid.front()] = true; + // unselect the rest + for (size_t i = 1; i < selected_uuid.size(); i++) { + m_selected_repositories_uuid[selected_uuid[i]] = false; + } + } else if (selected_uuid.empty()) { + // This is a rare case, where there are no selected repos with matching id but id has installed printers + // Repro: install printer, unselect repo in the next run of wizard, next, cancel wizard, run wizard again and press finish. + // Solution: Select the first unselected + m_has_installed_printer_repositories_uuid[unselected_uuid.front()] = true; + m_selected_repositories_uuid[unselected_uuid.front()] = true; + } + + } + save_app_manifest_json(); +} + +std::string PresetArchiveDatabase::add_local_archive(const boost::filesystem::path path, std::string& msg) +{ + if (auto it = std::find_if(m_archive_repositories.begin(), m_archive_repositories.end(), [path](const std::unique_ptr& ptr) { + return ptr->get_manifest().source_path == path; + }); it != m_archive_repositories.end()) + { + msg = GUI::format(_L("Failed to add local archive %1%. Path already used."), path); + BOOST_LOG_TRIVIAL(error) << msg; + return std::string(); + } + std::string uuid = get_next_uuid(); + ArchiveRepository::RepositoryManifest header_data; + header_data.source_path = path; + header_data.tmp_path = m_unq_tmp_path / uuid; + if (!extract_local_archive_repository(header_data)) { + msg = GUI::format(_L("Failed to extract local archive %1%."), path); + BOOST_LOG_TRIVIAL(error) << msg; + return std::string(); + } + // Solve if it can be set true first. + m_selected_repositories_uuid[uuid] = false; + m_has_installed_printer_repositories_uuid[uuid] = false; + m_archive_repositories.emplace_back(std::make_unique(uuid, std::move(header_data), true)); + + save_app_manifest_json(); + return uuid; +} +void PresetArchiveDatabase::remove_local_archive(const std::string& uuid) +{ + auto compare_repo = [uuid](const std::unique_ptr& repo) { + return repo->get_uuid() == uuid; + }; + + auto archives_it = std::find_if(m_archive_repositories.begin(), m_archive_repositories.end(), compare_repo); + assert(archives_it != m_archive_repositories.end()); + std::string removed_uuid = archives_it->get()->get_uuid(); + m_archive_repositories.erase(archives_it); + + auto used_it = m_selected_repositories_uuid.find(removed_uuid); + assert(used_it != m_selected_repositories_uuid.end()); + m_selected_repositories_uuid.erase(used_it); + + auto inst_it = m_has_installed_printer_repositories_uuid.find(removed_uuid); + assert(inst_it != m_has_installed_printer_repositories_uuid.end()); + m_has_installed_printer_repositories_uuid.erase(inst_it); + + save_app_manifest_json(); +} + + void PresetArchiveDatabase::extract_local_archives() + { + for (auto &archive : m_archive_repositories) { + archive->do_extract(); + } + } + +void PresetArchiveDatabase::load_app_manifest_json() +{ + const fs::path path = get_stored_manifest_path(); + boost::system::error_code ec; + if (!fs::exists(path, ec) || ec) { + copy_initial_manifest(); + } + boost::nowide::ifstream file(path.string()); + std::string data; + if (file.is_open()) { + std::string line; + while (getline(file, line)) { + data += line; + } + file.close(); + } + else { + assert(true); + BOOST_LOG_TRIVIAL(error) << "Failed to read Archive Source Manifest at " << path; + } + if (data.empty()) { + return; + } + + m_archive_repositories.clear(); + m_selected_repositories_uuid.clear(); + m_has_installed_printer_repositories_uuid.clear(); + try + { + std::stringstream ss(data); + pt::ptree ptree; + pt::read_json(ss, ptree); + for (const auto& subtree : ptree) { + // if has tmp_path its local repo else its online repo (manifest is written in its zip, not in our json) + if (const auto source_path = subtree.second.get_optional("source_path"); source_path) { + ArchiveRepository::RepositoryManifest manifest; + std::string uuid = get_next_uuid(); + manifest.source_path = boost::filesystem::path(*source_path); + manifest.tmp_path = m_unq_tmp_path / uuid; + bool extracted = extract_local_archive_repository(manifest); + // "selected" flag + if(const auto used = subtree.second.get_optional("selected"); used) { + m_selected_repositories_uuid[uuid] = extracted && *used; + } else { + assert(true); + m_selected_repositories_uuid[uuid] = extracted; + } + // "has_installed_printers" flag + if (const auto used = subtree.second.get_optional("has_installed_printers"); used) { + m_has_installed_printer_repositories_uuid[uuid] = extracted && *used; + } else { + assert(true); + m_has_installed_printer_repositories_uuid[uuid] = false; + } + m_archive_repositories.emplace_back(std::make_unique(std::move(uuid), std::move(manifest), extracted)); + + continue; + } + // online repo + ArchiveRepository::RepositoryManifest manifest; + std::string uuid = get_next_uuid(); + if (!extract_repository_header(subtree.second, manifest)) { + assert(true); + BOOST_LOG_TRIVIAL(error) << "Failed to read one of source headers."; + continue; + } + // "selected" flag + if (const auto used = subtree.second.get_optional("selected"); used) { + m_selected_repositories_uuid[uuid] = *used; + } else { + assert(true); + m_selected_repositories_uuid[uuid] = true; + } + // "has_installed_printers" flag + if (const auto used = subtree.second.get_optional("has_installed_printers"); used) { + m_has_installed_printer_repositories_uuid[uuid] = *used; + } else { + assert(true); + m_has_installed_printer_repositories_uuid[uuid] = false; + } + m_archive_repositories.emplace_back(std::make_unique(std::move(uuid), std::move(manifest))); + } + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to read archives JSON. " << e.what(); + } +} + +void PresetArchiveDatabase::copy_initial_manifest() +{ + const fs::path target_path = get_stored_manifest_path(); + const fs::path source_path = fs::path(resources_dir()) / "profiles" / "ArchiveRepositoryManifest.json"; + assert(fs::exists(source_path)); + std::string error_message; + CopyFileResult cfr = Slic3r::copy_file(source_path.string(), target_path.string(), error_message, false); + assert(cfr == CopyFileResult::SUCCESS); + if (cfr != CopyFileResult::SUCCESS) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy ArchiveRepositoryManifest.json from resources."; + return; + } + static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; + fs::permissions(target_path, perms); +} + +void PresetArchiveDatabase::save_app_manifest_json() const +{ + /* + [{ + "name": "Production", + "description": "Production repository", + "visibility": null, + "id": "prod", + "url": "http://10.24.3.3:8001/v1/repos/prod", + "index_url": "http://10.24.3.3:8001/v1/repos/prod/vendor_indices.zip" + "selected": 1 + "has_installed_printers": 1 + }, { + "name": "Development", + "description": "Production repository", + "visibility": "developers only", + "id": "dev", + "url": "http://10.24.3.3:8001/v1/repos/dev", + "index_url": "http://10.24.3.3:8001/v1/repos/dev/vendor_indices.zip" + "selected": 0 + "has_installed_printers": 0 + }] + */ + std::string data = "["; + + for (const auto& archive : m_archive_repositories) { + // local writes only source_path and "selected". Rest is read from zip on source_path. + if (!archive->get_manifest().tmp_path.empty()) { + const ArchiveRepository::RepositoryManifest& man = archive->get_manifest(); + std::string line = archive == m_archive_repositories.front() ? std::string() : ","; + line += GUI::format( + "{" + "\"source_path\": \"%1%\"," + "\"selected\": %2%," + "\"has_installed_printers\": %3%" + "}", + man.source_path.generic_string() + , is_selected(archive->get_uuid()) ? "1" : "0" + , has_installed_printers(archive->get_uuid()) ? "1" : "0" + ); + data += line; + continue; + } + // online repo writes whole manifest - in case of offline run, this info is load from here + const ArchiveRepository::RepositoryManifest& man = archive->get_manifest(); + std::string line = archive == m_archive_repositories.front() ? std::string() : ","; + line += GUI::format( + "{\"name\": \"%1%\"," + "\"description\": \"%2%\"," + "\"visibility\": \"%3%\"," + "\"id\": \"%4%\"," + "\"url\": \"%5%\"," + "\"index_url\": \"%6%\"," + "\"selected\": %7%," + "\"has_installed_printers\": %8%" + "}" + , man.name, man.description + , man. visibility + , man.id + , man.url + , man.index_url + , is_selected(archive->get_uuid()) ? "1" : "0" + , has_installed_printers(archive->get_uuid()) ? "1" : "0" + ); + data += line; + } + data += "]"; + + std::string path = get_stored_manifest_path().string(); + boost::nowide::ofstream file(path); + if (file.is_open()) { + file << data; + file.close(); + } else { + assert(true); + BOOST_LOG_TRIVIAL(error) << "Failed to write Archive Repository Manifest to " << path; + } +} + +fs::path PresetArchiveDatabase::get_stored_manifest_path() const +{ + return (boost::filesystem::path(Slic3r::data_dir()) / "ArchiveRepositoryManifest.json").make_preferred(); +} + +bool PresetArchiveDatabase::is_selected(const std::string& uuid) const +{ + auto search = m_selected_repositories_uuid.find(uuid); + assert(search != m_selected_repositories_uuid.end()); + return search->second; +} +bool PresetArchiveDatabase::has_installed_printers(const std::string &uuid) const +{ + auto search = m_has_installed_printer_repositories_uuid.find(uuid); + assert(search != m_has_installed_printer_repositories_uuid.end()); + return search->second; +} +void PresetArchiveDatabase::clear_online_repos() +{ + auto it = m_archive_repositories.begin(); + while (it != m_archive_repositories.end()) { + // Do not clean repos with local path (local repo). + if ((*it)->get_manifest().tmp_path.empty()) { + it = m_archive_repositories.erase(it); + } else { + ++it; + } + } +} + +void PresetArchiveDatabase::read_server_manifest(const std::string& json_body) +{ + pt::ptree ptree; + try + { + std::stringstream ss(json_body); + pt::read_json(ss, ptree); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to read archives JSON. " << e.what(); + + } + // Online repo manifests are in json_body. We already have read local manifest and online manifest from last run. + // Keep the local ones and replace the online ones but keep uuid for same id so the selected map is correct. + // Solution: Create id - uuid translate table for online repos. + std::map id_to_uuid; + for (const auto& repo_ptr : m_archive_repositories) { + if (repo_ptr->get_manifest().source_path.empty()){ + id_to_uuid[repo_ptr->get_manifest().id] = repo_ptr->get_uuid(); + } + } + + // Make a stash of secret repos that are online and has installed printers. + // If some of these will be missing afer reading the json tree, it needs to be added back to main population. + PrivateArchiveRepositoryVector secret_online_used_repos_cache; + for (const auto &repo_ptr : m_archive_repositories) { + if (repo_ptr->get_manifest().visibility.empty() || !repo_ptr->get_manifest().tmp_path.empty()) { + continue; + } + const auto &it = m_has_installed_printer_repositories_uuid.find(repo_ptr->get_uuid()); + assert(it != m_has_installed_printer_repositories_uuid.end()); + if (it->second) { + ArchiveRepository::RepositoryManifest manifest(repo_ptr->get_manifest()); + secret_online_used_repos_cache.emplace_back(std::make_unique(repo_ptr->get_uuid(), std::move(manifest))); + } + } + + clear_online_repos(); + + for (const auto& subtree : ptree) { + ArchiveRepository::RepositoryManifest manifest; + if (!extract_repository_header(subtree.second, manifest)) { + assert(true); + BOOST_LOG_TRIVIAL(error) << "Failed to read one of repository headers."; + continue; + } + auto id_it = id_to_uuid.find(manifest.id); + std::string uuid = (id_it == id_to_uuid.end() ? get_next_uuid() : id_it->second); + // Set default selected value to true - its a never before seen repository + if (auto search = m_selected_repositories_uuid.find(uuid); search == m_selected_repositories_uuid.end()) { + m_selected_repositories_uuid[uuid] = true; + } + // Set default "has installed printers" value to false - its a never before seen repository + if (auto search = m_has_installed_printer_repositories_uuid.find(uuid); + search == m_has_installed_printer_repositories_uuid.end()) { + m_has_installed_printer_repositories_uuid[uuid] = false; + } + m_archive_repositories.emplace_back(std::make_unique(uuid, std::move(manifest))); + } + + // return missing secret online repos with installed printers to the vector + for (const auto &repo_ptr : secret_online_used_repos_cache) { + std::string uuid = repo_ptr->get_uuid(); + if (std::find_if( + m_archive_repositories.begin(), m_archive_repositories.end(), + [uuid](const std::unique_ptr &ptr) { + return ptr->get_uuid() == uuid; + } + ) == m_archive_repositories.end()) + { + ArchiveRepository::RepositoryManifest manifest(repo_ptr->get_manifest()); + m_archive_repositories.emplace_back(std::make_unique(repo_ptr->get_uuid(), std::move(manifest))); + } + } + + consolidate_uuid_maps(); + save_app_manifest_json(); +} + +SharedArchiveRepositoryVector PresetArchiveDatabase::get_all_archive_repositories() const +{ + SharedArchiveRepositoryVector result; + result.reserve(m_archive_repositories.size()); + for (const auto &repo_ptr : m_archive_repositories) + { + result.emplace_back(repo_ptr.get()); + } + return result; +} + +SharedArchiveRepositoryVector PresetArchiveDatabase::get_selected_archive_repositories() const +{ + SharedArchiveRepositoryVector result; + result.reserve(m_archive_repositories.size()); + for (const auto &repo_ptr : m_archive_repositories) + { + auto it = m_selected_repositories_uuid.find(repo_ptr->get_uuid()); + assert(it != m_selected_repositories_uuid.end()); + if (it->second) { + result.emplace_back(repo_ptr.get()); + } + } + return result; +} + +bool PresetArchiveDatabase::is_selected_repository_by_uuid(const std::string& uuid) const +{ + auto selected_it = m_selected_repositories_uuid.find(uuid); + assert(selected_it != m_selected_repositories_uuid.end()); + return selected_it->second; +} +bool PresetArchiveDatabase::is_selected_repository_by_id(const std::string& repo_id) const +{ + assert(!repo_id.empty()); + for (const auto& repo_ptr : m_archive_repositories) { + if (repo_ptr->get_manifest().id == repo_id) { + return true; + } + } + return false; +} +void PresetArchiveDatabase::consolidate_uuid_maps() +{ + //std::vector> m_archive_repositories; + //std::map m_selected_repositories_uuid; + auto selected_it = m_selected_repositories_uuid.begin(); + while (selected_it != m_selected_repositories_uuid.end()) { + bool found = false; + for (const auto& repo_ptr : m_archive_repositories) { + if (repo_ptr->get_uuid() == selected_it->first) { + found = true; + break; + } + } + if (!found) { + selected_it = m_selected_repositories_uuid.erase(selected_it); + } else { + ++selected_it; + } + } + // Do the same for m_has_installed_printer_repositories_uuid + auto installed_it = m_has_installed_printer_repositories_uuid.begin(); + while (installed_it != m_has_installed_printer_repositories_uuid.end()) { + bool found = false; + for (const auto &repo_ptr : m_archive_repositories) { + if (repo_ptr->get_uuid() == installed_it->first) { + found = true; + break; + } + } + if (!found) { + installed_it = m_has_installed_printer_repositories_uuid.erase(installed_it); + } else { + ++installed_it; + } + } +} + +std::string PresetArchiveDatabase::get_next_uuid() +{ + boost::uuids::uuid uuid = m_uuid_generator(); + return boost::uuids::to_string(uuid); +} + +namespace { +bool sync_inner(std::string& manifest) +{ + bool ret = false; + std::string url = Utils::ServiceConfig::instance().preset_repo_repos_url(); + auto http = Http::get(std::move(url)); + add_authorization_header(http); + http + .timeout_max(30) + .on_error([&](std::string body, std::string error, unsigned http_status) { + BOOST_LOG_TRIVIAL(error) << "Failed to get online archive source manifests: "<< body << " ; " << error << " ; " << http_status; + ret = false; + }) + .on_complete([&](std::string body, unsigned /* http_status */) { + manifest = body; + ret = true; + }) + .perform_sync(); + return ret; +} +} + +void PresetArchiveDatabase::sync_blocking() +{ + std::string manifest; + if (!sync_inner(manifest)) + return; + read_server_manifest(std::move(manifest)); +} + +}} // Slic3r::GUI diff --git a/src/slic3r/GUI/PresetArchiveDatabase.hpp b/src/slic3r/GUI/PresetArchiveDatabase.hpp new file mode 100644 index 0000000..e5720e1 --- /dev/null +++ b/src/slic3r/GUI/PresetArchiveDatabase.hpp @@ -0,0 +1,185 @@ +#ifndef slic3r_PresetArchiveDatabase_hpp_ +#define slic3r_PresetArchiveDatabase_hpp_ + +#include "Event.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace Slic3r { +class AppConfig; +namespace GUI { + +struct ArchiveRepositoryGetFileArgs { + boost::filesystem::path target_path; + + std::string repository_id; +}; + +class ArchiveRepository +{ +public: + struct RepositoryManifest { + // mandatory + std::string id; + std::string name; + std::string url; + // optional + std::string index_url; + std::string description; + std::string visibility; + // not read from manifest json + boost::filesystem::path tmp_path; // Where archive is unzziped. Created each app run. + boost::filesystem::path source_path; // Path given by user. Stored between app runs. + + RepositoryManifest() = default; + RepositoryManifest( + const std::string &id, + const std::string &name, + const std::string &url, + const std::string &index_url = "", + const std::string &description = "", + const std::string &visibility = "", + const boost::filesystem::path &tmp_path = "", + const boost::filesystem::path &source_path = "" + ) + : id(id) + , name(name) + , url(url) + , index_url(index_url) + , description(description) + , visibility(visibility) + , tmp_path(tmp_path) + , source_path(source_path) + {} + RepositoryManifest(const RepositoryManifest &other) + : id(other.id) + , name(other.name) + , url(other.url) + , index_url(other.index_url) + , description(other.description) + , visibility(other.visibility) + , tmp_path(other.tmp_path) + , source_path(other.source_path) + {} + }; + // Use std::move when calling constructor. + ArchiveRepository(const std::string& uuid, RepositoryManifest&& data) + : m_data(std::move(data)) + , m_uuid(uuid) + {} + virtual ~ArchiveRepository() {} + // Gets vendor_indices.zip to target_path + virtual bool get_archive(const boost::filesystem::path& target_path) const = 0; + // Gets file if repository_id arg matches m_id. + // Should be used to get the most recent ini file and every missing resource. + virtual bool get_file(const std::string& source_subpath, const boost::filesystem::path& target_path, const std::string& repository_id) const = 0; + // Gets file without id check - for not yet encountered vendors only! + virtual bool get_ini_no_id(const std::string& source_subpath, const boost::filesystem::path& target_path) const = 0; + const RepositoryManifest& get_manifest() const { return m_data; } + std::string get_uuid() const { return m_uuid; } + // Only local archvies can return false + virtual bool is_extracted() const { return true; } + virtual void do_extract() {} + void set_manifest(RepositoryManifest &&manifest) { m_data = std::move(manifest); } + +protected: + RepositoryManifest m_data; + std::string m_uuid; +}; + +class OnlineArchiveRepository : public ArchiveRepository +{ +public: + OnlineArchiveRepository(const std::string& uuid, RepositoryManifest&& data) : ArchiveRepository(uuid, std::move(data)) + { + if (m_data.url.back() != '/') { + m_data.url += "/"; + } + } + // Gets vendor_indices.zip to target_path. + bool get_archive(const boost::filesystem::path& target_path) const override; + // Gets file if repository_id arg matches m_id. + // Should be used to get the most recent ini file and every missing resource. + bool get_file(const std::string& source_subpath, const boost::filesystem::path& target_path, const std::string& repository_id) const override; + // Gets file without checking id. + // Should be used only if no previous ini file exists. + bool get_ini_no_id(const std::string& source_subpath, const boost::filesystem::path& target_path) const override; +private: + bool get_file_inner(const std::string& url, const boost::filesystem::path& target_path) const; +}; + +class LocalArchiveRepository : public ArchiveRepository +{ +public: + LocalArchiveRepository(const std::string& uuid, RepositoryManifest&& data, bool extracted) : ArchiveRepository(uuid, std::move(data)), m_extracted(extracted) + {} + // Gets vendor_indices.zip to target_path. + bool get_archive(const boost::filesystem::path& target_path) const override; + // Gets file if repository_id arg matches m_id. + // Should be used to get the most recent ini file and every missing resource. + bool get_file(const std::string& source_subpath, const boost::filesystem::path& target_path, const std::string& repository_id) const override; + // Gets file without checking id. + // Should be used only if no previous ini file exists. + bool get_ini_no_id(const std::string& source_subpath, const boost::filesystem::path& target_path) const override; + bool is_extracted() const override { return m_extracted; } + void do_extract() override; + +private: + bool get_file_inner(const boost::filesystem::path& source_path, const boost::filesystem::path& target_path) const; + bool m_extracted; +}; + +typedef std::vector> PrivateArchiveRepositoryVector; +typedef std::vector SharedArchiveRepositoryVector; + +class PresetArchiveDatabase +{ +public: + PresetArchiveDatabase(AppConfig* app_config, wxEvtHandler* evt_handler); + ~PresetArchiveDatabase() {} + + void sync_blocking(); + + // Do not use get_all_archive_repositories to perform any GET calls. Use get_selected_archive_repositories instead. + SharedArchiveRepositoryVector get_all_archive_repositories() const; + // Creates copy of m_archive_repositories of shared pointers that are selected in m_selected_repositories_uuid. + SharedArchiveRepositoryVector get_selected_archive_repositories() const; + bool is_selected_repository_by_uuid(const std::string& uuid) const; + bool is_selected_repository_by_id(const std::string& repo_id) const; + const std::map& get_selected_repositories_uuid() const { assert(m_selected_repositories_uuid.size() == m_archive_repositories.size()); return m_selected_repositories_uuid; } + // Does re-extract all local archives + bool set_selected_repositories(const std::vector& used_uuids, std::string& msg); + void set_installed_printer_repositories(const std::vector &used_ids); + std::string add_local_archive(const boost::filesystem::path path, std::string& msg); + void remove_local_archive(const std::string& uuid); + bool extract_archives_with_check(std::string &msg); + +private: + void load_app_manifest_json(); + void copy_initial_manifest(); + void read_server_manifest(const std::string& json_body); + void save_app_manifest_json() const; + void clear_online_repos(); + bool is_selected(const std::string& uuid) const; + bool has_installed_printers(const std::string &uuid) const; + boost::filesystem::path get_stored_manifest_path() const; + void consolidate_uuid_maps(); + void extract_local_archives(); + std::string get_next_uuid(); + wxEvtHandler* p_evt_handler; + boost::filesystem::path m_unq_tmp_path; + PrivateArchiveRepositoryVector m_archive_repositories; + std::map m_selected_repositories_uuid; + std::map m_has_installed_printer_repositories_uuid; + boost::uuids::random_generator m_uuid_generator; +}; + +}} // Slic3r::GUI + +#endif // PresetArchiveDatabase \ No newline at end of file diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 982a06c..039d2a7 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1,3 +1,4 @@ + #include "PresetComboBoxes.hpp" #include @@ -15,6 +16,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -39,6 +41,7 @@ #include "BitmapCache.hpp" #include "PhysicalPrinterDialog.hpp" #include "MsgDialog.hpp" +#include "UserAccount.hpp" #include "Widgets/ComboBox.hpp" @@ -78,9 +81,32 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const BitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY), m_type(preset_type), m_last_selected(wxNOT_FOUND), - m_em_unit(em_unit(this)), - m_preset_bundle(preset_bundle ? preset_bundle : wxGetApp().preset_bundle) + m_em_unit(em_unit(this)) { + init_from_bundle(preset_bundle); + + m_bitmapCompatible = get_bmp_bundle("flag_green"); + m_bitmapIncompatible = get_bmp_bundle("flag_red"); + + // parameters for an icon's drawing + fill_width_height(); + + Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent& e) { + if (m_suppress_change) + e.StopPropagation(); + else + e.Skip(); + }); + Bind(wxEVT_COMBOBOX_DROPDOWN, [this](wxCommandEvent&) { m_suppress_change = false; }); + Bind(wxEVT_COMBOBOX_CLOSEUP, [this](wxCommandEvent&) { m_suppress_change = true; }); + + Bind(wxEVT_COMBOBOX, &PresetComboBox::OnSelect, this); +} + +void PresetComboBox::init_from_bundle(PresetBundle* preset_bundle) +{ + m_preset_bundle = preset_bundle ? preset_bundle : wxGetApp().preset_bundle; + switch (m_type) { case Preset::TYPE_PRINT: { @@ -110,23 +136,6 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const } default: break; } - - m_bitmapCompatible = get_bmp_bundle("flag_green"); - m_bitmapIncompatible = get_bmp_bundle("flag_red"); - - // parameters for an icon's drawing - fill_width_height(); - - Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent& e) { - if (m_suppress_change) - e.StopPropagation(); - else - e.Skip(); - }); - Bind(wxEVT_COMBOBOX_DROPDOWN, [this](wxCommandEvent&) { m_suppress_change = false; }); - Bind(wxEVT_COMBOBOX_CLOSEUP, [this](wxCommandEvent&) { m_suppress_change = true; }); - - Bind(wxEVT_COMBOBOX, &PresetComboBox::OnSelect, this); } void PresetComboBox::OnSelect(wxCommandEvent& evt) @@ -203,7 +212,6 @@ void PresetComboBox::update_selection() SetToolTip(GetString(m_last_selected)); // A workaround for a set of issues related to text fitting into gtk widgets: -// See e.g.: https://github.com/qidi3d/QIDISlicer/issues/4584 #if defined(__WXGTK20__) || defined(__WXGTK3__) GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_widget)); @@ -248,6 +256,8 @@ void PresetComboBox::update(std::string select_preset_name) Clear(); invalidate_selection(); + const ExtruderFilaments* extruder_filaments = m_preset_bundle->extruders_filaments.empty() ? nullptr : &m_preset_bundle->extruders_filaments[m_extruder_idx]; + const std::deque& presets = m_collection->get_presets(); struct PresetData { @@ -259,6 +269,9 @@ void PresetComboBox::update(std::string select_preset_name) std::vector system_presets; std::vector nonsys_presets; std::vector incomp_presets; + std::vector template_presets; + + const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates"); wxString selected = ""; if (!presets.front().is_visible) @@ -267,7 +280,9 @@ void PresetComboBox::update(std::string select_preset_name) for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; - if (!m_show_all && (!preset.is_visible || !preset.is_compatible)) + const bool is_compatible = m_type == Preset::TYPE_FILAMENT && extruder_filaments ? extruder_filaments->filament(i).is_compatible : preset.is_compatible; + + if (!m_show_all && (!preset.is_visible || !is_compatible)) continue; // marker used for disable incompatible printer models for the selected physical printer @@ -283,7 +298,7 @@ void PresetComboBox::update(std::string select_preset_name) } std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, is_compatible, preset.is_system || preset.is_default); assert(bmp); if (!is_enabled) { @@ -293,13 +308,24 @@ void PresetComboBox::update(std::string select_preset_name) } else if (preset.is_default || preset.is_system) { - system_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled}); + if (preset.vendor && preset.vendor->templates_profile) { + if (allow_templates) + template_presets.push_back({ get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled }); + } + else { + system_presets.push_back({ get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled }); + } if (preset.name == select_preset_name) selected = preset.name; if (preset.is_dirty && m_show_modif_preset_separately) { wxString preset_name = get_preset_name_with_suffix(preset); - system_presets.push_back({preset_name, preset_name.Lower(), bmp, is_enabled}); + if (preset.vendor && preset.vendor->templates_profile) { + if (allow_templates) + template_presets.push_back({ get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled }); + } + else + system_presets.push_back({preset_name, preset_name.Lower(), bmp, is_enabled}); if (into_u8(preset_name) == select_preset_name) selected = preset_name; } @@ -347,6 +373,22 @@ void PresetComboBox::update(std::string select_preset_name) validate_selection(it->name == selected); } } + + if (!template_presets.empty()) + { + std::sort(template_presets.begin(), template_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); + for (std::vector::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + int item_id = Append(it->name, *it->bitmap); + if (!it->enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->name == selected); + } + } + if (!incomp_presets.empty()) { std::sort(incomp_presets.begin(), incomp_presets.end(), [](const PresetData& a, const PresetData& b) { @@ -451,7 +493,10 @@ void PresetComboBox::update() void PresetComboBox::update_from_bundle() { - this->update(m_collection->get_selected_preset().name); + if (m_collection->type() == Preset::TYPE_FILAMENT && !m_preset_bundle->extruders_filaments.empty()) + this->update(m_preset_bundle->extruders_filaments[m_extruder_idx].get_selected_preset_name()); + else + this->update(m_collection->get_selected_preset().name); } void PresetComboBox::msw_rescale() @@ -624,6 +669,9 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers() else if (dynamic_cast(this)!=nullptr) wxGetApp().sidebar().update_presets(m_type); + // Check and show "Physical printer" page if needed + // wxGetApp().show_printer_webview_tab(); + return true; } @@ -675,6 +723,51 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset else switch_to_tab(); }); + + if (m_type == Preset::TYPE_PRINTER) { + +#ifdef _WIN32 + connect_info_sizer = new wxBoxSizer(wxHORIZONTAL); + + connect_available_info = new wxGenericStaticText(parent, wxID_ANY, /*"Info about Connect for printer preset"*/ ""); + connect_offline_info = new wxGenericStaticText(parent, wxID_ANY, /*"Info about Connect for printer preset"*/ ""); + connect_printing_info = new wxGenericStaticText(parent, wxID_ANY, /*"Info about Connect for printer preset"*/ ""); + + connect_info_sizer->Add(new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("connect_status", 14, 14, "#5CD800")), 0, wxALIGN_CENTER_VERTICAL); + connect_info_sizer->Add(connect_available_info, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + + connect_info_sizer->Add(new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("connect_status", 14, 14, "#FB3636")), 0, wxALIGN_CENTER_VERTICAL); + connect_info_sizer->Add(connect_offline_info, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + + connect_info_sizer->Add(new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("connect_status", 14, 14, "#2E9BFF")), 0, wxALIGN_CENTER_VERTICAL); + connect_info_sizer->Add(connect_printing_info, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); +#else + connect_info_sizer = new wxFlexGridSizer(9, 10, 0); + connect_info_sizer->SetFlexibleDirection(wxBOTH); + + connect_available_info = new wxStaticText(parent, wxID_ANY, "0"); + connect_offline_info = new wxStaticText(parent, wxID_ANY, "0"); + connect_printing_info = new wxStaticText(parent, wxID_ANY, "0"); + connect_available_info->SetFont(wxGetApp().bold_font()); + connect_offline_info ->SetFont(wxGetApp().bold_font()); + connect_printing_info ->SetFont(wxGetApp().bold_font()); + + connect_info_sizer->Add(new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("connect_status", 14, 14, "#5CD800")), 0, wxALIGN_CENTER_VERTICAL | wxTOP, 1); + connect_info_sizer->Add(connect_available_info, 0, wxALIGN_CENTER_VERTICAL); + // TRN: this is part of the infoline below Printer Settings dropdown, informing about number of printers available/offline/printing in QIDI Connect. + connect_info_sizer->Add(new wxStaticText(parent, wxID_ANY, _L("available")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + + connect_info_sizer->Add(new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("connect_status", 14, 14, "#FB3636")), 0, wxALIGN_CENTER_VERTICAL | wxTOP, 1); + connect_info_sizer->Add(connect_offline_info, 0, wxALIGN_CENTER_VERTICAL); + // TRN: this is part of the infoline below Printer Settings dropdown, informing about number of printers available/offline/printing in QIDI Connect. + connect_info_sizer->Add(new wxStaticText(parent, wxID_ANY, _L("offline")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + + connect_info_sizer->Add(new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("connect_status", 14, 14, "#2E9BFF")), 0, wxALIGN_CENTER_VERTICAL | wxTOP, 1); + connect_info_sizer->Add(connect_printing_info, 0, wxALIGN_CENTER_VERTICAL); + // TRN: this is part of the infoline below Printer Settings dropdown, informing about number of printers available/offline/printing in QIDI Connect. + connect_info_sizer->Add(new wxStaticText(parent, wxID_ANY, _L("printing")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); +#endif + } } PlaterPresetComboBox::~PlaterPresetComboBox() @@ -852,6 +945,168 @@ wxString PlaterPresetComboBox::get_preset_name(const Preset& preset) return from_u8(name + suffix(preset)); } + +struct PrinterStatesCount +{ + size_t offline_cnt { 0 }; + size_t busy_cnt { 0 }; + size_t available_cnt { 0 }; + size_t total { 0 }; +}; + +static PrinterStatesCount get_printe_states_count(const std::vector& states) +{ + PrinterStatesCount states_cnt; + + for (size_t i = 0; i < states.size(); i++) { + if (states[i] == 0) + continue; + + ConnectPrinterState state = static_cast(i); + + if (state == ConnectPrinterState::CONNECT_PRINTER_OFFLINE) + states_cnt.offline_cnt += states[i]; + else if (state == ConnectPrinterState::CONNECT_PRINTER_PAUSED || + state == ConnectPrinterState::CONNECT_PRINTER_STOPPED || + state == ConnectPrinterState::CONNECT_PRINTER_PRINTING || + state == ConnectPrinterState::CONNECT_PRINTER_BUSY || + state == ConnectPrinterState::CONNECT_PRINTER_ATTENTION || + state == ConnectPrinterState::CONNECT_PRINTER_ERROR) + states_cnt.busy_cnt += states[i]; + else + states_cnt.available_cnt += states[i]; + } + states_cnt.total = states_cnt.offline_cnt + states_cnt.busy_cnt + states_cnt.available_cnt; + + return states_cnt; +} + +static std::string get_connect_state_suffix_for_printer(const Preset& printer_preset) +{ + // process real data from Connect + if (auto printer_state_map = wxGetApp().plater()->get_user_account()->get_printer_state_map(); + !printer_state_map.empty()) { + + for (const auto& [printer_model_nozzle_pair, states] : printer_state_map) { + std::string printer_model = printer_preset.config.opt_string("printer_model"); + std::string vendor_repo_prefix; + if (printer_preset.vendor) { + vendor_repo_prefix = printer_preset.vendor->repo_prefix; + } else if (std::string inherits = printer_preset.inherits(); !inherits.empty()) { + const Preset *parent = wxGetApp().preset_bundle->printers.find_preset(inherits); + if (parent && parent->vendor) { + vendor_repo_prefix = parent->vendor->repo_prefix; + } + } + if (printer_model.find(vendor_repo_prefix) == 0) { + printer_model = printer_model.substr(vendor_repo_prefix.size()); + boost::trim_left(printer_model); + } + + if (printer_preset.config.has("nozzle_diameter")) { + double nozzle_diameter = static_cast(printer_preset.config.option("nozzle_diameter"))->values[0]; + wxString nozzle_diameter_serialized = double_to_string(nozzle_diameter); + nozzle_diameter_serialized.Replace(L",", L"."); + + if (printer_model_nozzle_pair.first == printer_model + && printer_model_nozzle_pair.second == GUI::into_u8(nozzle_diameter_serialized)) + { + PrinterStatesCount states_cnt = get_printe_states_count(states); + + if (states_cnt.available_cnt > 0) + return "_available"; + if (states_cnt.busy_cnt > 0) + return "_busy"; + return "_offline"; + } + } else { + if (printer_model_nozzle_pair.first == printer_model) { + PrinterStatesCount states_cnt = get_printe_states_count(states); + + if (states_cnt.available_cnt > 0) + return "_available"; + if (states_cnt.busy_cnt > 0) + return "_busy"; + return "_offline"; + } + } + } + } + + return ""; +} + +static bool fill_data_to_connect_info_line( const Preset& printer_preset, +#ifdef _WIN32 + wxGenericStaticText* connect_available_info, + wxGenericStaticText* connect_offline_info, + wxGenericStaticText* connect_printing_info) +#else + wxStaticText* connect_available_info, + wxStaticText* connect_offline_info, + wxStaticText* connect_printing_info) +#endif +{ + if (auto printer_state_map = wxGetApp().plater()->get_user_account()->get_printer_state_map(); + !printer_state_map.empty()) { + + for (const auto& [printer_model_nozzle_pair, states] : printer_state_map) { + // get printer_model without repo prefix + std::string printer_model = printer_preset.config.opt_string("printer_model"); + std::string vendor_repo_prefix; + if (printer_preset.vendor) { + vendor_repo_prefix = printer_preset.vendor->repo_prefix; + } else if (std::string inherits = printer_preset.inherits(); !inherits.empty()) { + const Preset *parent = wxGetApp().preset_bundle->printers.find_preset(inherits); + if (parent && parent->vendor) { + vendor_repo_prefix = parent->vendor->repo_prefix; + } + } + if (printer_model.find(vendor_repo_prefix) == 0) { + printer_model = printer_model.substr(vendor_repo_prefix.size()); + boost::trim_left(printer_model); + } + + if (printer_preset.config.has("nozzle_diameter")) { + double nozzle_diameter = static_cast(printer_preset.config.option("nozzle_diameter"))->values[0]; + wxString nozzle_diameter_serialized = double_to_string(nozzle_diameter); + nozzle_diameter_serialized.Replace(L",", L"."); + + if (printer_model_nozzle_pair.first == printer_model + && printer_model_nozzle_pair.second == GUI::into_u8(nozzle_diameter_serialized)) + { + PrinterStatesCount states_cnt = get_printe_states_count(states); +#ifdef _WIN32 + connect_available_info->SetLabelMarkup(format_wxstr("%1% %2%", format("%1%", states_cnt.available_cnt), _L("available"))); + connect_offline_info ->SetLabelMarkup(format_wxstr("%1% %2%", format("%1%", states_cnt.offline_cnt), _L("offline"))); + connect_printing_info ->SetLabelMarkup(format_wxstr("%1% %2%", format("%1%", states_cnt.busy_cnt), _L("printing"))); +#else + connect_available_info->SetLabel(format_wxstr("%1% ", states_cnt.available_cnt)); + connect_offline_info ->SetLabel(format_wxstr("%1% ", states_cnt.offline_cnt)); + connect_printing_info ->SetLabel(format_wxstr("%1% ", states_cnt.busy_cnt)); +#endif + return true; + } + } else { + if (printer_model_nozzle_pair.first == printer_model) { + PrinterStatesCount states_cnt = get_printe_states_count(states); +#ifdef _WIN32 + connect_available_info->SetLabelMarkup(format_wxstr("%1% %2%", format("%1%", states_cnt.available_cnt), _L("available"))); + connect_offline_info ->SetLabelMarkup(format_wxstr("%1% %2%", format("%1%", states_cnt.offline_cnt), _L("offline"))); + connect_printing_info ->SetLabelMarkup(format_wxstr("%1% %2%", format("%1%", states_cnt.busy_cnt), _L("printing"))); +#else + connect_available_info->SetLabel(format_wxstr("%1% ", states_cnt.available_cnt)); + connect_offline_info ->SetLabel(format_wxstr("%1% ", states_cnt.offline_cnt)); + connect_printing_info ->SetLabel(format_wxstr("%1% ", states_cnt.busy_cnt)); +#endif + return true; + } + } + } + } + return false; +} + // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void PlaterPresetComboBox::update() @@ -924,6 +1179,12 @@ void PlaterPresetComboBox::update() std::string bitmap_key, filament_rgb, extruder_rgb, material_rgb; std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + if (m_type == Preset::TYPE_PRINTER) { + bitmap_type_name = bitmap_key += get_connect_state_suffix_for_printer(preset); + if (is_selected) + connect_info_sizer->Show(fill_data_to_connect_info_line(preset, connect_available_info, connect_offline_info, connect_printing_info)); + } + bool single_bar = false; if (m_type == Preset::TYPE_FILAMENT) { @@ -1029,11 +1290,17 @@ void PlaterPresetComboBox::update() bool selected; // is selected }; std::vector preset_data; + bool is_selected_some_ph_printer{ false }; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { for (const std::string& preset_name : it->get_preset_names()) { - preset_data.push_back({ wxString::FromUTF8(it->get_full_name(preset_name)).Lower(), preset_name, it->get_full_name(preset_name), ph_printers.is_selected(it, preset_name) }); + bool is_selected = ph_printers.is_selected(it, preset_name); + preset_data.push_back({ wxString::FromUTF8(it->get_full_name(preset_name)).Lower(), preset_name, it->get_full_name(preset_name), is_selected }); + if (is_selected) + is_selected_some_ph_printer = true; } } + if (is_selected_some_ph_printer) + connect_info_sizer->Show(false); std::sort(preset_data.begin(), preset_data.end(), [](const PhysicalPrinterPresetData& a, const PhysicalPrinterPresetData& b) { return a.lower_name < b.lower_name; }); @@ -1105,6 +1372,12 @@ void PlaterPresetComboBox::sys_color_changed() { PresetComboBox::sys_color_changed(); edit_btn->sys_color_changed(); + + if (connect_info_sizer) { + wxGetApp().UpdateDarkUI(connect_available_info); + wxGetApp().UpdateDarkUI(connect_printing_info); + wxGetApp().UpdateDarkUI(connect_offline_info); + } } // --------------------------------- @@ -1166,7 +1439,7 @@ void TabPresetComboBox::update() Clear(); invalidate_selection(); - const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_active_extruder_idx]; + const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_extruder_idx]; const std::deque& presets = m_collection->get_presets(); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 6575c1a..0a0af00 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -1,3 +1,4 @@ + #ifndef slic3r_PresetComboBoxes_hpp_ #define slic3r_PresetComboBoxes_hpp_ @@ -12,6 +13,7 @@ class wxString; class wxTextCtrl; class wxStaticText; +class wxGenericStaticText; class ScalableButton; class wxBoxSizer; class wxComboBox; @@ -36,6 +38,8 @@ public: PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr); ~PresetComboBox(); + void init_from_bundle(PresetBundle* preset_bundle); + enum LabelItemType { LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01, LABEL_ITEM_DISABLED, @@ -77,6 +81,10 @@ public: virtual void sys_color_changed(); virtual void OnSelect(wxCommandEvent& evt); + // used by Filaments list to update preset list according to the particular extruder + void set_extruder_idx(int extruder_idx) { m_extruder_idx = extruder_idx; } + int get_extruder_idx() { return m_extruder_idx; } + protected: typedef std::size_t Marker; std::function on_selection_changed { nullptr }; @@ -99,6 +107,9 @@ protected: int m_em_unit; bool m_suppress_change { true }; + // This parameter is used by FilamentSettings tab to show filament setting related to the active extruder + int m_extruder_idx{ 0 }; + // parameters for an icon's drawing int icon_height; int norm_icon_width; @@ -150,8 +161,17 @@ public: ScalableButton* edit_btn { nullptr }; - void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; } - int get_extruder_idx() const { return m_extruder_idx; } +#ifdef _WIN32 + wxBoxSizer* connect_info_sizer { nullptr }; + wxGenericStaticText* connect_available_info { nullptr }; + wxGenericStaticText* connect_printing_info { nullptr }; + wxGenericStaticText* connect_offline_info { nullptr }; +#else + wxFlexGridSizer* connect_info_sizer { nullptr }; + wxStaticText* connect_available_info { nullptr }; + wxStaticText* connect_printing_info { nullptr }; + wxStaticText* connect_offline_info { nullptr }; +#endif void switch_to_tab(); void change_extruder_color(); @@ -165,9 +185,6 @@ public: void OnSelect(wxCommandEvent& evt) override; std::string get_selected_ph_printer_name() const; - -private: - int m_extruder_idx = -1; }; @@ -179,8 +196,6 @@ class TabPresetComboBox : public PresetComboBox { bool show_incompatible {false}; bool m_enable_all {false}; - // This parameter is used by FilamentSettings tab to show filament setting related to the active extruder - int m_active_extruder_idx {0}; public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); @@ -199,9 +214,6 @@ public: PresetCollection* presets() const { return m_collection; } Preset::Type type() const { return m_type; } - - // used by Filaments tab to update preset list according to the particular extruder - void set_active_extruder(int extruder_idx) { m_active_extruder_idx = extruder_idx; } }; } // namespace GUI diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 686f4a9..1bc2e5d 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -26,7 +26,6 @@ std::string PresetHints::cooling_description(const Preset &preset) if (cooling) { int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0); int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); - int max_fan_speed = preset.config.opt_int("max_fan_speed", 0); int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5); @@ -309,4 +308,5 @@ std::string PresetHints::top_bottom_shell_thickness_explanation(const PresetBund return out; } + }; // namespace Slic3r diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 98f071d..f6bdf7a 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -71,7 +71,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path & path, auto *label_dir_hint2 = new wxStaticText(this, wxID_ANY, _L("Upload to Printer Host with the following filename:")); label_dir_hint2->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); - //B61 //B64 + //B64 ThumbnailData thumbnail_data = m_plater->get_thumbnailldate_send(); wxImage image(thumbnail_data.width, thumbnail_data.height); @@ -163,7 +163,10 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path & path, wxScrolledWindow *scroll_macine_list = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(800), FromDIP(300)), wxHSCROLL | wxVSCROLL); - scroll_macine_list->SetBackgroundColour(*wxWHITE); + if(check_dark_mode()) + scroll_macine_list->SetBackgroundColour(wxColour(62,62,62)); + else + scroll_macine_list->SetBackgroundColour(*wxWHITE); scroll_macine_list->SetScrollRate(5, 5); scroll_macine_list->SetMinSize(wxSize(FromDIP(320), 10 * FromDIP(27))); scroll_macine_list->SetMaxSize(wxSize(FromDIP(320), 10 * FromDIP(27))); @@ -222,7 +225,10 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path & path, wxBoxSizer *scrool_box_sizer = new wxBoxSizer(wxVERTICAL); wxPanel *panel = new wxPanel(this, wxID_ANY); - panel->SetBackgroundColour(*wxWHITE); + if(check_dark_mode()) + panel->SetBackgroundColour(wxColour(62,62,62)); + else + panel->SetBackgroundColour(*wxWHITE); wxBoxSizer *box_sizer = new wxBoxSizer(wxHORIZONTAL); panel->SetSizer(box_sizer); @@ -261,7 +267,10 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path & path, wxScrolledWindow *scroll_macine_list2 = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(800), FromDIP(300)), wxHSCROLL | wxVSCROLL); - scroll_macine_list2->SetBackgroundColour(*wxWHITE); + if(check_dark_mode()) + scroll_macine_list2->SetBackgroundColour(wxColour(62,62,62)); + else + scroll_macine_list2->SetBackgroundColour(*wxWHITE); scroll_macine_list2->SetScrollRate(5, 5); scroll_macine_list2->SetMinSize(wxSize(FromDIP(320), 10 * FromDIP(27))); scroll_macine_list2->SetMaxSize(wxSize(FromDIP(320), 10 * FromDIP(27))); @@ -296,7 +305,10 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path & path, wxBoxSizer *scrool_box_sizer2 = new wxBoxSizer(wxVERTICAL); wxPanel *panel2 = new wxPanel(this, wxID_ANY); - panel2->SetBackgroundColour(*wxWHITE); + if(check_dark_mode()) + panel2->SetBackgroundColour(wxColour(62,62,62)); + else + panel2->SetBackgroundColour(*wxWHITE); wxBoxSizer *box_sizer2 = new wxBoxSizer(wxHORIZONTAL); panel2->SetSizer(box_sizer2); @@ -574,7 +586,7 @@ std::string PrintHostSendDialog::storage() const return GUI::format("%1%", m_preselected_storage); if (combo_storage->GetSelection() < 0 || combo_storage->GetSelection() >= int(m_paths.size())) return {}; - return boost::nowide::narrow(m_paths[combo_storage->GetSelection()]); + return into_u8(m_paths[combo_storage->GetSelection()]); } //B64 wxBoxSizer *PrintHostSendDialog::create_item_input( @@ -608,20 +620,39 @@ wxBoxSizer *PrintHostSendDialog::create_item_input( sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 3); sizer_input->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); - input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, param, input](wxCommandEvent &e) { + //input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, param, input](wxCommandEvent &e) { + // auto value = input->GetTextCtrl()->GetValue(); + // wxGetApp().app_config->set(param, std::string(value.mb_str())); + // wxGetApp().app_config->save(); + // e.Skip(); + //}); + + //input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, param, input](wxFocusEvent &e) { + // auto value = input->GetTextCtrl()->GetValue(); + // wxGetApp().app_config->set(param, std::string(value.mb_str())); + // wxGetApp().app_config->save(); + // e.Skip(); + //}); + + input->GetTextCtrl()->Bind(wxEVT_TEXT, [this, param, input](wxCommandEvent &e) { auto value = input->GetTextCtrl()->GetValue(); - wxGetApp().app_config->set(param, std::string(value.mb_str())); - wxGetApp().app_config->save(); + if (!value.empty()) { + if (std::stoi(into_u8(value)) > 6 && (param == "max_send")) { + MessageDialog msg_wingow(nullptr, _L("The max send number cannot exceed 6"), "", wxICON_WARNING | wxOK); + msg_wingow.ShowModal(); + value = "6"; + input->GetTextCtrl()->SetValue(value); + } else if (std::stoi(into_u8(value)) > 240 && (param == "sending_interval")) { + MessageDialog msg_wingow(nullptr, _L("The sending interval cannot exceed 240"), "", wxICON_WARNING | wxOK); + msg_wingow.ShowModal(); + value = "240"; + input->GetTextCtrl()->SetValue(value); + } + wxGetApp().app_config->set(param, std::string(value.mb_str())); + wxGetApp().app_config->save(); + } e.Skip(); }); - - input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, param, input](wxFocusEvent &e) { - auto value = input->GetTextCtrl()->GetValue(); - wxGetApp().app_config->set(param, std::string(value.mb_str())); - wxGetApp().app_config->save(); - e.Skip(); - }); - return sizer_input; } void PrintHostSendDialog::EndModal(int ret) @@ -926,7 +957,9 @@ void PrintHostQueueDialog::on_progress(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()), evt.progress / 100.f); + const wchar_t * nm_str = nm.GetString(); + const wchar_t * hst_str = hst.GetString(); + wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, into_u8(nm_str), into_u8(hst_str), evt.progress / 100.f); } } @@ -937,8 +970,8 @@ void PrintHostQueueDialog::on_wait(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - wxGetApp().notification_manager()->set_upload_job_notification_waittime(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), - boost::nowide::narrow(hst.GetString()), + wxGetApp().notification_manager()->set_upload_job_notification_waittime(evt.job_id + 1, into_u8(nm.GetString()), + into_u8(hst.GetString()), evt.waittime); } void PrintHostQueueDialog::on_error(Event &evt) @@ -972,7 +1005,7 @@ void PrintHostQueueDialog::on_error(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - wxGetApp().notification_manager()->upload_job_notification_show_error(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); + wxGetApp().notification_manager()->upload_job_notification_show_error(evt.job_id + 1, into_u8(nm.GetString()), into_u8(hst.GetString())); } void PrintHostQueueDialog::on_cancel(Event &evt) @@ -987,7 +1020,7 @@ void PrintHostQueueDialog::on_cancel(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); + wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, into_u8(nm.GetString()), into_u8(hst.GetString())); } void PrintHostQueueDialog::on_info(Event& evt) @@ -997,17 +1030,17 @@ void PrintHostQueueDialog::on_info(Event& evt) if (evt.tag == L"resolve") { wxVariant hst(evt.status); job_list->SetValue(hst, evt.job_id, COL_HOST); - wxGetApp().notification_manager()->set_upload_job_notification_host(evt.job_id + 1, boost::nowide::narrow(evt.status)); + wxGetApp().notification_manager()->set_upload_job_notification_host(evt.job_id + 1, into_u8(evt.status)); } else if (evt.tag == L"complete") { wxVariant hst(evt.status); job_list->SetValue(hst, evt.job_id, COL_ERRORMSG); wxGetApp().notification_manager()->set_upload_job_notification_completed(evt.job_id + 1); - wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, boost::nowide::narrow(evt.status)); + wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, into_u8(evt.status)); } else if(evt.tag == L"complete_with_warning"){ wxVariant hst(evt.status); job_list->SetValue(hst, evt.job_id, COL_ERRORMSG); wxGetApp().notification_manager()->set_upload_job_notification_completed_with_warning(evt.job_id + 1); - wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, boost::nowide::narrow(evt.status)); + wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, into_u8(evt.status)); } else if (evt.tag == L"set_complete_off") { wxGetApp().notification_manager()->set_upload_job_notification_comp_on_100(evt.job_id + 1, false); } diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index 1d6c039..5dcc44f 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ class wxTextCtrl; class wxChoice; class wxComboBox; class wxDataViewListCtrl; +class wxCheckBox; namespace Slic3r { @@ -29,8 +31,8 @@ namespace GUI { class SendCheckBox : public wxCheckBox { public: - SendCheckBox(wxWindow *parent, wxWindowID id, const wxString &label, const wxPoint &pos = wxDefaultPosition) - : wxCheckBox(parent, id, label, pos) + SendCheckBox(wxWindow *parent, wxWindowID id, const wxString &label) + : wxCheckBox(parent, id, label) {} void SetState(bool value) diff --git a/src/slic3r/GUI/PrinterWebView.cpp b/src/slic3r/GUI/PrinterWebView.cpp index 0d7c743..7a854ea 100644 --- a/src/slic3r/GUI/PrinterWebView.cpp +++ b/src/slic3r/GUI/PrinterWebView.cpp @@ -317,7 +317,6 @@ void PrinterWebView::CreatThread() { if (m_pauseThread) break; if (!m_net_buttons.empty()) { - BOOST_LOG_TRIVIAL(error) << "machine IP: " << device.local_ip; //y5 std::unique_ptr printhost(PrintHost::get_print_host_url(device.url, device.local_ip)); if (!printhost) { @@ -413,7 +412,7 @@ void PrinterWebView::SetPresetChanged(bool status) { //y3 if (webisNetMode == isNetWeb) { for (DeviceButton* button : m_net_buttons) { - if (m_ip == (button->getIPLabel())) { + if (button->getIPLabel().find(m_ip) != std::string::npos) { button->SetIsSelected(true); break; } @@ -421,17 +420,14 @@ void PrinterWebView::SetPresetChanged(bool status) { } else if (webisNetMode == isLocalWeb) { - if (m_exit_host.find(into_u8(m_ip)) != m_exit_host.end()) - { - for (DeviceButton* button : m_buttons) - { - if (m_ip == (button->getIPLabel())) - { - button->SetIsSelected(true); - break; - } - } - } + for (DeviceButton* button : m_buttons) + { + if (button->getIPLabel().find(m_ip) != std::string::npos) + { + button->SetIsSelected(true); + break; + } + } } else { @@ -480,9 +476,6 @@ void PrinterWebView::SetLoginStatus(bool status) { //y3 if (webisNetMode == isNetWeb) webisNetMode = isDisconnect; - //y5 - std::string head_name = wxGetApp().app_config->get("user_head_name"); - wxString head_savePath = (boost::filesystem::path(Slic3r::data_dir()) / "user" / head_name).make_preferred().string(); m_user_head_name = ""; SetPresetChanged(true); UpdateState(); diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index 51e0cfa..de487ab 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -183,6 +183,5 @@ void ProgressStatusBar::set_font(const wxFont &font) self->SetFont(font); } - } diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 553c841..3643e99 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -58,7 +58,6 @@ public: wxString get_status_text() const; void set_font(const wxFont &font); - void update_dark_ui(); private: diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp index 3f5decf..6d967a4 100644 --- a/src/slic3r/GUI/RammingChart.cpp +++ b/src/slic3r/GUI/RammingChart.cpp @@ -133,6 +133,7 @@ void Chart::mouse_moved(wxMouseEvent& event) { } int delta_x = pos.x - m_previous_mouse.x; int delta_y = pos.y - m_previous_mouse.y; + double new_y = m_dragged->get_pos().m_y - double(delta_y) / m_rect.GetHeight() * visible_area.m_height; if (m_uniform) @@ -140,6 +141,7 @@ void Chart::mouse_moved(wxMouseEvent& event) { b.move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width, new_y - b.get_pos().m_y); else m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width, new_y - m_dragged->get_pos().m_y); + m_previous_mouse = pos; recalculate_line(); } diff --git a/src/slic3r/GUI/RammingChart.hpp b/src/slic3r/GUI/RammingChart.hpp index c5c04ef..510cb06 100644 --- a/src/slic3r/GUI/RammingChart.hpp +++ b/src/slic3r/GUI/RammingChart.hpp @@ -100,19 +100,17 @@ private: } return (-1); } - - + void recalculate_line(); - - + wxRect m_rect; // rectangle on screen the chart is mapped into (screen coordinates) wxPoint m_previous_mouse; std::vector m_buttons; std::vector m_line_to_draw; wxRect2DDouble visible_area; ButtonToDrag* m_dragged = nullptr; - float m_total_volume = 0.f; - + float m_total_volume = 0.f; + bool m_uniform = false; // testing only }; diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index 0b83093..320ae78 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #endif @@ -42,6 +41,7 @@ wxDEFINE_EVENT(EVT_REMOVABLE_DRIVE_EJECTED, RemovableDriveEjectEvent); wxDEFINE_EVENT(EVT_REMOVABLE_DRIVES_CHANGED, RemovableDrivesChangedEvent); wxDEFINE_EVENT(EVT_REMOVABLE_DRIVE_ADDED, wxCommandEvent); + #if _WIN32 std::vector RemovableDriveManager::search_for_removable_drives() const { @@ -72,8 +72,8 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons ULARGE_INTEGER free_space; ::GetDiskFreeSpaceExW(wpath.c_str(), &free_space, nullptr, nullptr); if (free_space.QuadPart > 0) { - path += "\\"; - current_drives.emplace_back(DriveData{ boost::nowide::narrow(volume_name), path }); + path += "\\"; + current_drives.emplace_back(DriveData{ boost::nowide::narrow(volume_name), path }); } } } @@ -1037,6 +1037,7 @@ void RemovableDriveManager::update() assert(m_callback_evt_handler); if (m_callback_evt_handler) wxPostEvent(m_callback_evt_handler, RemovableDrivesChangedEvent(EVT_REMOVABLE_DRIVES_CHANGED)); + // event for printer config file std::vector new_drives; std::set_difference(current_drives.begin(), current_drives.end(), m_current_drives.begin(), m_current_drives.end(), @@ -1050,6 +1051,7 @@ void RemovableDriveManager::update() evt->SetInt((int)m_first_update); m_callback_evt_handler->QueueEvent(evt); } + } m_current_drives = std::move(current_drives); m_first_update = false; diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp index 5482f28..ed64733 100644 --- a/src/slic3r/GUI/RemovableDriveManager.hpp +++ b/src/slic3r/GUI/RemovableDriveManager.hpp @@ -39,6 +39,7 @@ using RemovableDrivesChangedEvent = SimpleEvent; wxDECLARE_EVENT(EVT_REMOVABLE_DRIVES_CHANGED, RemovableDrivesChangedEvent); wxDECLARE_EVENT(EVT_REMOVABLE_DRIVE_ADDED, wxCommandEvent); + #if __APPLE__ // Callbacks on device plug / unplug work reliably on OSX. #define REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS @@ -89,14 +90,12 @@ public: // Called by Win32 Volume arrived / detached callback. void volumes_changed(); #endif // _WIN32 - // returns copy of m_current_drives (protected by mutex) std::vector get_drive_list(); private: bool m_initialized { false }; wxEvtHandler* m_callback_evt_handler { nullptr }; - bool m_first_update{ true }; #ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS // Worker thread, worker thread synchronization and callbacks to the UI thread. diff --git a/src/slic3r/GUI/RulerForDoubleSlider.cpp b/src/slic3r/GUI/RulerForDoubleSlider.cpp new file mode 100644 index 0000000..9f3dab7 --- /dev/null +++ b/src/slic3r/GUI/RulerForDoubleSlider.cpp @@ -0,0 +1,97 @@ + +#include +#include + +#include "RulerForDoubleSlider.hpp" +#include "libslic3r/CustomGCode.hpp" +#include "libslic3r/libslic3r.h" + +using namespace Slic3r; +using namespace CustomGCode; + +namespace DoubleSlider { + +static const double PIXELS_PER_SM_DEFAULT = 96./*DEFAULT_DPI*/ * 5. / 25.4; + +void Ruler::init(const std::vector& values, double scroll_step) +{ + if (m_is_valid) + return; + max_values.clear(); + max_values.reserve(std::count(values.begin(), values.end(), values.front())); + + auto it = std::find(values.begin() + 1, values.end(), values.front()); + while (it != values.end()) { + max_values.push_back(*(it - 1)); + it = std::find(it + 1, values.end(), values.front()); + } + max_values.push_back(*(it - 1)); + + m_is_valid = true; + update(values, scroll_step); +} + +void Ruler::update(const std::vector& values, double scroll_step) +{ + if (!m_is_valid || values.empty() || + // check if need to update ruler in respect to input values + (values.front() == m_min_val && values.back() == m_max_val && m_scroll_step == scroll_step && max_values.size() == m_max_values_cnt)) + return; + + m_min_val = values.front(); + m_max_val = values.back(); + m_scroll_step = scroll_step; + m_max_values_cnt = max_values.size(); + + int pixels_per_sm = lround(m_scale * PIXELS_PER_SM_DEFAULT); + + if (lround(scroll_step) > pixels_per_sm) { + long_step = -1.0; + return; + } + + int pow = -2; + int step = 0; + auto end_it = std::find(values.begin() + 1, values.end(), values.front()); + + while (pow < 3) { + for (int istep : {1, 2, 5}) { + double val = (double)istep * std::pow(10, pow); + auto val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); + + if (val_it == values.end()) + break; + int tick = val_it - values.begin(); + + // find next tick with istep + val *= 2; + val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); + // count of short ticks between ticks + int short_ticks_cnt = val_it == values.end() ? tick : val_it - values.begin() - tick; + + if (lround(short_ticks_cnt * scroll_step) > pixels_per_sm) { + step = istep; + // there couldn't be more then 10 short ticks between ticks + short_step = 0.1 * short_ticks_cnt; + break; + } + } + if (step > 0) + break; + pow++; + } + + long_step = step == 0 ? -1.0 : (double)step * std::pow(10, pow); + if (long_step < 0) + short_step = long_step; +} + +void Ruler::set_scale(double scale) +{ + if (!is_approx(m_scale, scale)) + m_scale = scale; +} + +} // DoubleSlider + + diff --git a/src/slic3r/GUI/RulerForDoubleSlider.hpp b/src/slic3r/GUI/RulerForDoubleSlider.hpp new file mode 100644 index 0000000..2c11f97 --- /dev/null +++ b/src/slic3r/GUI/RulerForDoubleSlider.hpp @@ -0,0 +1,41 @@ + +#ifndef slic3r_GUI_RulerForDoubleSlider_hpp_ +#define slic3r_GUI_RulerForDoubleSlider_hpp_ + +#include +#include +#include +#include + +namespace DoubleSlider { + +class Ruler +{ + bool m_is_valid { false }; + double m_scale { 1. }; + double m_min_val; + double m_max_val; + double m_scroll_step; + size_t m_max_values_cnt; + +public: + + double long_step; + double short_step; + std::vector max_values;// max value for each object/instance in sequence print + // > 1 for sequential print + + void init(const std::vector& values, double scroll_step); + void update(const std::vector& values, double scroll_step); + void set_scale(double scale); + void invalidate() { m_is_valid = false; } + bool is_ok() { return long_step > 0 && short_step > 0; } + size_t count() { return max_values.size(); } + bool valid() { return m_is_valid; } +}; + +} // DoubleSlider; + + + +#endif // slic3r_GUI_RulerForDoubleSlider_hpp_ diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index 02892d6..41c5a34 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -29,7 +29,7 @@ constexpr auto BORDER_W = 10; std::string SavePresetDialog::Item::get_init_preset_name(const std::string &suffix) { - PresetBundle* preset_bundle = dynamic_cast(m_parent)->get_preset_bundle(); + const PresetBundle* preset_bundle = dynamic_cast(m_parent)->get_preset_bundle(); if (!preset_bundle) preset_bundle = wxGetApp().preset_bundle; m_presets = &preset_bundle->get_presets(m_type); @@ -48,6 +48,37 @@ std::string SavePresetDialog::Item::get_init_preset_name(const std::string &suff return preset_name; } +void SavePresetDialog::Item::init_casei_preset_names() +{ + m_casei_preset_names.clear(); + + auto add_names_from_collection = [this](const PresetCollection& presets) { + for (const Preset& preset : presets) + if (!preset.is_default) + m_casei_preset_names.emplace_back(PresetName({ boost::to_lower_copy(preset.name), preset.name })); + }; + + if (m_presets) { + // This item is a part of SavePresetDialog and will check names inside selected PresetCollection + m_casei_preset_names.reserve(m_presets->size()); + add_names_from_collection(*m_presets); + } + else // This item is a part of ConfigWizard and will check names inside all PresetCollections in respect to the m_printer_technology + if (m_preset_bundle) { + auto types_list = PresetBundle::types_list(m_printer_technology); + + size_t presets_cnt = 0; + for (const Preset::Type& type : types_list) + presets_cnt += m_preset_bundle->get_presets(type).size(); + m_casei_preset_names.reserve(presets_cnt); + + for (const Preset::Type& type : types_list) + add_names_from_collection(m_preset_bundle->get_presets(type)); + } + + std::sort(m_casei_preset_names.begin(), m_casei_preset_names.end()); +} + void SavePresetDialog::Item::init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string preset_name) { if (m_use_text_ctrl) { @@ -110,6 +141,8 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox input_name_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); init_input_name_ctrl(input_name_sizer, get_init_preset_name(suffix)); + init_casei_preset_names(); + if (label_top) sizer->Add(label_top, 0, wxEXPAND | wxTOP| wxBOTTOM, BORDER_W); sizer->Add(input_name_sizer,0, wxEXPAND | (label_top ? 0 : wxTOP) | wxBOTTOM, BORDER_W); @@ -121,8 +154,9 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox update(); } -SavePresetDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, const std::string& def_name, PrinterTechnology pt /*= ptFFF*/): +SavePresetDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, const std::string& def_name, PresetBundle* preset_bundle, PrinterTechnology pt /*= ptFFF*/): m_preset_name(def_name), + m_preset_bundle(preset_bundle), m_printer_technology(pt), m_parent(parent), m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))), @@ -134,21 +168,55 @@ SavePresetDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, const std::str input_name_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); init_input_name_ctrl(input_name_sizer, m_preset_name); + init_casei_preset_names(); + sizer->Add(input_name_sizer,0, wxEXPAND | wxBOTTOM, BORDER_W); sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W); update(); } +static std::string get_conflict_name(const std::vector& casei_names, const std::string& preset_name) +{ + if (!casei_names.empty()) { + const std::string lower_name = boost::to_lower_copy(preset_name); + auto it = Slic3r::lower_bound_by_predicate(casei_names.begin(), casei_names.end(), + [lower_name](const auto& l) { return l.casei_name < lower_name; }); + if (it != casei_names.end() && it->casei_name == lower_name) + return it->name; + } + return std::string(); +} + +std::string SavePresetDialog::Item::preset_name() const +{ + if (m_use_text_ctrl) + return m_preset_name; + + const std::string existed_preset_name = get_conflict_name(m_casei_preset_names, m_preset_name); + if (existed_preset_name.empty()) + return m_preset_name; + + return existed_preset_name; +} + const Preset* SavePresetDialog::Item::get_existing_preset() const { - if (m_presets) - return m_presets->find_preset(m_preset_name, false); + std::string existed_preset_name = get_conflict_name(m_casei_preset_names, m_preset_name); + if (existed_preset_name.empty()) { + // Preset has not been not found in the sorted list of non-default presets. Try the defaults. + return nullptr; + } - for (const Preset::Type& type : PresetBundle::types_list(m_printer_technology)) { - const PresetCollection& presets = wxGetApp().preset_bundle->get_presets(type); - if (const Preset* preset = presets.find_preset(m_preset_name, false)) - return preset; + if (m_presets) + return m_presets->find_preset(existed_preset_name, false); + + if (m_preset_bundle) { + for (const Preset::Type& type : PresetBundle::types_list(m_printer_technology)) { + const PresetCollection& presets = m_preset_bundle->get_presets(type); + if (const Preset* preset = presets.find_preset(existed_preset_name, false)) + return preset; + } } return nullptr; @@ -187,6 +255,7 @@ void SavePresetDialog::Item::update() if (m_valid_type == ValidationType::Valid && existing && (existing->is_default || existing->is_system)) { info_line = m_use_text_ctrl ? _L("This name is used for a system profile name, use another.") : _L("Cannot overwrite a system profile."); + info_line += "\n" + GUI::format_wxstr("(%1%)", existing->name); m_valid_type = ValidationType::NoValid; } @@ -207,10 +276,11 @@ void SavePresetDialog::Item::update() } else { if (existing->is_compatible) - info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()); + info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % existing->name).str()); else - info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists and is incompatible with selected printer.")) % m_preset_name).str()); - info_line += "\n" + _L("Note: This preset will be replaced after saving"); + info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists and is incompatible with selected printer.")) % existing->name).str()); + info_line += "\n" + (m_use_text_ctrl ? _L("Note: This preset will be replaced after renaming") : + _L("Note: Preset modifications will be saved exactly into this preset")); m_valid_type = ValidationType::Warning; } } @@ -269,8 +339,6 @@ void SavePresetDialog::Item::update_valid_bmp() void SavePresetDialog::Item::accept() { - if (m_valid_type == ValidationType::Warning) - m_presets->delete_preset(m_preset_name); } void SavePresetDialog::Item::Enable(bool enable /*= true*/) diff --git a/src/slic3r/GUI/SavePresetDialog.hpp b/src/slic3r/GUI/SavePresetDialog.hpp index 1450ebb..e51401c 100644 --- a/src/slic3r/GUI/SavePresetDialog.hpp +++ b/src/slic3r/GUI/SavePresetDialog.hpp @@ -36,8 +36,11 @@ public: Warning }; + // Item as an item inside of the SavePresetDialog Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent, bool is_for_multiple_save); - Item(wxWindow* parent, wxBoxSizer* sizer, const std::string& def_name, PrinterTechnology pt = ptFFF); + + // Item as a separate control(f.e. as a part of ConfigWizard to check name of the new custom priter) + Item(wxWindow* parent, wxBoxSizer* sizer, const std::string& def_name, PresetBundle* preset_bundle, PrinterTechnology pt = ptFFF); void update_valid_bmp(); void accept(); @@ -45,7 +48,14 @@ public: bool is_valid() const { return m_valid_type != ValidationType::NoValid; } Preset::Type type() const { return m_type; } - std::string preset_name() const { return m_preset_name; } + std::string preset_name() const; + + struct PresetName { + std::string casei_name; + std::string name; + + bool operator<(const PresetName& other) const { return other.casei_name > this->casei_name; } + }; private: Preset::Type m_type {Preset::TYPE_INVALID}; @@ -60,10 +70,14 @@ public: wxTextCtrl* m_text_ctrl {nullptr}; wxStaticText* m_valid_label {nullptr}; - PresetCollection* m_presets {nullptr}; + const PresetCollection* m_presets {nullptr}; + const PresetBundle* m_preset_bundle {nullptr}; + + std::vector m_casei_preset_names; std::string get_init_preset_name(const std::string &suffix); void init_input_name_ctrl(wxBoxSizer *input_name_sizer, std::string preset_name); + void init_casei_preset_names(); const Preset* get_existing_preset() const ; void update(); @@ -94,7 +108,7 @@ public: void AddItem(Preset::Type type, const std::string& suffix, bool is_for_multiple_save); - PresetBundle* get_preset_bundle() const { return m_preset_bundle; } + const PresetBundle* get_preset_bundle() const { return m_preset_bundle; } std::string get_name(); std::string get_name(Preset::Type type); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 062560a..1846f5e 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -12,7 +12,9 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" #include "GUI_App.hpp" -#include "Plater.hpp" +#include "I18N.hpp" +#include "format.hpp" +#include "MainFrame.hpp" #include "Tab.hpp" #define FTS_FUZZY_MATCH_IMPLEMENTATION @@ -53,7 +55,7 @@ static char marker_by_type(Preset::Type type, PrinterTechnology pt) std::string Option::opt_key() const { - return boost::nowide::narrow(key).substr(2); + return into_u8(key).substr(2); } void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const @@ -93,8 +95,11 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty wxString suffix; wxString suffix_local; - if (gc.category == "Machine limits") { - suffix = id == 1 ? L("Stealth") : L("Normal"); + if (gc.category == "Machine limits" || gc.category == "Material printing profile") { + if (gc.category == "Machine limits") + suffix = id == 1 ? L("Stealth") : L("Normal"); + else + suffix = id == 1 ? L("Above") : L("Below"); suffix_local = " " + _(suffix); suffix = " " + suffix; } @@ -118,7 +123,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty int cnt = 0; - if ( type != Preset::TYPE_FILAMENT && type != Preset::TYPE_SLA_MATERIAL && !PresetCollection::is_independent_from_extruder_number_option(opt_key) ) + if ( type != Preset::TYPE_FILAMENT && !PresetCollection::is_independent_from_extruder_number_option(opt_key)) switch (config->option(opt_key)->type()) { case coInts: change_opt_key(opt_key, config, cnt); break; @@ -128,6 +133,8 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty case coPercents:change_opt_key(opt_key, config, cnt); break; case coPoints: change_opt_key(opt_key, config, cnt); break; case coFloatsOrPercents: change_opt_key(opt_key, config, cnt); break; + case coEnums: change_opt_key(opt_key, config, cnt); break; + default: break; } @@ -237,7 +244,7 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) return out; }; - auto get_tooltip = [this, &sep](const Option& opt) + auto get_tooltip = [this, &sep](const Option& opt) -> wxString { return marker_by_type(opt.type, printer_technology) + opt.category_local + sep + @@ -250,7 +257,7 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) const Option &opt = options[i]; if (full_list) { std::string label = into_u8(get_label(opt)); - found.emplace_back(FoundOption{ label, label, boost::nowide::narrow(get_tooltip(opt)), i, 0 }); + found.emplace_back(FoundOption{ label, label, into_u8(get_tooltip(opt)), i, 0 }); continue; } @@ -287,7 +294,7 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart))); boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd))); #endif - found.emplace_back(FoundOption{ label_plain, label_u8, boost::nowide::narrow(get_tooltip(opt)), i, score }); + found.emplace_back(FoundOption{ label_plain, label_u8, into_u8(get_tooltip(opt)), i, score }); } } @@ -302,6 +309,7 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) OptionsSearcher::OptionsSearcher() { + default_string = _L("Enter a search term"); } OptionsSearcher::~OptionsSearcher() @@ -348,24 +356,10 @@ void OptionsSearcher::append_preferences_option(const GUI::Line& opt_line) void OptionsSearcher::append_preferences_options(const std::vector& opt_lines) { - //Preset::Type type = Preset::TYPE_PREFERENCES; for (const GUI::Line& line : opt_lines) { if (line.is_separator()) continue; append_preferences_option(line); - //wxString label = line.label; - //if (label.IsEmpty()) - // continue; - - //std::string key = get_key(line.get_options().front().opt_id, type); - //const GroupAndCategory& gc = groups_and_categories[key]; - //if (gc.group.IsEmpty() || gc.category.IsEmpty()) - // continue; - // - //preferences_options.emplace_back(Search::Option{ boost::nowide::widen(key), type, - // label.ToStdWstring(), _(label).ToStdWstring(), - // gc.group.ToStdWstring(), _(gc.group).ToStdWstring(), - // gc.category.ToStdWstring(), _(gc.category).ToStdWstring() }); } } @@ -431,20 +425,73 @@ Option OptionsSearcher::get_option(const std::string& opt_key, const wxString& l return create_option(opt_key, label, type, gc); } -void OptionsSearcher::show_dialog() +static bool has_focus(wxWindow* win) { - if (!search_dialog) { - search_dialog = new SearchDialog(this); + if (win->HasFocus()) + return true; - auto parent = search_dialog->GetParent(); - wxPoint pos = parent->ClientToScreen(wxPoint(0, 0)); - pos.x += em_unit(parent) * 40; - pos.y += em_unit(parent) * 4; - - search_dialog->SetPosition(pos); + auto children = win->GetChildren(); + for (auto child : children) { + if (has_focus(child)) + return true; } + return false; +} + +void OptionsSearcher::update_dialog_position() +{ + if (search_dialog) { + wxPoint old_pos = search_dialog->GetPosition(); + wxPoint pos = search_input->GetScreenPosition() + wxPoint(-5, search_input->GetSize().y); + if (old_pos != pos) + search_dialog->SetPosition(pos); + } +} + +void OptionsSearcher::check_and_hide_dialog() +{ +#ifdef __linux__ + // Temporary linux specific workaround: + // has_focus(search_dialog) always returns false + // That's why search dialog will be hidden whole the time + return; +#endif + if (search_dialog && search_dialog->IsShown() && !has_focus(search_dialog)) + show_dialog(false); +} + +void OptionsSearcher::set_focus_to_parent() +{ + if (search_input) + search_input->GetParent()->SetFocus(); +} + +void OptionsSearcher::show_dialog(bool show /*= true*/) +{ + if (search_dialog && !show) { + search_dialog->Hide(); + return; + } + + if (!search_dialog) { + search_dialog = new SearchDialog(this, search_input); + + search_dialog->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) + { + if (search_dialog->IsShown() && !search_input->HasFocus()) + show_dialog(false); + e.Skip(); + }); + } + update_dialog_position(); + + search_string(); + search_input->SetSelection(-1,-1); + search_dialog->Popup(); + if (!search_input->HasFocus()) + search_input->SetFocus(); } void OptionsSearcher::dlg_sys_color_changed() @@ -459,6 +506,39 @@ void OptionsSearcher::dlg_msw_rescale() search_dialog->msw_rescale(); } +void OptionsSearcher::edit_search_input() +{ + if (!search_input) + return; + + if (search_dialog) { + search_dialog->input_text(search_input->GetValue()); + if (!search_dialog->IsShown()) + search_dialog->Popup(); + } + else + GUI::wxGetApp().show_search_dialog(); +} + +void OptionsSearcher::process_key_down_from_input(wxKeyEvent& e) +{ + int key = e.GetKeyCode(); + if (key == WXK_ESCAPE) + search_dialog->Hide(); + else if (search_dialog && (key == WXK_UP || key == WXK_DOWN || key == WXK_NUMPAD_ENTER || key == WXK_RETURN)) { + search_dialog->KeyDown(e); +#ifdef __linux__ + search_dialog->SetFocus(); +#endif // __linux__ + } +} + +void OptionsSearcher::set_search_input(TextInput* input_ctrl) +{ + search_input = input_ctrl; + update_dialog_position(); +} + void OptionsSearcher::add_key(const std::string& opt_key, Preset::Type type, const wxString& group, const wxString& category) { groups_and_categories[get_key(opt_key, type)] = GroupAndCategory{group, category}; @@ -478,8 +558,8 @@ static const std::map icon_idxs = { {ImGui::PreferencesButton , 5}, }; -SearchDialog::SearchDialog(OptionsSearcher* searcher) - : GUI::DPIDialog(GUI::wxGetApp().tab_panel(), wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), +SearchDialog::SearchDialog(OptionsSearcher* searcher, wxWindow* parent) + : GUI::DPIDialog(parent ? parent : GUI::wxGetApp().tab_panel(), wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxSTAY_ON_TOP | wxRESIZE_BORDER), searcher(searcher) { SetFont(GUI::wxGetApp().normal_font()); @@ -489,13 +569,9 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif - default_string = _L("Enter a search term"); int border = 10; int em = em_unit(); - search_line = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); - GUI::wxGetApp().UpdateDarkUI(search_line); - search_list = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 40, em * 30), wxDV_NO_HEADER | wxDV_SINGLE #ifdef _WIN32 | wxBORDER_SIMPLE @@ -541,15 +617,9 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(search_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(search_list, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(check_sizer, 0, wxEXPAND | wxALL, border); - search_line->Bind(wxEVT_TEXT, &SearchDialog::OnInputText, this); - search_line->Bind(wxEVT_LEFT_UP, &SearchDialog::OnLeftUpInTextCtrl, this); - // process wxEVT_KEY_DOWN to navigate inside search_list, if ArrowUp/Down was pressed - search_line->Bind(wxEVT_KEY_DOWN,&SearchDialog::OnKeyDown, this); - search_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &SearchDialog::OnSelect, this); search_list->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, &SearchDialog::OnActivate, this); #ifdef __WXMSW__ @@ -569,7 +639,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); // Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this); - Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this); +// Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -580,13 +650,9 @@ SearchDialog::~SearchDialog() if (search_list_model) search_list_model->DecRef(); } + void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/) { - const std::string& line = searcher->search_string(); - search_line->SetValue(line.empty() ? default_string : from_u8(line)); - search_line->SetFocus(); - search_line->SelectAll(); - update_list(); const OptionViewParameters& params = searcher->view_params; @@ -596,14 +662,18 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/) if (position != wxDefaultPosition) this->SetPosition(position); - this->ShowModal(); +#ifdef __APPLE__ + this->ShowWithoutActivating(); +#else + this->Show(); +#endif } void SearchDialog::ProcessSelection(wxDataViewItem selection) { if (!selection.IsOk()) return; - this->EndModal(wxID_CLOSE); + this->Hide(); // If call GUI::wxGetApp().sidebar.jump_to_option() directly from here, // then mainframe will not have focus and found option will not be "active" (have cursor) as a result @@ -612,13 +682,12 @@ void SearchDialog::ProcessSelection(wxDataViewItem selection) // So, post event to plater: wxCommandEvent event(wxCUSTOMEVT_JUMP_TO_OPTION); event.SetInt(search_list_model->GetRow(selection)); - wxPostEvent(GUI::wxGetApp().plater(), event); + wxPostEvent(GUI::wxGetApp().mainframe, event); } -void SearchDialog::OnInputText(wxCommandEvent&) +void SearchDialog::input_text(wxString input_string) { - wxString input_string = search_line->GetValue(); - if (input_string == default_string) + if (input_string == searcher->default_string) input_string.Clear(); searcher->search(into_u8(input_string)); @@ -626,14 +695,6 @@ void SearchDialog::OnInputText(wxCommandEvent&) update_list(); } -void SearchDialog::OnLeftUpInTextCtrl(wxEvent& event) -{ - if (search_line->GetValue() == default_string) - search_line->SetValue(""); - - event.Skip(); -} - void SearchDialog::OnKeyDown(wxKeyEvent& event) { int key = event.GetKeyCode(); @@ -642,7 +703,7 @@ void SearchDialog::OnKeyDown(wxKeyEvent& event) if (key == WXK_UP || key == WXK_DOWN) { // So, for the next correct navigation, set focus on the search_list - search_list->SetFocus(); + // search_list->SetFocus(); // #ys_delete_after_test -> Looks like no need anymore auto item = search_list->GetSelection(); @@ -758,7 +819,7 @@ void SearchDialog::on_sys_color_changed() #ifdef _WIN32 GUI::wxGetApp().UpdateAllStaticTextDarkUI(this); GUI::wxGetApp().UpdateDarkUI(static_cast(this->FindWindowById(wxID_CANCEL, this)), true); - for (wxWindow* win : std::vector {search_line, search_list, check_category, check_english}) + for (wxWindow* win : std::vector {search_list, check_category, check_english}) if (win) GUI::wxGetApp().UpdateDarkUI(win); #endif diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 8081ab8..d31e63c 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -23,6 +23,7 @@ #include "Widgets/CheckBox.hpp" class CheckBox; +class TextInput; namespace Slic3r { @@ -87,6 +88,8 @@ class OptionsSearcher std::map groups_and_categories; PrinterTechnology printer_technology {ptAny}; ConfigOptionMode mode{ comUndef }; + TextInput* search_input { nullptr }; + SearchDialog* search_dialog { nullptr }; std::vector
%s