From f7284a6569182ae2a0cfee6dd96100a25551531c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 27 Jan 2022 17:23:10 +0100 Subject: [PATCH 01/97] Cut: Extensions for Dialog of CutGizmo --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 155 ++++++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 55 ++++++++++ 2 files changed, 194 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index ff5d89f5e..3d5d1cd48 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -27,7 +27,22 @@ static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -{} +{ + m_modes = { _u8L("Planar"), _u8L("Grid") +// , _u8L("Radial"), _u8L("Modular") + }; + + m_connector_types = { _u8L("Plug"), _u8L("Dowel") }; + + m_connector_styles = { _u8L("Prizm"), _u8L("Frustrum") +// , _u8L("Claw") + }; + + m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Circle"), _u8L("Hexagon") +// , _u8L("D-shape") + }; + +} std::string GLGizmoCut::get_tooltip() const { @@ -220,6 +235,86 @@ void GLGizmoCut::on_render_for_picking() render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } +void GLGizmoCut::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) +{ + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width); + + size_t selection_out = selection_idx; + // It is necessary to use BeginGroup(). Otherwise, when using SameLine() is called, then other items will be drawn inside the combobox. + ImGui::BeginGroup(); + ImVec2 combo_pos = ImGui::GetCursorScreenPos(); + if (ImGui::BeginCombo(("##"+label).c_str(), "")) { + for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { + ImGui::PushID(int(line_idx)); + ImVec2 start_position = ImGui::GetCursorScreenPos(); + + if (ImGui::Selectable("", line_idx == selection_idx)) + selection_out = line_idx; + + ImGui::SameLine(); + ImGui::Text("%s", lines[line_idx].c_str()); + ImGui::PopID(); + } + + ImGui::EndCombo(); + } + + ImVec2 backup_pos = ImGui::GetCursorScreenPos(); + ImGuiStyle& style = ImGui::GetStyle(); + + ImGui::SetCursorScreenPos(ImVec2(combo_pos.x + style.FramePadding.x, combo_pos.y + style.FramePadding.y)); + ImGui::Text("%s", lines[selection_out].c_str()); + ImGui::SetCursorScreenPos(backup_pos); + ImGui::EndGroup(); + + selection_idx = selection_out; +} + +void GLGizmoCut::render_double_input(const std::string& label, double& value_in) +{ + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width); + + double value = value_in; + if (m_imperial_units) + value *= ObjectManipulation::mm_to_in; + 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")); + + value_in = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); +} + +void GLGizmoCut::render_rotation_input(const std::string& label, double& value_in) +{ + m_imgui->text(label); + ImGui::SameLine(); + + double value = value_in; + if (value > 360) + value -= 360; + + ImGui::PushItemWidth(0.3*m_control_width); + ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + + value_in = value; +} + +void GLGizmoCut::render_radio_button(ConnectorType type) +{ + ImGui::SameLine(type == ConnectorType::Plug ? m_label_width : 2*m_label_width); + ImGui::PushItemWidth(m_control_width); + if (m_imgui->radio_button(m_connector_types[int(type)], m_connector_type == type)) + m_connector_type = type; +} + + void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) { static float last_y = 0.0f; @@ -227,7 +322,9 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + m_label_width = m_imgui->get_style_scaling() * 100.0f; + m_control_width = m_imgui->get_style_scaling() * 150.0f; // adjust window position to avoid overlap the view toolbar const float win_h = ImGui::GetWindowHeight(); @@ -242,26 +339,52 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) last_y = y; } - ImGui::AlignTextToFramePadding(); - m_imgui->text("Z"); - ImGui::SameLine(); - ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); + render_combo(_u8L("Mode"), m_modes, m_mode); - double cut_z = m_cut_z; - if (imperial_units) - cut_z *= ObjectManipulation::mm_to_in; - ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + if (m_mode == CutMode::cutPlanar) { + ImGui::Separator(); - ImGui::SameLine(); - m_imgui->text(imperial_units ? _L("in") : _L("mm")); + render_double_input("Z", m_cut_z); - m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Rotation")); + ImGui::SameLine(m_label_width); + render_rotation_input("X:", m_rotation_x); + ImGui::SameLine(); + render_rotation_input("Y:", m_rotation_y); + ImGui::SameLine(); + render_rotation_input("Z:", m_rotation_z); + + ImGui::SameLine(); + m_imgui->text(_L("°")); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("After cut")); + ImGui::SameLine(m_label_width); + m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); + m_imgui->text(""); + ImGui::SameLine(m_label_width); + m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); + m_imgui->text(""); + ImGui::SameLine(m_label_width); + m_imgui->disabled_begin(!m_keep_lower); + m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + m_imgui->disabled_end(); + } + + // Connectors section ImGui::Separator(); - m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); - m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); - m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + m_imgui->text(_L("Connectors")); + render_radio_button(ConnectorType::Plug); + render_radio_button(ConnectorType::Dowel); + + render_combo(_u8L("Style"), m_connector_styles, m_connector_style); + render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape); + + render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio); + render_double_input(_u8L("Size"), m_connector_size); ImGui::Separator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ccf8732cf..14f305c03 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -15,6 +15,9 @@ class GLGizmoCut : public GLGizmoBase static const double Margin; double m_cut_z{ 0.0 }; + double m_rotation_x{ 0.0 }; + double m_rotation_y{ 0.0 }; + double m_rotation_z{ 0.0 }; double m_max_z{ 0.0 }; double m_start_z{ 0.0 }; Vec3d m_drag_pos; @@ -27,6 +30,53 @@ class GLGizmoCut : public GLGizmoBase GLModel m_grabber_connection; float m_old_z{ 0.0f }; #endif // ENABLE_GLBEGIN_GLEND_REMOVAL + double m_connector_depth_ratio{ 1.5 }; + double m_connector_size{ 5.0 }; + + float m_label_width{ 150.0 }; + float m_control_width{ 200.0 }; + bool m_imperial_units{ false }; + +public: + enum CutMode { + cutPlanar = 0 + ,cutGrig + //,cutRadial + //,cutModular + }; + + enum ConnectorType { + Plug = 0 + ,Dowel + }; + + enum ConnectorStyle { + Prizm = 0 + ,Frustrum + //,Claw + }; + + enum ConnectorShape { + Triangle = 0 + ,Square + ,Circle + ,Hexagon +// ,D-shape + }; + +private: + + std::vector m_modes; + size_t m_mode{ size_t(cutPlanar) }; + + std::vector m_connector_types; + ConnectorType m_connector_type{ Plug }; + + std::vector m_connector_styles; + size_t m_connector_style{ size_t(Prizm) }; + + std::vector m_connector_shapes; + size_t m_connector_shape{ size_t(Hexagon) }; struct CutContours { @@ -63,6 +113,11 @@ protected: virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; + void render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); + void render_double_input(const std::string& label, double& value_in); + void render_rotation_input(const std::string& label, double& value_in); + void render_radio_button(ConnectorType type); + private: void perform_cut(const Selection& selection); double calc_projection(const Linef3& mouse_ray) const; From 76784441be56794478948c23f02e5ee174d01d34 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 9 Feb 2022 16:11:53 +0100 Subject: [PATCH 02/97] Cut: next UI improvements --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 522 +++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 195 +++++--- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 9 + src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- 5 files changed, 559 insertions(+), 172 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3d5d1cd48..3688b10f9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -28,20 +28,6 @@ static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { - m_modes = { _u8L("Planar"), _u8L("Grid") -// , _u8L("Radial"), _u8L("Modular") - }; - - m_connector_types = { _u8L("Plug"), _u8L("Dowel") }; - - m_connector_styles = { _u8L("Prizm"), _u8L("Frustrum") -// , _u8L("Claw") - }; - - m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Circle"), _u8L("Hexagon") -// , _u8L("D-shape") - }; - } std::string GLGizmoCut::get_tooltip() const @@ -56,7 +42,7 @@ std::string GLGizmoCut::get_tooltip() const bool GLGizmoCut::on_init() { m_grabbers.emplace_back(); - m_shortcut_key = WXK_CONTROL_C; +// m_shortcut_key = WXK_CONTROL_C; return true; } @@ -68,7 +54,7 @@ std::string GLGizmoCut::on_get_name() const void GLGizmoCut::on_set_state() { // Reset m_cut_z on gizmo activation - if (get_state() == On) + if (get_state() == On && m_cut_z == 0.0) m_cut_z = bounding_box().center().z(); } @@ -107,14 +93,20 @@ void GLGizmoCut::on_render() update_contours(); - const float min_x = box.min.x() - Margin; - const float max_x = box.max.x() + Margin; - const float min_y = box.min.y() - Margin; - const float max_y = box.max.y() + Margin; - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + //const float min_x = box.min.x() - Margin - plane_center.x(); + //const float max_x = box.max.x() + Margin - plane_center.x(); + //const float min_y = box.min.y() - Margin - plane_center.y(); + //const float max_y = box.max.y() + Margin - plane_center.y(); +// glsafe(::glEnable(GL_DEPTH_TEST)); +// glsafe(::glDisable(GL_CULL_FACE)); +// glsafe(::glEnable(GL_BLEND)); +// glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); +// +//#if ENABLE_GLBEGIN_GLEND_REMOVAL +//#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +// +// glsafe(::glEnable(GL_CULL_FACE)); +// glsafe(::glDisable(GL_BLEND)); #if ENABLE_GLBEGIN_GLEND_REMOVAL GLShaderProgram* shader = wxGetApp().get_shader("flat"); @@ -229,13 +221,80 @@ void GLGizmoCut::on_render() #endif // ENABLE_GLBEGIN_GLEND_REMOVAL } + glsafe(::glPushMatrix()); + glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); +// glsafe(::glTranslated(plane_center.x(), plane_center.y(), plane_center.z())); + glsafe(::glRotated(Geometry::rad2deg(m_angles.z()), 0.0, 0.0, 1.0)); + glsafe(::glRotated(Geometry::rad2deg(m_angles.y()), 0.0, 1.0, 0.0)); + glsafe(::glRotated(Geometry::rad2deg(m_angles.x()), 1.0, 0.0, 0.0)); + + glsafe(::glLineWidth(2.0f)); + m_cut_contours.contours.render(); + glsafe(::glPopMatrix()); +} + void GLGizmoCut::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } -void GLGizmoCut::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + ImGui::AlignTextToFramePadding(); + m_imgui->text("Z"); + ImGui::SameLine(); + ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); + + double cut_z = m_cut_z; + if (imperial_units) + cut_z *= ObjectManipulation::mm_to_in; + ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + + ImGui::SameLine(); + m_imgui->text(imperial_units ? _L("in") : _L("mm")); + + m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); + + ImGui::Separator(); + + m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); + m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); + m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + + ImGui::Separator(); + + m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); + const bool cut_clicked = m_imgui->button(_L("Perform cut")); + m_imgui->disabled_end(); + + m_imgui->end(); + + if (cut_clicked && (m_keep_upper || m_keep_lower)) + perform_cut(m_parent.get_selection()); +} + +void GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) { ImGui::AlignTextToFramePadding(); m_imgui->text(label); @@ -273,7 +332,7 @@ void GLGizmoCut::render_combo(const std::string& label, const std::vectortext(label); @@ -291,22 +350,46 @@ void GLGizmoCut::render_double_input(const std::string& label, double& value_in) value_in = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); } -void GLGizmoCut::render_rotation_input(const std::string& label, double& value_in) +void GLGizmoCut3D::render_move_center_input(int axis) { - m_imgui->text(label); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_axis_names[axis]+":"); + ImGui::SameLine(); + ImGui::PushItemWidth(0.3*m_control_width); + + double value = axis == Z ? m_cut_plane_gizmo.get_cut_z() : 0.0; + if (m_imperial_units) + value *= ObjectManipulation::mm_to_in; + ImGui::InputDouble(("##move_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); - double value = value_in; + if (axis == Z) { + double val = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); + m_cut_plane_gizmo.set_cut_z(val); + set_center_z(val); + } +} + +void GLGizmoCut3D::render_rotation_input(int axis) +{ + m_imgui->text(m_axis_names[axis] + ":"); + ImGui::SameLine(); + + Vec3d rotation = get_rotation(); + double value = rotation[axis]; if (value > 360) value -= 360; ImGui::PushItemWidth(0.3*m_control_width); - ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + ImGui::InputDouble(("##rotate_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + ImGui::SameLine(); - value_in = value; + rotation[axis] = value; + set_rotation(rotation); + m_cut_plane_gizmo.set_angles(rotation); } -void GLGizmoCut::render_radio_button(ConnectorType type) +void GLGizmoCut3D::render_connect_type_radio_button(ConnectorType type) { ImGui::SameLine(type == ConnectorType::Plug ? m_label_width : 2*m_label_width); ImGui::PushItemWidth(m_control_width); @@ -314,88 +397,93 @@ void GLGizmoCut::render_radio_button(ConnectorType type) m_connector_type = type; } - -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) +void GLGizmoCut3D::render_connect_mode_radio_button(ConnectorMode mode) { - static float last_y = 0.0f; - static float last_h = 0.0f; + ImGui::SameLine(mode == ConnectorMode::Auto ? m_label_width : 2*m_label_width); + ImGui::PushItemWidth(m_control_width); + if (m_imgui->radio_button(m_connector_modes[int(mode)], m_connector_mode == mode)) + m_connector_mode = mode; +} - m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); +void GLGizmoCut3D::render_cut_plane() +{ + const BoundingBoxf3 box = m_cut_plane_gizmo.bounding_box(); + Vec3d plane_center = box.center(); + plane_center.z() = m_cut_plane_gizmo.get_cut_z(); - m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - m_label_width = m_imgui->get_style_scaling() * 100.0f; - m_control_width = m_imgui->get_style_scaling() * 150.0f; + const float min_x = box.min.x() - GLGizmoCut::Margin - plane_center.x(); + const float max_x = box.max.x() + GLGizmoCut::Margin - plane_center.x(); + const float min_y = box.min.y() - GLGizmoCut::Margin - plane_center.y(); + const float max_y = box.max.y() + GLGizmoCut::Margin - plane_center.y(); + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - // adjust window position to avoid overlap the view toolbar - const float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if (last_h != win_h || last_y != y) { - // ask canvas for another frame to render the window in the correct position - m_imgui->set_requires_extra_frame(); - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + shader->start_using(); + +// bool z_changed = std::abs(plane_center.z() - m_old_z) > EPSILON; +// m_old_z = plane_center.z(); + + Vec3d angles = get_rotation(); + + glsafe(::glPushMatrix()); + glsafe(::glTranslated(plane_center.x(), plane_center.y(), plane_center.z())); + glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); + glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); + glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); + + if (!m_plane.is_initialized()/* || z_changed*/) { + m_plane.reset(); + + GLModel::InitializationData init_data; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Triangles; + entity.positions.reserve(4); + entity.positions.emplace_back(Vec3f(min_x, min_y, 0.0)); + entity.positions.emplace_back(Vec3f(max_x, min_y, 0.0)); + entity.positions.emplace_back(Vec3f(max_x, max_y, 0.0)); + entity.positions.emplace_back(Vec3f(min_x, max_y, 0.0)); + + entity.normals.reserve(4); + for (size_t i = 0; i < 4; ++i) { + entity.normals.emplace_back(Vec3f::UnitZ()); + } + + entity.indices.reserve(6); + entity.indices.emplace_back(0); + entity.indices.emplace_back(1); + entity.indices.emplace_back(2); + entity.indices.emplace_back(2); + entity.indices.emplace_back(3); + entity.indices.emplace_back(0); + + init_data.entities.emplace_back(entity); + m_plane.init_from(init_data); + m_plane.set_color(-1, PLANE_COLOR); } - render_combo(_u8L("Mode"), m_modes, m_mode); + m_plane.render(); + glsafe(::glPopMatrix()); +#else + // Draw the cutting plane + ::glBegin(GL_QUADS); + ::glColor4fv(PLANE_COLOR.data()); + ::glVertex3f(min_x, min_y, plane_center.z()); + ::glVertex3f(max_x, min_y, plane_center.z()); + ::glVertex3f(max_x, max_y, plane_center.z()); + ::glVertex3f(min_x, max_y, plane_center.z()); + glsafe(::glEnd()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - if (m_mode == CutMode::cutPlanar) { - ImGui::Separator(); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); - render_double_input("Z", m_cut_z); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Rotation")); - ImGui::SameLine(m_label_width); - - render_rotation_input("X:", m_rotation_x); - ImGui::SameLine(); - render_rotation_input("Y:", m_rotation_y); - ImGui::SameLine(); - render_rotation_input("Z:", m_rotation_z); - - ImGui::SameLine(); - m_imgui->text(_L("°")); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("After cut")); - ImGui::SameLine(m_label_width); - m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); - m_imgui->text(""); - ImGui::SameLine(m_label_width); - m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); - m_imgui->text(""); - ImGui::SameLine(m_label_width); - m_imgui->disabled_begin(!m_keep_lower); - m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); - m_imgui->disabled_end(); - } - - // Connectors section - ImGui::Separator(); - - m_imgui->text(_L("Connectors")); - render_radio_button(ConnectorType::Plug); - render_radio_button(ConnectorType::Dowel); - - render_combo(_u8L("Style"), m_connector_styles, m_connector_style); - render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape); - - render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio); - render_double_input(_u8L("Size"), m_connector_size); - - ImGui::Separator(); - - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); - const bool cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); - - m_imgui->end(); - - if (cut_clicked && (m_keep_upper || m_keep_lower)) - perform_cut(m_parent.get_selection()); + shader->stop_using(); } void GLGizmoCut::set_cut_z(double cut_z) @@ -481,18 +569,28 @@ void GLGizmoCut::update_contours() if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) m_cut_contours.mesh = model_object->raw_mesh(); - m_cut_contours.position = box.center(); - m_cut_contours.shift = Vec3d::Zero(); + m_cut_contours.position = Vec3d::Zero();//box.center(); + m_cut_contours.shift = box.center();//Vec3d::Zero(); m_cut_contours.object_id = model_object->id(); m_cut_contours.instance_idx = instance_idx; m_cut_contours.volumes_idxs = volumes_idxs; m_cut_contours.contours.reset(); + // addition back transformation + Geometry::Transformation m_transformation = Geometry::Transformation(); + m_transformation.set_offset(-first_glvolume->get_instance_transformation().get_offset()); + m_transformation.set_rotation(-m_angles); + auto cut_params = m_transformation.get_matrix(); + + MeshSlicingParams slicing_params; - slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); - const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); + slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix() * cut_params; + + auto cut_z = m_cut_z - box.center().z(); + + const Polygons polys = slice_mesh(m_cut_contours.mesh.its, /*m_*/cut_z, slicing_params); if (!polys.empty()) { - m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); + m_cut_contours.contours.init_from(polys, static_cast(/*m_*/cut_z)); #if ENABLE_GLBEGIN_GLEND_REMOVAL m_cut_contours.contours.set_color(ColorRGBA::WHITE()); #else @@ -500,13 +598,211 @@ void GLGizmoCut::update_contours() #endif // ENABLE_GLBEGIN_GLEND_REMOVAL } } - else if (box.center() != m_cut_contours.position) { - m_cut_contours.shift = box.center() - m_cut_contours.position; + else if (box.center() != m_cut_contours.shift) { + m_cut_contours.shift = box.center();// -m_cut_contours.position; } + //else if (box.center() != m_cut_contours.position) { + // m_cut_contours.shift = -box.center();// -m_cut_contours.position; + //} } else m_cut_contours.contours.reset(); } +GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoRotate3D(parent, icon_filename, sprite_id) + , m_cut_plane_gizmo(GLGizmoCut(parent, "", -1)) +{ + m_cut_plane_gizmo.set_group_id(3); + + m_modes = { _u8L("Planar"), _u8L("By Line"),_u8L("Grid") +// , _u8L("Radial"), _u8L("Modular") + }; + + m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; + m_connector_types = { _u8L("Plug"), _u8L("Dowel") }; + + m_connector_styles = { _u8L("Prizm"), _u8L("Frustrum") +// , _u8L("Claw") + }; + + m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Circle"), _u8L("Hexagon") +// , _u8L("D-shape") + }; + + m_axis_names = {"X", "Y", "Z"}; +} + +bool GLGizmoCut3D::on_init() +{ + if(!GLGizmoRotate3D::on_init()) + return false; + + if (!m_cut_plane_gizmo.init()) + return false; + + m_shortcut_key = WXK_CONTROL_C; + return true; +} + +std::string GLGizmoCut3D::on_get_name() const +{ + return _u8L("Cut"); +} + +bool GLGizmoCut3D::on_is_activable() const +{ + return m_cut_plane_gizmo.is_activable(); +} + +void GLGizmoCut3D::on_start_dragging() +{ + GLGizmoRotate3D::on_start_dragging(); + if (m_hover_id == 3) + m_cut_plane_gizmo.start_dragging(); +} + +void GLGizmoCut3D::on_stop_dragging() +{ + GLGizmoRotate3D::on_stop_dragging(); + if (m_hover_id == 3) + m_cut_plane_gizmo.stop_dragging(); +} + +void GLGizmoCut3D::on_render() +{ + render_cut_plane(); + if (m_mode == CutMode::cutPlanar) { + GLGizmoRotate3D::on_render(); +// if (m_hover_id == -1 || m_hover_id == 3) + } + m_cut_plane_gizmo.render(); +} + +void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + m_label_width = m_imgui->get_style_scaling() * 100.0f; + m_control_width = m_imgui->get_style_scaling() * 150.0f; + + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + render_combo(_u8L("Mode"), m_modes, m_mode); + + if (m_mode <= CutMode::cutByLine) { + ImGui::Separator(); + + if (m_mode == CutMode::cutPlanar) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Move center")); + ImGui::SameLine(m_label_width); + for (Axis axis : {X, Y, Z}) + render_move_center_input(axis); + m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Rotation")); + ImGui::SameLine(m_label_width); + for (Axis axis : {X, Y, Z}) + render_rotation_input(axis); + m_imgui->text(_L("°")); + } + else { + ImGui::AlignTextToFramePadding(); + ImGui::AlignTextToFramePadding(); + ImGui::AlignTextToFramePadding(); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 3*m_control_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connect some two points of object to cteate a cut plane")); + ImGui::PopTextWrapPos(); + ImGui::AlignTextToFramePadding(); + ImGui::AlignTextToFramePadding(); + } + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("After cut")); + ImGui::SameLine(m_label_width); + m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); + m_imgui->text(""); + ImGui::SameLine(m_label_width); + m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); + m_imgui->text(""); + ImGui::SameLine(m_label_width); + m_imgui->disabled_begin(!m_keep_lower); + m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + m_imgui->disabled_end(); + } + + m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); + // Connectors section + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); + + m_imgui->text(_L("Mode")); + render_connect_mode_radio_button(ConnectorMode::Auto); + render_connect_mode_radio_button(ConnectorMode::Manual); + + m_imgui->text(_L("Type")); + render_connect_type_radio_button(ConnectorType::Plug); + render_connect_type_radio_button(ConnectorType::Dowel); + + render_combo(_u8L("Style"), m_connector_styles, m_connector_style); + render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape); + + render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio); + render_double_input(_u8L("Size"), m_connector_size); + + m_imgui->disabled_end(); + + ImGui::Separator(); + + m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) /*|| m_cut_z <= 0.0 || m_max_z <= m_cut_z*/); + const bool cut_clicked = m_imgui->button(_L("Perform cut")); + m_imgui->disabled_end(); + + m_imgui->end(); + + if (cut_clicked && (m_keep_upper || m_keep_lower)) + perform_cut(m_parent.get_selection()); +} + +void GLGizmoCut3D::perform_cut(const Selection& selection) +{ + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + + wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); + + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. + const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); + const double object_cut_z = m_cut_plane_gizmo.get_cut_z() - first_glvolume->get_sla_shift_z(); + + if (0.0 < object_cut_z/* && object_cut_z < m_max_z*/) + wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, + only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); + else { + // the object is SLA-elevated and the plane is under it. + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 14f305c03..f8d7570a4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -2,6 +2,7 @@ #define slic3r_GLGizmoCut_hpp_ #include "GLGizmoBase.hpp" +#include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLModel.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/ObjectID.hpp" @@ -11,13 +12,11 @@ namespace GUI { class GLGizmoCut : public GLGizmoBase { +public: static const double Offset; static const double Margin; - +private: double m_cut_z{ 0.0 }; - double m_rotation_x{ 0.0 }; - double m_rotation_y{ 0.0 }; - double m_rotation_z{ 0.0 }; double m_max_z{ 0.0 }; double m_start_z{ 0.0 }; Vec3d m_drag_pos; @@ -26,57 +25,11 @@ class GLGizmoCut : public GLGizmoBase bool m_keep_lower{ true }; bool m_rotate_lower{ false }; #if ENABLE_GLBEGIN_GLEND_REMOVAL - GLModel m_plane; GLModel m_grabber_connection; float m_old_z{ 0.0f }; #endif // ENABLE_GLBEGIN_GLEND_REMOVAL - double m_connector_depth_ratio{ 1.5 }; - double m_connector_size{ 5.0 }; - float m_label_width{ 150.0 }; - float m_control_width{ 200.0 }; - bool m_imperial_units{ false }; - -public: - enum CutMode { - cutPlanar = 0 - ,cutGrig - //,cutRadial - //,cutModular - }; - - enum ConnectorType { - Plug = 0 - ,Dowel - }; - - enum ConnectorStyle { - Prizm = 0 - ,Frustrum - //,Claw - }; - - enum ConnectorShape { - Triangle = 0 - ,Square - ,Circle - ,Hexagon -// ,D-shape - }; - -private: - - std::vector m_modes; - size_t m_mode{ size_t(cutPlanar) }; - - std::vector m_connector_types; - ConnectorType m_connector_type{ Plug }; - - std::vector m_connector_styles; - size_t m_connector_style{ size_t(Prizm) }; - - std::vector m_connector_shapes; - size_t m_connector_shape{ size_t(Hexagon) }; + Vec3d m_angles{ Vec3d::Zero() }; struct CutContours { @@ -95,8 +48,9 @@ private: public: GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - double get_cut_z() const { return m_cut_z; } - void set_cut_z(double cut_z); + double get_cut_z() const { return m_cut_z; } + void set_cut_z(double cut_z); + void set_angles(const Vec3d& angles) { m_angles = angles; } std::string get_tooltip() const override; @@ -113,18 +67,143 @@ protected: virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; - void render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); - void render_double_input(const std::string& label, double& value_in); - void render_rotation_input(const std::string& label, double& value_in); - void render_radio_button(ConnectorType type); - private: void perform_cut(const Selection& selection); double calc_projection(const Linef3& mouse_ray) const; + +public: BoundingBoxf3 bounding_box() const; void update_contours(); }; +class GLGizmoCut3D : public GLGizmoRotate3D +{ + GLGizmoCut m_cut_plane_gizmo; + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel m_plane; + float m_old_z{ 0.0f }; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + bool m_keep_upper{ true }; + bool m_keep_lower{ true }; + bool m_rotate_lower{ false }; + + double m_connector_depth_ratio{ 1.5 }; + double m_connector_size{ 5.0 }; + + float m_label_width{ 150.0 }; + float m_control_width{ 200.0 }; + bool m_imperial_units{ false }; + + enum CutMode { + cutPlanar + , cutByLine + , cutGrig + //,cutRadial + //,cutModular + }; + + enum ConnectorMode { + Auto + , Manual + }; + + enum ConnectorType { + Plug + , Dowel + }; + + enum ConnectorStyle { + Prizm + , Frustrum + //,Claw + }; + + enum ConnectorShape { + Triangle + , Square + , Circle + , Hexagon + //,D-shape + }; + + std::vector m_modes; + size_t m_mode{ size_t(cutPlanar) }; + + std::vector m_connector_modes; + ConnectorMode m_connector_mode{ Auto }; + + std::vector m_connector_types; + ConnectorType m_connector_type{ Plug }; + + std::vector m_connector_styles; + size_t m_connector_style{ size_t(Prizm) }; + + std::vector m_connector_shapes; + size_t m_connector_shape{ size_t(Hexagon) }; + + std::vector m_axis_names; + +public: + GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + + std::string get_tooltip() const override { + std::string tooltip = GLGizmoRotate3D::get_tooltip(); + if (tooltip.empty()) + tooltip = m_cut_plane_gizmo.get_tooltip(); + return tooltip; + } + +protected: + bool on_init() override; + std::string on_get_name() const override; + void on_set_state() override { + GLGizmoRotate3D::on_set_state(); + m_cut_plane_gizmo.set_state(m_state); + } + void on_set_hover_id() override { + GLGizmoRotate3D::on_set_hover_id(); + m_cut_plane_gizmo.set_hover_id((m_hover_id == 3) ? 0 : -1); + } + void on_enable_grabber(unsigned int id) override { + GLGizmoRotate3D::on_enable_grabber(id); + if (id == 3) + m_cut_plane_gizmo.enable_grabber(0); + } + void on_disable_grabber(unsigned int id) override { + GLGizmoRotate3D::on_disable_grabber(id); + if (id == 3) + m_cut_plane_gizmo.disable_grabber(0); + } + bool on_is_activable() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_update(const UpdateData& data) override { + GLGizmoRotate3D::on_update(data); + m_cut_plane_gizmo.update(data); + } + void on_render() override; + void on_render_for_picking() override { + GLGizmoRotate3D::on_render_for_picking(); + m_cut_plane_gizmo.render_for_picking(); + } + + void on_render_input_window(float x, float y, float bottom_limit) override; + +private: + + void render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); + void render_double_input(const std::string& label, double& value_in); + void render_move_center_input(int axis); + void render_rotation_input(int axis); + void render_connect_mode_radio_button(ConnectorMode mode); + void render_connect_type_radio_button(ConnectorType type); + + void render_cut_plane(); + void perform_cut(const Selection& selection); +}; + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index dca578bd7..9869634cc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -38,6 +38,11 @@ void GLGizmoRotate::set_angle(double angle) m_angle = angle; } +void GLGizmoRotate::set_center_z(double center_z) +{ + m_center_z = center_z; +} + std::string GLGizmoRotate::get_tooltip() const { std::string axis; @@ -60,6 +65,8 @@ void GLGizmoRotate::on_start_dragging() { const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_center = box.center(); + if (m_center_z >= 0) + m_center[Z] = m_center_z; m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; @@ -112,6 +119,8 @@ void GLGizmoRotate::on_render() if (m_hover_id != 0 && !m_grabbers.front().dragging) { m_center = box.center(); + if (m_center_z >= 0) + m_center[Z] = m_center_z; m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index bb33e0f73..0c291cbef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -28,6 +28,7 @@ public: private: Axis m_axis; double m_angle{ 0.0 }; + double m_center_z{ -1.0 }; Vec3d m_center{ Vec3d::Zero() }; float m_radius{ 0.0f }; float m_snap_coarse_in_radius{ 0.0f }; @@ -58,6 +59,7 @@ public: double get_angle() const { return m_angle; } void set_angle(double angle); + void set_center_z(double center_z); std::string get_tooltip() const override; @@ -101,6 +103,7 @@ public: Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } + void set_center_z(double center_z) { m_gizmos[X].set_center_z(center_z); m_gizmos[Y].set_center_z(center_z); m_gizmos[Z].set_center_z(center_z); } std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 55cbb0c30..3a144a65d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -97,7 +97,7 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1)); m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, "rotate.svg", 2)); m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3)); - m_gizmos.emplace_back(new GLGizmoCut(m_parent, "cut.svg", 4)); + m_gizmos.emplace_back(new GLGizmoCut3D(m_parent, "cut.svg", 4)); m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5)); m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6)); m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7)); From c45c004545518e4f4e713867b78236094e99765e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 16 Feb 2022 12:36:22 +0100 Subject: [PATCH 03/97] Cut: next improvements. Rewrite GLGizmoCut3D as a new GLGizmoBase which contained GLGizmoRotation3D and GLGizmoMove3D --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 617 ++++++---------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 107 +--- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 40 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 6 + src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 13 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 4 +- 7 files changed, 234 insertions(+), 559 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3688b10f9..847a15a9d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -21,277 +21,115 @@ namespace Slic3r { namespace GUI { -const double GLGizmoCut::Offset = 10.0; -const double GLGizmoCut::Margin = 20.0; -static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); +const double GLGizmoCenterMove::Margin = 20.0; -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) +GLGizmoCenterMove::GLGizmoCenterMove(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoMove3D(parent, "", -1) { } -std::string GLGizmoCut::get_tooltip() const +void GLGizmoCenterMove::set_center_pos(const Vec3d& centre_pos) { - double cut_z = m_cut_z; - if (wxGetApp().app_config->get("use_inches") == "1") - cut_z *= ObjectManipulation::mm_to_in; - - return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : ""; + // Clamp the center position of the cut plane to the object's bounding box + set_center(Vec3d(std::clamp(centre_pos.x(), m_min_pos.x(), m_max_pos.x()), + std::clamp(centre_pos.y(), m_min_pos.y(), m_max_pos.y()), + std::clamp(centre_pos.z(), m_min_pos.z(), m_max_pos.z()))); } -bool GLGizmoCut::on_init() +std::string GLGizmoCenterMove::get_tooltip() const { - m_grabbers.emplace_back(); -// m_shortcut_key = WXK_CONTROL_C; - return true; + double koef = wxGetApp().app_config->get("use_inches") == "1" ? ObjectManipulation::mm_to_in : 1.0; + + const Vec3d& center_pos = get_center(); + + if (m_hover_id == 0 || m_grabbers[0].dragging) + return "X: " + format(center_pos.x() * koef, 2); + else if (m_hover_id == 1 || m_grabbers[1].dragging) + return "Y: " + format(center_pos.y() * koef, 2); + else if (m_hover_id == 2 || m_grabbers[2].dragging) + return "Z: " + format(center_pos.z() * koef, 2); + else + return ""; } -std::string GLGizmoCut::on_get_name() const +void GLGizmoCenterMove::on_set_state() { - return _u8L("Cut"); + // Reset internal variables on gizmo activation, if bounding box was changed + if (get_state() == On) { + const BoundingBoxf3 box = bounding_box(); + if (m_max_pos != box.max && m_min_pos != box.min) { + m_max_pos = box.max; + m_min_pos = box.min; + set_center_pos(box.center()); + } + } } -void GLGizmoCut::on_set_state() +void GLGizmoCenterMove::on_update(const UpdateData& data) { - // Reset m_cut_z on gizmo activation - if (get_state() == On && m_cut_z == 0.0) - m_cut_z = bounding_box().center().z(); + GLGizmoMove3D::on_update(data); + set_center_pos(get_center()); } -bool GLGizmoCut::on_is_activable() const +BoundingBoxf3 GLGizmoCenterMove::bounding_box() const { + BoundingBoxf3 ret; const Selection& selection = m_parent.get_selection(); - return selection.is_single_full_instance() && !selection.is_wipe_tower(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int i : idxs) { + const GLVolume* volume = selection.get_volume(i); + if (!volume->is_modifier) + ret.merge(volume->transformed_convex_hull_bounding_box()); + } + return ret; } -void GLGizmoCut::on_start_dragging() -{ - if (m_hover_id == -1) - return; - const BoundingBoxf3 box = bounding_box(); - m_max_z = box.max.z(); - m_start_z = m_cut_z; - m_drag_pos = m_grabbers[m_hover_id].center; - m_drag_center = box.center(); - m_drag_center.z() = m_cut_z; + +GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) + , m_rotation_gizmo(GLGizmoRotate3D(parent, "", -1)) + , m_move_gizmo(GLGizmoCenterMove(parent, "", -1)) +{ + m_move_gizmo.set_group_id(3); + + m_modes = { _u8L("Planar"), _u8L("By Line"),_u8L("Grid") +// , _u8L("Radial"), _u8L("Modular") + }; + + m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; + m_connector_types = { _u8L("Plug"), _u8L("Dowel") }; + + m_connector_styles = { _u8L("Prizm"), _u8L("Frustrum") +// , _u8L("Claw") + }; + + m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Circle"), _u8L("Hexagon") +// , _u8L("D-shape") + }; + + m_axis_names = { "X", "Y", "Z" }; } -void GLGizmoCut::on_update(const UpdateData& data) +std::string GLGizmoCut3D::get_tooltip() const { - if (m_hover_id != -1) - set_cut_z(m_start_z + calc_projection(data.mouse_ray)); + std::string tooltip = m_rotation_gizmo.get_tooltip(); + if (tooltip.empty()) + tooltip = m_move_gizmo.get_tooltip(); + return tooltip; } -void GLGizmoCut::on_render() +void GLGizmoCut3D::shift_cut_z(double delta) { - const BoundingBoxf3 box = bounding_box(); - Vec3d plane_center = box.center(); - plane_center.z() = m_cut_z; - m_max_z = box.max.z(); - set_cut_z(m_cut_z); - - update_contours(); - - //const float min_x = box.min.x() - Margin - plane_center.x(); - //const float max_x = box.max.x() + Margin - plane_center.x(); - //const float min_y = box.min.y() - Margin - plane_center.y(); - //const float max_y = box.max.y() + Margin - plane_center.y(); -// glsafe(::glEnable(GL_DEPTH_TEST)); -// glsafe(::glDisable(GL_CULL_FACE)); -// glsafe(::glEnable(GL_BLEND)); -// glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); -// -//#if ENABLE_GLBEGIN_GLEND_REMOVAL -//#endif // ENABLE_GLBEGIN_GLEND_REMOVAL -// -// glsafe(::glEnable(GL_CULL_FACE)); -// glsafe(::glDisable(GL_BLEND)); - -#if ENABLE_GLBEGIN_GLEND_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); - - const bool z_changed = std::abs(plane_center.z() - m_old_z) > EPSILON; - m_old_z = plane_center.z(); - - if (!m_plane.is_initialized() || z_changed) { - m_plane.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z())); - init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z())); - init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z())); - init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z())); - - // indices - init_data.add_ushort_triangle(0, 1, 2); - init_data.add_ushort_triangle(2, 3, 0); - - m_plane.init_from(std::move(init_data)); - } - - m_plane.render(); -#else - // Draw the cutting plane - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, plane_center.z()); - ::glVertex3f(max_x, min_y, plane_center.z()); - ::glVertex3f(max_x, max_y, plane_center.z()); - ::glVertex3f(min_x, max_y, plane_center.z()); - glsafe(::glEnd()); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); - - // Draw the grabber and the connecting line - m_grabbers[0].center = plane_center; - m_grabbers[0].center.z() = plane_center.z() + Offset; - - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); -#if ENABLE_GLBEGIN_GLEND_REMOVAL - if (!m_grabber_connection.is_initialized() || z_changed) { - m_grabber_connection.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.color = ColorRGBA::YELLOW(); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - init_data.add_vertex((Vec3f)plane_center.cast()); - init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); - - // indices - init_data.add_ushort_line(0, 1); - - m_grabber_connection.init_from(std::move(init_data)); - } - - m_grabber_connection.render(); - - shader->stop_using(); - } - - shader = wxGetApp().get_shader("gouraud_light"); -#else - glsafe(::glColor3f(1.0, 1.0, 0.0)); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center.data()); - ::glVertex3dv(m_grabbers[0].center.data()); - glsafe(::glEnd()); - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - - m_grabbers[0].color = GRABBER_COLOR; - m_grabbers[0].render(m_hover_id == 0, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); - - shader->stop_using(); - } - -#if ENABLE_GLBEGIN_GLEND_REMOVAL - shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); - glsafe(::glLineWidth(2.0f)); - m_cut_contours.contours.render(); - glsafe(::glPopMatrix()); -#if ENABLE_GLBEGIN_GLEND_REMOVAL - shader->stop_using(); - } -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - } - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); -// glsafe(::glTranslated(plane_center.x(), plane_center.y(), plane_center.z())); - glsafe(::glRotated(Geometry::rad2deg(m_angles.z()), 0.0, 0.0, 1.0)); - glsafe(::glRotated(Geometry::rad2deg(m_angles.y()), 0.0, 1.0, 0.0)); - glsafe(::glRotated(Geometry::rad2deg(m_angles.x()), 1.0, 0.0, 0.0)); - - glsafe(::glLineWidth(2.0f)); - m_cut_contours.contours.render(); - glsafe(::glPopMatrix()); + Vec3d new_cut_center = m_move_gizmo.get_center(); + new_cut_center[Z] += delta; + set_center(new_cut_center); } -void GLGizmoCut::on_render_for_picking() +void GLGizmoCut3D::set_center(const Vec3d& center) { - glsafe(::glDisable(GL_DEPTH_TEST)); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); -} - -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) -{ - static float last_y = 0.0f; - static float last_h = 0.0f; - - m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - - // adjust window position to avoid overlap the view toolbar - const float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if (last_h != win_h || last_y != y) { - // ask canvas for another frame to render the window in the correct position - m_imgui->set_requires_extra_frame(); - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; - } - - ImGui::AlignTextToFramePadding(); - m_imgui->text("Z"); - ImGui::SameLine(); - ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); - - double cut_z = m_cut_z; - if (imperial_units) - cut_z *= ObjectManipulation::mm_to_in; - ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); - - ImGui::SameLine(); - m_imgui->text(imperial_units ? _L("in") : _L("mm")); - - m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); - - ImGui::Separator(); - - m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); - m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); - m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); - - ImGui::Separator(); - - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); - const bool cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); - - m_imgui->end(); - - if (cut_clicked && (m_keep_upper || m_keep_lower)) - perform_cut(m_parent.get_selection()); + m_move_gizmo.set_center_pos(center); + m_rotation_gizmo.set_center(m_move_gizmo.get_center()); } void GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) @@ -357,17 +195,19 @@ void GLGizmoCut3D::render_move_center_input(int axis) ImGui::SameLine(); ImGui::PushItemWidth(0.3*m_control_width); - double value = axis == Z ? m_cut_plane_gizmo.get_cut_z() : 0.0; + Vec3d move = m_move_gizmo.get_center(); + double in_val, value = in_val = move[axis]; if (m_imperial_units) value *= ObjectManipulation::mm_to_in; ImGui::InputDouble(("##move_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); - if (axis == Z) { - double val = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); - m_cut_plane_gizmo.set_cut_z(val); - set_center_z(val); - } + double val = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); + + if (in_val != val) { + move[axis] = val; + set_center(move); + } } void GLGizmoCut3D::render_rotation_input(int axis) @@ -375,8 +215,8 @@ void GLGizmoCut3D::render_rotation_input(int axis) m_imgui->text(m_axis_names[axis] + ":"); ImGui::SameLine(); - Vec3d rotation = get_rotation(); - double value = rotation[axis]; + Vec3d rotation = m_rotation_gizmo.get_rotation(); + double value = rotation[axis] * (180. / M_PI); if (value > 360) value -= 360; @@ -384,9 +224,8 @@ void GLGizmoCut3D::render_rotation_input(int axis) ImGui::InputDouble(("##rotate_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); - rotation[axis] = value; - set_rotation(rotation); - m_cut_plane_gizmo.set_angles(rotation); + rotation[axis] = (M_PI / 180.) * value; + m_rotation_gizmo.set_rotation(rotation); } void GLGizmoCut3D::render_connect_type_radio_button(ConnectorType type) @@ -407,14 +246,13 @@ void GLGizmoCut3D::render_connect_mode_radio_button(ConnectorMode mode) void GLGizmoCut3D::render_cut_plane() { - const BoundingBoxf3 box = m_cut_plane_gizmo.bounding_box(); - Vec3d plane_center = box.center(); - plane_center.z() = m_cut_plane_gizmo.get_cut_z(); + const BoundingBoxf3 box = m_move_gizmo.bounding_box(); + Vec3d plane_center = m_move_gizmo.get_center();// == Vec3d::Zero() ? box.center() : m_move_gizmo.get_center(); - const float min_x = box.min.x() - GLGizmoCut::Margin - plane_center.x(); - const float max_x = box.max.x() + GLGizmoCut::Margin - plane_center.x(); - const float min_y = box.min.y() - GLGizmoCut::Margin - plane_center.y(); - const float max_y = box.max.y() + GLGizmoCut::Margin - plane_center.y(); + const float min_x = box.min.x() - GLGizmoCenterMove::Margin - plane_center.x(); + const float max_x = box.max.x() + GLGizmoCenterMove::Margin - plane_center.x(); + const float min_y = box.min.y() - GLGizmoCenterMove::Margin - plane_center.y(); + const float max_y = box.max.y() + GLGizmoCenterMove::Margin - plane_center.y(); glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); @@ -426,10 +264,7 @@ void GLGizmoCut3D::render_cut_plane() return; shader->start_using(); -// bool z_changed = std::abs(plane_center.z() - m_old_z) > EPSILON; -// m_old_z = plane_center.z(); - - Vec3d angles = get_rotation(); + Vec3d angles = m_rotation_gizmo.get_rotation(); glsafe(::glPushMatrix()); glsafe(::glTranslated(plane_center.x(), plane_center.y(), plane_center.z())); @@ -437,34 +272,26 @@ void GLGizmoCut3D::render_cut_plane() glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); - if (!m_plane.is_initialized()/* || z_changed*/) { + if (!m_plane.is_initialized()) { m_plane.reset(); - GLModel::InitializationData init_data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; - entity.positions.reserve(4); - entity.positions.emplace_back(Vec3f(min_x, min_y, 0.0)); - entity.positions.emplace_back(Vec3f(max_x, min_y, 0.0)); - entity.positions.emplace_back(Vec3f(max_x, max_y, 0.0)); - entity.positions.emplace_back(Vec3f(min_x, max_y, 0.0)); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); - entity.normals.reserve(4); - for (size_t i = 0; i < 4; ++i) { - entity.normals.emplace_back(Vec3f::UnitZ()); - } + // vertices + init_data.add_vertex(Vec3f(min_x, min_y, 0.0)); + init_data.add_vertex(Vec3f(max_x, min_y, 0.0)); + init_data.add_vertex(Vec3f(max_x, max_y, 0.0)); + init_data.add_vertex(Vec3f(min_x, max_y, 0.0)); - entity.indices.reserve(6); - entity.indices.emplace_back(0); - entity.indices.emplace_back(1); - entity.indices.emplace_back(2); - entity.indices.emplace_back(2); - entity.indices.emplace_back(3); - entity.indices.emplace_back(0); + // indices + init_data.add_ushort_triangle(0, 1, 2); + init_data.add_ushort_triangle(2, 3, 0); - init_data.entities.emplace_back(entity); - m_plane.init_from(init_data); - m_plane.set_color(-1, PLANE_COLOR); + m_plane.init_from(std::move(init_data)); } m_plane.render(); @@ -486,159 +313,11 @@ void GLGizmoCut3D::render_cut_plane() shader->stop_using(); } -void GLGizmoCut::set_cut_z(double cut_z) -{ - // Clamp the plane to the object's bounding box - m_cut_z = std::clamp(cut_z, 0.0, m_max_z); -} - -void GLGizmoCut::perform_cut(const Selection& selection) -{ - const int instance_idx = selection.get_instance_idx(); - const int object_idx = selection.get_object_idx(); - - wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - - // m_cut_z is the distance from the bed. Subtract possible SLA elevation. - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); - - if (0.0 < object_cut_z && object_cut_z < m_max_z) - wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, - only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | - only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | - only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); - else { - // the object is SLA-elevated and the plane is under it. - } -} - -double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const -{ - double projection = 0.0; - - const Vec3d starting_vec = m_drag_pos - m_drag_center; - const double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) { - const Vec3d mouse_dir = mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - const Vec3d inters_vec = inters - m_drag_pos; - - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); - } - return projection; -} - -BoundingBoxf3 GLGizmoCut::bounding_box() const -{ - BoundingBoxf3 ret; - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int i : idxs) { - const GLVolume* volume = selection.get_volume(i); - if (!volume->is_modifier) - ret.merge(volume->transformed_convex_hull_bounding_box()); - } - return ret; -} - -void GLGizmoCut::update_contours() -{ - const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); - - const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; - const int instance_idx = selection.get_instance_idx(); - std::vector volumes_idxs = std::vector(model_object->volumes.size()); - for (size_t i = 0; i < model_object->volumes.size(); ++i) { - volumes_idxs[i] = model_object->volumes[i]->id(); - } - - if (0.0 < m_cut_z && m_cut_z < m_max_z) { - if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || - m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) { - m_cut_contours.cut_z = m_cut_z; - - if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) - m_cut_contours.mesh = model_object->raw_mesh(); - - m_cut_contours.position = Vec3d::Zero();//box.center(); - m_cut_contours.shift = box.center();//Vec3d::Zero(); - m_cut_contours.object_id = model_object->id(); - m_cut_contours.instance_idx = instance_idx; - m_cut_contours.volumes_idxs = volumes_idxs; - m_cut_contours.contours.reset(); - - // addition back transformation - Geometry::Transformation m_transformation = Geometry::Transformation(); - m_transformation.set_offset(-first_glvolume->get_instance_transformation().get_offset()); - m_transformation.set_rotation(-m_angles); - auto cut_params = m_transformation.get_matrix(); - - - MeshSlicingParams slicing_params; - slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix() * cut_params; - - auto cut_z = m_cut_z - box.center().z(); - - const Polygons polys = slice_mesh(m_cut_contours.mesh.its, /*m_*/cut_z, slicing_params); - if (!polys.empty()) { - m_cut_contours.contours.init_from(polys, static_cast(/*m_*/cut_z)); -#if ENABLE_GLBEGIN_GLEND_REMOVAL - m_cut_contours.contours.set_color(ColorRGBA::WHITE()); -#else - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - } - } - else if (box.center() != m_cut_contours.shift) { - m_cut_contours.shift = box.center();// -m_cut_contours.position; - } - //else if (box.center() != m_cut_contours.position) { - // m_cut_contours.shift = -box.center();// -m_cut_contours.position; - //} - } - else - m_cut_contours.contours.reset(); -} - -GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoRotate3D(parent, icon_filename, sprite_id) - , m_cut_plane_gizmo(GLGizmoCut(parent, "", -1)) -{ - m_cut_plane_gizmo.set_group_id(3); - - m_modes = { _u8L("Planar"), _u8L("By Line"),_u8L("Grid") -// , _u8L("Radial"), _u8L("Modular") - }; - - m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; - m_connector_types = { _u8L("Plug"), _u8L("Dowel") }; - - m_connector_styles = { _u8L("Prizm"), _u8L("Frustrum") -// , _u8L("Claw") - }; - - m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Circle"), _u8L("Hexagon") -// , _u8L("D-shape") - }; - - m_axis_names = {"X", "Y", "Z"}; -} - bool GLGizmoCut3D::on_init() { - if(!GLGizmoRotate3D::on_init()) - return false; - - if (!m_cut_plane_gizmo.init()) + if(!m_rotation_gizmo.init()) + return false; + if(!m_move_gizmo.init()) return false; m_shortcut_key = WXK_CONTROL_C; @@ -650,33 +329,66 @@ std::string GLGizmoCut3D::on_get_name() const return _u8L("Cut"); } +void GLGizmoCut3D::on_set_state() +{ + m_move_gizmo.set_state(m_state); + m_rotation_gizmo.set_center(m_move_gizmo.get_center()); + m_rotation_gizmo.set_state(m_state); +} + +void GLGizmoCut3D::on_set_hover_id() +{ + int move_group_id = m_move_gizmo.get_group_id(); + m_rotation_gizmo. set_hover_id((m_hover_id < move_group_id) ? m_hover_id : -1); + m_move_gizmo. set_hover_id((m_hover_id >= move_group_id) ? m_hover_id - move_group_id : -1); +} + +void GLGizmoCut3D::on_enable_grabber(unsigned int id) +{ + m_rotation_gizmo.enable_grabber(id); + m_move_gizmo.enable_grabber(id- m_move_gizmo.get_group_id()); +} + +void GLGizmoCut3D::on_disable_grabber(unsigned int id) +{ + m_rotation_gizmo.disable_grabber(id); + m_move_gizmo.disable_grabber(id- m_move_gizmo.get_group_id()); +} + bool GLGizmoCut3D::on_is_activable() const { - return m_cut_plane_gizmo.is_activable(); + return m_move_gizmo.is_activable(); } void GLGizmoCut3D::on_start_dragging() { - GLGizmoRotate3D::on_start_dragging(); - if (m_hover_id == 3) - m_cut_plane_gizmo.start_dragging(); + m_rotation_gizmo.start_dragging(); + m_move_gizmo.start_dragging(); } void GLGizmoCut3D::on_stop_dragging() { - GLGizmoRotate3D::on_stop_dragging(); - if (m_hover_id == 3) - m_cut_plane_gizmo.stop_dragging(); + m_rotation_gizmo.stop_dragging(); + m_rotation_gizmo.set_center(m_move_gizmo.get_center()); + m_move_gizmo.stop_dragging(); +} + +void GLGizmoCut3D::on_update(const UpdateData& data) +{ + m_move_gizmo.update(data); + m_rotation_gizmo.update(data); } void GLGizmoCut3D::on_render() { render_cut_plane(); if (m_mode == CutMode::cutPlanar) { - GLGizmoRotate3D::on_render(); -// if (m_hover_id == -1 || m_hover_id == 3) + int move_group_id = m_move_gizmo.get_group_id(); + if (m_hover_id < move_group_id) + m_rotation_gizmo.render(); + if (m_hover_id == -1 || m_hover_id >= move_group_id) + m_move_gizmo.render(); } - m_cut_plane_gizmo.render(); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) @@ -773,7 +485,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) /*|| m_cut_z <= 0.0 || m_max_z <= m_cut_z*/); + m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || !can_perform_cut()); const bool cut_clicked = m_imgui->button(_L("Perform cut")); m_imgui->disabled_end(); @@ -783,6 +495,11 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) perform_cut(m_parent.get_selection()); } +bool GLGizmoCut3D::can_perform_cut() const +{ + return true; +} + void GLGizmoCut3D::perform_cut(const Selection& selection) { const int instance_idx = selection.get_instance_idx(); @@ -792,9 +509,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // m_cut_z is the distance from the bed. Subtract possible SLA elevation. const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const double object_cut_z = m_cut_plane_gizmo.get_cut_z() - first_glvolume->get_sla_shift_z(); + const double object_cut_z = m_move_gizmo.get_center().z() - first_glvolume->get_sla_shift_z(); - if (0.0 < object_cut_z/* && object_cut_z < m_max_z*/) + if (0.0 < object_cut_z && can_perform_cut()) wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index f8d7570a4..d1461404b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -3,6 +3,7 @@ #include "GLGizmoBase.hpp" #include "GLGizmoRotate.hpp" +#include "GLGizmoMove.hpp" #include "slic3r/GUI/GLModel.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/ObjectID.hpp" @@ -10,75 +11,33 @@ namespace Slic3r { namespace GUI { -class GLGizmoCut : public GLGizmoBase +class GLGizmoCenterMove : public GLGizmoMove3D { public: - static const double Offset; static const double Margin; private: - double m_cut_z{ 0.0 }; - double m_max_z{ 0.0 }; - double m_start_z{ 0.0 }; - Vec3d m_drag_pos; - Vec3d m_drag_center; - bool m_keep_upper{ true }; - bool m_keep_lower{ true }; - bool m_rotate_lower{ false }; -#if ENABLE_GLBEGIN_GLEND_REMOVAL - GLModel m_grabber_connection; - float m_old_z{ 0.0f }; -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - Vec3d m_angles{ Vec3d::Zero() }; - - struct CutContours - { - TriangleMesh mesh; - GLModel contours; - double cut_z{ 0.0 }; - Vec3d position{ Vec3d::Zero() }; - Vec3d shift{ Vec3d::Zero() }; - ObjectID object_id; - int instance_idx{ -1 }; - std::vector volumes_idxs; - }; - - CutContours m_cut_contours; + Vec3d m_min_pos{ Vec3d::Zero() }; + Vec3d m_max_pos{ Vec3d::Zero() }; public: - GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - - double get_cut_z() const { return m_cut_z; } - void set_cut_z(double cut_z); - void set_angles(const Vec3d& angles) { m_angles = angles; } - + GLGizmoCenterMove(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; protected: - virtual bool on_init() override; - virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } - virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } - virtual std::string on_get_name() const override; virtual void on_set_state() override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; virtual void on_update(const UpdateData& data) override; - virtual void on_render() override; - virtual void on_render_for_picking() override; - virtual void on_render_input_window(float x, float y, float bottom_limit) override; - -private: - void perform_cut(const Selection& selection); - double calc_projection(const Linef3& mouse_ray) const; public: - BoundingBoxf3 bounding_box() const; - void update_contours(); + void set_center_pos(const Vec3d& center_pos); + BoundingBoxf3 bounding_box() const; }; -class GLGizmoCut3D : public GLGizmoRotate3D + +class GLGizmoCut3D : public GLGizmoBase { - GLGizmoCut m_cut_plane_gizmo; + GLGizmoRotate3D m_rotation_gizmo; + GLGizmoCenterMove m_move_gizmo; #if ENABLE_GLBEGIN_GLEND_REMOVAL GLModel m_plane; @@ -148,57 +107,37 @@ class GLGizmoCut3D : public GLGizmoRotate3D public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - std::string get_tooltip() const override { - std::string tooltip = GLGizmoRotate3D::get_tooltip(); - if (tooltip.empty()) - tooltip = m_cut_plane_gizmo.get_tooltip(); - return tooltip; - } + std::string get_tooltip() const override; + + void shift_cut_z(double delta); protected: bool on_init() override; std::string on_get_name() const override; - void on_set_state() override { - GLGizmoRotate3D::on_set_state(); - m_cut_plane_gizmo.set_state(m_state); - } - void on_set_hover_id() override { - GLGizmoRotate3D::on_set_hover_id(); - m_cut_plane_gizmo.set_hover_id((m_hover_id == 3) ? 0 : -1); - } - void on_enable_grabber(unsigned int id) override { - GLGizmoRotate3D::on_enable_grabber(id); - if (id == 3) - m_cut_plane_gizmo.enable_grabber(0); - } - void on_disable_grabber(unsigned int id) override { - GLGizmoRotate3D::on_disable_grabber(id); - if (id == 3) - m_cut_plane_gizmo.disable_grabber(0); - } + void on_set_state() override; + void on_set_hover_id() override; + void on_enable_grabber(unsigned int id) override; + void on_disable_grabber(unsigned int id) override; bool on_is_activable() const override; void on_start_dragging() override; void on_stop_dragging() override; - void on_update(const UpdateData& data) override { - GLGizmoRotate3D::on_update(data); - m_cut_plane_gizmo.update(data); - } + void on_update(const UpdateData& data) override; void on_render() override; void on_render_for_picking() override { - GLGizmoRotate3D::on_render_for_picking(); - m_cut_plane_gizmo.render_for_picking(); + m_rotation_gizmo.render_for_picking(); + m_move_gizmo.render_for_picking(); } - void on_render_input_window(float x, float y, float bottom_limit) override; private: - + void set_center(const Vec3d& center); void render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); void render_double_input(const std::string& label, double& value_in); void render_move_center_input(int axis); void render_rotation_input(int axis); void render_connect_mode_radio_button(ConnectorMode mode); void render_connect_type_radio_button(ConnectorType type); + bool can_perform_cut() const; void render_cut_plane(); void perform_cut(const Selection& selection); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index ff921ea7c..e12974b75 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -78,6 +78,9 @@ void GLGizmoMove3D::on_update(const UpdateData& data) m_displacement.y() = calc_projection(data); else if (m_hover_id == 2) m_displacement.z() = calc_projection(data); + + if (m_has_forced_center) + m_center += m_displacement; } void GLGizmoMove3D::on_render() @@ -91,19 +94,27 @@ void GLGizmoMove3D::on_render() glsafe(::glEnable(GL_DEPTH_TEST)); const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = box.center(); + const Vec3d& center = m_has_forced_center ? m_center : box.center(); - // x axis - m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; - m_grabbers[0].color = AXES_COLOR[0]; + if (m_has_forced_center) + for (auto axis : { X, Y, Z }) { + m_grabbers[axis].center = center; + m_grabbers[axis].center[axis] += 0.5*fabs(box.max[axis] - box.min[axis]); + m_grabbers[axis].color = AXES_COLOR[axis]; + } + else { + // x axis + m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; + m_grabbers[0].color = AXES_COLOR[0]; - // y axis - m_grabbers[1].center = { center.x(), box.max.y() + Offset, center.z() }; - m_grabbers[1].color = AXES_COLOR[1]; + // y axis + m_grabbers[1].center = { center.x(), box.max.y() + Offset, center.z() }; + m_grabbers[1].color = AXES_COLOR[1]; - // z axis - m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset }; - m_grabbers[2].color = AXES_COLOR[2]; + // z axis + m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset }; + m_grabbers[2].color = AXES_COLOR[2]; + } glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); @@ -216,7 +227,10 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const { double projection = 0.0; - Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; + const Vec3d& starting_drag_position = m_has_forced_center ? m_grabbers[m_hover_id].center : m_starting_drag_position; + const Vec3d& starting_box_center = m_has_forced_center ? m_center : m_starting_box_center; + + Vec3d starting_vec = starting_drag_position - starting_box_center; double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); @@ -224,9 +238,9 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; + Vec3d inters_vec = inters - starting_drag_position; // finds projection of the vector along the staring direction projection = inters_vec.dot(starting_vec.normalized()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 2a75df866..9e404a95c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -17,6 +17,9 @@ class GLGizmoMove3D : public GLGizmoBase Vec3d m_starting_box_center{ Vec3d::Zero() }; Vec3d m_starting_box_bottom_center{ Vec3d::Zero() }; + Vec3d m_center{ Vec3d::Zero() }; + bool m_has_forced_center{ false }; + GLModel m_cone; #if ENABLE_GLBEGIN_GLEND_REMOVAL struct GrabberConnection @@ -38,6 +41,9 @@ public: std::string get_tooltip() const override; + void set_center(Vec3d center) { m_center = center; m_has_forced_center = true; } + const Vec3d& get_center() const { return m_center; } + protected: virtual bool on_init() override; virtual std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 9869634cc..b61310d6c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -38,9 +38,10 @@ void GLGizmoRotate::set_angle(double angle) m_angle = angle; } -void GLGizmoRotate::set_center_z(double center_z) +void GLGizmoRotate::set_center(const Vec3d& center) { - m_center_z = center_z; + m_forced_center = center; + m_has_forced_center = true; } std::string GLGizmoRotate::get_tooltip() const @@ -64,9 +65,7 @@ bool GLGizmoRotate::on_init() void GLGizmoRotate::on_start_dragging() { const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - m_center = box.center(); - if (m_center_z >= 0) - m_center[Z] = m_center_z; + m_center = m_has_forced_center ? m_forced_center : box.center(); m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; @@ -118,9 +117,7 @@ void GLGizmoRotate::on_render() const BoundingBoxf3& box = selection.get_bounding_box(); if (m_hover_id != 0 && !m_grabbers.front().dragging) { - m_center = box.center(); - if (m_center_z >= 0) - m_center[Z] = m_center_z; + m_center = m_has_forced_center ? m_forced_center : box.center(); m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 0c291cbef..7eee2069c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -28,13 +28,14 @@ public: private: Axis m_axis; double m_angle{ 0.0 }; - double m_center_z{ -1.0 }; Vec3d m_center{ Vec3d::Zero() }; float m_radius{ 0.0f }; float m_snap_coarse_in_radius{ 0.0f }; float m_snap_coarse_out_radius{ 0.0f }; float m_snap_fine_in_radius{ 0.0f }; float m_snap_fine_out_radius{ 0.0f }; + bool m_has_forced_center{false}; + Vec3d m_forced_center{ Vec3d::Zero() }; GLModel m_cone; #if ENABLE_GLBEGIN_GLEND_REMOVAL @@ -60,6 +61,7 @@ public: double get_angle() const { return m_angle; } void set_angle(double angle); void set_center_z(double center_z); + void set_center(const Vec3d& center); std::string get_tooltip() const override; @@ -103,7 +105,7 @@ public: Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } - void set_center_z(double center_z) { m_gizmos[X].set_center_z(center_z); m_gizmos[Y].set_center_z(center_z); m_gizmos[Z].set_center_z(center_z); } + void set_center(const Vec3d& center) { m_gizmos[X].set_center(center); m_gizmos[Y].set_center(center); m_gizmos[Z].set_center(center); } std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 3a144a65d..ba1419fea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -919,8 +919,8 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) else if (m_current == Cut) { auto do_move = [this, &processed](double delta_z) { - GLGizmoCut* cut = dynamic_cast(get_current()); - cut->set_cut_z(delta_z + cut->get_cut_z()); + GLGizmoCut3D* cut = dynamic_cast(get_current()); + cut->shift_cut_z(delta_z); processed = true; }; From 389b7ce4bd8968de92cc1c31aa5f6dd4e1cfb03a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 7 Feb 2022 10:09:46 +0100 Subject: [PATCH 04/97] MeshClipper extended: - direction and range of the clipping plane can be now set from the outside - it is now able to show a contour of the cut (not yet ideal with multipart objects that overlap) --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 10 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 33 +++++- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 8 +- src/slic3r/GUI/MeshUtils.cpp | 109 +++++++++++++++--- src/slic3r/GUI/MeshUtils.hpp | 13 ++- 10 files changed, 161 insertions(+), 38 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 66b6dcf60..b6da2e63d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -295,7 +295,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -304,7 +304,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l 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"))) - m_c->object_clipper()->set_position(clp_dist, true); + m_c->object_clipper()->set_position_by_ratio(clp_dist, true); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index be52ebcb9..e8a6f6e2c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -383,19 +383,19 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos if (action == SLAGizmoEventType::MouseWheelUp && control_down) { double pos = m_c->object_clipper()->get_position(); pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); + m_c->object_clipper()->set_position_by_ratio(pos, true); return true; } if (action == SLAGizmoEventType::MouseWheelDown && control_down) { double pos = m_c->object_clipper()->get_position(); pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); + m_c->object_clipper()->set_position_by_ratio(pos, true); return true; } if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); return true; } @@ -693,7 +693,7 @@ RENDER_AGAIN: else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -702,7 +702,7 @@ RENDER_AGAIN: 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")) - m_c->object_clipper()->set_position(clp_dist, true); + m_c->object_clipper()->set_position_by_ratio(clp_dist, true); // make sure supports are shown/hidden as appropriate bool show_sups = m_c->instances_hider()->are_supports_shown(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index dd9cf0de2..77dc3a966 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -463,7 +463,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_imgui->text(m_desc.at("clipping_of_view")); } else { if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position(-1., false); }); + wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -471,7 +471,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott 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"))) - m_c->object_clipper()->set_position(clp_dist, true); + m_c->object_clipper()->set_position_by_ratio(clp_dist, true); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 82a816d1b..30cf2b31c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -429,7 +429,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous pos = action == SLAGizmoEventType::MouseWheelDown ? std::max(0., pos - 0.01) : std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); + m_c->object_clipper()->set_position_by_ratio(pos, true); return true; } else if (alt_down) { @@ -461,7 +461,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 5f6cd7a95..6d49b276c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -157,7 +157,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -166,7 +166,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) 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"))) - m_c->object_clipper()->set_position(clp_dist, true); + m_c->object_clipper()->set_position_by_ratio(clp_dist, true); ImGui::Separator(); if (m_imgui->button(m_desc.at("remove_all"))) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index fa0b27269..e1bc32bbe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -516,19 +516,19 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (action == SLAGizmoEventType::MouseWheelUp && control_down) { double pos = m_c->object_clipper()->get_position(); pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); + m_c->object_clipper()->set_position_by_ratio(pos, true); return true; } if (action == SLAGizmoEventType::MouseWheelDown && control_down) { double pos = m_c->object_clipper()->get_position(); pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); + m_c->object_clipper()->set_position_by_ratio(pos, true); return true; } if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); return true; } @@ -826,7 +826,7 @@ RENDER_AGAIN: else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); + m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } @@ -835,7 +835,7 @@ RENDER_AGAIN: ImGui::PushItemWidth(window_width - clipping_slider_left); float clp_dist = m_c->object_clipper()->get_position(); if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); + m_c->object_clipper()->set_position_by_ratio(clp_dist, true); if (m_imgui->button("?")) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 1e49ebc8c..48a54fbae 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -425,18 +425,20 @@ void ObjectClipper::render_cut() const glsafe(::glPushMatrix()); #if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); + clipper->render_contour({ 1.f, 1.f, 1.f, 1.f}); #else glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); clipper->render_cut(); -#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + glsafe(::glColor3f(1.f, 1.f, 1.f)); + clipper->render_contour(); +#endif glsafe(::glPopMatrix()); ++clipper_id; } } - -void ObjectClipper::set_position(double pos, bool keep_normal) +void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) { const ModelObject* mo = get_pool()->selection_info()->model_object(); int active_inst = get_pool()->selection_info()->get_active_instance(); @@ -454,6 +456,28 @@ void ObjectClipper::set_position(double pos, bool keep_normal) get_pool()->get_canvas()->set_as_dirty(); } +void ObjectClipper::set_range_and_pos(const Vec3d& origin, const Vec3d& end, double pos) +{ + Vec3d normal = end-origin; + double norm = normal.norm(); + pos = std::clamp(pos, 0.0001, norm); + m_clp.reset(new ClippingPlane(normal, pos)); + m_clp_ratio = pos/norm; + get_pool()->get_canvas()->set_as_dirty(); +} + +const ClippingPlane* ObjectClipper::get_clipping_plane() const +{ + static const ClippingPlane no_clip = ClippingPlane::ClipsNothing(); + return m_hide_clipped ? m_clp.get() : &no_clip; +} + +void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contour_width) +{ + m_hide_clipped = hide_clipped; + for (auto& clipper : m_clippers) + clipper->set_behaviour(fill_cut, contour_width); +} void SupportsClipper::on_update() @@ -542,9 +566,12 @@ void SupportsClipper::render_cut() const glsafe(::glPushMatrix()); #if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f }); + m_clipper->render_contour({ 1.f, 1.f, 1.f, 1.f }); #else glsafe(::glColor3f(1.0f, 0.f, 0.37f)); m_clipper->render_cut(); + glsafe(::glColor3f(1.0f, 1.f, 1.f)); + m_clipper->render_contour(); #endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL glsafe(::glPopMatrix()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 228f5b58c..c5c238408 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -255,10 +255,13 @@ public: CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } #endif // NDEBUG - void set_position(double pos, bool keep_normal); + void set_normal(const Vec3d& dir); double get_position() const { return m_clp_ratio; } - ClippingPlane* get_clipping_plane() const { return m_clp.get(); } + const ClippingPlane* get_clipping_plane() const; void render_cut() const; + void set_position_by_ratio(double pos, bool keep_normal); + void set_range_and_pos(const Vec3d& origin, const Vec3d& end, double pos); + void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); protected: @@ -271,6 +274,7 @@ private: std::unique_ptr m_clp; double m_clp_ratio = 0.; double m_active_inst_bb_radius = 0.; + bool m_hide_clipped = true; }; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 3787abb2f..ac9437590 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -19,6 +19,16 @@ namespace Slic3r { namespace GUI { +void MeshClipper::set_behaviour(bool fill_cut, double contour_width) +{ + if (fill_cut != m_fill_cut || contour_width != m_contour_width) + m_triangles_valid = false; + m_fill_cut = fill_cut; + m_contour_width = contour_width; +} + + + void MeshClipper::set_plane(const ClippingPlane& plane) { if (m_plane != plane) { @@ -43,7 +53,6 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) if (m_mesh != &mesh) { m_mesh = &mesh; m_triangles_valid = false; - m_triangles2d.resize(0); } } @@ -52,7 +61,6 @@ void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) if (m_negative_mesh != &mesh) { m_negative_mesh = &mesh; m_triangles_valid = false; - m_triangles2d.resize(0); } } @@ -63,12 +71,10 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo) if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { m_trafo = trafo; m_triangles_valid = false; - m_triangles2d.resize(0); } } - #if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL void MeshClipper::render_cut(const ColorRGBA& color) #else @@ -77,7 +83,6 @@ void MeshClipper::render_cut() { if (! m_triangles_valid) recalculate_triangles(); - #if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); if (curr_shader != nullptr) @@ -100,6 +105,36 @@ void MeshClipper::render_cut() } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void MeshClipper::render_contour(const ColorRGBA& color) +#else +void MeshClipper::render_contour() +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +{ + if (! m_triangles_valid) + recalculate_triangles(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + m_model_expanded.set_color(color); + m_model_expanded.render(); + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +#else + if (m_vertex_array_expanded.has_VBOs()) + m_vertex_array_expanded.render(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +} + + void MeshClipper::recalculate_triangles() { @@ -181,24 +216,25 @@ void MeshClipper::recalculate_triangles() } } - m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); - tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting + std::vector triangles2d = m_fill_cut + ? triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.) + : std::vector(); #if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL m_model.reset(); GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(m_triangles2d.size()) }; - init_data.reserve_vertices(m_triangles2d.size()); - init_data.reserve_indices(m_triangles2d.size()); + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(triangles2d.size()) }; + init_data.reserve_vertices(triangles2d.size()); + init_data.reserve_indices(triangles2d.size()); // vertices + indices - for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) { + for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - const size_t idx = it - m_triangles2d.cbegin(); + const size_t idx = it - triangles2d.cbegin(); if (init_data.format.index_type == GLModel::Geometry::EIndexType::USHORT) init_data.add_ushort_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); else @@ -209,16 +245,61 @@ void MeshClipper::recalculate_triangles() m_model.init_from(std::move(init_data)); #else m_vertex_array.release_geometry(); - for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { + for (auto it=triangles2d.cbegin(); it != triangles2d.cend(); it=it+3) { m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); - const size_t idx = it - m_triangles2d.cbegin(); + const size_t idx = it - triangles2d.cbegin(); m_vertex_array.push_triangle(idx, idx+1, idx+2); } m_vertex_array.finalize_geometry(true); #endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + + triangles2d = {}; + if (m_contour_width != 0.) { + ExPolygons expolys_exp = offset_ex(expolys, scale_(m_contour_width)); + expolys_exp = diff_ex(expolys_exp, expolys); + triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.); + } + + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_model_expanded.reset(); + + init_data = GLModel::Geometry(); + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(triangles2d.size()) }; + init_data.reserve_vertices(triangles2d.size()); + init_data.reserve_indices(triangles2d.size()); + + // vertices + indices + for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - triangles2d.cbegin(); + if (init_data.format.index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); + else + init_data.add_uint_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + } + + if (!init_data.is_empty()) + m_model_expanded.init_from(std::move(init_data)); +#else + m_vertex_array_expanded.release_geometry(); + for (auto it=triangles2d.cbegin(); it != triangles2d.cend(); it=it+3) { + m_vertex_array_expanded.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); + m_vertex_array_expanded.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); + m_vertex_array_expanded.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); + const size_t idx = it - triangles2d.cbegin(); + m_vertex_array_expanded.push_triangle(idx, idx+1, idx+2); + } + m_vertex_array_expanded.finalize_geometry(true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + + + m_triangles_valid = true; } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index cc961ee8f..59a7b34ae 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -77,6 +77,10 @@ public: class MeshClipper { public: + // Set whether the cut should be triangulated and whether a cut + // contour should be calculated and shown. + void set_behaviour(bool fill_cut, double contour_width); + // Inform MeshClipper about which plane we want to use to cut the mesh // This is supposed to be in world coordinates. void set_plane(const ClippingPlane& plane); @@ -100,8 +104,12 @@ public: // be set in world coords. #if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL void render_cut(const ColorRGBA& color); + void render_contour(const ColorRGBA& color); #else void render_cut(); + // Render the triangulated contour. Transformation matrices should + // be set in world coords. + void render_contour(); #endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL private: @@ -112,13 +120,16 @@ private: const TriangleMesh* m_negative_mesh = nullptr; ClippingPlane m_plane; ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing(); - std::vector m_triangles2d; #if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL GLModel m_model; + GLModel m_model_expanded; #else GLIndexedVertexArray m_vertex_array; + GLIndexedVertexArray m_vertex_array_expanded; #endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL bool m_triangles_valid = false; + bool m_fill_cut = true; + double m_contour_width = 0.; }; From 7fef26527b02a80d604fa774c08e661fc37006ae Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 7 Feb 2022 10:13:15 +0100 Subject: [PATCH 05/97] Cut gizmo uses the common ObjectClipper to show the cut and contour --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 140 ++++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 50 +++++----- 2 files changed, 109 insertions(+), 81 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index ff5d89f5e..e5692cc53 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -88,9 +88,10 @@ void GLGizmoCut::on_render() Vec3d plane_center = box.center(); plane_center.z() = m_cut_z; m_max_z = box.max.z(); - set_cut_z(m_cut_z); + set_cut_z(m_cut_z); // FIXME: We should not call this during each render loop. - update_contours(); + // update_contours(); + m_c->object_clipper()->render_cut(); const float min_x = box.min.x() - Margin; const float max_x = box.max.x() + Margin; @@ -198,20 +199,20 @@ void GLGizmoCut::on_render() shader->stop_using(); } -#if ENABLE_GLBEGIN_GLEND_REMOVAL - shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); - glsafe(::glLineWidth(2.0f)); - m_cut_contours.contours.render(); - glsafe(::glPopMatrix()); -#if ENABLE_GLBEGIN_GLEND_REMOVAL - shader->stop_using(); - } -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +// #if ENABLE_GLBEGIN_GLEND_REMOVAL +// shader = wxGetApp().get_shader("flat"); +// if (shader != nullptr) { +// shader->start_using(); +// #endif // ENABLE_GLBEGIN_GLEND_REMOVAL +// glsafe(::glPushMatrix()); +// glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); +// glsafe(::glLineWidth(2.0f)); +// m_cut_contours.contours.render(); +// glsafe(::glPopMatrix()); +// #if ENABLE_GLBEGIN_GLEND_REMOVAL +// shader->stop_using(); +// } +// #endif // ENABLE_GLBEGIN_GLEND_REMOVAL } void GLGizmoCut::on_render_for_picking() @@ -269,6 +270,16 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) const bool cut_clicked = m_imgui->button(_L("Perform cut")); m_imgui->disabled_end(); + //////// + static bool hide_clipped = true; + static bool fill_cut = true; + static float contour_width = 0.; + m_imgui->checkbox("hide_clipped", hide_clipped); + m_imgui->checkbox("fill_cut", fill_cut); + m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); + m_c->object_clipper()->set_behavior(hide_clipped, fill_cut, contour_width); + //////// + m_imgui->end(); if (cut_clicked && (m_keep_upper || m_keep_lower)) @@ -279,6 +290,12 @@ void GLGizmoCut::set_cut_z(double cut_z) { // Clamp the plane to the object's bounding box m_cut_z = std::clamp(cut_z, 0.0, m_max_z); + + const BoundingBoxf3 box = bounding_box(); + Vec3d plane_center = box.center(); + plane_center.z() = 0; + m_c->object_clipper()->set_range_and_pos(plane_center, + plane_center + m_max_z * Vec3d::UnitZ(), m_cut_z); } void GLGizmoCut::perform_cut(const Selection& selection) @@ -337,52 +354,61 @@ BoundingBoxf3 GLGizmoCut::bounding_box() const return ret; } -void GLGizmoCut::update_contours() -{ - const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); +// void GLGizmoCut::update_contours() +// { +// const Selection& selection = m_parent.get_selection(); +// const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); +// const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); - const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; - const int instance_idx = selection.get_instance_idx(); - std::vector volumes_idxs = std::vector(model_object->volumes.size()); - for (size_t i = 0; i < model_object->volumes.size(); ++i) { - volumes_idxs[i] = model_object->volumes[i]->id(); - } +// const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; +// const int instance_idx = selection.get_instance_idx(); +// std::vector volumes_idxs = std::vector(model_object->volumes.size()); +// for (size_t i = 0; i < model_object->volumes.size(); ++i) { +// volumes_idxs[i] = model_object->volumes[i]->id(); +// } - if (0.0 < m_cut_z && m_cut_z < m_max_z) { - if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || - m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) { - m_cut_contours.cut_z = m_cut_z; +// if (0.0 < m_cut_z && m_cut_z < m_max_z) { +// if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || +// m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) { +// m_cut_contours.cut_z = m_cut_z; - if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) - m_cut_contours.mesh = model_object->raw_mesh(); +// if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) +// m_cut_contours.mesh = model_object->raw_mesh(); - m_cut_contours.position = box.center(); - m_cut_contours.shift = Vec3d::Zero(); - m_cut_contours.object_id = model_object->id(); - m_cut_contours.instance_idx = instance_idx; - m_cut_contours.volumes_idxs = volumes_idxs; - m_cut_contours.contours.reset(); +// m_cut_contours.position = box.center(); +// m_cut_contours.shift = Vec3d::Zero(); +// m_cut_contours.object_id = model_object->id(); +// m_cut_contours.instance_idx = instance_idx; +// m_cut_contours.volumes_idxs = volumes_idxs; +// m_cut_contours.contours.reset(); - MeshSlicingParams slicing_params; - slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); - const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); - if (!polys.empty()) { - m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); -#if ENABLE_GLBEGIN_GLEND_REMOVAL - m_cut_contours.contours.set_color(ColorRGBA::WHITE()); -#else - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - } - } - else if (box.center() != m_cut_contours.position) { - m_cut_contours.shift = box.center() - m_cut_contours.position; - } - } - else - m_cut_contours.contours.reset(); +// MeshSlicingParams slicing_params; +// slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); +// const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); +// if (!polys.empty()) { +// m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); +// #if ENABLE_GLBEGIN_GLEND_REMOVAL +// m_cut_contours.contours.set_color(ColorRGBA::WHITE()); +// #else +// m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); +// #endif // ENABLE_GLBEGIN_GLEND_REMOVAL +// } +// } +// else if (box.center() != m_cut_contours.position) { +// m_cut_contours.shift = box.center() - m_cut_contours.position; +// } +// } +// else +// m_cut_contours.contours.reset(); +// } + + + +CommonGizmosDataID GLGizmoCut::on_get_requirements() const { + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::ObjectClipper)); } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ccf8732cf..26bf7c56f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -28,19 +28,19 @@ class GLGizmoCut : public GLGizmoBase float m_old_z{ 0.0f }; #endif // ENABLE_GLBEGIN_GLEND_REMOVAL - struct CutContours - { - TriangleMesh mesh; - GLModel contours; - double cut_z{ 0.0 }; - Vec3d position{ Vec3d::Zero() }; - Vec3d shift{ Vec3d::Zero() }; - ObjectID object_id; - int instance_idx{ -1 }; - std::vector volumes_idxs; - }; + // struct CutContours + // { + // TriangleMesh mesh; + // GLModel contours; + // double cut_z{ 0.0 }; + // Vec3d position{ Vec3d::Zero() }; + // Vec3d shift{ Vec3d::Zero() }; + // ObjectID object_id; + // int instance_idx{ -1 }; + // std::vector volumes_idxs; + // }; - CutContours m_cut_contours; + // CutContours m_cut_contours; public: GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -51,23 +51,25 @@ public: std::string get_tooltip() const override; protected: - virtual bool on_init() override; - virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } - virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } - virtual std::string on_get_name() const override; - virtual void on_set_state() override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; - virtual void on_update(const UpdateData& data) override; - virtual void on_render() override; - virtual void on_render_for_picking() override; - virtual void on_render_input_window(float x, float y, float bottom_limit) override; + bool on_init() override; + void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } + void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } + std::string on_get_name() const override; + void on_set_state() override; + bool on_is_activable() const override; + void on_start_dragging() override; + void on_update(const UpdateData& data) override; + void on_render() override; + void on_render_for_picking() override; + void on_render_input_window(float x, float y, float bottom_limit) override; + CommonGizmosDataID on_get_requirements() const override; + private: void perform_cut(const Selection& selection); double calc_projection(const Linef3& mouse_ray) const; BoundingBoxf3 bounding_box() const; - void update_contours(); + //void update_contours(); }; } // namespace GUI From 016a7feb3d95cedc0006d369ba16a331538f7a65 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 7 Feb 2022 14:34:21 +0100 Subject: [PATCH 06/97] Extender MeshRaycaster so it can also provide hits on the clipping plane --- src/slic3r/GUI/MeshUtils.cpp | 28 ++++++++++++++++++++++++---- src/slic3r/GUI/MeshUtils.hpp | 5 +++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index ac9437590..40b1dc3bc 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -334,8 +334,11 @@ void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3 bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, - size_t* facet_idx) const + size_t* facet_idx, bool* was_clipping_plane_hit) const { + if (was_clipping_plane_hit) + *was_clipping_plane_hit = false; + Vec3d point; Vec3d direction; line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); @@ -356,9 +359,26 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& break; } - if (i==hits.size() || (hits.size()-i) % 2 != 0) { - // All hits are either clipped, or there is an odd number of unclipped - // hits - meaning the nearest must be from inside the mesh. + if (i==hits.size()) { + // All hits are clipped. + return false; + } + if ((hits.size()-i) % 2 != 0) { + // There is an odd number of unclipped hits - meaning the nearest must be from inside the mesh. + // In that case, calculate intersection with the clipping place. + if (clipping_plane && was_clipping_plane_hit) { + direction = direction + point; + point = trafo * point; // transform to world coords + direction = trafo * direction - point; + + Vec3d normal = -clipping_plane->get_normal().cast(); + double den = normal.dot(direction); + if (den != 0.) { + double t = (-clipping_plane->get_offset() - normal.dot(point))/den; + position = (point + t * direction).cast(); + *was_clipping_plane_hit = true; + } + } return false; } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 59a7b34ae..f5d031d92 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -154,10 +154,11 @@ public: const Vec2d& mouse_pos, const Transform3d& trafo, // how to get the mesh into world coords const Camera& camera, // current camera position - Vec3f& position, // where to save the positibon of the hit (mesh coords) + Vec3f& position, // where to save the positibon of the hit (mesh coords if mesh, world coords if clipping plane) Vec3f& normal, // normal of the triangle that was hit const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active) - size_t* facet_idx = nullptr // index of the facet hit + size_t* facet_idx = nullptr, // index of the facet hit + bool* was_clipping_plane_hit = nullptr // is the hit on the clipping place cross section? ) const; // Given a vector of points in woorld coordinates, this returns vector From a8564bf289224f833ba3a129f29c2fe8e1d7f860 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 7 Feb 2022 14:35:34 +0100 Subject: [PATCH 07/97] Cut gizmo is now able to see clicks on the clipping plane --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 34 ++++++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 ++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 4 ++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index e5692cc53..fc7fe0fe9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -403,12 +403,44 @@ BoundingBoxf3 GLGizmoCut::bounding_box() const // } +bool GLGizmoCut::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (is_dragging()) + return false; + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[m_c->selection_info()->get_active_instance()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); + const Camera& camera = wxGetApp().plater()->get_camera(); + + int mesh_id = -1; + for (const ModelVolume *mv : mo->volumes) { + ++mesh_id; + if (! mv->is_model_part()) + continue; + Vec3f hit; + Vec3f normal; + bool clipping_plane_was_hit = false; + m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * mv->get_matrix(), + camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), + nullptr, &clipping_plane_was_hit); + if (clipping_plane_was_hit) { + // The clipping plane was clicked, hit containts coordinates of the hit in world coords. + std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; + return true; + } + } + return false; +} + + CommonGizmosDataID GLGizmoCut::on_get_requirements() const { return CommonGizmosDataID( int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::ObjectClipper)); + | int(CommonGizmosDataID::ObjectClipper) + | int(CommonGizmosDataID::Raycaster)); } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 26bf7c56f..a084f346c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -9,6 +9,8 @@ namespace Slic3r { namespace GUI { +enum class SLAGizmoEventType : unsigned char; + class GLGizmoCut : public GLGizmoBase { static const double Offset; @@ -49,6 +51,7 @@ public: void set_cut_z(double cut_z); std::string get_tooltip() const override; + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); protected: bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 55cbb0c30..6d122a419 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -445,6 +445,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == MmuSegmentation) return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == Cut) + return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else return false; } @@ -656,7 +658,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_tooltip.clear(); if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) + if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Cut) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown())) // the gizmo got the event and took some action, there is no need to do anything more processed = true; From af03bed09484369ff9d725f72ed22fb0f01af21b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 17 Feb 2022 14:32:08 +0100 Subject: [PATCH 08/97] Cut: Implemented update_clipper() --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 44 +++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 8 ++--- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 6 ++++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 58484c8a5..208549bc4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -126,6 +126,35 @@ void GLGizmoCut3D::shift_cut_z(double delta) set_center(new_cut_center); } +void GLGizmoCut3D::update_clipper() +{ + const Vec3d& angles = m_rotation_gizmo.get_rotation(); + Matrix3d m; + m = Eigen::AngleAxisd(angles[X], Vec3d::UnitX()) + * Eigen::AngleAxisd(angles[Y], Vec3d::UnitY()) + * Eigen::AngleAxisd(angles[Z], Vec3d::UnitZ()); + + Vec3d plane_center = m_move_gizmo.get_center(); + BoundingBoxf3 box = m_move_gizmo.bounding_box(); + + Vec3d min, max = min = plane_center = m_move_gizmo.get_center(); + min[Z] = box.min.z(); + max[Z] = box.max.z(); + + min -= plane_center; + max -= plane_center; + + Vec3d beg = m * min; + Vec3d end = m * max; + + beg += plane_center; + end += plane_center; + + double dist = (plane_center - beg).norm(); + + m_c->object_clipper()->set_range_and_pos(beg, end, dist); +} + void GLGizmoCut3D::set_center(const Vec3d& center) { m_move_gizmo.set_center_pos(center); @@ -216,7 +245,7 @@ void GLGizmoCut3D::render_rotation_input(int axis) ImGui::SameLine(); Vec3d rotation = m_rotation_gizmo.get_rotation(); - double value = rotation[axis] * (180. / M_PI); + double value = Geometry::rad2deg(rotation[axis]); if (value > 360) value -= 360; @@ -224,7 +253,7 @@ void GLGizmoCut3D::render_rotation_input(int axis) ImGui::InputDouble(("##rotate_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); - rotation[axis] = (M_PI / 180.) * value; + rotation[axis] = Geometry::deg2rad(value); m_rotation_gizmo.set_rotation(rotation); } @@ -248,7 +277,7 @@ void GLGizmoCut3D::render_cut_plane() { const BoundingBoxf3 box = m_move_gizmo.bounding_box(); Vec3d plane_center = m_move_gizmo.get_center();// == Vec3d::Zero() ? box.center() : m_move_gizmo.get_center(); - // update_contours(); + m_c->object_clipper()->render_cut(); const float min_x = box.min.x() - GLGizmoCenterMove::Margin - plane_center.x(); @@ -383,6 +412,7 @@ void GLGizmoCut3D::on_update(const UpdateData& data) void GLGizmoCut3D::on_render() { + update_clipper(); render_cut_plane(); if (m_mode == CutMode::cutPlanar) { int move_group_id = m_move_gizmo.get_group_id(); @@ -435,7 +465,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(m_label_width); for (Axis axis : {X, Y, Z}) render_rotation_input(axis); - m_imgui->text(_L("")); + m_imgui->text(_L("°")); } else { ImGui::AlignTextToFramePadding(); @@ -510,12 +540,6 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) bool GLGizmoCut3D::can_perform_cut() const { return true; - - const BoundingBoxf3 box = bounding_box(); - Vec3d plane_center = box.center(); - plane_center.z() = 0; - m_c->object_clipper()->set_range_and_pos(plane_center, - plane_center + m_max_z * Vec3d::UnitZ(), m_cut_z); } void GLGizmoCut3D::perform_cut(const Selection& selection) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index b450b6ec7..80b128cbe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -113,16 +113,14 @@ public: bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void shift_cut_z(double delta); + void update_clipper(); protected: bool on_init() override; - void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } - void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } + void on_load(cereal::BinaryInputArchive& ar) override { ar(/*m_cut_z, */m_keep_upper, m_keep_lower, m_rotate_lower); } + void on_save(cereal::BinaryOutputArchive& ar) const override { ar(/*m_cut_z, */m_keep_upper, m_keep_lower, m_rotate_lower); } std::string on_get_name() const override; void on_set_state() override; - bool on_is_activable() const override; - void on_start_dragging() override; - void on_update(const UpdateData& data) override; CommonGizmosDataID on_get_requirements() const override; void on_set_hover_id() override; void on_enable_grabber(unsigned int id) override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 48a54fbae..c4828da8b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -458,9 +458,15 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) void ObjectClipper::set_range_and_pos(const Vec3d& origin, const Vec3d& end, double pos) { + std::cout << "origin:\t"<< origin.x() << "\t" << origin.y() << "\t"<< origin.z() << "\n"; + std::cout << "end:\t" << end.x() << "\t" << end.y() << "\t"<< end.z() << "\n"; + Vec3d normal = end-origin; double norm = normal.norm(); pos = std::clamp(pos, 0.0001, norm); + + std::cout << "NORM:\t" << norm << "\tPOS:\t" << pos << "\n\n"; + m_clp.reset(new ClippingPlane(normal, pos)); m_clp_ratio = pos/norm; get_pool()->get_canvas()->set_as_dirty(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index b2d543c5d..c87a2ba45 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -446,7 +446,7 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p else if (m_current == MmuSegmentation) return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Cut) - return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else return false; } From 9917b8e58b4b5ad77ce18d8e156874600f1a26bf Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 17 Feb 2022 15:16:47 +0100 Subject: [PATCH 09/97] Cut: fixed clipping plane when it is not horizonal --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 11 +++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 208549bc4..f365323ab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -130,9 +130,9 @@ void GLGizmoCut3D::update_clipper() { const Vec3d& angles = m_rotation_gizmo.get_rotation(); Matrix3d m; - m = Eigen::AngleAxisd(angles[X], Vec3d::UnitX()) + m = Eigen::AngleAxisd(angles[Z], Vec3d::UnitZ()) * Eigen::AngleAxisd(angles[Y], Vec3d::UnitY()) - * Eigen::AngleAxisd(angles[Z], Vec3d::UnitZ()); + * Eigen::AngleAxisd(angles[X], Vec3d::UnitX()); Vec3d plane_center = m_move_gizmo.get_center(); BoundingBoxf3 box = m_move_gizmo.bounding_box(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index c4828da8b..7f5567d83 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -458,17 +458,12 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) void ObjectClipper::set_range_and_pos(const Vec3d& origin, const Vec3d& end, double pos) { - std::cout << "origin:\t"<< origin.x() << "\t" << origin.y() << "\t"<< origin.z() << "\n"; - std::cout << "end:\t" << end.x() << "\t" << end.y() << "\t"<< end.z() << "\n"; - Vec3d normal = end-origin; double norm = normal.norm(); pos = std::clamp(pos, 0.0001, norm); - - std::cout << "NORM:\t" << norm << "\tPOS:\t" << pos << "\n\n"; - - m_clp.reset(new ClippingPlane(normal, pos)); - m_clp_ratio = pos/norm; + normal.normalize(); + m_clp.reset(new ClippingPlane(normal, normal.dot(origin)+pos)); + m_clp_ratio = pos; get_pool()->get_canvas()->set_as_dirty(); } From 1b9f42d71bc81ab1a12da06f64063de3206286a6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 23 Feb 2022 13:24:06 +0100 Subject: [PATCH 10/97] Cut improvements: * Added new cut() function witch respects to the rotation of the cut plane * Added revert buttons to the GizmoCutDialog * Fixed GLGizmoCenterMove::bounding_box(). Pad and supports don't added to the bb now --- src/imgui/imconfig.h | 2 + src/libslic3r/Model.cpp | 190 +++++++++++++++++++++++++++ src/libslic3r/Model.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 107 ++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 14 +- src/slic3r/GUI/ImGuiWrapper.cpp | 2 + src/slic3r/GUI/Plater.cpp | 24 ++++ src/slic3r/GUI/Plater.hpp | 1 + 8 files changed, 318 insertions(+), 23 deletions(-) diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index f2c3ef083..856d29318 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -140,6 +140,8 @@ namespace ImGui const wchar_t CancelButton = 0x14; const wchar_t CancelHoverButton = 0x15; // const wchar_t VarLayerHeightMarker = 0x16; + const wchar_t RevertButton = 0x16; + const wchar_t RevertButton2 = 0x17; const wchar_t RightArrowButton = 0x18; const wchar_t RightArrowHoverButton = 0x19; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 67450fb11..8d44f3e7c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1336,6 +1336,196 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr return res; } +ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes) +{ + if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) + return {}; + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; + ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + upper->set_model(nullptr); + upper->sla_support_points.clear(); + upper->sla_drain_holes.clear(); + upper->sla_points_status = sla::PointsStatus::NoPoints; + upper->clear_volumes(); + upper->input_file.clear(); + } + + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + lower->set_model(nullptr); + lower->sla_support_points.clear(); + lower->sla_drain_holes.clear(); + lower->sla_points_status = sla::PointsStatus::NoPoints; + lower->clear_volumes(); + lower->input_file.clear(); + } + + // Because transformations are going to be applied to meshes directly, + // we reset transformation of all instances and volumes, + // except for translation and Z-rotation on instances, which are preserved + // in the transformation matrix and not applied to the mesh transform. + + // const auto instance_matrix = instances[instance]->get_matrix(true); + const auto instance_matrix = Geometry::assemble_transform( + Vec3d::Zero(), // don't apply offset + instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), + instances[instance]->get_scaling_factor(), + instances[instance]->get_mirror() + ); + + const auto cut_matrix = Geometry::assemble_transform( + -cut_center, + Vec3d::Zero(), + Vec3d::Ones(), + Vec3d::Ones() + ); + + const auto invert_cut_matrix = Geometry::assemble_transform( + cut_center, + cut_rotation, + Vec3d::Ones(), + Vec3d::Ones() + ); + + // Displacement (in instance coordinates) to be applied to place the upper parts + Vec3d local_displace = Vec3d::Zero(); + + for (ModelVolume* volume : volumes) { + const auto volume_matrix = volume->get_matrix(); + + volume->supported_facets.reset(); + volume->seam_facets.reset(); + volume->mmu_segmentation_facets.reset(); + + if (!volume->is_model_part()) { + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + + volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower->add_volume(*volume); + } + else if (!volume->mesh().empty()) { + // Transform the mesh by the combined transformation matrix. + // Flip the triangles in case the composite transformation is left handed. + TriangleMesh mesh(volume->mesh()); + mesh.transform(cut_matrix * instance_matrix * volume_matrix, true); + mesh.rotate(-cut_rotation.z(), Z); + mesh.rotate(-cut_rotation.y(), Y); + mesh.rotate(-cut_rotation.x(), X); + + volume->reset_mesh(); + // Reset volume transformation except for offset + const Vec3d offset = volume->get_offset(); + volume->set_transformation(Geometry::Transformation()); + volume->set_offset(offset); + + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + { + indexed_triangle_set upper_its, lower_its; + cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper_mesh = TriangleMesh(upper_its); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower_mesh = TriangleMesh(lower_its); + } + + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { + upper_mesh.transform(invert_cut_matrix); + + ModelVolume* vol = upper->add_volume(upper_mesh); + vol->name = volume->name; + // Don't copy the config's ID. + vol->config.assign_config(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); + vol->set_material(volume->material_id(), *volume->material()); + } + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { + lower_mesh.transform(invert_cut_matrix); + + ModelVolume* vol = lower->add_volume(lower_mesh); + vol->name = volume->name; + // Don't copy the config's ID. + vol->config.assign_config(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); + vol->set_material(volume->material_id(), *volume->material()); + + // Compute the displacement (in instance coordinates) to be applied to place the upper parts + // The upper part displacement is set to half of the lower part bounding box + // this is done in hope at least a part of the upper part will always be visible and draggable + local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); + } + } + } + + ModelObjectPtrs res; + + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { + if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { + upper->center_around_origin(); + upper->translate_instances(-upper->origin_translation); + upper->origin_translation = Vec3d::Zero(); + } + else { + upper->invalidate_bounding_box(); + upper->center_around_origin(); + } + + // Reset instance transformation except offset and Z-rotation + for (size_t i = 0; i < instances.size(); ++i) { + auto& obj_instance = upper->instances[i]; + const Vec3d offset = obj_instance->get_offset(); + const double rot_z = obj_instance->get_rotation().z(); + const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), obj_instance->get_rotation()) * local_displace; + + obj_instance->set_transformation(Geometry::Transformation()); + obj_instance->set_offset(offset + displace); + if (i != instance) + obj_instance->set_rotation(Vec3d(0.0, 0.0, rot_z)); + } + + res.push_back(upper); + } + if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { + if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { + lower->center_around_origin(); + lower->translate_instances(-lower->origin_translation); + lower->origin_translation = Vec3d::Zero(); + } + else { + lower->invalidate_bounding_box(); + lower->center_around_origin(); + } + + // Reset instance transformation except offset and Z-rotation + for (size_t i = 0; i < instances.size(); ++i) { + auto& obj_instance = lower->instances[i]; + const Vec3d offset = obj_instance->get_offset(); + const double rot_z = obj_instance->get_rotation().z(); + obj_instance->set_transformation(Geometry::Transformation()); + obj_instance->set_offset(offset); + obj_instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, i == instance ? 0.0 : rot_z)); + } + + res.push_back(lower); + } + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + + return res; +} + void ModelObject::split(ModelObjectPtrs* new_objects) { for (ModelVolume* volume : this->volumes) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7a1cf206e..3d933c470 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -354,6 +354,7 @@ public: size_t facets_count() const; size_t parts_count() const; ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); + ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); void merge(); // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index f365323ab..1e4b9ea2c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -34,6 +34,8 @@ void GLGizmoCenterMove::set_center_pos(const Vec3d& centre_pos) set_center(Vec3d(std::clamp(centre_pos.x(), m_min_pos.x(), m_max_pos.x()), std::clamp(centre_pos.y(), m_min_pos.y(), m_max_pos.y()), std::clamp(centre_pos.z(), m_min_pos.z(), m_max_pos.z()))); + + m_center_offset = get_center() - m_bb_center; } std::string GLGizmoCenterMove::get_tooltip() const @@ -55,14 +57,8 @@ std::string GLGizmoCenterMove::get_tooltip() const void GLGizmoCenterMove::on_set_state() { // Reset internal variables on gizmo activation, if bounding box was changed - if (get_state() == On) { - const BoundingBoxf3 box = bounding_box(); - if (m_max_pos != box.max && m_min_pos != box.min) { - m_max_pos = box.max; - m_min_pos = box.min; - set_center_pos(box.center()); - } - } + if (get_state() == On) + update_bb(); } void GLGizmoCenterMove::on_update(const UpdateData& data) @@ -78,12 +74,26 @@ BoundingBoxf3 GLGizmoCenterMove::bounding_box() const const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int i : idxs) { const GLVolume* volume = selection.get_volume(i); - if (!volume->is_modifier) + // respect just to the solid parts for FFF and ignore pad and supports for SLA + if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) ret.merge(volume->transformed_convex_hull_bounding_box()); } return ret; } +bool GLGizmoCenterMove::update_bb() +{ + const BoundingBoxf3 box = bounding_box(); + if (m_max_pos != box.max && m_min_pos != box.min) { + m_max_pos = box.max; + m_min_pos = box.min; + m_bb_center = box.center(); + set_center_pos(m_bb_center + m_center_offset); + return true; + } + + return false; +} GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) @@ -137,7 +147,7 @@ void GLGizmoCut3D::update_clipper() Vec3d plane_center = m_move_gizmo.get_center(); BoundingBoxf3 box = m_move_gizmo.bounding_box(); - Vec3d min, max = min = plane_center = m_move_gizmo.get_center(); + Vec3d min, max = min = plane_center; min[Z] = box.min.z(); max[Z] = box.max.z(); @@ -155,10 +165,17 @@ void GLGizmoCut3D::update_clipper() m_c->object_clipper()->set_range_and_pos(beg, end, dist); } +void GLGizmoCut3D::update_clipper_on_render() +{ + update_clipper(); + suppress_update_clipper_on_render = true; +} + void GLGizmoCut3D::set_center(const Vec3d& center) { m_move_gizmo.set_center_pos(center); m_rotation_gizmo.set_center(m_move_gizmo.get_center()); + update_clipper(); } void GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) @@ -253,8 +270,11 @@ void GLGizmoCut3D::render_rotation_input(int axis) ImGui::InputDouble(("##rotate_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); - rotation[axis] = Geometry::deg2rad(value); - m_rotation_gizmo.set_rotation(rotation); + if (double val = Geometry::deg2rad(value); val != rotation[axis]) { + rotation[axis] = val; + m_rotation_gizmo.set_rotation(rotation); + update_clipper(); + } } void GLGizmoCut3D::render_connect_type_radio_button(ConnectorType type) @@ -273,6 +293,30 @@ void GLGizmoCut3D::render_connect_mode_radio_button(ConnectorMode mode) m_connector_mode = mode; } +bool GLGizmoCut3D::render_revert_button(const wxString& label) +{ + const ImGuiStyle& style = ImGui::GetStyle(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); + ImGui::SameLine(m_label_width); + + 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.4f, 0.4f, 0.4f, 1.0f }); + + bool revert = m_imgui->button(label); + + ImGui::PopStyleColor(3); + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(into_u8(_L("Revert")).c_str(), ImGui::GetFontSize() * 20.0f); + + ImGui::PopStyleVar(); + + ImGui::SameLine(); + return revert; +} + void GLGizmoCut3D::render_cut_plane() { const BoundingBoxf3 box = m_move_gizmo.bounding_box(); @@ -365,6 +409,8 @@ void GLGizmoCut3D::on_set_state() m_move_gizmo.set_state(m_state); m_rotation_gizmo.set_center(m_move_gizmo.get_center()); m_rotation_gizmo.set_state(m_state); + + suppress_update_clipper_on_render = m_state != On; } void GLGizmoCut3D::on_set_hover_id() @@ -388,7 +434,7 @@ void GLGizmoCut3D::on_disable_grabber(unsigned int id) bool GLGizmoCut3D::on_is_activable() const { - return m_move_gizmo.is_activable(); + return m_rotation_gizmo.is_activable() && m_move_gizmo.is_activable(); } void GLGizmoCut3D::on_start_dragging() @@ -408,11 +454,16 @@ void GLGizmoCut3D::on_update(const UpdateData& data) { m_move_gizmo.update(data); m_rotation_gizmo.update(data); + update_clipper(); } void GLGizmoCut3D::on_render() { - update_clipper(); + if (m_move_gizmo.update_bb()) { + m_rotation_gizmo.set_center(m_move_gizmo.get_center()); + update_clipper_on_render(); + } + render_cut_plane(); if (m_mode == CutMode::cutPlanar) { int move_group_id = m_move_gizmo.get_group_id(); @@ -421,6 +472,9 @@ void GLGizmoCut3D::on_render() if (m_hover_id == -1 || m_hover_id >= move_group_id) m_move_gizmo.render(); } + + if (!suppress_update_clipper_on_render) + update_clipper_on_render(); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) @@ -449,20 +503,23 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) render_combo(_u8L("Mode"), m_modes, m_mode); + bool revert_rotation{ false }; + bool revert_move{ false }; + if (m_mode <= CutMode::cutByLine) { ImGui::Separator(); if (m_mode == CutMode::cutPlanar) { ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Move center")); - ImGui::SameLine(m_label_width); + revert_move = render_revert_button(ImGui::RevertButton); for (Axis axis : {X, Y, Z}) render_move_center_input(axis); - m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Rotation")); - ImGui::SameLine(m_label_width); + revert_rotation = render_revert_button(ImGui::RevertButton2); for (Axis axis : {X, Y, Z}) render_rotation_input(axis); m_imgui->text(_L("°")); @@ -524,7 +581,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) //////// static bool hide_clipped = true; static bool fill_cut = true; - static float contour_width = 0.; + static float contour_width = 0.2f; m_imgui->checkbox("hide_clipped", hide_clipped); m_imgui->checkbox("fill_cut", fill_cut); m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); @@ -535,6 +592,13 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (cut_clicked && (m_keep_upper || m_keep_lower)) perform_cut(m_parent.get_selection()); + + if (revert_move) + set_center(m_move_gizmo.bounding_box().center()); + if (revert_rotation) { + m_rotation_gizmo.set_rotation(Vec3d::Zero()); + update_clipper(); + } } bool GLGizmoCut3D::can_perform_cut() const @@ -553,8 +617,13 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); const double object_cut_z = m_move_gizmo.get_center().z() - first_glvolume->get_sla_shift_z(); + Vec3d instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); + + Vec3d cut_center_offset = m_move_gizmo.get_center() - instance_offset; + cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); + if (0.0 < object_cut_z && can_perform_cut()) - wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, + wxGetApp().plater()->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 80b128cbe..fcf024db1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -19,8 +19,10 @@ public: static const double Margin; private: - Vec3d m_min_pos{ Vec3d::Zero() }; - Vec3d m_max_pos{ Vec3d::Zero() }; + Vec3d m_min_pos { Vec3d::Zero() }; + Vec3d m_max_pos { Vec3d::Zero() }; + Vec3d m_bb_center { Vec3d::Zero() }; + Vec3d m_center_offset { Vec3d::Zero() }; public: GLGizmoCenterMove(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -33,6 +35,7 @@ protected: public: void set_center_pos(const Vec3d& center_pos); BoundingBoxf3 bounding_box() const; + bool update_bb(); }; @@ -56,6 +59,7 @@ class GLGizmoCut3D : public GLGizmoBase float m_label_width{ 150.0 }; float m_control_width{ 200.0 }; bool m_imperial_units{ false }; + bool suppress_update_clipper_on_render{false}; enum CutMode { cutPlanar @@ -114,11 +118,12 @@ public: void shift_cut_z(double delta); void update_clipper(); + void update_clipper_on_render(); protected: bool on_init() override; - void on_load(cereal::BinaryInputArchive& ar) override { ar(/*m_cut_z, */m_keep_upper, m_keep_lower, m_rotate_lower); } - void on_save(cereal::BinaryOutputArchive& ar) const override { ar(/*m_cut_z, */m_keep_upper, m_keep_lower, m_rotate_lower); } + void on_load(cereal::BinaryInputArchive& ar) override { ar(m_keep_upper, m_keep_lower, m_rotate_lower); } + void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_keep_upper, m_keep_lower, m_rotate_lower); } std::string on_get_name() const override; void on_set_state() override; CommonGizmosDataID on_get_requirements() const override; @@ -144,6 +149,7 @@ private: void render_move_center_input(int axis); void render_rotation_input(int axis); void render_connect_mode_radio_button(ConnectorMode mode); + bool render_revert_button(const wxString& label); void render_connect_type_radio_button(ConnectorType type); bool can_perform_cut() const; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e70c1111b..5e154e48a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -70,6 +70,8 @@ static const std::map font_icons = { {ImGui::LegendShells , "legend_shells" }, {ImGui::LegendToolMarker , "legend_toolmarker" }, #endif // ENABLE_LEGEND_TOOLBAR_ICONS + {ImGui::RevertButton , "undo" }, + {ImGui::RevertButton2 , "undo" }, }; static const std::map font_icons_large = { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 32cd327a8..0ff3c294c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5854,6 +5854,30 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCut selection.add_object((unsigned int)(last_id - i), i == 0); } +void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes) +{ + wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); + auto* object = p->model.objects[obj_idx]; + + wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); + + if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) + return; + + Plater::TakeSnapshot snapshot(this, _L("Cut by Plane")); + wxBusyCursor wait; + + const auto new_objects = object->cut(instance_idx, cut_center, cut_rotation, attributes); + + remove(obj_idx); + p->load_model_objects(new_objects); + + Selection& selection = p->get_selection(); + size_t last_id = p->model.objects.size() - 1; + for (size_t i = 0; i < new_objects.size(); ++i) + selection.add_object((unsigned int)(last_id - i), i == 0); +} + void Plater::export_gcode(bool prefer_removable) { if (p->model.objects.empty()) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index baa54480c..a168c32d1 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -254,6 +254,7 @@ public: void toggle_layers_editing(bool enable); void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes); + void cut(size_t obj_idx, size_t instance_idx, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); void export_gcode(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); From bf6abf71d081b019d9f2abf102c6808a3cf5036f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 28 Feb 2022 12:06:56 +0100 Subject: [PATCH 11/97] Cut: + Code refactoring: grabbers to move cut plane by Axes are changed to one "plane grabber" + Code cleaning in GizmoMove3D: reverted changes from c45c0045 --- src/imgui/imconfig.h | 1 - src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 365 ++++++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 55 ++-- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 22 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 6 - src/slic3r/GUI/ImGuiWrapper.cpp | 1 - 7 files changed, 251 insertions(+), 202 deletions(-) diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 856d29318..dcb2d2338 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -141,7 +141,6 @@ namespace ImGui const wchar_t CancelHoverButton = 0x15; // const wchar_t VarLayerHeightMarker = 0x16; const wchar_t RevertButton = 0x16; - const wchar_t RevertButton2 = 0x17; const wchar_t RightArrowButton = 0x18; const wchar_t RightArrowHoverButton = 0x19; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index e0e018a13..4c82c9d5d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -81,7 +81,8 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u void GLGizmoBase::set_hover_id(int id) { - if (m_grabbers.empty() || id < (int)m_grabbers.size()) { +// !??? if (m_grabbers.empty() || id < (int)m_grabbers.size()) + { m_hover_id = id; on_set_hover_id(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 1e4b9ea2c..1d16ee17f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -21,87 +21,17 @@ namespace Slic3r { namespace GUI { -const double GLGizmoCenterMove::Margin = 20.0; - -GLGizmoCenterMove::GLGizmoCenterMove(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoMove3D(parent, "", -1) -{ -} - -void GLGizmoCenterMove::set_center_pos(const Vec3d& centre_pos) -{ - // Clamp the center position of the cut plane to the object's bounding box - set_center(Vec3d(std::clamp(centre_pos.x(), m_min_pos.x(), m_max_pos.x()), - std::clamp(centre_pos.y(), m_min_pos.y(), m_max_pos.y()), - std::clamp(centre_pos.z(), m_min_pos.z(), m_max_pos.z()))); - - m_center_offset = get_center() - m_bb_center; -} - -std::string GLGizmoCenterMove::get_tooltip() const -{ - double koef = wxGetApp().app_config->get("use_inches") == "1" ? ObjectManipulation::mm_to_in : 1.0; - - const Vec3d& center_pos = get_center(); - - if (m_hover_id == 0 || m_grabbers[0].dragging) - return "X: " + format(center_pos.x() * koef, 2); - else if (m_hover_id == 1 || m_grabbers[1].dragging) - return "Y: " + format(center_pos.y() * koef, 2); - else if (m_hover_id == 2 || m_grabbers[2].dragging) - return "Z: " + format(center_pos.z() * koef, 2); - else - return ""; -} - -void GLGizmoCenterMove::on_set_state() -{ - // Reset internal variables on gizmo activation, if bounding box was changed - if (get_state() == On) - update_bb(); -} - -void GLGizmoCenterMove::on_update(const UpdateData& data) -{ - GLGizmoMove3D::on_update(data); - set_center_pos(get_center()); -} - -BoundingBoxf3 GLGizmoCenterMove::bounding_box() const -{ - BoundingBoxf3 ret; - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int i : idxs) { - const GLVolume* volume = selection.get_volume(i); - // respect just to the solid parts for FFF and ignore pad and supports for SLA - if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) - ret.merge(volume->transformed_convex_hull_bounding_box()); - } - return ret; -} - -bool GLGizmoCenterMove::update_bb() -{ - const BoundingBoxf3 box = bounding_box(); - if (m_max_pos != box.max && m_min_pos != box.min) { - m_max_pos = box.max; - m_min_pos = box.min; - m_bb_center = box.center(); - set_center_pos(m_bb_center + m_center_offset); - return true; - } - - return false; -} - +static const double Margin = 20.0; +static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) , m_rotation_gizmo(GLGizmoRotate3D(parent, "", -1)) - , m_move_gizmo(GLGizmoCenterMove(parent, "", -1)) + , m_rotation_matrix( Eigen::AngleAxisd(0.0, Vec3d::UnitZ()) + * Eigen::AngleAxisd(0.0, Vec3d::UnitY()) + * Eigen::AngleAxisd(0.0, Vec3d::UnitX())) { - m_move_gizmo.set_group_id(3); + set_group_id(3); m_modes = { _u8L("Planar"), _u8L("By Line"),_u8L("Grid") // , _u8L("Radial"), _u8L("Modular") @@ -124,43 +54,53 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, std::string GLGizmoCut3D::get_tooltip() const { std::string tooltip = m_rotation_gizmo.get_tooltip(); - if (tooltip.empty()) - tooltip = m_move_gizmo.get_tooltip(); + if (tooltip.empty()) { + double koef = wxGetApp().app_config->get("use_inches") == "1" ? ObjectManipulation::mm_to_in : 1.0; + if (m_hover_id == get_group_id() || m_grabbers[0].dragging) + return "X: " + format(m_plane_center.x() * koef, 2) + "; " +//"\n" + + "Y: " + format(m_plane_center.y() * koef, 2) + "; " +//"\n" + + "Z: " + format(m_plane_center.z() * koef, 2); + } + return tooltip; } void GLGizmoCut3D::shift_cut_z(double delta) { - Vec3d new_cut_center = m_move_gizmo.get_center(); + Vec3d new_cut_center = m_plane_center; new_cut_center[Z] += delta; - set_center(new_cut_center); + set_center_pos(new_cut_center); +} + +void GLGizmoCut3D::rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center) +{ + if (m_rotations != angles) { + m_rotation_matrix = Eigen::AngleAxisd(angles[Z], Vec3d::UnitZ()) + * Eigen::AngleAxisd(angles[Y], Vec3d::UnitY()) + * Eigen::AngleAxisd(angles[X], Vec3d::UnitX()); + m_rotations = angles; + } + + vec -= center; + vec = m_rotation_matrix * vec; + vec += center; } void GLGizmoCut3D::update_clipper() { const Vec3d& angles = m_rotation_gizmo.get_rotation(); - Matrix3d m; - m = Eigen::AngleAxisd(angles[Z], Vec3d::UnitZ()) - * Eigen::AngleAxisd(angles[Y], Vec3d::UnitY()) - * Eigen::AngleAxisd(angles[X], Vec3d::UnitX()); + BoundingBoxf3 box = bounding_box(); - Vec3d plane_center = m_move_gizmo.get_center(); - BoundingBoxf3 box = m_move_gizmo.bounding_box(); + double radius = box.radius(); - Vec3d min, max = min = plane_center; - min[Z] = box.min.z(); - max[Z] = box.max.z(); + Vec3d beg, end = beg = m_plane_center; + beg[Z] = box.center().z() - radius;//box.min.z(); + end[Z] = box.center().z() + radius;//box.max.z(); - min -= plane_center; - max -= plane_center; + rotate_vec3d_around_center(beg, angles, m_plane_center); + rotate_vec3d_around_center(end, angles, m_plane_center); - Vec3d beg = m * min; - Vec3d end = m * max; - - beg += plane_center; - end += plane_center; - - double dist = (plane_center - beg).norm(); + double dist = (m_plane_center - beg).norm(); m_c->object_clipper()->set_range_and_pos(beg, end, dist); } @@ -173,8 +113,8 @@ void GLGizmoCut3D::update_clipper_on_render() void GLGizmoCut3D::set_center(const Vec3d& center) { - m_move_gizmo.set_center_pos(center); - m_rotation_gizmo.set_center(m_move_gizmo.get_center()); + set_center_pos(center); + m_rotation_gizmo.set_center(m_plane_center); update_clipper(); } @@ -241,7 +181,7 @@ void GLGizmoCut3D::render_move_center_input(int axis) ImGui::SameLine(); ImGui::PushItemWidth(0.3*m_control_width); - Vec3d move = m_move_gizmo.get_center(); + Vec3d move = m_plane_center; double in_val, value = in_val = move[axis]; if (m_imperial_units) value *= ObjectManipulation::mm_to_in; @@ -293,7 +233,7 @@ void GLGizmoCut3D::render_connect_mode_radio_button(ConnectorMode mode) m_connector_mode = mode; } -bool GLGizmoCut3D::render_revert_button(const wxString& label) +bool GLGizmoCut3D::render_revert_button(const std::string& label_id) { const ImGuiStyle& style = ImGui::GetStyle(); @@ -304,7 +244,9 @@ bool GLGizmoCut3D::render_revert_button(const wxString& label) ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.4f, 0.4f, 0.4f, 1.0f }); - bool revert = m_imgui->button(label); + std::string label; + label += ImGui::RevertButton; + bool revert = ImGui::Button((label + "##" + label_id).c_str()); ImGui::PopStyleColor(3); @@ -319,15 +261,18 @@ bool GLGizmoCut3D::render_revert_button(const wxString& label) void GLGizmoCut3D::render_cut_plane() { - const BoundingBoxf3 box = m_move_gizmo.bounding_box(); - Vec3d plane_center = m_move_gizmo.get_center();// == Vec3d::Zero() ? box.center() : m_move_gizmo.get_center(); - m_c->object_clipper()->render_cut(); - const float min_x = box.min.x() - GLGizmoCenterMove::Margin - plane_center.x(); - const float max_x = box.max.x() + GLGizmoCenterMove::Margin - plane_center.x(); - const float min_y = box.min.y() - GLGizmoCenterMove::Margin - plane_center.y(); - const float max_y = box.max.y() + GLGizmoCenterMove::Margin - plane_center.y(); + if (m_hide_cut_plane) + return; + + const BoundingBoxf3 box = bounding_box(); + + const float min_x = box.min.x() - Margin - m_plane_center.x(); + const float max_x = box.max.x() + Margin - m_plane_center.x(); + const float min_y = box.min.y() - Margin - m_plane_center.y(); + const float max_y = box.max.y() + Margin - m_plane_center.y(); + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); @@ -339,10 +284,10 @@ void GLGizmoCut3D::render_cut_plane() return; shader->start_using(); - Vec3d angles = m_rotation_gizmo.get_rotation(); + const Vec3d& angles = m_rotation_gizmo.get_rotation(); glsafe(::glPushMatrix()); - glsafe(::glTranslated(plane_center.x(), plane_center.y(), plane_center.z())); + glsafe(::glTranslated(m_plane_center.x(), m_plane_center.y(), m_plane_center.z())); glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); @@ -388,14 +333,70 @@ void GLGizmoCut3D::render_cut_plane() shader->stop_using(); } +void GLGizmoCut3D::render_cut_center_graber() +{ + const Vec3d& angles = m_rotation_gizmo.get_rotation(); + const BoundingBoxf3 box = bounding_box(); + + Vec3d grabber_center = m_plane_center; + grabber_center[Z] += 10; // Margin + + rotate_vec3d_around_center(grabber_center, angles, m_plane_center); + + m_grabbers[0].center = grabber_center; + m_grabbers[0].angles = angles; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glLineWidth(m_hover_id == get_group_id() ? 2.0f : 1.5f)); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + // if (!m_grabber_connection.is_initialized() || z_changed) + { + m_grabber_connection.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)m_plane_center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); + + // indices + init_data.add_ushort_line(0, 1); + + m_grabber_connection.init_from(std::move(init_data)); + } + m_grabber_connection.render(); + + shader->stop_using(); + } + + shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + + m_grabbers[0].color = GRABBER_COLOR; + m_grabbers[0].render(m_hover_id == get_group_id(), float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); + + shader->stop_using(); + } +} + bool GLGizmoCut3D::on_init() { + m_grabbers.emplace_back(); + m_shortcut_key = WXK_CONTROL_C; + if(!m_rotation_gizmo.init()) return false; - if(!m_move_gizmo.init()) - return false; - m_shortcut_key = WXK_CONTROL_C; return true; } @@ -406,8 +407,9 @@ std::string GLGizmoCut3D::on_get_name() const void GLGizmoCut3D::on_set_state() { - m_move_gizmo.set_state(m_state); - m_rotation_gizmo.set_center(m_move_gizmo.get_center()); + if (get_state() == On) + update_bb(); + m_rotation_gizmo.set_center(m_plane_center); m_rotation_gizmo.set_state(m_state); suppress_update_clipper_on_render = m_state != On; @@ -415,68 +417,138 @@ void GLGizmoCut3D::on_set_state() void GLGizmoCut3D::on_set_hover_id() { - int move_group_id = m_move_gizmo.get_group_id(); - m_rotation_gizmo. set_hover_id((m_hover_id < move_group_id) ? m_hover_id : -1); - m_move_gizmo. set_hover_id((m_hover_id >= move_group_id) ? m_hover_id - move_group_id : -1); + m_rotation_gizmo.set_hover_id(m_hover_id < get_group_id() ? m_hover_id: -1); } void GLGizmoCut3D::on_enable_grabber(unsigned int id) { m_rotation_gizmo.enable_grabber(id); - m_move_gizmo.enable_grabber(id- m_move_gizmo.get_group_id()); + if (id == get_group_id()) + m_grabbers[0].enabled = true; } void GLGizmoCut3D::on_disable_grabber(unsigned int id) { m_rotation_gizmo.disable_grabber(id); - m_move_gizmo.disable_grabber(id- m_move_gizmo.get_group_id()); + if (id == get_group_id()) + m_grabbers[0].enabled = false; } bool GLGizmoCut3D::on_is_activable() const { - return m_rotation_gizmo.is_activable() && m_move_gizmo.is_activable(); + // This is assumed in GLCanvas3D::do_rotate, do not change this + // without updating that function too. + return m_parent.get_selection().is_single_full_instance(); } void GLGizmoCut3D::on_start_dragging() { m_rotation_gizmo.start_dragging(); - m_move_gizmo.start_dragging(); } void GLGizmoCut3D::on_stop_dragging() { m_rotation_gizmo.stop_dragging(); - m_rotation_gizmo.set_center(m_move_gizmo.get_center()); - m_move_gizmo.stop_dragging(); } void GLGizmoCut3D::on_update(const UpdateData& data) { - m_move_gizmo.update(data); - m_rotation_gizmo.update(data); - update_clipper(); + if (m_hover_id == get_group_id()) { + const Vec3d& starting_box_center = m_plane_center; + const Vec3d& starting_drag_position = m_grabbers[0].center; + + double projection = 0.0; + + Vec3d starting_vec = starting_drag_position - starting_box_center; + if (starting_vec.norm() != 0.0) { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - starting_drag_position; + + starting_vec.normalize(); + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec); + } + if (wxGetKeyState(WXK_SHIFT)) + projection = m_snap_step * (double)std::round(projection / m_snap_step); + + set_center(starting_box_center + starting_vec * projection); + } + else { + m_rotation_gizmo.update(data); + update_clipper(); + } +} + +void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos) +{ + m_plane_center = center_pos; + + // !!! ysFIXME add smart clamp calculation + // Clamp the center position of the cut plane to the object's bounding box + //m_plane_center = Vec3d(std::clamp(center_pos.x(), m_min_pos.x(), m_max_pos.x()), + // std::clamp(center_pos.y(), m_min_pos.y(), m_max_pos.y()), + // std::clamp(center_pos.z(), m_min_pos.z(), m_max_pos.z()))); + + m_center_offset = m_plane_center - m_bb_center; +} + +BoundingBoxf3 GLGizmoCut3D::bounding_box() const +{ + BoundingBoxf3 ret; + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int i : idxs) { + const GLVolume* volume = selection.get_volume(i); + // respect just to the solid parts for FFF and ignore pad and supports for SLA + if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) + ret.merge(volume->transformed_convex_hull_bounding_box()); + } + return ret; +} + +bool GLGizmoCut3D::update_bb() +{ + const BoundingBoxf3 box = bounding_box(); + if (m_max_pos != box.max && m_min_pos != box.min) { + m_max_pos = box.max; + m_min_pos = box.min; + m_bb_center = box.center(); + set_center_pos(m_bb_center + m_center_offset); + return true; + } + return false; } void GLGizmoCut3D::on_render() { - if (m_move_gizmo.update_bb()) { - m_rotation_gizmo.set_center(m_move_gizmo.get_center()); + if (update_bb()) { + m_rotation_gizmo.set_center(m_plane_center); update_clipper_on_render(); } render_cut_plane(); + render_cut_center_graber(); if (m_mode == CutMode::cutPlanar) { - int move_group_id = m_move_gizmo.get_group_id(); - if (m_hover_id < move_group_id) + if (m_hover_id < get_group_id()) m_rotation_gizmo.render(); - if (m_hover_id == -1 || m_hover_id >= move_group_id) - m_move_gizmo.render(); } if (!suppress_update_clipper_on_render) update_clipper_on_render(); } +void GLGizmoCut3D::on_render_for_picking() +{ + m_rotation_gizmo.render_for_picking(); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); +} + void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) { static float last_y = 0.0f; @@ -512,14 +584,14 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (m_mode == CutMode::cutPlanar) { ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Move center")); - revert_move = render_revert_button(ImGui::RevertButton); + revert_move = render_revert_button("move"); for (Axis axis : {X, Y, Z}) render_move_center_input(axis); m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Rotation")); - revert_rotation = render_revert_button(ImGui::RevertButton2); + revert_rotation = render_revert_button("rotation"); for (Axis axis : {X, Y, Z}) render_rotation_input(axis); m_imgui->text(_L("°")); @@ -578,6 +650,10 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) const bool cut_clicked = m_imgui->button(_L("Perform cut")); m_imgui->disabled_end(); + ImGui::Separator(); + + m_imgui->checkbox("hide_cut_plane", m_hide_cut_plane); + //////// static bool hide_clipped = true; static bool fill_cut = true; @@ -594,7 +670,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) perform_cut(m_parent.get_selection()); if (revert_move) - set_center(m_move_gizmo.bounding_box().center()); + set_center(bounding_box().center()); if (revert_rotation) { m_rotation_gizmo.set_rotation(Vec3d::Zero()); update_clipper(); @@ -602,7 +678,12 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) } bool GLGizmoCut3D::can_perform_cut() const -{ +{ + BoundingBoxf3 box = bounding_box(); + double dist = (m_plane_center - box.center()).norm(); + if (dist > box.radius()) + return false; + return true; } @@ -615,11 +696,11 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // m_cut_z is the distance from the bed. Subtract possible SLA elevation. const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const double object_cut_z = m_move_gizmo.get_center().z() - first_glvolume->get_sla_shift_z(); + const double object_cut_z = m_plane_center.z() - first_glvolume->get_sla_shift_z(); Vec3d instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); - Vec3d cut_center_offset = m_move_gizmo.get_center() - instance_offset; + Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); if (0.0 < object_cut_z && can_perform_cut()) @@ -632,8 +713,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } } - - bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (is_dragging()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index fcf024db1..2c6eddc26 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -13,39 +13,22 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; -class GLGizmoCenterMove : public GLGizmoMove3D -{ -public: - static const double Margin; -private: - - Vec3d m_min_pos { Vec3d::Zero() }; - Vec3d m_max_pos { Vec3d::Zero() }; - Vec3d m_bb_center { Vec3d::Zero() }; - Vec3d m_center_offset { Vec3d::Zero() }; - -public: - GLGizmoCenterMove(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - std::string get_tooltip() const override; - -protected: - virtual void on_set_state() override; - virtual void on_update(const UpdateData& data) override; - -public: - void set_center_pos(const Vec3d& center_pos); - BoundingBoxf3 bounding_box() const; - bool update_bb(); -}; - - class GLGizmoCut3D : public GLGizmoBase { GLGizmoRotate3D m_rotation_gizmo; - GLGizmoCenterMove m_move_gizmo; + double m_snap_step{ 1.0 }; + + Vec3d m_plane_center{ Vec3d::Zero() }; + // data to check position of the cut palne center on gizmo activation + Vec3d m_min_pos{ Vec3d::Zero() }; + Vec3d m_max_pos{ Vec3d::Zero() }; + Vec3d m_bb_center{ Vec3d::Zero() }; + Vec3d m_center_offset{ Vec3d::Zero() }; + #if ENABLE_GLBEGIN_GLEND_REMOVAL GLModel m_plane; + GLModel m_grabber_connection; float m_old_z{ 0.0f }; #endif // ENABLE_GLBEGIN_GLEND_REMOVAL @@ -53,6 +36,8 @@ class GLGizmoCut3D : public GLGizmoBase bool m_keep_lower{ true }; bool m_rotate_lower{ false }; + bool m_hide_cut_plane{ false }; + double m_connector_depth_ratio{ 1.5 }; double m_connector_size{ 5.0 }; @@ -61,6 +46,9 @@ class GLGizmoCut3D : public GLGizmoBase bool m_imperial_units{ false }; bool suppress_update_clipper_on_render{false}; + Matrix3d m_rotation_matrix; + Vec3d m_rotations{ Vec3d::Zero() }; + enum CutMode { cutPlanar , cutByLine @@ -117,9 +105,12 @@ public: bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void shift_cut_z(double delta); + void rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center); void update_clipper(); void update_clipper_on_render(); + BoundingBoxf3 bounding_box() const; + protected: bool on_init() override; void on_load(cereal::BinaryInputArchive& ar) override { ar(m_keep_upper, m_keep_lower, m_rotate_lower); } @@ -135,10 +126,7 @@ protected: void on_stop_dragging() override; void on_update(const UpdateData& data) override; void on_render() override; - void on_render_for_picking() override { - m_rotation_gizmo.render_for_picking(); - m_move_gizmo.render_for_picking(); - } + void on_render_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; @@ -149,12 +137,15 @@ private: void render_move_center_input(int axis); void render_rotation_input(int axis); void render_connect_mode_radio_button(ConnectorMode mode); - bool render_revert_button(const wxString& label); + bool render_revert_button(const std::string& label); void render_connect_type_radio_button(ConnectorType type); bool can_perform_cut() const; void render_cut_plane(); + void render_cut_center_graber(); void perform_cut(const Selection& selection); + void set_center_pos(const Vec3d& center_pos); + bool update_bb(); }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index e12974b75..095234e02 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -78,9 +78,6 @@ void GLGizmoMove3D::on_update(const UpdateData& data) m_displacement.y() = calc_projection(data); else if (m_hover_id == 2) m_displacement.z() = calc_projection(data); - - if (m_has_forced_center) - m_center += m_displacement; } void GLGizmoMove3D::on_render() @@ -94,15 +91,8 @@ void GLGizmoMove3D::on_render() glsafe(::glEnable(GL_DEPTH_TEST)); const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = m_has_forced_center ? m_center : box.center(); + const Vec3d& center = box.center(); - if (m_has_forced_center) - for (auto axis : { X, Y, Z }) { - m_grabbers[axis].center = center; - m_grabbers[axis].center[axis] += 0.5*fabs(box.max[axis] - box.min[axis]); - m_grabbers[axis].color = AXES_COLOR[axis]; - } - else { // x axis m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; m_grabbers[0].color = AXES_COLOR[0]; @@ -114,7 +104,6 @@ void GLGizmoMove3D::on_render() // z axis m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset }; m_grabbers[2].color = AXES_COLOR[2]; - } glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); @@ -227,10 +216,7 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const { double projection = 0.0; - const Vec3d& starting_drag_position = m_has_forced_center ? m_grabbers[m_hover_id].center : m_starting_drag_position; - const Vec3d& starting_box_center = m_has_forced_center ? m_center : m_starting_box_center; - - Vec3d starting_vec = starting_drag_position - starting_box_center; + Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); @@ -238,9 +224,9 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; // vector from the starting position to the found intersection - Vec3d inters_vec = inters - starting_drag_position; + Vec3d inters_vec = inters - m_starting_drag_position; // finds projection of the vector along the staring direction projection = inters_vec.dot(starting_vec.normalized()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 9e404a95c..2a75df866 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -17,9 +17,6 @@ class GLGizmoMove3D : public GLGizmoBase Vec3d m_starting_box_center{ Vec3d::Zero() }; Vec3d m_starting_box_bottom_center{ Vec3d::Zero() }; - Vec3d m_center{ Vec3d::Zero() }; - bool m_has_forced_center{ false }; - GLModel m_cone; #if ENABLE_GLBEGIN_GLEND_REMOVAL struct GrabberConnection @@ -41,9 +38,6 @@ public: std::string get_tooltip() const override; - void set_center(Vec3d center) { m_center = center; m_has_forced_center = true; } - const Vec3d& get_center() const { return m_center; } - protected: virtual bool on_init() override; virtual std::string on_get_name() const override; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 5e154e48a..9a2e5d266 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -71,7 +71,6 @@ static const std::map font_icons = { {ImGui::LegendToolMarker , "legend_toolmarker" }, #endif // ENABLE_LEGEND_TOOLBAR_ICONS {ImGui::RevertButton , "undo" }, - {ImGui::RevertButton2 , "undo" }, }; static const std::map font_icons_large = { From 5d83781780154fcd312502fd6bd2bed40e229d0c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 28 Feb 2022 16:56:10 +0100 Subject: [PATCH 12/97] Fixes after merge with master --- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 18 +++-- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 101 ++++++++---------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 3 +- 5 files changed, 51 insertions(+), 79 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 82702f212..d503c024e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -84,8 +84,8 @@ void GLGizmoBase::set_hover_id(int id) assert(!m_dragging); // allow empty grabbers when not using grabbers but use hover_id - flatten, rotate - if (!m_grabbers.empty() && id >= (int) m_grabbers.size()) - return; +// if (!m_grabbers.empty() && id >= (int) m_grabbers.size()) +// return; m_hover_id = id; on_set_hover_id(); @@ -160,14 +160,20 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { if (mouse_event.LeftDown()) { Selection &selection = m_parent.get_selection(); - if (!selection.is_empty() && m_hover_id != -1 && - (m_grabbers.empty() || m_hover_id < static_cast(m_grabbers.size()))) { + if (!selection.is_empty() && m_hover_id != -1 /*&& + (m_grabbers.empty() || m_hover_id < static_cast(m_grabbers.size()))*/) { selection.setup_cache(); m_dragging = true; for (auto &grabber : m_grabbers) grabber.dragging = false; - if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) - m_grabbers[m_hover_id].dragging = true; + //if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) + // m_grabbers[m_hover_id].dragging = true; + if (!m_grabbers.empty()) { + if (m_hover_id < int(m_grabbers.size())) + m_grabbers[m_hover_id].dragging = true; + else if (m_group_id >= 0 && m_hover_id >= m_group_id) + m_grabbers[m_hover_id - m_group_id].dragging = true; + } // prevent change of hover_id during dragging m_parent.set_mouse_as_dragging(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 08c54e2d2..3dd37ad39 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -31,7 +31,8 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, * Eigen::AngleAxisd(0.0, Vec3d::UnitY()) * Eigen::AngleAxisd(0.0, Vec3d::UnitX())) { - set_group_id(3); + m_rotation_gizmo.use_only_grabbers(); + m_group_id = 3; m_modes = { _u8L("Planar"), _u8L("By Line"),_u8L("Grid") // , _u8L("Radial"), _u8L("Modular") @@ -56,7 +57,7 @@ std::string GLGizmoCut3D::get_tooltip() const std::string tooltip = m_rotation_gizmo.get_tooltip(); if (tooltip.empty()) { double koef = wxGetApp().app_config->get("use_inches") == "1" ? ObjectManipulation::mm_to_in : 1.0; - if (m_hover_id == get_group_id() || m_grabbers[0].dragging) + if (m_hover_id == m_group_id || m_grabbers[0].dragging) return "X: " + format(m_plane_center.x() * koef, 2) + "; " +//"\n" + "Y: " + format(m_plane_center.y() * koef, 2) + "; " +//"\n" + "Z: " + format(m_plane_center.z() * koef, 2); @@ -65,8 +66,12 @@ std::string GLGizmoCut3D::get_tooltip() const return tooltip; } -bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event) +bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) { + if (m_rotation_gizmo.on_mouse(mouse_event)) { + update_clipper(); + return true; + } return use_grabbers(mouse_event); } @@ -288,13 +293,6 @@ void GLGizmoCut3D::render_cut_plane() if (shader == nullptr) return; shader->start_using(); - Vec3d diff = plane_center - m_old_center; - // Z changed when move with cut plane - // X and Y changed when move with cutted object - bool is_changed = std::abs(diff.x()) > EPSILON || - std::abs(diff.y()) > EPSILON || - std::abs(diff.z()) > EPSILON; - m_old_center = plane_center; const Vec3d& angles = m_rotation_gizmo.get_rotation(); @@ -360,7 +358,7 @@ void GLGizmoCut3D::render_cut_center_graber() glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glLineWidth(m_hover_id == get_group_id() ? 2.0f : 1.5f)); + glsafe(::glLineWidth(m_hover_id == m_group_id ? 2.0f : 1.5f)); GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader != nullptr) { @@ -395,7 +393,7 @@ void GLGizmoCut3D::render_cut_center_graber() shader->set_uniform("emission_factor", 0.1f); m_grabbers[0].color = GRABBER_COLOR; - m_grabbers[0].render(m_hover_id == get_group_id(), float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); + m_grabbers[0].render(m_hover_id == m_group_id, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); shader->stop_using(); } @@ -429,21 +427,7 @@ void GLGizmoCut3D::on_set_state() void GLGizmoCut3D::on_set_hover_id() { - m_rotation_gizmo.set_hover_id(m_hover_id < get_group_id() ? m_hover_id: -1); -} - -void GLGizmoCut3D::on_enable_grabber(unsigned int id) -{ - m_rotation_gizmo.enable_grabber(id); - if (id == get_group_id()) - m_grabbers[0].enabled = true; -} - -void GLGizmoCut3D::on_disable_grabber(unsigned int id) -{ - m_rotation_gizmo.disable_grabber(id); - if (id == get_group_id()) - m_grabbers[0].enabled = false; + m_rotation_gizmo.set_hover_id(m_hover_id < m_group_id ? m_hover_id: -1); } bool GLGizmoCut3D::on_is_activable() const @@ -453,48 +437,33 @@ bool GLGizmoCut3D::on_is_activable() const return m_parent.get_selection().is_single_full_instance(); } -void GLGizmoCut3D::on_start_dragging() +void GLGizmoCut3D::on_dragging(const UpdateData& data) { - m_rotation_gizmo.start_dragging(); -} + assert(m_hover_id == m_group_id); -void GLGizmoCut3D::on_stop_dragging() -{ - m_rotation_gizmo.stop_dragging(); -} + const Vec3d & starting_box_center = m_plane_center; + const Vec3d & starting_drag_position = m_grabbers[0].center; + double projection = 0.0; -void GLGizmoCut3D::on_update(const UpdateData& data) -{ - if (m_hover_id == get_group_id()) { - const Vec3d& starting_box_center = m_plane_center; - const Vec3d& starting_drag_position = m_grabbers[0].center; + Vec3d starting_vec = starting_drag_position - starting_box_center; + if (starting_vec.norm() != 0.0) { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - starting_drag_position; - double projection = 0.0; - - Vec3d starting_vec = starting_drag_position - starting_box_center; - if (starting_vec.norm() != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - starting_drag_position; - - starting_vec.normalize(); - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec); - } - if (wxGetKeyState(WXK_SHIFT)) - projection = m_snap_step * (double)std::round(projection / m_snap_step); - - set_center(starting_box_center + starting_vec * projection); - } - else { - m_rotation_gizmo.update(data); - update_clipper(); + starting_vec.normalize(); + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec); } + if (wxGetKeyState(WXK_SHIFT)) + projection = m_snap_step * (double)std::round(projection / m_snap_step); + + set_center(starting_box_center + starting_vec * projection); } void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos) @@ -547,7 +516,7 @@ void GLGizmoCut3D::on_render() render_cut_plane(); render_cut_center_graber(); if (m_mode == CutMode::cutPlanar) { - if (m_hover_id < get_group_id()) + if (m_hover_id < m_group_id) m_rotation_gizmo.render(); } @@ -606,7 +575,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) revert_rotation = render_revert_button("rotation"); for (Axis axis : {X, Y, Z}) render_rotation_input(axis); - m_imgui->text(_L("")); + m_imgui->text(_L("°")); } else { ImGui::AlignTextToFramePadding(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 4da400305..995d1f82d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -127,12 +127,8 @@ protected: void on_set_state() override; CommonGizmosDataID on_get_requirements() const override; void on_set_hover_id() override; - void on_enable_grabber(unsigned int id) override; - void on_disable_grabber(unsigned int id) override; bool on_is_activable() const override; - void on_start_dragging() override; - void on_stop_dragging() override; - void on_update(const UpdateData& data) override; + void on_dragging(const UpdateData& data) override; void on_render() override; void on_render_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 31f1a24c0..1402c2e45 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -679,7 +679,7 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { - if (mouse_event.Dragging() && m_dragging) { + if (mouse_event.Dragging() && m_dragging && !m_use_only_grabbers) { // Apply new temporary rotations TransformationType transformation_type( TransformationType::World_Relative_Joint); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 171f61ab2..125fa0730 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -63,7 +63,6 @@ public: double get_angle() const { return m_angle; } void set_angle(double angle); - void set_center_z(double center_z); void set_center(const Vec3d& center); std::string get_tooltip() const override; @@ -118,6 +117,7 @@ private: class GLGizmoRotate3D : public GLGizmoBase { std::array m_gizmos; + bool m_use_only_grabbers{ false }; public: GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -125,6 +125,7 @@ public: Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } void set_center(const Vec3d& center) { m_gizmos[X].set_center(center); m_gizmos[Y].set_center(center); m_gizmos[Z].set_center(center); } + void use_only_grabbers() { m_use_only_grabbers = true; } std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); From 0fba32fa5327e6ef7a3708e87e0ebb82cebbf56a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Mar 2022 14:10:25 +0100 Subject: [PATCH 13/97] Cut: Add connectors. WIP --- src/libslic3r/Model.hpp | 43 +++ src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 368 ++++++++++++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 21 +- 4 files changed, 375 insertions(+), 59 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3d933c470..2e96910f4 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -219,6 +219,46 @@ private: friend class ModelObject; }; +struct CutConnector +{ + Vec3f pos; + Vec3f normal; + float radius; + float height; + bool failed = false; + + CutConnector() + : pos(Vec3f::Zero()), normal(Vec3f::UnitZ()), radius(5.f), height(10.f) + {} + + CutConnector(Vec3f p, Vec3f n, float r, float h, bool fl = false) + : pos(p), normal(n), radius(r), height(h), failed(fl) + {} + + CutConnector(const CutConnector& rhs) : + CutConnector(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {} + + bool operator==(const CutConnector& sp) const; + + bool operator!=(const CutConnector& sp) const { return !(sp == (*this)); } +/* + bool is_inside(const Vec3f& pt) const; + + bool get_intersections(const Vec3f& s, const Vec3f& dir, + std::array, 2>& out) const; + + indexed_triangle_set to_mesh() const; +*/ + template inline void serialize(Archive& ar) + { + ar(pos, normal, radius, height, failed); + } + + static constexpr size_t steps = 32; +}; + +using CutConnectors = std::vector; + // Declared outside of ModelVolume, so it could be forward declared. enum class ModelVolumeType : int { INVALID = -1, @@ -269,6 +309,9 @@ public: // Holes to be drilled into the object so resin can flow out sla::DrainHoles sla_drain_holes; + // Connectors to be added into the object after cut + CutConnectors cut_connectors; + /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index d503c024e..b188d60af 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -171,7 +171,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { if (!m_grabbers.empty()) { if (m_hover_id < int(m_grabbers.size())) m_grabbers[m_hover_id].dragging = true; - else if (m_group_id >= 0 && m_hover_id >= m_group_id) + else if (m_group_id >= 0 && m_hover_id < int(m_grabbers.size() + m_group_id)) m_grabbers[m_hover_id - m_group_id].dragging = true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3dd37ad39..90084b066 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -33,6 +33,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, { m_rotation_gizmo.use_only_grabbers(); m_group_id = 3; + m_connectors_group_id = 4; m_modes = { _u8L("Planar"), _u8L("By Line"),_u8L("Grid") // , _u8L("Radial"), _u8L("Modular") @@ -50,6 +51,8 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, }; m_axis_names = { "X", "Y", "Z" }; + + update_connector_shape(); } std::string GLGizmoCut3D::get_tooltip() const @@ -68,11 +71,71 @@ std::string GLGizmoCut3D::get_tooltip() const bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) { + if (mouse_event.Moving()) + return false; + if (m_rotation_gizmo.on_mouse(mouse_event)) { update_clipper(); return true; } - return use_grabbers(mouse_event); + + if (use_grabbers(mouse_event)) + return true; + + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + static bool pending_right_up = false; + if (mouse_event.LeftDown()) { + bool grabber_contains_mouse = (get_hover_id() != -1); + bool control_down = mouse_event.CmdDown(); + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + return true; + } + else if (mouse_event.Dragging()) { + bool control_down = mouse_event.CmdDown(); + if (m_parent.get_move_volume_id() != -1) { + // don't allow dragging objects with the Sla gizmo on + return true; + } + else if (!control_down && + gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } + else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) { + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) + pending_right_up = false; + } + } + else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) { + // in case SLA/FDM gizmo is selected, we just pass the LeftUp event + // and stop processing - neither object moving or selecting is + // suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + return true; + } + else if (mouse_event.RightDown()) { + if (m_parent.get_selection().get_object_idx() != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { + // we need to set the following right up as processed to avoid showing + // the context menu if the user release the mouse over the object + pending_right_up = true; + // event was taken care of by the SlaSupports gizmo + return true; + } + } + else if (pending_right_up && mouse_event.RightUp()) { + pending_right_up = false; + return true; + } + return false; } void GLGizmoCut3D::shift_cut_z(double delta) @@ -128,7 +191,7 @@ void GLGizmoCut3D::set_center(const Vec3d& center) update_clipper(); } -void GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) +bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx) { ImGui::AlignTextToFramePadding(); m_imgui->text(label); @@ -163,10 +226,13 @@ void GLGizmoCut3D::render_combo(const std::string& label, const std::vectortext(label); @@ -176,12 +242,14 @@ void GLGizmoCut3D::render_double_input(const std::string& label, double& value_i double value = value_in; if (m_imperial_units) value *= ObjectManipulation::mm_to_in; + double old_val = value; 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")); value_in = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); + return old_val != value; } void GLGizmoCut3D::render_move_center_input(int axis) @@ -271,11 +339,6 @@ bool GLGizmoCut3D::render_revert_button(const std::string& label_id) void GLGizmoCut3D::render_cut_plane() { - m_c->object_clipper()->render_cut(); - - if (m_hide_cut_plane) - return; - const BoundingBoxf3 box = bounding_box(); const float min_x = box.min.x() - Margin - m_plane_center.x(); @@ -349,7 +412,7 @@ void GLGizmoCut3D::render_cut_center_graber() const BoundingBoxf3 box = bounding_box(); Vec3d grabber_center = m_plane_center; - grabber_center[Z] += 10; // Margin + grabber_center[Z] += float(box.radius()/2.0); // Margin rotate_vec3d_around_center(grabber_center, angles, m_plane_center); @@ -439,31 +502,50 @@ bool GLGizmoCut3D::on_is_activable() const void GLGizmoCut3D::on_dragging(const UpdateData& data) { - assert(m_hover_id == m_group_id); + if (m_hover_id < m_group_id) + return; - const Vec3d & starting_box_center = m_plane_center; - const Vec3d & starting_drag_position = m_grabbers[0].center; - double projection = 0.0; + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - Vec3d starting_vec = starting_drag_position - starting_box_center; - if (starting_vec.norm() != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - starting_drag_position; + if (m_hover_id == m_group_id) { + const Vec3d& starting_box_center = m_plane_center; + const Vec3d& starting_drag_position = m_grabbers[0].center; + double projection = 0.0; - starting_vec.normalize(); - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec); + Vec3d starting_vec = starting_drag_position - starting_box_center; + if (starting_vec.norm() != 0.0) { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - starting_drag_position; + + starting_vec.normalize(); + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec); + } + if (wxGetKeyState(WXK_SHIFT)) + projection = m_snap_step * (double)std::round(projection / m_snap_step); + + // move cut plane center + set_center(starting_box_center + starting_vec * projection); + + // move connectors + Vec3f shift = Vec3f(starting_vec.cast() * projection); + for (auto& connector : connectors) + connector.pos += shift; + } + else if (m_hover_id > m_group_id) + { + std::pair pos_and_normal; + if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal)) + return; + connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; + connectors[m_hover_id - m_connectors_group_id].normal = -pos_and_normal.second; } - if (wxGetKeyState(WXK_SHIFT)) - projection = m_snap_step * (double)std::round(projection / m_snap_step); - - set_center(starting_box_center + starting_vec * projection); } void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos) @@ -474,7 +556,7 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos) // Clamp the center position of the cut plane to the object's bounding box //m_plane_center = Vec3d(std::clamp(center_pos.x(), m_min_pos.x(), m_max_pos.x()), // std::clamp(center_pos.y(), m_min_pos.y(), m_max_pos.y()), - // std::clamp(center_pos.z(), m_min_pos.z(), m_max_pos.z()))); + // std::clamp(center_pos.z(), m_min_pos.z(), m_max_pos.z())); m_center_offset = m_plane_center - m_bb_center; } @@ -513,11 +595,17 @@ void GLGizmoCut3D::on_render() update_clipper_on_render(); } - render_cut_plane(); - render_cut_center_graber(); - if (m_mode == CutMode::cutPlanar) { - if (m_hover_id < m_group_id) - m_rotation_gizmo.render(); + render_connectors(false); + + m_c->object_clipper()->render_cut(); + + if (!m_hide_cut_plane) { + render_cut_plane(); + render_cut_center_graber(); + if (m_mode == CutMode::cutPlanar) { + if (m_hover_id < m_group_id) + m_rotation_gizmo.render(); + } } if (!suppress_update_clipper_on_render) @@ -528,6 +616,8 @@ void GLGizmoCut3D::on_render_for_picking() { m_rotation_gizmo.render_for_picking(); render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); + + render_connectors(true); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) @@ -617,11 +707,23 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) render_connect_type_radio_button(ConnectorType::Plug); render_connect_type_radio_button(ConnectorType::Dowel); - render_combo(_u8L("Style"), m_connector_styles, m_connector_style); - render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape); + if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) + update_connector_shape(); + if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) + update_connector_shape(); - render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio); - render_double_input(_u8L("Size"), m_connector_size); + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + if (render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio)) + for (auto& connector : connectors) + connector.height = float(m_connector_depth_ratio); + if (render_double_input(_u8L("Size"), m_connector_size)) + for (auto& connector : connectors) + connector.radius = float(m_connector_size * 0.5); + + m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || !can_perform_cut()); + if (m_imgui->button(_L("Reset connectors"))) + reset_connectors(); + m_imgui->disabled_end(); m_imgui->disabled_end(); @@ -633,7 +735,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); - m_imgui->checkbox("hide_cut_plane", m_hide_cut_plane); + m_imgui->checkbox(_L("Hide cut plane and grabbers"), m_hide_cut_plane); //////// static bool hide_clipped = true; @@ -658,6 +760,87 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) } } +void GLGizmoCut3D::render_connectors(bool picking) +{ + const Selection& selection = m_parent.get_selection(); + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); +#else + GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); + if (shader) + shader->start_using(); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + //const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + //const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); + + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); +// glsafe(::glMultMatrixd(instance_matrix.data())); + + ColorRGBA render_color; + const CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + size_t cache_size = connectors.size(); + + for (size_t i = 0; i < cache_size; ++i) { + const CutConnector& connector = connectors[i]; + const bool& point_selected = m_selected[i]; + + // First decide about the color of the point. + if (picking) + render_color = picking_decode(BASE_ID - i - m_connectors_group_id); + else { + if (size_t(m_hover_id- m_connectors_group_id) == i) + render_color = ColorRGBA::CYAN(); + else // neither hover nor picking + render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + } + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_connector_shape.set_color(render_color); +#else + const_cast(&m_connector_shape)->set_color(-1, render_color); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glPushMatrix()); +// glsafe(::glTranslatef(connector.pos.x() - m_plane_center.x(), connector.pos.y() - m_plane_center.y(), connector.pos.z() - m_plane_center.z())); + glsafe(::glTranslatef(connector.pos.x(), connector.pos.y(), connector.pos.z())); +// glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + + if (vol->is_left_handed()) + glFrontFace(GL_CW); + + const Vec3d& angles = m_rotation_gizmo.get_rotation(); + glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); + glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); + glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); + + // Matrices set, we can render the point mark now. + /* Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-connector.normal).cast()); + Eigen::AngleAxisd aa(q); + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); +*/ glsafe(::glTranslated(0., 0., -0.5*connector.height)); + glsafe(::glScaled(connector.radius, connector.radius, connector.height)); + m_connector_shape.render(); + + if (vol->is_left_handed()) + glFrontFace(GL_CCW); + glsafe(::glPopMatrix()); + } + + glsafe(::glPopMatrix()); +} + bool GLGizmoCut3D::can_perform_cut() const { BoundingBoxf3 box = bounding_box(); @@ -684,46 +867,125 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); - if (0.0 < object_cut_z && can_perform_cut()) + if (0.0 < object_cut_z && can_perform_cut()) { wxGetApp().plater()->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); + m_selected.clear(); + } else { // the object is SLA-elevated and the plane is under it. } } -bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) + + +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal) { - if (is_dragging()) + if (!m_c->raycaster()->raycaster()) return false; - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[m_c->selection_info()->get_active_instance()]; - const Transform3d instance_trafo = mi->get_transformation().get_matrix(); + + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); const Camera& camera = wxGetApp().plater()->get_camera(); int mesh_id = -1; - for (const ModelVolume *mv : mo->volumes) { + for (const ModelVolume* mv : mo->volumes) { ++mesh_id; - if (! mv->is_model_part()) + if (!mv->is_model_part()) continue; Vec3f hit; Vec3f normal; bool clipping_plane_was_hit = false; m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * mv->get_matrix(), - camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), - nullptr, &clipping_plane_was_hit); + camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), + nullptr, &clipping_plane_was_hit); if (clipping_plane_was_hit) { - // The clipping plane was clicked, hit containts coordinates of the hit in world coords. - std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); return true; } } return false; } +void GLGizmoCut3D::reset_connectors() +{ + m_c->selection_info()->model_object()->cut_connectors.clear(); + m_selected.clear(); +} + +void GLGizmoCut3D::update_connector_shape() +{ + if (m_connector_shape.is_initialized()) + m_connector_shape.reset(); + + bool is_prizm = m_connector_style == size_t(Prizm); + const std::function& its_make_shape = is_prizm ? its_make_cylinder : its_make_cone; + + + switch (ConnectorShape(m_connector_shape_id)) { + case Triangle: + m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 3))); + break; + case Square: + m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 4))); + break; + case Circle: + m_connector_shape.init_from(its_make_shape(1.0, 1.0, 2 * PI / 360)); + break; + case Hexagon: + m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 6))); + break; + } +} + +bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (is_dragging() || action != SLAGizmoEventType::LeftDown) + return false; + + ModelObject *mo = m_c->selection_info()->model_object(); + const Camera& camera = wxGetApp().plater()->get_camera(); + + int mesh_id = -1; + + // left down without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftDown && /*!m_selection_rectangle.is_dragging() && */!shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id != -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + std::pair pos_and_normal; + if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal)) { + const Vec3f& hit = pos_and_normal.first; + const Vec3f& normal = pos_and_normal.second; + // The clipping plane was clicked, hit containts coordinates of the hit in world coords. + std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add pin")); + + mo->cut_connectors.emplace_back(hit, -normal, float(m_connector_size * 0.5), float(m_connector_depth_ratio)); + m_selected.push_back(false); + assert(m_selected.size() == mo->cut_connectors.size()); + m_parent.set_as_dirty(); + m_wait_for_up_event = true; + + return true; + } + return false; + } + return true; + } + return false; +} + CommonGizmosDataID GLGizmoCut3D::on_get_requirements() const { return CommonGizmosDataID( int(CommonGizmosDataID::SelectionInfo) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 995d1f82d..e116a6df1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -18,6 +18,7 @@ class GLGizmoCut3D : public GLGizmoBase { GLGizmoRotate3D m_rotation_gizmo; double m_snap_step{ 1.0 }; + int m_connectors_group_id; Vec3d m_plane_center{ Vec3d::Zero() }; // data to check position of the cut palne center on gizmo activation @@ -26,6 +27,7 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_bb_center{ Vec3d::Zero() }; Vec3d m_center_offset{ Vec3d::Zero() }; + GLModel m_connector_shape; #if ENABLE_GLBEGIN_GLEND_REMOVAL GLModel m_plane; @@ -39,14 +41,18 @@ class GLGizmoCut3D : public GLGizmoBase bool m_hide_cut_plane{ false }; - double m_connector_depth_ratio{ 1.5 }; - double m_connector_size{ 5.0 }; + double m_connector_depth_ratio{ 5.0 }; + double m_connector_size{ 2.0 }; float m_label_width{ 150.0 }; float m_control_width{ 200.0 }; bool m_imperial_units{ false }; bool suppress_update_clipper_on_render{false}; + mutable std::vector m_selected; // which pins are currently selected + bool m_selection_empty = true; + bool m_wait_for_up_event = false; + Matrix3d m_rotation_matrix; Vec3d m_rotations{ Vec3d::Zero() }; @@ -95,7 +101,7 @@ class GLGizmoCut3D : public GLGizmoBase size_t m_connector_style{ size_t(Prizm) }; std::vector m_connector_shapes; - size_t m_connector_shape{ size_t(Hexagon) }; + size_t m_connector_shape_id{ size_t(Hexagon) }; std::vector m_axis_names; @@ -103,6 +109,7 @@ public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; + bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair& pos_and_normal); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); /// @@ -136,13 +143,15 @@ protected: private: void set_center(const Vec3d& center); - void render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); - void render_double_input(const std::string& label, double& value_in); + bool render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); + bool render_double_input(const std::string& label, double& value_in); void render_move_center_input(int axis); void render_rotation_input(int axis); void render_connect_mode_radio_button(ConnectorMode mode); bool render_revert_button(const std::string& label); void render_connect_type_radio_button(ConnectorType type); + void render_connectors(bool picking); + bool can_perform_cut() const; void render_cut_plane(); @@ -150,6 +159,8 @@ private: void perform_cut(const Selection& selection); void set_center_pos(const Vec3d& center_pos); bool update_bb(); + void reset_connectors(); + void update_connector_shape(); }; } // namespace GUI From 01aa99f67fc0c11f31879b3d48b2e7e242e798b1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 14 Mar 2022 14:09:10 +0100 Subject: [PATCH 14/97] After merge fixes --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 147 +++++++++++++++------------ src/slic3r/GUI/MeshUtils.cpp | 41 +++++--- 2 files changed, 111 insertions(+), 77 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5ba119685..0e3dd3067 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -360,34 +360,33 @@ void GLGizmoCut3D::render_cut_plane() if (shader == nullptr) return; shader->start_using(); -/* const Vec3d diff = plane_center - m_old_center; - // Z changed when move with cut plane - // X and Y changed when move with cutted object - bool is_changed = std::abs(diff.x()) > EPSILON || - std::abs(diff.y()) > EPSILON || - std::abs(diff.z()) > EPSILON; - m_old_center = plane_center; -*/ +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( + m_plane_center, + m_rotation_gizmo.get_rotation(), + Vec3d::Ones(), + Vec3d::Ones() + ); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else const Vec3d& angles = m_rotation_gizmo.get_rotation(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - glsafe(::glPushMatrix()); glsafe(::glTranslated(m_plane_center.x(), m_plane_center.y(), m_plane_center.z())); glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (!m_plane.is_initialized()) { m_plane.reset(); - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); // vertices init_data.add_vertex(Vec3f(min_x, min_y, 0.0)); @@ -395,14 +394,17 @@ void GLGizmoCut3D::render_cut_plane() init_data.add_vertex(Vec3f(max_x, max_y, 0.0)); init_data.add_vertex(Vec3f(min_x, max_y, 0.0)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_plane.init_from(std::move(init_data)); + } m_plane.render(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif //!ENABLE_GL_SHADERS_ATTRIBUTES #else // Draw the cutting plane ::glBegin(GL_QUADS); @@ -437,33 +439,41 @@ void GLGizmoCut3D::render_cut_center_graber() glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glLineWidth(m_hover_id == m_group_id ? 2.0f : 1.5f)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader != nullptr) { shader->start_using(); - // if (!m_grabber_connection.is_initialized() || z_changed) - { - m_grabber_connection.reset(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_grabber_connection.reset(); - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = ColorRGBA::YELLOW(); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); - // vertices - init_data.add_vertex((Vec3f)m_plane_center.cast()); - init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); + // vertices + init_data.add_vertex((Vec3f)m_plane_center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); - // indices - init_data.add_line(0, 1); + // indices + init_data.add_line(0, 1); + + m_grabber_connection.init_from(std::move(init_data)); - m_grabber_connection.init_from(std::move(init_data)); - } m_grabber_connection.render(); shader->stop_using(); } +#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES shader = wxGetApp().get_shader("gouraud_light_attr"); #else @@ -565,7 +575,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) for (auto& connector : connectors) connector.pos += shift; } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL + else if (m_hover_id > m_group_id) { std::pair pos_and_normal; @@ -693,7 +703,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) revert_rotation = render_revert_button("rotation"); for (Axis axis : {X, Y, Z}) render_rotation_input(axis); - m_imgui->text(_L("")); + m_imgui->text(_L("°")); } else { ImGui::AlignTextToFramePadding(); @@ -792,27 +802,31 @@ void GLGizmoCut3D::render_connectors(bool picking) { const Selection& selection = m_parent.get_selection(); -#if ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat_attr") : wxGetApp().get_shader("gouraud_light_attr"); +#else GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); #else GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); if (shader) shader->start_using(); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - //const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); - //const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); +#else glsafe(::glPushMatrix()); glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); -// glsafe(::glMultMatrixd(instance_matrix.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES ColorRGBA render_color; const CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; @@ -832,41 +846,44 @@ void GLGizmoCut3D::render_connectors(bool picking) render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); } -#if ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_LEGACY_OPENGL_REMOVAL m_connector_shape.set_color(render_color); #else const_cast(&m_connector_shape)->set_color(-1, render_color); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( + Vec3d(connector.pos.x(), connector.pos.y(), connector.pos.z() - 0.5 * connector.height), + m_rotation_gizmo.get_rotation(), + Vec3d(connector.radius, connector.radius, connector.height), + Vec3d::Ones() + ); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else glsafe(::glPushMatrix()); -// glsafe(::glTranslatef(connector.pos.x() - m_plane_center.x(), connector.pos.y() - m_plane_center.y(), connector.pos.z() - m_plane_center.z())); glsafe(::glTranslatef(connector.pos.x(), connector.pos.y(), connector.pos.z())); -// glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); - - if (vol->is_left_handed()) - glFrontFace(GL_CW); const Vec3d& angles = m_rotation_gizmo.get_rotation(); glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); - // Matrices set, we can render the point mark now. - /* Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-connector.normal).cast()); - Eigen::AngleAxisd aa(q); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); -*/ glsafe(::glTranslated(0., 0., -0.5*connector.height)); + glsafe(::glTranslated(0., 0., -0.5*connector.height)); glsafe(::glScaled(connector.radius, connector.radius, connector.height)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_connector_shape.render(); - if (vol->is_left_handed()) - glFrontFace(GL_CCW); +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif //!ENABLE_GL_SHADERS_ATTRIBUTES } +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif //!ENABLE_GL_SHADERS_ATTRIBUTES } bool GLGizmoCut3D::can_perform_cut() const @@ -916,10 +933,12 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair if (!m_c->raycaster()->raycaster()) return false; + const float sla_shift = m_c->selection_info()->get_sla_shift(); + const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; - const Transform3d instance_trafo = mi->get_transformation().get_matrix(); - const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); + const Transform3d instance_trafo = sla_shift > 0.0 ? + Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift), Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones()) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); int mesh_id = -1; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 604339bf0..e5cd1f79e 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -119,22 +119,32 @@ void MeshClipper::render_cut() } -#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_LEGACY_OPENGL_REMOVAL void MeshClipper::render_contour(const ColorRGBA& color) #else void MeshClipper::render_contour() -#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL { if (! m_triangles_valid) recalculate_triangles(); -#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_LEGACY_OPENGL_REMOVAL GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); if (curr_shader != nullptr) curr_shader->stop_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (shader != nullptr) { shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES m_model_expanded.set_color(color); m_model_expanded.render(); shader->stop_using(); @@ -145,7 +155,7 @@ void MeshClipper::render_contour() #else if (m_vertex_array_expanded.has_VBOs()) m_vertex_array_expanded.render(); -#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL } @@ -232,13 +242,17 @@ void MeshClipper::recalculate_triangles() tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting + std::vector triangles2d = m_fill_cut + ? triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.) + : std::vector(); + #if ENABLE_LEGACY_OPENGL_REMOVAL m_model.reset(); GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(m_triangles2d.size()); - init_data.reserve_indices(m_triangles2d.size()); + init_data.reserve_vertices(triangles2d.size()); + init_data.reserve_indices(triangles2d.size()); // vertices + indices for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { @@ -272,11 +286,11 @@ void MeshClipper::recalculate_triangles() } -#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_LEGACY_OPENGL_REMOVAL m_model_expanded.reset(); init_data = GLModel::Geometry(); - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(triangles2d.size()) }; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; init_data.reserve_vertices(triangles2d.size()); init_data.reserve_indices(triangles2d.size()); @@ -286,10 +300,11 @@ void MeshClipper::recalculate_triangles() init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); const size_t idx = it - triangles2d.cbegin(); - if (init_data.format.index_type == GLModel::Geometry::EIndexType::USHORT) - init_data.add_ushort_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); - else - init_data.add_uint_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + init_data.add_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); + //if (init_data./*format.*/index_type == GLModel::Geometry::EIndexType::USHORT) + // init_data.add_ushort_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); + //else + // init_data.add_uint_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); } if (!init_data.is_empty()) @@ -304,7 +319,7 @@ void MeshClipper::recalculate_triangles() m_vertex_array_expanded.push_triangle(idx, idx+1, idx+2); } m_vertex_array_expanded.finalize_geometry(true); -#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL From b204f05809f929d46cf5bb4f85cb94cc6a75f7f4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 14 Mar 2022 16:54:50 +0100 Subject: [PATCH 15/97] Cut: ObjectList: Show info about added cut connectors. + Some code refactoring: Put CutConnectorsType, CutConnectorsStyle and CutConnectorsShape to the Model.hpp. --- src/libslic3r/Model.hpp | 23 ++++++++++- src/slic3r/GUI/GUI_ObjectList.cpp | 16 +++++++- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 56 ++++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 41 ++++++------------- src/slic3r/GUI/ObjectDataViewModel.cpp | 1 + src/slic3r/GUI/ObjectDataViewModel.hpp | 1 + 6 files changed, 89 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2e96910f4..c0e7e907d 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -257,8 +257,6 @@ struct CutConnector static constexpr size_t steps = 32; }; -using CutConnectors = std::vector; - // Declared outside of ModelVolume, so it could be forward declared. enum class ModelVolumeType : int { INVALID = -1, @@ -269,6 +267,27 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER, }; +using CutConnectors = std::vector; + +enum class CutConnectorType : int { + Plug + , Dowel +}; + +enum class CutConnectorStyle : int { + Prizm + , Frustrum + //,Claw +}; + +enum class CutConnectorShape : int { + Triangle + , Square + , Hexagon + , Circle + //,D-shape +}; + enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; using ModelObjectCutAttributes = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 27c374b3f..7462e8153 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1841,6 +1841,12 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) mv->seam_facets.reset(); break; + case InfoItemType::Cut: + cnv->get_gizmos_manager().reset_all_states(); + Plater::TakeSnapshot(plater, _L("Remove cut connectors")); + (*m_objects)[obj_idx]->cut_connectors.clear(); + break; + case InfoItemType::MmuSegmentation: cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); @@ -2464,10 +2470,12 @@ void ObjectList::part_selection_changed() } case InfoItemType::CustomSupports: case InfoItemType::CustomSeam: + case InfoItemType::Cut: case InfoItemType::MmuSegmentation: { - GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports : - info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam : + GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports : + info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam : + info_type == InfoItemType::Cut ? GLGizmosManager::EType::Cut : GLGizmosManager::EType::MmuSegmentation; GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); if (gizmos_mgr.get_current_type() != gizmo_type) @@ -2604,6 +2612,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio for (InfoItemType type : {InfoItemType::CustomSupports, InfoItemType::CustomSeam, + InfoItemType::Cut, InfoItemType::MmuSegmentation, InfoItemType::Sinking, InfoItemType::VariableLayerHeight}) { @@ -2624,6 +2633,9 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio }); break; + case InfoItemType::Cut : + should_show = !model_object->cut_connectors.empty(); + break; case InfoItemType::VariableLayerHeight : should_show = printer_technology() == ptFFF && ! model_object->layer_height_profile.empty(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 0e3dd3067..9c5074a45 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -26,6 +26,9 @@ static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) + , m_connector_type (CutConnectorType::Plug) + , m_connector_style (size_t(CutConnectorStyle::Prizm)) + , m_connector_shape_id (size_t(CutConnectorShape::Hexagon)) , m_rotation_gizmo(GLGizmoRotate3D(parent, "", -1)) , m_rotation_matrix( Eigen::AngleAxisd(0.0, Vec3d::UnitZ()) * Eigen::AngleAxisd(0.0, Vec3d::UnitY()) @@ -46,7 +49,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, // , _u8L("Claw") }; - m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Circle"), _u8L("Hexagon") + m_connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Hexagon"), _u8L("Circle") // , _u8L("D-shape") }; @@ -295,17 +298,17 @@ void GLGizmoCut3D::render_rotation_input(int axis) } } -void GLGizmoCut3D::render_connect_type_radio_button(ConnectorType type) +void GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type) { - ImGui::SameLine(type == ConnectorType::Plug ? m_label_width : 2*m_label_width); + ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 2*m_label_width); ImGui::PushItemWidth(m_control_width); if (m_imgui->radio_button(m_connector_types[int(type)], m_connector_type == type)) m_connector_type = type; } -void GLGizmoCut3D::render_connect_mode_radio_button(ConnectorMode mode) +void GLGizmoCut3D::render_connect_mode_radio_button(CutConnectorMode mode) { - ImGui::SameLine(mode == ConnectorMode::Auto ? m_label_width : 2*m_label_width); + ImGui::SameLine(mode == CutConnectorMode::Auto ? m_label_width : 2*m_label_width); ImGui::PushItemWidth(m_control_width); if (m_imgui->radio_button(m_connector_modes[int(mode)], m_connector_mode == mode)) m_connector_mode = mode; @@ -517,8 +520,16 @@ std::string GLGizmoCut3D::on_get_name() const void GLGizmoCut3D::on_set_state() { - if (get_state() == On) + if (get_state() == On) { update_bb(); + + m_selected.clear(); + if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { + const CutConnectors& connectors = selection->model_object()->cut_connectors; + for (size_t i = 0; i < connectors.size(); ++i) + m_selected.push_back(false); + } + } m_rotation_gizmo.set_center(m_plane_center); m_rotation_gizmo.set_state(m_state); @@ -738,12 +749,12 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); m_imgui->text(_L("Mode")); - render_connect_mode_radio_button(ConnectorMode::Auto); - render_connect_mode_radio_button(ConnectorMode::Manual); + render_connect_mode_radio_button(CutConnectorMode::Auto); + render_connect_mode_radio_button(CutConnectorMode::Manual); m_imgui->text(_L("Type")); - render_connect_type_radio_button(ConnectorType::Plug); - render_connect_type_radio_button(ConnectorType::Dowel); + render_connect_type_radio_button(CutConnectorType::Plug); + render_connect_type_radio_button(CutConnectorType::Dowel); if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) update_connector_shape(); @@ -854,7 +865,7 @@ void GLGizmoCut3D::render_connectors(bool picking) #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( - Vec3d(connector.pos.x(), connector.pos.y(), connector.pos.z() - 0.5 * connector.height), + Vec3d(connector.pos.x(), connector.pos.y(), connector.pos.z()), m_rotation_gizmo.get_rotation(), Vec3d(connector.radius, connector.radius, connector.height), Vec3d::Ones() @@ -964,6 +975,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair void GLGizmoCut3D::reset_connectors() { m_c->selection_info()->model_object()->cut_connectors.clear(); + update_model_object(); m_selected.clear(); } @@ -972,26 +984,35 @@ void GLGizmoCut3D::update_connector_shape() if (m_connector_shape.is_initialized()) m_connector_shape.reset(); - bool is_prizm = m_connector_style == size_t(Prizm); + bool is_prizm = m_connector_style == size_t(CutConnectorStyle::Prizm); const std::function& its_make_shape = is_prizm ? its_make_cylinder : its_make_cone; - switch (ConnectorShape(m_connector_shape_id)) { - case Triangle: + switch (CutConnectorShape(m_connector_shape_id)) { + case CutConnectorShape::Triangle: m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 3))); break; - case Square: + case CutConnectorShape::Square: m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 4))); break; - case Circle: + case CutConnectorShape::Circle: m_connector_shape.init_from(its_make_shape(1.0, 1.0, 2 * PI / 360)); break; - case Hexagon: + case CutConnectorShape::Hexagon: m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 6))); break; } } +void GLGizmoCut3D::update_model_object() const +{ + const ModelObjectPtrs& mos = wxGetApp().model().objects; + ModelObject* mo = m_c->selection_info()->model_object(); + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (is_dragging() || action != SLAGizmoEventType::LeftDown) @@ -1019,6 +1040,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add pin")); mo->cut_connectors.emplace_back(hit, -normal, float(m_connector_size * 0.5), float(m_connector_depth_ratio)); + update_model_object(); m_selected.push_back(false); assert(m_selected.size() == mo->cut_connectors.size()); m_parent.set_as_dirty(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 8394dba60..1fedcd22e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -9,6 +9,9 @@ #include "libslic3r/ObjectID.hpp" namespace Slic3r { + +enum class CutConnectorType : int; + namespace GUI { class Selection; @@ -41,8 +44,8 @@ class GLGizmoCut3D : public GLGizmoBase bool m_hide_cut_plane{ false }; - double m_connector_depth_ratio{ 5.0 }; - double m_connector_size{ 2.0 }; + double m_connector_depth_ratio{ 3.0 }; + double m_connector_size{ 2.5 }; float m_label_width{ 150.0 }; float m_control_width{ 200.0 }; @@ -64,44 +67,25 @@ class GLGizmoCut3D : public GLGizmoBase //,cutModular }; - enum ConnectorMode { + enum CutConnectorMode { Auto , Manual }; - enum ConnectorType { - Plug - , Dowel - }; - - enum ConnectorStyle { - Prizm - , Frustrum - //,Claw - }; - - enum ConnectorShape { - Triangle - , Square - , Circle - , Hexagon - //,D-shape - }; - std::vector m_modes; size_t m_mode{ size_t(cutPlanar) }; std::vector m_connector_modes; - ConnectorMode m_connector_mode{ Auto }; + CutConnectorMode m_connector_mode{ Auto }; std::vector m_connector_types; - ConnectorType m_connector_type{ Plug }; + CutConnectorType m_connector_type; std::vector m_connector_styles; - size_t m_connector_style{ size_t(Prizm) }; + size_t m_connector_style; std::vector m_connector_shapes; - size_t m_connector_shape_id{ size_t(Hexagon) }; + size_t m_connector_shape_id; std::vector m_axis_names; @@ -147,9 +131,9 @@ private: bool render_double_input(const std::string& label, double& value_in); void render_move_center_input(int axis); void render_rotation_input(int axis); - void render_connect_mode_radio_button(ConnectorMode mode); + void render_connect_mode_radio_button(CutConnectorMode mode); bool render_revert_button(const std::string& label); - void render_connect_type_radio_button(ConnectorType type); + void render_connect_type_radio_button(CutConnectorType type); void render_connectors(bool picking); bool can_perform_cut() const; @@ -161,6 +145,7 @@ private: bool update_bb(); void reset_connectors(); void update_connector_shape(); + void update_model_object() const; }; } // namespace GUI diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 496cdcfc7..908307125 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -48,6 +48,7 @@ const std::map INFO_ITEMS{ // info_item Type info_item Name info_item BitmapName { InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, }, { InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, }, + { InfoItemType::Cut, {L("Cut connectors"), "cut_" }, }, { InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, }, { InfoItemType::Sinking, {L("Sinking"), "sinking"}, }, { InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, }, diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index f8885b206..8669a8fd0 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -51,6 +51,7 @@ enum class InfoItemType Undef, CustomSupports, CustomSeam, + Cut, MmuSegmentation, Sinking, VariableLayerHeight From 09249e3b8dfc2ecb7172903bac49c6ad1731a1bf Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 15 Mar 2022 17:08:15 +0100 Subject: [PATCH 16/97] Cut: Perform cut with connectors --- src/libslic3r/Model.cpp | 77 +++++++++++++++++++++++++--- src/libslic3r/Model.hpp | 55 +++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 40 +++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- 4 files changed, 139 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8d44f3e7c..5e018a212 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1336,6 +1336,53 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr return res; } +void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes) +{ + if (cut_connectors.empty()) + return; + + bool is_prizm = connector_attributes.style == CutConnectorStyle::Prizm; + const std::function& its_make_shape = is_prizm ? its_make_cylinder : its_make_cone; + + indexed_triangle_set connector_mesh; + switch (CutConnectorShape(connector_attributes.shape)) { + case CutConnectorShape::Triangle: + connector_mesh = its_make_shape(1.0, 1.0, (2 * PI / 3)); + break; + case CutConnectorShape::Square: + connector_mesh = its_make_shape(1.0, 1.0, (2 * PI / 4)); + break; + case CutConnectorShape::Circle: + connector_mesh = its_make_shape(1.0, 1.0, 2 * PI / 360); + break; + case CutConnectorShape::Hexagon: + connector_mesh = its_make_shape(1.0, 1.0, (2 * PI / 6)); + break; + } + + size_t connector_id = 0; + + for (const CutConnector& connector : cut_connectors) { + TriangleMesh mesh = TriangleMesh(connector_mesh); + // Mesh will be centered when loading. + ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); + + // Transform the new modifier to be aligned inside the instance + new_volume->set_transformation(Geometry::assemble_transform( + connector.pos, + connector.rotation, + Vec3d(connector.radius, connector.radius, connector.height), + Vec3d::Ones() + )); + + new_volume->name = name + "-" + std::to_string(++connector_id); + new_volume->source.is_connector = true; + } + + // delete all connectors + cut_connectors.clear(); +} + ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes) { if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) @@ -1405,15 +1452,31 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const if (!volume->is_model_part()) { // Modifiers are not cut, but we still need to add the instance transformation // to the modifier volume transformation to preserve their shape properly. + // But if this modifier is a connector, then just set volume transformation + if (volume->source.is_connector) + volume->set_transformation(Geometry::Transformation(volume_matrix)); + else + volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper->add_volume(*volume); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower->add_volume(*volume); + ModelVolume* vol = { nullptr }; + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + ModelVolume* vol = upper->add_volume(*volume); + if (volume->source.is_connector) + vol->source.is_connector = true; + } + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + ModelVolume* vol = lower->add_volume(*volume); + if (volume->source.is_connector) { + vol->source.is_connector = true; + // for lower part change type of conector from NEGATIVE_VOLUME to MODEL_PART + if (vol->type() == ModelVolumeType::NEGATIVE_VOLUME) + vol->set_type(ModelVolumeType::MODEL_PART); + } + } } - else if (!volume->mesh().empty()) { + else if (!volume->mesh().empty() && + !volume->source.is_connector // we don't allow to cut a connectors + ) { // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. TriangleMesh mesh(volume->mesh()); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c0e7e907d..dbaded841 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -221,22 +221,22 @@ private: struct CutConnector { - Vec3f pos; - Vec3f normal; + Vec3d pos; + Vec3d rotation; float radius; float height; bool failed = false; CutConnector() - : pos(Vec3f::Zero()), normal(Vec3f::UnitZ()), radius(5.f), height(10.f) + : pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f) {} - CutConnector(Vec3f p, Vec3f n, float r, float h, bool fl = false) - : pos(p), normal(n), radius(r), height(h), failed(fl) + CutConnector(Vec3d p, Vec3d n, float r, float h, bool fl = false) + : pos(p), rotation(n), radius(r), height(h), failed(fl) {} CutConnector(const CutConnector& rhs) : - CutConnector(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {} + CutConnector(rhs.pos, rhs.rotation, rhs.radius, rhs.height, rhs.failed) {} bool operator==(const CutConnector& sp) const; @@ -251,22 +251,12 @@ struct CutConnector */ template inline void serialize(Archive& ar) { - ar(pos, normal, radius, height, failed); + ar(pos, rotation, radius, height, failed); } static constexpr size_t steps = 32; }; -// Declared outside of ModelVolume, so it could be forward declared. -enum class ModelVolumeType : int { - INVALID = -1, - MODEL_PART = 0, - NEGATIVE_VOLUME, - PARAMETER_MODIFIER, - SUPPORT_BLOCKER, - SUPPORT_ENFORCER, -}; - using CutConnectors = std::vector; enum class CutConnectorType : int { @@ -288,6 +278,32 @@ enum class CutConnectorShape : int { //,D-shape }; +struct CutConnectorAttributes +{ + CutConnectorType type{ CutConnectorType::Plug }; + CutConnectorStyle style{ CutConnectorStyle::Prizm}; + CutConnectorShape shape{CutConnectorShape::Circle}; + + CutConnectorAttributes() {} + + CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh) + : type(t), style(st), shape(sh) + {} + + CutConnectorAttributes(const CutConnectorAttributes& rhs) : + CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {} +}; + +// Declared outside of ModelVolume, so it could be forward declared. +enum class ModelVolumeType : int { + INVALID = -1, + MODEL_PART = 0, + NEGATIVE_VOLUME, + PARAMETER_MODIFIER, + SUPPORT_BLOCKER, + SUPPORT_ENFORCER, +}; + enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; using ModelObjectCutAttributes = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); @@ -416,6 +432,7 @@ public: size_t facets_count() const; size_t parts_count() const; ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); + void apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes); ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); void merge(); @@ -671,10 +688,12 @@ public: bool is_converted_from_meters{ false }; bool is_from_builtin_objects{ false }; + bool is_connector{ false }; + template void serialize(Archive& ar) { //FIXME Vojtech: Serialize / deserialize only if the Source is set. // likely testing input_file or object_idx would be sufficient. - ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects); + ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects, is_connector); } }; Source source; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9c5074a45..5448dd1a2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -578,22 +578,22 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) if (wxGetKeyState(WXK_SHIFT)) projection = m_snap_step * (double)std::round(projection / m_snap_step); + Vec3d shift = starting_vec * projection; + // move cut plane center - set_center(starting_box_center + starting_vec * projection); + set_center(starting_box_center + shift); // move connectors - Vec3f shift = Vec3f(starting_vec.cast() * projection); for (auto& connector : connectors) connector.pos += shift; } else if (m_hover_id > m_group_id) { - std::pair pos_and_normal; + std::pair pos_and_normal; if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal)) return; connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; - connectors[m_hover_id - m_connectors_group_id].normal = -pos_and_normal.second; } } @@ -924,6 +924,28 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); if (0.0 < object_cut_z && can_perform_cut()) { + ModelObject* mo = wxGetApp().plater()->model().objects[object_idx]; + // update connectors pos as offset of its center before cut performing + if (!mo->cut_connectors.empty()) { + const std::string name = _u8L("Connector"); + for (CutConnector& connector : mo->cut_connectors) { + connector.rotation = m_rotation_gizmo.get_rotation(); + + // culculate shift of the connector center regarding to the position on the cut plane + Vec3d norm = m_grabbers[0].center - m_plane_center; + norm.normalize(); + Vec3d shift = norm * (0.5 * connector.height); + + // culculate offset of the connector pos regarding to the instance offset and possible SLA elevation + Vec3d connector_offset = connector.pos - instance_offset; + connector_offset[Z] -= first_glvolume->get_sla_shift_z(); + + // Update connector pos. It will be used as a center of created modifiers + connector.pos = connector_offset + shift; + } + mo->apply_cut_connectors(name, CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); + } + wxGetApp().plater()->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | @@ -939,7 +961,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // Return false if no intersection was found, true otherwise. -bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal) +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal) { if (!m_c->raycaster()->raycaster()) return false; @@ -965,7 +987,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair nullptr, &clipping_plane_was_hit); if (clipping_plane_was_hit) { // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); + pos_and_normal = std::make_pair(hit.cast(), normal.cast()); return true; } } @@ -1031,10 +1053,10 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi // If there is some selection, don't add new point and deselect everything instead. if (m_selection_empty) { - std::pair pos_and_normal; + std::pair pos_and_normal; if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal)) { - const Vec3f& hit = pos_and_normal.first; - const Vec3f& normal = pos_and_normal.second; + const Vec3d& hit = pos_and_normal.first; + const Vec3d& normal = pos_and_normal.second; // The clipping plane was clicked, hit containts coordinates of the hit in world coords. std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add pin")); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 1fedcd22e..33d254c0d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -93,7 +93,7 @@ public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; - bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair& pos_and_normal); + bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair& pos_and_normal); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); /// From e785a66a014cfd4173ce0e5498e989fab0e234d1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 17 Mar 2022 17:35:41 +0100 Subject: [PATCH 17/97] Cut: Added possibility to delete a selected connector + Save connector position in object's local coordinates + Added missed cut_.svg --- resources/icons/cut_.svg | 28 +++++++++++ src/libslic3r/Model.cpp | 4 +- src/libslic3r/Model.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 75 ++++++++++++++++------------ 4 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 resources/icons/cut_.svg diff --git a/resources/icons/cut_.svg b/resources/icons/cut_.svg new file mode 100644 index 000000000..a7f462bb9 --- /dev/null +++ b/resources/icons/cut_.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5e018a212..dcb4c503a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1474,8 +1474,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const } } } - else if (!volume->mesh().empty() && - !volume->source.is_connector // we don't allow to cut a connectors + else if (!volume->mesh().empty() +// && !volume->source.is_connector // we don't allow to cut a connectors ) { // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index dbaded841..c0b2e8e39 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -231,8 +231,8 @@ struct CutConnector : pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f) {} - CutConnector(Vec3d p, Vec3d n, float r, float h, bool fl = false) - : pos(p), rotation(n), radius(r), height(h), failed(fl) + CutConnector(Vec3d p, Vec3d rot, float r, float h, bool fl = false) + : pos(p), rotation(rot), radius(r), height(h), failed(fl) {} CutConnector(const CutConnector& rhs) : diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5448dd1a2..ea82d61e2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -30,9 +30,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_style (size_t(CutConnectorStyle::Prizm)) , m_connector_shape_id (size_t(CutConnectorShape::Hexagon)) , m_rotation_gizmo(GLGizmoRotate3D(parent, "", -1)) - , m_rotation_matrix( Eigen::AngleAxisd(0.0, Vec3d::UnitZ()) - * Eigen::AngleAxisd(0.0, Vec3d::UnitY()) - * Eigen::AngleAxisd(0.0, Vec3d::UnitX())) + , m_rotation_matrix(Slic3r::Matrix3d::Identity()) { m_rotation_gizmo.use_only_grabbers(); m_group_id = 3; @@ -518,16 +516,14 @@ std::string GLGizmoCut3D::on_get_name() const return _u8L("Cut"); } -void GLGizmoCut3D::on_set_state() +void GLGizmoCut3D::on_set_state() { if (get_state() == On) { update_bb(); - m_selected.clear(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { - const CutConnectors& connectors = selection->model_object()->cut_connectors; - for (size_t i = 0; i < connectors.size(); ++i) - m_selected.push_back(false); + m_selected.clear(); + m_selected.resize(selection->model_object()->cut_connectors.size(), false); } } m_rotation_gizmo.set_center(m_plane_center); @@ -811,8 +807,6 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) void GLGizmoCut3D::render_connectors(bool picking) { - const Selection& selection = m_parent.get_selection(); - #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat_attr") : wxGetApp().get_shader("gouraud_light_attr"); @@ -840,7 +834,8 @@ void GLGizmoCut3D::render_connectors(bool picking) #endif // ENABLE_GL_SHADERS_ATTRIBUTES ColorRGBA render_color; - const CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + const ModelObject* mo = m_c->selection_info()->model_object(); + const CutConnectors& connectors = mo->cut_connectors; size_t cache_size = connectors.size(); for (size_t i = 0; i < cache_size; ++i) { @@ -863,9 +858,14 @@ void GLGizmoCut3D::render_connectors(bool picking) const_cast(&m_connector_shape)->set_color(-1, render_color); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL + // recalculate connector position to world position + Vec3d pos = connector.pos; + pos += mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); + pos[Z] += m_c->selection_info()->get_sla_shift(); + #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( - Vec3d(connector.pos.x(), connector.pos.y(), connector.pos.z()), + Vec3d(pos.x(), pos.y(), pos.z()), m_rotation_gizmo.get_rotation(), Vec3d(connector.radius, connector.radius, connector.height), Vec3d::Ones() @@ -874,7 +874,7 @@ void GLGizmoCut3D::render_connectors(bool picking) shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #else glsafe(::glPushMatrix()); - glsafe(::glTranslatef(connector.pos.x(), connector.pos.y(), connector.pos.z())); + glsafe(::glTranslatef(pos.x(), pos.y(), pos.z())); const Vec3d& angles = m_rotation_gizmo.get_rotation(); glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); @@ -927,7 +927,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) ModelObject* mo = wxGetApp().plater()->model().objects[object_idx]; // update connectors pos as offset of its center before cut performing if (!mo->cut_connectors.empty()) { - const std::string name = _u8L("Connector"); for (CutConnector& connector : mo->cut_connectors) { connector.rotation = m_rotation_gizmo.get_rotation(); @@ -935,15 +934,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d norm = m_grabbers[0].center - m_plane_center; norm.normalize(); Vec3d shift = norm * (0.5 * connector.height); - - // culculate offset of the connector pos regarding to the instance offset and possible SLA elevation - Vec3d connector_offset = connector.pos - instance_offset; - connector_offset[Z] -= first_glvolume->get_sla_shift_z(); - - // Update connector pos. It will be used as a center of created modifiers - connector.pos = connector_offset + shift; + connector.pos += shift; } - mo->apply_cut_connectors(name, CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); + mo->apply_cut_connectors(_u8L("Connector"), CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); } wxGetApp().plater()->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), @@ -963,9 +956,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // Return false if no intersection was found, true otherwise. bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal) { - if (!m_c->raycaster()->raycaster()) - return false; - const float sla_shift = m_c->selection_info()->get_sla_shift(); const ModelObject* mo = m_c->selection_info()->model_object(); @@ -986,8 +976,13 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), nullptr, &clipping_plane_was_hit); if (clipping_plane_was_hit) { + // recalculate hit to object's local position + Vec3d hit_d = hit.cast(); + hit_d -= mi->get_offset(); + hit_d[Z] -= sla_shift; + // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit.cast(), normal.cast()); + pos_and_normal = std::make_pair(hit_d, normal.cast()); return true; } } @@ -1037,15 +1032,15 @@ void GLGizmoCut3D::update_model_object() const bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { - if (is_dragging() || action != SLAGizmoEventType::LeftDown) + if (is_dragging()) return false; - ModelObject *mo = m_c->selection_info()->model_object(); + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; const Camera& camera = wxGetApp().plater()->get_camera(); int mesh_id = -1; - // left down without selection rectangle - place point on the mesh: + // left down without selection rectangle - place connector on the cut plane: if (action == SLAGizmoEventType::LeftDown && /*!m_selection_rectangle.is_dragging() && */!shift_down) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id != -1) @@ -1059,12 +1054,12 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi const Vec3d& normal = pos_and_normal.second; // The clipping plane was clicked, hit containts coordinates of the hit in world coords. std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add pin")); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector")); - mo->cut_connectors.emplace_back(hit, -normal, float(m_connector_size * 0.5), float(m_connector_depth_ratio)); + connectors.emplace_back(hit, m_rotation_gizmo.get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio)); update_model_object(); m_selected.push_back(false); - assert(m_selected.size() == mo->cut_connectors.size()); + assert(m_selected.size() == connectors.size()); m_parent.set_as_dirty(); m_wait_for_up_event = true; @@ -1074,6 +1069,22 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi } return true; } + else if (action == SLAGizmoEventType::RightDown && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id < m_connectors_group_id) + return false; + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete connector")); + + size_t connector_id = m_hover_id - m_connectors_group_id; + connectors.erase(connectors.begin() + connector_id); + update_model_object(); + m_selected.erase(m_selected.begin() + connector_id); + assert(m_selected.size() == connectors.size()); + m_parent.set_as_dirty(); + + return true; + } return false; } From 861187997bf989c6cef5aff866564bcaba7ce8f4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 18 Mar 2022 11:31:10 +0100 Subject: [PATCH 18/97] Cut: Pt connectors to the cut plane --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 40 ++++++++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index ea82d61e2..511622cf2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -160,6 +160,30 @@ void GLGizmoCut3D::rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, c vec += center; } +void GLGizmoCut3D::put_connetors_on_cut_plane() +{ + ModelObject* mo = m_c->selection_info()->model_object(); + if (CutConnectors& connectors = mo->cut_connectors; !connectors.empty()) { + const float sla_shift = m_c->selection_info()->get_sla_shift(); + const Vec3d& instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); + + const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); + const Vec3d& normal = cp->get_normal(); + + for (auto& connector : connectors) { + // convert connetor pos to the world coordinates + Vec3d pos = connector.pos + instance_offset; + pos[Z] += sla_shift; + + // scalar distance from point to plane along the normal + double distance = cp->distance(pos); + + // move connector + connector.pos += distance * normal; + } + } +} + void GLGizmoCut3D::update_clipper() { const Vec3d& angles = m_rotation_gizmo.get_rotation(); @@ -177,6 +201,8 @@ void GLGizmoCut3D::update_clipper() double dist = (m_plane_center - beg).norm(); m_c->object_clipper()->set_range_and_pos(beg, end, dist); + + put_connetors_on_cut_plane(); } void GLGizmoCut3D::update_clipper_on_render() @@ -578,10 +604,6 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) // move cut plane center set_center(starting_box_center + shift); - - // move connectors - for (auto& connector : connectors) - connector.pos += shift; } else if (m_hover_id > m_group_id) @@ -838,6 +860,9 @@ void GLGizmoCut3D::render_connectors(bool picking) const CutConnectors& connectors = mo->cut_connectors; size_t cache_size = connectors.size(); + const Vec3d& instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); + const float sla_shift = m_c->selection_info()->get_sla_shift(); + for (size_t i = 0; i < cache_size; ++i) { const CutConnector& connector = connectors[i]; const bool& point_selected = m_selected[i]; @@ -859,9 +884,8 @@ void GLGizmoCut3D::render_connectors(bool picking) #endif // ENABLE_GLBEGIN_GLEND_REMOVAL // recalculate connector position to world position - Vec3d pos = connector.pos; - pos += mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); - pos[Z] += m_c->selection_info()->get_sla_shift(); + Vec3d pos = connector.pos + instance_offset; + pos[Z] += sla_shift; #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( @@ -918,7 +942,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); const double object_cut_z = m_plane_center.z() - first_glvolume->get_sla_shift_z(); - Vec3d instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); + const Vec3d& instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 33d254c0d..a9552e5e7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -105,6 +105,7 @@ public: void shift_cut_z(double delta); void rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center); + void put_connetors_on_cut_plane(); void update_clipper(); void update_clipper_on_render(); From 301d0d5288d46f6328fd805e4104fde55a08eb15 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 22 Mar 2022 11:25:48 +0100 Subject: [PATCH 19/97] Cut WIP: * Processed Auto/Manual connetor's mode * Processed Dowel type of connectors * Added TriangeMesh::its_make_frustum_dowel --- src/libslic3r/Model.cpp | 128 +++++++++++++++++++-------- src/libslic3r/Model.hpp | 3 +- src/libslic3r/TriangleMesh.cpp | 55 ++++++++++++ src/libslic3r/TriangleMesh.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 92 +++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 8 +- 6 files changed, 206 insertions(+), 81 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index dcb4c503a..00c489ac1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -712,6 +712,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, ModelVolumeType t ModelVolume* v = new ModelVolume(this, other); if (type != ModelVolumeType::INVALID && v->type() != type) v->set_type(type); + v->source.is_connector = other.source.is_connector; this->volumes.push_back(v); // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull. // v->center_geometry_after_creation(); @@ -1336,29 +1337,42 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr return res; } +indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) +{ + indexed_triangle_set connector_mesh; + + int sectorCount; + switch (CutConnectorShape(connector_attributes.shape)) { + case CutConnectorShape::Triangle: + sectorCount = 3; + break; + case CutConnectorShape::Square: + sectorCount = 4; + break; + case CutConnectorShape::Circle: + sectorCount = 360; + break; + case CutConnectorShape::Hexagon: + sectorCount = 6; + break; + } + + if (connector_attributes.style == CutConnectorStyle::Prizm) + connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); + else if (connector_attributes.type == CutConnectorType::Plug) + connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount)); + else + connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); + + return connector_mesh; +} + void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes) { if (cut_connectors.empty()) return; - bool is_prizm = connector_attributes.style == CutConnectorStyle::Prizm; - const std::function& its_make_shape = is_prizm ? its_make_cylinder : its_make_cone; - - indexed_triangle_set connector_mesh; - switch (CutConnectorShape(connector_attributes.shape)) { - case CutConnectorShape::Triangle: - connector_mesh = its_make_shape(1.0, 1.0, (2 * PI / 3)); - break; - case CutConnectorShape::Square: - connector_mesh = its_make_shape(1.0, 1.0, (2 * PI / 4)); - break; - case CutConnectorShape::Circle: - connector_mesh = its_make_shape(1.0, 1.0, 2 * PI / 360); - break; - case CutConnectorShape::Hexagon: - connector_mesh = its_make_shape(1.0, 1.0, (2 * PI / 6)); - break; - } + indexed_triangle_set connector_mesh = get_connector_mesh(connector_attributes); size_t connector_id = 0; @@ -1393,6 +1407,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // Clone the object to duplicate instances, materials etc. ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; + ModelObject* dowels = attributes.has(ModelObjectCutAttribute::CreateDowels) ? ModelObject::new_clone(*this) : nullptr; if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { upper->set_model(nullptr); @@ -1412,6 +1427,15 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const lower->input_file.clear(); } + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { + dowels->set_model(nullptr); + dowels->sla_support_points.clear(); + dowels->sla_drain_holes.clear(); + dowels->sla_points_status = sla::PointsStatus::NoPoints; + dowels->clear_volumes(); + dowels->input_file.clear(); + } + // Because transformations are going to be applied to meshes directly, // we reset transformation of all instances and volumes, // except for translation and Z-rotation on instances, which are preserved @@ -1450,29 +1474,31 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const volume->mmu_segmentation_facets.reset(); if (!volume->is_model_part()) { - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - // But if this modifier is a connector, then just set volume transformation - if (volume->source.is_connector) - volume->set_transformation(Geometry::Transformation(volume_matrix)); - else - volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - - ModelVolume* vol = { nullptr }; - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - ModelVolume* vol = upper->add_volume(*volume); - if (volume->source.is_connector) - vol->source.is_connector = true; - } - if (attributes.has(ModelObjectCutAttribute::KeepLower)) { - ModelVolume* vol = lower->add_volume(*volume); - if (volume->source.is_connector) { - vol->source.is_connector = true; - // for lower part change type of conector from NEGATIVE_VOLUME to MODEL_PART - if (vol->type() == ModelVolumeType::NEGATIVE_VOLUME) + if (volume->source.is_connector) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + ModelVolume* vol = upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + ModelVolume* vol = lower->add_volume(*volume); + if (!attributes.has(ModelObjectCutAttribute::CreateDowels)) + // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug vol->set_type(ModelVolumeType::MODEL_PART); } + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { + // add one more solid part same as connector if this connector is a dowel + // But discard rotation and Z-offset for this volume + volume->set_rotation(Vec3d::Zero()); + Vec3d offset = volume->get_offset(); + offset[Z] = 0.0; + volume->set_offset(offset); + + ModelVolume* vol = dowels->add_volume(*volume); + vol->set_type(ModelVolumeType::MODEL_PART); + } } + else + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); } else if (!volume->mesh().empty() // && !volume->source.is_connector // we don't allow to cut a connectors @@ -1584,6 +1610,32 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const res.push_back(lower); } + if (attributes.has(ModelObjectCutAttribute::CreateDowels) && dowels->volumes.size() > 0) { + if (!dowels->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { + dowels->center_around_origin(); + dowels->translate_instances(-dowels->origin_translation); + dowels->origin_translation = Vec3d::Zero(); + } + else { + dowels->invalidate_bounding_box(); + dowels->center_around_origin(); + } + + dowels->name += "-Dowels"; + + // Reset instance transformation except offset and Z-rotation + for (size_t i = 0; i < instances.size(); ++i) { + auto& obj_instance = dowels->instances[i]; + const Vec3d offset = obj_instance->get_offset(); + const double rot_z = obj_instance->get_rotation().z(); + obj_instance->set_transformation(Geometry::Transformation()); + obj_instance->set_offset(offset); + obj_instance->set_rotation(Vec3d(0.0, 0.0, i == instance ? 0.0 : rot_z)); + } + + res.push_back(dowels); + } + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; return res; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c0b2e8e39..c8ae063ec 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -304,7 +304,7 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER, }; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower, CreateDowels }; using ModelObjectCutAttributes = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); @@ -432,6 +432,7 @@ public: size_t facets_count() const; size_t parts_count() const; ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); + static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes); ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 0dffdaab0..f98d7d4dc 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1053,6 +1053,61 @@ indexed_triangle_set its_make_sphere(double radius, double fa) return mesh; } +// Generates mesh for a frustum dowel centered about the origin, using the count of sectors +// Note: This function uses code for sphere generation, but for stackCount = 2; +indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount) +{ + int stackCount = 2; + float sectorStep = float(2. * M_PI / sectorCount); + float stackStep = float(M_PI / stackCount); + + indexed_triangle_set mesh; + auto& vertices = mesh.vertices; + vertices.reserve((stackCount - 1) * sectorCount + 2); + for (int i = 0; i <= stackCount; ++i) { + // from pi/2 to -pi/2 + double stackAngle = 0.5 * M_PI - stackStep * i; + double xy = radius * cos(stackAngle); + double z = radius * sin(stackAngle); + if (i == 0 || i == stackCount) + vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle)))); + else + for (int j = 0; j < sectorCount; ++j) { + // from 0 to 2pi + double sectorAngle = sectorStep * j; + vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); + } + } + + auto& facets = mesh.indices; + facets.reserve(2 * (stackCount - 1) * sectorCount); + for (int i = 0; i < stackCount; ++i) { + // Beginning of current stack. + int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + int k1_first = k1; + // Beginning of next stack. + int k2 = (i == 0) ? 1 : (k1 + sectorCount); + int k2_first = k2; + for (int j = 0; j < sectorCount; ++j) { + // 2 triangles per sector excluding first and last stacks + int k1_next = k1; + int k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + facets.emplace_back(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + facets.emplace_back(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + + return mesh; +} + indexed_triangle_set its_convex_hull(const std::vector &pts) { std::vector dst_vertices; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 3f3af0261..abf9cefb7 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -302,6 +302,7 @@ indexed_triangle_set its_make_cube(double x, double y, double z); indexed_triangle_set its_make_prism(float width, float length, float height); indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360)); +indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount); indexed_triangle_set its_make_pyramid(float base, float height); indexed_triangle_set its_make_sphere(double radius, double fa); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 511622cf2..411cebbb5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -326,8 +326,10 @@ void GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type) { ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 2*m_label_width); ImGui::PushItemWidth(m_control_width); - if (m_imgui->radio_button(m_connector_types[int(type)], m_connector_type == type)) + if (m_imgui->radio_button(m_connector_types[size_t(type)], m_connector_type == type)) { m_connector_type = type; + update_connector_shape(); + } } void GLGizmoCut3D::render_connect_mode_radio_button(CutConnectorMode mode) @@ -606,7 +608,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) set_center(starting_box_center + shift); } - else if (m_hover_id > m_group_id) + else if (m_hover_id > m_group_id && m_connector_mode == CutConnectorMode::Manual) { std::pair pos_and_normal; if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal)) @@ -669,7 +671,7 @@ void GLGizmoCut3D::on_render() if (!m_hide_cut_plane) { render_cut_plane(); render_cut_center_graber(); - if (m_mode == CutMode::cutPlanar) { + if (m_mode == size_t(CutMode::cutPlanar)) { if (m_hover_id < m_group_id) m_rotation_gizmo.render(); } @@ -716,10 +718,12 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) bool revert_rotation{ false }; bool revert_move{ false }; - if (m_mode <= CutMode::cutByLine) { + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + + if (m_mode <= size_t(CutMode::cutByLine)) { ImGui::Separator(); - if (m_mode == CutMode::cutPlanar) { + if (m_mode == size_t(CutMode::cutPlanar)) { ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Move center")); revert_move = render_revert_button("move"); @@ -748,11 +752,18 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::AlignTextToFramePadding(); m_imgui->text(_L("After cut")); ImGui::SameLine(m_label_width); - m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); + + m_imgui->disabled_begin(!connectors.empty()); + + bool keep = true; + m_imgui->checkbox(_L("Keep upper part"), connectors.empty() ? m_keep_upper : keep); m_imgui->text(""); ImGui::SameLine(m_label_width); - m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); + m_imgui->checkbox(_L("Keep lower part"), connectors.empty() ? m_keep_lower : keep); m_imgui->text(""); + + m_imgui->disabled_end(); + ImGui::SameLine(m_label_width); m_imgui->disabled_begin(!m_keep_lower); m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); @@ -779,7 +790,6 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) update_connector_shape(); - CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; if (render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio)) for (auto& connector : connectors) connector.height = float(m_connector_depth_ratio); @@ -829,6 +839,9 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) void GLGizmoCut3D::render_connectors(bool picking) { + if (m_connector_mode == CutConnectorMode::Auto) + return; + #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat_attr") : wxGetApp().get_shader("gouraud_light_attr"); @@ -863,6 +876,9 @@ void GLGizmoCut3D::render_connectors(bool picking) const Vec3d& instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); const float sla_shift = m_c->selection_info()->get_sla_shift(); + const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); + const Vec3d& normal = cp ? cp->get_normal() : Vec3d::Ones(); + for (size_t i = 0; i < cache_size; ++i) { const CutConnector& connector = connectors[i]; const bool& point_selected = m_selected[i]; @@ -883,15 +899,21 @@ void GLGizmoCut3D::render_connectors(bool picking) const_cast(&m_connector_shape)->set_color(-1, render_color); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL + double height = connector.height; // recalculate connector position to world position Vec3d pos = connector.pos + instance_offset; + if (m_connector_type == CutConnectorType::Dowel && + m_connector_style == size_t(CutConnectorStyle::Prizm)) { + pos -= height * normal; + height *= 2; + } pos[Z] += sla_shift; #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( Vec3d(pos.x(), pos.y(), pos.z()), m_rotation_gizmo.get_rotation(), - Vec3d(connector.radius, connector.radius, connector.height), + Vec3d(connector.radius, connector.radius, height), Vec3d::Ones() ); shader->set_uniform("view_model_matrix", view_model_matrix); @@ -906,7 +928,7 @@ void GLGizmoCut3D::render_connectors(bool picking) glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); glsafe(::glTranslated(0., 0., -0.5*connector.height)); - glsafe(::glScaled(connector.radius, connector.radius, connector.height)); + glsafe(::glScaled(connector.radius, connector.radius, height)); #endif // ENABLE_GL_SHADERS_ATTRIBUTES m_connector_shape.render(); @@ -947,26 +969,37 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); + bool create_dowels_as_separate_object = false; if (0.0 < object_cut_z && can_perform_cut()) { ModelObject* mo = wxGetApp().plater()->model().objects[object_idx]; + const bool has_connectors = !mo->cut_connectors.empty(); // update connectors pos as offset of its center before cut performing - if (!mo->cut_connectors.empty()) { + if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { for (CutConnector& connector : mo->cut_connectors) { connector.rotation = m_rotation_gizmo.get_rotation(); - // culculate shift of the connector center regarding to the position on the cut plane - Vec3d norm = m_grabbers[0].center - m_plane_center; - norm.normalize(); - Vec3d shift = norm * (0.5 * connector.height); - connector.pos += shift; + if (m_connector_style == size_t(CutConnectorStyle::Prizm)) { + if (m_connector_type == CutConnectorType::Dowel) + connector.height *= 2; + else { + // culculate shift of the connector center regarding to the position on the cut plane + Vec3d norm = m_grabbers[0].center - m_plane_center; + norm.normalize(); + Vec3d shift = norm * (0.5 * connector.height); + connector.pos += shift; + } + } } mo->apply_cut_connectors(_u8L("Connector"), CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); + if (m_connector_type == CutConnectorType::Dowel) + create_dowels_as_separate_object = true; } wxGetApp().plater()->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), - only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | - only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | - only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); + only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | + only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); m_selected.clear(); } else { @@ -1025,24 +1058,7 @@ void GLGizmoCut3D::update_connector_shape() if (m_connector_shape.is_initialized()) m_connector_shape.reset(); - bool is_prizm = m_connector_style == size_t(CutConnectorStyle::Prizm); - const std::function& its_make_shape = is_prizm ? its_make_cylinder : its_make_cone; - - - switch (CutConnectorShape(m_connector_shape_id)) { - case CutConnectorShape::Triangle: - m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 3))); - break; - case CutConnectorShape::Square: - m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 4))); - break; - case CutConnectorShape::Circle: - m_connector_shape.init_from(its_make_shape(1.0, 1.0, 2 * PI / 360)); - break; - case CutConnectorShape::Hexagon: - m_connector_shape.init_from(its_make_shape(1.0, 1.0, (2 * PI / 6))); - break; - } + m_connector_shape.init_from(ModelObject::get_connector_mesh({ m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) })); } void GLGizmoCut3D::update_model_object() const @@ -1056,7 +1072,7 @@ void GLGizmoCut3D::update_model_object() const bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { - if (is_dragging()) + if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) return false; CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index a9552e5e7..57be3c739 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -59,7 +59,7 @@ class GLGizmoCut3D : public GLGizmoBase Matrix3d m_rotation_matrix; Vec3d m_rotations{ Vec3d::Zero() }; - enum CutMode { + enum class CutMode { cutPlanar , cutByLine , cutGrig @@ -67,16 +67,16 @@ class GLGizmoCut3D : public GLGizmoBase //,cutModular }; - enum CutConnectorMode { + enum class CutConnectorMode { Auto , Manual }; std::vector m_modes; - size_t m_mode{ size_t(cutPlanar) }; + size_t m_mode{ size_t(CutMode::cutPlanar) }; std::vector m_connector_modes; - CutConnectorMode m_connector_mode{ Auto }; + CutConnectorMode m_connector_mode{ CutConnectorMode::Manual }; std::vector m_connector_types; CutConnectorType m_connector_type; From fdaca50d4b8843bc9b2240e1091d7b1395701cd4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 23 Mar 2022 09:26:15 +0100 Subject: [PATCH 20/97] Cut WIP: Implemented flip of the upper part after performing of the cut --- src/libslic3r/Model.cpp | 18 +++++-- src/libslic3r/Model.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 71 ++++++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 4 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 00c489ac1..ed341dfc6 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1465,6 +1465,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // Displacement (in instance coordinates) to be applied to place the upper parts Vec3d local_displace = Vec3d::Zero(); + Vec3d local_dowels_displace = Vec3d::Zero(); for (ModelVolume* volume : volumes) { const auto volume_matrix = volume->get_matrix(); @@ -1493,6 +1494,9 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const ModelVolume* vol = dowels->add_volume(*volume); vol->set_type(ModelVolumeType::MODEL_PART); + + // Compute the displacement (in instance coordinates) to be applied to place the dowels + local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); } } else @@ -1580,8 +1584,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const obj_instance->set_transformation(Geometry::Transformation()); obj_instance->set_offset(offset + displace); - if (i != instance) - obj_instance->set_rotation(Vec3d(0.0, 0.0, rot_z)); + obj_instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipUpper) ? Geometry::deg2rad(180.0) : 0.0, 0.0, i == instance ? 0.0 : rot_z)); } res.push_back(upper); @@ -1627,10 +1630,15 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const for (size_t i = 0; i < instances.size(); ++i) { auto& obj_instance = dowels->instances[i]; const Vec3d offset = obj_instance->get_offset(); - const double rot_z = obj_instance->get_rotation().z(); + Vec3d rotation = Vec3d::Zero(); + if (i != instance) + rotation[Z] = obj_instance->get_rotation().z(); + + const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), rotation) * local_dowels_displace; + obj_instance->set_transformation(Geometry::Transformation()); - obj_instance->set_offset(offset); - obj_instance->set_rotation(Vec3d(0.0, 0.0, i == instance ? 0.0 : rot_z)); + obj_instance->set_offset(offset + displace); + obj_instance->set_rotation(rotation); } res.push_back(dowels); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c8ae063ec..b3c5b47db 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -266,7 +266,7 @@ enum class CutConnectorType : int { enum class CutConnectorStyle : int { Prizm - , Frustrum + , Frustum //,Claw }; @@ -304,7 +304,7 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER, }; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower, CreateDowels }; +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, CreateDowels }; using ModelObjectCutAttributes = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 411cebbb5..be040e729 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -43,7 +43,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; m_connector_types = { _u8L("Plug"), _u8L("Dowel") }; - m_connector_styles = { _u8L("Prizm"), _u8L("Frustrum") + m_connector_styles = { _u8L("Prizm"), _u8L("Frustum") // , _u8L("Claw") }; @@ -750,24 +750,54 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) } ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("After cut")); + m_imgui->text(_L("After cut") + ": "); + bool keep = true; + ImGui::SameLine(m_label_width); + m_imgui->text(_L("Upper part")); + ImGui::SameLine(2*m_label_width); m_imgui->disabled_begin(!connectors.empty()); - - bool keep = true; - m_imgui->checkbox(_L("Keep upper part"), connectors.empty() ? m_keep_upper : keep); - m_imgui->text(""); - ImGui::SameLine(m_label_width); - m_imgui->checkbox(_L("Keep lower part"), connectors.empty() ? m_keep_lower : keep); - m_imgui->text(""); - + m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); + m_imgui->disabled_end(); + ImGui::SameLine(); + m_imgui->disabled_begin(!m_keep_upper); + m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper); m_imgui->disabled_end(); + m_imgui->text(""); ImGui::SameLine(m_label_width); + m_imgui->text(_L("Lower part")); + ImGui::SameLine(2*m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); + m_imgui->disabled_end(); + ImGui::SameLine(); m_imgui->disabled_begin(!m_keep_lower); - m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower); m_imgui->disabled_end(); + + //m_imgui->disabled_begin(!connectors.empty()); + + //bool keep = true; + //m_imgui->checkbox(_L("Keep upper part"), connectors.empty() ? m_keep_upper : keep); + //m_imgui->text(""); + //ImGui::SameLine(m_label_width); + //m_imgui->checkbox(_L("Keep lower part"), connectors.empty() ? m_keep_lower : keep); + + //m_imgui->disabled_end(); + + //m_imgui->disabled_begin(!m_keep_upper); + //m_imgui->text(""); + //ImGui::SameLine(m_label_width); + //m_imgui->checkbox(_L("Rotate upper part upwards"), m_rotate_upper); + //m_imgui->disabled_end(); + //m_imgui->disabled_begin(!m_keep_lower); + //m_imgui->text(""); + //ImGui::SameLine(m_label_width); + //m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + //m_imgui->disabled_end(); } m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); @@ -978,16 +1008,16 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) for (CutConnector& connector : mo->cut_connectors) { connector.rotation = m_rotation_gizmo.get_rotation(); - if (m_connector_style == size_t(CutConnectorStyle::Prizm)) { - if (m_connector_type == CutConnectorType::Dowel) + if (m_connector_type == CutConnectorType::Dowel) { + if (m_connector_style == size_t(CutConnectorStyle::Prizm)) connector.height *= 2; - else { - // culculate shift of the connector center regarding to the position on the cut plane - Vec3d norm = m_grabbers[0].center - m_plane_center; - norm.normalize(); - Vec3d shift = norm * (0.5 * connector.height); - connector.pos += shift; - } + } + else { + // culculate shift of the connector center regarding to the position on the cut plane + Vec3d norm = m_grabbers[0].center - m_plane_center; + norm.normalize(); + Vec3d shift = norm * (0.5 * connector.height); + connector.pos += shift; } } mo->apply_cut_connectors(_u8L("Connector"), CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); @@ -998,6 +1028,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) wxGetApp().plater()->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); m_selected.clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 57be3c739..99bc66d5e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -40,6 +40,7 @@ class GLGizmoCut3D : public GLGizmoBase bool m_keep_upper{ true }; bool m_keep_lower{ true }; + bool m_rotate_upper{ false }; bool m_rotate_lower{ false }; bool m_hide_cut_plane{ false }; From a42212487d618efc2d84287b0bc230395de30dc7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 23 Mar 2022 16:01:31 +0100 Subject: [PATCH 21/97] Cut WIP: * rewrite ObjectClipper::set_range_and_pos(). A calculation of a normal and distance is extracted from this function and are used for a putting of connectors to the cut plane. * Some replacement of items of the CutGizmo window --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 121 +++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 7 ++ src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 1 + 4 files changed, 66 insertions(+), 65 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index be040e729..a56218860 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -160,26 +160,21 @@ void GLGizmoCut3D::rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, c vec += center; } -void GLGizmoCut3D::put_connetors_on_cut_plane() +void GLGizmoCut3D::put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset) { ModelObject* mo = m_c->selection_info()->model_object(); if (CutConnectors& connectors = mo->cut_connectors; !connectors.empty()) { const float sla_shift = m_c->selection_info()->get_sla_shift(); const Vec3d& instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); - const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); - const Vec3d& normal = cp->get_normal(); - for (auto& connector : connectors) { // convert connetor pos to the world coordinates Vec3d pos = connector.pos + instance_offset; pos[Z] += sla_shift; - // scalar distance from point to plane along the normal - double distance = cp->distance(pos); - + double distance = -cp_normal.dot(pos) + cp_offset; // move connector - connector.pos += distance * normal; + connector.pos += distance * cp_normal; } } } @@ -200,9 +195,15 @@ void GLGizmoCut3D::update_clipper() double dist = (m_plane_center - beg).norm(); - m_c->object_clipper()->set_range_and_pos(beg, end, dist); + // calculate normal and offset for clipping plane + Vec3d normal = end - beg; + dist = std::clamp(dist, 0.0001, normal.norm()); + normal.normalize(); + const double offset = normal.dot(beg) + dist; - put_connetors_on_cut_plane(); + m_c->object_clipper()->set_range_and_pos(normal, offset, dist); + + put_connetors_on_cut_plane(normal, offset); } void GLGizmoCut3D::update_clipper_on_render() @@ -726,14 +727,22 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (m_mode == size_t(CutMode::cutPlanar)) { ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Move center")); + + m_imgui->disabled_begin(m_plane_center == bounding_box().center()); revert_move = render_revert_button("move"); + m_imgui->disabled_end(); + for (Axis axis : {X, Y, Z}) render_move_center_input(axis); m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Rotation")); + + m_imgui->disabled_begin(m_rotation_gizmo.get_rotation() == Vec3d::Zero()); revert_rotation = render_revert_button("rotation"); + m_imgui->disabled_end(); + for (Axis axis : {X, Y, Z}) render_rotation_input(axis); m_imgui->text(_L("°")); @@ -748,56 +757,6 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding(); } - - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("After cut") + ": "); - bool keep = true; - - ImGui::SameLine(m_label_width); - m_imgui->text(_L("Upper part")); - ImGui::SameLine(2*m_label_width); - - m_imgui->disabled_begin(!connectors.empty()); - m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); - m_imgui->disabled_end(); - ImGui::SameLine(); - m_imgui->disabled_begin(!m_keep_upper); - m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper); - m_imgui->disabled_end(); - - m_imgui->text(""); - ImGui::SameLine(m_label_width); - m_imgui->text(_L("Lower part")); - ImGui::SameLine(2*m_label_width); - - m_imgui->disabled_begin(!connectors.empty()); - m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); - m_imgui->disabled_end(); - ImGui::SameLine(); - m_imgui->disabled_begin(!m_keep_lower); - m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower); - m_imgui->disabled_end(); - - //m_imgui->disabled_begin(!connectors.empty()); - - //bool keep = true; - //m_imgui->checkbox(_L("Keep upper part"), connectors.empty() ? m_keep_upper : keep); - //m_imgui->text(""); - //ImGui::SameLine(m_label_width); - //m_imgui->checkbox(_L("Keep lower part"), connectors.empty() ? m_keep_lower : keep); - - //m_imgui->disabled_end(); - - //m_imgui->disabled_begin(!m_keep_upper); - //m_imgui->text(""); - //ImGui::SameLine(m_label_width); - //m_imgui->checkbox(_L("Rotate upper part upwards"), m_rotate_upper); - //m_imgui->disabled_end(); - //m_imgui->disabled_begin(!m_keep_lower); - //m_imgui->text(""); - //ImGui::SameLine(m_label_width); - //m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); - //m_imgui->disabled_end(); } m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); @@ -807,6 +766,12 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::AlignTextToFramePadding(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); + m_imgui->disabled_begin(connectors.empty()); + ImGui::SameLine(m_label_width); + if (m_imgui->button(" " + _L("Reset") + " ##connectors")) + reset_connectors(); + m_imgui->disabled_end(); + m_imgui->text(_L("Mode")); render_connect_mode_radio_button(CutConnectorMode::Auto); render_connect_mode_radio_button(CutConnectorMode::Manual); @@ -827,12 +792,40 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) for (auto& connector : connectors) connector.radius = float(m_connector_size * 0.5); - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || !can_perform_cut()); - if (m_imgui->button(_L("Reset connectors"))) - reset_connectors(); m_imgui->disabled_end(); - m_imgui->disabled_end(); + if (m_mode <= size_t(CutMode::cutByLine)) { + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("After cut") + ": "); + bool keep = true; + + ImGui::SameLine(m_label_width); + m_imgui->text(_L("Upper part")); + ImGui::SameLine(2 * m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); + m_imgui->disabled_end(); + ImGui::SameLine(); + m_imgui->disabled_begin(!m_keep_upper); + m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper); + m_imgui->disabled_end(); + + m_imgui->text(""); + ImGui::SameLine(m_label_width); + m_imgui->text(_L("Lower part")); + ImGui::SameLine(2 * m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); + m_imgui->disabled_end(); + ImGui::SameLine(); + m_imgui->disabled_begin(!m_keep_lower); + m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower); + m_imgui->disabled_end(); + } ImGui::Separator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 99bc66d5e..9fc43ec2a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -106,7 +106,7 @@ public: void shift_cut_z(double delta); void rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center); - void put_connetors_on_cut_plane(); + void put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset); void update_clipper(); void update_clipper_on_render(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index a6008b183..26003085e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -476,6 +476,13 @@ void ObjectClipper::set_range_and_pos(const Vec3d& origin, const Vec3d& end, dou get_pool()->get_canvas()->set_as_dirty(); } +void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos) +{ + m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset)); + m_clp_ratio = pos; + get_pool()->get_canvas()->set_as_dirty(); +} + const ClippingPlane* ObjectClipper::get_clipping_plane() const { static const ClippingPlane no_clip = ClippingPlane::ClipsNothing(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index c5c238408..226e5f7b5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -261,6 +261,7 @@ public: void render_cut() const; void set_position_by_ratio(double pos, bool keep_normal); void set_range_and_pos(const Vec3d& origin, const Vec3d& end, double pos); + void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); From f9e22513c10b3b5c120551ddea0a19a34601a6be Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 30 Mar 2022 11:48:22 +0200 Subject: [PATCH 22/97] Cut WIP: * Added a first detection if a connector position is valid * Code cleaning: Deleted unused set_range_and_pos function * Some code refactoring --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 148 ++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 11 -- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 1 - src/slic3r/GUI/MeshUtils.cpp | 11 ++ src/slic3r/GUI/MeshUtils.hpp | 2 + 6 files changed, 124 insertions(+), 58 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index a56218860..5ee74cc49 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -369,18 +369,6 @@ bool GLGizmoCut3D::render_revert_button(const std::string& label_id) void GLGizmoCut3D::render_cut_plane() { - const BoundingBoxf3 box = bounding_box(); - - const float min_x = box.min.x() - Margin - m_plane_center.x(); - const float max_x = box.max.x() + Margin - m_plane_center.x(); - const float min_y = box.min.y() - Margin - m_plane_center.y(); - const float max_y = box.max.y() + Margin - m_plane_center.y(); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); @@ -389,6 +377,12 @@ void GLGizmoCut3D::render_cut_plane() #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); @@ -410,14 +404,18 @@ void GLGizmoCut3D::render_cut_plane() #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (!m_plane.is_initialized()) { - m_plane.reset(); - GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; init_data.reserve_vertices(4); init_data.reserve_indices(6); + const BoundingBoxf3 bb = bounding_box(); + const float min_x = bb.min.x() - Margin - m_plane_center.x(); + const float max_x = bb.max.x() + Margin - m_plane_center.x(); + const float min_y = bb.min.y() - Margin - m_plane_center.y(); + const float max_y = bb.max.y() + Margin - m_plane_center.y(); + // vertices init_data.add_vertex(Vec3f(min_x, min_y, 0.0)); init_data.add_vertex(Vec3f(max_x, min_y, 0.0)); @@ -547,14 +545,9 @@ std::string GLGizmoCut3D::on_get_name() const void GLGizmoCut3D::on_set_state() { - if (get_state() == On) { + if (get_state() == On) update_bb(); - if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { - m_selected.clear(); - m_selected.resize(selection->model_object()->cut_connectors.size(), false); - } - } m_rotation_gizmo.set_center(m_plane_center); m_rotation_gizmo.set_state(m_state); @@ -653,6 +646,13 @@ bool GLGizmoCut3D::update_bb() m_min_pos = box.min; m_bb_center = box.center(); set_center_pos(m_bb_center + m_center_offset); + + m_plane.reset(); + if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { + m_selected.clear(); + m_selected.resize(selection->model_object()->cut_connectors.size(), false); + } + return true; } return false; @@ -841,7 +841,8 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) static bool hide_clipped = true; static bool fill_cut = true; static float contour_width = 0.2f; - m_imgui->checkbox("hide_clipped", hide_clipped); + if (m_imgui->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); m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); m_c->object_clipper()->set_behavior(hide_clipped, fill_cut, contour_width); @@ -860,11 +861,43 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) } } +// get volume transformation regarding to the "border". Border is related from the siae of connectors +Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) const +{ + bool is_prizm_dowel = m_connector_type == CutConnectorType::Dowel && m_connector_style == size_t(CutConnectorStyle::Prizm); + const Transform3d connector_trafo = Geometry::assemble_transform( + is_prizm_dowel ? Vec3d(0.0, 0.0, -m_connector_depth_ratio) : Vec3d::Zero(), + m_rotation_gizmo.get_rotation(), + Vec3d(0.5*m_connector_size, 0.5*m_connector_size, is_prizm_dowel ? 2 * m_connector_depth_ratio : m_connector_depth_ratio), + Vec3d::Ones()); + const Vec3d connector_bb = m_connector_mesh.transformed_bounding_box(connector_trafo).size(); + + const Vec3d bb = volume->mesh().bounding_box().size(); + + // calculate an unused border - part of the the volume, where we can't put connectors + const Vec3d border_scale(connector_bb.x() / bb.x(), connector_bb.y() / bb.y(), connector_bb.z() / bb.z()); + + const Transform3d vol_matrix = volume->get_matrix(); + const Vec3d vol_trans = vol_matrix.translation(); + // offset of the volume will be changed after scaling, so calculate the needed offset and set it to a volume_trafo + const Vec3d offset(vol_trans.x() * border_scale.x(), vol_trans.y() * border_scale.y(), vol_trans.z() * border_scale.z()); + + // scale and translate volume to suppress to put connectors too close to the border + return Geometry::assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix; +} + void GLGizmoCut3D::render_connectors(bool picking) { + m_has_invalid_connector = false; + if (m_connector_mode == CutConnectorMode::Auto) return; + const ModelObject* mo = m_c->selection_info()->model_object(); + const CutConnectors& connectors = mo->cut_connectors; + if (connectors.size() != m_selected.size()) + return; + #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat_attr") : wxGetApp().get_shader("gouraud_light_attr"); @@ -892,47 +925,59 @@ void GLGizmoCut3D::render_connectors(bool picking) #endif // ENABLE_GL_SHADERS_ATTRIBUTES ColorRGBA render_color; - const ModelObject* mo = m_c->selection_info()->model_object(); - const CutConnectors& connectors = mo->cut_connectors; - size_t cache_size = connectors.size(); - const Vec3d& instance_offset = mo->instances[m_c->selection_info()->get_active_instance()]->get_offset(); + const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; + const Vec3d& instance_offset = mi->get_offset(); const float sla_shift = m_c->selection_info()->get_sla_shift(); const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); - const Vec3d& normal = cp ? cp->get_normal() : Vec3d::Ones(); + const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; - for (size_t i = 0; i < cache_size; ++i) { + const Transform3d instance_trafo = Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift), Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones()) * mi->get_transformation().get_matrix(); + + for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; const bool& point_selected = m_selected[i]; + double height = connector.height; + // recalculate connector position to world position + Vec3d pos = connector.pos + instance_offset; + if (m_connector_type == CutConnectorType::Dowel && + m_connector_style == size_t(CutConnectorStyle::Prizm)) { + pos -= height * normal; + height *= 2; + } + pos[Z] += sla_shift; + // First decide about the color of the point. if (picking) render_color = picking_decode(BASE_ID - i - m_connectors_group_id); else { if (size_t(m_hover_id- m_connectors_group_id) == i) render_color = ColorRGBA::CYAN(); - else // neither hover nor picking - render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); - } + else { // neither hover nor picking + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + ++mesh_id; + if (!mv->is_model_part()) + continue; -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_connector_shape.set_color(render_color); -#else - const_cast(&m_connector_shape)->set_color(-1, render_color); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + const Transform3d volume_trafo = get_volume_transformation(mv); - double height = connector.height; - // recalculate connector position to world position - Vec3d pos = connector.pos + instance_offset; - if (m_connector_type == CutConnectorType::Dowel && - m_connector_style == size_t(CutConnectorStyle::Prizm)) { - pos -= height * normal; - height *= 2; + if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { + render_color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + break; + } + render_color = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); + } + if (!m_has_invalid_connector && render_color == ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f)) + m_has_invalid_connector = true; + } } - pos[Z] += sla_shift; #if ENABLE_GL_SHADERS_ATTRIBUTES + m_connector_shape.set_color(render_color); + const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( Vec3d(pos.x(), pos.y(), pos.z()), m_rotation_gizmo.get_rotation(), @@ -942,6 +987,8 @@ void GLGizmoCut3D::render_connectors(bool picking) shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #else + const_cast(&m_connector_shape)->set_color(-1, render_color); + glsafe(::glPushMatrix()); glsafe(::glTranslatef(pos.x(), pos.y(), pos.z())); @@ -968,6 +1015,9 @@ void GLGizmoCut3D::render_connectors(bool picking) bool GLGizmoCut3D::can_perform_cut() const { + if (m_has_invalid_connector) + return false; + BoundingBoxf3 box = bounding_box(); double dist = (m_plane_center - box.center()).norm(); if (dist > box.radius()) @@ -1053,7 +1103,10 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair Vec3f hit; Vec3f normal; bool clipping_plane_was_hit = false; - m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * mv->get_matrix(), + + const Transform3d volume_trafo = get_volume_transformation(mv); + + m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * volume_trafo, camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), nullptr, &clipping_plane_was_hit); if (clipping_plane_was_hit) { @@ -1082,7 +1135,12 @@ void GLGizmoCut3D::update_connector_shape() if (m_connector_shape.is_initialized()) m_connector_shape.reset(); - m_connector_shape.init_from(ModelObject::get_connector_mesh({ m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) })); + const indexed_triangle_set its = ModelObject::get_connector_mesh({ m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }); + m_connector_shape.init_from(its); + + m_connector_mesh.clear(); + m_connector_mesh = TriangleMesh(its); + } void GLGizmoCut3D::update_model_object() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 9fc43ec2a..ca49d2e4e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -11,6 +11,7 @@ namespace Slic3r { enum class CutConnectorType : int; +class ModelVolume; namespace GUI { class Selection; @@ -30,7 +31,10 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_bb_center{ Vec3d::Zero() }; Vec3d m_center_offset{ Vec3d::Zero() }; - GLModel m_connector_shape; + GLModel m_connector_shape; + TriangleMesh m_connector_mesh; + // workaround for using of the clipping plane normal + Vec3d m_clp_normal{ Vec3d::Ones() }; #if ENABLE_LEGACY_OPENGL_REMOVAL GLModel m_plane; @@ -57,6 +61,8 @@ class GLGizmoCut3D : public GLGizmoBase bool m_selection_empty = true; bool m_wait_for_up_event = false; + bool m_has_invalid_connector{ false }; + Matrix3d m_rotation_matrix; Vec3d m_rotations{ Vec3d::Zero() }; @@ -136,6 +142,7 @@ private: void render_connect_mode_radio_button(CutConnectorMode mode); bool render_revert_button(const std::string& label); void render_connect_type_radio_button(CutConnectorType type); + Transform3d get_volume_transformation(const ModelVolume* volume) const; void render_connectors(bool picking); bool can_perform_cut() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 26003085e..7f60892b1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -465,17 +465,6 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) get_pool()->get_canvas()->set_as_dirty(); } -void ObjectClipper::set_range_and_pos(const Vec3d& origin, const Vec3d& end, double pos) -{ - Vec3d normal = end-origin; - double norm = normal.norm(); - pos = std::clamp(pos, 0.0001, norm); - normal.normalize(); - m_clp.reset(new ClippingPlane(normal, normal.dot(origin)+pos)); - m_clp_ratio = pos; - get_pool()->get_canvas()->set_as_dirty(); -} - void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos) { m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 226e5f7b5..efd3b436e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -260,7 +260,6 @@ public: const ClippingPlane* get_clipping_plane() const; void render_cut() const; void set_position_by_ratio(double pos, bool keep_normal); - void set_range_and_pos(const Vec3d& origin, const Vec3d& end, double pos); void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index e5cd1f79e..ca274e429 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -416,6 +416,17 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& } +bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const +{ + point = trafo.inverse() * point; + + std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector neg_hits = m_emesh.query_ray_hits(point, -direction); + + return !hits.empty() && !neg_hits.empty(); +} + + std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, const ClippingPlane* clipping_plane) const { diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index dc522eb3b..0faf07e29 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -157,6 +157,8 @@ public: bool* was_clipping_plane_hit = nullptr // is the hit on the clipping place cross section? ) const; + bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const; + // Given a vector of points in woorld coordinates, this returns vector // of indices of points that are visible (i.e. not cut by clipping plane // or obscured by part of the mesh. From 463e9ab5300e74f1e665f5871ae446e0404c8453 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 11 Apr 2022 11:20:00 +0200 Subject: [PATCH 23/97] Cut WIP: + Added CutObjectBase class which contains cut attributes for object + ObjectList and ManipulationPanel : * Disable all ManipulationEditors for solid/negative volumes of cut object * Disable Scale/Size ManipulationEditors for objects/instances of objects which are CutParts of initial object + Scale/Rotation/Move gizmos are disabled for solid/negative volumes of cut object + Select whole CutParts of initial object when ScaleGizmo is active --- src/libslic3r/Model.cpp | 42 ++++++- src/libslic3r/Model.hpp | 6 + src/libslic3r/ObjectID.hpp | 35 ++++++ src/slic3r/GUI/GLCanvas3D.cpp | 5 + src/slic3r/GUI/GUI_ObjectList.cpp | 129 +++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 3 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 16 +++ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 5 + src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 6 + src/slic3r/GUI/Plater.cpp | 6 +- 12 files changed, 261 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ed341dfc6..829c76fb5 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,4 +1,5 @@ #include "Model.hpp" +#include "Model.hpp" #include "libslic3r.h" #include "BuildVolume.hpp" #include "Exception.hpp" @@ -610,6 +611,8 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->layer_height_profile = rhs.layer_height_profile; this->printable = rhs.printable; this->origin_translation = rhs.origin_translation; + this->cut_connectors_count = rhs.cut_connectors_count; + this->cut_id.copy(rhs.cut_id); m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; m_raw_bounding_box = rhs.m_raw_bounding_box; @@ -1369,12 +1372,17 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes) { + // discard old connector markers vor volumes + for (ModelVolume* volume : volumes) { + volume->source.is_connector = false; + } + if (cut_connectors.empty()) return; indexed_triangle_set connector_mesh = get_connector_mesh(connector_attributes); - size_t connector_id = 0; + size_t connector_id = cut_connectors_count; for (const CutConnector& connector : cut_connectors) { TriangleMesh mesh = TriangleMesh(connector_mesh); @@ -1393,10 +1401,20 @@ void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttr new_volume->source.is_connector = true; } + cut_connectors_count += cut_connectors.size(); // delete all connectors cut_connectors.clear(); } +void ModelObject::synchronize_model_after_cut() +{ + for (ModelObject* obj : m_model->objects) { + if (obj == this || obj->cut_id.is_equal(this->cut_id)) + continue; + obj->cut_id.set_check_sum(this->cut_id.check_sum()); + } +} + ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes) { if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) @@ -1404,6 +1422,18 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + // initiate/update cut attributes for object + if (cut_id.id().invalid()) + cut_id.init(); + { + int cut_obj_cnt = -1; + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::KeepLower)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) cut_obj_cnt++; + if (cut_obj_cnt > 0) + cut_id.increase_check_sum(size_t(cut_obj_cnt)); + } + // Clone the object to duplicate instances, materials etc. ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; @@ -1499,10 +1529,16 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); } } - else + else { // Modifiers are not cut, but we still need to add the instance transformation // to the modifier volume transformation to preserve their shape properly. volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower->add_volume(*volume); + } } else if (!volume->mesh().empty() // && !volume->source.is_connector // we don't allow to cut a connectors @@ -1646,6 +1682,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + synchronize_model_after_cut(); + return res; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b3c5b47db..6fa724162 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -346,6 +346,9 @@ public: // Connectors to be added into the object after cut CutConnectors cut_connectors; + // count of connectors in object + size_t cut_connectors_count{ 0 }; + CutObjectBase cut_id; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation @@ -434,6 +437,7 @@ public: ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes); + void synchronize_model_after_cut(); ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); void merge(); @@ -458,6 +462,8 @@ public: // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) int get_repaired_errors_count(const int vol_idx = -1) const; + bool is_cut() const { return cut_id.id().valid(); } + private: friend class Model; // This constructor assigns new ID to this ModelObject and its config. diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 1030171e7..599c243b5 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -128,6 +128,41 @@ private: template void serialize(Archive &ar) { ar(m_timestamp); } }; +class CutObjectBase : public ObjectBase +{ + // check sum of CutPartsObject + size_t m_check_sum{ 1 }; + +public: + // Default Constructor to assign an invalid ID + CutObjectBase() : ObjectBase(-1) {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + CutObjectBase(int) : ObjectBase(-1) {} + // The class tree will have virtual tables and type information. + virtual ~CutObjectBase() = default; + + bool operator<(const CutObjectBase& other) const { return other.id() > this->id(); } + bool operator==(const CutObjectBase& other) const { return other.id() == this->id(); } + + void copy(const CutObjectBase& rhs) { + this->copy_id(rhs); + this->m_check_sum = rhs.check_sum(); + } + CutObjectBase operator=(const CutObjectBase& other) { + this->copy(other); + return *this; + } + + void init() { this->set_new_unique_id(); } + bool has_same_id(const CutObjectBase& rhs) { return this->id() == rhs.id(); } + bool is_equal(const CutObjectBase& rhs) { return this->id() == rhs.id() && this->check_sum() == rhs.check_sum(); } + + size_t check_sum() const { return m_check_sum; } + void set_check_sum(size_t cs) { m_check_sum = cs; } + void increase_check_sum(size_t cnt) { m_check_sum += cnt; } +}; + // Unique object / instance ID for the wipe tower. extern ObjectID wipe_tower_object_id(); extern ObjectID wipe_tower_instance_id(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 91add7c56..5fe2dab23 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3360,6 +3360,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) show_sinking_contours(); } } + else if (evt.LeftUp() && + m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && + m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { + wxGetApp().obj_list()->selection_changed(); + } #if ENABLE_OBJECT_MANIPULATOR_FOCUS handle_sidebar_focus_event("", false); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7462e8153..3cb5bf3ce 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -665,6 +665,8 @@ void ObjectList::selection_changed() fix_multiselection_conflicts(); + fix_cut_selection(); + // update object selection on Plater if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); @@ -2437,6 +2439,9 @@ void ObjectList::part_selection_changed() bool update_and_show_settings = false; bool update_and_show_layers = false; + bool enable_manipulation {true}; + bool disable_ss_manipulation {false}; + const auto item = GetSelection(); if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { @@ -2445,6 +2450,43 @@ void ObjectList::part_selection_changed() const Selection& selection = scene_selection(); // don't show manipulation panel for case of all Object's parts selection update_and_show_manipulations = !selection.is_single_full_instance(); + + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) { + if (selection.is_any_volume() || selection.is_any_modifier()) + enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); + else// if (item && m_objects_model->GetItemType(item) == itInstanceRoot) + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + } + else { + wxDataViewItemArray sels; + GetSelections(sels); + if (selection.is_single_full_object() || selection.is_multiple_full_instance() ) { + int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + } + else if (selection.is_mixed() || selection.is_multiple_full_object()) { + std::map> cut_objects; + + // find cut objects + for (auto item : sels) { + int obj_idx = m_objects_model->GetObjectIdByItem(item); + const ModelObject* obj = object(obj_idx); + if (obj->is_cut()) { + if (cut_objects.find(obj->cut_id) == cut_objects.end()) + cut_objects[obj->cut_id] = std::set{ obj_idx }; + else + cut_objects.at(obj->cut_id).insert(obj_idx); + } + } + + // check if selected cut objects are "full selected" + for (auto cut_object : cut_objects) + if (cut_object.first.check_sum() != cut_object.second.size()) { + disable_ss_manipulation = true; + break; + } + } + } } else { if (item) { @@ -2486,6 +2528,8 @@ void ObjectList::part_selection_changed() default: { break; } } } + else + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } else { if (type & itSettings) { @@ -2509,6 +2553,7 @@ void ObjectList::part_selection_changed() volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; + enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); } else if (type & itInstance) { og_name = _L("Instance manipulation"); @@ -2516,6 +2561,7 @@ void ObjectList::part_selection_changed() // fill m_config by object's values m_config = &(*m_objects)[obj_idx]->config; + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } else if (type & (itLayerRoot|itLayer)) { og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range"); @@ -2538,6 +2584,11 @@ void ObjectList::part_selection_changed() wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_info(obj_idx, volume_id)); } + + if (disable_ss_manipulation) + wxGetApp().obj_manipul()->DisableScale(); + else + wxGetApp().obj_manipul()->Enable(enable_manipulation); } if (update_and_show_settings) @@ -2554,6 +2605,7 @@ void ObjectList::part_selection_changed() panel.Freeze(); wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + wxGetApp().plater()->canvas3D()->enable_moving(enable_manipulation); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); @@ -3420,11 +3472,34 @@ void ObjectList::update_selections() if (sels.size() == 0 || m_selection_mode & smSettings) m_selection_mode = smUndef; - - select_items(sels); - // Scroll selected Item in the middle of an object list - ensure_current_item_visible(); + if (fix_cut_selection(sels)) { + m_prevent_list_events = true; + + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + + m_prevent_list_events = false; + + // update object selection on Plater + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); + + // to update the toolbar and info sizer + if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { + auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT); + event.SetEventObject(this); + wxPostEvent(this, event); + } + part_selection_changed(); + } + else { + select_items(sels); + + // Scroll selected Item in the middle of an object list + ensure_current_item_visible(); + } } void ObjectList::update_selections_on_canvas() @@ -3753,6 +3828,52 @@ void ObjectList::fix_multiselection_conflicts() m_prevent_list_events = false; } +bool ObjectList::fix_cut_selection(wxDataViewItemArray& sels) +{ + if (wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Scale) { + for (const auto& item : sels) { + if (m_objects_model->GetItemType(item) & (itInstance | itObject) || + (m_objects_model->GetItemType(item) & itSettings && + m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject)) { + + bool is_instance_selection = m_objects_model->GetItemType(item) & itInstance; + + int obj_idx = m_objects_model->GetObjectIdByItem(item); + int inst_idx = is_instance_selection ? m_objects_model->GetInstanceIdByItem(item) : 0; + + if (auto obj = object(obj_idx); obj->is_cut()) { + sels.Clear(); + + auto cut_id = obj->cut_id; + + for (int obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx) { + auto object = (*m_objects)[obj_idx]; + if (object->is_cut() && object->cut_id.has_same_id(cut_id)) + sels.Add(is_instance_selection ? m_objects_model->GetItemByInstanceId(obj_idx, inst_idx) : m_objects_model->GetItemById(obj_idx)); + } + return true; + } + } + } + } + return false; +} + +void ObjectList::fix_cut_selection() +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (fix_cut_selection(sels)) { + m_prevent_list_events = true; + + // If some part is selected, unselect all items except of selected parts of the current object + UnselectAll(); + SetSelections(sels); + + m_prevent_list_events = false; + } +} + ModelVolume* ObjectList::get_selected_model_volume() { wxDataViewItem item = GetSelection(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b9b816b7b..45072d4a5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -353,6 +353,9 @@ public: bool check_last_selection(wxString& msg_str); // correct current selections to avoid of the possible conflicts void fix_multiselection_conflicts(); + // correct selection in respect to the cut_id if any exists + void fix_cut_selection(); + bool fix_cut_selection(wxDataViewItemArray& sels); ModelVolume* get_selected_model_volume(); void change_part_type(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6ab87150b..089e89dfe 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -465,6 +465,22 @@ void ObjectManipulation::UpdateAndShow(const bool show) OG_Settings::UpdateAndShow(show); } +void ObjectManipulation::Enable(const bool enadle) +{ + for (auto editor : m_editors) + editor->Enable(enadle); + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt }) + win->Enable(enadle); +} + +void ObjectManipulation::DisableScale() +{ + for (auto editor : m_editors) + editor->Enable(editor->has_opt_key("scale") || editor->has_opt_key("size") ? false : true); + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_lock_bnt }) + win->Enable(false); +} + void ObjectManipulation::update_ui_from_settings() { if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index a15c72fb8..3a2eca2b6 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -57,6 +57,8 @@ public: void set_value(const wxString& new_value); void kill_focus(ObjectManipulation *parent); + bool has_opt_key(const std::string& key) { return m_opt_key == key; } + private: double get_value(); }; @@ -173,6 +175,9 @@ public: void Show(const bool show) override; bool IsShown() override; void UpdateAndShow(const bool show) override; + void Enable(const bool enadle = true); + void Disable() { Enable(false); } + void DisableScale(); void update_ui_from_settings(); bool use_colors() { return m_use_colors; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index fb1269d26..a172672ea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -5,6 +5,7 @@ #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#include "libslic3r/Model.hpp" #include @@ -63,7 +64,13 @@ std::string GLGizmoMove3D::on_get_name() const bool GLGizmoMove3D::on_is_activable() const { - return !m_parent.get_selection().is_empty(); + const Selection& selection = m_parent.get_selection(); + if (selection.is_any_volume() || selection.is_any_modifier()) { + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) + return !m_parent.get_model()->objects[obj_idx]->is_cut(); + } + + return !selection.is_empty(); } void GLGizmoMove3D::on_start_dragging() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 6ab87e025..6b61befcf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Model.hpp" #include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" @@ -814,7 +815,13 @@ std::string GLGizmoRotate3D::on_get_name() const bool GLGizmoRotate3D::on_is_activable() const { - return !m_parent.get_selection().is_empty(); + const Selection& selection = m_parent.get_selection(); + if (selection.is_any_volume() || selection.is_any_modifier()) { + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) + return !m_parent.get_model()->objects[obj_idx]->is_cut(); + } + + return !selection.is_empty(); } void GLGizmoRotate3D::on_start_dragging() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 373a2396d..be7c797be 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -5,6 +5,7 @@ #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#include "libslic3r/Model.hpp" #include @@ -136,6 +137,11 @@ std::string GLGizmoScale3D::on_get_name() const bool GLGizmoScale3D::on_is_activable() const { const Selection& selection = m_parent.get_selection(); + if (selection.is_any_volume() || selection.is_any_modifier()) { + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) + return !m_parent.get_model()->objects[obj_idx]->is_cut(); + } + return !selection.is_empty() && !selection.is_wipe_tower(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b88cd7ce8..bff535bed 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4860,7 +4860,8 @@ bool Plater::priv::can_increase_instances() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) + && !model.objects[obj_idx]->is_cut(); } bool Plater::priv::can_decrease_instances() const @@ -4870,7 +4871,8 @@ bool Plater::priv::can_decrease_instances() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1) + && !model.objects[obj_idx]->is_cut(); } bool Plater::priv::can_split_to_objects() const From c29b7b1eef193c83914e26acb647c9d2b76bccd0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 12 Apr 2022 14:27:34 +0200 Subject: [PATCH 24/97] Cut WIP: * Suppress to delete/add a SolidPart/NegativeVolume from/for objects which are marked as "is cut" * Suppress to delete Instances which are marked as "is cut" * Allow delete an object which is marked as "is cut", but show warning message about break of the "cut consistency". And if this deletion was performed, the all related objects will be unmarked. * m_connectors_cnt is added into CutObjectBase class to correct synchronization of a connectors count between related objects --- src/libslic3r/Model.cpp | 17 +++++++++++------ src/libslic3r/Model.hpp | 4 ++-- src/libslic3r/ObjectID.hpp | 18 ++++++++++++++++-- src/slic3r/GUI/GUI_Factories.cpp | 9 +++++++-- src/slic3r/GUI/Plater.cpp | 26 +++++++++++++++++++++----- src/slic3r/GUI/Plater.hpp | 2 +- 6 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 829c76fb5..30dd64aa0 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,5 +1,4 @@ #include "Model.hpp" -#include "Model.hpp" #include "libslic3r.h" #include "BuildVolume.hpp" #include "Exception.hpp" @@ -611,7 +610,6 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->layer_height_profile = rhs.layer_height_profile; this->printable = rhs.printable; this->origin_translation = rhs.origin_translation; - this->cut_connectors_count = rhs.cut_connectors_count; this->cut_id.copy(rhs.cut_id); m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; @@ -1382,8 +1380,7 @@ void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttr indexed_triangle_set connector_mesh = get_connector_mesh(connector_attributes); - size_t connector_id = cut_connectors_count; - + size_t connector_id = cut_id.connectors_cnt(); for (const CutConnector& connector : cut_connectors) { TriangleMesh mesh = TriangleMesh(connector_mesh); // Mesh will be centered when loading. @@ -1400,18 +1397,26 @@ void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttr new_volume->name = name + "-" + std::to_string(++connector_id); new_volume->source.is_connector = true; } + cut_id.increase_connectors_cnt(cut_connectors.size()); - cut_connectors_count += cut_connectors.size(); // delete all connectors cut_connectors.clear(); } +void ModelObject::invalidate_cut() +{ + for (ModelObject* obj : m_model->objects) + if (obj != this && obj->cut_id.is_equal(this->cut_id)) + obj->cut_id.ivalidate(); +} + void ModelObject::synchronize_model_after_cut() { for (ModelObject* obj : m_model->objects) { if (obj == this || obj->cut_id.is_equal(this->cut_id)) continue; - obj->cut_id.set_check_sum(this->cut_id.check_sum()); + if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id)) + obj->cut_id.copy(this->cut_id); } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 6fa724162..3afa94c75 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -346,8 +346,6 @@ public: // Connectors to be added into the object after cut CutConnectors cut_connectors; - // count of connectors in object - size_t cut_connectors_count{ 0 }; CutObjectBase cut_id; /* This vector accumulates the total translation applied to the object by the @@ -437,6 +435,8 @@ public: ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes); + // invalidate cut state for this and related objects from the whole model + void invalidate_cut(); void synchronize_model_after_cut(); ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 599c243b5..42cd21699 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -130,8 +130,10 @@ private: class CutObjectBase : public ObjectBase { - // check sum of CutPartsObject + // check sum of CutParts in initial Object size_t m_check_sum{ 1 }; + // connectors count + size_t m_connectors_cnt{ 0 }; public: // Default Constructor to assign an invalid ID @@ -148,19 +150,31 @@ public: void copy(const CutObjectBase& rhs) { this->copy_id(rhs); this->m_check_sum = rhs.check_sum(); + this->m_connectors_cnt = rhs.connectors_cnt() ; } CutObjectBase operator=(const CutObjectBase& other) { this->copy(other); return *this; } + void ivalidate() { + set_invalid_id(); + m_check_sum = 1; + m_connectors_cnt = 0; + } + void init() { this->set_new_unique_id(); } bool has_same_id(const CutObjectBase& rhs) { return this->id() == rhs.id(); } - bool is_equal(const CutObjectBase& rhs) { return this->id() == rhs.id() && this->check_sum() == rhs.check_sum(); } + bool is_equal(const CutObjectBase& rhs) { return this->id() == rhs.id() && + this->check_sum() == rhs.check_sum() && + this->connectors_cnt() == rhs.connectors_cnt() ; } size_t check_sum() const { return m_check_sum; } void set_check_sum(size_t cs) { m_check_sum = cs; } void increase_check_sum(size_t cnt) { m_check_sum += cnt; } + + size_t connectors_cnt() const { return m_connectors_cnt; } + void increase_connectors_cnt(size_t connectors_cnt) { m_connectors_cnt += connectors_cnt; } }; // Unique object / instance ID for the wipe tower. diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 7b3476d71..d5e7260af 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -494,7 +494,9 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu) append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", [](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); }, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, - []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + []() { return obj_list()->is_instance_or_object_selected() + && !obj_list()->is_selected_object_cut(); + }, m_parent); } if (mode == comSimple) { append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", @@ -515,7 +517,10 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu) wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type)); append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, - []() { return obj_list()->is_instance_or_object_selected(); }, m_parent); + [type]() { + bool can_add = type < size_t(ModelVolumeType::PARAMETER_MODIFIER) ? !obj_list()->is_selected_object_cut() : true; + return can_add && obj_list()->is_instance_or_object_selected(); + }, m_parent); } append_menu_item_layers_editing(menu); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bff535bed..cc577da2f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1794,7 +1794,7 @@ struct Plater::priv void select_all(); void deselect_all(); void remove(size_t obj_idx); - void delete_object_from_model(size_t obj_idx); + bool delete_object_from_model(size_t obj_idx); void delete_all_objects_from_model(); void reset(); void mirror(Axis axis); @@ -2959,16 +2959,32 @@ void Plater::priv::remove(size_t obj_idx) } -void Plater::priv::delete_object_from_model(size_t obj_idx) +bool Plater::priv::delete_object_from_model(size_t obj_idx) { + // check if object isn't cut + // show warning message that "cut consistancy" will not be supported any more + ModelObject* obj = model.objects[obj_idx]; + if (obj->is_cut()) { + MessageDialog dialog(q, _L("You try to delete an object which is a part of a cut object.\n" + "This action will break a cut correspondence.\n" + "After that PrusaSlicer can't garantie model consistency"), + _L("Delete object which is a part of cut object"), wxYES | wxCANCEL | wxCANCEL_DEFAULT); + dialog.SetButtonLabel(wxID_YES, _L("Delete object")); + if (dialog.ShowModal() == wxID_CANCEL) + return false; + // unmark all related CutParts of initial object + obj->invalidate_cut(); + } + wxString snapshot_label = _L("Delete Object"); - if (! model.objects[obj_idx]->name.empty()) - snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); + if (!obj->name.empty()) + snapshot_label += ": " + wxString::FromUTF8(obj->name.c_str()); Plater::TakeSnapshot snapshot(q, snapshot_label); m_worker.cancel_all(); model.delete_object(obj_idx); update(); object_list_changed(); + return true; } void Plater::priv::delete_all_objects_from_model() @@ -5679,7 +5695,7 @@ void Plater::reset_with_confirm() reset(); } -void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); } +bool Plater::delete_object_from_model(size_t obj_idx) { return p->delete_object_from_model(obj_idx); } void Plater::remove_selected() { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a168c32d1..39aa7cf8b 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -242,7 +242,7 @@ public: void remove(size_t obj_idx); void reset(); void reset_with_confirm(); - void delete_object_from_model(size_t obj_idx); + bool delete_object_from_model(size_t obj_idx); void remove_selected(); void increase_instances(size_t num = 1); void decrease_instances(size_t num = 1); From 87e1df2fb2bf15e3fe2862261c76c92b47498914 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 13 Apr 2022 13:16:29 +0200 Subject: [PATCH 25/97] Cut WIP: Lock icon is added for objects after a cut performing * ObjectDataViewModel: Some code refactoring to update bitmap in respect to the warning mane and lock appearance --- src/slic3r/GUI/GUI_ObjectList.cpp | 112 ++++++++++++++------ src/slic3r/GUI/GUI_ObjectList.hpp | 11 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 135 +++++++++++++++---------- src/slic3r/GUI/ObjectDataViewModel.hpp | 30 +++--- 4 files changed, 182 insertions(+), 106 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3cb5bf3ce..9b2edb660 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1383,6 +1383,15 @@ bool ObjectList::is_instance_or_object_selected() return selection.is_single_full_instance() || selection.is_single_full_object(); } +bool ObjectList::is_selected_object_cut() +{ + const Selection& selection = scene_selection(); + int obj_idx = selection.get_object_idx(); + if (obj_idx < 0) + return false; + return object(obj_idx)->is_cut(); +} + void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false*/) { if (type == ModelVolumeType::INVALID && from_galery) { @@ -1779,22 +1788,22 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name #endif /* _DEBUG */ } -void ObjectList::del_object(const int obj_idx) +bool ObjectList::del_object(const int obj_idx) { - wxGetApp().plater()->delete_object_from_model(obj_idx); + return wxGetApp().plater()->delete_object_from_model(obj_idx); } // Delete subobject -void ObjectList::del_subobject_item(wxDataViewItem& item) +bool ObjectList::del_subobject_item(wxDataViewItem& item) { - if (!item) return; + if (!item) return false; int obj_idx, idx; ItemType type; m_objects_model->GetItemInfo(item, type, obj_idx, idx); if (type == itUndef) - return; + return false; wxDataViewItem parent = m_objects_model->GetParent(item); @@ -1808,10 +1817,8 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); else if (type & itInfo && obj_idx != -1) del_info_item(obj_idx, m_objects_model->GetInfoItemType(item)); - else if (idx == -1) - return; - else if (!del_subobject_from_object(obj_idx, idx, type)) - return; + else if (idx == -1 || !del_subobject_from_object(obj_idx, idx, type)) + return false; // If last volume item with warning was deleted, unmark object item if (type & itVolume) { @@ -1821,6 +1828,8 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) m_objects_model->Delete(item); update_info_items(obj_idx); + + return true; } void ObjectList::del_info_item(const int obj_idx, InfoItemType type) @@ -1965,6 +1974,16 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object.")); return false; } + if (object->is_cut()) { + if (volume->is_model_part()) { + Slic3r::GUI::show_error(nullptr, _L("Solid part cannot be deleted from cut object.")); + return false; + } + if (volume->is_negative_volume()) { + Slic3r::GUI::show_error(nullptr, _L("Negative volume cannot be deleted from cut object.")); + return false; + } + } take_snapshot(_L("Delete Subobject")); @@ -1992,6 +2011,10 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con Slic3r::GUI::show_error(nullptr, _L("Last instance of an object cannot be deleted.")); return false; } + if (object->is_cut()) { + Slic3r::GUI::show_error(nullptr, _L("Instance cannot be deleted from cut object.")); + return false; + } take_snapshot(_L("Delete Instance")); object->delete_instance(idx); @@ -2735,7 +2758,8 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) const wxString& item_name = from_u8(model_object->name); const auto item = m_objects_model->Add(item_name, model_object->config.has("extruder") ? model_object->config.extruder() : 0, - get_warning_icon_name(model_object->mesh().stats())); + get_warning_icon_name(model_object->mesh().stats()), + model_object->is_cut()); update_info_items(obj_idx, nullptr, call_selection_changed); @@ -2805,29 +2829,40 @@ void ObjectList::delete_instance_from_list(const size_t obj_idx, const size_t in select_item([this, obj_idx, inst_idx]() { return m_objects_model->Delete(m_objects_model->GetItemByInstanceId(obj_idx, inst_idx)); }); } -void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx) +void ObjectList::update_lock_icons_for_model() { - if ( !(type&(itObject|itVolume|itInstance)) ) - return; - - take_snapshot(_(L("Delete Selected Item"))); - - if (type&itObject) { - del_object(obj_idx); - delete_object_from_list(obj_idx); - } - else { - del_subobject_from_object(obj_idx, sub_obj_idx, type); - - type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) : - delete_instance_from_list(obj_idx, sub_obj_idx); - } + for (int obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx) + if (!(*m_objects)[obj_idx]->is_cut()) + m_objects_model->UpdateLockIcon(m_objects_model->GetItemById(obj_idx), false); } -void ObjectList::delete_from_model_and_list(const std::vector& items_for_delete) +bool ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx) +{ +// take_snapshot(_(L("Delete Selected Item"))); // #ysFIXME - delete this redundant snapshot after test + + if (type & (itObject | itVolume | itInstance)) { + if (type & itObject) { + bool was_cut = object(obj_idx)->is_cut(); + if (del_object(obj_idx)) { + delete_object_from_list(obj_idx); + if (was_cut) + update_lock_icons_for_model(); + return true; + } + } + else if (del_subobject_from_object(obj_idx, sub_obj_idx, type)) { + type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) : + delete_instance_from_list(obj_idx, sub_obj_idx); + return true; + } + } + return false; +} + +bool ObjectList::delete_from_model_and_list(const std::vector& items_for_delete) { if (items_for_delete.empty()) - return; + return false; m_prevent_list_events = true; @@ -2836,8 +2871,12 @@ void ObjectList::delete_from_model_and_list(const std::vector& it if (!(item->type&(itObject | itVolume | itInstance))) continue; if (item->type&itObject) { - del_object(item->obj_idx); + bool was_cut = object(item->obj_idx)->is_cut(); + if (!del_object(item->obj_idx)) + continue; m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx)); + if (was_cut) + update_lock_icons_for_model(); } else { if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) @@ -2867,8 +2906,12 @@ void ObjectList::delete_from_model_and_list(const std::vector& it update_info_items(id); } - m_prevent_list_events = true; + m_prevent_list_events = false; + if (modified_objects_ids.empty()) + return false; part_selection_changed(); + + return true; } void ObjectList::delete_all_objects_from_list() @@ -2973,8 +3016,10 @@ void ObjectList::remove() { wxDataViewItem parent = m_objects_model->GetParent(item); ItemType type = m_objects_model->GetItemType(item); - if (type & itObject) - delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); + if (type & itObject) { + if (!delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1)) + return item; + } else { if (type & (itLayer | itInstance)) { // In case there is just one layer or two instances and we delete it, del_subobject_item will @@ -2984,7 +3029,8 @@ void ObjectList::remove() parent = m_objects_model->GetTopParent(item); } - del_subobject_item(item); + if (!del_subobject_item(item)) + return item; } return parent; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 45072d4a5..25614581b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -247,7 +247,7 @@ public: void add_category_to_settings_from_frequent(const std::vector& category_options, wxDataViewItem item); void show_settings(const wxDataViewItem settings_item); bool is_instance_or_object_selected(); - + bool is_selected_object_cut(); void load_subobject(ModelVolumeType type, bool from_galery = false); // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common //void load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false); @@ -257,8 +257,8 @@ public: void load_shape_object_from_gallery(); void load_shape_object_from_gallery(const wxArrayString& input_files); void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true); - void del_object(const int obj_idx); - void del_subobject_item(wxDataViewItem& item); + bool del_object(const int obj_idx); + bool del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); void del_instances_from_object(const int obj_idx); void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); @@ -295,8 +295,9 @@ public: void delete_object_from_list(const size_t obj_idx); void delete_volume_from_list(const size_t obj_idx, const size_t vol_idx); void delete_instance_from_list(const size_t obj_idx, const size_t inst_idx); - void delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx); - void delete_from_model_and_list(const std::vector& items_for_delete); + void update_lock_icons_for_model(); + bool delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx); + bool delete_from_model_and_list(const std::vector& items_for_delete); // Delete all objects from the list void delete_all_objects_from_list(); // Increase instances count diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 908307125..619de379b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -38,6 +38,7 @@ static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char WarningIcon[] = "exclamation"; static constexpr char WarningManifoldIcon[] = "exclamation_manifold"; +static constexpr char LockIcon[] = "lock_closed_white"; struct InfoItemAtributes { std::string name; @@ -57,19 +58,15 @@ const std::map INFO_ITEMS{ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmap& bmp, const wxString& extruder, - const int idx/* = -1*/, - const std::string& warning_icon_name /*= std::string*/) : + const int idx/* = -1*/) : m_parent(parent), m_name(sub_obj_name), m_type(itVolume), m_volume_type(type), m_idx(idx), - m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : ""), - m_warning_icon_name(warning_icon_name) + m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : "") { - m_bmp = bmp; set_action_and_extruder_icons(); init_container(); } @@ -174,13 +171,6 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) create_scaled_bitmap(m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); } -void ObjectDataViewModelNode::set_warning_icon(const std::string& warning_icon_name) -{ - m_warning_icon_name = warning_icon_name; - if (warning_icon_name.empty()) - m_bmp = m_empty_bmp; -} - void ObjectDataViewModelNode::update_settings_digest_bitmaps() { m_bmp = m_empty_bmp; @@ -328,6 +318,7 @@ ObjectDataViewModel::ObjectDataViewModel() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); + m_lock_bmp = create_scaled_bitmap(LockIcon); for (auto item : INFO_ITEMS) m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name); @@ -341,19 +332,56 @@ ObjectDataViewModel::~ObjectDataViewModel() m_bitmap_cache = nullptr; } -wxBitmap& ObjectDataViewModel::GetWarningBitmap(const std::string& warning_icon_name) +void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node) { - return warning_icon_name.empty() ? m_empty_bmp : warning_icon_name == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp; + int vol_type = static_cast(node->GetVolumeType()); + bool is_volume_node = vol_type >= 0; + + if (!node->has_warning_icon() && !node->has_lock()) { + node->SetBitmap(is_volume_node ? m_volume_bmps[vol_type] : m_empty_bmp); + return; + } + + std::string scaled_bitmap_name = std::string(); + if (node->has_warning_icon()) + scaled_bitmap_name += node->warning_icon_name(); + if (node->has_lock()) + scaled_bitmap_name += LockIcon; + if (is_volume_node) + scaled_bitmap_name += std::to_string(vol_type); + scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm"); + + wxBitmap* bmp = m_bitmap_cache->find(scaled_bitmap_name); + if (!bmp) { + std::vector bmps; + if (node->has_warning_icon()) + bmps.emplace_back(node->warning_icon_name() == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp); + if (node->has_lock()) + bmps.emplace_back(m_lock_bmp); + if (is_volume_node) + bmps.emplace_back(m_volume_bmps[vol_type]); + bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + } + + node->SetBitmap(*bmp); +} + +void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node, const std::string& warning_icon_name, bool has_lock) +{ + node->SetWarningIconName(warning_icon_name); + node->SetLock(has_lock); + UpdateBitmapForNode(node); } wxDataViewItem ObjectDataViewModel::Add(const wxString &name, const int extruder, - const std::string& warning_icon_name/* = std::string()*/ ) + const std::string& warning_icon_name, + const bool has_lock) { const wxString extruder_str = extruder == 0 ? _L("default") : wxString::Format("%d", extruder); auto root = new ObjectDataViewModelNode(name, extruder_str); // Add warning icon if detected auto-repaire - root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); + UpdateBitmapForNode(root, warning_icon_name, has_lock); m_objects.push_back(root); // notify control @@ -384,7 +412,8 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent if (create_frst_child && root->m_volumes_cnt == 0) { const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; - const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, root->m_warning_icon_name), extruder_str, 0, root->m_warning_icon_name); + const auto node = new ObjectDataViewModelNode(root, root->m_name, type, extruder_str, 0); + UpdateBitmapForNode(node, root->warning_icon_name(), root->has_lock()); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control @@ -395,13 +424,16 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent if (insert_position >= 0) insert_position++; } - const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, warning_icon_name), extruder_str, root->m_volumes_cnt, warning_icon_name); + const auto node = new ObjectDataViewModelNode(root, name, volume_type, extruder_str, root->m_volumes_cnt); + UpdateBitmapForNode(node, warning_icon_name, root->has_lock() && volume_type < ModelVolumeType::PARAMETER_MODIFIER); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // if part with errors is added, but object wasn't marked, then mark it - if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name && - (root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon) ) - root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); + if (!warning_icon_name.empty() && warning_icon_name != root->warning_icon_name() && + (!root->has_warning_icon() || root->warning_icon_name() == WarningManifoldIcon)) { + root->SetWarningIconName(warning_icon_name); + UpdateBitmapForNode(root); + } // notify control const wxDataViewItem child((void*)node); @@ -1682,6 +1714,7 @@ void ObjectDataViewModel::Rescale() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); + m_lock_bmp = create_scaled_bitmap(LockIcon); for (auto item : INFO_ITEMS) m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name); @@ -1700,10 +1733,8 @@ void ObjectDataViewModel::Rescale() switch (node->m_type) { case itObject: - if (node->m_bmp.IsOk()) node->m_bmp = GetWarningBitmap(node->m_warning_icon_name); - break; case itVolume: - node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name); + UpdateBitmapForNode(node); break; case itLayerRoot: node->m_bmp = create_scaled_bitmap(LayerRootIcon); @@ -1719,27 +1750,6 @@ void ObjectDataViewModel::Rescale() } } -wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/) -{ - if (warning_icon_name.empty()) - return m_volume_bmps[static_cast(vol_type)]; - - std::string scaled_bitmap_name = warning_icon_name + std::to_string(static_cast(vol_type)); - scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm"); - - wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); - if (bmp == nullptr) { - std::vector bmps; - - bmps.emplace_back(GetWarningBitmap(warning_icon_name)); - bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); - - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); - } - - return *bmp; -} - void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::string& warning_icon_name) { if (!item.IsOk()) @@ -1747,13 +1757,14 @@ void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std:: ObjectDataViewModelNode *node = static_cast(item.GetID()); if (node->GetType() & itObject) { - node->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); + UpdateBitmapForNode(node, warning_icon_name, node->has_lock()); return; } if (node->GetType() & itVolume) { - node->SetWarningBitmap(GetVolumeIcon(node->GetVolumeType(), warning_icon_name), warning_icon_name); - node->GetParent()->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); + UpdateBitmapForNode(node, warning_icon_name, node->has_lock()); + if (ObjectDataViewModelNode* parent = node->GetParent()) + UpdateBitmapForNode(parent, warning_icon_name, parent->has_lock()); return; } } @@ -1768,12 +1779,9 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject))) return; - if (node->GetType() & itVolume) { - node->SetWarningBitmap(m_volume_bmps[static_cast(node->volume_type())], ""); - return; - } + node->SetWarningIconName(std::string()); + UpdateBitmapForNode(node); - node->SetWarningBitmap(wxNullBitmap, ""); if (unmark_object) { wxDataViewItemArray children; @@ -1800,6 +1808,25 @@ void ObjectDataViewModel::UpdateWarningIcon(const wxDataViewItem& item, const st AddWarningIcon(item, warning_icon_name); } +void ObjectDataViewModel::UpdateLockIcon(const wxDataViewItem& item, bool has_lock) +{ + if (!item.IsOk()) + return; + ObjectDataViewModelNode* node = static_cast(item.GetID()); + if (node->has_lock() == has_lock) + return; + + node->SetLock(has_lock); + UpdateBitmapForNode(node); + + if (node->GetType() & itObject) { + wxDataViewItemArray children; + GetChildren(item, children); + for (const wxDataViewItem& child : children) + UpdateLockIcon(child, has_lock); + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 8669a8fd0..ec1d801c9 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -80,6 +80,7 @@ class ObjectDataViewModelNode PrintIndicator m_printable {piUndef}; wxBitmap m_printable_icon; std::string m_warning_icon_name{ "" }; + bool m_has_lock{false}; std::string m_action_icon_name = ""; ModelVolumeType m_volume_type; @@ -100,10 +101,8 @@ public: ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmap& bmp, const wxString& extruder, - const int idx = -1, - const std::string& warning_icon_name = std::string()); + const int idx = -1 ); ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const t_layer_height_range& layer_range, @@ -179,10 +178,11 @@ public: } bool SetValue(const wxVariant &variant, unsigned int col); - void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } - void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } - void SetExtruder(const wxString &extruder) { m_extruder = extruder; } - void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } + void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } + void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } + void SetExtruder(const wxString &extruder) { m_extruder = extruder; } + void SetWarningIconName(const std::string& warning_icon_name) { m_warning_icon_name = warning_icon_name; } + void SetLock(bool has_lock) { m_has_lock = has_lock; } const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } @@ -229,8 +229,6 @@ public: void set_extruder_icon(); // Set printable icon for node void set_printable_icon(PrintIndicator printable); - // Set warning icon for node - void set_warning_icon(const std::string& warning_icon); void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); @@ -241,7 +239,9 @@ public: bool valid(); #endif /* NDEBUG */ bool invalid() const { return m_idx < -1; } - bool has_warning_icon() const { return !m_warning_icon_name.empty(); } + bool has_warning_icon() const { return !m_warning_icon_name.empty(); } + bool has_lock() const { return m_has_lock; } + const std::string& warning_icon_name() const { return m_warning_icon_name; } private: friend class ObjectDataViewModel; @@ -263,6 +263,7 @@ class ObjectDataViewModel :public wxDataViewModel wxBitmap m_empty_bmp; wxBitmap m_warning_bmp; wxBitmap m_warning_manifold_bmp; + wxBitmap m_lock_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -272,7 +273,8 @@ public: wxDataViewItem Add( const wxString &name, const int extruder, - const std::string& warning_icon_name = std::string()); + const std::string& warning_icon_name, + const bool has_lock); wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, @@ -386,11 +388,10 @@ public: // Rescale bitmaps for existing Items void Rescale(); - wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, - const std::string& warning_icon_name = std::string()); void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); void UpdateWarningIcon(const wxDataViewItem& item, const std::string& warning_name); + void UpdateLockIcon(const wxDataViewItem& item, bool has_lock); bool HasWarningIcon(const wxDataViewItem& item) const; t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; @@ -404,7 +405,8 @@ private: wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); void AddAllChildren(const wxDataViewItem& parent); - wxBitmap& GetWarningBitmap(const std::string& warning_icon_name); + void UpdateBitmapForNode(ObjectDataViewModelNode* node); + void UpdateBitmapForNode(ObjectDataViewModelNode* node, const std::string& warning_icon_name, bool has_lock); }; From 6fcb6afd81860d0ba19b9652687fa8a5d1c04df3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 14 Apr 2022 10:33:36 +0200 Subject: [PATCH 26/97] After merge fixes --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 26 ++++---------------------- src/slic3r/GUI/MeshUtils.cpp | 5 ----- src/slic3r/GUI/ObjectDataViewModel.cpp | 2 +- 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 1db09956d..45f991c4c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -370,11 +370,7 @@ bool GLGizmoCut3D::render_revert_button(const std::string& label_id) void GLGizmoCut3D::render_cut_plane() { #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -467,11 +463,7 @@ void GLGizmoCut3D::render_cut_center_graber() glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glLineWidth(m_hover_id == m_group_id ? 2.0f : 1.5f)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader != nullptr) { shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES @@ -501,21 +493,15 @@ void GLGizmoCut3D::render_cut_center_graber() shader->stop_using(); } -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader = wxGetApp().get_shader("gouraud_light_attr"); -#else - shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#else +#if !ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glColor3f(1.0, 1.0, 0.0)); ::glBegin(GL_LINES); ::glVertex3dv(plane_center.data()); ::glVertex3dv(m_grabbers[0].center.data()); glsafe(::glEnd()); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL + shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.1f); @@ -745,7 +731,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) for (Axis axis : {X, Y, Z}) render_rotation_input(axis); - m_imgui->text(_L("")); + m_imgui->text(_L("°")); } else { ImGui::AlignTextToFramePadding(); @@ -899,11 +885,7 @@ void GLGizmoCut3D::render_connectors(bool picking) return; #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat_attr") : wxGetApp().get_shader("gouraud_light_attr"); -#else GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index cbea17d8e..58b8c7183 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -128,12 +128,7 @@ void MeshClipper::render_contour() if (curr_shader != nullptr) curr_shader->stop_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (shader != nullptr) { shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 619de379b..9c5fac89e 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -38,7 +38,7 @@ static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char WarningIcon[] = "exclamation"; static constexpr char WarningManifoldIcon[] = "exclamation_manifold"; -static constexpr char LockIcon[] = "lock_closed_white"; +static constexpr char LockIcon[] = "lock_closed"; struct InfoItemAtributes { std::string name; From b5f565308ad9981a55228d92692a421fd47e8644 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 28 Apr 2022 13:14:57 +0200 Subject: [PATCH 27/97] Cut WIP: Added an extension for cut plane grabber + Information about build size during the normal of CutPlane is added to CutGizmo + Tooltip for cut plane grabber shows an info about heights of top and bottom parts now --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 182 ++++++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 + 2 files changed, 168 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 45f991c4c..4065c2060 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -24,6 +24,8 @@ namespace GUI { static const double Margin = 20.0; static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); +#define use_grabber_extension 1 + GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) , m_connector_type (CutConnectorType::Plug) @@ -59,12 +61,22 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, std::string GLGizmoCut3D::get_tooltip() const { std::string tooltip = m_rotation_gizmo.get_tooltip(); - if (tooltip.empty()) { - double koef = wxGetApp().app_config->get("use_inches") == "1" ? ObjectManipulation::mm_to_in : 1.0; - if (m_hover_id == m_group_id || m_grabbers[0].dragging) - return "X: " + format(m_plane_center.x() * koef, 2) + "; " +//"\n" + - "Y: " + format(m_plane_center.y() * koef, 2) + "; " +//"\n" + - "Z: " + format(m_plane_center.z() * koef, 2); + if (tooltip.empty() && + (m_hover_id == m_group_id || m_grabbers[0].dragging)) { + double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; + std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm")); + const BoundingBoxf3 tbb = transformed_bounding_box(); + if (tbb.max.z() >= 0.0) { + double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; + tooltip += format(top, 2) + " " + unit_str + " (" + _u8L("Top part") + ")"; + if (tbb.min.z() <= 0.0) + tooltip += "\n"; + } + if (tbb.min.z() <= 0.0) { + double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; + tooltip += format(bottom, 2) + " " + unit_str + " (" + _u8L("Bottom part") + ")"; + } + return tooltip; } return tooltip; @@ -449,15 +461,74 @@ void GLGizmoCut3D::render_cut_plane() void GLGizmoCut3D::render_cut_center_graber() { const Vec3d& angles = m_rotation_gizmo.get_rotation(); + m_grabbers[0].angles = angles; + m_grabbers[0].color = GRABBER_COLOR; + +#if use_grabber_extension + // UI experiments with grabber + + m_grabbers[0].center = m_plane_center; + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (!shader) + return; + + auto color = m_hover_id == m_group_id ? complementary(GRABBER_COLOR) : GRABBER_COLOR; + m_sphere.set_color(color); + m_cone.set_color(color); + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Grabber& graber = m_grabbers.front(); + const Vec3d& center = graber.center; + + const BoundingBoxf3 box = bounding_box(); + + const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 6.0); + const double size = m_dragging ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + + const Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + const Vec3d offset = 1.25 * size * Vec3d::UnitZ(); + + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + const Transform3d view_matrix = camera.get_view_matrix() * graber.matrix * + Geometry::assemble_transform(center, angles); + + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_sphere.render(); + + const BoundingBoxf3 tbb = transformed_bounding_box(); + if (tbb.max.z() >= 0.0) { + view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), scale); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_cone.render(); + } + + if (tbb.min.z() <= 0.0) { + view_model_matrix = view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), scale); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_cone.render(); + } + + shader->stop_using(); + +#else const BoundingBoxf3 box = bounding_box(); Vec3d grabber_center = m_plane_center; grabber_center[Z] += float(box.radius()/2.0); // Margin - rotate_vec3d_around_center(grabber_center, angles, m_plane_center); m_grabbers[0].center = grabber_center; - m_grabbers[0].angles = angles; glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); @@ -506,11 +577,11 @@ void GLGizmoCut3D::render_cut_center_graber() shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - m_grabbers[0].color = GRABBER_COLOR; m_grabbers[0].render(m_hover_id == m_group_id, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); shader->stop_using(); } +#endif } bool GLGizmoCut3D::on_init() @@ -560,7 +631,13 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; if (m_hover_id == m_group_id) { +#if use_grabber_extension + Vec3d starting_box_center = m_plane_center; + starting_box_center[Z] -= 1.0; // some Margin + rotate_vec3d_around_center(starting_box_center, m_rotation_gizmo.get_rotation(), m_plane_center); +#else const Vec3d& starting_box_center = m_plane_center; +#endif const Vec3d& starting_drag_position = m_grabbers[0].center; double projection = 0.0; @@ -585,7 +662,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) Vec3d shift = starting_vec * projection; // move cut plane center - set_center(starting_box_center + shift); + set_center(m_plane_center + shift); } else if (m_hover_id > m_group_id && m_connector_mode == CutConnectorMode::Manual) @@ -624,6 +701,49 @@ BoundingBoxf3 GLGizmoCut3D::bounding_box() const return ret; } +BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box() const +{ + // #ysFIXME !!! + BoundingBoxf3 ret; + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + if (instance_idx < 0 || object_idx < 0) + return ret; + + const Vec3d& instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); + + Vec3d cut_center_offset = m_plane_center - instance_offset; + cut_center_offset[Z] -= selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); + + const Vec3d& rotation = m_rotation_gizmo.get_rotation(); + const auto move = Geometry::assemble_transform(-cut_center_offset, Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones() ); + const auto rot_z = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, 0, -rotation.z()), Vec3d::Ones(), Vec3d::Ones()); + const auto rot_y = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, -rotation.y(), 0), Vec3d::Ones(), Vec3d::Ones()); + const auto rot_x = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(-rotation.x(), 0, 0), Vec3d::Ones(), Vec3d::Ones()); + + const auto cut_matrix = rot_x * rot_y * rot_z * move; + + for (unsigned int i : idxs) { + const GLVolume* volume = selection.get_volume(i); + // respect just to the solid parts for FFF and ignore pad and supports for SLA + if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) { + + const auto instance_matrix = Geometry::assemble_transform( + Vec3d::Zero(), // don't apply offset + volume->get_instance_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), + volume->get_instance_scaling_factor(), + volume->get_instance_mirror() + ); + + ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * instance_matrix * volume->get_volume_transformation().get_matrix())); + } + } + return ret; +} + bool GLGizmoCut3D::update_bb() { const BoundingBoxf3 box = bounding_box(); @@ -634,6 +754,8 @@ bool GLGizmoCut3D::update_bb() set_center_pos(m_bb_center + m_center_offset); m_plane.reset(); + m_cone.reset(); + m_sphere.reset(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { m_selected.clear(); m_selected.resize(selection->model_object()->cut_connectors.size(), false); @@ -651,13 +773,18 @@ void GLGizmoCut3D::on_render() update_clipper_on_render(); } + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + if (!m_sphere.is_initialized()) + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + render_connectors(false); m_c->object_clipper()->render_cut(); if (!m_hide_cut_plane) { - render_cut_plane(); render_cut_center_graber(); + render_cut_plane(); if (m_mode == size_t(CutMode::cutPlanar)) { if (m_hover_id < m_group_id) m_rotation_gizmo.render(); @@ -732,16 +859,37 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) for (Axis axis : {X, Y, Z}) render_rotation_input(axis); m_imgui->text(_L("°")); + + ImGui::Separator(); + + double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; + wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); + + const BoundingBoxf3 tbb = transformed_bounding_box(); + Vec3d tbb_sz = tbb.size(); + wxString size = "X: " + double_to_string(tbb.size().x() * koef,2) + unit_str + + ", Y: " + double_to_string(tbb.size().y() * koef,2) + unit_str + + ", Z: " + double_to_string(tbb.size().z() * koef,2) + unit_str ; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Build size")); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); +#if 0 + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("DistToTop")); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, double_to_string(tbb.max.z() * koef, 2)); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("DistToBottom")); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, double_to_string(tbb.min.z() * koef, 2)); +#endif } else { ImGui::AlignTextToFramePadding(); - ImGui::AlignTextToFramePadding(); - ImGui::AlignTextToFramePadding(); - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 3*m_control_width); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connect some two points of object to cteate a cut plane")); - ImGui::PopTextWrapPos(); - ImGui::AlignTextToFramePadding(); - ImGui::AlignTextToFramePadding(); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ca49d2e4e..cb6d222ef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -39,6 +39,8 @@ class GLGizmoCut3D : public GLGizmoBase #if ENABLE_LEGACY_OPENGL_REMOVAL GLModel m_plane; GLModel m_grabber_connection; + GLModel m_cone; + GLModel m_sphere; Vec3d m_old_center; #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -117,6 +119,7 @@ public: void update_clipper_on_render(); BoundingBoxf3 bounding_box() const; + BoundingBoxf3 transformed_bounding_box() const; protected: bool on_init() override; From 94f3aaacd402cd12d9b54dcdf404866c3516ac5a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 2 May 2022 12:44:47 +0200 Subject: [PATCH 28/97] Cut WIP: Undo/Redo implementation --- resources/localization/list.txt | 5 ++ src/libslic3r/Model.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 91 +++++++++++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 18 +++-- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 3 +- src/slic3r/GUI/Plater.cpp | 5 +- 6 files changed, 100 insertions(+), 25 deletions(-) diff --git a/resources/localization/list.txt b/resources/localization/list.txt index d68f99bff..f02b017d7 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -17,16 +17,21 @@ src/slic3r/GUI/GalleryDialog.cpp src/slic3r/GUI/GCodeViewer.cpp src/slic3r/GUI/GLCanvas3D.cpp src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +src/slic3r/GUI/Gizmos/GLGizmoCut.hpp src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp src/slic3r/GUI/Gizmos/GLGizmoMove.cpp src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp src/slic3r/GUI/Gizmos/GLGizmoScale.cpp src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp src/slic3r/GUI/Gizmos/GLGizmosManager.cpp src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp src/slic3r/GUI/GUI.cpp diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 108e30511..871859cf0 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -584,7 +584,8 @@ private: Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, - m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid, + cut_connectors, cut_id); } // Called by Print::validate() from the UI thread. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 4065c2060..6fafdbf9b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -14,6 +14,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" @@ -88,6 +89,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return false; if (m_rotation_gizmo.on_mouse(mouse_event)) { + if (mouse_event.LeftUp()) + on_stop_dragging(); update_clipper(); return true; } @@ -221,7 +224,7 @@ void GLGizmoCut3D::update_clipper() void GLGizmoCut3D::update_clipper_on_render() { update_clipper(); - suppress_update_clipper_on_render = true; + force_update_clipper_on_render = false; } void GLGizmoCut3D::set_center(const Vec3d& center) @@ -595,6 +598,27 @@ bool GLGizmoCut3D::on_init() return true; } +void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) +{ + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, //m_selected, + m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, + m_ar_plane_center, m_ar_rotations); + + set_center_pos(m_ar_plane_center); + m_rotation_gizmo.set_center(m_ar_plane_center); + m_rotation_gizmo.set_rotation(m_ar_rotations); + force_update_clipper_on_render = true; + + m_parent.request_extra_frame(); +} + +void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, //m_selected, + m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, + m_ar_plane_center, m_ar_rotations); +} + std::string GLGizmoCut3D::on_get_name() const { return _u8L("Cut"); @@ -602,13 +626,22 @@ std::string GLGizmoCut3D::on_get_name() const void GLGizmoCut3D::on_set_state() { - if (get_state() == On) + if (m_state == On) { update_bb(); + // initiate archived values + m_ar_plane_center = m_plane_center; + m_ar_rotations = m_rotation_gizmo.get_rotation(); + + m_parent.request_extra_frame(); + } + else + m_c->object_clipper()->release(); + m_rotation_gizmo.set_center(m_plane_center); m_rotation_gizmo.set_state(m_state); - suppress_update_clipper_on_render = m_state != On; + force_update_clipper_on_render = m_state == On; } void GLGizmoCut3D::on_set_hover_id() @@ -674,6 +707,24 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) } } +void GLGizmoCut3D::on_start_dragging() +{ + if (m_hover_id > m_group_id && m_connector_mode == CutConnectorMode::Manual) + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move connector"), UndoRedo::SnapshotType::GizmoAction); +} + +void GLGizmoCut3D::on_stop_dragging() +{ + if (m_hover_id < m_group_id) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); + m_ar_rotations = m_rotation_gizmo.get_rotation(); + } + else if (m_hover_id == m_group_id) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); + m_ar_plane_center = m_plane_center; + } +} + void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos) { m_plane_center = center_pos; @@ -778,6 +829,9 @@ void GLGizmoCut3D::on_render() if (!m_sphere.is_initialized()) m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + if (force_update_clipper_on_render) + update_clipper_on_render(); + render_connectors(false); m_c->object_clipper()->render_cut(); @@ -790,9 +844,6 @@ void GLGizmoCut3D::on_render() m_rotation_gizmo.render(); } } - - if (!suppress_update_clipper_on_render) - update_clipper_on_render(); } void GLGizmoCut3D::on_render_for_picking() @@ -963,7 +1014,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || !can_perform_cut()); + m_imgui->disabled_begin(!can_perform_cut()); const bool cut_clicked = m_imgui->button(_L("Perform cut")); m_imgui->disabled_end(); @@ -1024,13 +1075,16 @@ void GLGizmoCut3D::render_connectors(bool picking) { m_has_invalid_connector = false; - if (m_connector_mode == CutConnectorMode::Auto) + if (m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) return; const ModelObject* mo = m_c->selection_info()->model_object(); const CutConnectors& connectors = mo->cut_connectors; - if (connectors.size() != m_selected.size()) - return; + if (connectors.size() != m_selected.size()) { + // #ysFIXME + m_selected.clear(); + m_selected.resize(connectors.size(), false); + } #if ENABLE_LEGACY_OPENGL_REMOVAL GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); @@ -1145,7 +1199,7 @@ void GLGizmoCut3D::render_connectors(bool picking) bool GLGizmoCut3D::can_perform_cut() const { - if (m_has_invalid_connector) + if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower)) return false; BoundingBoxf3 box = bounding_box(); @@ -1172,9 +1226,16 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); + Plater* plater = wxGetApp().plater(); + bool create_dowels_as_separate_object = false; if (0.0 < object_cut_z && can_perform_cut()) { - ModelObject* mo = wxGetApp().plater()->model().objects[object_idx]; + ModelObject* mo = plater->model().objects[object_idx]; + if(!mo) + return; + + Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); + const bool has_connectors = !mo->cut_connectors.empty(); // update connectors pos as offset of its center before cut performing if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { @@ -1198,7 +1259,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) create_dowels_as_separate_object = true; } - wxGetApp().plater()->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), + plater->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | @@ -1306,7 +1367,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi const Vec3d& normal = pos_and_normal.second; // The clipping plane was clicked, hit containts coordinates of the hit in world coords. std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector")); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); connectors.emplace_back(hit, m_rotation_gizmo.get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio)); update_model_object(); @@ -1326,7 +1387,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (m_hover_id < m_connectors_group_id) return false; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete connector")); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete connector"), UndoRedo::SnapshotType::GizmoAction); size_t connector_id = m_hover_id - m_connectors_group_id; connectors.erase(connectors.begin() + connector_id); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index cb6d222ef..f8935d61f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -24,6 +24,10 @@ class GLGizmoCut3D : public GLGizmoBase double m_snap_step{ 1.0 }; int m_connectors_group_id; + // archived values + Vec3d m_ar_plane_center { Vec3d::Zero() }; + Vec3d m_ar_rotations { Vec3d::Zero() }; + Vec3d m_plane_center{ Vec3d::Zero() }; // data to check position of the cut palne center on gizmo activation Vec3d m_min_pos{ Vec3d::Zero() }; @@ -38,7 +42,7 @@ class GLGizmoCut3D : public GLGizmoBase #if ENABLE_LEGACY_OPENGL_REMOVAL GLModel m_plane; - GLModel m_grabber_connection; +// GLModel m_grabber_connection; GLModel m_cone; GLModel m_sphere; Vec3d m_old_center; @@ -57,7 +61,7 @@ class GLGizmoCut3D : public GLGizmoBase float m_label_width{ 150.0 }; float m_control_width{ 200.0 }; bool m_imperial_units{ false }; - bool suppress_update_clipper_on_render{false}; + bool force_update_clipper_on_render{false}; mutable std::vector m_selected; // which pins are currently selected bool m_selection_empty = true; @@ -123,18 +127,24 @@ public: protected: bool on_init() override; - void on_load(cereal::BinaryInputArchive& ar) override { ar(m_keep_upper, m_keep_lower, m_rotate_lower); } - void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_keep_upper, m_keep_lower, m_rotate_lower); } + void on_load(cereal::BinaryInputArchive& ar) override; + void on_save(cereal::BinaryOutputArchive& ar) const override; std::string on_get_name() const override; void on_set_state() override; CommonGizmosDataID on_get_requirements() const override; void on_set_hover_id() override; bool on_is_activable() const override; void on_dragging(const UpdateData& data) override; + void on_start_dragging() override; + void on_stop_dragging() override; void on_render() override; void on_render_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; + bool wants_enter_leave_snapshots() const override { return true; } + std::string get_gizmo_entering_text() const override { return _u8L("Entering Cut gizmo"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); } + std::string get_action_snapshot_name() override { return _u8L("Cut gizmo editing"); } private: void set_center(const Vec3d& center); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index a5848013b..41f681894 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -836,7 +836,8 @@ void GLGizmoRotate3D::on_start_dragging() void GLGizmoRotate3D::on_stop_dragging() { assert(0 <= m_hover_id && m_hover_id < 3); - m_parent.do_rotate(L("Gizmo-Rotate")); + if (!m_use_only_grabbers) + m_parent.do_rotate(L("Gizmo-Rotate")); m_gizmos[m_hover_id].stop_dragging(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 00f2aee17..00274dfb5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5915,10 +5915,7 @@ void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) - return; - - Plater::TakeSnapshot snapshot(this, _L("Cut by Plane")); + //Plater::TakeSnapshot snapshot(this, _L("Cut by Plane")); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, cut_center, cut_rotation, attributes); From c903414005c20ceec4aba6684a8ad68525eebe1e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 3 May 2022 11:13:43 +0200 Subject: [PATCH 29/97] Cut WIP: Improved can_perform_cut() --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 13 +++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 6fafdbf9b..09e59d239 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -752,7 +752,7 @@ BoundingBoxf3 GLGizmoCut3D::bounding_box() const return ret; } -BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box() const +BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false*/) const { // #ysFIXME !!! BoundingBoxf3 ret; @@ -774,8 +774,9 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box() const const auto rot_z = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, 0, -rotation.z()), Vec3d::Ones(), Vec3d::Ones()); const auto rot_y = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, -rotation.y(), 0), Vec3d::Ones(), Vec3d::Ones()); const auto rot_x = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(-rotation.x(), 0, 0), Vec3d::Ones(), Vec3d::Ones()); + const auto move2 = Geometry::assemble_transform(m_plane_center, Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones() ); - const auto cut_matrix = rot_x * rot_y * rot_z * move; + const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * rot_x * rot_y * rot_z * move; for (unsigned int i : idxs) { const GLVolume* volume = selection.get_volume(i); @@ -1202,12 +1203,8 @@ bool GLGizmoCut3D::can_perform_cut() const if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower)) return false; - BoundingBoxf3 box = bounding_box(); - double dist = (m_plane_center - box.center()).norm(); - if (dist > box.radius()) - return false; - - return true; + const BoundingBoxf3 tbb = transformed_bounding_box(true); + return tbb.contains(m_plane_center); } void GLGizmoCut3D::perform_cut(const Selection& selection) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index f8935d61f..400018897 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -123,7 +123,7 @@ public: void update_clipper_on_render(); BoundingBoxf3 bounding_box() const; - BoundingBoxf3 transformed_bounding_box() const; + BoundingBoxf3 transformed_bounding_box(bool revert_move = false) const; protected: bool on_init() override; From 90e359c5d4d718b9e34b0913ef550f54a0dc513b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 9 May 2022 11:23:05 +0200 Subject: [PATCH 30/97] Cut WIP: Implemented "Cut By Line" --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 226 +++++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 9 +- src/slic3r/GUI/MeshUtils.cpp | 18 +++ src/slic3r/GUI/MeshUtils.hpp | 3 + 4 files changed, 192 insertions(+), 64 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 09e59d239..7ad1d71f6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -39,7 +39,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, m_group_id = 3; m_connectors_group_id = 4; - m_modes = { _u8L("Planar"), _u8L("By Line"),_u8L("Grid") + m_modes = { _u8L("Planar"), _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") }; @@ -85,7 +85,7 @@ std::string GLGizmoCut3D::get_tooltip() const bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) { - if (mouse_event.Moving()) + if (mouse_event.Moving() && !cut_line_processing()) return false; if (m_rotation_gizmo.on_mouse(mouse_event)) { @@ -147,6 +147,11 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return true; } } + else if (mouse_event.Moving()) { + // draw cut line + gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + return true; + } else if (pending_right_up && mouse_event.RightUp()) { pending_right_up = false; return true; @@ -212,6 +217,8 @@ void GLGizmoCut3D::update_clipper() // calculate normal and offset for clipping plane Vec3d normal = end - beg; + if (normal == Vec3d::Zero()) + return; dist = std::clamp(dist, 0.0001, normal.norm()); normal.normalize(); const double offset = normal.dot(beg) + dist; @@ -384,6 +391,9 @@ bool GLGizmoCut3D::render_revert_button(const std::string& label_id) void GLGizmoCut3D::render_cut_plane() { + if (cut_line_processing()) + return; + #if ENABLE_LEGACY_OPENGL_REMOVAL GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader == nullptr) @@ -587,6 +597,45 @@ void GLGizmoCut3D::render_cut_center_graber() #endif } +void GLGizmoCut3D::render_cut_line() +{ + if (!cut_line_processing() || m_line_end == Vec3d::Zero()) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glLineWidth(2.0f)); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cut_line.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)m_line_beg.cast()); + init_data.add_vertex((Vec3f)m_line_end.cast()); + + // indices + init_data.add_line(0, 1); + + m_cut_line.init_from(std::move(init_data)); + m_cut_line.render(); + + shader->stop_using(); + } +} + bool GLGizmoCut3D::on_init() { m_grabbers.emplace_back(); @@ -770,11 +819,11 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* cut_center_offset[Z] -= selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); const Vec3d& rotation = m_rotation_gizmo.get_rotation(); - const auto move = Geometry::assemble_transform(-cut_center_offset, Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones() ); - const auto rot_z = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, 0, -rotation.z()), Vec3d::Ones(), Vec3d::Ones()); - const auto rot_y = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, -rotation.y(), 0), Vec3d::Ones(), Vec3d::Ones()); - const auto rot_x = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(-rotation.x(), 0, 0), Vec3d::Ones(), Vec3d::Ones()); - const auto move2 = Geometry::assemble_transform(m_plane_center, Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones() ); + const auto move = Geometry::assemble_transform(-cut_center_offset); + const auto rot_z = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, 0, -rotation.z())); + const auto rot_y = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, -rotation.y(), 0)); + const auto rot_x = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(-rotation.x(), 0, 0)); + const auto move2 = Geometry::assemble_transform(m_plane_center); const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * rot_x * rot_y * rot_z * move; @@ -840,11 +889,11 @@ void GLGizmoCut3D::on_render() if (!m_hide_cut_plane) { render_cut_center_graber(); render_cut_plane(); - if (m_mode == size_t(CutMode::cutPlanar)) { - if (m_hover_id < m_group_id) + if (m_hover_id < m_group_id && m_mode == size_t(CutMode::cutPlanar) && !cut_line_processing()) m_rotation_gizmo.render(); - } } + + render_cut_line(); } void GLGizmoCut3D::on_render_for_picking() @@ -886,63 +935,48 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - if (m_mode <= size_t(CutMode::cutByLine)) { + if (m_mode == size_t(CutMode::cutPlanar)) { + ImGui::AlignTextToFramePadding(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); ImGui::Separator(); - if (m_mode == size_t(CutMode::cutPlanar)) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Move center")); + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Move center")); - m_imgui->disabled_begin(m_plane_center == bounding_box().center()); - revert_move = render_revert_button("move"); - m_imgui->disabled_end(); + m_imgui->disabled_begin(m_plane_center == bounding_box().center()); + revert_move = render_revert_button("move"); + m_imgui->disabled_end(); - for (Axis axis : {X, Y, Z}) - render_move_center_input(axis); - m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + for (Axis axis : {X, Y, Z}) + render_move_center_input(axis); + m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Rotation")); + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Rotation")); - m_imgui->disabled_begin(m_rotation_gizmo.get_rotation() == Vec3d::Zero()); - revert_rotation = render_revert_button("rotation"); - m_imgui->disabled_end(); + m_imgui->disabled_begin(m_rotation_gizmo.get_rotation() == Vec3d::Zero()); + revert_rotation = render_revert_button("rotation"); + m_imgui->disabled_end(); - for (Axis axis : {X, Y, Z}) - render_rotation_input(axis); - m_imgui->text(_L("°")); + for (Axis axis : {X, Y, Z}) + render_rotation_input(axis); + m_imgui->text(_L("°")); - ImGui::Separator(); + ImGui::Separator(); - double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; - wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); + double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; + wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); - const BoundingBoxf3 tbb = transformed_bounding_box(); - Vec3d tbb_sz = tbb.size(); - wxString size = "X: " + double_to_string(tbb.size().x() * koef,2) + unit_str + - ", Y: " + double_to_string(tbb.size().y() * koef,2) + unit_str + - ", Z: " + double_to_string(tbb.size().z() * koef,2) + unit_str ; + const BoundingBoxf3 tbb = transformed_bounding_box(); + Vec3d tbb_sz = tbb.size(); + wxString size = "X: " + double_to_string(tbb.size().x() * koef, 2) + unit_str + + ", Y: " + double_to_string(tbb.size().y() * koef, 2) + unit_str + + ", Z: " + double_to_string(tbb.size().z() * koef, 2) + unit_str; - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Build size")); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); -#if 0 - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("DistToTop")); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, double_to_string(tbb.max.z() * koef, 2)); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("DistToBottom")); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, double_to_string(tbb.min.z() * koef, 2)); -#endif - } - else { - ImGui::AlignTextToFramePadding(); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connect some two points of object to cteate a cut plane")); - } + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Build size")); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); } m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); @@ -980,7 +1014,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->disabled_end(); - if (m_mode <= size_t(CutMode::cutByLine)) { + if (m_mode == size_t(CutMode::cutPlanar)) { ImGui::Separator(); ImGui::AlignTextToFramePadding(); @@ -1074,12 +1108,15 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c void GLGizmoCut3D::render_connectors(bool picking) { - m_has_invalid_connector = false; - - if (m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) + if (cut_line_processing() || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) return; + m_has_invalid_connector = false; + const ModelObject* mo = m_c->selection_info()->model_object(); + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; const CutConnectors& connectors = mo->cut_connectors; if (connectors.size() != m_selected.size()) { // #ysFIXME @@ -1111,7 +1148,7 @@ void GLGizmoCut3D::render_connectors(bool picking) ColorRGBA render_color; - const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; + const ModelInstance* mi = mo->instances[inst_id]; const Vec3d& instance_offset = mi->get_offset(); const float sla_shift = m_c->selection_info()->get_sla_shift(); @@ -1280,7 +1317,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; const Transform3d instance_trafo = sla_shift > 0.0 ? - Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift), Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones()) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); + Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); int mesh_id = -1; @@ -1340,6 +1377,67 @@ void GLGizmoCut3D::update_model_object() const m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } +bool GLGizmoCut3D::cut_line_processing() const +{ + return m_line_beg != Vec3d::Zero(); +} + +bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position) +{ + const float sla_shift = m_c->selection_info()->get_sla_shift(); + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; + Transform3d inst_trafo = sla_shift > 0.0 ? + Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : + mi->get_transformation().get_matrix(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + ++mesh_id; + if (!mv->is_model_part()) + continue; + + const Transform3d trafo = inst_trafo * mv->get_matrix(); + const MeshRaycaster* raycaster = m_c->raycaster()->raycasters()[mesh_id]; + + Vec3d point; + Vec3d direction; + if (raycaster->unproject_on_mesh(mouse_position, trafo, camera, point, direction)) + { + point += mi->get_offset(); + point[Z] += sla_shift; + + if (action == SLAGizmoEventType::LeftDown && !cut_line_processing()) { + m_line_beg = point; + return true; + } + if (action == SLAGizmoEventType::Moving && cut_line_processing()) { + m_line_end = point; + return true; + } + if (action == SLAGizmoEventType::LeftDown && cut_line_processing()) { + Vec3f camera_dir = camera.get_dir_forward().cast(); + Vec3f line_dir = (m_line_end - m_line_beg).cast(); + + Vec3f cross_dir = line_dir.cross(camera_dir).normalized(); + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir.cast()).toRotationMatrix(); + + m_rotation_gizmo.set_rotation(Geometry::Transformation(m).get_rotation()); + + set_center(Vec3d(0.5 * (point[X] + m_line_beg[X]), 0.5 * (point[Y] + m_line_beg[Y]), 0.5 * (point[Z] + m_line_beg[Z]))); + + m_line_end = m_line_beg = Vec3d::Zero(); + return true; + } + } + } + return false; +} + bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) @@ -1348,6 +1446,10 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; const Camera& camera = wxGetApp().plater()->get_camera(); + if ( m_hover_id < 0 && shift_down && + (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::Moving) ) + return process_cut_line(action, mouse_position); + int mesh_id = -1; // left down without selection rectangle - place connector on the cut plane: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 400018897..6edcded0f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -40,9 +40,12 @@ class GLGizmoCut3D : public GLGizmoBase // workaround for using of the clipping plane normal Vec3d m_clp_normal{ Vec3d::Ones() }; + Vec3d m_line_beg{ Vec3d::Zero() }; + Vec3d m_line_end{ Vec3d::Zero() }; + #if ENABLE_LEGACY_OPENGL_REMOVAL GLModel m_plane; -// GLModel m_grabber_connection; + GLModel m_cut_line; GLModel m_cone; GLModel m_sphere; Vec3d m_old_center; @@ -74,7 +77,6 @@ class GLGizmoCut3D : public GLGizmoBase enum class CutMode { cutPlanar - , cutByLine , cutGrig //,cutRadial //,cutModular @@ -159,15 +161,18 @@ private: void render_connectors(bool picking); bool can_perform_cut() const; + bool cut_line_processing() const; void render_cut_plane(); void render_cut_center_graber(); + void render_cut_line(); void perform_cut(const Selection& selection); void set_center_pos(const Vec3d& center_pos); bool update_bb(); void reset_connectors(); void update_connector_shape(); void update_model_object() const; + bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); }; } // namespace GUI diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 58b8c7183..7822a8182 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -406,6 +406,24 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& return true; } +bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3d& position, Vec3d& normal) const +{ + Vec3d point; + Vec3d direction; + line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); + + std::vector hits = m_emesh.query_ray_hits(point, direction); + + if (hits.empty()) + return false; // no intersection found + + // Now stuff the points in the provided vector and calculate normals if asked about them: + position = hits[0].position(); + normal = hits[0].normal(); + + return true; +} bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const { diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 2a25ddc73..621245394 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -153,6 +153,9 @@ public: bool* was_clipping_plane_hit = nullptr // is the hit on the clipping place cross section? ) const; + // Given a mouse position, this returns true in case it is on the mesh. + bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& position, Vec3d& normal) const; + bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const; // Given a vector of points in woorld coordinates, this returns vector From 51e77fd81b440d203df2b1341f81d42fa877732f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 9 May 2022 16:13:13 +0200 Subject: [PATCH 31/97] Cut WIP: Make negative volumes a little bit "dipper" + Some improvements for Undo/Redo stack --- src/libslic3r/Model.cpp | 10 ++++-- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 50 ++++++++++++++++------------ 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index a034787ac..8a8bb9490 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1511,11 +1511,17 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const if (!volume->is_model_part()) { if (volume->source.is_connector) { - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { ModelVolume* vol = upper->add_volume(*volume); + // make a "hole" dipper + vol->set_scaling_factor(Z, 1.1 * vol->get_scaling_factor(Z)); + } if (attributes.has(ModelObjectCutAttribute::KeepLower)) { ModelVolume* vol = lower->add_volume(*volume); - if (!attributes.has(ModelObjectCutAttribute::CreateDowels)) + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) + // make a "hole" dipper + vol->set_scaling_factor(Z, 1.2 * vol->get_scaling_factor(Z)); + else // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug vol->set_type(ModelVolumeType::MODEL_PART); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 7ad1d71f6..da2b28dcd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -714,8 +714,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) if (m_hover_id == m_group_id) { #if use_grabber_extension - Vec3d starting_box_center = m_plane_center; - starting_box_center[Z] -= 1.0; // some Margin + Vec3d starting_box_center = m_plane_center - Vec3d::UnitZ();// some Margin rotate_vec3d_around_center(starting_box_center, m_rotation_gizmo.get_rotation(), m_plane_center); #else const Vec3d& starting_box_center = m_plane_center; @@ -1155,7 +1154,7 @@ void GLGizmoCut3D::render_connectors(bool picking) const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; - const Transform3d instance_trafo = Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift), Vec3d::Zero(), Vec3d::Ones(), Vec3d::Ones()) * mi->get_transformation().get_matrix(); + const Transform3d instance_trafo = Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix(); for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; @@ -1268,29 +1267,34 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) if(!mo) return; - Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); - const bool has_connectors = !mo->cut_connectors.empty(); - // update connectors pos as offset of its center before cut performing - if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { - for (CutConnector& connector : mo->cut_connectors) { - connector.rotation = m_rotation_gizmo.get_rotation(); + { + Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); + // update connectors pos as offset of its center before cut performing + if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { + for (CutConnector& connector : mo->cut_connectors) { + connector.rotation = m_rotation_gizmo.get_rotation(); - if (m_connector_type == CutConnectorType::Dowel) { - if (m_connector_style == size_t(CutConnectorStyle::Prizm)) - connector.height *= 2; - } - else { - // culculate shift of the connector center regarding to the position on the cut plane - Vec3d norm = m_grabbers[0].center - m_plane_center; - norm.normalize(); - Vec3d shift = norm * (0.5 * connector.height); - connector.pos += shift; + if (m_connector_type == CutConnectorType::Dowel) { + if (m_connector_style == size_t(CutConnectorStyle::Prizm)) + connector.height *= 2; + } + else { + // culculate shift of the connector center regarding to the position on the cut plane +#if use_grabber_extension + Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); + rotate_vec3d_around_center(shifted_center, m_rotation_gizmo.get_rotation(), m_plane_center); + Vec3d norm = (shifted_center - m_plane_center).normalized(); +#else + Vec3d norm = (m_grabbers[0].center - m_plane_center).normalize(); +#endif + connector.pos += norm * (0.5 * connector.height); + } } + mo->apply_cut_connectors(_u8L("Connector"), CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); + if (m_connector_type == CutConnectorType::Dowel) + create_dowels_as_separate_object = true; } - mo->apply_cut_connectors(_u8L("Connector"), CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); - if (m_connector_type == CutConnectorType::Dowel) - create_dowels_as_separate_object = true; } plater->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), @@ -1421,6 +1425,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse Vec3f camera_dir = camera.get_dir_forward().cast(); Vec3f line_dir = (m_line_end - m_line_beg).cast(); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by line"), UndoRedo::SnapshotType::GizmoAction); + Vec3f cross_dir = line_dir.cross(camera_dir).normalized(); Eigen::Quaterniond q; Transform3d m = Transform3d::Identity(); From 279b116533843aa406850427fad7d831c4ea53ec Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 9 May 2022 16:52:02 +0200 Subject: [PATCH 32/97] Cut WIP: Fix compilation warnings and errors --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 19 ++++++------------- src/slic3r/GUI/Plater.cpp | 9 +++++---- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index da2b28dcd..b83d4ffbb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -255,8 +255,6 @@ bool GLGizmoCut3D::render_combo(const std::string& label, const std::vectortext(_L("Build size")); @@ -1158,7 +1155,7 @@ void GLGizmoCut3D::render_connectors(bool picking) for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; - const bool& point_selected = m_selected[i]; +// const bool& point_selected = m_selected[i]; double height = connector.height; // recalculate connector position to world position @@ -1449,14 +1446,11 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) return false; - CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - const Camera& camera = wxGetApp().plater()->get_camera(); - if ( m_hover_id < 0 && shift_down && (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::Moving) ) return process_cut_line(action, mouse_position); - int mesh_id = -1; + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; // left down without selection rectangle - place connector on the cut plane: if (action == SLAGizmoEventType::LeftDown && /*!m_selection_rectangle.is_dragging() && */!shift_down) { @@ -1469,7 +1463,6 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi std::pair pos_and_normal; if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal)) { const Vec3d& hit = pos_and_normal.first; - const Vec3d& normal = pos_and_normal.second; // The clipping plane was clicked, hit containts coordinates of the hit in world coords. std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a1773c649..a1694dc17 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2977,10 +2977,11 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx) // show warning message that "cut consistancy" will not be supported any more ModelObject* obj = model.objects[obj_idx]; if (obj->is_cut()) { - MessageDialog dialog(q, _L("You try to delete an object which is a part of a cut object.\n" - "This action will break a cut correspondence.\n" - "After that PrusaSlicer can't garantie model consistency"), - _L("Delete object which is a part of cut object"), wxYES | wxCANCEL | wxCANCEL_DEFAULT); + InfoDialog dialog(q, _L("Delete object which is a part of cut object"), + _L("You try to delete an object which is a part of a cut object.\n" + "This action will break a cut correspondence.\n" + "After that PrusaSlicer can't garantie model consistency"), + false, wxYES | wxCANCEL | wxCANCEL_DEFAULT | wxICON_WARNING); dialog.SetButtonLabel(wxID_YES, _L("Delete object")); if (dialog.ShowModal() == wxID_CANCEL) return false; From d6f46bedeb73bb717cd6e8138a75a872481f0fcd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 12 May 2022 13:23:59 +0200 Subject: [PATCH 33/97] Cut WIP: Fixed some bugs * Fixed a crash on adding of the object (ObjectDataViewModel:volume_type wasn't initialized) * Cur grabber color is changed to YELLOW * Check position of the cut plane center on moving of the cut plane --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 10 +++++++--- src/slic3r/GUI/ObjectDataViewModel.hpp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index b83d4ffbb..5e8c07dd9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -23,7 +23,7 @@ namespace Slic3r { namespace GUI { static const double Margin = 20.0; -static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); +static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); #define use_grabber_extension 1 @@ -163,7 +163,7 @@ void GLGizmoCut3D::shift_cut_z(double delta) { Vec3d new_cut_center = m_plane_center; new_cut_center[Z] += delta; - set_center_pos(new_cut_center); + set_center(new_cut_center); } void GLGizmoCut3D::rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center) @@ -616,7 +616,7 @@ void GLGizmoCut3D::render_cut_line() GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = ColorRGBA::YELLOW(); + init_data.color = GRABBER_COLOR; init_data.reserve_vertices(2); init_data.reserve_indices(2); @@ -773,6 +773,10 @@ void GLGizmoCut3D::on_stop_dragging() void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos) { + const BoundingBoxf3 tbb = transformed_bounding_box(true); + if (!tbb.contains(center_pos)) + return; + m_plane_center = center_pos; // !!! ysFIXME add smart clamp calculation diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index ec1d801c9..f5c61a6d4 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -83,7 +83,7 @@ class ObjectDataViewModelNode bool m_has_lock{false}; std::string m_action_icon_name = ""; - ModelVolumeType m_volume_type; + ModelVolumeType m_volume_type{ -1 }; InfoItemType m_info_item_type {InfoItemType::Undef}; public: From 496481e9726e78a0bd50f3822aa98aeac43888fd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 12 May 2022 17:07:13 +0200 Subject: [PATCH 34/97] Cut WIP: Fix for Undo/Redo --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 43 ++++++++++-------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 5 ++- src/slic3r/GUI/Plater.cpp | 38 ++++++++++++++------- 4 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5e8c07dd9..820124a7e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -651,7 +651,7 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, m_ar_plane_center, m_ar_rotations); - set_center_pos(m_ar_plane_center); + set_center_pos(m_ar_plane_center, true); m_rotation_gizmo.set_center(m_ar_plane_center); m_rotation_gizmo.set_rotation(m_ar_rotations); force_update_clipper_on_render = true; @@ -771,21 +771,12 @@ void GLGizmoCut3D::on_stop_dragging() } } -void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos) +void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/) { - const BoundingBoxf3 tbb = transformed_bounding_box(true); - if (!tbb.contains(center_pos)) - return; - - m_plane_center = center_pos; - - // !!! ysFIXME add smart clamp calculation - // Clamp the center position of the cut plane to the object's bounding box - //m_plane_center = Vec3d(std::clamp(center_pos.x(), m_min_pos.x(), m_max_pos.x()), - // std::clamp(center_pos.y(), m_min_pos.y(), m_max_pos.y()), - // std::clamp(center_pos.z(), m_min_pos.z(), m_max_pos.z())); - - m_center_offset = m_plane_center - m_bb_center; + if (force || transformed_bounding_box(true).contains(center_pos)) { + m_plane_center = center_pos; + m_center_offset = m_plane_center - m_bb_center; + } } BoundingBoxf3 GLGizmoCut3D::bounding_box() const @@ -806,18 +797,18 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* { // #ysFIXME !!! BoundingBoxf3 ret; - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - const int instance_idx = selection.get_instance_idx(); - const int object_idx = selection.get_object_idx(); - if (instance_idx < 0 || object_idx < 0) + const ModelObject* mo = m_c->selection_info()->model_object(); + if (!mo) return ret; + const int instance_idx = m_c->selection_info()->get_active_instance(); + if (instance_idx < 0) + return ret; + const ModelInstance* mi = mo->instances[instance_idx]; - const Vec3d& instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); - + const Vec3d& instance_offset = mi->get_offset(); Vec3d cut_center_offset = m_plane_center - instance_offset; - cut_center_offset[Z] -= selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); + cut_center_offset[Z] -= m_c->selection_info()->get_sla_shift(); const Vec3d& rotation = m_rotation_gizmo.get_rotation(); const auto move = Geometry::assemble_transform(-cut_center_offset); @@ -828,6 +819,8 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * rot_x * rot_y * rot_z * move; + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int i : idxs) { const GLVolume* volume = selection.get_volume(i); // respect just to the solid parts for FFF and ignore pad and supports for SLA @@ -853,7 +846,7 @@ bool GLGizmoCut3D::update_bb() m_max_pos = box.max; m_min_pos = box.min; m_bb_center = box.center(); - set_center_pos(m_bb_center + m_center_offset); + set_center_pos(m_bb_center + m_center_offset, true); m_plane.reset(); m_cone.reset(); @@ -1270,9 +1263,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) const bool has_connectors = !mo->cut_connectors.empty(); { - Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); // update connectors pos as offset of its center before cut performing if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { + Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); for (CutConnector& connector : mo->cut_connectors) { connector.rotation = m_rotation_gizmo.get_rotation(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 6edcded0f..439abea8d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -167,7 +167,7 @@ private: void render_cut_center_graber(); void render_cut_line(); void perform_cut(const Selection& selection); - void set_center_pos(const Vec3d& center_pos); + void set_center_pos(const Vec3d& center_pos, bool force = false); bool update_bb(); void reset_connectors(); void update_connector_shape(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 7f60892b1..e7ba37010 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -414,8 +414,11 @@ void ObjectClipper::render_cut() const if (m_clp_ratio == 0.) return; const SelectionInfo* sel_info = get_pool()->selection_info(); + int sel_instance_idx = sel_info->get_active_instance(); + if (sel_instance_idx < 0) + return; const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); + const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); size_t clipper_id = 0; for (const ModelVolume* mv : mo->volumes) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a1694dc17..26c7da784 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1780,7 +1780,7 @@ struct Plater::priv std::string get_config(const std::string &key) const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config, bool used_inches = false); - std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); + std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool call_selection_changed = true); fs::path get_export_file_path(GUI::FileType file_type); wxString get_export_file(GUI::FileType file_type); @@ -2691,7 +2691,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ // #define AUTOPLACEMENT_ON_LOAD -std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z) +std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z, bool call_selection_changed /*= true*/) { const Vec3d bed_size = Slic3r::to_3d(this->bed.build_volume().bounding_volume2d().size(), 1.0) - 2.0 * Vec3d::Ones(); @@ -2774,17 +2774,18 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode notification_manager->close_notification_of_type(NotificationType::UpdatedItemsInfo); for (const size_t idx : obj_idxs) { - wxGetApp().obj_list()->add_object_to_list(idx); + wxGetApp().obj_list()->add_object_to_list(idx, call_selection_changed); } - update(); - // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), - // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call - for (const size_t idx : obj_idxs) - wxGetApp().obj_list()->update_info_items(idx); - - object_list_changed(); + if (call_selection_changed) { + update(); + // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), + // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call + for (const size_t idx : obj_idxs) + wxGetApp().obj_list()->update_info_items(idx); + object_list_changed(); + } this->schedule_background_process(); return obj_idxs; @@ -5919,18 +5920,29 @@ void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - //Plater::TakeSnapshot snapshot(this, _L("Cut by Plane")); + this->suppress_snapshots(); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, cut_center, cut_rotation, attributes); - remove(obj_idx); - p->load_model_objects(new_objects); + model().delete_object(obj_idx); + sidebar().obj_list()->delete_object_from_list(obj_idx); + + // suppress to call selection update for Object List to avoid call of early Gizmos on/off update + p->load_model_objects(new_objects, false, false); Selection& selection = p->get_selection(); size_t last_id = p->model.objects.size() - 1; for (size_t i = 0; i < new_objects.size(); ++i) selection.add_object((unsigned int)(last_id - i), i == 0); + this->allow_snapshots(); + + // now process all updates of the 3d scene + update(); + // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), + // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call + for (size_t idx = 0; idx < p->model.objects.size(); idx++) + wxGetApp().obj_list()->update_info_items(idx); } void Plater::export_gcode(bool prefer_removable) From ef26b1abebd56d8a1f6014b49e633b66d8d4fad7 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 16 May 2022 11:44:30 +0200 Subject: [PATCH 35/97] Cut gizmo: cut by line does not rely on mesh raycasters --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 62 +++++++++++----------------- src/slic3r/GUI/MeshUtils.cpp | 2 +- src/slic3r/GUI/MeshUtils.hpp | 4 +- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 820124a7e..6a8a99856 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1391,49 +1391,37 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse const Camera& camera = wxGetApp().plater()->get_camera(); - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - ++mesh_id; - if (!mv->is_model_part()) - continue; + Vec3d pt; + Vec3d dir; + MeshRaycaster::line_from_mouse_pos(mouse_position, Transform3d::Identity(), camera, pt, dir); + dir.normalize(); + pt += dir; // Move the pt along dir so it is not clipped. - const Transform3d trafo = inst_trafo * mv->get_matrix(); - const MeshRaycaster* raycaster = m_c->raycaster()->raycasters()[mesh_id]; + if (action == SLAGizmoEventType::LeftDown && !cut_line_processing()) { + m_line_beg = pt; + m_line_end = pt; + return true; + } - Vec3d point; - Vec3d direction; - if (raycaster->unproject_on_mesh(mouse_position, trafo, camera, point, direction)) - { - point += mi->get_offset(); - point[Z] += sla_shift; + if (cut_line_processing()) { + m_line_end = pt; + if (action == SLAGizmoEventType::LeftDown) { + Vec3d point = m_line_end; + Vec3d line_dir = m_line_end - m_line_beg; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by line"), UndoRedo::SnapshotType::GizmoAction); - if (action == SLAGizmoEventType::LeftDown && !cut_line_processing()) { - m_line_beg = point; - return true; - } - if (action == SLAGizmoEventType::Moving && cut_line_processing()) { - m_line_end = point; - return true; - } - if (action == SLAGizmoEventType::LeftDown && cut_line_processing()) { - Vec3f camera_dir = camera.get_dir_forward().cast(); - Vec3f line_dir = (m_line_end - m_line_beg).cast(); + Vec3d cross_dir = line_dir.cross(dir).normalized(); + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir).toRotationMatrix(); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by line"), UndoRedo::SnapshotType::GizmoAction); + m_rotation_gizmo.set_rotation(Geometry::Transformation(m).get_rotation()); - Vec3f cross_dir = line_dir.cross(camera_dir).normalized(); - Eigen::Quaterniond q; - Transform3d m = Transform3d::Identity(); - m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir.cast()).toRotationMatrix(); + set_center(m_plane_center + cross_dir * (cross_dir.dot(pt - m_plane_center))); - m_rotation_gizmo.set_rotation(Geometry::Transformation(m).get_rotation()); - - set_center(Vec3d(0.5 * (point[X] + m_line_beg[X]), 0.5 * (point[Y] + m_line_beg[Y]), 0.5 * (point[Z] + m_line_beg[Z]))); - - m_line_end = m_line_beg = Vec3d::Zero(); - return true; - } + m_line_end = m_line_beg = Vec3d::Zero(); } + return true; } return false; } @@ -1461,7 +1449,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal)) { const Vec3d& hit = pos_and_normal.first; // The clipping plane was clicked, hit containts coordinates of the hit in world coords. - std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; + //std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); connectors.emplace_back(hit, m_rotation_gizmo.get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio)); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 7822a8182..171979406 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -324,7 +324,7 @@ Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const } void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) const + Vec3d& point, Vec3d& direction) { Matrix4d modelview = camera.get_view_matrix().matrix(); Matrix4d projection= camera.get_projection_matrix().matrix(); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 621245394..9c8bc8e67 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -138,8 +138,8 @@ public: { } - void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) const; + static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3d& point, Vec3d& direction); // Given a mouse position, this returns true in case it is on the mesh. bool unproject_on_mesh( From 7129ee382971e8fa5c9c6dc3443e558bdc332ed2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 May 2022 15:26:43 +0200 Subject: [PATCH 36/97] Merge branch 'tm_curl_new' (cherry-picked from master) --- CMakeLists.txt | 33 ++++++++++++++++----------------- deps/CMakeLists.txt | 9 +++++++-- deps/CURL/CURL.cmake | 14 ++++++++------ deps/deps-macos.cmake | 5 +++++ src/CMakeLists.txt | 2 +- src/libslic3r/CMakeLists.txt | 2 +- src/slic3r/CMakeLists.txt | 4 ---- 7 files changed, 38 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a486000f..002cd3456 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,14 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") set(IS_CROSS_COMPILE FALSE) +if (SLIC3R_STATIC) + # Prefer config scripts over find modules. This is helpful when building with + # the static dependencies. Many libraries have their own export scripts + # while having a Find module in standard cmake installation. + # (e.g. CURL) + set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) +endif () + if (APPLE) set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_APPBUNDLE LAST) @@ -438,23 +446,6 @@ else() target_link_libraries(libcurl INTERFACE crypt32) endif() -if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL) - if (NOT APPLE) - # libcurl is always linked dynamically to the system libcurl on OSX. - # On other systems, libcurl is linked statically if SLIC3R_STATIC is set. - target_compile_definitions(libcurl INTERFACE CURL_STATICLIB) - endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - # As of now, our build system produces a statically linked libcurl, - # which links the OpenSSL library dynamically. - find_package(OpenSSL REQUIRED) - message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") - message("OpenSSL libraries: ${OPENSSL_LIBRARIES}") - target_include_directories(libcurl INTERFACE ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(libcurl INTERFACE ${OPENSSL_LIBRARIES}) - endif() -endif() - ## OPTIONAL packages # Find eigen3 or use bundled version @@ -472,6 +463,14 @@ include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) find_package(EXPAT REQUIRED) +add_library(libexpat INTERFACE) + +if (TARGET EXPAT::EXPAT ) + target_link_libraries(libexpat INTERFACE EXPAT::EXPAT) +elseif(TARGET expat::expat) + target_link_libraries(libexpat INTERFACE expat::expat) +endif () + find_package(PNG REQUIRED) set(OpenGL_GL_PREFERENCE "LEGACY") diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 94daee85f..d129ff1c2 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -179,7 +179,12 @@ include(CGAL/CGAL.cmake) include(NLopt/NLopt.cmake) include(OpenSSL/OpenSSL.cmake) -include(CURL/CURL.cmake) + +set(CURL_PKG "") +if (NOT CURL_FOUND) + include(CURL/CURL.cmake) + set(CURL_PKG dep_CURL) +endif () include(JPEG/JPEG.cmake) include(TIFF/TIFF.cmake) @@ -188,7 +193,7 @@ include(wxWidgets/wxWidgets.cmake) set(_dep_list dep_Boost dep_TBB - dep_CURL + ${CURL_PKG} dep_wxWidgets dep_Cereal dep_NLopt diff --git a/deps/CURL/CURL.cmake b/deps/CURL/CURL.cmake index a05a4e97e..579a27f66 100644 --- a/deps/CURL/CURL.cmake +++ b/deps/CURL/CURL.cmake @@ -48,11 +48,13 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") ) endif () -if (BUILD_SHARED_LIBS) - set(_curl_static OFF) -else() - set(_curl_static ON) -endif() +set(_patch_command "") +if (UNIX AND NOT APPLE) + # On non-apple UNIX platforms, finding the location of OpenSSL certificates is necessary at runtime, as there is no standard location usable across platforms. + # The OPENSSL_CERT_OVERRIDE flag is understood by PrusaSlicer and will trigger the search of certificates at initial application launch. + # Then ask the user for consent about the correctness of the found location. + set (_patch_command echo set_target_properties(CURL::libcurl PROPERTIES INTERFACE_COMPILE_DEFINITIONS OPENSSL_CERT_OVERRIDE) >> CMake/curl-config.cmake.in) +endif () prusaslicer_add_cmake_project(CURL # GIT_REPOSITORY https://github.com/curl/curl.git @@ -62,10 +64,10 @@ prusaslicer_add_cmake_project(CURL DEPENDS ${ZLIB_PKG} # PATCH_COMMAND ${GIT_EXECUTABLE} checkout -f -- . && git clean -df && # ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/curl-mods.patch + PATCH_COMMAND "${_patch_command}" CMAKE_ARGS -DBUILD_TESTING:BOOL=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DCURL_STATICLIB=${_curl_static} ${_curl_platform_flags} ) diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index 42afc623d..d9e0ce377 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -15,6 +15,11 @@ set(DEP_CMAKE_OPTS include("deps-unix-common.cmake") +find_package(CURL QUIET) +if (NOT CURL_FOUND) + message(WARNING "No CURL dev package found in system, building static library. Mac SDK should include CURL from at least version 10.12. Check your SDK installation.") +endif () + # ExternalProject_Add(dep_boost # EXCLUDE_FROM_ALL 1 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8430e968..801760b8c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,7 +92,7 @@ if (SLIC3R_GUI) string(REGEX MATCH "wxexpat" WX_EXPAT_BUILTIN ${wxWidgets_LIBRARIES}) if (EXPAT_FOUND AND NOT WX_EXPAT_BUILTIN) list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat) - list(APPEND wxWidgets_LIBRARIES EXPAT::EXPAT) + list(APPEND wxWidgets_LIBRARIES libexpat) endif () # This is an issue in the new wxWidgets cmake build, doesn't deal with librt diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index c9d8aa4fa..396eb0764 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -372,7 +372,7 @@ target_link_libraries(libslic3r boost_libs clipper nowide - EXPAT::EXPAT + libexpat glu-libtess qhull semver diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ef7687f00..ed994be18 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -288,10 +288,6 @@ if (SLIC3R_STATIC) target_compile_definitions(libslic3r_gui PUBLIC -DwxDEBUG_LEVEL=0) endif() -if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL AND UNIX AND NOT APPLE) - target_compile_definitions(libslic3r_gui PRIVATE OPENSSL_CERT_OVERRIDE) -endif () - if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () From c695dcc141e5bb1d1a2cd5bb6f56bfb301b978d7 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 16 May 2022 16:24:35 +0200 Subject: [PATCH 37/97] Cut gizmo: UI simplification and changes --- src/imgui/imconfig.h | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 276 +++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 2 +- src/slic3r/GUI/ImGuiWrapper.cpp | 1 + 6 files changed, 166 insertions(+), 119 deletions(-) diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index dcb2d2338..9a29789d3 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -169,6 +169,7 @@ namespace ImGui const wchar_t LegendCOG = 0x2615; const wchar_t LegendShells = 0x2616; const wchar_t LegendToolMarker = 0x2617; + const wchar_t WarningMarkerSmall = 0x2618; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 6a8a99856..58eb19a04 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -19,6 +19,8 @@ #include "libslic3r/Model.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" +#include "imgui/imgui_internal.h" + namespace Slic3r { namespace GUI { @@ -39,7 +41,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, m_group_id = 3; m_connectors_group_id = 4; - m_modes = { _u8L("Planar"), _u8L("Grid") + m_modes = { _u8L("Planar")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") }; @@ -471,6 +473,8 @@ void GLGizmoCut3D::render_cut_plane() void GLGizmoCut3D::render_cut_center_graber() { + ::glDisable(GL_DEPTH_TEST); + Slic3r::ScopeGuard guard([]() { ::glEnable(GL_DEPTH_TEST); }); const Vec3d& angles = m_rotation_gizmo.get_rotation(); m_grabbers[0].angles = angles; m_grabbers[0].color = GRABBER_COLOR; @@ -878,9 +882,13 @@ void GLGizmoCut3D::on_render() render_connectors(false); + if (! m_connectors_editing) + ::glDisable(GL_DEPTH_TEST); m_c->object_clipper()->render_cut(); + if (! m_connectors_editing) + ::glEnable(GL_DEPTH_TEST); - if (!m_hide_cut_plane) { + if (!m_hide_cut_plane && ! m_connectors_editing) { render_cut_center_graber(); render_cut_plane(); if (m_hover_id < m_group_id && m_mode == size_t(CutMode::cutPlanar) && !cut_line_processing()) @@ -922,146 +930,175 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) last_y = y; } - render_combo(_u8L("Mode"), m_modes, m_mode); - - bool revert_rotation{ false }; - bool revert_move{ false }; + // render_combo(_u8L("Mode"), m_modes, m_mode); CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + bool cut_clicked = false; + bool revert_move{ false }; + bool revert_rotation{ false }; - if (m_mode == size_t(CutMode::cutPlanar)) { - ImGui::AlignTextToFramePadding(); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); - ImGui::Separator(); + if (! m_connectors_editing) { + if (m_mode == size_t(CutMode::cutPlanar)) { + ImGui::AlignTextToFramePadding(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); + ImGui::Separator(); - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Move center")); + //////// + double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; + std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm"); + const BoundingBoxf3 tbb = transformed_bounding_box(); + double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; + double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; + + //static float v = 0.; // TODO: connect to cutting plane position + m_imgui->text(_L("Cut position: ")); + render_move_center_input(Z); + //m_imgui->input_double(unit_str, v); + //v = std::clamp(v, 0.f, float(bottom+top)); + if (m_imgui->button("Reset cutting plane")) { + // TODO: reset both position and rotation + } + ////// - m_imgui->disabled_begin(m_plane_center == bounding_box().center()); - revert_move = render_revert_button("move"); - m_imgui->disabled_end(); + // ImGui::AlignTextToFramePadding(); + // m_imgui->text(_L("Move center")); - for (Axis axis : {X, Y, Z}) - render_move_center_input(axis); - m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + // m_imgui->disabled_begin(m_plane_center == bounding_box().center()); + // revert_move = render_revert_button("move"); + // m_imgui->disabled_end(); - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Rotation")); + // for (Axis axis : {X, Y, Z}) + // render_move_center_input(axis); + // m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); - m_imgui->disabled_begin(m_rotation_gizmo.get_rotation() == Vec3d::Zero()); - revert_rotation = render_revert_button("rotation"); - m_imgui->disabled_end(); + // ImGui::AlignTextToFramePadding(); + // m_imgui->text(_L("Rotation")); - for (Axis axis : {X, Y, Z}) - render_rotation_input(axis); - m_imgui->text(_L("°")); + // m_imgui->disabled_begin(m_rotation_gizmo.get_rotation() == Vec3d::Zero()); + // revert_rotation = render_revert_button("rotation"); + // m_imgui->disabled_end(); + + // for (Axis axis : {X, Y, Z}) + // render_rotation_input(axis); + // m_imgui->text(_L("°")); + + // ImGui::Separator(); + + // double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; + // wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); + + // Vec3d tbb_sz = 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; + + // ImGui::AlignTextToFramePadding(); + // m_imgui->text(_L("Build size")); + // ImGui::SameLine(m_label_width); + // m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); + } + + if (m_mode == size_t(CutMode::cutPlanar)) { + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("After cut") + ": "); + bool keep = true; + + ImGui::SameLine(m_label_width); + m_imgui->text(_L("Upper part")); + ImGui::SameLine(2 * m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); + m_imgui->disabled_end(); + ImGui::SameLine(); + m_imgui->disabled_begin(!m_keep_upper); + m_imgui->disabled_begin(is_approx(m_rotation_gizmo.get_rotation().x(), 0.) && is_approx(m_rotation_gizmo.get_rotation().y(), 0.)); + m_imgui->checkbox(_L("Place on cut") + "##upper", m_rotate_upper); + m_imgui->disabled_end(); + m_imgui->disabled_end(); + + m_imgui->text(""); + ImGui::SameLine(m_label_width); + m_imgui->text(_L("Lower part")); + ImGui::SameLine(2 * m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); + m_imgui->disabled_end(); + ImGui::SameLine(); + m_imgui->disabled_begin(!m_keep_lower); + m_imgui->checkbox(_L("Place on cut") + "##lower", m_rotate_lower); + m_imgui->disabled_end(); + } ImGui::Separator(); - double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; - wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); - - Vec3d tbb_sz = 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; - - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Build size")); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); - } - - m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); - // Connectors section - ImGui::Separator(); - - ImGui::AlignTextToFramePadding(); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); - - m_imgui->disabled_begin(connectors.empty()); - ImGui::SameLine(m_label_width); - if (m_imgui->button(" " + _L("Reset") + " ##connectors")) - reset_connectors(); - m_imgui->disabled_end(); - - m_imgui->text(_L("Mode")); - render_connect_mode_radio_button(CutConnectorMode::Auto); - render_connect_mode_radio_button(CutConnectorMode::Manual); - - m_imgui->text(_L("Type")); - render_connect_type_radio_button(CutConnectorType::Plug); - render_connect_type_radio_button(CutConnectorType::Dowel); - - if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) - update_connector_shape(); - if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) - update_connector_shape(); - - if (render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio)) - for (auto& connector : connectors) - connector.height = float(m_connector_depth_ratio); - if (render_double_input(_u8L("Size"), m_connector_size)) - for (auto& connector : connectors) - connector.radius = float(m_connector_size * 0.5); - - m_imgui->disabled_end(); - - if (m_mode == size_t(CutMode::cutPlanar)) { + if (m_imgui->button(_L("Add/Edit connectors"))) + m_connectors_editing = true; + } else { // connectors mode + m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); + // Connectors section ImGui::Separator(); ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("After cut") + ": "); - bool keep = true; + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); + m_imgui->disabled_begin(connectors.empty()); ImGui::SameLine(m_label_width); - m_imgui->text(_L("Upper part")); - ImGui::SameLine(2 * m_label_width); - - m_imgui->disabled_begin(!connectors.empty()); - m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); - m_imgui->disabled_end(); - ImGui::SameLine(); - m_imgui->disabled_begin(!m_keep_upper); - m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper); + if (m_imgui->button(" " + _L("Reset") + " ##connectors")) + reset_connectors(); m_imgui->disabled_end(); - m_imgui->text(""); - ImGui::SameLine(m_label_width); - m_imgui->text(_L("Lower part")); - ImGui::SameLine(2 * m_label_width); + m_imgui->text(_L("Mode")); + render_connect_mode_radio_button(CutConnectorMode::Auto); + render_connect_mode_radio_button(CutConnectorMode::Manual); + + m_imgui->text(_L("Type")); + render_connect_type_radio_button(CutConnectorType::Plug); + render_connect_type_radio_button(CutConnectorType::Dowel); + + if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) + update_connector_shape(); + if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) + update_connector_shape(); + + if (render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio)) + for (auto& connector : connectors) + connector.height = float(m_connector_depth_ratio); + if (render_double_input(_u8L("Size"), m_connector_size)) + for (auto& connector : connectors) + connector.radius = float(m_connector_size * 0.5); - m_imgui->disabled_begin(!connectors.empty()); - m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); - m_imgui->disabled_end(); - ImGui::SameLine(); - m_imgui->disabled_begin(!m_keep_lower); - m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower); m_imgui->disabled_end(); + + if (m_imgui->button(_L("Confirm connectors"))) + m_connectors_editing = false; } ImGui::Separator(); + m_imgui->text(m_has_invalid_connector ? wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.") : wxString()); m_imgui->disabled_begin(!can_perform_cut()); - const bool cut_clicked = m_imgui->button(_L("Perform cut")); + cut_clicked = m_imgui->button(_L("Perform cut")); m_imgui->disabled_end(); - ImGui::Separator(); - - m_imgui->checkbox(_L("Hide cut plane and grabbers"), m_hide_cut_plane); + m_imgui->end(); //////// - static bool hide_clipped = true; - static bool fill_cut = true; - static float contour_width = 0.2f; + m_imgui->begin(wxString("DEBUG")); + 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) m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_imgui->checkbox("fill_cut", fill_cut); m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); - m_c->object_clipper()->set_behavior(hide_clipped, fill_cut, contour_width); - //////// - + m_c->object_clipper()->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, contour_width); m_imgui->end(); + //////// if (cut_clicked && (m_keep_upper || m_keep_lower)) perform_cut(m_parent.get_selection()); @@ -1101,10 +1138,16 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c void GLGizmoCut3D::render_connectors(bool picking) { - if (cut_line_processing() || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) + if (picking && ! m_connectors_editing) return; - m_has_invalid_connector = false; + const bool depth_test = m_connectors_editing; + if (! depth_test) + ::glDisable(GL_DEPTH_TEST); + Slic3r::ScopeGuard guard_depth_test([&](){ if (! depth_test) ::glEnable(GL_DEPTH_TEST); }); + + if (cut_line_processing() || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) + return; const ModelObject* mo = m_c->selection_info()->model_object(); auto inst_id = m_c->selection_info()->get_active_instance(); @@ -1150,6 +1193,8 @@ void GLGizmoCut3D::render_connectors(bool picking) const Transform3d instance_trafo = Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix(); + m_has_invalid_connector = false; + for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; // const bool& point_selected = m_selected[i]; @@ -1180,13 +1225,12 @@ void GLGizmoCut3D::render_connectors(bool picking) const Transform3d volume_trafo = get_volume_transformation(mv); if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { - render_color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(0.5f, 0.5f, 0.5f, 1.f); break; } render_color = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); - } - if (!m_has_invalid_connector && render_color == ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f)) m_has_invalid_connector = true; + } } } @@ -1330,7 +1374,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair const Transform3d volume_trafo = get_volume_transformation(mv); m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * volume_trafo, - camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), + camera, hit, normal, m_c->object_clipper()->get_clipping_plane(true), nullptr, &clipping_plane_was_hit); if (clipping_plane_was_hit) { // recalculate hit to object's local position @@ -1431,14 +1475,14 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) return false; - if ( m_hover_id < 0 && shift_down && + if ( m_hover_id < 0 && shift_down && ! m_connectors_editing && (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::Moving) ) return process_cut_line(action, mouse_position); CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; // left down without selection rectangle - place connector on the cut plane: - if (action == SLAGizmoEventType::LeftDown && /*!m_selection_rectangle.is_dragging() && */!shift_down) { + if (action == SLAGizmoEventType::LeftDown && /*!m_selection_rectangle.is_dragging() && */!shift_down && m_connectors_editing) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id != -1) return false; @@ -1465,7 +1509,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi } return true; } - else if (action == SLAGizmoEventType::RightDown && !shift_down) { + else if (action == SLAGizmoEventType::RightDown && !shift_down && m_connectors_editing) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id < m_connectors_group_id) return false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 439abea8d..dfaef3fb2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -57,6 +57,7 @@ class GLGizmoCut3D : public GLGizmoBase bool m_rotate_lower{ false }; bool m_hide_cut_plane{ false }; + bool m_connectors_editing{ false }; double m_connector_depth_ratio{ 3.0 }; double m_connector_size{ 2.5 }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index e7ba37010..c28fe6356 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -475,10 +475,10 @@ void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset get_pool()->get_canvas()->set_as_dirty(); } -const ClippingPlane* ObjectClipper::get_clipping_plane() const +const ClippingPlane* ObjectClipper::get_clipping_plane(bool ignore_hide_clipped) const { static const ClippingPlane no_clip = ClippingPlane::ClipsNothing(); - return m_hide_clipped ? m_clp.get() : &no_clip; + return (ignore_hide_clipped || m_hide_clipped) ? m_clp.get() : &no_clip; } void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contour_width) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index efd3b436e..72df620cf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -257,7 +257,7 @@ public: void set_normal(const Vec3d& dir); double get_position() const { return m_clp_ratio; } - const ClippingPlane* get_clipping_plane() const; + const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const; void render_cut() const; void set_position_by_ratio(double pos, bool keep_normal); void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 65b292c25..ff084b0a4 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -76,6 +76,7 @@ static const std::map font_icons = { {ImGui::LegendToolMarker , "legend_toolmarker" }, #endif // ENABLE_LEGEND_TOOLBAR_ICONS {ImGui::RevertButton , "undo" }, + {ImGui::WarningMarkerSmall , "notification_warning" }, }; static const std::map font_icons_large = { From 76ea74c28921a4831ed5ae14b22f5b3f17c1184f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 8 Jun 2022 12:25:01 +0200 Subject: [PATCH 38/97] Update wxWidgets to 3.1.7 added handling for nanosvg with cmake --- deps/CMakeLists.txt | 1 + deps/NanoSVG/NanoSVG.cmake | 4 + deps/wxWidgets/wxWidgets.cmake | 14 +- src/CMakeLists.txt | 5 +- src/nanosvg/README-prusa.txt | 1 - src/nanosvg/nanosvg.h | 2979 ------------------------------- src/nanosvg/nanosvgrast.h | 1452 --------------- src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/BitmapCache.cpp | 6 +- src/slic3r/GUI/GLTexture.cpp | 4 +- src/slic3r/GUI/ImGuiWrapper.cpp | 5 +- 11 files changed, 24 insertions(+), 4451 deletions(-) create mode 100644 deps/NanoSVG/NanoSVG.cmake delete mode 100644 src/nanosvg/README-prusa.txt delete mode 100644 src/nanosvg/nanosvg.h delete mode 100644 src/nanosvg/nanosvgrast.h diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index d129ff1c2..eb0c420fa 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -188,6 +188,7 @@ endif () include(JPEG/JPEG.cmake) include(TIFF/TIFF.cmake) +include(NanoSVG/NanoSVG.cmake) include(wxWidgets/wxWidgets.cmake) set(_dep_list diff --git a/deps/NanoSVG/NanoSVG.cmake b/deps/NanoSVG/NanoSVG.cmake new file mode 100644 index 000000000..9623d3226 --- /dev/null +++ b/deps/NanoSVG/NanoSVG.cmake @@ -0,0 +1,4 @@ +prusaslicer_add_cmake_project(NanoSVG + URL https://github.com/memononen/nanosvg/archive/4c8f0139b62c6e7faa3b67ce1fbe6e63590ed148.zip + URL_HASH SHA256=584e084af1a75bf633f79753ce2f6f6ec8686002ca27f35f1037c25675fecfb6 +) \ No newline at end of file diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index bf5fd6289..4a0875d62 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -1,5 +1,3 @@ -set(_wx_git_tag v3.1.4-patched) - set(_wx_toolkit "") if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(_gtk_ver 2) @@ -15,11 +13,9 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() prusaslicer_add_cmake_project(wxWidgets - # GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" - # GIT_TAG tm_cross_compile #${_wx_git_tag} - URL https://github.com/prusa3d/wxWidgets/archive/489f6118256853cf5b299d595868641938566cdb.zip - URL_HASH SHA256=5b22d465377cedd8044bba69bea958b248953fd3628c1de4913a84d4e6f6175b - DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG + URL https://github.com/prusa3d/wxWidgets/archive/5412ac15586da3ecb6952fcc875d2a23366c998f.zip + URL_HASH SHA256=85a6e13152289fbf1ea51f221fbe1452e7914bbaa665b89536780810e93948a6 + DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS -DwxBUILD_PRECOMP=ON ${_wx_toolkit} @@ -32,7 +28,9 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_OPENGL=ON -DwxUSE_LIBPNG=sys -DwxUSE_ZLIB=sys - -DwxUSE_REGEX=builtin + -DwxUSE_NANOSVG=sys + -DwxUSE_NANOSVG_EXTERNAL=ON + -DwxUSE_REGEX=OFF -DwxUSE_LIBXPM=builtin -DwxUSE_LIBJPEG=sys -DwxUSE_LIBTIFF=sys diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 801760b8c..8a093f639 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -105,7 +105,10 @@ if (SLIC3R_GUI) # wrong libs for opengl in the link line and it does not link to it by himself. # libslic3r_gui will link to opengl anyway, so lets override wx list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX OpenGL) - + + if (UNIX AND NOT APPLE) + list(APPEND wxWidgets_LIBRARIES X11 wayland-client wayland-egl EGL) + endif () # list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc) message(STATUS "wx libs: ${wxWidgets_LIBRARIES}") diff --git a/src/nanosvg/README-prusa.txt b/src/nanosvg/README-prusa.txt deleted file mode 100644 index 8388aa8ef..000000000 --- a/src/nanosvg/README-prusa.txt +++ /dev/null @@ -1 +0,0 @@ -Upstream source: https://github.com/memononen/nanosvg/tree/c1f6e209c16b18b46aa9f45d7e619acf42c29726 \ No newline at end of file diff --git a/src/nanosvg/nanosvg.h b/src/nanosvg/nanosvg.h deleted file mode 100644 index 57bcb7c2c..000000000 --- a/src/nanosvg/nanosvg.h +++ /dev/null @@ -1,2979 +0,0 @@ -/* - * Copyright (c) 2013-14 Mikko Mononen memon@inside.org - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example - * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) - * - * Arc calculation code based on canvg (https://code.google.com/p/canvg/) - * - * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - * - */ - -#ifndef NANOSVG_H -#define NANOSVG_H - -#ifndef NANOSVG_CPLUSPLUS -#ifdef __cplusplus -extern "C" { -#endif -#endif - -// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. -// -// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. -// -// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! -// -// The shapes in the SVG images are transformed by the viewBox and converted to specified units. -// That is, you should get the same looking data as your designed in your favorite app. -// -// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose -// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. -// -// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. -// DPI (dots-per-inch) controls how the unit conversion is done. -// -// If you don't know or care about the units stuff, "px" and 96 should get you going. - - -/* Example Usage: - // Load SVG - NSVGimage* image; - image = nsvgParseFromFile("test.svg", "px", 96); - printf("size: %f x %f\n", image->width, image->height); - // Use... - for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { - for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { - for (int i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); - } - } - } - // Delete - nsvgDelete(image); -*/ - -enum NSVGpaintType { - NSVG_PAINT_NONE = 0, - NSVG_PAINT_COLOR = 1, - NSVG_PAINT_LINEAR_GRADIENT = 2, - NSVG_PAINT_RADIAL_GRADIENT = 3 -}; - -enum NSVGspreadType { - NSVG_SPREAD_PAD = 0, - NSVG_SPREAD_REFLECT = 1, - NSVG_SPREAD_REPEAT = 2 -}; - -enum NSVGlineJoin { - NSVG_JOIN_MITER = 0, - NSVG_JOIN_ROUND = 1, - NSVG_JOIN_BEVEL = 2 -}; - -enum NSVGlineCap { - NSVG_CAP_BUTT = 0, - NSVG_CAP_ROUND = 1, - NSVG_CAP_SQUARE = 2 -}; - -enum NSVGfillRule { - NSVG_FILLRULE_NONZERO = 0, - NSVG_FILLRULE_EVENODD = 1 -}; - -enum NSVGflags { - NSVG_FLAGS_VISIBLE = 0x01 -}; - -typedef struct NSVGgradientStop { - unsigned int color; - float offset; -} NSVGgradientStop; - -typedef struct NSVGgradient { - float xform[6]; - char spread; - float fx, fy; - int nstops; - NSVGgradientStop stops[1]; -} NSVGgradient; - -typedef struct NSVGpaint { - char type; - union { - unsigned int color; - NSVGgradient* gradient; - }; -} NSVGpaint; - -typedef struct NSVGpath -{ - float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... - int npts; // Total number of bezier points. - char closed; // Flag indicating if shapes should be treated as closed. - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - struct NSVGpath* next; // Pointer to next path, or NULL if last element. -} NSVGpath; - -typedef struct NSVGshape -{ - char id[64]; // Optional 'id' attr of the shape or its group - NSVGpaint fill; // Fill paint - NSVGpaint stroke; // Stroke paint - float opacity; // Opacity of the shape. - float strokeWidth; // Stroke width (scaled). - float strokeDashOffset; // Stroke dash offset (scaled). - float strokeDashArray[8]; // Stroke dash array (scaled). - char strokeDashCount; // Number of dash values in dash array. - char strokeLineJoin; // Stroke join type. - char strokeLineCap; // Stroke cap type. - float miterLimit; // Miter limit - char fillRule; // Fill rule, see NSVGfillRule. - unsigned char flags; // Logical or of NSVG_FLAGS_* flags - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - NSVGpath* paths; // Linked list of paths in the image. - struct NSVGshape* next; // Pointer to next shape, or NULL if last element. -} NSVGshape; - -typedef struct NSVGimage -{ - float width; // Width of the image. - float height; // Height of the image. - NSVGshape* shapes; // Linked list of shapes in the image. -} NSVGimage; - -// Parses SVG file from a file, returns SVG image as paths. -NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); - -// Parses SVG file from a null terminated string, returns SVG image as paths. -// Important note: changes the string. -NSVGimage* nsvgParse(char* input, const char* units, float dpi); - -// Duplicates a path. -NSVGpath* nsvgDuplicatePath(NSVGpath* p); - -// Deletes an image. -void nsvgDelete(NSVGimage* image); - -#ifndef NANOSVG_CPLUSPLUS -#ifdef __cplusplus -} -#endif -#endif - -#endif // NANOSVG_H - -#ifdef NANOSVG_IMPLEMENTATION - -#include -#include -#include - -#include - -#define NSVG_PI (3.14159265358979323846264338327f) -#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. - -#define NSVG_ALIGN_MIN 0 -#define NSVG_ALIGN_MID 1 -#define NSVG_ALIGN_MAX 2 -#define NSVG_ALIGN_NONE 0 -#define NSVG_ALIGN_MEET 1 -#define NSVG_ALIGN_SLICE 2 - -#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) -#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) - -#ifdef _MSC_VER - #pragma warning (disable: 4996) // Switch off security warnings - #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings - #ifdef __cplusplus - #define NSVG_INLINE inline - #else - #define NSVG_INLINE - #endif -#else - #define NSVG_INLINE inline -#endif - - -static int nsvg__isspace(char c) -{ - return strchr(" \t\n\v\f\r", c) != 0; -} - -static int nsvg__isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int nsvg__isnum(char c) -{ - return strchr("0123456789+-.eE", c) != 0; -} - -static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } -static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } - - -// Simple XML parser - -#define NSVG_XML_TAG 1 -#define NSVG_XML_CONTENT 2 -#define NSVG_XML_MAX_ATTRIBS 256 - -static void nsvg__parseContent(char* s, - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - // Trim start white spaces - while (*s && nsvg__isspace(*s)) s++; - if (!*s) return; - - if (contentCb) - (*contentCb)(ud, s); -} - -static void nsvg__parseElement(char* s, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void* ud) -{ - const char* attr[NSVG_XML_MAX_ATTRIBS]; - int nattr = 0; - char* name; - int start = 0; - int end = 0; - char quote; - - // Skip white space after the '<' - while (*s && nsvg__isspace(*s)) s++; - - // Check if the tag is end tag - if (*s == '/') { - s++; - end = 1; - } else { - start = 1; - } - - // Skip comments, data and preprocessor stuff. - if (!*s || *s == '?' || *s == '!') - return; - - // Get tag name - name = s; - while (*s && !nsvg__isspace(*s)) s++; - if (*s) { *s++ = '\0'; } - - // Get attribs - while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { - char* name = NULL; - char* value = NULL; - - // Skip white space before the attrib name - while (*s && nsvg__isspace(*s)) s++; - if (!*s) break; - if (*s == '/') { - end = 1; - break; - } - name = s; - // Find end of the attrib name. - while (*s && !nsvg__isspace(*s) && *s != '=') s++; - if (*s) { *s++ = '\0'; } - // Skip until the beginning of the value. - while (*s && *s != '\"' && *s != '\'') s++; - if (!*s) break; - quote = *s; - s++; - // Store value and find the end of it. - value = s; - while (*s && *s != quote) s++; - if (*s) { *s++ = '\0'; } - - // Store only well formed attributes - if (name && value) { - attr[nattr++] = name; - attr[nattr++] = value; - } - } - - // List terminator - attr[nattr++] = 0; - attr[nattr++] = 0; - - // Call callbacks. - if (start && startelCb) - (*startelCb)(ud, name, attr); - if (end && endelCb) - (*endelCb)(ud, name); -} - -int nsvg__parseXML(char* input, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - char* s = input; - char* mark = s; - int state = NSVG_XML_CONTENT; - while (*s) { - if (*s == '<' && state == NSVG_XML_CONTENT) { - // Start of a tag - *s++ = '\0'; - nsvg__parseContent(mark, contentCb, ud); - mark = s; - state = NSVG_XML_TAG; - } else if (*s == '>' && state == NSVG_XML_TAG) { - // Start of a content or new tag. - *s++ = '\0'; - nsvg__parseElement(mark, startelCb, endelCb, ud); - mark = s; - state = NSVG_XML_CONTENT; - } else { - s++; - } - } - - return 1; -} - - -/* Simple SVG parser. */ - -#define NSVG_MAX_ATTR 128 - -enum NSVGgradientUnits { - NSVG_USER_SPACE = 0, - NSVG_OBJECT_SPACE = 1 -}; - -#define NSVG_MAX_DASHES 8 - -enum NSVGunits { - NSVG_UNITS_USER, - NSVG_UNITS_PX, - NSVG_UNITS_PT, - NSVG_UNITS_PC, - NSVG_UNITS_MM, - NSVG_UNITS_CM, - NSVG_UNITS_IN, - NSVG_UNITS_PERCENT, - NSVG_UNITS_EM, - NSVG_UNITS_EX -}; - -typedef struct NSVGcoordinate { - float value; - int units; -} NSVGcoordinate; - -typedef struct NSVGlinearData { - NSVGcoordinate x1, y1, x2, y2; -} NSVGlinearData; - -typedef struct NSVGradialData { - NSVGcoordinate cx, cy, r, fx, fy; -} NSVGradialData; - -typedef struct NSVGgradientData -{ - char id[64]; - char ref[64]; - char type; - union { - NSVGlinearData linear; - NSVGradialData radial; - }; - char spread; - char units; - float xform[6]; - int nstops; - NSVGgradientStop* stops; - struct NSVGgradientData* next; -} NSVGgradientData; - -typedef struct NSVGattrib -{ - char id[64]; - float xform[6]; - unsigned int fillColor; - unsigned int strokeColor; - float opacity; - float fillOpacity; - float strokeOpacity; - char fillGradient[64]; - char strokeGradient[64]; - float strokeWidth; - float strokeDashOffset; - float strokeDashArray[NSVG_MAX_DASHES]; - int strokeDashCount; - char strokeLineJoin; - char strokeLineCap; - float miterLimit; - char fillRule; - float fontSize; - unsigned int stopColor; - float stopOpacity; - float stopOffset; - char hasFill; - char hasStroke; - char visible; -} NSVGattrib; - -typedef struct NSVGparser -{ - NSVGattrib attr[NSVG_MAX_ATTR]; - int attrHead; - float* pts; - int npts; - int cpts; - NSVGpath* plist; - NSVGimage* image; - NSVGgradientData* gradients; - NSVGshape* shapesTail; - float viewMinx, viewMiny, viewWidth, viewHeight; - int alignX, alignY, alignType; - float dpi; - char pathFlag; - char defsFlag; -} NSVGparser; - -static void nsvg__xformIdentity(float* t) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetTranslation(float* t, float tx, float ty) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = tx; t[5] = ty; -} - -static void nsvg__xformSetScale(float* t, float sx, float sy) -{ - t[0] = sx; t[1] = 0.0f; - t[2] = 0.0f; t[3] = sy; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewX(float* t, float a) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = tanf(a); t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewY(float* t, float a) -{ - t[0] = 1.0f; t[1] = tanf(a); - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetRotation(float* t, float a) -{ - float cs = cosf(a), sn = sinf(a); - t[0] = cs; t[1] = sn; - t[2] = -sn; t[3] = cs; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformMultiply(float* t, float* s) -{ - float t0 = t[0] * s[0] + t[1] * s[2]; - float t2 = t[2] * s[0] + t[3] * s[2]; - float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; - t[1] = t[0] * s[1] + t[1] * s[3]; - t[3] = t[2] * s[1] + t[3] * s[3]; - t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; - t[0] = t0; - t[2] = t2; - t[4] = t4; -} - -static void nsvg__xformInverse(float* inv, float* t) -{ - double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; - if (det > -1e-6 && det < 1e-6) { - nsvg__xformIdentity(t); - return; - } - invdet = 1.0 / det; - inv[0] = (float)(t[3] * invdet); - inv[2] = (float)(-t[2] * invdet); - inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); - inv[1] = (float)(-t[1] * invdet); - inv[3] = (float)(t[0] * invdet); - inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); -} - -static void nsvg__xformPremultiply(float* t, float* s) -{ - float s2[6]; - memcpy(s2, s, sizeof(float)*6); - nsvg__xformMultiply(s2, t); - memcpy(t, s2, sizeof(float)*6); -} - -static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2] + t[4]; - *dy = x*t[1] + y*t[3] + t[5]; -} - -static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2]; - *dy = x*t[1] + y*t[3]; -} - -#define NSVG_EPSILON (1e-12) - -static int nsvg__ptInBounds(float* pt, float* bounds) -{ - return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; -} - - -static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) -{ - double it = 1.0-t; - return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; -} - -static void nsvg__curveBounds(float* bounds, float* curve) -{ - int i, j, count; - double roots[2], a, b, c, b2ac, t, v; - float* v0 = &curve[0]; - float* v1 = &curve[2]; - float* v2 = &curve[4]; - float* v3 = &curve[6]; - - // Start the bounding box by end points - bounds[0] = nsvg__minf(v0[0], v3[0]); - bounds[1] = nsvg__minf(v0[1], v3[1]); - bounds[2] = nsvg__maxf(v0[0], v3[0]); - bounds[3] = nsvg__maxf(v0[1], v3[1]); - - // Bezier curve fits inside the convex hull of it's control points. - // If control points are inside the bounds, we're done. - if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) - return; - - // Add bezier curve inflection points in X and Y. - for (i = 0; i < 2; i++) { - a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; - b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; - c = 3.0 * v1[i] - 3.0 * v0[i]; - count = 0; - if (fabs(a) < NSVG_EPSILON) { - if (fabs(b) > NSVG_EPSILON) { - t = -c / b; - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } else { - b2ac = b*b - 4.0*c*a; - if (b2ac > NSVG_EPSILON) { - t = (-b + sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - t = (-b - sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } - for (j = 0; j < count; j++) { - v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); - bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); - bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); - } - } -} - -static NSVGparser* nsvg__createParser() -{ - NSVGparser* p; - p = (NSVGparser*)malloc(sizeof(NSVGparser)); - if (p == NULL) goto error; - memset(p, 0, sizeof(NSVGparser)); - - p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); - if (p->image == NULL) goto error; - memset(p->image, 0, sizeof(NSVGimage)); - - // Init style - nsvg__xformIdentity(p->attr[0].xform); - memset(p->attr[0].id, 0, sizeof p->attr[0].id); - p->attr[0].fillColor = NSVG_RGB(0,0,0); - p->attr[0].strokeColor = NSVG_RGB(0,0,0); - p->attr[0].opacity = 1; - p->attr[0].fillOpacity = 1; - p->attr[0].strokeOpacity = 1; - p->attr[0].stopOpacity = 1; - p->attr[0].strokeWidth = 1; - p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; - p->attr[0].strokeLineCap = NSVG_CAP_BUTT; - p->attr[0].miterLimit = 4; - p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; - p->attr[0].hasFill = 1; - p->attr[0].visible = 1; - - return p; - -error: - if (p) { - if (p->image) free(p->image); - free(p); - } - return NULL; -} - -static void nsvg__deletePaths(NSVGpath* path) -{ - while (path) { - NSVGpath *next = path->next; - if (path->pts != NULL) - free(path->pts); - free(path); - path = next; - } -} - -static void nsvg__deletePaint(NSVGpaint* paint) -{ - if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) - free(paint->gradient); -} - -static void nsvg__deleteGradientData(NSVGgradientData* grad) -{ - NSVGgradientData* next; - while (grad != NULL) { - next = grad->next; - free(grad->stops); - free(grad); - grad = next; - } -} - -static void nsvg__deleteParser(NSVGparser* p) -{ - if (p != NULL) { - nsvg__deletePaths(p->plist); - nsvg__deleteGradientData(p->gradients); - nsvgDelete(p->image); - free(p->pts); - free(p); - } -} - -static void nsvg__resetPath(NSVGparser* p) -{ - p->npts = 0; -} - -static void nsvg__addPoint(NSVGparser* p, float x, float y) -{ - if (p->npts+1 > p->cpts) { - p->cpts = p->cpts ? p->cpts*2 : 8; - p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); - if (!p->pts) return; - } - p->pts[p->npts*2+0] = x; - p->pts[p->npts*2+1] = y; - p->npts++; -} - -static void nsvg__moveTo(NSVGparser* p, float x, float y) -{ - if (p->npts > 0) { - p->pts[(p->npts-1)*2+0] = x; - p->pts[(p->npts-1)*2+1] = y; - } else { - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__lineTo(NSVGparser* p, float x, float y) -{ - float px,py, dx,dy; - if (p->npts > 0) { - px = p->pts[(p->npts-1)*2+0]; - py = p->pts[(p->npts-1)*2+1]; - dx = x - px; - dy = y - py; - nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); - nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) -{ - nsvg__addPoint(p, cpx1, cpy1); - nsvg__addPoint(p, cpx2, cpy2); - nsvg__addPoint(p, x, y); -} - -static NSVGattrib* nsvg__getAttr(NSVGparser* p) -{ - return &p->attr[p->attrHead]; -} - -static void nsvg__pushAttr(NSVGparser* p) -{ - if (p->attrHead < NSVG_MAX_ATTR-1) { - p->attrHead++; - memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); - } -} - -static void nsvg__popAttr(NSVGparser* p) -{ - if (p->attrHead > 0) - p->attrHead--; -} - -static float nsvg__actualOrigX(NSVGparser* p) -{ - return p->viewMinx; -} - -static float nsvg__actualOrigY(NSVGparser* p) -{ - return p->viewMiny; -} - -static float nsvg__actualWidth(NSVGparser* p) -{ - return p->viewWidth; -} - -static float nsvg__actualHeight(NSVGparser* p) -{ - return p->viewHeight; -} - -static float nsvg__actualLength(NSVGparser* p) -{ - float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); - return sqrtf(w*w + h*h) / sqrtf(2.0f); -} - -static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) -{ - NSVGattrib* attr = nsvg__getAttr(p); - switch (c.units) { - case NSVG_UNITS_USER: return c.value; - case NSVG_UNITS_PX: return c.value; - case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; - case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; - case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; - case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; - case NSVG_UNITS_IN: return c.value * p->dpi; - case NSVG_UNITS_EM: return c.value * attr->fontSize; - case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. - case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; - default: return c.value; - } - return c.value; -} - -static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) -{ - NSVGgradientData* grad = p->gradients; - while (grad) { - if (strcmp(grad->id, id) == 0) - return grad; - grad = grad->next; - } - return NULL; -} - -static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGgradientData* data = NULL; - NSVGgradientData* ref = NULL; - NSVGgradientStop* stops = NULL; - NSVGgradient* grad; - float ox, oy, sw, sh, sl; - int nstops = 0; - - data = nsvg__findGradientData(p, id); - if (data == NULL) return NULL; - - // TODO: use ref to fill in all unset values too. - ref = data; - while (ref != NULL) { - if (stops == NULL && ref->stops != NULL) { - stops = ref->stops; - nstops = ref->nstops; - break; - } - ref = nsvg__findGradientData(p, ref->ref); - } - if (stops == NULL) return NULL; - - grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); - if (grad == NULL) return NULL; - - // The shape width and height. - if (data->units == NSVG_OBJECT_SPACE) { - ox = localBounds[0]; - oy = localBounds[1]; - sw = localBounds[2] - localBounds[0]; - sh = localBounds[3] - localBounds[1]; - } else { - ox = nsvg__actualOrigX(p); - oy = nsvg__actualOrigY(p); - sw = nsvg__actualWidth(p); - sh = nsvg__actualHeight(p); - } - sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); - - if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { - float x1, y1, x2, y2, dx, dy; - x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); - y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); - x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); - y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); - // Calculate transform aligned to the line - dx = x2 - x1; - dy = y2 - y1; - grad->xform[0] = dy; grad->xform[1] = -dx; - grad->xform[2] = dx; grad->xform[3] = dy; - grad->xform[4] = x1; grad->xform[5] = y1; - } else { - float cx, cy, fx, fy, r; - cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); - cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); - fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); - fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); - r = nsvg__convertToPixels(p, data->radial.r, 0, sl); - // Calculate transform aligned to the circle - grad->xform[0] = r; grad->xform[1] = 0; - grad->xform[2] = 0; grad->xform[3] = r; - grad->xform[4] = cx; grad->xform[5] = cy; - grad->fx = fx / r; - grad->fy = fy / r; - } - - nsvg__xformMultiply(grad->xform, data->xform); - nsvg__xformMultiply(grad->xform, attr->xform); - - grad->spread = data->spread; - memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); - grad->nstops = nstops; - - *paintType = data->type; - - return grad; -} - -static float nsvg__getAverageScale(float* t) -{ - float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); - float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); - return (sx + sy) * 0.5f; -} - -static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) -{ - NSVGpath* path; - float curve[4*2], curveBounds[4]; - int i, first = 1; - for (path = shape->paths; path != NULL; path = path->next) { - nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); - for (i = 0; i < path->npts-1; i += 3) { - nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); - nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); - nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); - nsvg__curveBounds(curveBounds, curve); - if (first) { - bounds[0] = curveBounds[0]; - bounds[1] = curveBounds[1]; - bounds[2] = curveBounds[2]; - bounds[3] = curveBounds[3]; - first = 0; - } else { - bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); - bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); - bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); - bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); - } - curve[0] = curve[6]; - curve[1] = curve[7]; - } - } -} - -static void nsvg__addShape(NSVGparser* p) -{ - NSVGattrib* attr = nsvg__getAttr(p); - float scale = 1.0f; - NSVGshape* shape; - NSVGpath* path; - int i; - - if (p->plist == NULL) - return; - - shape = (NSVGshape*)malloc(sizeof(NSVGshape)); - if (shape == NULL) goto error; - memset(shape, 0, sizeof(NSVGshape)); - - memcpy(shape->id, attr->id, sizeof shape->id); - scale = nsvg__getAverageScale(attr->xform); - shape->strokeWidth = attr->strokeWidth * scale; - shape->strokeDashOffset = attr->strokeDashOffset * scale; - shape->strokeDashCount = (char)attr->strokeDashCount; - for (i = 0; i < attr->strokeDashCount; i++) - shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; - shape->strokeLineJoin = attr->strokeLineJoin; - shape->strokeLineCap = attr->strokeLineCap; - shape->miterLimit = attr->miterLimit; - shape->fillRule = attr->fillRule; - shape->opacity = attr->opacity; - - shape->paths = p->plist; - p->plist = NULL; - - // Calculate shape bounds - shape->bounds[0] = shape->paths->bounds[0]; - shape->bounds[1] = shape->paths->bounds[1]; - shape->bounds[2] = shape->paths->bounds[2]; - shape->bounds[3] = shape->paths->bounds[3]; - for (path = shape->paths->next; path != NULL; path = path->next) { - shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); - shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); - shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); - shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); - } - - // Set fill - if (attr->hasFill == 0) { - shape->fill.type = NSVG_PAINT_NONE; - } else if (attr->hasFill == 1) { - shape->fill.type = NSVG_PAINT_COLOR; - shape->fill.color = attr->fillColor; - shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; - } else if (attr->hasFill == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); - if (shape->fill.gradient == NULL) { - shape->fill.type = NSVG_PAINT_NONE; - } - } - - // Set stroke - if (attr->hasStroke == 0) { - shape->stroke.type = NSVG_PAINT_NONE; - } else if (attr->hasStroke == 1) { - shape->stroke.type = NSVG_PAINT_COLOR; - shape->stroke.color = attr->strokeColor; - shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; - } else if (attr->hasStroke == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); - if (shape->stroke.gradient == NULL) - shape->stroke.type = NSVG_PAINT_NONE; - } - - // Set flags - shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); - - // Add to tail - if (p->image->shapes == NULL) - p->image->shapes = shape; - else - p->shapesTail->next = shape; - p->shapesTail = shape; - - return; - -error: - if (shape) free(shape); -} - -static void nsvg__addPath(NSVGparser* p, char closed) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGpath* path = NULL; - float bounds[4]; - float* curve; - int i; - - if (p->npts < 4) - return; - - if (closed) - nsvg__lineTo(p, p->pts[0], p->pts[1]); - - path = (NSVGpath*)malloc(sizeof(NSVGpath)); - if (path == NULL) goto error; - memset(path, 0, sizeof(NSVGpath)); - - path->pts = (float*)malloc(p->npts*2*sizeof(float)); - if (path->pts == NULL) goto error; - path->closed = closed; - path->npts = p->npts; - - // Transform path. - for (i = 0; i < p->npts; ++i) - nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); - - // Find bounds - for (i = 0; i < path->npts-1; i += 3) { - curve = &path->pts[i*2]; - nsvg__curveBounds(bounds, curve); - if (i == 0) { - path->bounds[0] = bounds[0]; - path->bounds[1] = bounds[1]; - path->bounds[2] = bounds[2]; - path->bounds[3] = bounds[3]; - } else { - path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); - path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); - path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); - path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); - } - } - - path->next = p->plist; - p->plist = path; - - return; - -error: - if (path != NULL) { - if (path->pts != NULL) free(path->pts); - free(path); - } -} - -// We roll our own string to float because the std library one uses locale and messes things up. -static double nsvg__atof(const char* s) -{ - char* cur = (char*)s; - char* end = NULL; - double res = 0.0, sign = 1.0; - long long intPart = 0, fracPart = 0; - char hasIntPart = 0, hasFracPart = 0; - - // Parse optional sign - if (*cur == '+') { - cur++; - } else if (*cur == '-') { - sign = -1; - cur++; - } - - // Parse integer part - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - intPart = (double)strtoll(cur, &end, 10); - if (cur != end) { - res = (double)intPart; - hasIntPart = 1; - cur = end; - } - } - - // Parse fractional part. - if (*cur == '.') { - cur++; // Skip '.' - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - fracPart = strtoll(cur, &end, 10); - if (cur != end) { - res += (double)fracPart / pow(10.0, (double)(end - cur)); - hasFracPart = 1; - cur = end; - } - } - } - - // A valid number should have integer or fractional part. - if (!hasIntPart && !hasFracPart) - return 0.0; - - // Parse optional exponent - if (*cur == 'e' || *cur == 'E') { - int expPart = 0; - cur++; // skip 'E' - expPart = strtol(cur, &end, 10); // Parse digit sequence with sign - if (cur != end) { - res *= pow(10.0, (double)expPart); - } - } - - return res * sign; -} - - -static const char* nsvg__parseNumber(const char* s, char* it, const int size) -{ - const int last = size-1; - int i = 0; - - // sign - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - // integer part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - if (*s == '.') { - // decimal point - if (i < last) it[i++] = *s; - s++; - // fraction part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - // exponent - if (*s == 'e' || *s == 'E') { - if (i < last) it[i++] = *s; - s++; - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - it[i] = '\0'; - - return s; -} - -static const char* nsvg__getNextPathItem(const char* s, char* it) -{ - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - if (!*s) return s; - if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { - s = nsvg__parseNumber(s, it, 64); - } else { - // Parse command - it[0] = *s++; - it[1] = '\0'; - return s; - } - - return s; -} - -static unsigned int nsvg__parseColorHex(const char* str) -{ - unsigned int c = 0, r = 0, g = 0, b = 0; - int n = 0; - str++; // skip # - // Calculate number of characters. - while(str[n] && !nsvg__isspace(str[n])) - n++; - if (n == 6) { - sscanf(str, "%x", &c); - } else if (n == 3) { - sscanf(str, "%x", &c); - c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); - c |= c<<4; - } - r = (c >> 16) & 0xff; - g = (c >> 8) & 0xff; - b = c & 0xff; - return NSVG_RGB(r,g,b); -} - -static unsigned int nsvg__parseColorRGB(const char* str) -{ - int r = -1, g = -1, b = -1; - char s1[32]="", s2[32]=""; - sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); - if (strchr(s1, '%')) { - return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); - } else { - return NSVG_RGB(r,g,b); - } -} - -typedef struct NSVGNamedColor { - const char* name; - unsigned int color; -} NSVGNamedColor; - -NSVGNamedColor nsvg__colors[] = { - - { "red", NSVG_RGB(255, 0, 0) }, - { "green", NSVG_RGB( 0, 128, 0) }, - { "blue", NSVG_RGB( 0, 0, 255) }, - { "yellow", NSVG_RGB(255, 255, 0) }, - { "cyan", NSVG_RGB( 0, 255, 255) }, - { "magenta", NSVG_RGB(255, 0, 255) }, - { "black", NSVG_RGB( 0, 0, 0) }, - { "grey", NSVG_RGB(128, 128, 128) }, - { "gray", NSVG_RGB(128, 128, 128) }, - { "white", NSVG_RGB(255, 255, 255) }, - -#ifdef NANOSVG_ALL_COLOR_KEYWORDS - { "aliceblue", NSVG_RGB(240, 248, 255) }, - { "antiquewhite", NSVG_RGB(250, 235, 215) }, - { "aqua", NSVG_RGB( 0, 255, 255) }, - { "aquamarine", NSVG_RGB(127, 255, 212) }, - { "azure", NSVG_RGB(240, 255, 255) }, - { "beige", NSVG_RGB(245, 245, 220) }, - { "bisque", NSVG_RGB(255, 228, 196) }, - { "blanchedalmond", NSVG_RGB(255, 235, 205) }, - { "blueviolet", NSVG_RGB(138, 43, 226) }, - { "brown", NSVG_RGB(165, 42, 42) }, - { "burlywood", NSVG_RGB(222, 184, 135) }, - { "cadetblue", NSVG_RGB( 95, 158, 160) }, - { "chartreuse", NSVG_RGB(127, 255, 0) }, - { "chocolate", NSVG_RGB(210, 105, 30) }, - { "coral", NSVG_RGB(255, 127, 80) }, - { "cornflowerblue", NSVG_RGB(100, 149, 237) }, - { "cornsilk", NSVG_RGB(255, 248, 220) }, - { "crimson", NSVG_RGB(220, 20, 60) }, - { "darkblue", NSVG_RGB( 0, 0, 139) }, - { "darkcyan", NSVG_RGB( 0, 139, 139) }, - { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, - { "darkgray", NSVG_RGB(169, 169, 169) }, - { "darkgreen", NSVG_RGB( 0, 100, 0) }, - { "darkgrey", NSVG_RGB(169, 169, 169) }, - { "darkkhaki", NSVG_RGB(189, 183, 107) }, - { "darkmagenta", NSVG_RGB(139, 0, 139) }, - { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, - { "darkorange", NSVG_RGB(255, 140, 0) }, - { "darkorchid", NSVG_RGB(153, 50, 204) }, - { "darkred", NSVG_RGB(139, 0, 0) }, - { "darksalmon", NSVG_RGB(233, 150, 122) }, - { "darkseagreen", NSVG_RGB(143, 188, 143) }, - { "darkslateblue", NSVG_RGB( 72, 61, 139) }, - { "darkslategray", NSVG_RGB( 47, 79, 79) }, - { "darkslategrey", NSVG_RGB( 47, 79, 79) }, - { "darkturquoise", NSVG_RGB( 0, 206, 209) }, - { "darkviolet", NSVG_RGB(148, 0, 211) }, - { "deeppink", NSVG_RGB(255, 20, 147) }, - { "deepskyblue", NSVG_RGB( 0, 191, 255) }, - { "dimgray", NSVG_RGB(105, 105, 105) }, - { "dimgrey", NSVG_RGB(105, 105, 105) }, - { "dodgerblue", NSVG_RGB( 30, 144, 255) }, - { "firebrick", NSVG_RGB(178, 34, 34) }, - { "floralwhite", NSVG_RGB(255, 250, 240) }, - { "forestgreen", NSVG_RGB( 34, 139, 34) }, - { "fuchsia", NSVG_RGB(255, 0, 255) }, - { "gainsboro", NSVG_RGB(220, 220, 220) }, - { "ghostwhite", NSVG_RGB(248, 248, 255) }, - { "gold", NSVG_RGB(255, 215, 0) }, - { "goldenrod", NSVG_RGB(218, 165, 32) }, - { "greenyellow", NSVG_RGB(173, 255, 47) }, - { "honeydew", NSVG_RGB(240, 255, 240) }, - { "hotpink", NSVG_RGB(255, 105, 180) }, - { "indianred", NSVG_RGB(205, 92, 92) }, - { "indigo", NSVG_RGB( 75, 0, 130) }, - { "ivory", NSVG_RGB(255, 255, 240) }, - { "khaki", NSVG_RGB(240, 230, 140) }, - { "lavender", NSVG_RGB(230, 230, 250) }, - { "lavenderblush", NSVG_RGB(255, 240, 245) }, - { "lawngreen", NSVG_RGB(124, 252, 0) }, - { "lemonchiffon", NSVG_RGB(255, 250, 205) }, - { "lightblue", NSVG_RGB(173, 216, 230) }, - { "lightcoral", NSVG_RGB(240, 128, 128) }, - { "lightcyan", NSVG_RGB(224, 255, 255) }, - { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, - { "lightgray", NSVG_RGB(211, 211, 211) }, - { "lightgreen", NSVG_RGB(144, 238, 144) }, - { "lightgrey", NSVG_RGB(211, 211, 211) }, - { "lightpink", NSVG_RGB(255, 182, 193) }, - { "lightsalmon", NSVG_RGB(255, 160, 122) }, - { "lightseagreen", NSVG_RGB( 32, 178, 170) }, - { "lightskyblue", NSVG_RGB(135, 206, 250) }, - { "lightslategray", NSVG_RGB(119, 136, 153) }, - { "lightslategrey", NSVG_RGB(119, 136, 153) }, - { "lightsteelblue", NSVG_RGB(176, 196, 222) }, - { "lightyellow", NSVG_RGB(255, 255, 224) }, - { "lime", NSVG_RGB( 0, 255, 0) }, - { "limegreen", NSVG_RGB( 50, 205, 50) }, - { "linen", NSVG_RGB(250, 240, 230) }, - { "maroon", NSVG_RGB(128, 0, 0) }, - { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, - { "mediumblue", NSVG_RGB( 0, 0, 205) }, - { "mediumorchid", NSVG_RGB(186, 85, 211) }, - { "mediumpurple", NSVG_RGB(147, 112, 219) }, - { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, - { "mediumslateblue", NSVG_RGB(123, 104, 238) }, - { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, - { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, - { "mediumvioletred", NSVG_RGB(199, 21, 133) }, - { "midnightblue", NSVG_RGB( 25, 25, 112) }, - { "mintcream", NSVG_RGB(245, 255, 250) }, - { "mistyrose", NSVG_RGB(255, 228, 225) }, - { "moccasin", NSVG_RGB(255, 228, 181) }, - { "navajowhite", NSVG_RGB(255, 222, 173) }, - { "navy", NSVG_RGB( 0, 0, 128) }, - { "oldlace", NSVG_RGB(253, 245, 230) }, - { "olive", NSVG_RGB(128, 128, 0) }, - { "olivedrab", NSVG_RGB(107, 142, 35) }, - { "orange", NSVG_RGB(255, 165, 0) }, - { "orangered", NSVG_RGB(255, 69, 0) }, - { "orchid", NSVG_RGB(218, 112, 214) }, - { "palegoldenrod", NSVG_RGB(238, 232, 170) }, - { "palegreen", NSVG_RGB(152, 251, 152) }, - { "paleturquoise", NSVG_RGB(175, 238, 238) }, - { "palevioletred", NSVG_RGB(219, 112, 147) }, - { "papayawhip", NSVG_RGB(255, 239, 213) }, - { "peachpuff", NSVG_RGB(255, 218, 185) }, - { "peru", NSVG_RGB(205, 133, 63) }, - { "pink", NSVG_RGB(255, 192, 203) }, - { "plum", NSVG_RGB(221, 160, 221) }, - { "powderblue", NSVG_RGB(176, 224, 230) }, - { "purple", NSVG_RGB(128, 0, 128) }, - { "rosybrown", NSVG_RGB(188, 143, 143) }, - { "royalblue", NSVG_RGB( 65, 105, 225) }, - { "saddlebrown", NSVG_RGB(139, 69, 19) }, - { "salmon", NSVG_RGB(250, 128, 114) }, - { "sandybrown", NSVG_RGB(244, 164, 96) }, - { "seagreen", NSVG_RGB( 46, 139, 87) }, - { "seashell", NSVG_RGB(255, 245, 238) }, - { "sienna", NSVG_RGB(160, 82, 45) }, - { "silver", NSVG_RGB(192, 192, 192) }, - { "skyblue", NSVG_RGB(135, 206, 235) }, - { "slateblue", NSVG_RGB(106, 90, 205) }, - { "slategray", NSVG_RGB(112, 128, 144) }, - { "slategrey", NSVG_RGB(112, 128, 144) }, - { "snow", NSVG_RGB(255, 250, 250) }, - { "springgreen", NSVG_RGB( 0, 255, 127) }, - { "steelblue", NSVG_RGB( 70, 130, 180) }, - { "tan", NSVG_RGB(210, 180, 140) }, - { "teal", NSVG_RGB( 0, 128, 128) }, - { "thistle", NSVG_RGB(216, 191, 216) }, - { "tomato", NSVG_RGB(255, 99, 71) }, - { "turquoise", NSVG_RGB( 64, 224, 208) }, - { "violet", NSVG_RGB(238, 130, 238) }, - { "wheat", NSVG_RGB(245, 222, 179) }, - { "whitesmoke", NSVG_RGB(245, 245, 245) }, - { "yellowgreen", NSVG_RGB(154, 205, 50) }, -#endif -}; - -static unsigned int nsvg__parseColorName(const char* str) -{ - int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); - - for (i = 0; i < ncolors; i++) { - if (strcmp(nsvg__colors[i].name, str) == 0) { - return nsvg__colors[i].color; - } - } - - return NSVG_RGB(128, 128, 128); -} - -static unsigned int nsvg__parseColor(const char* str) -{ - size_t len = 0; - while(*str == ' ') ++str; - len = strlen(str); - if (len >= 1 && *str == '#') - return nsvg__parseColorHex(str); - else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') - return nsvg__parseColorRGB(str); - return nsvg__parseColorName(str); -} - -static float nsvg__parseOpacity(const char* str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) val = 0.0f; - if (val > 1.0f) val = 1.0f; - return val; -} - -static float nsvg__parseMiterLimit(const char* str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) val = 0.0f; - return val; -} - -static int nsvg__parseUnits(const char* units) -{ - if (units[0] == 'p' && units[1] == 'x') - return NSVG_UNITS_PX; - else if (units[0] == 'p' && units[1] == 't') - return NSVG_UNITS_PT; - else if (units[0] == 'p' && units[1] == 'c') - return NSVG_UNITS_PC; - else if (units[0] == 'm' && units[1] == 'm') - return NSVG_UNITS_MM; - else if (units[0] == 'c' && units[1] == 'm') - return NSVG_UNITS_CM; - else if (units[0] == 'i' && units[1] == 'n') - return NSVG_UNITS_IN; - else if (units[0] == '%') - return NSVG_UNITS_PERCENT; - else if (units[0] == 'e' && units[1] == 'm') - return NSVG_UNITS_EM; - else if (units[0] == 'e' && units[1] == 'x') - return NSVG_UNITS_EX; - return NSVG_UNITS_USER; -} - -static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) -{ - NSVGcoordinate coord = {0, NSVG_UNITS_USER}; - char buf[64]; - coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); - coord.value = nsvg__atof(buf); - return coord; -} - -static NSVGcoordinate nsvg__coord(float v, int units) -{ - NSVGcoordinate coord = {v, units}; - return coord; -} - -static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) -{ - NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); - return nsvg__convertToPixels(p, coord, orig, length); -} - -static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) -{ - const char* end; - const char* ptr; - char it[64]; - - *na = 0; - ptr = str; - while (*ptr && *ptr != '(') ++ptr; - if (*ptr == 0) - return 1; - end = ptr; - while (*end && *end != ')') ++end; - if (*end == 0) - return 1; - - while (ptr < end) { - if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { - if (*na >= maxNa) return 0; - ptr = nsvg__parseNumber(ptr, it, 64); - args[(*na)++] = (float)nsvg__atof(it); - } else { - ++ptr; - } - } - return (int)(end - str); -} - - -static int nsvg__parseMatrix(float* xform, const char* str) -{ - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, t, 6, &na); - if (na != 6) return len; - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseTranslate(float* xform, const char* str) -{ - float args[2]; - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = 0.0; - - nsvg__xformSetTranslation(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseScale(float* xform, const char* str) -{ - float args[2]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = args[0]; - nsvg__xformSetScale(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewX(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewY(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseRotate(float* xform, const char* str) -{ - float args[3]; - int na = 0; - float m[6]; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 3, &na); - if (na == 1) - args[1] = args[2] = 0.0f; - nsvg__xformIdentity(m); - - if (na > 1) { - nsvg__xformSetTranslation(t, -args[1], -args[2]); - nsvg__xformMultiply(m, t); - } - - nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); - nsvg__xformMultiply(m, t); - - if (na > 1) { - nsvg__xformSetTranslation(t, args[1], args[2]); - nsvg__xformMultiply(m, t); - } - - memcpy(xform, m, sizeof(float)*6); - - return len; -} - -static void nsvg__parseTransform(float* xform, const char* str) -{ - float t[6]; - nsvg__xformIdentity(xform); - while (*str) - { - if (strncmp(str, "matrix", 6) == 0) - str += nsvg__parseMatrix(t, str); - else if (strncmp(str, "translate", 9) == 0) - str += nsvg__parseTranslate(t, str); - else if (strncmp(str, "scale", 5) == 0) - str += nsvg__parseScale(t, str); - else if (strncmp(str, "rotate", 6) == 0) - str += nsvg__parseRotate(t, str); - else if (strncmp(str, "skewX", 5) == 0) - str += nsvg__parseSkewX(t, str); - else if (strncmp(str, "skewY", 5) == 0) - str += nsvg__parseSkewY(t, str); - else{ - ++str; - continue; - } - - nsvg__xformPremultiply(xform, t); - } -} - -static void nsvg__parseUrl(char* id, const char* str) -{ - int i = 0; - str += 4; // "url("; - if (*str == '#') - str++; - while (i < 63 && *str != ')') { - id[i] = *str++; - i++; - } - id[i] = '\0'; -} - -static char nsvg__parseLineCap(const char* str) -{ - if (strcmp(str, "butt") == 0) - return NSVG_CAP_BUTT; - else if (strcmp(str, "round") == 0) - return NSVG_CAP_ROUND; - else if (strcmp(str, "square") == 0) - return NSVG_CAP_SQUARE; - // TODO: handle inherit. - return NSVG_CAP_BUTT; -} - -static char nsvg__parseLineJoin(const char* str) -{ - if (strcmp(str, "miter") == 0) - return NSVG_JOIN_MITER; - else if (strcmp(str, "round") == 0) - return NSVG_JOIN_ROUND; - else if (strcmp(str, "bevel") == 0) - return NSVG_JOIN_BEVEL; - // TODO: handle inherit. - return NSVG_JOIN_MITER; -} - -static char nsvg__parseFillRule(const char* str) -{ - if (strcmp(str, "nonzero") == 0) - return NSVG_FILLRULE_NONZERO; - else if (strcmp(str, "evenodd") == 0) - return NSVG_FILLRULE_EVENODD; - // TODO: handle inherit. - return NSVG_FILLRULE_NONZERO; -} - -static const char* nsvg__getNextDashItem(const char* s, char* it) -{ - int n = 0; - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - // Advance until whitespace, comma or end. - while (*s && (!nsvg__isspace(*s) && *s != ',')) { - if (n < 63) - it[n++] = *s; - s++; - } - it[n++] = '\0'; - return s; -} - -static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) -{ - char item[64]; - int count = 0, i; - float sum = 0.0f; - - // Handle "none" - if (str[0] == 'n') - return 0; - - // Parse dashes - while (*str) { - str = nsvg__getNextDashItem(str, item); - if (!*item) break; - if (count < NSVG_MAX_DASHES) - strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); - } - - for (i = 0; i < count; i++) - sum += strokeDashArray[i]; - if (sum <= 1e-6f) - count = 0; - - return count; -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str); - -static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) -{ - float xform[6]; - NSVGattrib* attr = nsvg__getAttr(p); - if (!attr) return 0; - - if (strcmp(name, "style") == 0) { - nsvg__parseStyle(p, value); - } else if (strcmp(name, "display") == 0) { - if (strcmp(value, "none") == 0) - attr->visible = 0; - // Don't reset ->visible on display:inline, one display:none hides the whole subtree - - } else if (strcmp(name, "fill") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasFill = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasFill = 2; - nsvg__parseUrl(attr->fillGradient, value); - } else { - attr->hasFill = 1; - attr->fillColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "opacity") == 0) { - attr->opacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "fill-opacity") == 0) { - attr->fillOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasStroke = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasStroke = 2; - nsvg__parseUrl(attr->strokeGradient, value); - } else { - attr->hasStroke = 1; - attr->strokeColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "stroke-width") == 0) { - attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-dasharray") == 0) { - attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); - } else if (strcmp(name, "stroke-dashoffset") == 0) { - attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-opacity") == 0) { - attr->strokeOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke-linecap") == 0) { - attr->strokeLineCap = nsvg__parseLineCap(value); - } else if (strcmp(name, "stroke-linejoin") == 0) { - attr->strokeLineJoin = nsvg__parseLineJoin(value); - } else if (strcmp(name, "stroke-miterlimit") == 0) { - attr->miterLimit = nsvg__parseMiterLimit(value); - } else if (strcmp(name, "fill-rule") == 0) { - attr->fillRule = nsvg__parseFillRule(value); - } else if (strcmp(name, "font-size") == 0) { - attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "transform") == 0) { - nsvg__parseTransform(xform, value); - nsvg__xformPremultiply(attr->xform, xform); - } else if (strcmp(name, "stop-color") == 0) { - attr->stopColor = nsvg__parseColor(value); - } else if (strcmp(name, "stop-opacity") == 0) { - attr->stopOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "offset") == 0) { - attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); - } else if (strcmp(name, "id") == 0) { - strncpy(attr->id, value, 63); - attr->id[63] = '\0'; - } else { - return 0; - } - return 1; -} - -static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) -{ - const char* str; - const char* val; - char name[512]; - char value[512]; - int n; - - str = start; - while (str < end && *str != ':') ++str; - - val = str; - - // Right Trim - while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; - ++str; - - n = (int)(str - start); - if (n > 511) n = 511; - if (n) memcpy(name, start, n); - name[n] = 0; - - while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; - - n = (int)(end - val); - if (n > 511) n = 511; - if (n) memcpy(value, val, n); - value[n] = 0; - - return nsvg__parseAttr(p, name, value); -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str) -{ - const char* start; - const char* end; - - while (*str) { - // Left Trim - while(*str && nsvg__isspace(*str)) ++str; - start = str; - while(*str && *str != ';') ++str; - end = str; - - // Right Trim - while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; - ++end; - - nsvg__parseNameValue(p, start, end); - if (*str) ++str; - } -} - -static void nsvg__parseAttribs(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) - { - if (strcmp(attr[i], "style") == 0) - nsvg__parseStyle(p, attr[i + 1]); - else - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } -} - -static int nsvg__getArgsPerElement(char cmd) -{ - switch (cmd) { - case 'v': - case 'V': - case 'h': - case 'H': - return 1; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - return 2; - case 'q': - case 'Q': - case 's': - case 'S': - return 4; - case 'c': - case 'C': - return 6; - case 'a': - case 'A': - return 7; - } - return 0; -} - -static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__moveTo(p, *cpx, *cpy); -} - -static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpx += args[0]; - else - *cpx = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpy += args[0]; - else - *cpy = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x2, y2, cx1, cy1, cx2, cy2; - - if (rel) { - cx1 = *cpx + args[0]; - cy1 = *cpy + args[1]; - cx2 = *cpx + args[2]; - cy2 = *cpy + args[3]; - x2 = *cpx + args[4]; - y2 = *cpy + args[5]; - } else { - cx1 = args[0]; - cy1 = args[1]; - cx2 = args[2]; - cy2 = args[3]; - x2 = args[4]; - y2 = args[5]; - } - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx2 = *cpx + args[0]; - cy2 = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx2 = args[0]; - cy2 = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - cx1 = 2*x1 - *cpx2; - cy1 = 2*y1 - *cpy2; - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx = *cpx + args[0]; - cy = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx = args[0]; - cy = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - // Convert to cubic bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - x2 = *cpx + args[0]; - y2 = *cpy + args[1]; - } else { - x2 = args[0]; - y2 = args[1]; - } - - cx = 2*x1 - *cpx2; - cy = 2*y1 - *cpy2; - - // Convert to cubix bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static float nsvg__sqr(float x) { return x*x; } -static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } - -static float nsvg__vecrat(float ux, float uy, float vx, float vy) -{ - return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); -} - -static float nsvg__vecang(float ux, float uy, float vx, float vy) -{ - float r = nsvg__vecrat(ux,uy, vx,vy); - if (r < -1.0f) r = -1.0f; - if (r > 1.0f) r = 1.0f; - return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); -} - -static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - // Ported from canvg (https://code.google.com/p/canvg/) - float rx, ry, rotx; - float x1, y1, x2, y2, cx, cy, dx, dy, d; - float x1p, y1p, cxp, cyp, s, sa, sb; - float ux, uy, vx, vy, a1, da; - float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; - float sinrx, cosrx; - int fa, fs; - int i, ndivs; - float hda, kappa; - - rx = fabsf(args[0]); // y radius - ry = fabsf(args[1]); // x radius - rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle - fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc - fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction - x1 = *cpx; // start point - y1 = *cpy; - if (rel) { // end point - x2 = *cpx + args[5]; - y2 = *cpy + args[6]; - } else { - x2 = args[5]; - y2 = args[6]; - } - - dx = x1 - x2; - dy = y1 - y2; - d = sqrtf(dx*dx + dy*dy); - if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { - // The arc degenerates to a line - nsvg__lineTo(p, x2, y2); - *cpx = x2; - *cpy = y2; - return; - } - - sinrx = sinf(rotx); - cosrx = cosf(rotx); - - // Convert to center point parameterization. - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - // 1) Compute x1', y1' - x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; - y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; - d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); - if (d > 1) { - d = sqrtf(d); - rx *= d; - ry *= d; - } - // 2) Compute cx', cy' - s = 0.0f; - sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); - sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); - if (sa < 0.0f) sa = 0.0f; - if (sb > 0.0f) - s = sqrtf(sa / sb); - if (fa == fs) - s = -s; - cxp = s * rx * y1p / ry; - cyp = s * -ry * x1p / rx; - - // 3) Compute cx,cy from cx',cy' - cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; - cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; - - // 4) Calculate theta1, and delta theta. - ux = (x1p - cxp) / rx; - uy = (y1p - cyp) / ry; - vx = (-x1p - cxp) / rx; - vy = (-y1p - cyp) / ry; - a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle - da = nsvg__vecang(ux,uy, vx,vy); // Delta angle - -// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; -// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; - - if (fs == 0 && da > 0) - da -= 2 * NSVG_PI; - else if (fs == 1 && da < 0) - da += 2 * NSVG_PI; - - // Approximate the arc using cubic spline segments. - t[0] = cosrx; t[1] = sinrx; - t[2] = -sinrx; t[3] = cosrx; - t[4] = cx; t[5] = cy; - - // Split arc into max 90 degree segments. - // The loop assumes an iteration per end point (including start and end), this +1. - ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); - hda = (da / (float)ndivs) / 2.0f; - kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); - if (da < 0.0f) - kappa = -kappa; - - for (i = 0; i <= ndivs; i++) { - a = a1 + da * ((float)i/(float)ndivs); - dx = cosf(a); - dy = sinf(a); - nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position - nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent - if (i > 0) - nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); - px = x; - py = y; - ptanx = tanx; - ptany = tany; - } - - *cpx = x2; - *cpy = y2; -} - -static void nsvg__parsePath(NSVGparser* p, const char** attr) -{ - const char* s = NULL; - char cmd = '\0'; - float args[10]; - int nargs; - int rargs = 0; - float cpx, cpy, cpx2, cpy2; - const char* tmp[4]; - char closedFlag; - int i; - char item[64]; - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "d") == 0) { - s = attr[i + 1]; - } else { - tmp[0] = attr[i]; - tmp[1] = attr[i + 1]; - tmp[2] = 0; - tmp[3] = 0; - nsvg__parseAttribs(p, tmp); - } - } - - if (s) { - nsvg__resetPath(p); - cpx = 0; cpy = 0; - cpx2 = 0; cpy2 = 0; - closedFlag = 0; - nargs = 0; - - while (*s) { - s = nsvg__getNextPathItem(s, item); - if (!*item) break; - if (nsvg__isnum(item[0])) { - if (nargs < 10) - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= rargs) { - switch (cmd) { - case 'm': - case 'M': - nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); - // Moveto can be followed by multiple coordinate pairs, - // which should be treated as linetos. - cmd = (cmd == 'm') ? 'l' : 'L'; - rargs = nsvg__getArgsPerElement(cmd); - cpx2 = cpx; cpy2 = cpy; - break; - case 'l': - case 'L': - nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'H': - case 'h': - nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'V': - case 'v': - nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'C': - case 'c': - nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); - break; - case 'S': - case 's': - nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); - break; - case 'Q': - case 'q': - nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); - break; - case 'T': - case 't': - nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); - break; - case 'A': - case 'a': - nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - default: - if (nargs >= 2) { - cpx = args[nargs-2]; - cpy = args[nargs-1]; - cpx2 = cpx; cpy2 = cpy; - } - break; - } - nargs = 0; - } - } else { - cmd = item[0]; - rargs = nsvg__getArgsPerElement(cmd); - if (cmd == 'M' || cmd == 'm') { - // Commit path. - if (p->npts > 0) - nsvg__addPath(p, closedFlag); - // Start new subpath. - nsvg__resetPath(p); - closedFlag = 0; - nargs = 0; - } else if (cmd == 'Z' || cmd == 'z') { - closedFlag = 1; - // Commit path. - if (p->npts > 0) { - // Move current point to first point - cpx = p->pts[0]; - cpy = p->pts[1]; - cpx2 = cpx; cpy2 = cpy; - nsvg__addPath(p, closedFlag); - } - // Start new subpath. - nsvg__resetPath(p); - nsvg__moveTo(p, cpx, cpy); - closedFlag = 0; - nargs = 0; - } - } - } - // Commit path. - if (p->npts) - nsvg__addPath(p, closedFlag); - } - - nsvg__addShape(p); -} - -static void nsvg__parseRect(NSVGparser* p, const char** attr) -{ - float x = 0.0f; - float y = 0.0f; - float w = 0.0f; - float h = 0.0f; - float rx = -1.0f; // marks not set - float ry = -1.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); - if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx < 0.0f && ry > 0.0f) rx = ry; - if (ry < 0.0f && rx > 0.0f) ry = rx; - if (rx < 0.0f) rx = 0.0f; - if (ry < 0.0f) ry = 0.0f; - if (rx > w/2.0f) rx = w/2.0f; - if (ry > h/2.0f) ry = h/2.0f; - - if (w != 0.0f && h != 0.0f) { - nsvg__resetPath(p); - - if (rx < 0.00001f || ry < 0.0001f) { - nsvg__moveTo(p, x, y); - nsvg__lineTo(p, x+w, y); - nsvg__lineTo(p, x+w, y+h); - nsvg__lineTo(p, x, y+h); - } else { - // Rounded rectangle - nsvg__moveTo(p, x+rx, y); - nsvg__lineTo(p, x+w-rx, y); - nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); - nsvg__lineTo(p, x+w, y+h-ry); - nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); - nsvg__lineTo(p, x+rx, y+h); - nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); - nsvg__lineTo(p, x, y+ry); - nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); - } - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseCircle(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float r = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); - } - } - - if (r > 0.0f) { - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+r, cy); - nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); - nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); - nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); - nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseEllipse(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float rx = 0.0f; - float ry = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx > 0.0f && ry > 0.0f) { - - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+rx, cy); - nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); - nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); - nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); - nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseLine(NSVGparser* p, const char** attr) -{ - float x1 = 0.0; - float y1 = 0.0; - float x2 = 0.0; - float y2 = 0.0; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - } - } - - nsvg__resetPath(p); - - nsvg__moveTo(p, x1, y1); - nsvg__lineTo(p, x2, y2); - - nsvg__addPath(p, 0); - - nsvg__addShape(p); -} - -static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) -{ - int i; - const char* s; - float args[2]; - int nargs, npts = 0; - char item[64]; - - nsvg__resetPath(p); - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "points") == 0) { - s = attr[i + 1]; - nargs = 0; - while (*s) { - s = nsvg__getNextPathItem(s, item); - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= 2) { - if (npts == 0) - nsvg__moveTo(p, args[0], args[1]); - else - nsvg__lineTo(p, args[0], args[1]); - nargs = 0; - npts++; - } - } - } - } - } - - nsvg__addPath(p, (char)closeFlag); - - nsvg__addShape(p); -} - -static void nsvg__parseSVG(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "width") == 0) { - p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } else if (strcmp(attr[i], "height") == 0) { - p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } else if (strcmp(attr[i], "viewBox") == 0) { - const char *s = attr[i + 1]; - char buf[64]; - s = nsvg__parseNumber(s, buf, 64); - p->viewMinx = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewMiny = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewWidth = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewHeight = nsvg__atof(buf); - } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { - if (strstr(attr[i + 1], "none") != 0) { - // No uniform scaling - p->alignType = NSVG_ALIGN_NONE; - } else { - // Parse X align - if (strstr(attr[i + 1], "xMin") != 0) - p->alignX = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "xMid") != 0) - p->alignX = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "xMax") != 0) - p->alignX = NSVG_ALIGN_MAX; - // Parse X align - if (strstr(attr[i + 1], "yMin") != 0) - p->alignY = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "yMid") != 0) - p->alignY = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "yMax") != 0) - p->alignY = NSVG_ALIGN_MAX; - // Parse meet/slice - p->alignType = NSVG_ALIGN_MEET; - if (strstr(attr[i + 1], "slice") != 0) - p->alignType = NSVG_ALIGN_SLICE; - } - } - } - } -} - -static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) -{ - int i; - NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); - if (grad == NULL) return; - memset(grad, 0, sizeof(NSVGgradientData)); - grad->units = NSVG_OBJECT_SPACE; - grad->type = type; - if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { - grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); - grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { - grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - } - - nsvg__xformIdentity(grad->xform); - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "id") == 0) { - strncpy(grad->id, attr[i+1], 63); - grad->id[63] = '\0'; - } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "gradientUnits") == 0) { - if (strcmp(attr[i+1], "objectBoundingBox") == 0) - grad->units = NSVG_OBJECT_SPACE; - else - grad->units = NSVG_USER_SPACE; - } else if (strcmp(attr[i], "gradientTransform") == 0) { - nsvg__parseTransform(grad->xform, attr[i + 1]); - } else if (strcmp(attr[i], "cx") == 0) { - grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "cy") == 0) { - grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "r") == 0) { - grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fx") == 0) { - grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fy") == 0) { - grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x1") == 0) { - grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y1") == 0) { - grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x2") == 0) { - grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y2") == 0) { - grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "spreadMethod") == 0) { - if (strcmp(attr[i+1], "pad") == 0) - grad->spread = NSVG_SPREAD_PAD; - else if (strcmp(attr[i+1], "reflect") == 0) - grad->spread = NSVG_SPREAD_REFLECT; - else if (strcmp(attr[i+1], "repeat") == 0) - grad->spread = NSVG_SPREAD_REPEAT; - } else if (strcmp(attr[i], "xlink:href") == 0) { - const char *href = attr[i+1]; - strncpy(grad->ref, href+1, 62); - grad->ref[62] = '\0'; - } - } - } - - grad->next = p->gradients; - p->gradients = grad; -} - -static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) -{ - NSVGattrib* curAttr = nsvg__getAttr(p); - NSVGgradientData* grad; - NSVGgradientStop* stop; - int i, idx; - - curAttr->stopOffset = 0; - curAttr->stopColor = 0; - curAttr->stopOpacity = 1.0f; - - for (i = 0; attr[i]; i += 2) { - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } - - // Add stop to the last gradient. - grad = p->gradients; - if (grad == NULL) return; - - grad->nstops++; - grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); - if (grad->stops == NULL) return; - - // Insert - idx = grad->nstops-1; - for (i = 0; i < grad->nstops-1; i++) { - if (curAttr->stopOffset < grad->stops[i].offset) { - idx = i; - break; - } - } - if (idx != grad->nstops-1) { - for (i = grad->nstops-1; i > idx; i--) - grad->stops[i] = grad->stops[i-1]; - } - - stop = &grad->stops[idx]; - stop->color = curAttr->stopColor; - stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; - stop->offset = curAttr->stopOffset; -} - -static void nsvg__startElement(void* ud, const char* el, const char** attr) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (p->defsFlag) { - // Skip everything but gradients in defs - if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } - return; - } - - if (strcmp(el, "g") == 0) { - nsvg__pushAttr(p); - nsvg__parseAttribs(p, attr); - } else if (strcmp(el, "path") == 0) { - if (p->pathFlag) // Do not allow nested paths. - return; - nsvg__pushAttr(p); - nsvg__parsePath(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "rect") == 0) { - nsvg__pushAttr(p); - nsvg__parseRect(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "circle") == 0) { - nsvg__pushAttr(p); - nsvg__parseCircle(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "ellipse") == 0) { - nsvg__pushAttr(p); - nsvg__parseEllipse(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "line") == 0) { - nsvg__pushAttr(p); - nsvg__parseLine(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "polyline") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 0); - nsvg__popAttr(p); - } else if (strcmp(el, "polygon") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 1); - nsvg__popAttr(p); - } else if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 1; - } else if (strcmp(el, "svg") == 0) { - nsvg__parseSVG(p, attr); - } -} - -static void nsvg__endElement(void* ud, const char* el) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (strcmp(el, "g") == 0) { - nsvg__popAttr(p); - } else if (strcmp(el, "path") == 0) { - p->pathFlag = 0; - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 0; - } -} - -static void nsvg__content(void* ud, const char* s) -{ - NSVG_NOTUSED(ud); - NSVG_NOTUSED(s); - // empty -} - -static void nsvg__imageBounds(NSVGparser* p, float* bounds) -{ - NSVGshape* shape; - shape = p->image->shapes; - if (shape == NULL) { - bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; - return; - } - bounds[0] = shape->bounds[0]; - bounds[1] = shape->bounds[1]; - bounds[2] = shape->bounds[2]; - bounds[3] = shape->bounds[3]; - for (shape = shape->next; shape != NULL; shape = shape->next) { - bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); - bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); - bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); - bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); - } -} - -static float nsvg__viewAlign(float content, float container, int type) -{ - if (type == NSVG_ALIGN_MIN) - return 0; - else if (type == NSVG_ALIGN_MAX) - return container - content; - // mid - return (container - content) * 0.5f; -} - -static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) -{ - float t[6]; - nsvg__xformSetTranslation(t, tx, ty); - nsvg__xformMultiply (grad->xform, t); - - nsvg__xformSetScale(t, sx, sy); - nsvg__xformMultiply (grad->xform, t); -} - -static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) -{ - NSVGshape* shape; - NSVGpath* path; - float tx, ty, sx, sy, us, bounds[4], t[6], avgs; - int i; - float* pt; - - // Guess image size if not set completely. - nsvg__imageBounds(p, bounds); - - if (p->viewWidth == 0) { - if (p->image->width > 0) { - p->viewWidth = p->image->width; - } else { - p->viewMinx = bounds[0]; - p->viewWidth = bounds[2] - bounds[0]; - } - } - if (p->viewHeight == 0) { - if (p->image->height > 0) { - p->viewHeight = p->image->height; - } else { - p->viewMiny = bounds[1]; - p->viewHeight = bounds[3] - bounds[1]; - } - } - if (p->image->width == 0) - p->image->width = p->viewWidth; - if (p->image->height == 0) - p->image->height = p->viewHeight; - - tx = -p->viewMinx; - ty = -p->viewMiny; - sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; - sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; - // Unit scaling - us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); - - // Fix aspect ratio - if (p->alignType == NSVG_ALIGN_MEET) { - // fit whole image into viewbox - sx = sy = nsvg__minf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } else if (p->alignType == NSVG_ALIGN_SLICE) { - // fill whole viewbox with image - sx = sy = nsvg__maxf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } - - // Transform - sx *= us; - sy *= us; - avgs = (sx+sy) / 2.0f; - for (shape = p->image->shapes; shape != NULL; shape = shape->next) { - shape->bounds[0] = (shape->bounds[0] + tx) * sx; - shape->bounds[1] = (shape->bounds[1] + ty) * sy; - shape->bounds[2] = (shape->bounds[2] + tx) * sx; - shape->bounds[3] = (shape->bounds[3] + ty) * sy; - for (path = shape->paths; path != NULL; path = path->next) { - path->bounds[0] = (path->bounds[0] + tx) * sx; - path->bounds[1] = (path->bounds[1] + ty) * sy; - path->bounds[2] = (path->bounds[2] + tx) * sx; - path->bounds[3] = (path->bounds[3] + ty) * sy; - for (i =0; i < path->npts; i++) { - pt = &path->pts[i*2]; - pt[0] = (pt[0] + tx) * sx; - pt[1] = (pt[1] + ty) * sy; - } - } - - if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); - memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->fill.gradient->xform, t); - } - if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); - memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->stroke.gradient->xform, t); - } - - shape->strokeWidth *= avgs; - shape->strokeDashOffset *= avgs; - for (i = 0; i < shape->strokeDashCount; i++) - shape->strokeDashArray[i] *= avgs; - } -} - -NSVGimage* nsvgParse(char* input, const char* units, float dpi) -{ - NSVGparser* p; - NSVGimage* ret = 0; - - p = nsvg__createParser(); - if (p == NULL) { - return NULL; - } - p->dpi = dpi; - - nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); - - // Scale to viewBox - nsvg__scaleToViewbox(p, units); - - ret = p->image; - p->image = NULL; - - nsvg__deleteParser(p); - - return ret; -} - -#include - -NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) -{ - FILE* fp = NULL; - size_t size; - char* data = NULL; - NSVGimage* image = NULL; - - fp = boost::nowide::fopen(filename, "rb"); - if (!fp) goto error; - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - data = (char*)malloc(size+1); - if (data == NULL) goto error; - if (fread(data, 1, size, fp) != size) goto error; - data[size] = '\0'; // Must be null terminated. - fclose(fp); - - image = nsvgParse(data, units, dpi); - free(data); - return image; - -error: - if (fp) fclose(fp); - if (data) free(data); - if (image) nsvgDelete(image); - return NULL; -} - -NSVGpath* nsvgDuplicatePath(NSVGpath* p) -{ - NSVGpath* res = NULL; - - if (p == NULL) - return NULL; - - res = (NSVGpath*)malloc(sizeof(NSVGpath)); - if (res == NULL) goto error; - memset(res, 0, sizeof(NSVGpath)); - - res->pts = (float*)malloc(p->npts*2*sizeof(float)); - if (res->pts == NULL) goto error; - memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); - res->npts = p->npts; - - memcpy(res->bounds, p->bounds, sizeof(p->bounds)); - - res->closed = p->closed; - - return res; - -error: - if (res != NULL) { - free(res->pts); - free(res); - } - return NULL; -} - -void nsvgDelete(NSVGimage* image) -{ - NSVGshape *snext, *shape; - if (image == NULL) return; - shape = image->shapes; - while (shape != NULL) { - snext = shape->next; - nsvg__deletePaths(shape->paths); - nsvg__deletePaint(&shape->fill); - nsvg__deletePaint(&shape->stroke); - free(shape); - shape = snext; - } - free(image); -} - -#endif diff --git a/src/nanosvg/nanosvgrast.h b/src/nanosvg/nanosvgrast.h deleted file mode 100644 index b740c316c..000000000 --- a/src/nanosvg/nanosvgrast.h +++ /dev/null @@ -1,1452 +0,0 @@ -/* - * Copyright (c) 2013-14 Mikko Mononen memon@inside.org - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * The polygon rasterization is heavily based on stb_truetype rasterizer - * by Sean Barrett - http://nothings.org/ - * - */ - -#ifndef NANOSVGRAST_H -#define NANOSVGRAST_H - -#ifndef NANOSVGRAST_CPLUSPLUS -#ifdef __cplusplus -extern "C" { -#endif -#endif - -typedef struct NSVGrasterizer NSVGrasterizer; - -/* Example Usage: - // Load SVG - NSVGimage* image; - image = nsvgParseFromFile("test.svg", "px", 96); - - // Create rasterizer (can be used to render multiple images). - struct NSVGrasterizer* rast = nsvgCreateRasterizer(); - // Allocate memory for image - unsigned char* img = malloc(w*h*4); - // Rasterize - nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); -*/ - -// Allocated rasterizer context. -NSVGrasterizer* nsvgCreateRasterizer(); - -// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) -// r - pointer to rasterizer context -// image - pointer to image to rasterize -// tx,ty - image offset (applied after scaling) -// scale - image scale -// dst - pointer to destination image data, 4 bytes per pixel (RGBA) -// w - width of the image to render -// h - height of the image to render -// stride - number of bytes per scaleline in the destination buffer -void nsvgRasterize(NSVGrasterizer* r, - NSVGimage* image, float tx, float ty, float scale, - unsigned char* dst, int w, int h, int stride); - -// Deletes rasterizer context. -void nsvgDeleteRasterizer(NSVGrasterizer*); - - -#ifndef NANOSVGRAST_CPLUSPLUS -#ifdef __cplusplus -} -#endif -#endif - -#endif // NANOSVGRAST_H - -#ifdef NANOSVGRAST_IMPLEMENTATION - -#include - -#define NSVG__SUBSAMPLES 5 -#define NSVG__FIXSHIFT 10 -#define NSVG__FIX (1 << NSVG__FIXSHIFT) -#define NSVG__FIXMASK (NSVG__FIX-1) -#define NSVG__MEMPAGE_SIZE 1024 - -typedef struct NSVGedge { - float x0,y0, x1,y1; - int dir; - struct NSVGedge* next; -} NSVGedge; - -typedef struct NSVGpoint { - float x, y; - float dx, dy; - float len; - float dmx, dmy; - unsigned char flags; -} NSVGpoint; - -typedef struct NSVGactiveEdge { - int x,dx; - float ey; - int dir; - struct NSVGactiveEdge *next; -} NSVGactiveEdge; - -typedef struct NSVGmemPage { - unsigned char mem[NSVG__MEMPAGE_SIZE]; - int size; - struct NSVGmemPage* next; -} NSVGmemPage; - -typedef struct NSVGcachedPaint { - char type; - char spread; - float xform[6]; - unsigned int colors[256]; -} NSVGcachedPaint; - -struct NSVGrasterizer -{ - float px, py; - - float tessTol; - float distTol; - - NSVGedge* edges; - int nedges; - int cedges; - - NSVGpoint* points; - int npoints; - int cpoints; - - NSVGpoint* points2; - int npoints2; - int cpoints2; - - NSVGactiveEdge* freelist; - NSVGmemPage* pages; - NSVGmemPage* curpage; - - unsigned char* scanline; - int cscanline; - - unsigned char* bitmap; - int width, height, stride; -}; - -NSVGrasterizer* nsvgCreateRasterizer() -{ - NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); - if (r == NULL) goto error; - memset(r, 0, sizeof(NSVGrasterizer)); - - r->tessTol = 0.25f; - r->distTol = 0.01f; - - return r; - -error: - nsvgDeleteRasterizer(r); - return NULL; -} - -void nsvgDeleteRasterizer(NSVGrasterizer* r) -{ - NSVGmemPage* p; - - if (r == NULL) return; - - p = r->pages; - while (p != NULL) { - NSVGmemPage* next = p->next; - free(p); - p = next; - } - - if (r->edges) free(r->edges); - if (r->points) free(r->points); - if (r->points2) free(r->points2); - if (r->scanline) free(r->scanline); - - free(r); -} - -static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) -{ - NSVGmemPage *newp; - - // If using existing chain, return the next page in chain - if (cur != NULL && cur->next != NULL) { - return cur->next; - } - - // Alloc new page - newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); - if (newp == NULL) return NULL; - memset(newp, 0, sizeof(NSVGmemPage)); - - // Add to linked list - if (cur != NULL) - cur->next = newp; - else - r->pages = newp; - - return newp; -} - -static void nsvg__resetPool(NSVGrasterizer* r) -{ - NSVGmemPage* p = r->pages; - while (p != NULL) { - p->size = 0; - p = p->next; - } - r->curpage = r->pages; -} - -static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) -{ - unsigned char* buf; - if (size > NSVG__MEMPAGE_SIZE) return NULL; - if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { - r->curpage = nsvg__nextPage(r, r->curpage); - } - buf = &r->curpage->mem[r->curpage->size]; - r->curpage->size += size; - return buf; -} - -static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) -{ - float dx = x2 - x1; - float dy = y2 - y1; - return dx*dx + dy*dy < tol*tol; -} - -static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) -{ - NSVGpoint* pt; - - if (r->npoints > 0) { - pt = &r->points[r->npoints-1]; - if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { - pt->flags = (unsigned char)(pt->flags | flags); - return; - } - } - - if (r->npoints+1 > r->cpoints) { - r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; - r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); - if (r->points == NULL) return; - } - - pt = &r->points[r->npoints]; - pt->x = x; - pt->y = y; - pt->flags = (unsigned char)flags; - r->npoints++; -} - -static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) -{ - if (r->npoints+1 > r->cpoints) { - r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; - r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); - if (r->points == NULL) return; - } - r->points[r->npoints] = pt; - r->npoints++; -} - -static void nsvg__duplicatePoints(NSVGrasterizer* r) -{ - if (r->npoints > r->cpoints2) { - r->cpoints2 = r->npoints; - r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); - if (r->points2 == NULL) return; - } - - memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); - r->npoints2 = r->npoints; -} - -static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) -{ - NSVGedge* e; - - // Skip horizontal edges - if (y0 == y1) - return; - - if (r->nedges+1 > r->cedges) { - r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; - r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); - if (r->edges == NULL) return; - } - - e = &r->edges[r->nedges]; - r->nedges++; - - if (y0 < y1) { - e->x0 = x0; - e->y0 = y0; - e->x1 = x1; - e->y1 = y1; - e->dir = 1; - } else { - e->x0 = x1; - e->y0 = y1; - e->x1 = x0; - e->y1 = y0; - e->dir = -1; - } -} - -static float nsvg__normalize(float *x, float* y) -{ - float d = sqrtf((*x)*(*x) + (*y)*(*y)); - if (d > 1e-6f) { - float id = 1.0f / d; - *x *= id; - *y *= id; - } - return d; -} - -static float nsvg__absf(float x) { return x < 0 ? -x : x; } - -static void nsvg__flattenCubicBez(NSVGrasterizer* r, - float x1, float y1, float x2, float y2, - float x3, float y3, float x4, float y4, - int level, int type) -{ - float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; - float dx,dy,d2,d3; - - if (level > 10) return; - - x12 = (x1+x2)*0.5f; - y12 = (y1+y2)*0.5f; - x23 = (x2+x3)*0.5f; - y23 = (y2+y3)*0.5f; - x34 = (x3+x4)*0.5f; - y34 = (y3+y4)*0.5f; - x123 = (x12+x23)*0.5f; - y123 = (y12+y23)*0.5f; - - dx = x4 - x1; - dy = y4 - y1; - d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); - d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); - - if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { - nsvg__addPathPoint(r, x4, y4, type); - return; - } - - x234 = (x23+x34)*0.5f; - y234 = (y23+y34)*0.5f; - x1234 = (x123+x234)*0.5f; - y1234 = (y123+y234)*0.5f; - - nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); - nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); -} - -static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) -{ - int i, j; - NSVGpath* path; - - for (path = shape->paths; path != NULL; path = path->next) { - r->npoints = 0; - // Flatten path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); - } - // Close path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); - // Build edges - for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) - nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); - } -} - -enum NSVGpointFlags -{ - NSVG_PT_CORNER = 0x01, - NSVG_PT_BEVEL = 0x02, - NSVG_PT_LEFT = 0x04 -}; - -static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - float len = nsvg__normalize(&dx, &dy); - float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) -{ - float w = lineWidth * 0.5f; - float px = p->x, py = p->y; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - - nsvg__addEdge(r, lx, ly, rx, ry); - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) -{ - float w = lineWidth * 0.5f; - float px = p->x - dx*w, py = p->y - dy*w; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - - nsvg__addEdge(r, lx, ly, rx, ry); - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -#ifndef NSVG_PI -#define NSVG_PI (3.14159265358979323846264338327f) -#endif - -static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) -{ - int i; - float w = lineWidth * 0.5f; - float px = p->x, py = p->y; - float dlx = dy, dly = -dx; - float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; - - for (i = 0; i < ncap; i++) { - float a = (float)i/(float)(ncap-1)*NSVG_PI; - float ax = cosf(a) * w, ay = sinf(a) * w; - float x = px - dlx*ax - dx*ay; - float y = py - dly*ax - dy*ay; - - if (i > 0) - nsvg__addEdge(r, prevx, prevy, x, y); - - prevx = x; - prevy = y; - - if (i == 0) { - lx = x; ly = y; - } else if (i == ncap-1) { - rx = x; ry = y; - } - } - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); - float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); - float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); - float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); - - nsvg__addEdge(r, lx0, ly0, left->x, left->y); - nsvg__addEdge(r, lx1, ly1, lx0, ly0); - - nsvg__addEdge(r, right->x, right->y, rx0, ry0); - nsvg__addEdge(r, rx0, ry0, rx1, ry1); - - left->x = lx1; left->y = ly1; - right->x = rx1; right->y = ry1; -} - -static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float lx0, rx0, lx1, rx1; - float ly0, ry0, ly1, ry1; - - if (p1->flags & NSVG_PT_LEFT) { - lx0 = lx1 = p1->x - p1->dmx * w; - ly0 = ly1 = p1->y - p1->dmy * w; - nsvg__addEdge(r, lx1, ly1, left->x, left->y); - - rx0 = p1->x + (dlx0 * w); - ry0 = p1->y + (dly0 * w); - rx1 = p1->x + (dlx1 * w); - ry1 = p1->y + (dly1 * w); - nsvg__addEdge(r, right->x, right->y, rx0, ry0); - nsvg__addEdge(r, rx0, ry0, rx1, ry1); - } else { - lx0 = p1->x - (dlx0 * w); - ly0 = p1->y - (dly0 * w); - lx1 = p1->x - (dlx1 * w); - ly1 = p1->y - (dly1 * w); - nsvg__addEdge(r, lx0, ly0, left->x, left->y); - nsvg__addEdge(r, lx1, ly1, lx0, ly0); - - rx0 = rx1 = p1->x + p1->dmx * w; - ry0 = ry1 = p1->y + p1->dmy * w; - nsvg__addEdge(r, right->x, right->y, rx1, ry1); - } - - left->x = lx1; left->y = ly1; - right->x = rx1; right->y = ry1; -} - -static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) -{ - int i, n; - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float a0 = atan2f(dly0, dlx0); - float a1 = atan2f(dly1, dlx1); - float da = a1 - a0; - float lx, ly, rx, ry; - - if (da < NSVG_PI) da += NSVG_PI*2; - if (da > NSVG_PI) da -= NSVG_PI*2; - - n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); - if (n < 2) n = 2; - if (n > ncap) n = ncap; - - lx = left->x; - ly = left->y; - rx = right->x; - ry = right->y; - - for (i = 0; i < n; i++) { - float u = (float)i/(float)(n-1); - float a = a0 + u*da; - float ax = cosf(a) * w, ay = sinf(a) * w; - float lx1 = p1->x - ax, ly1 = p1->y - ay; - float rx1 = p1->x + ax, ry1 = p1->y + ay; - - nsvg__addEdge(r, lx1, ly1, lx, ly); - nsvg__addEdge(r, rx, ry, rx1, ry1); - - lx = lx1; ly = ly1; - rx = rx1; ry = ry1; - } - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); - float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); - - nsvg__addEdge(r, lx, ly, left->x, left->y); - nsvg__addEdge(r, right->x, right->y, rx, ry); - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static int nsvg__curveDivs(float r, float arc, float tol) -{ - float da = acosf(r / (r + tol)) * 2.0f; - int divs = (int)ceilf(arc / da); - if (divs < 2) divs = 2; - return divs; -} - -static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) -{ - int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. - NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; - NSVGpoint* p0, *p1; - int j, s, e; - - // Build stroke edges - if (closed) { - // Looping - p0 = &points[npoints-1]; - p1 = &points[0]; - s = 0; - e = npoints; - } else { - // Add cap - p0 = &points[0]; - p1 = &points[1]; - s = 1; - e = npoints-1; - } - - if (closed) { - nsvg__initClosed(&left, &right, p0, p1, lineWidth); - firstLeft = left; - firstRight = right; - } else { - // Add cap - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - nsvg__normalize(&dx, &dy); - if (lineCap == NSVG_CAP_BUTT) - nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); - else if (lineCap == NSVG_CAP_SQUARE) - nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); - else if (lineCap == NSVG_CAP_ROUND) - nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); - } - - for (j = s; j < e; ++j) { - if (p1->flags & NSVG_PT_CORNER) { - if (lineJoin == NSVG_JOIN_ROUND) - nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); - else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) - nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); - else - nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); - } else { - nsvg__straightJoin(r, &left, &right, p1, lineWidth); - } - p0 = p1++; - } - - if (closed) { - // Loop it - nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); - nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); - } else { - // Add cap - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - nsvg__normalize(&dx, &dy); - if (lineCap == NSVG_CAP_BUTT) - nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); - else if (lineCap == NSVG_CAP_SQUARE) - nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); - else if (lineCap == NSVG_CAP_ROUND) - nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); - } -} - -static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) -{ - int i, j; - NSVGpoint* p0, *p1; - - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - for (i = 0; i < r->npoints; i++) { - // Calculate segment direction and length - p0->dx = p1->x - p0->x; - p0->dy = p1->y - p0->y; - p0->len = nsvg__normalize(&p0->dx, &p0->dy); - // Advance - p0 = p1++; - } - - // calculate joins - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - for (j = 0; j < r->npoints; j++) { - float dlx0, dly0, dlx1, dly1, dmr2, cross; - dlx0 = p0->dy; - dly0 = -p0->dx; - dlx1 = p1->dy; - dly1 = -p1->dx; - // Calculate extrusions - p1->dmx = (dlx0 + dlx1) * 0.5f; - p1->dmy = (dly0 + dly1) * 0.5f; - dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; - if (dmr2 > 0.000001f) { - float s2 = 1.0f / dmr2; - if (s2 > 600.0f) { - s2 = 600.0f; - } - p1->dmx *= s2; - p1->dmy *= s2; - } - - // Clear flags, but keep the corner. - p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; - - // Keep track of left turns. - cross = p1->dx * p0->dy - p0->dx * p1->dy; - if (cross > 0.0f) - p1->flags |= NSVG_PT_LEFT; - - // Check to see if the corner needs to be beveled. - if (p1->flags & NSVG_PT_CORNER) { - if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { - p1->flags |= NSVG_PT_BEVEL; - } - } - - p0 = p1++; - } -} - -static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) -{ - int i, j, closed; - NSVGpath* path; - NSVGpoint* p0, *p1; - float miterLimit = shape->miterLimit; - int lineJoin = shape->strokeLineJoin; - int lineCap = shape->strokeLineCap; - float lineWidth = shape->strokeWidth * scale; - - for (path = shape->paths; path != NULL; path = path->next) { - // Flatten path - r->npoints = 0; - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); - } - if (r->npoints < 2) - continue; - - closed = path->closed; - - // If the first and last points are the same, remove the last, mark as closed path. - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { - r->npoints--; - p0 = &r->points[r->npoints-1]; - closed = 1; - } - - if (shape->strokeDashCount > 0) { - int idash = 0, dashState = 1; - float totalDist = 0, dashLen, allDashLen, dashOffset; - NSVGpoint cur; - - if (closed) - nsvg__appendPathPoint(r, r->points[0]); - - // Duplicate points -> points2. - nsvg__duplicatePoints(r); - - r->npoints = 0; - cur = r->points2[0]; - nsvg__appendPathPoint(r, cur); - - // Figure out dash offset. - allDashLen = 0; - for (j = 0; j < shape->strokeDashCount; j++) - allDashLen += shape->strokeDashArray[j]; - if (shape->strokeDashCount & 1) - allDashLen *= 2.0f; - // Find location inside pattern - dashOffset = fmodf(shape->strokeDashOffset, allDashLen); - if (dashOffset < 0.0f) - dashOffset += allDashLen; - - while (dashOffset > shape->strokeDashArray[idash]) { - dashOffset -= shape->strokeDashArray[idash]; - idash = (idash + 1) % shape->strokeDashCount; - } - dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; - - for (j = 1; j < r->npoints2; ) { - float dx = r->points2[j].x - cur.x; - float dy = r->points2[j].y - cur.y; - float dist = sqrtf(dx*dx + dy*dy); - - if ((totalDist + dist) > dashLen) { - // Calculate intermediate point - float d = (dashLen - totalDist) / dist; - float x = cur.x + dx * d; - float y = cur.y + dy * d; - nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); - - // Stroke - if (r->npoints > 1 && dashState) { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); - } - // Advance dash pattern - dashState = !dashState; - idash = (idash+1) % shape->strokeDashCount; - dashLen = shape->strokeDashArray[idash] * scale; - // Restart - cur.x = x; - cur.y = y; - cur.flags = NSVG_PT_CORNER; - totalDist = 0.0f; - r->npoints = 0; - nsvg__appendPathPoint(r, cur); - } else { - totalDist += dist; - cur = r->points2[j]; - nsvg__appendPathPoint(r, cur); - j++; - } - } - // Stroke any leftover path - if (r->npoints > 1 && dashState) - nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); - } else { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); - } - } -} - -static int nsvg__cmpEdge(const void *p, const void *q) -{ - const NSVGedge* a = (const NSVGedge*)p; - const NSVGedge* b = (const NSVGedge*)q; - - if (a->y0 < b->y0) return -1; - if (a->y0 > b->y0) return 1; - return 0; -} - - -static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) -{ - NSVGactiveEdge* z; - - if (r->freelist != NULL) { - // Restore from freelist. - z = r->freelist; - r->freelist = z->next; - } else { - // Alloc new edge. - z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); - if (z == NULL) return NULL; - } - - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); -// STBTT_assert(e->y0 <= start_point); - // round dx down to avoid going too far - if (dxdy < 0) - z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); - else - z->dx = (int)floorf(NSVG__FIX * dxdy); - z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); -// z->x -= off_x * FIX; - z->ey = e->y1; - z->next = 0; - z->dir = e->dir; - - return z; -} - -static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) -{ - z->next = r->freelist; - r->freelist = z; -} - -static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) -{ - int i = x0 >> NSVG__FIXSHIFT; - int j = x1 >> NSVG__FIXSHIFT; - if (i < *xmin) *xmin = i; - if (j > *xmax) *xmax = j; - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = (unsigned char)(scanline[i] + maxWeight); - } - } -} - -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) -{ - // non-zero winding fill - int x0 = 0, w = 0; - - if (fillRule == NSVG_FILLRULE_NONZERO) { - // Non-zero - while (e != NULL) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->dir; - } else { - int x1 = e->x; w += e->dir; - // if we went to zero, we need to draw - if (w == 0) - nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); - } - e = e->next; - } - } else if (fillRule == NSVG_FILLRULE_EVENODD) { - // Even-odd - while (e != NULL) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w = 1; - } else { - int x1 = e->x; w = 0; - nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); - } - e = e->next; - } - } -} - -static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } - -static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return (r) | (g << 8) | (b << 16) | (a << 24); -} - -static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) -{ - int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); - int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; - int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; - int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; - int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; - return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); -} - -static unsigned int nsvg__applyOpacity(unsigned int c, float u) -{ - int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); - int r = (c) & 0xff; - int g = (c>>8) & 0xff; - int b = (c>>16) & 0xff; - int a = (((c>>24) & 0xff)*iu) >> 8; - return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); -} - -static inline int nsvg__div255(int x) -{ - return ((x+1) * 257) >> 16; -} - -static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, - float tx, float ty, float scale, NSVGcachedPaint* cache) -{ - - if (cache->type == NSVG_PAINT_COLOR) { - int i, cr, cg, cb, ca; - cr = cache->colors[0] & 0xff; - cg = (cache->colors[0] >> 8) & 0xff; - cb = (cache->colors[0] >> 16) & 0xff; - ca = (cache->colors[0] >> 24) & 0xff; - - for (i = 0; i < count; i++) { - int r,g,b; - int a = nsvg__div255((int)cover[0] * ca); - int ia = 255 - a; - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - } - } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { - // TODO: spread modes. - // TODO: plenty of opportunities to optimize. - float fx, fy, dx, gy; - float* t = cache->xform; - int i, cr, cg, cb, ca; - unsigned int c; - - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; - - for (i = 0; i < count; i++) { - int r,g,b,a,ia; - gy = fx*t[1] + fy*t[3] + t[5]; - c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; - cr = (c) & 0xff; - cg = (c >> 8) & 0xff; - cb = (c >> 16) & 0xff; - ca = (c >> 24) & 0xff; - - a = nsvg__div255((int)cover[0] * ca); - ia = 255 - a; - - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - fx += dx; - } - } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { - // TODO: spread modes. - // TODO: plenty of opportunities to optimize. - // TODO: focus (fx,fy) - float fx, fy, dx, gx, gy, gd; - float* t = cache->xform; - int i, cr, cg, cb, ca; - unsigned int c; - - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; - - for (i = 0; i < count; i++) { - int r,g,b,a,ia; - gx = fx*t[0] + fy*t[2] + t[4]; - gy = fx*t[1] + fy*t[3] + t[5]; - gd = sqrtf(gx*gx + gy*gy); - c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; - cr = (c) & 0xff; - cg = (c >> 8) & 0xff; - cb = (c >> 16) & 0xff; - ca = (c >> 24) & 0xff; - - a = nsvg__div255((int)cover[0] * ca); - ia = 255 - a; - - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - fx += dx; - } - } -} - -static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) -{ - NSVGactiveEdge *active = NULL; - int y, s; - int e = 0; - int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline - int xmin, xmax; - - for (y = 0; y < r->height; y++) { - memset(r->scanline, 0, r->width); - xmin = r->width; - xmax = 0; - for (s = 0; s < NSVG__SUBSAMPLES; ++s) { - // find center of pixel for this scanline - float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; - NSVGactiveEdge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - NSVGactiveEdge *z = *step; - if (z->ey <= scany) { - *step = z->next; // delete from list -// NSVG__assert(z->valid); - nsvg__freeActive(r, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for (;;) { - int changed = 0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - NSVGactiveEdge* t = *step; - NSVGactiveEdge* q = t->next; - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e < r->nedges && r->edges[e].y0 <= scany) { - if (r->edges[e].y1 > scany) { - NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); - if (z == NULL) break; - // find insertion point - if (active == NULL) { - active = z; - } else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - NSVGactiveEdge* p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - e++; - } - - // now process all active edges in non-zero fashion - if (active != NULL) - nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); - } - // Blit - if (xmin < 0) xmin = 0; - if (xmax > r->width-1) xmax = r->width-1; - if (xmin <= xmax) { - nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); - } - } - -} - -static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) -{ - int x,y; - - // Unpremultiply - for (y = 0; y < h; y++) { - unsigned char *row = &image[y*stride]; - for (x = 0; x < w; x++) { - int r = row[0], g = row[1], b = row[2], a = row[3]; - if (a != 0) { - row[0] = (unsigned char)(r*255/a); - row[1] = (unsigned char)(g*255/a); - row[2] = (unsigned char)(b*255/a); - } - row += 4; - } - } - - // Defringe - for (y = 0; y < h; y++) { - unsigned char *row = &image[y*stride]; - for (x = 0; x < w; x++) { - int r = 0, g = 0, b = 0, a = row[3], n = 0; - if (a == 0) { - if (x-1 > 0 && row[-1] != 0) { - r += row[-4]; - g += row[-3]; - b += row[-2]; - n++; - } - if (x+1 < w && row[7] != 0) { - r += row[4]; - g += row[5]; - b += row[6]; - n++; - } - if (y-1 > 0 && row[-stride+3] != 0) { - r += row[-stride]; - g += row[-stride+1]; - b += row[-stride+2]; - n++; - } - if (y+1 < h && row[stride+3] != 0) { - r += row[stride]; - g += row[stride+1]; - b += row[stride+2]; - n++; - } - if (n > 0) { - row[0] = (unsigned char)(r/n); - row[1] = (unsigned char)(g/n); - row[2] = (unsigned char)(b/n); - } - } - row += 4; - } - } -} - - -static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) -{ - int i, j; - NSVGgradient* grad; - - cache->type = paint->type; - - if (paint->type == NSVG_PAINT_COLOR) { - cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); - return; - } - - grad = paint->gradient; - - cache->spread = grad->spread; - memcpy(cache->xform, grad->xform, sizeof(float)*6); - - if (grad->nstops == 0) { - for (i = 0; i < 256; i++) - cache->colors[i] = 0; - } if (grad->nstops == 1) { - for (i = 0; i < 256; i++) - cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); - } else { - unsigned int ca, cb = 0; - float ua, ub, du, u; - int ia, ib, count; - - ca = nsvg__applyOpacity(grad->stops[0].color, opacity); - ua = nsvg__clampf(grad->stops[0].offset, 0, 1); - ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - for (i = 0; i < ia; i++) { - cache->colors[i] = ca; - } - - for (i = 0; i < grad->nstops-1; i++) { - ca = nsvg__applyOpacity(grad->stops[i].color, opacity); - cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); - ua = nsvg__clampf(grad->stops[i].offset, 0, 1); - ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - count = ib - ia; - if (count <= 0) continue; - u = 0; - du = 1.0f / (float)count; - for (j = 0; j < count; j++) { - cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); - u += du; - } - } - - for (i = ib; i < 256; i++) - cache->colors[i] = cb; - } - -} - -/* -static void dumpEdges(NSVGrasterizer* r, const char* name) -{ - float xmin = 0, xmax = 0, ymin = 0, ymax = 0; - NSVGedge *e = NULL; - int i; - if (r->nedges == 0) return; - FILE* fp = fopen(name, "w"); - if (fp == NULL) return; - - xmin = xmax = r->edges[0].x0; - ymin = ymax = r->edges[0].y0; - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - xmin = nsvg__minf(xmin, e->x0); - xmin = nsvg__minf(xmin, e->x1); - xmax = nsvg__maxf(xmax, e->x0); - xmax = nsvg__maxf(xmax, e->x1); - ymin = nsvg__minf(ymin, e->y0); - ymin = nsvg__minf(ymin, e->y1); - ymax = nsvg__maxf(ymax, e->y0); - ymax = nsvg__maxf(ymax, e->y1); - } - - fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); - - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); - } - - for (i = 0; i < r->npoints; i++) { - if (i+1 < r->npoints) - fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); - fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); - } - - fprintf(fp, ""); - fclose(fp); -} -*/ - -void nsvgRasterize(NSVGrasterizer* r, - NSVGimage* image, float tx, float ty, float scale, - unsigned char* dst, int w, int h, int stride) -{ - NSVGshape *shape = NULL; - NSVGedge *e = NULL; - NSVGcachedPaint cache; - int i; - - r->bitmap = dst; - r->width = w; - r->height = h; - r->stride = stride; - - if (w > r->cscanline) { - r->cscanline = w; - r->scanline = (unsigned char*)realloc(r->scanline, w); - if (r->scanline == NULL) return; - } - - for (i = 0; i < h; i++) - memset(&dst[i*stride], 0, w*4); - - for (shape = image->shapes; shape != NULL; shape = shape->next) { - if (!(shape->flags & NSVG_FLAGS_VISIBLE)) - continue; - - if (shape->fill.type != NSVG_PAINT_NONE) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShape(r, shape, scale); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } - - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaint(&cache, &shape->fill, shape->opacity); - - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); - } - if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShapeStroke(r, shape, scale); - -// dumpEdges(r, "edge.svg"); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } - - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaint(&cache, &shape->stroke, shape->opacity); - - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); - } - } - - nsvg__unpremultiplyAlpha(dst, w, h, stride); - - r->bitmap = NULL; - r->width = 0; - r->height = 0; - r->stride = 0; -} - -#endif diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 29b8b7e73..430da0c34 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -255,6 +255,8 @@ set(SLIC3R_GUI_SOURCES Utils/WinRegistry.hpp ) +find_package(NanoSVG REQUIRED) + if (APPLE) list(APPEND SLIC3R_GUI_SOURCES Utils/RetinaHelperImpl.mm @@ -279,7 +281,7 @@ endforeach() encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) +target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES} NanoSVG::nanosvg NanoSVG::nanosvgrast) if (MSVC) target_link_libraries(libslic3r_gui Setupapi.lib) diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index b585798d3..844f6dea9 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -15,10 +15,8 @@ #include #endif /* __WXGTK2__ */ -//#define NANOSVG_IMPLEMENTATION -#include "nanosvg/nanosvg.h" -#define NANOSVGRAST_IMPLEMENTATION -#include "nanosvg/nanosvgrast.h" +#include +#include namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 6065f22a5..9c039376d 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -23,8 +23,8 @@ #define STB_DXT_IMPLEMENTATION #include "stb_dxt/stb_dxt.h" -#include "nanosvg/nanosvg.h" -#include "nanosvg/nanosvgrast.h" +#include +#include #include "libslic3r/Utils.hpp" diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 5ab4685bf..9aa8833ff 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -37,9 +37,8 @@ #endif // ENABLE_GL_IMGUI_SHADERS #include "../Utils/MacDarkMode.hpp" - -#include "nanosvg/nanosvg.h" -#include "nanosvg/nanosvgrast.h" +#include +#include namespace Slic3r { namespace GUI { From 2ab64819aabdc28661fcc4e552c05e91d9366bc0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 26 Apr 2022 15:53:28 +0200 Subject: [PATCH 39/97] Fixes to support wxWidgets 3.1.6(7) --- src/slic3r/GUI/AboutDialog.cpp | 9 ++++--- src/slic3r/GUI/BitmapComboBox.cpp | 4 ++- src/slic3r/GUI/GUI_App.cpp | 3 ++- src/slic3r/GUI/GUI_Factories.cpp | 14 ++++++++--- src/slic3r/GUI/GUI_Factories.hpp | 4 ++- src/slic3r/GUI/KBShortcutsDialog.cpp | 10 +++++--- src/slic3r/GUI/MsgDialog.cpp | 8 ++++++ src/slic3r/GUI/ObjectDataViewModel.cpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 7 +++--- src/slic3r/GUI/SysInfoDialog.cpp | 10 +++++--- src/slic3r/GUI/Tab.cpp | 7 ++++-- src/slic3r/GUI/wxExtensions.cpp | 35 ++++++++++++++++++-------- src/slic3r/GUI/wxExtensions.hpp | 7 ++++-- 13 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index e444fb03c..0fe75d453 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -221,8 +221,9 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo - m_logo_bitmap = ScalableBitmap(this, wxGetApp().logo_name(), 192); - m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); +// m_logo_bitmap = ScalableBitmap(this, wxGetApp().logo_name(), 192); +// m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); + m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name()+".svg"), wxSize(192, 192))); hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); @@ -322,8 +323,8 @@ AboutDialog::AboutDialog() void AboutDialog::on_dpi_changed(const wxRect &suggested_rect) { - m_logo_bitmap.msw_rescale(); - m_logo->SetBitmap(m_logo_bitmap.bmp()); +// m_logo_bitmap.msw_rescale(); +// m_logo->SetBitmap(m_logo_bitmap.bmp()); const wxFont& font = GetFont(); const int fs = font.GetPointSize() - 1; diff --git a/src/slic3r/GUI/BitmapComboBox.cpp b/src/slic3r/GUI/BitmapComboBox.cpp index 3396c627b..54e1a31fa 100644 --- a/src/slic3r/GUI/BitmapComboBox.cpp +++ b/src/slic3r/GUI/BitmapComboBox.cpp @@ -166,7 +166,8 @@ int BitmapComboBox::Append(const wxString& item) //2. But then set width to 0 value for no using of bitmap left and right spacing //3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct - wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1)); +// wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1)); + wxBitmap bitmap(1, 16); { // bitmap.SetWidth(0); is depricated now // so, use next code @@ -268,6 +269,7 @@ void BitmapComboBox::DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED( void BitmapComboBox::Rescale() { + return; // Next workaround: To correct scaling of a BitmapCombobox // we need to refill control with new bitmaps const wxString selection = this->GetValue(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9a4921a84..4b0544488 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2088,6 +2088,7 @@ bool GUI_App::load_language(wxString language, bool initial) { // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. wxLocale temp_locale; + temp_locale.Init(); // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer @@ -2228,7 +2229,7 @@ void GUI_App::update_mode() { sidebar().update_mode(); -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 //_MSW_DARK_MODE if (!wxGetApp().tabs_as_menu()) dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); #endif diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 7b3476d71..4adb161c2 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -142,13 +142,20 @@ std::map SettingsFactory::CATEGORY_ICON = { L("Hollowing") , "hollowing" } }; -wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, bool menu_bmp /*= true*/) +//wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, bool menu_bmp /*= true*/) +wxBitmap SettingsFactory::get_category_bitmap_(const std::string& category_name, bool menu_bmp /*= true*/) { if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) return wxNullBitmap; - return menu_bmp ? create_menu_bitmap(CATEGORY_ICON.at(category_name)) : create_scaled_bitmap(CATEGORY_ICON.at(category_name)); + return /*menu_bmp ? create_menu_bitmap(CATEGORY_ICON.at(category_name)) : */create_scaled_bitmap(CATEGORY_ICON.at(category_name)); } +wxBitmapBundle SettingsFactory::get_category_bitmap(const std::string& category_name) +{ + if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) + return wxNullBitmap; + return create_menu_bitmap(CATEGORY_ICON.at(category_name)); +} //------------------------------------- // MenuFactory @@ -435,7 +442,8 @@ std::vector MenuFactory::get_volume_bitmaps() std::vector volume_bmps; volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size()); for (auto item : ADD_VOLUME_MENU_ITEMS) - volume_bmps.push_back(create_menu_bitmap(item.second)); +// volume_bmps.push_back(create_menu_bitmap(item.second)); + volume_bmps.push_back(create_scaled_bitmap(item.second, nullptr, 16, false, "", true)); return volume_bmps; } diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 0c478a97b..cba828c29 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -25,7 +25,9 @@ struct SettingsFactory typedef std::map> Bundle; static std::map CATEGORY_ICON; - static wxBitmap get_category_bitmap(const std::string& category_name, bool menu_bmp = true); +// static wxBitmap get_category_bitmap(const std::string& category_name, bool menu_bmp = true); + static wxBitmap get_category_bitmap_(const std::string& category_name, bool menu_bmp = true); + static wxBitmapBundle get_category_bitmap(const std::string& category_name); static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings); static std::vector get_options(bool is_part); }; diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index ba3f6675f..a749ad405 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -57,8 +57,8 @@ KBShortcutsDialog::KBShortcutsDialog() void KBShortcutsDialog::on_dpi_changed(const wxRect& suggested_rect) { - m_logo_bmp.msw_rescale(); - m_header_bitmap->SetBitmap(m_logo_bmp.bmp()); + //m_logo_bmp.msw_rescale(); + //m_header_bitmap->SetBitmap(m_logo_bmp.bmp()); msw_buttons_rescale(this, em_unit(), { wxID_OK }); Layout(); @@ -270,8 +270,10 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f sizer->AddStretchSpacer(); // logo - m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 32); - m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, m_logo_bmp.bmp()); + //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 32); + //m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, m_logo_bmp.bmp()); + m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(32, 32))); + sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); // text diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 94e9ca5f3..76bcfdd4a 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -99,9 +99,17 @@ void MsgDialog::apply_style(long style) if (style & wxNO) add_button(wxID_NO, (style & wxNO_DEFAULT)); if (style & wxCANCEL) add_button(wxID_CANCEL, (style & wxCANCEL_DEFAULT)); +#if 0 logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" : style & wxICON_INFORMATION ? "info" : style & wxICON_QUESTION ? "question" : "PrusaSlicer", this, 64, style & wxICON_ERROR)); +#else + std::string icon_name = style & wxICON_WARNING ? "exclamation" : + style & wxICON_INFORMATION ? "info" : + style & wxICON_QUESTION ? "question" : "PrusaSlicer"; + icon_name += ".svg"; + logo->SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name), wxSize(64, 64))); +#endif } void MsgDialog::finalize() diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 496cdcfc7..4344deb24 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -191,7 +191,7 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() if (bmp == nullptr) { std::vector bmps; for (auto& category : m_opt_categories) - bmps.emplace_back(SettingsFactory::get_category_bitmap(category, false)); + bmps.emplace_back(SettingsFactory::get_category_bitmap_(category, false)); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index ed4888a87..def48a1e4 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -376,8 +376,8 @@ void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); - m_bitmapIncompatible.msw_rescale(); - m_bitmapCompatible.msw_rescale(); + //m_bitmapIncompatible.msw_rescale(); + //m_bitmapCompatible.msw_rescale(); // parameters for an icon's drawing fill_width_height(); @@ -403,7 +403,8 @@ void PresetComboBox::fill_width_height() * So set sizes for solid_colored icons used for filament preset * and scale them in respect to em_unit value */ - const float scale_f = (float)m_em_unit * 0.1f; +// const float scale_f = (float)m_em_unit * 0.1f; + const float scale_f = 1.0f; thin_icon_width = lroundf(8 * scale_f); // analogue to 8px; wide_icon_width = norm_icon_width + thin_icon_width; diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 53e7d637d..db8cfc9c2 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -102,8 +102,10 @@ SysInfoDialog::SysInfoDialog() main_sizer->Add(hsizer, 1, wxEXPAND | wxALL, 10); // logo - m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 192); - m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); + //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 192); + //m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); + m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(192, 192))); + hsizer->Add(m_logo, 0, wxALIGN_CENTER_VERTICAL); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); @@ -194,8 +196,8 @@ SysInfoDialog::SysInfoDialog() void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect) { - m_logo_bmp.msw_rescale(); - m_logo->SetBitmap(m_logo_bmp.bmp()); + //m_logo_bmp.msw_rescale(); + //m_logo->SetBitmap(m_logo_bmp.bmp()); wxFont font = get_default_font(this); const int fs = font.GetPointSize() - 1; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 96151d8c7..1cb6ec8fc 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -772,8 +772,11 @@ void Tab::update_changed_tree_ui() void Tab::update_undo_buttons() { - m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); - m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); + m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert.name(): m_bmp_white_bullet.name()); + m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? m_bmp_non_system->name() : m_bmp_value_lock.name()); + + //m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); + //m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 876398510..9326c9258 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -16,6 +16,7 @@ #include "Plater.hpp" #include "../Utils/MacDarkMode.hpp" #include "BitmapComboBox.hpp" +#include "libslic3r/Utils.hpp" #include "OG_CustomCtrl.hpp" #include "libslic3r/Color.hpp" @@ -26,11 +27,13 @@ static std::map msw_menuitem_bitmaps; #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu) { + return; struct update_icons { static void run(wxMenuItem* item) { const auto it = msw_menuitem_bitmaps.find(item->GetId()); if (it != msw_menuitem_bitmaps.end()) { - const wxBitmap& item_icon = create_menu_bitmap(it->second); +// const wxBitmap& item_icon = create_menu_bitmap(it->second); + const wxBitmapBundle& item_icon = create_menu_bitmap(it->second); if (item_icon.IsOk()) item->SetBitmap(item_icon); } @@ -63,7 +66,8 @@ void enable_menu_item(wxUpdateUIEvent& evt, std::function const cb_condi } wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, - std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler, +// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler, + std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler, std::function const cb_condition, wxWindow* parent, int insert_pos/* = wxNOT_FOUND*/) { if (id == wxID_ANY) @@ -100,7 +104,9 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const if (id == wxID_ANY) id = wxNewId(); - const wxBitmap& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr +// const wxBitmap& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr + const wxBitmapBundle& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr + //#ifdef __WXMSW__ #ifndef __WXGTK__ if (bmp.IsOk()) @@ -420,9 +426,11 @@ int mode_icon_px_size() #endif } -wxBitmap create_menu_bitmap(const std::string& bmp_name) +//wxBitmap create_menu_bitmap(const std::string& bmp_name) +wxBitmapBundle create_menu_bitmap(const std::string& bmp_name) { - return create_scaled_bitmap(bmp_name, nullptr, 16, false, "", true); + // return create_scaled_bitmap(bmp_name, nullptr, 16, false, "", true); + return wxBitmapBundle::FromSVGFile(Slic3r::var(bmp_name + ".svg"), wxSize(16, 16)); } // win is used to get a correct em_unit value @@ -601,6 +609,7 @@ void LockButton::SetLock(bool lock) void LockButton::msw_rescale() { + return; m_bmp_lock_closed.msw_rescale(); m_bmp_lock_closed_f.msw_rescale(); m_bmp_lock_open.msw_rescale(); @@ -639,7 +648,8 @@ ModeButton::ModeButton( wxWindow* parent, const wxString& mode/* = wxEmptyString*/, const std::string& icon_name/* = ""*/, int px_cnt/* = 16*/) : - ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, icon_name, px_cnt), mode, wxBU_EXACTFIT) +// ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, icon_name, px_cnt), mode, wxBU_EXACTFIT) + ScalableButton(parent, wxID_ANY, icon_name, mode, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT) { Init(mode); } @@ -852,9 +862,10 @@ ScalableButton::ScalableButton( wxWindow * parent, Slic3r::GUI::wxGetApp().UpdateDarkUI(this); if (!icon_name.empty()) { - SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); - if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); +// SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); + SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name + ".svg"), wxSize(m_px_cnt, m_px_cnt))); + //if (m_use_default_disabled_bitmap) + // SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); if (!label.empty()) SetBitmapMargins(int(0.5* em_unit(parent)), 0); } @@ -896,7 +907,8 @@ bool ScalableButton::SetBitmap_(const std::string& bmp_name) if (m_current_icon_name.empty()) return false; - wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); +// wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); + wxBitmapBundle bmp = wxBitmapBundle::FromSVGFile(Slic3r::var(m_current_icon_name + ".svg"), wxSize(16, 16)); SetBitmap(bmp); SetBitmapCurrent(bmp); SetBitmapPressed(bmp); @@ -931,7 +943,8 @@ void ScalableButton::msw_rescale() { Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border); - if (!m_current_icon_name.empty()) { +// if (!m_current_icon_name.empty()) { + if (0) { wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); SetBitmap(bmp); SetBitmapCurrent(bmp); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index f3921919e..8387551f2 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,8 @@ inline void msw_rescale_menu(wxMenu* /* menu */) {} #endif /* __WXMSW__ */ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, - std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr, +// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr, + std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler = nullptr, std::function const cb_condition = []() { return true;}, wxWindow* parent = nullptr, int insert_pos = wxNOT_FOUND); wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr, @@ -49,7 +51,8 @@ void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector< int em_unit(wxWindow* win); int mode_icon_px_size(); -wxBitmap create_menu_bitmap(const std::string& bmp_name); +//wxBitmap create_menu_bitmap(const std::string& bmp_name); +wxBitmapBundle create_menu_bitmap(const std::string& bmp_name); wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr, const int px_cnt = 16, const bool grayscale = false, From dd6f7a71f1aaf95b93270a14073d1a5239669d5b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 10 May 2022 12:24:04 +0200 Subject: [PATCH 40/97] Using of wxWidgets 3.1.6 WIP: * Create Cache of wxBitmapBundles instead of wxBitmaps * Use wxBitmapBundles instead of wxBitmap for most of Widgets * Use empty bitmabundles instead of wxNullBitmap for wxBitmapComboBoxes. * Updated wxWidgets.cmake * OSX specific: Discard BitmapComboBox overrides + some code cleaning --- src/slic3r/GUI/AboutDialog.cpp | 4 +- src/slic3r/GUI/BitmapCache.cpp | 327 +++++++++++++++++++++- src/slic3r/GUI/BitmapCache.hpp | 21 +- src/slic3r/GUI/BitmapComboBox.cpp | 92 +----- src/slic3r/GUI/BitmapComboBox.hpp | 17 +- src/slic3r/GUI/ButtonsDescription.cpp | 3 - src/slic3r/GUI/ConfigWizard.cpp | 24 +- src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- src/slic3r/GUI/DoubleSlider.cpp | 101 +++---- src/slic3r/GUI/ExtraRenderers.cpp | 2 +- src/slic3r/GUI/ExtruderSequenceDialog.cpp | 3 - src/slic3r/GUI/Field.hpp | 16 +- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_Factories.cpp | 42 +-- src/slic3r/GUI/GUI_Factories.hpp | 7 +- src/slic3r/GUI/GUI_ObjectLayers.cpp | 68 ++--- src/slic3r/GUI/GUI_ObjectList.cpp | 8 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 41 +-- src/slic3r/GUI/GUI_ObjectSettings.cpp | 13 +- src/slic3r/GUI/GUI_ObjectSettings.hpp | 1 - src/slic3r/GUI/GalleryDialog.cpp | 44 ++- src/slic3r/GUI/KBShortcutsDialog.cpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 9 +- src/slic3r/GUI/MsgDialog.cpp | 11 +- src/slic3r/GUI/Notebook.cpp | 12 +- src/slic3r/GUI/Notebook.hpp | 6 + src/slic3r/GUI/OG_CustomCtrl.cpp | 42 +-- src/slic3r/GUI/OG_CustomCtrl.hpp | 2 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 84 +++--- src/slic3r/GUI/ObjectDataViewModel.hpp | 38 +-- src/slic3r/GUI/OptionsGroup.cpp | 3 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 27 +- src/slic3r/GUI/PhysicalPrinterDialog.hpp | 5 +- src/slic3r/GUI/Plater.cpp | 41 +-- src/slic3r/GUI/PresetComboBoxes.cpp | 177 ++++++------ src/slic3r/GUI/PresetComboBoxes.hpp | 14 +- src/slic3r/GUI/SavePresetDialog.cpp | 4 +- src/slic3r/GUI/Search.cpp | 11 +- src/slic3r/GUI/Search.hpp | 2 +- src/slic3r/GUI/SysInfoDialog.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 116 +++----- src/slic3r/GUI/Tab.hpp | 6 - src/slic3r/GUI/UnsavedChangesDialog.cpp | 58 ++-- src/slic3r/GUI/wxExtensions.cpp | 223 +++++---------- src/slic3r/GUI/wxExtensions.hpp | 60 ++-- 45 files changed, 930 insertions(+), 867 deletions(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 0fe75d453..abd137941 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -221,9 +221,7 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo -// m_logo_bitmap = ScalableBitmap(this, wxGetApp().logo_name(), 192); -// m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); - m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name()+".svg"), wxSize(192, 192))); + m_logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 192)); hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 844f6dea9..d2bf98b5b 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -60,7 +60,164 @@ static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image, float scale = 1. #endif } -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height) +wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vector& bmps) +{ + wxVector bitmaps; + + std::set scales = {1.0}; +#ifdef __APPLE__ + scales.emplace(m_scale); +#else + size_t disp_cnt = wxDisplay::GetCount(); + for (size_t disp = 0; disp < disp_cnt; ++disp) + scales.emplace(wxDisplay(disp).GetScaleFactor()); +#endif + + for (double scale : scales) { + size_t width = 0; + size_t height = 0; + for (const wxBitmapBundle* bmp_bndl : bmps) { +#ifdef __APPLE__ + wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(1.0); +#else + wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(scale); +#endif + width += size.GetWidth(); + height = std::max(height, size.GetHeight()); + } + + std::string bitmap_key = name + "," +float_to_string_decimal_point(scale); + +#ifdef __WXGTK2__ + // Broken alpha workaround + wxImage image(width, height); + image.InitAlpha(); + // Fill in with a white color. + memset(image.GetData(), 0x0ff, width * height * 3); + // Fill in with full transparency. + memset(image.GetAlpha(), 0, width * height); + size_t x = 0; + for (const wxBitmapBundle* bmp_bndl : bmps) { + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetPreferredBitmapSizeAtScale(scale)); + if (bmp.GetWidth() > 0) { + if (bmp.GetDepth() == 32) { + wxAlphaPixelData data(bmp); + //FIXME The following method is missing from wxWidgets 3.1.1. + // It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha(). + //data.UseAlpha(); + if (data) { + for (int r = 0; r < bmp.GetHeight(); ++r) { + wxAlphaPixelData::Iterator src(data); + src.Offset(data, 0, r); + unsigned char* dst_pixels = image.GetData() + (x + r * width) * 3; + unsigned char* dst_alpha = image.GetAlpha() + x + r * width; + for (int c = 0; c < bmp.GetWidth(); ++c, ++src) { + *dst_pixels++ = src.Red(); + *dst_pixels++ = src.Green(); + *dst_pixels++ = src.Blue(); + *dst_alpha++ = src.Alpha(); + } + } + } + } + else if (bmp.GetDepth() == 24) { + wxNativePixelData data(bmp); + if (data) { + for (int r = 0; r < bmp.GetHeight(); ++r) { + wxNativePixelData::Iterator src(data); + src.Offset(data, 0, r); + unsigned char* dst_pixels = image.GetData() + (x + r * width) * 3; + unsigned char* dst_alpha = image.GetAlpha() + x + r * width; + for (int c = 0; c < bmp.GetWidth(); ++c, ++src) { + *dst_pixels++ = src.Red(); + *dst_pixels++ = src.Green(); + *dst_pixels++ = src.Blue(); + *dst_alpha++ = wxALPHA_OPAQUE; + } + } + } + } + } + x += bmp.GetWidth(); + } + + bitmaps.push_back(* this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)))); + +#else + + wxBitmap* bitmap = this->insert(bitmap_key, width, height, scale); + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + size_t x = 0; + for (const wxBitmapBundle* bmp_bndl : bmps) { + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetPreferredBitmapSizeAtScale(scale)); + + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, x, 0, true); +#ifdef __APPLE__ + // we should "move" with step equal to non-scaled width + x += bmp.GetScaledWidth(); +#else + x += bmp.GetWidth(); +#endif + } + memDC.SelectObject(wxNullBitmap); + bitmaps.push_back(*bitmap); + +#endif + } + + return insert_bndl(name, bitmaps); +} + +wxBitmapBundle* BitmapCache::insert_bndl(const std::string &bitmap_key, const char* data, size_t width, size_t height) +{ + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + bndl = new wxBitmapBundle(wxBitmapBundle::FromSVG(data, wxSize(width, height))); + m_bndl_map[bitmap_key] = bndl; + } + else { + bndl = it->second; + *bndl = wxBitmapBundle::FromSVG(data, wxSize(width, height)); + } + return bndl; +} + +wxBitmapBundle* BitmapCache::insert_bndl(const std::string& bitmap_key, const wxBitmapBundle& bmp) +{ + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + bndl = new wxBitmapBundle(bmp); + m_bndl_map[bitmap_key] = bndl; + } + else { + bndl = it->second; + *bndl = wxBitmapBundle(bmp); + } + return bndl; +} + +wxBitmapBundle* BitmapCache::insert_bndl(const std::string& bitmap_key, const wxVector& bmps) +{ + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + bndl = new wxBitmapBundle(wxBitmapBundle::FromBitmaps(bmps)); + m_bndl_map[bitmap_key] = bndl; + } + else { + bndl = it->second; + *bndl = wxBitmapBundle::FromBitmaps(bmps); + } + return bndl; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height, double scale/* = -1.0*/) { wxBitmap *bitmap = nullptr; auto it = m_map.find(bitmap_key); @@ -76,7 +233,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_ // So, We need to let the Mac OS wxBitmap implementation // know that the image may already be scaled appropriately for Retina, // and thereby that it's not supposed to upscale it. - bitmap->CreateScaled(width, height, -1, m_scale); + bitmap->CreateScaled(width, height, -1, scale < 0.0 ? m_scale : scale); #endif m_map[bitmap_key] = bitmap; } else { @@ -297,6 +454,93 @@ error: return NULL; } +void BitmapCache::nsvgGetDataFromFileWithReplace(const char* filename, std::string& data_str, const std::map& replaces) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + + fp = boost::nowide::fopen(filename, "rb"); + if (!fp) goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char*)malloc(size + 1); + if (data == NULL) goto error; + if (fread(data, 1, size, fp) != size) goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + + data_str.assign(data); + for (auto val : replaces) + boost::replace_all(data_str, val.first, val.second); + + free(data); + return; + +error: + if (fp) fclose(fp); + if (data) free(data); + return; +} + +wxBitmapBundle* BitmapCache::from_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, + const bool dark_mode, const std::string& new_color /*= ""*/) +{ + if (target_width == 0) + target_width = target_height; + std::string bitmap_key = bitmap_name + (target_height != 0 ? + "-h" + std::to_string(target_height) : + "-w" + std::to_string(target_width)) +// + (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "") + + (dark_mode ? "-dm" : "") + + new_color; + + auto it = m_bndl_map.find(bitmap_key); + if (it != m_bndl_map.end()) + return it->second; + + // map of color replaces + std::map replaces; + if (dark_mode) + replaces["\"#808080\""] = "\"#FFFFFF\""; + if (!new_color.empty()) + replaces["\"#ED6B21\""] = "\"" + new_color + "\""; + + std::string str; + nsvgGetDataFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), str, replaces); + if (str.empty()) + return nullptr; + + return insert_bndl(bitmap_key, str.data(), target_width, target_height); +} + +wxBitmapBundle* BitmapCache::from_png(const std::string& bitmap_name, unsigned width, unsigned height) +{ + std::string bitmap_key = bitmap_name + (height != 0 ? + "-h" + std::to_string(height) : + "-w" + std::to_string(width)); + + auto it = m_bndl_map.find(bitmap_key); + if (it != m_bndl_map.end()) + return it->second; + + wxImage image; + if (!image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) || + image.GetWidth() == 0 || image.GetHeight() == 0) + return nullptr; + + if (height != 0 && unsigned(image.GetHeight()) != height) + width = unsigned(0.5f + float(image.GetWidth()) * height / image.GetHeight()); + else if (width != 0 && unsigned(image.GetWidth()) != width) + height = unsigned(0.5f + float(image.GetHeight()) * width / image.GetWidth()); + + if (height != 0 && width != 0) + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + return this->insert_bndl(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); +} + wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, const bool grayscale/* = false*/, const bool dark_mode/* = false*/, const std::string& new_color /*= ""*/) { @@ -395,5 +639,84 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); } +//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap +wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width /*= 0*/, bool dark_mode/* = false*/) +{ + wxVector bitmaps; + + std::set scales = { 1.0 }; +#ifdef __APPLE__ + scales.emplace(m_scale); +#else + size_t disp_cnt = wxDisplay::GetCount(); + for (size_t disp = 0; disp < disp_cnt; ++disp) + scales.emplace(wxDisplay(disp).GetScaleFactor()); +#endif + + for (double scale : scales) { + size_t width = width_in * scale; + size_t height = height_in * scale; + + wxImage image(width, height); + image.InitAlpha(); + unsigned char* imgdata = image.GetData(); + unsigned char* imgalpha = image.GetAlpha(); + for (size_t i = 0; i < width * height; ++i) { + *imgdata++ = r; + *imgdata++ = g; + *imgdata++ = b; + *imgalpha++ = transparency; + } + + // Add border, make white/light spools easier to see + if (border_width > 0) { + + // Restrict to width of image + if (border_width > height) border_width = height - 1; + if (border_width > width) border_width = width - 1; + + auto px_data = (uint8_t*)image.GetData(); + auto a_data = (uint8_t*)image.GetAlpha(); + + for (size_t x = 0; x < width; ++x) { + for (size_t y = 0; y < height; ++y) { + if (x < border_width || y < border_width || + x >= (width - border_width) || y >= (height - border_width)) { + const size_t idx = (x + y * width); + const size_t idx_rgb = (x + y * width) * 3; + px_data[idx_rgb] = px_data[idx_rgb + 1] = px_data[idx_rgb + 2] = dark_mode ? 245u : 110u; + a_data[idx] = 255u; + } + } + } + } + + bitmaps.push_back(wxImage_to_wxBitmap_with_alpha(std::move(image), scale)); + } + return wxBitmapBundle::FromBitmaps(bitmaps); +} + +wxBitmapBundle* BitmapCache::mksolid_bndl(size_t width, size_t height, const std::string& color, size_t border_width, bool dark_mode) +{ + std::string bitmap_key = (color.empty() ? "empty-w" : color) + "-h" + std::to_string(height) + "-w" + std::to_string(width) + (dark_mode ? "-dm" : ""); + + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + if (color.empty()) + bndl = new wxBitmapBundle(mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT, size_t(0))); + else { + ColorRGB rgb;// [3] ; + decode_color(color, rgb); + bndl = new wxBitmapBundle(mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, border_width, dark_mode)); + } + m_bndl_map[bitmap_key] = bndl; + } + else + return it->second; + + return bndl; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 5af90c5f7..28058d94b 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -24,10 +24,18 @@ public: void clear(); double scale() { return m_scale; } + wxBitmapBundle* find_bndl(const std::string &name) { auto it = m_bndl_map.find(name); return (it == m_bndl_map.end()) ? nullptr : it->second; } + const wxBitmapBundle* find_bndl(const std::string &name) const { return const_cast(this)->find_bndl(name); } wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; } const wxBitmap* find(const std::string &name) const { return const_cast(this)->find(name); } - wxBitmap* insert(const std::string &name, size_t width, size_t height); + wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const char* data, size_t width, size_t height); + wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const wxBitmapBundle &bmp); + wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const wxVector& bmps); + wxBitmapBundle* insert_bndl(const std::string& name, const std::vector& bmps); + wxBitmapBundle* insert_raw_rgba_bndl(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false); + + wxBitmap* insert(const std::string &name, size_t width, size_t height, double scale = -1.0); wxBitmap* insert(const std::string &name, const wxBitmap &bmp); wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); @@ -42,15 +50,24 @@ public: // And makes replases befor parsing // replace_map containes old_value->new_value static NSVGimage* nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map& replaces); + // Gets a data from SVG file and makes replases + // replace_map containes old_value->new_value + static void nsvgGetDataFromFileWithReplace(const char* filename, std::string& data_str, const std::map& replaces); + wxBitmapBundle* from_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, const bool dark_mode, const std::string& new_color = ""); + wxBitmapBundle* from_png(const std::string& bitmap_name, unsigned width, unsigned height); // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = ""); wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false); wxBitmap mksolid(size_t width, size_t height, const ColorRGB& rgb, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } - wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } + wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT, true, 0); } + wxBitmapBundle mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width = 0, bool dark_mode = false); + wxBitmapBundle* mksolid_bndl(size_t width, size_t height, const std::string& color = std::string(), size_t border_width = 0, bool dark_mode = false); + wxBitmapBundle* mkclear_bndl(size_t width, size_t height) { return mksolid_bndl(width, height); } private: std::map m_map; + std::map m_bndl_map; double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs) double m_scale = 1.0; // value, used for correct scaling of SVG icons on Retina display }; diff --git a/src/slic3r/GUI/BitmapComboBox.cpp b/src/slic3r/GUI/BitmapComboBox.cpp index 54e1a31fa..70c985cf9 100644 --- a/src/slic3r/GUI/BitmapComboBox.cpp +++ b/src/slic3r/GUI/BitmapComboBox.cpp @@ -54,17 +54,6 @@ using Slic3r::GUI::format_wxstr; namespace Slic3r { namespace GUI { -/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ - BitmapComboBox::BitmapComboBox(wxWindow* parent, wxWindowID id/* = wxID_ANY*/, const wxString& value/* = wxEmptyString*/, @@ -90,72 +79,6 @@ BitmapComboBox::~BitmapComboBox() { } -#ifdef __APPLE__ -bool BitmapComboBox::OnAddBitmap(const wxBitmap& bitmap) -{ - if (bitmap.IsOk()) - { - // we should use scaled! size values of bitmap - int width = (int)bitmap.GetScaledWidth(); - int height = (int)bitmap.GetScaledHeight(); - - if (m_usedImgSize.x < 0) - { - // If size not yet determined, get it from this image. - m_usedImgSize.x = width; - m_usedImgSize.y = height; - - // Adjust control size to vertically fit the bitmap - wxWindow* ctrl = GetControl(); - ctrl->InvalidateBestSize(); - wxSize newSz = ctrl->GetBestSize(); - wxSize sz = ctrl->GetSize(); - if (newSz.y > sz.y) - ctrl->SetSize(sz.x, newSz.y); - else - DetermineIndent(); - } - - wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y, - false, - "you can only add images of same size"); - - return true; - } - - return false; -} - -void BitmapComboBox::OnDrawItem(wxDC& dc, - const wxRect& rect, - int item, - int flags) const -{ - const wxBitmap& bmp = *(static_cast(m_bitmaps[item])); - if (bmp.IsOk()) - { - // we should use scaled! size values of bitmap - wxCoord w = bmp.GetScaledWidth(); - wxCoord h = bmp.GetScaledHeight(); - - const int imgSpacingLeft = 4; - - // Draw the image centered - dc.DrawBitmap(bmp, - rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft, - rect.y + (rect.height - h) / 2, - true); - } - - wxString text = GetString(item); - if (!text.empty()) - dc.DrawText(text, - rect.x + m_imgAreaWidth + 1, - rect.y + (rect.height - dc.GetCharHeight()) / 2); -} -#endif - - #ifdef _WIN32 int BitmapComboBox::Append(const wxString& item) @@ -166,19 +89,11 @@ int BitmapComboBox::Append(const wxString& item) //2. But then set width to 0 value for no using of bitmap left and right spacing //3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct -// wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1)); - wxBitmap bitmap(1, 16); - { - // bitmap.SetWidth(0); is depricated now - // so, use next code - bitmap.UnShare();// AllocExclusive(); - bitmap.GetGDIImageData()->m_width = 0; - } - + wxBitmapBundle bitmap = *get_empty_bmp_bundle(1, 16); OnAddBitmap(bitmap); + const int n = wxComboBox::Append(item); - if (n != wxNOT_FOUND) - DoSetItemBitmap(n, bitmap); + return n; } @@ -269,7 +184,6 @@ void BitmapComboBox::DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED( void BitmapComboBox::Rescale() { - return; // Next workaround: To correct scaling of a BitmapCombobox // we need to refill control with new bitmaps const wxString selection = this->GetValue(); diff --git a/src/slic3r/GUI/BitmapComboBox.hpp b/src/slic3r/GUI/BitmapComboBox.hpp index a77bf401d..545213fc3 100644 --- a/src/slic3r/GUI/BitmapComboBox.hpp +++ b/src/slic3r/GUI/BitmapComboBox.hpp @@ -29,28 +29,13 @@ BitmapComboBox(wxWindow* parent, #ifdef _WIN32 int Append(const wxString& item); #endif - int Append(const wxString& item, const wxBitmap& bitmap) + int Append(const wxString& item, const wxBitmapBundle& bitmap) { return wxBitmapComboBox::Append(item, bitmap); } protected: -#ifdef __APPLE__ -/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ -bool OnAddBitmap(const wxBitmap& bitmap) override; -void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; -#endif - #ifdef _WIN32 bool MSWOnDraw(WXDRAWITEMSTRUCT* item) override; void DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED(item), int flags) const; diff --git a/src/slic3r/GUI/ButtonsDescription.cpp b/src/slic3r/GUI/ButtonsDescription.cpp index 2c5262d47..37daffd9d 100644 --- a/src/slic3r/GUI/ButtonsDescription.cpp +++ b/src/slic3r/GUI/ButtonsDescription.cpp @@ -17,9 +17,6 @@ void ButtonsDescription::FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWi wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 5, 5); sizer->Add(grid_sizer, 0, wxEXPAND); - ScalableBitmap bmp_delete = ScalableBitmap(parent, "cross"); - ScalableBitmap bmp_delete_focus = ScalableBitmap(parent, "cross_focus"); - auto add_color = [grid_sizer, parent](wxColourPickerCtrl** color_picker, const wxColour& color, const wxColour& def_color, wxString label_text) { // wrap the label_text to the max 80 characters if (label_text.Len() > 80) { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 080de997e..b5da80e90 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1609,7 +1609,7 @@ ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) #ifndef __WXOSX__ SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX #endif //__WXOSX__ - SetMinSize(bg.bmp().GetSize()); + SetMinSize(bg.GetSize()); const wxSize size = GetTextExtent("m"); em_w = size.x; @@ -1734,8 +1734,8 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) wxPaintDC dc(this); - const auto bullet_w = bullet_black.bmp().GetSize().GetWidth(); - const auto bullet_h = bullet_black.bmp().GetSize().GetHeight(); + const auto bullet_w = bullet_black.GetWidth(); + const auto bullet_h = bullet_black.GetHeight(); const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; const int yinc = item_height(); @@ -1748,10 +1748,10 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) unsigned x = em_w/2 + item.indent * em_w; if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { - dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false); + dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false); } - else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); } - else if (i > item_active) { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); } + else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); } + else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); } x += + bullet_w + em_w/2; const auto text_size = dc.GetTextExtent(item.label); @@ -1763,9 +1763,9 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) } //draw logo - if (int y = size.y - bg.GetBmpHeight(); y>=0) { - dc.DrawBitmap(bg.bmp(), 0, y, false); - index_width = std::max(index_width, bg.GetBmpWidth() + em_w / 2); + if (int y = size.y - bg.GetHeight(); y>=0) { + dc.DrawBitmap(bg.get_bitmap(), 0, y, false); + index_width = std::max(index_width, bg.GetWidth() + em_w / 2); } if (GetMinSize().x < index_width) { @@ -1797,12 +1797,8 @@ void ConfigWizardIndex::msw_rescale() em_w = size.x; em_h = size.y; - bg.msw_rescale(); - SetMinSize(bg.bmp().GetSize()); + SetMinSize(bg.GetSize()); - bullet_black.msw_rescale(); - bullet_blue.msw_rescale(); - bullet_white.msw_rescale(); Refresh(); } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 4de8381ff..aa074f925 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -519,7 +519,7 @@ private: ssize_t item_hover; size_t last_page; - int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; } + int item_height() const { return std::max(bullet_black.GetHeight(), em_w) + em_w; } void on_paint(wxPaintEvent &evt); void on_mouse_move(wxMouseEvent &evt); diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index dda50ec05..717af39ba 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -86,24 +86,24 @@ Control::Control( wxWindow *parent, 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.GetBmpSize(); + 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.GetBmpWidth(); + 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.GetBmpWidth(); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetWidth(); m_bmp_revert = ScalableBitmap(this, "undo"); - m_revert_icon_dim = m_bmp_revert.GetBmpWidth(); + m_revert_icon_dim = m_bmp_revert.GetWidth(); m_bmp_cog = ScalableBitmap(this, "cog"); - m_cog_icon_dim = m_bmp_cog.GetBmpWidth(); + m_cog_icon_dim = m_bmp_cog.GetWidth(); m_selection = ssUndef; m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume printing"))); @@ -155,26 +155,11 @@ void Control::msw_rescale() { m_font = GUI::wxGetApp().normal_font(); - m_bmp_thumb_higher.msw_rescale(); - m_bmp_thumb_lower .msw_rescale(); - m_thumb_size = m_bmp_thumb_lower.bmp().GetSize(); - - m_bmp_add_tick_on .msw_rescale(); - m_bmp_add_tick_off.msw_rescale(); - m_bmp_del_tick_on .msw_rescale(); - m_bmp_del_tick_off.msw_rescale(); - m_tick_icon_dim = m_bmp_add_tick_on.bmp().GetSize().x; - - m_bmp_one_layer_lock_on .msw_rescale(); - m_bmp_one_layer_lock_off .msw_rescale(); - m_bmp_one_layer_unlock_on .msw_rescale(); - m_bmp_one_layer_unlock_off.msw_rescale(); - m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x; - - m_bmp_revert.msw_rescale(); - m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; - m_bmp_cog.msw_rescale(); - m_cog_icon_dim = m_bmp_cog.bmp().GetSize().x; + 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(); @@ -189,22 +174,18 @@ void Control::sys_color_changed() { GUI::wxGetApp().UpdateDarkUI(GetParent()); - m_bmp_add_tick_on .msw_rescale(); - m_bmp_add_tick_off.msw_rescale(); - m_bmp_del_tick_on .msw_rescale(); - m_bmp_del_tick_off.msw_rescale(); - m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth(); + 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 .msw_rescale(); - m_bmp_one_layer_lock_off .msw_rescale(); - m_bmp_one_layer_unlock_on .msw_rescale(); - m_bmp_one_layer_unlock_off.msw_rescale(); - m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth(); + 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.msw_rescale(); - m_revert_icon_dim = m_bmp_revert.GetBmpWidth(); - m_bmp_cog.msw_rescale(); - m_cog_icon_dim = m_bmp_cog.GetBmpWidth(); + m_bmp_revert.sys_color_changed(); + m_bmp_cog .sys_color_changed(); } int Control::GetActiveValue() const @@ -604,9 +585,12 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ return; } - wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); + //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.bmp() : &m_bmp_del_tick_on.bmp(); + 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; @@ -615,10 +599,12 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ else is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3; - if (m_draw_mode == dmSequentialFffPrint) - dc.DrawBitmap(create_scaled_bitmap("colorchange_add", nullptr, 16, true), x_draw, y_draw); + 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, x_draw, y_draw); + 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); @@ -851,7 +837,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider { 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.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw); + 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); @@ -945,12 +931,12 @@ void Control::draw_ticks(wxDC& dc) // Draw icon for "Pause print", "Custom Gcode" or conflict tick if (!icon_name.empty()) { - wxBitmap icon = create_scaled_bitmap(icon_name); + 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, x_draw, y_draw); + dc.DrawBitmap(icon->GetBitmapFor(this), x_draw, y_draw); } } } @@ -1262,9 +1248,12 @@ 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 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); @@ -1273,7 +1262,7 @@ void Control::draw_one_layer_icon(wxDC& dc) 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, x_draw, y_draw); + 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); @@ -1291,7 +1280,7 @@ void Control::draw_revert_icon(wxDC& dc) 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.bmp(), x_draw, y_draw); + 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); @@ -1315,7 +1304,7 @@ void Control::draw_cog_icon(wxDC& dc) is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2; } - dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw); + 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); @@ -1673,7 +1662,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current 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); + std::vector icons = get_extruder_color_icons(true); wxMenu* change_extruder_menu = new wxMenu(); @@ -1684,7 +1673,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current 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, + [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, icons[i-1], menu, [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); } @@ -1722,7 +1711,7 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren 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(create_menu_bitmap("colorchange_add_m")); + add_color_change_menu_item->SetBitmap(*get_bmp_bundle("colorchange_add_m")); } } diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index d72e1dd32..9bccb6b63 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -297,7 +297,7 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR if (can_create_editor_ctrl && !can_create_editor_ctrl()) return nullptr; - std::vector icons = get_extruder_color_icons(); + std::vector icons = get_extruder_color_icons(); if (icons.empty()) return nullptr; diff --git a/src/slic3r/GUI/ExtruderSequenceDialog.cpp b/src/slic3r/GUI/ExtruderSequenceDialog.cpp index 42313636e..e1c6a7ce0 100644 --- a/src/slic3r/GUI/ExtruderSequenceDialog.cpp +++ b/src/slic3r/GUI/ExtruderSequenceDialog.cpp @@ -264,9 +264,6 @@ void ExtruderSequenceDialog::on_dpi_changed(const wxRect& suggested_rect) { SetFont(wxGetApp().normal_font()); - m_bmp_add.msw_rescale(); - m_bmp_del.msw_rescale(); - const int em = em_unit(); m_intervals_grid_sizer->SetHGap(em); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index a9812abf2..95caa8ed3 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -106,14 +106,14 @@ public: bool set_undo_to_sys_tooltip(const wxString* tip) { return m_undo_ui.set_undo_to_sys_tooltip(tip); } // ui items used for revert line value - bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; } - const wxBitmap& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); } - const wxString* undo_tooltip() const { return m_undo_ui.undo_tooltip; } - const wxBitmap& 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; } - const bool blink() const { return m_undo_ui.blink; } - bool* get_blink_ptr() { return &m_undo_ui.blink; } + bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; } + const wxBitmapBundle& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); } + const wxString* undo_tooltip() const { return m_undo_ui.undo_tooltip; } + 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; } + const bool blink() const { return m_undo_ui.blink; } + bool* get_blink_ptr() { return &m_undo_ui.blink; } }; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4b0544488..0412bca21 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -331,7 +331,7 @@ private: // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) wxNativeFontInfo nfi= *font.GetNativeFontInfo(); - float pointSizeNew = scale * font.GetPointSize(); + float pointSizeNew = wxDisplay(this).GetScaleFactor() * scale * font.GetPointSize(); nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); nfi.pointSize = pointSizeNew; font = wxFont(nfi); @@ -1179,7 +1179,7 @@ bool GUI_App::on_init_inner() } // create splash screen with updated bmp - scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), + scrn = new SplashScreen(bmp.IsOk() ? bmp : get_bmp_bundle("PrusaSlicer", 400)->GetPreferredBitmapSizeAtScale(1.0), wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); if (!default_splashscreen_pos) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 4adb161c2..6a3dad5f4 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -142,19 +142,11 @@ std::map SettingsFactory::CATEGORY_ICON = { L("Hollowing") , "hollowing" } }; -//wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, bool menu_bmp /*= true*/) -wxBitmap SettingsFactory::get_category_bitmap_(const std::string& category_name, bool menu_bmp /*= true*/) +wxBitmapBundle* SettingsFactory::get_category_bitmap(const std::string& category_name) { if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) - return wxNullBitmap; - return /*menu_bmp ? create_menu_bitmap(CATEGORY_ICON.at(category_name)) : */create_scaled_bitmap(CATEGORY_ICON.at(category_name)); -} - -wxBitmapBundle SettingsFactory::get_category_bitmap(const std::string& category_name) -{ - if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) - return wxNullBitmap; - return create_menu_bitmap(CATEGORY_ICON.at(category_name)); + return get_bmp_bundle("empty"); + return get_bmp_bundle(CATEGORY_ICON.at(category_name)); } //------------------------------------- @@ -437,13 +429,12 @@ static void create_freq_settings_popupmenu(wxMenu* menu, const bool is_object_se #endif } -std::vector MenuFactory::get_volume_bitmaps() +std::vector MenuFactory::get_volume_bitmaps() { - std::vector volume_bmps; + std::vector volume_bmps; volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size()); for (auto item : ADD_VOLUME_MENU_ITEMS) -// volume_bmps.push_back(create_menu_bitmap(item.second)); - volume_bmps.push_back(create_scaled_bitmap(item.second, nullptr, 16, false, "", true)); + volume_bmps.push_back(get_bmp_bundle(item.second)); return volume_bmps; } @@ -623,7 +614,7 @@ wxMenuItem* MenuFactory::append_menu_item_settings(wxMenu* menu_) // Add full settings list auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name); - menu_item->SetBitmap(create_menu_bitmap("cog")); + menu_item->SetBitmap(*get_bmp_bundle("cog")); menu_item->SetSubMenu(create_settings_popupmenu(menu, is_object_settings, item)); return menu->Append(menu_item); @@ -768,7 +759,7 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) return; } - std::vector icons = get_extruder_color_icons(true); + std::vector icons = get_extruder_color_icons(true); wxMenu* extruder_selection_menu = new wxMenu(); const wxString& name = sels.Count() == 1 ? names[0] : names[1]; @@ -787,7 +778,7 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) (is_active_extruder ? " (" + _L("active") + ")" : ""); append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "", - [i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, *icons[icon_idx], menu, + [i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, icons[icon_idx], menu, [is_active_extruder]() { return !is_active_extruder; }, m_parent); } @@ -1147,12 +1138,6 @@ void MenuFactory::update_default_menu() create_default_menu(); } -void MenuFactory::msw_rescale() -{ - for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) - msw_rescale_menu(dynamic_cast(menu)); -} - #ifdef _WIN32 // For this class is used code from stackoverflow: // https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence @@ -1182,7 +1167,7 @@ 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 }) { - msw_rescale_menu(dynamic_cast(menu));// msw_rescale_menu updates just icons, so use it + sys_color_changed_menu(dynamic_cast(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()) @@ -1195,14 +1180,17 @@ void MenuFactory::sys_color_changed(wxMenuBar* menubar) { for (size_t id = 0; id < menubar->GetMenuCount(); id++) { wxMenu* menu = menubar->GetMenu(id); - msw_rescale_menu(menu); + sys_color_changed_menu(menu); +#ifndef __linux__ + menu->SetupBitmaps(); #ifdef _WIN32 // but under MSW we have to update item's bachground color for (wxMenuItem* item : menu->GetMenuItems()) update_menu_item_def_colors(item); +#endif #endif } - menubar->Refresh(); +// menubar->Refresh(); } diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index cba828c29..bbbc00d42 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -25,9 +25,7 @@ struct SettingsFactory typedef std::map> Bundle; static std::map CATEGORY_ICON; -// static wxBitmap get_category_bitmap(const std::string& category_name, bool menu_bmp = true); - static wxBitmap get_category_bitmap_(const std::string& category_name, bool menu_bmp = true); - static wxBitmapBundle get_category_bitmap(const std::string& category_name); + static wxBitmapBundle* get_category_bitmap(const std::string& category_name); static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings); static std::vector get_options(bool is_part); }; @@ -36,7 +34,7 @@ class MenuFactory { public: static const std::vector> ADD_VOLUME_MENU_ITEMS; - static std::vector get_volume_bitmaps(); + static std::vector get_volume_bitmaps(); MenuFactory(); ~MenuFactory() = default; @@ -45,7 +43,6 @@ public: void update(); void update_object_menu(); void update_default_menu(); - void msw_rescale(); void sys_color_changed(); static void sys_color_changed(wxMenuBar* menu_bar); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index b7ff8e48f..437a526af 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -234,47 +234,47 @@ void ObjectLayers::UpdateAndShow(const bool show) void ObjectLayers::msw_rescale() { - m_bmp_delete.msw_rescale(); - m_bmp_add.msw_rescale(); + //m_bmp_delete.msw_rescale(); + //m_bmp_add.msw_rescale(); - m_grid_sizer->SetHGap(wxGetApp().em_unit()); + //m_grid_sizer->SetHGap(wxGetApp().em_unit()); - // rescale edit-boxes - const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); - for (int i = 0; i < cells_cnt; ++i) { - const wxSizerItem* item = m_grid_sizer->GetItem(i); - if (item->IsWindow()) { - LayerRangeEditor* editor = dynamic_cast(item->GetWindow()); - if (editor != nullptr) - editor->msw_rescale(); - } - else if (item->IsSizer()) // case when we have editor with buttons - { - wxSizerItem* e_item = item->GetSizer()->GetItem(size_t(0)); // editor - if (e_item->IsWindow()) { - LayerRangeEditor* editor = dynamic_cast(e_item->GetWindow()); - if (editor != nullptr) - editor->msw_rescale(); - } + //// rescale edit-boxes + //const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); + //for (int i = 0; i < cells_cnt; ++i) { + // const wxSizerItem* item = m_grid_sizer->GetItem(i); + // if (item->IsWindow()) { + // LayerRangeEditor* editor = dynamic_cast(item->GetWindow()); + // if (editor != nullptr) + // editor->msw_rescale(); + // } + // else if (item->IsSizer()) // case when we have editor with buttons + // { + // wxSizerItem* e_item = item->GetSizer()->GetItem(size_t(0)); // editor + // if (e_item->IsWindow()) { + // LayerRangeEditor* editor = dynamic_cast(e_item->GetWindow()); + // if (editor != nullptr) + // editor->msw_rescale(); + // } - if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons - for (size_t btn : {2, 3}) { // del_btn, add_btn - wxSizerItem* b_item = item->GetSizer()->GetItem(btn); - if (b_item->IsWindow()) { - auto button = dynamic_cast(b_item->GetWindow()); - if (button != nullptr) - button->msw_rescale(); - } - } - } - } + // if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons + // for (size_t btn : {2, 3}) { // del_btn, add_btn + // wxSizerItem* b_item = item->GetSizer()->GetItem(btn); + // if (b_item->IsWindow()) { + // auto button = dynamic_cast(b_item->GetWindow()); + // if (button != nullptr) + // button->msw_rescale(); + // } + // } + // } + //} m_grid_sizer->Layout(); } void ObjectLayers::sys_color_changed() { - m_bmp_delete.msw_rescale(); - m_bmp_add.msw_rescale(); + m_bmp_delete.sys_color_changed(); + m_bmp_add.sys_color_changed(); // rescale edit-boxes const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); @@ -286,7 +286,7 @@ void ObjectLayers::sys_color_changed() if (b_item->IsWindow()) { auto button = dynamic_cast(b_item->GetWindow()); if (button != nullptr) - button->msw_rescale(); + button->sys_color_changed(); } } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index bfedd8e1e..1171149b6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4239,9 +4239,6 @@ void ObjectList::msw_rescale() GetColumn(colExtruder)->SetWidth( 8 * em); GetColumn(colEditing )->SetWidth( 3 * em); - // rescale/update existing items with bitmaps - m_objects_model->Rescale(); - Layout(); } @@ -4249,7 +4246,10 @@ void ObjectList::sys_color_changed() { wxGetApp().UpdateDVCDarkUI(this, true); - msw_rescale(); + // update existing items with bitmaps + m_objects_model->UpdateBitmaps(); + + Layout(); } void ObjectList::ItemValueChanged(wxDataViewEvent &event) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 24ae01389..a538f2b33 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -126,7 +126,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Load bitmaps to be used for the mirroring buttons: m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off"); - m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); + m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent"); const int border = wxOSX ? 0 : 4; const int em = wxGetApp().em_unit(); @@ -1009,7 +1009,7 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning m_manifold_warning_bmp = ScalableBitmap(m_parent, warning_icon_name); const wxString& tooltip = warning.tooltip; m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); - m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.bmp().GetSize()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.GetSize()); m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } @@ -1336,25 +1336,10 @@ void ObjectManipulation::msw_rescale() m_item_name->SetMinSize(wxSize(20*em, wxDefaultCoord)); msw_rescale_word_local_combo(m_word_local_combo); m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1))); - m_manifold_warning_bmp.msw_rescale(); const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText(); m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); - m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.bmp().GetSize()); - - m_mirror_bitmap_on.msw_rescale(); - m_mirror_bitmap_off.msw_rescale(); - m_mirror_bitmap_hidden.msw_rescale(); - m_reset_scale_button->msw_rescale(); - m_reset_rotation_button->msw_rescale(); -#if ENABLE_WORLD_COORDINATE - m_reset_skew_button->msw_rescale(); -#endif /// ENABLE_WORLD_COORDINATE - m_drop_to_bed_button->msw_rescale(); - m_lock_bnt->msw_rescale(); - - for (int id = 0; id < 3; ++id) - m_mirror_buttons[id].first->msw_rescale(); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.GetSize()); // rescale label-heights // Text trick to grid sizer layout: @@ -1383,20 +1368,16 @@ void ObjectManipulation::sys_color_changed() for (ManipulationEditor* editor : m_editors) editor->sys_color_changed(this); - // btn...->msw_rescale() updates icon on button, so use it - m_mirror_bitmap_on.msw_rescale(); - m_mirror_bitmap_off.msw_rescale(); - m_mirror_bitmap_hidden.msw_rescale(); - m_reset_scale_button->msw_rescale(); - m_reset_rotation_button->msw_rescale(); -#if ENABLE_WORLD_COORDINATE - m_reset_skew_button->msw_rescale(); -#endif // ENABLE_WORLD_COORDINATE - m_drop_to_bed_button->msw_rescale(); - m_lock_bnt->msw_rescale(); + m_mirror_bitmap_on.sys_color_changed(); + m_mirror_bitmap_off.sys_color_changed(); + m_mirror_bitmap_hidden.sys_color_changed(); + m_reset_scale_button->sys_color_changed(); + m_reset_rotation_button->sys_color_changed(); + m_drop_to_bed_button->sys_color_changed(); + m_lock_bnt->sys_color_changed(); for (int id = 0; id < 3; ++id) - m_mirror_buttons[id].first->msw_rescale(); + m_mirror_buttons[id].first->sys_color_changed(); } #if ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 291013fe9..97eb5f10d 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -99,7 +99,7 @@ bool ObjectSettings::update_settings_list() btn->SetToolTip(_(L("Remove parameter"))); btn->SetBitmapFocus(m_bmp_delete_focus.bmp()); - btn->SetBitmapHover(m_bmp_delete_focus.bmp()); + btn->SetBitmapCurrent(m_bmp_delete_focus.bmp()); btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { wxGetApp().plater()->take_snapshot(from_u8((boost::format(_utf8(L("Delete Option %s"))) % opt_key).str())); @@ -133,7 +133,7 @@ bool ObjectSettings::update_settings_list() return; ctrl->SetBitmap_(m_bmp_delete); ctrl->SetBitmapFocus(m_bmp_delete_focus.bmp()); - ctrl->SetBitmapHover(m_bmp_delete_focus.bmp()); + ctrl->SetBitmapCurrent(m_bmp_delete_focus.bmp()); }; const bool is_extruders_cat = cat.first == "Extruders"; @@ -268,15 +268,6 @@ void ObjectSettings::UpdateAndShow(const bool show) OG_Settings::UpdateAndShow(show ? update_settings_list() : false); } -void ObjectSettings::msw_rescale() -{ - m_bmp_delete.msw_rescale(); - m_bmp_delete_focus.msw_rescale(); - - for (auto group : m_og_settings) - group->msw_rescale(); -} - void ObjectSettings::sys_color_changed() { m_og->sys_color_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index e5a6937f1..5d0be1308 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -56,7 +56,6 @@ public: bool add_missed_options(ModelConfig *config_to, const DynamicPrintConfig &config_from); void update_config_values(ModelConfig *config); void UpdateAndShow(const bool show) override; - void msw_rescale(); void sys_color_changed(); }; diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index 975b807dc..6be6a94fe 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -157,8 +157,9 @@ bool GalleryDialog::can_change_thumbnail() void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) { - const int& em = em_unit(); + update(); + const int& em = em_unit(); msw_buttons_rescale(this, em, { ID_BTN_ADD_CUSTOM_SHAPE, ID_BTN_DEL_CUSTOM_SHAPE, ID_BTN_REPLACE_CUSTOM_PNG, wxID_OK, wxID_CLOSE }); wxSize size = wxSize(50 * em, 35 * em); @@ -169,13 +170,14 @@ void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } -static void add_lock(wxImage& image) +static void add_lock(wxImage& image, wxWindow* parent_win) { - int lock_sz = 22; + wxBitmapBundle* bmp_bndl = get_bmp_bundle("lock", 22); #ifdef __APPLE__ - lock_sz /= mac_max_scaling_factor(); + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize() * mac_max_scaling_factor()); +#else + wxBitmap bmp = bmp_bndl->GetBitmapFor(parent_win); #endif - wxBitmap bmp = create_scaled_bitmap("lock", nullptr, lock_sz); wxImage lock_image = bmp.ConvertToImage(); if (!lock_image.IsOk() || lock_image.GetWidth() == 0 || lock_image.GetHeight() == 0) @@ -213,21 +215,28 @@ static void add_lock(wxImage& image) } } -static void add_default_image(wxImageList* img_list, bool is_system) +static void add_default_image(wxImageList* img_list, bool is_system, wxWindow* parent_win) { - int sz = IMG_PX_CNT; + wxBitmapBundle* bmp_bndl = get_bmp_bundle("cog", IMG_PX_CNT); #ifdef __APPLE__ - sz /= mac_max_scaling_factor(); + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize() * mac_max_scaling_factor()); +#else + wxBitmap bmp = bmp_bndl->GetBitmapFor(parent_win); #endif - wxBitmap bmp = create_scaled_bitmap("cog", nullptr, sz, true); + bmp = bmp.ConvertToDisabled(); if (is_system) { wxImage image = bmp.ConvertToImage(); if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) { - add_lock(image); + add_lock(image, parent_win); +#ifdef __APPLE__ + bmp = wxBitmap(std::move(image), -1, mac_max_scaling_factor()); +#else bmp = wxBitmap(std::move(image)); +#endif } } + img_list->Add(bmp); }; @@ -344,8 +353,13 @@ void GalleryDialog::load_label_icon_list() // Make an image list containing large icons +#ifdef __APPLE__ + m_image_list = new wxImageList(IMG_PX_CNT, IMG_PX_CNT); + int px_cnt = IMG_PX_CNT * mac_max_scaling_factor(); +#else int px_cnt = (int)(em_unit() * IMG_PX_CNT * 0.1f + 0.5f); m_image_list = new wxImageList(px_cnt, px_cnt); +#endif std::string ext = ".png"; @@ -364,7 +378,7 @@ void GalleryDialog::load_label_icon_list() if (can_generate_thumbnail) generate_thumbnail_from_model(model_name); else { - add_default_image(m_image_list, item.is_system); + add_default_image(m_image_list, item.is_system, this); continue; } } @@ -373,14 +387,18 @@ void GalleryDialog::load_label_icon_list() if (!image.CanRead(from_u8(img_name)) || !image.LoadFile(from_u8(img_name), wxBITMAP_TYPE_PNG) || image.GetWidth() == 0 || image.GetHeight() == 0) { - add_default_image(m_image_list, item.is_system); + add_default_image(m_image_list, item.is_system, this); continue; } image.Rescale(px_cnt, px_cnt, wxIMAGE_QUALITY_BILINEAR); if (item.is_system) - add_lock(image); + add_lock(image, this); +#ifdef __APPLE__ + wxBitmap bmp = wxBitmap(std::move(image), -1, mac_max_scaling_factor()); +#else wxBitmap bmp = wxBitmap(std::move(image)); +#endif m_image_list->Add(bmp); } diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index a749ad405..bf4fe9dc7 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -270,9 +270,7 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f sizer->AddStretchSpacer(); // logo - //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 32); - //m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, m_logo_bmp.bmp()); - m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(32, 32))); + m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 32)); sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 3d80954bd..bf1f67994 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1014,9 +1014,6 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); - for (size_t id = 0; id < m_menubar->GetMenuCount(); id++) - msw_rescale_menu(m_menubar->GetMenu(id)); - // Workarounds for correct Window rendering after rescale /* Even if Window is maximized during moving, @@ -1051,7 +1048,7 @@ void MainFrame::on_sys_color_changed() #ifdef _MSW_DARK_MODE // update common mode sizer if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->Rescale(); + dynamic_cast(m_tabpanel)->OnColorsChanged(); #endif #endif @@ -1608,9 +1605,9 @@ void MainFrame::update_menubar() m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _L("S&end G-code") : _L("S&end to print")) + dots + "\tCtrl+Shift+G"); m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _L("&Filament Settings Tab") : _L("Mate&rial Settings Tab")) + "\tCtrl+3"); - m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_menu_bitmap(is_fff ? "spool" : "resin")); + m_changeable_menu_items[miMaterialTab] ->SetBitmap(*get_bmp_bundle(is_fff ? "spool" : "resin")); - m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_menu_bitmap(is_fff ? "printer" : "sla_printer")); + m_changeable_menu_items[miPrinterTab] ->SetBitmap(*get_bmp_bundle(is_fff ? "printer" : "sla_printer")); } #if 0 diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 76bcfdd4a..43e13841c 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -99,17 +99,10 @@ void MsgDialog::apply_style(long style) if (style & wxNO) add_button(wxID_NO, (style & wxNO_DEFAULT)); if (style & wxCANCEL) add_button(wxID_CANCEL, (style & wxCANCEL_DEFAULT)); -#if 0 - logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" : - style & wxICON_INFORMATION ? "info" : - style & wxICON_QUESTION ? "question" : "PrusaSlicer", this, 64, style & wxICON_ERROR)); -#else std::string icon_name = style & wxICON_WARNING ? "exclamation" : style & wxICON_INFORMATION ? "info" : style & wxICON_QUESTION ? "question" : "PrusaSlicer"; - icon_name += ".svg"; - logo->SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name), wxSize(64, 64))); -#endif + logo->SetBitmap(*get_bmp_bundle(icon_name, 64)); } void MsgDialog::finalize() @@ -238,7 +231,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_ add_msg_content(this, content_sizer, msg, monospaced_font); // Use a small bitmap with monospaced font, as the error text will not be wrapped. - logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/84)); + logo->SetBitmap(*get_bmp_bundle("PrusaSlicer_192px_grayscale.png", monospaced_font ? 48 : /*1*/84)); SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit())); diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp index 9c5ccb834..380b402d5 100644 --- a/src/slic3r/GUI/Notebook.cpp +++ b/src/slic3r/GUI/Notebook.cpp @@ -95,10 +95,6 @@ void ButtonsListCtrl::UpdateMode() void ButtonsListCtrl::Rescale() { - m_mode_sizer->msw_rescale(); - for (ScalableButton* btn : m_pageButtons) - btn->msw_rescale(); - int em = em_unit(this); m_btn_margin = std::lround(0.3 * em); m_line_margin = std::lround(0.1 * em); @@ -108,6 +104,14 @@ void ButtonsListCtrl::Rescale() m_sizer->Layout(); } +void ButtonsListCtrl::OnColorsChanged() +{ + for (ScalableButton* btn : m_pageButtons) + btn->sys_color_changed(); + + m_sizer->Layout(); +} + void ButtonsListCtrl::SetSelection(int sel) { if (m_selection == sel) diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp index af03a6a08..bd6c5d85a 100644 --- a/src/slic3r/GUI/Notebook.hpp +++ b/src/slic3r/GUI/Notebook.hpp @@ -21,6 +21,7 @@ public: void SetSelection(int sel); void UpdateMode(); void Rescale(); + void OnColorsChanged(); 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; @@ -245,6 +246,11 @@ public: GetBtnsListCtrl()->Rescale(); } + void OnColorsChanged() + { + GetBtnsListCtrl()->OnColorsChanged(); + } + void OnNavigationKey(wxNavigationKeyEvent& event) { if (event.IsWindowChange()) { diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 400db751a..c202de5e2 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -19,12 +19,12 @@ static bool is_point_in_rect(const wxPoint& pt, const wxRect& rect) rect.GetTop() <= pt.y && pt.y <= rect.GetBottom(); } -static wxSize get_bitmap_size(const wxBitmap& bmp) +static wxSize get_bitmap_size(const wxBitmapBundle* bmp, wxWindow* parent) { #ifdef __APPLE__ - return bmp.GetScaledSize(); + return bmp->GetDefaultSize(); #else - return bmp.GetSize(); + return bmp->GetBitmapFor(parent).GetSize(); #endif } @@ -45,8 +45,8 @@ OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent, m_v_gap = lround(1.0 * m_em_unit); m_h_gap = lround(0.2 * m_em_unit); - m_bmp_mode_sz = get_bitmap_size(create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12)); - m_bmp_blinking_sz = get_bitmap_size(create_scaled_bitmap("search_blink", this)); + m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode_simple", wxOSX ? 10 : 12), this); + m_bmp_blinking_sz = get_bitmap_size(get_bmp_bundle("search_blink"), this); init_ctrl_lines();// from og.lines() @@ -416,8 +416,8 @@ void OG_CustomCtrl::msw_rescale() m_v_gap = lround(1.0 * m_em_unit); m_h_gap = lround(0.2 * m_em_unit); - m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize(); - m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize(); + m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode_simple", wxOSX ? 10 : 12), this); + m_bmp_blinking_sz = get_bitmap_size(get_bmp_bundle("search_blink"), this); m_max_win_width = 0; @@ -497,7 +497,7 @@ void OG_CustomCtrl::CtrlLine::msw_rescale() { // if we have a single option with no label, no sidetext if (draw_just_act_buttons) - height = get_bitmap_size(create_scaled_bitmap("empty")).GetHeight(); + height = get_bitmap_size(get_bmp_bundle("empty"), ctrl).GetHeight(); if (ctrl->opt_group->label_width != 0 && !og_line.label.IsEmpty()) { wxSize label_sz = ctrl->GetTextExtent(og_line.label); @@ -666,13 +666,13 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) ConfigOptionMode mode = og_line.get_options()[0].opt.mode; const std::string& bmp_name = mode == ConfigOptionMode::comSimple ? "mode_simple" : mode == ConfigOptionMode::comAdvanced ? "mode_advanced" : "mode_expert"; - wxBitmap bmp = create_scaled_bitmap(bmp_name, ctrl, wxOSX ? 10 : 12); - wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp).GetHeight()) / 2); + wxBitmapBundle* bmp = get_bmp_bundle(bmp_name, wxOSX ? 10 : 12); + wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp, ctrl).GetHeight()) / 2); if (og_line.get_options().front().opt.gui_type != ConfigOptionDef::GUIType::legend) - dc.DrawBitmap(bmp, 0, y_draw); + dc.DrawBitmap(bmp->GetBitmapFor(ctrl), 0, y_draw); - return get_bitmap_size(bmp).GetWidth() + ctrl->m_h_gap; + return get_bitmap_size(bmp, ctrl).GetWidth() + ctrl->m_h_gap; } wxCoord OG_CustomCtrl::CtrlLine::draw_text(wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url/* = false*/) @@ -734,33 +734,33 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_text(wxDC& dc, wxPoint pos, const wxStr wxPoint OG_CustomCtrl::CtrlLine::draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking) { - wxBitmap bmp_blinking = create_scaled_bitmap(is_blinking ? "search_blink" : "empty", ctrl); + wxBitmapBundle* bmp_blinking = get_bmp_bundle(is_blinking ? "search_blink" : "empty"); wxCoord h_pos = pos.x; - wxCoord v_pos = pos.y + lround((height - get_bitmap_size(bmp_blinking).GetHeight()) / 2); + wxCoord v_pos = pos.y + lround((height - get_bitmap_size(bmp_blinking, ctrl).GetHeight()) / 2); - dc.DrawBitmap(bmp_blinking, h_pos, v_pos); + dc.DrawBitmap(bmp_blinking->GetBitmapFor(ctrl), h_pos, v_pos); - int bmp_dim = get_bitmap_size(bmp_blinking).GetWidth(); + int bmp_dim = get_bitmap_size(bmp_blinking, ctrl).GetWidth(); h_pos += bmp_dim + ctrl->m_h_gap; return wxPoint(h_pos, v_pos); } -wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id) +wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmapBundle& bmp_undo_to_sys, const wxBitmapBundle& bmp_undo, bool is_blinking, size_t rect_id) { pos = draw_blinking_bmp(dc, pos, is_blinking); wxCoord h_pos = pos.x; wxCoord v_pos = pos.y; - dc.DrawBitmap(bmp_undo_to_sys, h_pos, v_pos); + dc.DrawBitmap(bmp_undo_to_sys.GetBitmapFor(ctrl), h_pos, v_pos); - int bmp_dim = get_bitmap_size(bmp_undo_to_sys).GetWidth(); + int bmp_dim = get_bitmap_size(&bmp_undo_to_sys, ctrl).GetWidth(); rects_undo_to_sys_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); h_pos += bmp_dim + ctrl->m_h_gap; - dc.DrawBitmap(bmp_undo, h_pos, v_pos); + dc.DrawBitmap(bmp_undo.GetBitmapFor(ctrl), h_pos, v_pos); - bmp_dim = get_bitmap_size(bmp_undo).GetWidth(); + bmp_dim = get_bitmap_size(&bmp_undo, ctrl).GetWidth(); rects_undo_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); h_pos += bmp_dim + ctrl->m_h_gap; diff --git a/src/slic3r/GUI/OG_CustomCtrl.hpp b/src/slic3r/GUI/OG_CustomCtrl.hpp index c15132fec..0308322f7 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.hpp +++ b/src/slic3r/GUI/OG_CustomCtrl.hpp @@ -63,7 +63,7 @@ class OG_CustomCtrl :public wxPanel wxCoord draw_mode_bmp(wxDC& dc, wxCoord v_pos); wxCoord draw_text (wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url = false); wxPoint draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking); - wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id = 0); + wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmapBundle& bmp_undo_to_sys, const wxBitmapBundle& bmp_undo, bool is_blinking, size_t rect_id = 0); bool launch_browser() const; bool is_separator() const { return og_line.is_separator(); } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 4344deb24..05d0d60ec 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -56,7 +56,7 @@ const std::map INFO_ITEMS{ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmap& bmp, + const wxBitmapBundle& bmp, const wxString& extruder, const int idx/* = -1*/, const std::string& warning_icon_name /*= std::string*/) : @@ -101,7 +101,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } else if (type == itLayerRoot) { - m_bmp = create_scaled_bitmap(LayerRootIcon); // FIXME: pass window ptr + m_bmp = *get_bmp_bundle(LayerRootIcon); m_name = _(L("Layers")); } else if (type == itInfo) @@ -132,7 +132,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; - m_bmp = create_scaled_bitmap(LayerIcon); // FIXME: pass window ptr + m_bmp = *get_bmp_bundle(LayerIcon); set_action_and_extruder_icons(); init_container(); @@ -151,7 +151,7 @@ void ObjectDataViewModelNode::set_action_and_extruder_icons() { m_action_icon_name = m_type & itObject ? "advanced_plus" : m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; - m_action_icon = create_scaled_bitmap(m_action_icon_name); // FIXME: pass window ptr + m_action_icon = *get_bmp_bundle(m_action_icon_name); // set extruder bitmap set_extruder_icon(); @@ -170,7 +170,7 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) { m_printable = printable; m_printable_icon = m_printable == piUndef ? m_empty_bmp : - create_scaled_bitmap(m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); + *get_bmp_bundle(m_printable == piPrintable ? "eye_open" : "eye_closed"); } void ObjectDataViewModelNode::set_warning_icon(const std::string& warning_icon_name) @@ -185,14 +185,14 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() m_bmp = m_empty_bmp; std::string scaled_bitmap_name = m_name.ToUTF8().data(); - scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : ""); + scaled_bitmap_name += (wxGetApp().dark_mode() ? "-dm" : ""); - wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + wxBitmapBundle *bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name); if (bmp == nullptr) { - std::vector bmps; + std::vector bmps; for (auto& category : m_opt_categories) - bmps.emplace_back(SettingsFactory::get_category_bitmap_(category, false)); - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + bmps.emplace_back(SettingsFactory::get_category_bitmap(category)); + bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps); } m_bmp = *bmp; @@ -216,13 +216,13 @@ bool ObjectDataViewModelNode::update_settings_digest(const std::vectorGetExtruder().IsEmpty()) @@ -1676,14 +1676,14 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( return obj_item; } -void ObjectDataViewModel::Rescale() +void ObjectDataViewModel::UpdateBitmaps() { m_volume_bmps = MenuFactory::get_volume_bitmaps(); - m_warning_bmp = create_scaled_bitmap(WarningIcon); - m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); + m_warning_bmp = *get_bmp_bundle(WarningIcon); + m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon); for (auto item : INFO_ITEMS) - m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name); + m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name); wxDataViewItemArray all_items; GetAllChildren(wxDataViewItem(0), all_items); @@ -1694,7 +1694,7 @@ void ObjectDataViewModel::Rescale() continue; ObjectDataViewModelNode *node = static_cast(item.GetID()); - node->msw_rescale(); + node->sys_color_changed(); switch (node->m_type) { @@ -1705,11 +1705,11 @@ void ObjectDataViewModel::Rescale() node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name); break; case itLayerRoot: - node->m_bmp = create_scaled_bitmap(LayerRootIcon); + node->m_bmp = *get_bmp_bundle(LayerRootIcon); case itLayer: - node->m_bmp = create_scaled_bitmap(LayerIcon); + node->m_bmp = *get_bmp_bundle(LayerIcon); case itInfo: - node->m_bmp = m_info_bmps.at(node->m_info_item_type); + node->m_bmp = *m_info_bmps.at(node->m_info_item_type); break; default: break; } @@ -1718,22 +1718,22 @@ void ObjectDataViewModel::Rescale() } } -wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/) +wxBitmapBundle ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/) { if (warning_icon_name.empty()) - return m_volume_bmps[static_cast(vol_type)]; + return *m_volume_bmps[static_cast(vol_type)]; std::string scaled_bitmap_name = warning_icon_name + std::to_string(static_cast(vol_type)); scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm"); - wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + wxBitmapBundle *bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name); if (bmp == nullptr) { - std::vector bmps; + std::vector bmps; - bmps.emplace_back(GetWarningBitmap(warning_icon_name)); + bmps.emplace_back(&GetWarningBitmap(warning_icon_name)); bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps); } return *bmp; @@ -1768,7 +1768,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo return; if (node->GetType() & itVolume) { - node->SetWarningBitmap(m_volume_bmps[static_cast(node->volume_type())], ""); + node->SetWarningBitmap(*m_volume_bmps[static_cast(node->volume_type())], ""); return; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index f8885b206..7014acccb 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -63,21 +63,21 @@ class ObjectDataViewModelNode { ObjectDataViewModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; - wxBitmap m_empty_bmp; + wxBitmapBundle m_empty_bmp; size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; t_layer_height_range m_layer_range = { 0.0f, 0.0f }; wxString m_name; - wxBitmap& m_bmp = m_empty_bmp; + wxBitmapBundle& m_bmp = m_empty_bmp; ItemType m_type; int m_idx = -1; bool m_container = false; wxString m_extruder = "default"; - wxBitmap m_extruder_bmp; - wxBitmap m_action_icon; + wxBitmapBundle m_extruder_bmp; + wxBitmapBundle m_action_icon; PrintIndicator m_printable {piUndef}; - wxBitmap m_printable_icon; + wxBitmapBundle m_printable_icon; std::string m_warning_icon_name{ "" }; std::string m_action_icon_name = ""; @@ -99,7 +99,7 @@ public: ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmap& bmp, + const wxBitmapBundle& bmp, const wxString& extruder, const int idx = -1, const std::string& warning_icon_name = std::string()); @@ -179,10 +179,10 @@ public: bool SetValue(const wxVariant &variant, unsigned int col); void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } - void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } + void SetBitmap(const wxBitmapBundle &icon) { m_bmp = icon; } void SetExtruder(const wxString &extruder) { m_extruder = extruder; } - void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } - const wxBitmap& GetBitmap() const { return m_bmp; } + void SetWarningBitmap(const wxBitmapBundle& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } + const wxBitmapBundle& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } InfoItemType GetInfoItemType() const { return m_info_item_type; } @@ -234,7 +234,7 @@ public: void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); int volume_type() const { return int(m_volume_type); } - void msw_rescale(); + void sys_color_changed(); #ifndef NDEBUG bool valid(); @@ -257,11 +257,11 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); class ObjectDataViewModel :public wxDataViewModel { std::vector m_objects; - std::vector m_volume_bmps; - std::map m_info_bmps; - wxBitmap m_empty_bmp; - wxBitmap m_warning_bmp; - wxBitmap m_warning_manifold_bmp; + std::vector m_volume_bmps; + std::map m_info_bmps; + wxBitmapBundle m_empty_bmp; + wxBitmapBundle m_warning_bmp; + wxBitmapBundle m_warning_manifold_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -315,7 +315,7 @@ public: // helper method for wxLog wxString GetName(const wxDataViewItem &item) const; - wxBitmap& GetBitmap(const wxDataViewItem &item) const; + wxBitmapBundle& GetBitmap(const wxDataViewItem &item) const; wxString GetExtruder(const wxDataViewItem &item) const; int GetExtruderNumber(const wxDataViewItem &item) const; @@ -383,9 +383,9 @@ public: void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } // Rescale bitmaps for existing Items - void Rescale(); + void UpdateBitmaps(); - wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, + wxBitmapBundle GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name = std::string()); void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); @@ -403,7 +403,7 @@ private: wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); void AddAllChildren(const wxDataViewItem& parent); - wxBitmap& GetWarningBitmap(const std::string& warning_icon_name); + wxBitmapBundle& GetWarningBitmap(const std::string& warning_icon_name); }; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 257fe2532..6055a8e78 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -728,7 +728,6 @@ void ConfigOptionsGroup::msw_rescale() // check if window is ScalableButton ScalableButton* sc_btn = dynamic_cast(win); if (sc_btn) { - sc_btn->msw_rescale(); sc_btn->SetSize(sc_btn->GetBestSize()); return; } @@ -773,7 +772,7 @@ void ConfigOptionsGroup::sys_color_changed() wxWindow* win = item->GetWindow(); // check if window is ScalableButton if (ScalableButton* sc_btn = dynamic_cast(win)) { - sc_btn->msw_rescale(); + sc_btn->sys_color_changed(); return; } wxGetApp().UpdateDarkUI(win, dynamic_cast(win) != nullptr); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index b2983f97f..04ee9d090 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -142,10 +142,10 @@ void PresetForPrinter::AllowDelete() m_presets_list->update(); } -void PresetForPrinter::msw_rescale() +void PresetForPrinter::on_sys_color_changed() { - m_presets_list->msw_rescale(); - m_delete_preset_btn->msw_rescale(); + m_presets_list->sys_color_changed(); + m_delete_preset_btn->sys_color_changed(); } @@ -603,19 +603,10 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); - m_add_preset_btn->msw_rescale(); - m_printhost_browse_btn->msw_rescale(); - m_printhost_test_btn->msw_rescale(); - if (m_printhost_cafile_browse_btn) - m_printhost_cafile_browse_btn->msw_rescale(); - m_optgroup->msw_rescale(); msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - for (PresetForPrinter* preset : m_presets) - preset->msw_rescale(); - const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(size); @@ -623,6 +614,18 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } +void PhysicalPrinterDialog::on_sys_color_changed() +{ + m_add_preset_btn->sys_color_changed(); + m_printhost_browse_btn->sys_color_changed(); + m_printhost_test_btn->sys_color_changed(); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->sys_color_changed(); + + for (PresetForPrinter* preset : m_presets) + preset->on_sys_color_changed(); +} + void PhysicalPrinterDialog::OnOK(wxEvent& event) { wxString printer_name = m_printer_name->GetValue(); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index cb9a48b3e..d8bb70d3c 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -48,8 +48,7 @@ public: void SuppressDelete(); void AllowDelete(); - void msw_rescale(); - void on_sys_color_changed() {}; + void on_sys_color_changed(); }; @@ -98,7 +97,7 @@ public: void DeletePreset(PresetForPrinter* preset_for_printer); protected: void on_dpi_changed(const wxRect& suggested_rect) override; - void on_sys_color_changed() override {}; + void on_sys_color_changed() override; bool had_all_mk3; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b78a6db95..c0a4bedf1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -180,7 +180,6 @@ public: bool showing_manifold_warning_icon; void show_sizer(bool show); - void msw_rescale(); void update_warning_icon(const std::string& warning_icon_name); }; @@ -210,7 +209,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : init_info_label(&info_size, _L("Size")); - info_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("info")); + 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); @@ -223,7 +222,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(m_warning_icon_name)); + 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); @@ -242,17 +241,11 @@ void ObjectInfo::show_sizer(bool show) manifold_warning_icon->Show(showing_manifold_warning_icon && show); } -void ObjectInfo::msw_rescale() -{ - manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name)); - info_icon->SetBitmap(create_scaled_bitmap("info")); -} - 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(create_scaled_bitmap(m_warning_icon_name)); + manifold_warning_icon->SetBitmap(*get_bmp_bundle(m_warning_icon_name)); } } @@ -350,9 +343,6 @@ void FreqChangedParams::msw_rescale() { m_og->msw_rescale(); m_og_sla->msw_rescale(); - - for (auto btn: m_empty_buttons) - btn->msw_rescale(); } void FreqChangedParams::sys_color_changed() @@ -361,7 +351,7 @@ void FreqChangedParams::sys_color_changed() m_og_sla->sys_color_changed(); for (auto btn: m_empty_buttons) - btn->msw_rescale(); + btn->sys_color_changed(); wxGetApp().UpdateDarkUI(m_wiping_dialog_button, true); } @@ -450,7 +440,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : */ auto empty_widget = [this] (wxWindow* parent) { auto sizer = new wxBoxSizer(wxHORIZONTAL); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + 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); @@ -508,7 +498,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : } })); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + 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())); @@ -1103,9 +1093,6 @@ void Sidebar::msw_rescale() { SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1)); - if (p->mode_sizer) - p->mode_sizer->msw_rescale(); - for (PlaterPresetComboBox* combo : std::vector { p->combo_print, p->combo_sla_print, p->combo_sla_material, @@ -1117,14 +1104,8 @@ void Sidebar::msw_rescale() p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); - p->object_settings->msw_rescale(); p->object_layers->msw_rescale(); - p->object_info->msw_rescale(); - - p->btn_send_gcode->msw_rescale(); -// p->btn_eject_device->msw_rescale(); - p->btn_export_gcode_removable->msw_rescale(); #ifdef _WIN32 const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight(); #else @@ -1145,14 +1126,13 @@ void Sidebar::sys_color_changed() for (wxWindow* win : std::vector{ this, p->sliced_info->GetStaticBox(), p->object_info->GetStaticBox(), p->btn_reslice, p->btn_export_gcode }) wxGetApp().UpdateDarkUI(win); - p->object_info->msw_rescale(); 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->msw_rescale(); + p->mode_sizer->sys_color_changed(); p->frequently_changed_parameters->sys_color_changed(); p->object_settings->sys_color_changed(); #endif @@ -1170,11 +1150,12 @@ void Sidebar::sys_color_changed() p->object_layers->sys_color_changed(); // btn...->msw_rescale() updates icon on button, so use it - p->btn_send_gcode->msw_rescale(); + p->btn_send_gcode->sys_color_changed(); // p->btn_eject_device->msw_rescale(); - p->btn_export_gcode_removable->msw_rescale(); + p->btn_export_gcode_removable->sys_color_changed(); p->scrolled->Layout(); + p->scrolled->Refresh(); p->searcher.dlg_sys_color_changed(); } @@ -6971,8 +6952,6 @@ void Plater::msw_rescale() p->sidebar->msw_rescale(); - p->menus.msw_rescale(); - Layout(); GetParent()->Layout(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index def48a1e4..93a5fe433 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -108,8 +108,8 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const default: break; } - m_bitmapCompatible = ScalableBitmap(this, "flag_green"); - m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); + m_bitmapCompatible = get_bmp_bundle("flag_green"); + m_bitmapIncompatible = get_bmp_bundle("flag_red"); // parameters for an icon's drawing fill_width_height(); @@ -242,12 +242,12 @@ void PresetComboBox::update(std::string select_preset_name) const std::deque& presets = m_collection->get_presets(); - std::map> nonsys_presets; - std::map incomp_presets; + std::map> nonsys_presets; + std::map incomp_presets; wxString selected = ""; if (!presets.front().is_visible) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { @@ -268,7 +268,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; - wxBitmap* 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, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); if (!is_enabled) @@ -280,17 +280,17 @@ void PresetComboBox::update(std::string select_preset_name) } else { - nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)) selected = get_preset_name(preset); } if (i + 1 == m_collection->num_default_presets()) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } if (!nonsys_presets.empty()) { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { int item_id = Append(it->first, *it->second.first); bool is_enabled = it->second.second; if (!is_enabled) @@ -300,8 +300,8 @@ void PresetComboBox::update(std::string select_preset_name) } if (!incomp_presets.empty()) { - set_label_marker(Append(separator(L("Incompatible presets")), wxNullBitmap)); - for (std::map::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) { + set_label_marker(Append(separator(L("Incompatible presets")), NullBitmapBndl())); + for (std::map::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) { set_label_marker(Append(it->first, *it->second), LABEL_ITEM_DISABLED); } } @@ -337,7 +337,6 @@ bool PresetComboBox::del_physical_printer(const wxString& note_string/* = wxEmpt msg += note_string + "\n"; msg += format_wxstr(_L("Are you sure you want to delete \"%1%\" printer?"), printer_name); - //if (wxMessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES) if (MessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES) return false; @@ -364,7 +363,8 @@ void PresetComboBox::show_all(bool show_all) void PresetComboBox::update() { - this->update(into_u8(this->GetString(this->GetSelection()))); + int n = this->GetSelection(); + this->update(n < 0 ? "" : into_u8(this->GetString(n))); } void PresetComboBox::update_from_bundle() @@ -375,43 +375,31 @@ void PresetComboBox::update_from_bundle() void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); +} - //m_bitmapIncompatible.msw_rescale(); - //m_bitmapCompatible.msw_rescale(); - - // parameters for an icon's drawing - fill_width_height(); +void PresetComboBox::sys_color_changed() +{ + m_bitmapCompatible = get_bmp_bundle("flag_green"); + m_bitmapIncompatible = get_bmp_bundle("flag_red"); + wxGetApp().UpdateDarkUI(this); // update the control to redraw the icons update(); } -void PresetComboBox::sys_color_changed() -{ - wxGetApp().UpdateDarkUI(this); - msw_rescale(); -} - void PresetComboBox::fill_width_height() { - // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so - // set a bitmap's height to m_bitmapCompatible->GetHeight() and norm_icon_width to m_bitmapCompatible->GetWidth() - icon_height = m_bitmapCompatible.GetBmpHeight(); - norm_icon_width = m_bitmapCompatible.GetBmpWidth(); + icon_height = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetHeight(); + norm_icon_width = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetWidth(); - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ -// const float scale_f = (float)m_em_unit * 0.1f; - const float scale_f = 1.0f; + null_icon_width = 2 * norm_icon_width; - thin_icon_width = lroundf(8 * scale_f); // analogue to 8px; + thin_icon_width = 8; wide_icon_width = norm_icon_width + thin_icon_width; - space_icon_width = lroundf(2 * scale_f); - thin_space_icon_width = lroundf(4 * scale_f); - wide_space_icon_width = lroundf(6 * scale_f); + space_icon_width = 2; + thin_space_icon_width = 4; + wide_space_icon_width = 6; } wxString PresetComboBox::separator(const std::string& label) @@ -419,7 +407,8 @@ wxString PresetComboBox::separator(const std::string& label) return wxString::FromUTF8(separator_head()) + _(label) + wxString::FromUTF8(separator_tail()); } -wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + +wxBitmapBundle* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/, const std::string& filament_rgb/* = ""*/, const std::string& extruder_rgb/* = ""*/, const std::string& material_rgb/* = ""*/) { @@ -435,45 +424,41 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con bitmap_key += ",dark"; bitmap_key += material_rgb; - wxBitmap* bmp = bitmap_cache().find(bitmap_key); - if (bmp == nullptr) { + wxBitmapBundle* bmp_bndl = bitmap_cache().find_bndl(bitmap_key); + if (bmp_bndl == nullptr) { // Create the bitmap with color bars. - std::vector bmps; + std::vector bmps; if (wide_icons) // Paint a red flag for incompatible presets. - bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); + bmps.emplace_back(is_compatible ? get_empty_bmp_bundle(norm_icon_width, icon_height) : m_bitmapIncompatible); if (m_type == Preset::TYPE_FILAMENT && !filament_rgb.empty()) { // Paint the color bars. - ColorRGB color; - decode_color(filament_rgb, color); - bmps.emplace_back(bitmap_cache().mksolid(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, color, false, 1, dark_mode)); - if (!is_single_bar) { - decode_color(extruder_rgb, color); - bmps.emplace_back(bitmap_cache().mksolid(thin_icon_width, icon_height, color, false, 1, dark_mode)); - } + bmps.emplace_back(get_solid_bmp_bundle(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, filament_rgb)); + if (!is_single_bar) + bmps.emplace_back(get_solid_bmp_bundle(thin_icon_width, icon_height, extruder_rgb)); // Paint a lock at the system presets. - bmps.emplace_back(bitmap_cache().mkclear(space_icon_width, icon_height)); + bmps.emplace_back(get_empty_bmp_bundle(space_icon_width, icon_height)); } else { // Paint the color bars. - bmps.emplace_back(bitmap_cache().mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(get_empty_bmp_bundle(thin_space_icon_width, icon_height)); if (m_type == Preset::TYPE_SLA_MATERIAL) - bmps.emplace_back(create_scaled_bitmap(main_icon_name, this, 16, false, material_rgb)); + bmps.emplace_back(bitmap_cache().from_svg(main_icon_name, 16, 16, dark_mode, material_rgb)); else - bmps.emplace_back(create_scaled_bitmap(main_icon_name)); + bmps.emplace_back(get_bmp_bundle(main_icon_name)); // Paint a lock at the system presets. - bmps.emplace_back(bitmap_cache().mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(get_empty_bmp_bundle(wide_space_icon_width, icon_height)); } - bmps.emplace_back(is_system ? create_scaled_bitmap("lock_closed") : bitmap_cache().mkclear(norm_icon_width, icon_height)); - bmp = bitmap_cache().insert(bitmap_key, bmps); + bmps.emplace_back(is_system ? get_bmp_bundle("lock_closed") : get_empty_bmp_bundle(norm_icon_width, icon_height)); + bmp_bndl = bitmap_cache().insert_bndl(bitmap_key, bmps); } - return bmp; + return bmp_bndl; } -wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, +wxBitmapBundle* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, bool is_enabled/* = true*/, bool is_compatible/* = true*/, bool is_system/* = false*/) { bitmap_key += !is_enabled ? "_disabled" : ""; @@ -483,20 +468,25 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& m if (wxGetApp().dark_mode()) bitmap_key += ",dark"; - wxBitmap* bmp = bitmap_cache().find(bitmap_key); + wxBitmapBundle* bmp = bitmap_cache().find_bndl(bitmap_key); if (bmp == nullptr) { // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? create_scaled_bitmap(main_icon_name, this, 16, !is_enabled) : - is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); + std::vector bmps; + bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? get_bmp_bundle(main_icon_name) : + is_compatible ? m_bitmapCompatible : m_bitmapIncompatible); // Paint a lock at the system presets. - bmps.emplace_back(is_system ? create_scaled_bitmap(next_icon_name, this, 16, !is_enabled) : bitmap_cache().mkclear(norm_icon_width, icon_height)); - bmp = bitmap_cache().insert(bitmap_key, bmps); + bmps.emplace_back(is_system ? get_bmp_bundle(next_icon_name) : get_empty_bmp_bundle(norm_icon_width, icon_height)); + bmp = bitmap_cache().insert_bndl(bitmap_key, bmps); } return bmp; } +wxBitmapBundle PresetComboBox::NullBitmapBndl() +{ + return *get_empty_bmp_bundle(null_icon_width, icon_height); +} + bool PresetComboBox::is_selected_physical_printer() { auto selected_item = this->GetSelection(); @@ -783,14 +773,16 @@ void PlaterPresetComboBox::update() // and draw a red flag in front of the selected preset. bool wide_icons = selected_preset && !selected_preset->is_compatible; - std::map nonsys_presets; + null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width; + + std::map nonsys_presets; wxString selected_user_preset; wxString tooltip; const std::deque& presets = m_collection->get_presets(); if (!presets.front().is_visible) - this->set_label_marker(this->Append(separator(L("System presets")), wxNullBitmap)); + this->set_label_marker(this->Append(separator(L("System presets")), NullBitmapBndl())); for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { @@ -824,7 +816,7 @@ void PlaterPresetComboBox::update() material_rgb = print_config_def.get("material_colour")->get_default_value()->value; } - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, + auto bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, preset.is_compatible, preset.is_system || preset.is_default, single_bar, filament_rgb, extruder_rgb, material_rgb); assert(bmp); @@ -845,12 +837,12 @@ void PlaterPresetComboBox::update() } } if (i + 1 == m_collection->num_default_presets()) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } if (!nonsys_presets.empty()) { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { Append(it->first, *it->second); validate_selection(it->first == selected_user_preset); } @@ -860,7 +852,7 @@ void PlaterPresetComboBox::update() { // add Physical printers, if any exists if (!m_preset_bundle->physical_printers.empty()) { - set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + set_label_marker(Append(separator(L("Physical printers")), NullBitmapBndl())); const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { @@ -869,7 +861,7 @@ void PlaterPresetComboBox::update() if (!preset || !preset->is_visible) continue; std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - wxBitmap* bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); + auto bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); assert(bmp); set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); @@ -880,7 +872,7 @@ void PlaterPresetComboBox::update() } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) { - wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); + auto bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); if (m_type == Preset::TYPE_FILAMENT) @@ -917,9 +909,20 @@ void PlaterPresetComboBox::update() void PlaterPresetComboBox::msw_rescale() { PresetComboBox::msw_rescale(); - edit_btn->msw_rescale(); +#ifdef __WXMSW__ + // Use this part of code just on Windows to avoid of some layout issues on Linux + // see https://github.com/prusa3d/PrusaSlicer/issues/5163 and https://github.com/prusa3d/PrusaSlicer/issues/5505 + // Update control min size after rescale (changed Display DPI under MSW) + if (GetMinWidth() != 20 * m_em_unit) + SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight())); +#endif //__WXMSW__ } +void PlaterPresetComboBox::sys_color_changed() +{ + PresetComboBox::sys_color_changed(); + edit_btn->sys_color_changed(); +} // --------------------------------- // *** TabPresetComboBox *** @@ -982,10 +985,10 @@ void TabPresetComboBox::update() const std::deque& presets = m_collection->get_presets(); - std::map> nonsys_presets; + std::map> nonsys_presets; wxString selected = ""; if (!presets.front().is_visible) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); size_t idx_selected = m_collection->get_selected_idx(); if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { @@ -1012,7 +1015,7 @@ void TabPresetComboBox::update() } std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - wxBitmap* 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, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); if (preset.is_default || preset.is_system) { @@ -1023,18 +1026,18 @@ void TabPresetComboBox::update() } else { - std::pair pair(bmp, is_enabled); - nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + std::pair pair(bmp, is_enabled); + nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (i == idx_selected) selected = get_preset_name(preset); } if (i + 1 == m_collection->num_default_presets()) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } if (!nonsys_presets.empty()) { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { int item_id = Append(it->first, *it->second.first); bool is_enabled = it->second.second; if (!is_enabled) @@ -1047,7 +1050,7 @@ void TabPresetComboBox::update() { // add Physical printers, if any exists if (!m_preset_bundle->physical_printers.empty()) { - set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + set_label_marker(Append(separator(L("Physical printers")), NullBitmapBndl())); const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { @@ -1057,7 +1060,7 @@ void TabPresetComboBox::update() continue; std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - wxBitmap* bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); + auto bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); assert(bmp); set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); @@ -1068,7 +1071,7 @@ void TabPresetComboBox::update() // add "Add/Remove printers" item std::string icon_name = "edit_uni"; - wxBitmap* bmp = get_bmp("edit_preset_list, tab,", icon_name, ""); + auto bmp = get_bmp("edit_preset_list, tab,", icon_name, ""); assert(bmp); set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 40a0cfc28..9fe75c126 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_PresetComboBoxes_hpp_ #define slic3r_PresetComboBoxes_hpp_ -//#include +#include #include #include "libslic3r/Preset.hpp" @@ -88,9 +88,9 @@ protected: static BitmapCache& bitmap_cache(); // Indicator, that the preset is compatible with the selected printer. - ScalableBitmap m_bitmapCompatible; + wxBitmapBundle* m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. - ScalableBitmap m_bitmapIncompatible; + wxBitmapBundle* m_bitmapIncompatible; int m_last_selected; int m_em_unit; @@ -99,6 +99,7 @@ protected: // parameters for an icon's drawing int icon_height; int norm_icon_width; + int null_icon_width; int thin_icon_width; int wide_icon_width; int space_icon_width; @@ -120,13 +121,15 @@ protected: #endif // __linux__ static wxString separator(const std::string& label); - wxBitmap* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + wxBitmapBundle* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, bool is_compatible = true, bool is_system = false, bool is_single_bar = false, const std::string& filament_rgb = "", const std::string& extruder_rgb = "", const std::string& material_rgb = ""); - wxBitmap* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, + wxBitmapBundle* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, bool is_enabled = true, bool is_compatible = true, bool is_system = false); + wxBitmapBundle NullBitmapBndl(); + private: void fill_width_height(); }; @@ -155,6 +158,7 @@ public: wxString get_preset_name(const Preset& preset) override; void update() override; void msw_rescale() override; + void sys_color_changed() override; void OnSelect(wxCommandEvent& evt) override; private: diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index 460b30126..2a2e3bb10 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -56,7 +56,7 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox wxStaticText* label_top = new wxStaticText(m_parent, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(tab->title())).str())); - m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); + m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark")); m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name), wxDefaultPosition, wxSize(35 * wxGetApp().em_unit(), -1)); for (const std::string& value : values) @@ -173,7 +173,7 @@ void SavePresetDialog::Item::update_valid_bmp() { std::string bmp_name = m_valid_type == Warning ? "exclamation" : m_valid_type == NoValid ? "cross" : "tick_mark" ; - m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); + m_valid_bmp->SetBitmap(*get_bmp_bundle(bmp_name)); } void SavePresetDialog::Item::accept() diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 6b5edc30e..52e58193c 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -720,12 +720,9 @@ void SearchDialog::msw_rescale() { const int& em = em_unit(); - search_list_model->msw_rescale(); search_list->GetColumn(SearchListModel::colIcon )->SetWidth(3 * em); search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(45 * em); - msw_buttons_rescale(this, em, { wxID_CANCEL }); - const wxSize& size = wxSize(40 * em, 30 * em); SetMinSize(size); @@ -743,7 +740,7 @@ void SearchDialog::on_sys_color_changed() #endif // msw_rescale updates just icons, so use it - search_list_model->msw_rescale(); + search_list_model->sys_color_changed(); Refresh(); } @@ -776,10 +773,10 @@ void SearchListModel::Prepend(const std::string& label) RowPrepended(); } -void SearchListModel::msw_rescale() +void SearchListModel::sys_color_changed() { for (ScalableBitmap& bmp : m_icon) - bmp.msw_rescale(); + bmp.sys_color_changed(); } wxString SearchListModel::GetColumnType(unsigned int col) const @@ -795,7 +792,7 @@ void SearchListModel::GetValueByRow(wxVariant& variant, switch (col) { case colIcon: - variant << m_icon[m_values[row].second].bmp(); + variant << m_icon[m_values[row].second].bmp().GetBitmapFor(m_icon[m_values[row].second].parent()); break; case colMarkedText: variant = m_values[row].first; diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 49866d066..0d7851132 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -213,7 +213,7 @@ public: void Clear(); void Prepend(const std::string& text); - void msw_rescale(); + void sys_color_changed(); // implementation of base class virtuals to define model diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index db8cfc9c2..d4d83dfda 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -104,7 +104,7 @@ SysInfoDialog::SysInfoDialog() // logo //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 192); //m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); - m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(192, 192))); + m_logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 192)); hsizer->Add(m_logo, 0, wxALIGN_CENTER_VERTICAL); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1cb6ec8fc..d8d6dbf19 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -155,10 +155,8 @@ void Tab::create_preset_tab() add_scaled_button(panel, &m_btn_edit_ph_printer, "cog"); m_show_incompatible_presets = false; - add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); - add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); - add_scaled_button(panel, &m_btn_hide_incompatible_presets, m_bmp_hide_incompatible_presets.name()); + add_scaled_button(panel, &m_btn_hide_incompatible_presets, "flag_green"); m_btn_compare_preset->SetToolTip(_L("Compare this preset with some another")); // TRN "Save current Settings" @@ -207,7 +205,7 @@ void Tab::create_preset_tab() #endif m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this))); - const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor(); + const float scale_factor = em_unit(this)*0.1;// GetContentScaleFactor(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); @@ -254,11 +252,8 @@ void Tab::create_preset_tab() m_treectrl = new wxTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(20 * m_em_unit, -1), wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); m_left_sizer->Add(m_treectrl, 1, wxEXPAND); - const int img_sz = int(16 * scale_factor + 0.5f); - m_icons = new wxImageList(img_sz, img_sz, true, 1); - // Index of the last icon inserted into $self->{icons}. + // Index of the last icon inserted into m_treectrl m_icon_count = -1; - m_treectrl->AssignImageList(m_icons); m_treectrl->AddRoot("root"); m_treectrl->SetIndent(0); wxGetApp().UpdateDarkUI(m_treectrl); @@ -321,6 +316,14 @@ void Tab::create_preset_tab() // Initialize the DynamicPrintConfig by default keys/values. build(); + if (!m_scaled_icons_list.empty()) { + // update icons for tree_ctrl + wxVector img_bundles; + for (const ScalableBitmap& bmp : m_scaled_icons_list) + img_bundles.push_back(bmp.bmp()); + m_treectrl->SetImages(img_bundles); + } + // ys_FIXME: Following should not be needed, the function will be called later // (update_mode->update_visibility->rebuild_page_tree). This does not work, during the // second call of rebuild_page_tree m_treectrl->GetFirstVisibleItem(); returns zero @@ -336,7 +339,7 @@ void Tab::add_scaled_button(wxWindow* parent, const wxString& label/* = wxEmptyString*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style, true); + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style); m_scaled_buttons.push_back(*btn); } @@ -366,7 +369,6 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str if (icon_idx == -1) { // Add a new icon to the icon list. m_scaled_icons_list.push_back(ScalableBitmap(this, icon)); - m_icons->Add(m_scaled_icons_list.back().bmp()); icon_idx = ++m_icon_count; m_icon_index[icon] = icon_idx; } @@ -656,16 +658,6 @@ void TabPrinter::init_options_list() m_options_list.emplace("extruders_count", m_opt_status_value); } -void TabPrinter::msw_rescale() -{ - Tab::msw_rescale(); - - if (m_reset_to_filament_color) - m_reset_to_filament_color->msw_rescale(); - - Layout(); -} - void TabSLAMaterial::init_options_list() { if (!m_options_list.empty()) @@ -924,31 +916,9 @@ void Tab::msw_rescale() { m_em_unit = em_unit(m_parent); - if (m_mode_sizer) - m_mode_sizer->msw_rescale(); m_presets_choice->msw_rescale(); - m_treectrl->SetMinSize(wxSize(20 * m_em_unit, -1)); - // rescale buttons and cached bitmaps - for (const auto btn : m_scaled_buttons) - btn->msw_rescale(); - for (const auto bmp : m_scaled_bitmaps) - bmp->msw_rescale(); - - if (m_detach_preset_btn) - m_detach_preset_btn->msw_rescale(); - - // rescale icons for tree_ctrl - for (ScalableBitmap& bmp : m_scaled_icons_list) - bmp.msw_rescale(); - // recreate and set new ImageList for tree_ctrl - m_icons->RemoveAll(); - m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight()); - for (ScalableBitmap& bmp : m_scaled_icons_list) - m_icons->Add(bmp.bmp()); - m_treectrl->AssignImageList(m_icons); - // rescale options_groups if (m_active_page) m_active_page->msw_rescale(); @@ -962,28 +932,28 @@ void Tab::sys_color_changed() // update buttons and cached bitmaps for (const auto btn : m_scaled_buttons) - btn->msw_rescale(); + btn->sys_color_changed(); for (const auto bmp : m_scaled_bitmaps) - bmp->msw_rescale(); + bmp->sys_color_changed(); if (m_detach_preset_btn) - m_detach_preset_btn->msw_rescale(); + m_detach_preset_btn->sys_color_changed(); + + update_show_hide_incompatible_button(); // update icons for tree_ctrl - for (ScalableBitmap& bmp : m_scaled_icons_list) - bmp.msw_rescale(); - // recreate and set new ImageList for tree_ctrl - m_icons->RemoveAll(); - m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight()); - for (ScalableBitmap& bmp : m_scaled_icons_list) - m_icons->Add(bmp.bmp()); - m_treectrl->AssignImageList(m_icons); + wxVector img_bundles; + for (ScalableBitmap& bmp : m_scaled_icons_list) { + bmp.sys_color_changed(); + img_bundles.push_back(bmp.bmp()); + } + m_treectrl->SetImages(img_bundles); // Colors for ui "decoration" update_label_colours(); #ifdef _WIN32 wxWindowUpdateLocker noUpdates(this); if (m_mode_sizer) - m_mode_sizer->msw_rescale(); + m_mode_sizer->sys_color_changed(); wxGetApp().UpdateDarkUI(this); wxGetApp().UpdateDarkUI(m_treectrl); #endif @@ -994,6 +964,7 @@ void Tab::sys_color_changed() m_active_page->sys_color_changed(); Layout(); + Refresh(); } Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const @@ -1257,7 +1228,7 @@ void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) auto detach_preset_btn = [this](wxWindow* parent) { m_detach_preset_btn = new ScalableButton(parent, wxID_ANY, "lock_open_sys", _L("Detach from system preset"), - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_detach_preset_btn; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -1670,14 +1641,14 @@ void TabPrint::build() option.opt.height = 5;//50; optgroup->append_single_option_line(option); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); option = optgroup->get_option("notes"); option.opt.full_width = true; option.opt.height = 25;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { @@ -1917,7 +1888,7 @@ void TabFilament::build() m_presets = &m_preset_bundle->filaments; load_initial_data(); - auto page = add_options_page(L("Filament"), "spool.png"); + auto page = add_options_page(L("Filament"), "spool"); auto optgroup = page->new_optgroup(L("Filament")); optgroup->append_single_option_line("filament_colour"); optgroup->append_single_option_line("filament_diameter"); @@ -2057,7 +2028,7 @@ void TabFilament::build() option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); optgroup->label_width = 0; option = optgroup->get_option("filament_notes"); @@ -2065,7 +2036,7 @@ void TabFilament::build() option.opt.height = notes_field_height;// 250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { return compatible_widget_create(parent, m_compatible_printers); @@ -2449,14 +2420,14 @@ void TabPrinter::build_fff() option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); option = optgroup->get_option("printer_notes"); option.opt.full_width = true; option.opt.height = notes_field_height;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); build_preset_description_line(optgroup.get()); @@ -2526,14 +2497,14 @@ void TabPrinter::build_sla() const int notes_field_height = 25; // 250 - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); option = optgroup->get_option("printer_notes"); option.opt.full_width = true; option.opt.height = notes_field_height;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); build_preset_description_line(optgroup.get()); @@ -2791,7 +2762,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) auto reset_to_filament_color = [this, extruder_idx](wxWindow* parent) { m_reset_to_filament_color = new ScalableButton(parent, wxID_ANY, "undo", _L("Reset to Filament Color"), - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_reset_to_filament_color; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetSize(btn->GetBestSize()); @@ -3758,8 +3729,7 @@ void Tab::toggle_show_hide_incompatible() void Tab::update_show_hide_incompatible_button() { - m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? - m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); + m_btn_hide_incompatible_presets->SetBitmap(*get_bmp_bundle(m_show_incompatible_presets ? "flag_red" : "flag_green")); m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); @@ -3808,7 +3778,7 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); wxGetApp().UpdateDarkUI(deps.checkbox, false, true); deps.btn = new ScalableButton(parent, wxID_ANY, "printer", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); deps.btn->SetSize(deps.btn->GetBestSize()); @@ -4117,7 +4087,7 @@ bool SubstitutionManager::is_empty_substitutions() wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent) { auto create_btn = [parent](ScalableButton** btn, const wxString& label, const std::string& icon_name) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, " " + label + " ", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + *btn = new ScalableButton(parent, wxID_ANY, icon_name, " " + label + " ", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().normal_font()); (*btn)->SetSize((*btn)->GetBestSize()); }; @@ -4170,7 +4140,7 @@ wxSizer* TabPrint::create_substitutions_widget(wxWindow* parent) wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) { ScalableButton* btn = new ScalableButton(parent, wxID_ANY, "printer", " " + _(L("Set")) + " " + dots, - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().normal_font()); btn->SetSize(btn->GetBestSize()); @@ -4564,7 +4534,7 @@ void TabSLAMaterial::build() optgroup->append_line(line); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); optgroup->label_width = 0; Option option = optgroup->get_option("material_notes"); @@ -4572,7 +4542,7 @@ void TabSLAMaterial::build() option.opt.height = 25;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { @@ -4593,7 +4563,7 @@ void TabSLAMaterial::build() build_preset_description_line(optgroup.get()); - page = add_options_page(L("Material printing profile"), "note.png"); + page = add_options_page(L("Material printing profile"), "note"); optgroup = page->new_optgroup(L("Material printing profile")); option = optgroup->get_option("material_print_speed"); optgroup->append_single_option_line(option); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index d6f6e0112..6625f71f6 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -174,7 +174,6 @@ protected: wxBoxSizer* m_hsizer; wxBoxSizer* m_left_sizer; wxTreeCtrl* m_treectrl; - wxImageList* m_icons; wxScrolledWindow* m_page_view {nullptr}; wxBoxSizer* m_page_sizer {nullptr}; @@ -203,10 +202,6 @@ protected: ScalableButton* m_undo_to_sys_btn; ScalableButton* m_question_btn; - // Cached bitmaps. - // A "flag" icon to be displayned next to the preset name in the Tab's combo box. - ScalableBitmap m_bmp_show_incompatible_presets; - ScalableBitmap m_bmp_hide_incompatible_presets; // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. ScalableBitmap m_bmp_value_lock; ScalableBitmap m_bmp_value_unlock; @@ -505,7 +500,6 @@ public: void build_unregular_pages(bool from_initial_build = false); void on_preset_loaded() override; void init_options_list() override; - void msw_rescale() override; bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index cab978284..ec5286abe 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -115,29 +115,21 @@ wxIcon ModelNode::get_bitmap(const wxString& color) wxBitmap ModelNode::get_bitmap(const wxString& color) #endif // __linux__ { - /* It's supposed that standard size of an icon is 48px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const double em = em_unit(m_parent_win); - const int icon_width = lround(6.4 * em); - const int icon_height = lround(1.6 * em); - - BitmapCache bmp_cache; - ColorRGB rgb; - decode_color(into_u8(color), rgb); - // there is no need to scale created solid bitmap + wxBitmap bmp = get_solid_bmp_bundle(64, 16, into_u8(color))->GetBitmapFor(m_parent_win); + if (!m_toggle) + bmp = bmp.ConvertToDisabled(); #ifndef __linux__ - return bmp_cache.mksolid(icon_width, icon_height, rgb, true); + return bmp; #else wxIcon icon; - icon.CopyFromBitmap(bmp_cache.mksolid(icon_width, icon_height, rgb, true)); + icon.CopyFromBitmap(bmp); return icon; #endif // __linux__ } // option node ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value) : + m_parent_win(parent->m_parent_win), m_parent(parent), m_old_color(old_value.StartsWith("#") ? old_value : ""), m_new_color(new_value.StartsWith("#") ? new_value : ""), @@ -202,18 +194,22 @@ void ModelNode::UpdateIcons() { // update icons for the colors, if any exists if (!m_old_color.IsEmpty()) - m_old_color_bmp = get_bitmap(m_toggle ? m_old_color : wxString::FromUTF8(grey.c_str())); + m_old_color_bmp = get_bitmap(m_old_color); if (!m_new_color.IsEmpty()) - m_new_color_bmp = get_bitmap(m_toggle ? m_new_color : wxString::FromUTF8(grey.c_str())); + m_new_color_bmp = get_bitmap(m_new_color); // update main icon, if any exists if (m_icon_name.empty()) return; + wxBitmap bmp = get_bmp_bundle(m_icon_name)->GetBitmapFor(m_parent_win); + if (!m_toggle) + bmp = bmp.ConvertToDisabled(); + #ifdef __linux__ - m_icon.CopyFromBitmap(create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle)); + m_icon.CopyFromBitmap(bmp); #else - m_icon = create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle); + m_icon = bmp; #endif //__linux__ } @@ -838,7 +834,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ auto add_btn = [this, buttons, btn_font, dependent_presets](ScalableButton** btn, int& btn_id, const std::string& icon_name, Action close_act, const wxString& label, bool process_enable = true) { - *btn = new ScalableButton(this, btn_id = NewControlId(), icon_name, label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24); + *btn = new ScalableButton(this, btn_id = NewControlId(), icon_name, label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); buttons->Add(*btn, 1, wxLEFT, 5); (*btn)->SetFont(btn_font); @@ -876,7 +872,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ if (ActionButtons::SAVE & m_buttons) add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save")); - ScalableButton* cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24); + ScalableButton* cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); buttons->Add(cancel_btn, 1, wxLEFT|wxRIGHT, 5); cancel_btn->SetFont(btn_font); cancel_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CANCEL); }); @@ -1307,8 +1303,6 @@ void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) int em = em_unit(); msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id }); - for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn } ) - if (btn) btn->msw_rescale(); const wxSize& size = wxSize(70 * em, 30 * em); SetMinSize(size); @@ -1322,7 +1316,7 @@ void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) void UnsavedChangesDialog::on_sys_color_changed() { for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn } ) - btn->msw_rescale(); + btn->sys_color_changed(); // msw_rescale updates just icons, so use it m_tree->Rescale(); @@ -1720,10 +1714,16 @@ void DiffPresetDialog::on_dpi_changed(const wxRect&) const wxSize& size = wxSize(80 * em, 30 * em); SetMinSize(size); + auto rescale = [em](PresetComboBox* pcb) { + pcb->msw_rescale(); + wxSize sz = wxSize(35 * em, -1); + pcb->SetMinSize(sz); + pcb->SetSize(sz); + }; + for (auto preset_combos : m_preset_combos) { - preset_combos.presets_left->msw_rescale(); - preset_combos.equal_bmp->msw_rescale(); - preset_combos.presets_right->msw_rescale(); + rescale(preset_combos.presets_left); + rescale(preset_combos.presets_right); } m_tree->Rescale(em); @@ -1741,9 +1741,9 @@ void DiffPresetDialog::on_sys_color_changed() #endif for (auto preset_combos : m_preset_combos) { - preset_combos.presets_left->msw_rescale(); - preset_combos.equal_bmp->msw_rescale(); - preset_combos.presets_right->msw_rescale(); + preset_combos.presets_left->sys_color_changed(); + preset_combos.equal_bmp->sys_color_changed(); + preset_combos.presets_right->sys_color_changed(); } // msw_rescale updates just icons, so use it m_tree->Rescale(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 9326c9258..67890bac8 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -24,18 +24,15 @@ #ifndef __linux__ // msw_menuitem_bitmaps is used for MSW and OSX static std::map msw_menuitem_bitmaps; -#ifdef __WXMSW__ -void msw_rescale_menu(wxMenu* menu) +void sys_color_changed_menu(wxMenu* menu) { - return; struct update_icons { static void run(wxMenuItem* item) { const auto it = msw_menuitem_bitmaps.find(item->GetId()); if (it != msw_menuitem_bitmaps.end()) { -// const wxBitmap& item_icon = create_menu_bitmap(it->second); - const wxBitmapBundle& item_icon = create_menu_bitmap(it->second); - if (item_icon.IsOk()) - item->SetBitmap(item_icon); + wxBitmapBundle* item_icon = get_bmp_bundle(it->second); + if (item_icon->IsOk()) + item->SetBitmap(*item_icon); } if (item->IsSubMenu()) for (wxMenuItem *sub_item : item->GetSubMenu()->GetMenuItems()) @@ -46,36 +43,24 @@ void msw_rescale_menu(wxMenu* menu) for (wxMenuItem *item : menu->GetMenuItems()) update_icons::run(item); } -#endif /* __WXMSW__ */ -#endif /* no __WXGTK__ */ +#endif /* no __linux__ */ void enable_menu_item(wxUpdateUIEvent& evt, std::function const cb_condition, wxMenuItem* item, wxWindow* win) { const bool enable = cb_condition(); evt.Enable(enable); - -#ifdef __WXOSX__ - const auto it = msw_menuitem_bitmaps.find(item->GetId()); - if (it != msw_menuitem_bitmaps.end()) - { - const wxBitmap& item_icon = create_scaled_bitmap(it->second, win, 16, !enable); - if (item_icon.IsOk()) - item->SetBitmap(item_icon); - } -#endif // __WXOSX__ } wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, -// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler, - std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler, + std::function cb, wxBitmapBundle* icon, wxEvtHandler* event_handler, std::function const cb_condition, wxWindow* parent, int insert_pos/* = wxNOT_FOUND*/) { if (id == wxID_ANY) id = wxNewId(); auto *item = new wxMenuItem(menu, id, string, description); - if (icon.IsOk()) { - item->SetBitmap(icon); + if (icon && icon->IsOk()) { + item->SetBitmap(*icon); } if (insert_pos == wxNOT_FOUND) menu->Append(item); @@ -104,14 +89,12 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const if (id == wxID_ANY) id = wxNewId(); -// const wxBitmap& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr - const wxBitmapBundle& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr + wxBitmapBundle* bmp = icon.empty() ? nullptr : get_bmp_bundle(icon); -//#ifdef __WXMSW__ -#ifndef __WXGTK__ - if (bmp.IsOk()) +#ifndef __linux__ + if (bmp && bmp->IsOk()) msw_menuitem_bitmaps[id] = icon; -#endif /* __WXMSW__ */ +#endif /* no __linux__ */ return append_menu_item(menu, id, string, description, cb, bmp, event_handler, cb_condition, parent, insert_pos); } @@ -124,7 +107,7 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* item = new wxMenuItem(menu, id, string, description); if (!icon.empty()) { - item->SetBitmap(create_menu_bitmap(icon)); // FIXME: pass window ptr + item->SetBitmap(*get_bmp_bundle(icon)); //#ifdef __WXMSW__ #ifndef __WXGTK__ msw_menuitem_bitmaps[id] = icon; @@ -426,11 +409,34 @@ int mode_icon_px_size() #endif } -//wxBitmap create_menu_bitmap(const std::string& bmp_name) -wxBitmapBundle create_menu_bitmap(const std::string& bmp_name) +wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name_in, int px_cnt/* = 16*/) { - // return create_scaled_bitmap(bmp_name, nullptr, 16, false, "", true); - return wxBitmapBundle::FromSVGFile(Slic3r::var(bmp_name + ".svg"), wxSize(16, 16)); + static Slic3r::GUI::BitmapCache cache; + + std::string bmp_name = bmp_name_in; + boost::replace_last(bmp_name, ".png", ""); + + // Try loading an SVG first, then PNG if SVG is not found: + wxBitmapBundle* bmp = cache.from_svg(bmp_name, px_cnt, px_cnt, Slic3r::GUI::wxGetApp().dark_mode()); + if (bmp == nullptr) { + bmp = cache.from_png(bmp_name, px_cnt, px_cnt); + if (!bmp) + // Neither SVG nor PNG has been found, raise error + throw Slic3r::RuntimeError("Could not load bitmap: " + bmp_name); + } + return bmp; +} + +wxBitmapBundle* get_empty_bmp_bundle(int width, int height) +{ + static Slic3r::GUI::BitmapCache cache; + return cache.mkclear_bndl(width, height); +} + +wxBitmapBundle* get_solid_bmp_bundle(int width, int height, const std::string& color ) +{ + static Slic3r::GUI::BitmapCache cache; + return cache.mksolid_bndl(width, height, color, 1, Slic3r::GUI::wxGetApp().dark_mode()); } // win is used to get a correct em_unit value @@ -471,41 +477,19 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in, return *bmp; } -std::vector get_extruder_color_icons(bool thin_icon/* = false*/) +std::vector get_extruder_color_icons(bool thin_icon/* = false*/) { - static Slic3r::GUI::BitmapCache bmp_cache; - // Create the bitmap with color bars. - std::vector bmps; + std::vector bmps; std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); if (colors.empty()) return bmps; - /* It's supposed that standard size of an icon is 36px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const double em = Slic3r::GUI::wxGetApp().em_unit(); - const int icon_width = lround((thin_icon ? 1.6 : 3.2) * em); - const int icon_height = lround(1.6 * em); - bool dark_mode = Slic3r::GUI::wxGetApp().dark_mode(); for (const std::string& color : colors) - { - std::string bitmap_key = color + "-h" + std::to_string(icon_height) + "-w" + std::to_string(icon_width); - - wxBitmap* bitmap = bmp_cache.find(bitmap_key); - if (bitmap == nullptr) { - // Paint the color icon. - Slic3r::ColorRGB rgb; - Slic3r::decode_color(color, rgb); - // there is no neede to scale created solid bitmap - bitmap = bmp_cache.insert(bitmap_key, bmp_cache.mksolid(icon_width, icon_height, rgb, true, 1, dark_mode)); - } - bmps.emplace_back(bitmap); - } + bmps.emplace_back(get_solid_bmp_bundle(thin_icon ? 16 : 32, 16, color)); return bmps; } @@ -518,7 +502,7 @@ void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, wxSize size/* = wxDefaultSize*/, bool use_thin_icon/* = false*/) { - std::vector icons = get_extruder_color_icons(use_thin_icon); + std::vector icons = get_extruder_color_icons(use_thin_icon); if (!*ctrl) { *ctrl = new Slic3r::GUI::BitmapComboBox(parent, wxID_ANY, wxEmptyString, pos, size, 0, nullptr, wxCB_READONLY); @@ -544,7 +528,7 @@ void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, int i = 0; wxString str = _(L("Extruder")); - for (wxBitmap* bmp : icons) { + for (wxBitmapBundle* bmp : icons) { if (i == 0) { if (!first_item.empty()) (*ctrl)->Append(_(first_item), *bmp); @@ -578,7 +562,7 @@ LockButton::LockButton( wxWindow *parent, Slic3r::GUI::wxGetApp().UpdateDarkUI(this); SetBitmap(m_bmp_lock_open.bmp()); SetBitmapDisabled(m_bmp_lock_open.bmp()); - SetBitmapHover(m_bmp_lock_closed_f.bmp()); + SetBitmapCurrent(m_bmp_lock_closed_f.bmp()); //button events Bind(wxEVT_BUTTON, &LockButton::OnButton, this); @@ -607,13 +591,14 @@ void LockButton::SetLock(bool lock) } } -void LockButton::msw_rescale() +void LockButton::sys_color_changed() { - return; - m_bmp_lock_closed.msw_rescale(); - m_bmp_lock_closed_f.msw_rescale(); - m_bmp_lock_open.msw_rescale(); - m_bmp_lock_open_f.msw_rescale(); + Slic3r::GUI::wxGetApp().UpdateDarkUI(this); + + m_bmp_lock_closed.sys_color_changed(); + m_bmp_lock_closed_f.sys_color_changed(); + m_bmp_lock_open.sys_color_changed(); + m_bmp_lock_open_f.sys_color_changed(); update_button_bitmaps(); } @@ -621,7 +606,7 @@ void LockButton::msw_rescale() void LockButton::update_button_bitmaps() { SetBitmap(m_is_pushed ? m_bmp_lock_closed.bmp() : m_bmp_lock_open.bmp()); - SetBitmapHover(m_is_pushed ? m_bmp_lock_closed_f.bmp() : m_bmp_lock_open_f.bmp()); + SetBitmapCurrent(m_is_pushed ? m_bmp_lock_closed_f.bmp() : m_bmp_lock_open_f.bmp()); Refresh(); Update(); @@ -648,8 +633,7 @@ ModeButton::ModeButton( wxWindow* parent, const wxString& mode/* = wxEmptyString*/, const std::string& icon_name/* = ""*/, int px_cnt/* = 16*/) : -// ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, icon_name, px_cnt), mode, wxBU_EXACTFIT) - ScalableButton(parent, wxID_ANY, icon_name, mode, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT) + ScalableButton(parent, wxID_ANY, icon_name, mode, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT, px_cnt) { Init(mode); } @@ -759,11 +743,10 @@ void ModeSizer::set_items_border(int border) item->SetBorder(border); } -void ModeSizer::msw_rescale() +void ModeSizer::sys_color_changed() { - this->SetHGap(std::lround(m_hgap_unscaled * em_unit(m_parent))); for (size_t m = 0; m < m_mode_btns.size(); m++) - m_mode_btns[m]->msw_rescale(); + m_mode_btns[m]->sys_color_changed(); } // ---------------------------------------------------------------------------- @@ -803,40 +786,13 @@ ScalableBitmap::ScalableBitmap( wxWindow *parent, m_parent(parent), m_icon_name(icon_name), m_px_cnt(px_cnt) { - m_bmp = create_scaled_bitmap(icon_name, parent, px_cnt, grayscale); + m_bmp = *get_bmp_bundle(icon_name, px_cnt); + m_bitmap = m_bmp.GetBitmapFor(m_parent); } -wxSize ScalableBitmap::GetBmpSize() const +void ScalableBitmap::sys_color_changed() { -#ifdef __APPLE__ - return m_bmp.GetScaledSize(); -#else - return m_bmp.GetSize(); -#endif -} - -int ScalableBitmap::GetBmpWidth() const -{ -#ifdef __APPLE__ - return m_bmp.GetScaledWidth(); -#else - return m_bmp.GetWidth(); -#endif -} - -int ScalableBitmap::GetBmpHeight() const -{ -#ifdef __APPLE__ - return m_bmp.GetScaledHeight(); -#else - return m_bmp.GetHeight(); -#endif -} - - -void ScalableBitmap::msw_rescale() -{ - m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt, m_grayscale); + m_bmp = *get_bmp_bundle(m_icon_name, m_px_cnt); } // ---------------------------------------------------------------------------- @@ -850,11 +806,9 @@ ScalableButton::ScalableButton( wxWindow * parent, const wxSize& size /* = wxDefaultSize*/, const wxPoint& pos /* = wxDefaultPosition*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/, - bool use_default_disabled_bitmap/* = false*/, int bmp_px_cnt/* = 16*/) : m_parent(parent), m_current_icon_name(icon_name), - m_use_default_disabled_bitmap (use_default_disabled_bitmap), m_px_cnt(bmp_px_cnt), m_has_border(!(style & wxNO_BORDER)) { @@ -862,10 +816,7 @@ ScalableButton::ScalableButton( wxWindow * parent, Slic3r::GUI::wxGetApp().UpdateDarkUI(this); if (!icon_name.empty()) { -// SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); - SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name + ".svg"), wxSize(m_px_cnt, m_px_cnt))); - //if (m_use_default_disabled_bitmap) - // SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + SetBitmap(*get_bmp_bundle(icon_name, m_px_cnt)); if (!label.empty()) SetBitmapMargins(int(0.5* em_unit(parent)), 0); } @@ -907,14 +858,12 @@ bool ScalableButton::SetBitmap_(const std::string& bmp_name) if (m_current_icon_name.empty()) return false; -// wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); - wxBitmapBundle bmp = wxBitmapBundle::FromSVGFile(Slic3r::var(m_current_icon_name + ".svg"), wxSize(16, 16)); + wxBitmapBundle bmp = *get_bmp_bundle(m_current_icon_name, m_px_cnt); SetBitmap(bmp); SetBitmapCurrent(bmp); SetBitmapPressed(bmp); SetBitmapFocus(bmp); - if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + SetBitmapDisabled(bmp); return true; } @@ -933,38 +882,21 @@ int ScalableButton::GetBitmapHeight() #endif } -void ScalableButton::UseDefaultBitmapDisabled() -{ - m_use_default_disabled_bitmap = true; - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); -} - -void ScalableButton::msw_rescale() +void ScalableButton::sys_color_changed() { Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border); -// if (!m_current_icon_name.empty()) { - if (0) { - wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); - SetBitmap(bmp); - SetBitmapCurrent(bmp); - SetBitmapPressed(bmp); - SetBitmapFocus(bmp); - if (!m_disabled_icon_name.empty()) - SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); - else if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); - } - - if (m_width > 0 || m_height>0) - { - const int em = em_unit(m_parent); - wxSize size(m_width * em, m_height * em); - SetMinSize(size); - } + wxBitmapBundle bmp = *get_bmp_bundle(m_current_icon_name, m_px_cnt); + SetBitmap(bmp); + SetBitmapCurrent(bmp); + SetBitmapPressed(bmp); + SetBitmapFocus(bmp); + if (!m_disabled_icon_name.empty()) + SetBitmapDisabled(*get_bmp_bundle(m_disabled_icon_name, m_px_cnt)); + if (!GetLabelText().IsEmpty()) + SetBitmapMargins(int(0.5 * em_unit(m_parent)), 0); } - // ---------------------------------------------------------------------------- // BlinkingBitmap // ---------------------------------------------------------------------------- @@ -975,13 +907,6 @@ BlinkingBitmap::BlinkingBitmap(wxWindow* parent, const std::string& icon_name) : bmp = ScalableBitmap(parent, icon_name); } -void BlinkingBitmap::msw_rescale() -{ - bmp.msw_rescale(); - this->SetSize(bmp.GetBmpSize()); - this->SetMinSize(bmp.GetBmpSize()); -} - void BlinkingBitmap::invalidate() { this->SetBitmap(wxNullBitmap); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 8387551f2..a22622d39 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -16,15 +16,14 @@ #include -#ifdef __WXMSW__ -void msw_rescale_menu(wxMenu* menu); -#else /* __WXMSW__ */ -inline void msw_rescale_menu(wxMenu* /* menu */) {} -#endif /* __WXMSW__ */ +#ifndef __linux__ +void sys_color_changed_menu(wxMenu* menu); +#else +inline void sys_color_changed_menu(wxMenu* /* menu */) {} +#endif // no __linux__ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, -// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr, - std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler = nullptr, + std::function cb, wxBitmapBundle* icon, wxEvtHandler* event_handler = nullptr, std::function const cb_condition = []() { return true;}, wxWindow* parent = nullptr, int insert_pos = wxNOT_FOUND); wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr, @@ -51,15 +50,16 @@ void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector< int em_unit(wxWindow* win); int mode_icon_px_size(); -//wxBitmap create_menu_bitmap(const std::string& bmp_name); -wxBitmapBundle create_menu_bitmap(const std::string& bmp_name); +wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name, int px_cnt = 16); +wxBitmapBundle* get_empty_bmp_bundle(int width, int height); +wxBitmapBundle* get_solid_bmp_bundle(int width, int height, const std::string& color); wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr, const int px_cnt = 16, const bool grayscale = false, const std::string& new_color = std::string(), // color witch will used instead of orange const bool menu_bitmap = false); -std::vector get_extruder_color_icons(bool thin_icon = false); +std::vector get_extruder_color_icons(bool thin_icon = false); namespace Slic3r { namespace GUI { @@ -148,21 +148,28 @@ public: ~ScalableBitmap() {} - wxSize GetBmpSize() const; - int GetBmpWidth() const; - int GetBmpHeight() const; + void sys_color_changed(); - void msw_rescale(); + const wxBitmapBundle& bmp() const { return m_bmp; } + wxBitmap get_bitmap() { return m_bmp.GetBitmapFor(m_parent); } + wxWindow* parent() const { return m_parent;} + const std::string& name() const { return m_icon_name; } + int px_cnt() const { return m_px_cnt;} - const wxBitmap& bmp() const { return m_bmp; } - wxBitmap& bmp() { return m_bmp; } - const std::string& name() const{ return m_icon_name; } - - int px_cnt()const {return m_px_cnt;} + wxSize GetSize() const { +#ifdef __APPLE__ + return m_bmp.GetDefaultSize(); +#else + return m_bmp.GetPreferredBitmapSizeFor(m_parent); +#endif + } + int GetWidth() const { return GetSize().GetWidth(); } + int GetHeight() const { return GetSize().GetHeight(); } private: wxWindow* m_parent{ nullptr }; - wxBitmap m_bmp = wxBitmap(); + wxBitmapBundle m_bmp = wxBitmapBundle(); + wxBitmap m_bitmap = wxBitmap(); std::string m_icon_name = ""; int m_px_cnt {16}; bool m_grayscale {false}; @@ -192,7 +199,7 @@ public: void enable() { m_disabled = false; } void disable() { m_disabled = true; } - void msw_rescale(); + void sys_color_changed(); protected: void update_button_bitmaps(); @@ -224,7 +231,6 @@ public: const wxSize& size = wxDefaultSize, const wxPoint& pos = wxDefaultPosition, long style = wxBU_EXACTFIT | wxNO_BORDER, - bool use_default_disabled_bitmap = false, int bmp_px_cnt = 16); ScalableButton( @@ -240,9 +246,8 @@ public: bool SetBitmap_(const std::string& bmp_name); void SetBitmapDisabled_(const ScalableBitmap &bmp); int GetBitmapHeight(); - void UseDefaultBitmapDisabled(); - void msw_rescale(); + void sys_color_changed(); private: wxWindow* m_parent { nullptr }; @@ -251,8 +256,6 @@ private: int m_width {-1}; // should be multiplied to em_unit int m_height{-1}; // should be multiplied to em_unit - bool m_use_default_disabled_bitmap {false}; - // bitmap dimensions int m_px_cnt{ 16 }; bool m_has_border {false}; @@ -318,7 +321,7 @@ public: void set_items_flag(int flag); void set_items_border(int border); - void msw_rescale(); + void sys_color_changed(); const std::vector& get_btns() { return m_mode_btns; } private: @@ -366,12 +369,11 @@ public: ~BlinkingBitmap() {} - void msw_rescale(); void invalidate(); void activate(); void blink(); - const wxBitmap& get_bmp() const { return bmp.bmp(); } + const wxBitmapBundle& get_bmp() const { return bmp.bmp(); } private: ScalableBitmap bmp; From ae08819a2430e0764fab69cc0d03c50820e046a8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 3 Jun 2022 12:49:21 +0200 Subject: [PATCH 41/97] Using of wxWidgets 3.1.6 WIP: Linux/OSX specific fixes OSX specific: Fixed get_mouse_position_in_control(). + Use GetItemRect() to calculation of the position and size for Extruder selector Linux specific: * Use just 1.0 scale for wxBitmapComboboxes under GTK3 and gtk3 * GTK2 specific: use GTK2 doesn't suppost get_scale, so scale bitmap size form em_unit() --- src/slic3r/GUI/BitmapCache.cpp | 129 ++++------------------------ src/slic3r/GUI/BitmapCache.hpp | 7 -- src/slic3r/GUI/ExtraRenderers.cpp | 23 +++-- src/slic3r/GUI/GUI_ObjectList.cpp | 26 ++++-- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/OG_CustomCtrl.cpp | 6 +- src/slic3r/GUI/PresetComboBoxes.cpp | 9 +- src/slic3r/GUI/wxExtensions.cpp | 27 ++++-- src/slic3r/GUI/wxExtensions.hpp | 6 +- 9 files changed, 85 insertions(+), 150 deletions(-) diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index d2bf98b5b..147615277 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -65,6 +65,8 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec wxVector bitmaps; std::set scales = {1.0}; +#ifndef __linux__ + #ifdef __APPLE__ scales.emplace(m_scale); #else @@ -73,12 +75,14 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec scales.emplace(wxDisplay(disp).GetScaleFactor()); #endif +#endif // !__linux__ + for (double scale : scales) { size_t width = 0; size_t height = 0; for (const wxBitmapBundle* bmp_bndl : bmps) { #ifdef __APPLE__ - wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(1.0); + wxSize size = bmp_bndl->GetDefaultSize(); #else wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(scale); #endif @@ -98,7 +102,7 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec memset(image.GetAlpha(), 0, width * height); size_t x = 0; for (const wxBitmapBundle* bmp_bndl : bmps) { - wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetPreferredBitmapSizeAtScale(scale)); + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize()); if (bmp.GetWidth() > 0) { if (bmp.GetDepth() == 32) { wxAlphaPixelData data(bmp); @@ -138,7 +142,7 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec } } } - x += bmp.GetWidth(); + x += bmp.GetScaledWidth(); } bitmaps.push_back(* this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)))); @@ -156,8 +160,8 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec if (bmp.GetWidth() > 0) memDC.DrawBitmap(bmp, x, 0, true); -#ifdef __APPLE__ // we should "move" with step equal to non-scaled width +#ifdef __APPLE__ x += bmp.GetScaledWidth(); #else x += bmp.GetWidth(); @@ -262,110 +266,6 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp return bitmap; } -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2) -{ - // Copying the wxBitmaps is cheap as the bitmap's content is reference counted. - const wxBitmap bmps[2] = { bmp, bmp2 }; - return this->insert(bitmap_key, bmps, bmps + 2); -} - -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3) -{ - // Copying the wxBitmaps is cheap as the bitmap's content is reference counted. - const wxBitmap bmps[3] = { bmp, bmp2, bmp3 }; - return this->insert(bitmap_key, bmps, bmps + 3); -} - -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end) -{ - size_t width = 0; - size_t height = 0; - for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { -#ifdef __APPLE__ - width += bmp->GetScaledWidth(); - height = std::max(height, bmp->GetScaledHeight()); -#else - width += bmp->GetWidth(); - height = std::max(height, bmp->GetHeight()); -#endif - } - -#ifdef __WXGTK2__ - // Broken alpha workaround - wxImage image(width, height); - image.InitAlpha(); - // Fill in with a white color. - memset(image.GetData(), 0x0ff, width * height * 3); - // Fill in with full transparency. - memset(image.GetAlpha(), 0, width * height); - size_t x = 0; - for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { - if (bmp->GetWidth() > 0) { - if (bmp->GetDepth() == 32) { - wxAlphaPixelData data(*const_cast(bmp)); - //FIXME The following method is missing from wxWidgets 3.1.1. - // It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha(). - //data.UseAlpha(); - if (data) { - for (int r = 0; r < bmp->GetHeight(); ++ r) { - wxAlphaPixelData::Iterator src(data); - src.Offset(data, 0, r); - unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3; - unsigned char *dst_alpha = image.GetAlpha() + x + r * width; - for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) { - *dst_pixels ++ = src.Red(); - *dst_pixels ++ = src.Green(); - *dst_pixels ++ = src.Blue(); - *dst_alpha ++ = src.Alpha(); - } - } - } - } else if (bmp->GetDepth() == 24) { - wxNativePixelData data(*const_cast(bmp)); - if (data) { - for (int r = 0; r < bmp->GetHeight(); ++ r) { - wxNativePixelData::Iterator src(data); - src.Offset(data, 0, r); - unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3; - unsigned char *dst_alpha = image.GetAlpha() + x + r * width; - for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) { - *dst_pixels ++ = src.Red(); - *dst_pixels ++ = src.Green(); - *dst_pixels ++ = src.Blue(); - *dst_alpha ++ = wxALPHA_OPAQUE; - } - } - } - } - } - x += bmp->GetWidth(); - } - return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); - -#else - - wxBitmap *bitmap = this->insert(bitmap_key, width, height); - wxMemoryDC memDC; - memDC.SelectObject(*bitmap); - memDC.SetBackground(*wxTRANSPARENT_BRUSH); - memDC.Clear(); - size_t x = 0; - for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { - if (bmp->GetWidth() > 0) - memDC.DrawBitmap(*bmp, x, 0, true); -#ifdef __APPLE__ - // we should "move" with step equal to non-scaled width - x += bmp->GetScaledWidth(); -#else - x += bmp->GetWidth(); -#endif - } - memDC.SelectObject(wxNullBitmap); - return bitmap; - -#endif -} - wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/) { wxImage image(width, height); @@ -492,7 +392,6 @@ wxBitmapBundle* BitmapCache::from_svg(const std::string& bitmap_name, unsigned t std::string bitmap_key = bitmap_name + (target_height != 0 ? "-h" + std::to_string(target_height) : "-w" + std::to_string(target_width)) -// + (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "") + (dark_mode ? "-dm" : "") + new_color; @@ -594,9 +493,9 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale); } - +/* //we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap -wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false*/, size_t border_width /*= 0*/, bool dark_mode/* = false*/) +wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false* /, size_t border_width /*= 0* /, bool dark_mode/* = false* /) { double scale = suppress_scaling ? 1.0f : m_scale; width *= scale; @@ -638,13 +537,15 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); } - +*/ //we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width /*= 0*/, bool dark_mode/* = false*/) { wxVector bitmaps; std::set scales = { 1.0 }; +#ifndef __linux__ + #ifdef __APPLE__ scales.emplace(m_scale); #else @@ -653,6 +554,8 @@ wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned scales.emplace(wxDisplay(disp).GetScaleFactor()); #endif +#endif // !__linux__ + for (double scale : scales) { size_t width = width_in * scale; size_t height = height_in * scale; @@ -698,7 +601,7 @@ wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned wxBitmapBundle* BitmapCache::mksolid_bndl(size_t width, size_t height, const std::string& color, size_t border_width, bool dark_mode) { - std::string bitmap_key = (color.empty() ? "empty-w" : color) + "-h" + std::to_string(height) + "-w" + std::to_string(width) + (dark_mode ? "-dm" : ""); + std::string bitmap_key = (color.empty() ? "empty" : color) + "-h" + std::to_string(height) + "-w" + std::to_string(width) + (dark_mode ? "-dm" : ""); wxBitmapBundle* bndl = nullptr; auto it = m_bndl_map.find(bitmap_key); diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 28058d94b..6ba9ae15b 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -37,10 +37,6 @@ public: wxBitmap* insert(const std::string &name, size_t width, size_t height, double scale = -1.0); wxBitmap* insert(const std::string &name, const wxBitmap &bmp); - wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); - wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); - wxBitmap* insert(const std::string &name, const std::vector &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); } - wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end); wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false); // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero. @@ -58,9 +54,6 @@ public: // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = ""); - wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false); - wxBitmap mksolid(size_t width, size_t height, const ColorRGB& rgb, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } - wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT, true, 0); } wxBitmapBundle mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width = 0, bool dark_mode = false); wxBitmapBundle* mksolid_bndl(size_t width, size_t height, const std::string& color = std::string(), size_t border_width = 0, bool dark_mode = false); wxBitmapBundle* mkclear_bndl(size_t width, size_t height) { return mksolid_bndl(width, height); } diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 9bccb6b63..0368a2fe2 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -33,6 +33,15 @@ wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) +static wxSize get_size(const wxBitmap& icon) +{ +#ifdef __WIN32__ + return icon.GetSize(); +#else + return icon.GetScaledSize(); +#endif +} + // --------------------------------------------------------- // BitmapTextRenderer // --------------------------------------------------------- @@ -124,11 +133,7 @@ bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) const wxBitmap& icon = m_value.GetBitmap(); if (icon.IsOk()) { -#ifdef __APPLE__ - wxSize icon_sz = icon.GetScaledSize(); -#else - wxSize icon_sz = icon.GetSize(); -#endif + wxSize icon_sz = get_size(icon); dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2); xoffset = icon_sz.x + 4; } @@ -264,11 +269,13 @@ bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) const wxBitmap& icon = m_value.GetBitmap(); if (icon.IsOk()) { - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); - xoffset = icon.GetWidth() + 4; + wxSize icon_sz = get_size(icon); + + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.GetHeight()) / 2); + xoffset = icon_sz.GetWidth() + 4; if (rect.height==0) - rect.height= icon.GetHeight(); + rect.height= icon_sz.GetHeight(); } #ifdef _WIN32 diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 1171149b6..da8f83388 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -971,12 +971,11 @@ void ObjectList::extruder_editing() if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject))) return; - const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5; - - wxPoint pos = this->get_mouse_position_in_control(); - wxSize size = wxSize(column_width, -1); - pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5; - pos.y -= GetTextExtent("m").y; + wxRect rect = this->GetItemRect(item, GetColumn(colExtruder)); + wxPoint pos = rect.GetPosition(); + pos.y -= 4; + wxSize size = rect.GetSize(); + size.SetWidth(size.GetWidth() + 8); apply_extruder_selector(&m_extruder_editor, this, L("default"), pos, size); @@ -2433,6 +2432,21 @@ bool ObjectList::can_merge_to_single_object() const return (*m_objects)[obj_idx]->volumes.size() > 1; } +wxPoint ObjectList::get_mouse_position_in_control() const +{ + wxPoint pt = wxGetMousePosition() - this->GetScreenPosition(); + +#ifdef __APPLE__ + // Workaround for OSX. From wxWidgets 3.1.6 Hittest doesn't respect to the header of wxDataViewCtrl + if (wxDataViewItem top_item = this->GetTopItem(); top_item.IsOk()) { + auto rect = this->GetItemRect(top_item, this->GetColumn(0)); + pt.y -= rect.y; + } +#endif // __APPLE__ + + return pt; +} + // NO_PARAMETERS function call means that changed object index will be determine from Selection() void ObjectList::changed_object(const int obj_idx/* = -1*/) const { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b9b816b7b..c838203ae 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -280,7 +280,7 @@ public: bool can_merge_to_multipart_object() const; bool can_merge_to_single_object() const; - wxPoint get_mouse_position_in_control() const { return wxGetMousePosition() - this->GetScreenPosition(); } + wxPoint get_mouse_position_in_control() const; wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; ModelConfig& get_item_config(const wxDataViewItem& item) const; diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index c202de5e2..2c367d62a 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -21,10 +21,10 @@ static bool is_point_in_rect(const wxPoint& pt, const wxRect& rect) static wxSize get_bitmap_size(const wxBitmapBundle* bmp, wxWindow* parent) { -#ifdef __APPLE__ - return bmp->GetDefaultSize(); -#else +#ifdef __WIN32__ return bmp->GetBitmapFor(parent).GetSize(); +#else + return bmp->GetDefaultSize(); #endif } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 93a5fe433..597c30312 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -389,14 +389,14 @@ void PresetComboBox::sys_color_changed() void PresetComboBox::fill_width_height() { - icon_height = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetHeight(); - norm_icon_width = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetWidth(); - - null_icon_width = 2 * norm_icon_width; + icon_height = 16; + norm_icon_width = 16; thin_icon_width = 8; wide_icon_width = norm_icon_width + thin_icon_width; + null_icon_width = 2 * norm_icon_width; + space_icon_width = 2; thin_space_icon_width = 4; wide_space_icon_width = 6; @@ -484,6 +484,7 @@ wxBitmapBundle* PresetComboBox::get_bmp( std::string bitmap_key, const std::str wxBitmapBundle PresetComboBox::NullBitmapBndl() { + assert(null_icon_width > 0); return *get_empty_bmp_bundle(null_icon_width, icon_height); } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 67890bac8..c397b4b39 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -108,10 +108,10 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* item = new wxMenuItem(menu, id, string, description); if (!icon.empty()) { item->SetBitmap(*get_bmp_bundle(icon)); -//#ifdef __WXMSW__ -#ifndef __WXGTK__ + +#ifndef __linux__ msw_menuitem_bitmaps[id] = icon; -#endif /* __WXMSW__ */ +#endif // no __linux__ } item->SetSubMenu(sub_menu); @@ -409,8 +409,19 @@ int mode_icon_px_size() #endif } +#ifdef __WXGTK2__ +static int scale() +{ + return int(em_unit(nullptr) * 0.1f + 0.5f); +} +#endif // __WXGTK2__ + wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name_in, int px_cnt/* = 16*/) { +#ifdef __WXGTK2__ + px_cnt *= scale(); +#endif // __WXGTK2__ + static Slic3r::GUI::BitmapCache cache; std::string bmp_name = bmp_name_in; @@ -430,13 +441,21 @@ wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name_in, int px_cnt/* = 16 wxBitmapBundle* get_empty_bmp_bundle(int width, int height) { static Slic3r::GUI::BitmapCache cache; +#ifdef __WXGTK2__ + return cache.mkclear_bndl(width * scale(), height * scale()); +#else return cache.mkclear_bndl(width, height); +#endif // __WXGTK2__ } wxBitmapBundle* get_solid_bmp_bundle(int width, int height, const std::string& color ) { static Slic3r::GUI::BitmapCache cache; +#ifdef __WXGTK2__ + return cache.mksolid_bndl(width * scale(), height * scale(), color, 1, Slic3r::GUI::wxGetApp().dark_mode()); +#else return cache.mksolid_bndl(width, height, color, 1, Slic3r::GUI::wxGetApp().dark_mode()); +#endif // __WXGTK2__ } // win is used to get a correct em_unit value @@ -486,8 +505,6 @@ std::vector get_extruder_color_icons(bool thin_icon/* = false*/ if (colors.empty()) return bmps; - bool dark_mode = Slic3r::GUI::wxGetApp().dark_mode(); - for (const std::string& color : colors) bmps.emplace_back(get_solid_bmp_bundle(thin_icon ? 16 : 32, 16, color)); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index a22622d39..db05af9eb 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -157,10 +157,10 @@ public: int px_cnt() const { return m_px_cnt;} wxSize GetSize() const { -#ifdef __APPLE__ - return m_bmp.GetDefaultSize(); -#else +#ifdef __WIN32__ return m_bmp.GetPreferredBitmapSizeFor(m_parent); +#else + return m_bmp.GetDefaultSize(); #endif } int GetWidth() const { return GetSize().GetWidth(); } From b6e6be27c073ed5c316e6796e153b1c63916b179 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jun 2022 15:57:56 +0200 Subject: [PATCH 42/97] wxWidgets.cmake: Changed url and SHA to use next commit in our wxWidgets patch --- deps/wxWidgets/wxWidgets.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 4a0875d62..96d56c0bc 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() prusaslicer_add_cmake_project(wxWidgets - URL https://github.com/prusa3d/wxWidgets/archive/5412ac15586da3ecb6952fcc875d2a23366c998f.zip - URL_HASH SHA256=85a6e13152289fbf1ea51f221fbe1452e7914bbaa665b89536780810e93948a6 + URL https://github.com/prusa3d/wxWidgets/archive/e616df45b3a8aedc31beb5540df793dd1f0f3914.zip + URL_HASH SHA256=30bc6cad64dce5cdc755e3a4119cfeb24c12d43279e1e062d8ac350d3880f315 DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS -DwxBUILD_PRECOMP=ON From 40afbe6371b2057c16100f6e80b8bb7d6c6893ae Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jan 2022 13:24:37 +0100 Subject: [PATCH 43/97] Update to GLEW 2.2 to prevent initialization crash with wx >= 3.1.6 Revert "Revert to GLEW 2.1 as most Linux distros as using that" This reverts commit 46c8f82f24127dc992e904b9b3f1c75a719f0491. --- deps/GLEW/GLEW.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/GLEW/GLEW.cmake b/deps/GLEW/GLEW.cmake index ae2832577..76ffc0a8d 100644 --- a/deps/GLEW/GLEW.cmake +++ b/deps/GLEW/GLEW.cmake @@ -4,8 +4,8 @@ find_package(OpenGL QUIET REQUIRED) prusaslicer_add_cmake_project( GLEW - URL https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0.zip - URL_HASH SHA256=2700383d4de2455f06114fbaf872684f15529d4bdc5cdea69b5fb0e9aa7763f1 + URL https://sourceforge.net/projects/glew/files/glew/2.2.0/glew-2.2.0.zip + URL_HASH SHA256=a9046a913774395a095edcc0b0ac2d81c3aacca61787b39839b941e9be14e0d4 SOURCE_SUBDIR build/cmake CMAKE_ARGS -DBUILD_UTILS=OFF From 0bc707e540d77d576405bf799f3635973ce1d55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 27 Jun 2022 09:25:38 +0200 Subject: [PATCH 44/97] Tried to disable EGL. --- deps/wxWidgets/wxWidgets.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 96d56c0bc..e2d48ce65 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -37,6 +37,7 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_EXPAT=sys -DwxUSE_LIBSDL=OFF -DwxUSE_XTEST=OFF + -DwxUSE_GLCANVAS_EGL=OFF ) if (MSVC) From 3d03ef015f435852fd1ae1961471c2e8aade77c6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 27 Jun 2022 14:27:26 +0200 Subject: [PATCH 45/97] OSX specific: Fixed a warning --- src/slic3r/GUI/GUI_App.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 0412bca21..42e325de7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2088,7 +2088,15 @@ bool GUI_App::load_language(wxString language, bool initial) { // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. wxLocale temp_locale; +#ifdef __WXOSX__ + // ysFIXME - temporary workaround till it isn't fixed in wxWidgets: + // Use English as an initial language, because of under OSX it try to load "inappropriate" language for wxLANGUAGE_DEFAULT. + // For example in our case it's trying to load "en_CZ" and as a result PrusaSlicer catch warning message. + // But wxWidgets guys work on it. + temp_locale.Init(wxLANGUAGE_ENGLISH); +#else temp_locale.Init(); +#endif // __WXOSX__ // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer From 4b214598a2375109482bf9c79c9d3df2b8c00dfd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 29 Jun 2022 13:42:10 +0200 Subject: [PATCH 46/97] After merge fixes --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 45 ++++++-------------------- src/slic3r/GUI/ObjectDataViewModel.hpp | 6 ++-- 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 350f8cdc0..972598b73 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -980,7 +980,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) // for (Axis axis : {X, Y, Z}) // render_rotation_input(axis); - // m_imgui->text(_L("")); + // m_imgui->text(_L("°")); // ImGui::Separator(); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 23668ee99..a6a767faa 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -58,7 +58,6 @@ const std::map INFO_ITEMS{ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmapBundle& bmp, const wxString& extruder, const int idx/* = -1*/) : m_parent(parent), @@ -319,7 +318,7 @@ ObjectDataViewModel::ObjectDataViewModel() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = *get_bmp_bundle(WarningIcon); m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon); - m_lock_bmp = create_scaled_bitmap(LockIcon); + m_lock_bmp = *get_bmp_bundle(LockIcon); for (auto item : INFO_ITEMS) m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name); @@ -333,14 +332,13 @@ ObjectDataViewModel::~ObjectDataViewModel() m_bitmap_cache = nullptr; } -//wxBitmapBundle& ObjectDataViewModel::GetWarningBitmap(const std::string& warning_icon_name) void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node) { int vol_type = static_cast(node->GetVolumeType()); bool is_volume_node = vol_type >= 0; if (!node->has_warning_icon() && !node->has_lock()) { - node->SetBitmap(is_volume_node ? m_volume_bmps[vol_type] : m_empty_bmp); + node->SetBitmap(is_volume_node ? *m_volume_bmps.at(vol_type) : m_empty_bmp); return; } @@ -351,18 +349,18 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node) scaled_bitmap_name += LockIcon; if (is_volume_node) scaled_bitmap_name += std::to_string(vol_type); - scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm"); + scaled_bitmap_name += (wxGetApp().dark_mode() ? "-dm" : "-lm"); - wxBitmap* bmp = m_bitmap_cache->find(scaled_bitmap_name); + wxBitmapBundle* bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name); if (!bmp) { - std::vector bmps; + std::vector bmps; if (node->has_warning_icon()) - bmps.emplace_back(node->warning_icon_name() == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp); + bmps.emplace_back(node->warning_icon_name() == WarningIcon ? &m_warning_bmp : &m_warning_manifold_bmp); if (node->has_lock()) - bmps.emplace_back(m_lock_bmp); + bmps.emplace_back(&m_lock_bmp); if (is_volume_node) bmps.emplace_back(m_volume_bmps[vol_type]); - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps); } node->SetBitmap(*bmp); @@ -1716,7 +1714,7 @@ void ObjectDataViewModel::UpdateBitmaps() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = *get_bmp_bundle(WarningIcon); m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon); - m_lock_bmp = create_scaled_bitmap(LockIcon); + m_lock_bmp = *get_bmp_bundle(LockIcon); for (auto item : INFO_ITEMS) m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name); @@ -1751,28 +1749,7 @@ void ObjectDataViewModel::UpdateBitmaps() ItemChanged(item); } } -/* -wxBitmapBundle ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/) -{ - if (warning_icon_name.empty()) - return *m_volume_bmps[static_cast(vol_type)]; - std::string scaled_bitmap_name = warning_icon_name + std::to_string(static_cast(vol_type)); - scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm"); - - wxBitmapBundle *bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name); - if (bmp == nullptr) { - std::vector bmps; - - bmps.emplace_back(&GetWarningBitmap(warning_icon_name)); - bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); - - bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps); - } - - return *bmp; -} -*/ void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::string& warning_icon_name) { if (!item.IsOk()) @@ -1804,10 +1781,6 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo node->SetWarningIconName(std::string()); UpdateBitmapForNode(node); -/* if (node->GetType() & itVolume) { - node->SetWarningBitmap(*m_volume_bmps[static_cast(node->volume_type())], ""); - return; - }*/ if (unmark_object) { diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 995fb1033..87be7a4e2 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -101,7 +101,6 @@ public: ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmapBundle& bmp, const wxString& extruder, const int idx = -1 ); @@ -182,7 +181,7 @@ public: void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } void SetBitmap(const wxBitmapBundle &icon) { m_bmp = icon; } void SetExtruder(const wxString &extruder) { m_extruder = extruder; } - void SetWarningBitmap(const wxBitmapBundle& icon, const std::string& warning_icon_name) { /*m_bmp = icon; */m_warning_icon_name = warning_icon_name; } + void SetWarningIconName(const std::string& warning_icon_name) { m_warning_icon_name = warning_icon_name; } void SetLock(bool has_lock) { m_has_lock = has_lock; } const wxBitmapBundle& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } @@ -264,7 +263,7 @@ class ObjectDataViewModel :public wxDataViewModel wxBitmapBundle m_empty_bmp; wxBitmapBundle m_warning_bmp; wxBitmapBundle m_warning_manifold_bmp; - wxBitmap m_lock_bmp; + wxBitmapBundle m_lock_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -408,7 +407,6 @@ private: wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); void AddAllChildren(const wxDataViewItem& parent); -// wxBitmapBundle& GetWarningBitmap(const std::string& warning_icon_name); void UpdateBitmapForNode(ObjectDataViewModelNode* node); void UpdateBitmapForNode(ObjectDataViewModelNode* node, const std::string& warning_icon_name, bool has_lock); }; From 07d455a12566f0c37fe6d58c8c5eb7a5382ba7b2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 11 Jul 2022 09:11:06 +0200 Subject: [PATCH 47/97] Cut WIP: some UI improvements + partially reverted https://github.com/prusa3d/PrusaSlicer/commit/63890b5f8d352d3ef1228fa00b0d3932717f933d --- src/slic3r/GUI/GLCanvas3D.cpp | 10 ++- src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_ObjectList.cpp | 5 ++ src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 4 ++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 84 ++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 + src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 6 +- 7 files changed, 84 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3a5c89bea..cbf1a61d4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1382,6 +1382,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) { vol->is_active = visible; + if (!vol->is_modifier) + vol->color.a(1.f); if (instance_idx == -1) { vol->force_native_color = false; @@ -1390,9 +1392,13 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject const GLGizmosManager& gm = get_gizmos_manager(); auto gizmo_type = gm.get_current_type(); if ( (gizmo_type == GLGizmosManager::FdmSupports - || gizmo_type == GLGizmosManager::Seam) - && ! vol->is_modifier) + || gizmo_type == GLGizmosManager::Seam + || gizmo_type == GLGizmosManager::Cut) + && ! vol->is_modifier) { vol->force_neutral_color = true; + if (gizmo_type == GLGizmosManager::Cut) + vol->color.a(0.95f); + } else if (gizmo_type == GLGizmosManager::MmuSegmentation) vol->is_active = false; else diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a5b2acb32..b761deaa6 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -855,6 +855,7 @@ public: Linef3 mouse_ray(const Point& mouse_pos); bool is_mouse_dragging() const { return m_mouse.dragging; } + void set_mouse_as_dragging() { m_mouse.dragging = true; } double get_size_proportional_to_max_bed_size(double factor) const; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2c1ee7a6c..3b5af8cd9 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -11,6 +11,7 @@ #include "GalleryDialog.hpp" #include "MainFrame.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "Gizmos/GLGizmoCut.hpp" #include "OptionsGroup.hpp" #include "Tab.hpp" @@ -2579,6 +2580,10 @@ void ObjectList::part_selection_changed() GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); if (gizmos_mgr.get_current_type() != gizmo_type) gizmos_mgr.open_gizmo(gizmo_type); + if (info_type == InfoItemType::Cut) { + GLGizmoCut3D* cut = dynamic_cast(gizmos_mgr.get_current()); + cut->set_connectors_editing(); + } break; } case InfoItemType::Sinking: { break; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 6ddb804d4..ced04197d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -318,6 +318,9 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { on_start_dragging(); + // prevent change of hover_id during dragging + m_parent.set_mouse_as_dragging(); + // Let the plater know that the dragging started m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED)); m_parent.set_as_dirty(); @@ -327,6 +330,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { // when mouse cursor leave window than finish actual dragging operation bool is_leaving = mouse_event.Leaving(); if (mouse_event.Dragging()) { + m_parent.set_mouse_as_dragging(); Point mouse_coord(mouse_event.GetX(), mouse_event.GetY()); auto ray = m_parent.mouse_ray(mouse_coord); UpdateData data(ray, mouse_coord); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 972598b73..8cf7ab42f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -302,6 +302,30 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i return old_val != value; } +bool GLGizmoCut3D::render_slicer_double_input(const std::string& label, double& value_in) +{ + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width); + + float value = (float)value_in; + if (m_imperial_units) + value *= ObjectManipulation::mm_to_in; + float old_val = value; + + const BoundingBoxf3 bbox = bounding_box(); + const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0); + + m_imgui->slider_float(("##" + label).c_str(), &value, 1.0f, mean_size); + + ImGui::SameLine(); + m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + + value_in = (double)(value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0)); + return old_val != value; +} + void GLGizmoCut3D::render_move_center_input(int axis) { ImGui::AlignTextToFramePadding(); @@ -511,23 +535,26 @@ void GLGizmoCut3D::render_cut_center_graber() const Transform3d view_matrix = camera.get_view_matrix() * graber.matrix * Geometry::assemble_transform(center, angles); - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones()); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_sphere.render(); - const BoundingBoxf3 tbb = transformed_bounding_box(); - if (tbb.max.z() >= 0.0) { - view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), scale); + + if (tbb.min.z() <= 0.0) { + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), scale); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); m_cone.render(); } - if (tbb.min.z() <= 0.0) { - view_model_matrix = view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), scale); + { + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_sphere.render(); + } + + if (tbb.max.z() >= 0.0) { + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), scale); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); @@ -892,7 +919,7 @@ void GLGizmoCut3D::on_render() render_cut_center_graber(); render_cut_plane(); if (m_hover_id < m_group_id && m_mode == size_t(CutMode::cutPlanar) && !cut_line_processing()) - m_rotation_gizmo.render(); + m_rotation_gizmo.render(); } render_cut_line(); @@ -950,13 +977,25 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; + Vec3d tbb_sz = tbb.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; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Build size")); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); + //static float v = 0.; // TODO: connect to cutting plane position m_imgui->text(_L("Cut position: ")); render_move_center_input(Z); //m_imgui->input_double(unit_str, v); //v = std::clamp(v, 0.f, float(bottom+top)); if (m_imgui->button("Reset cutting plane")) { - // TODO: reset both position and rotation + set_center(bounding_box().center()); + m_rotation_gizmo.set_rotation(Vec3d::Zero()); + update_clipper(); } ////// @@ -1015,7 +1054,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(); m_imgui->disabled_begin(!m_keep_upper); m_imgui->disabled_begin(is_approx(m_rotation_gizmo.get_rotation().x(), 0.) && is_approx(m_rotation_gizmo.get_rotation().y(), 0.)); - m_imgui->checkbox(_L("Place on cut") + "##upper", m_rotate_upper); + m_imgui->checkbox(_L("Place on cut") + "##upper", m_rotate_upper); // #ysTODO implement place on cut instead of Flip? m_imgui->disabled_end(); m_imgui->disabled_end(); @@ -1029,7 +1068,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->disabled_end(); ImGui::SameLine(); m_imgui->disabled_begin(!m_keep_lower); - m_imgui->checkbox(_L("Place on cut") + "##lower", m_rotate_lower); + m_imgui->checkbox(_L("Place on cut") + "##lower", m_rotate_lower); // #ysTODO implement place on cut instead of Flip? m_imgui->disabled_end(); } @@ -1064,17 +1103,19 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) update_connector_shape(); - if (render_double_input(_u8L("Depth ratio"), m_connector_depth_ratio)) + if (render_slicer_double_input(_u8L("Depth ratio"), m_connector_depth_ratio)) for (auto& connector : connectors) connector.height = float(m_connector_depth_ratio); - if (render_double_input(_u8L("Size"), m_connector_size)) + if (render_slicer_double_input(_u8L("Size"), m_connector_size)) for (auto& connector : connectors) connector.radius = float(m_connector_size * 0.5); m_imgui->disabled_end(); - if (m_imgui->button(_L("Confirm connectors"))) + if (m_imgui->button(_L("Confirm connectors"))) { + m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_connectors_editing = false; + } } ImGui::Separator(); @@ -1111,7 +1152,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) } } -// get volume transformation regarding to the "border". Border is related from the siae of connectors +// get volume transformation regarding to the "border". Border is related from the size of connectors Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) const { bool is_prizm_dowel = m_connector_type == CutConnectorType::Dowel && m_connector_style == size_t(CutConnectorStyle::Prizm); @@ -1141,10 +1182,7 @@ void GLGizmoCut3D::render_connectors(bool picking) if (picking && ! m_connectors_editing) return; - const bool depth_test = m_connectors_editing; - if (! depth_test) - ::glDisable(GL_DEPTH_TEST); - Slic3r::ScopeGuard guard_depth_test([&](){ if (! depth_test) ::glEnable(GL_DEPTH_TEST); }); + ::glEnable(GL_DEPTH_TEST); if (cut_line_processing() || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) return; @@ -1225,7 +1263,7 @@ void GLGizmoCut3D::render_connectors(bool picking) const Transform3d volume_trafo = get_volume_transformation(mv); if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { - render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(0.5f, 0.5f, 0.5f, 1.f); + render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : /*ColorRGBA(0.5f, 0.5f, 0.5f, 1.f)*/ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); break; } render_color = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index dfaef3fb2..76ec2f7ba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -124,6 +124,7 @@ public: void put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset); void update_clipper(); void update_clipper_on_render(); + void set_connectors_editing() { m_connectors_editing = true; } BoundingBoxf3 bounding_box() const; BoundingBoxf3 transformed_bounding_box(bool revert_move = false) const; @@ -153,6 +154,7 @@ private: void set_center(const Vec3d& center); bool render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); bool render_double_input(const std::string& label, double& value_in); + bool render_slicer_double_input(const std::string& label, double& value_in); void render_move_center_input(int axis); void render_rotation_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 9e2f9e08b..d82fbd587 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -301,13 +301,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { m_bounding_box = selection.get_bounding_box(); - m_center = m_bounding_box.center(); + m_center = m_has_forced_center ? m_forced_center : m_bounding_box.center(); } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_first_volume(); m_bounding_box = v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); - m_center = v.world_matrix() * m_bounding_box.center(); + m_center = v.world_matrix() * (m_has_forced_center ? m_forced_center : m_bounding_box.center()); } else { m_bounding_box.reset(); @@ -318,7 +318,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) } const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); - m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); + m_center = inst_trafo.get_matrix_no_scaling_factor() * (m_has_forced_center ? m_forced_center : m_bounding_box.center()); } m_radius = Offset + m_bounding_box.radius(); From 15418bfb76be3830b9352b4a7d29dae5d1dd1950 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 18 Jul 2022 16:11:54 +0200 Subject: [PATCH 48/97] Cut WIP: Extensions for grabbers + Some code refactoring (RotationGizmo isn't used anymore) + A reversion from https://github.com/prusa3d/PrusaSlicer/commit/63890b5f8d352d3ef1228fa00b0d3932717f933d is putted back --- src/slic3r/GUI/GLCanvas3D.hpp | 1 - src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 461 +++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 14 +- 4 files changed, 279 insertions(+), 201 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index b761deaa6..a5b2acb32 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -855,7 +855,6 @@ public: Linef3 mouse_ray(const Point& mouse_pos); bool is_mouse_dragging() const { return m_mouse.dragging; } - void set_mouse_as_dragging() { m_mouse.dragging = true; } double get_size_proportional_to_max_bed_size(double factor) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index ced04197d..6ddb804d4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -318,9 +318,6 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { on_start_dragging(); - // prevent change of hover_id during dragging - m_parent.set_mouse_as_dragging(); - // Let the plater know that the dragging started m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED)); m_parent.set_as_dirty(); @@ -330,7 +327,6 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { // when mouse cursor leave window than finish actual dragging operation bool is_leaving = mouse_event.Leaving(); if (mouse_event.Dragging()) { - m_parent.set_mouse_as_dragging(); Point mouse_coord(mouse_event.GetX(), mouse_event.GetY()); auto ray = m_parent.mouse_ray(mouse_coord); UpdateData data(ray, mouse_coord); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8cf7ab42f..5ea85964c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -27,6 +27,29 @@ namespace GUI { static const double Margin = 20.0; static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); +const unsigned int ScaleStepsCount = 72; +const float ScaleLongTooth = 0.1f; // in percent of radius +const unsigned int SnapRegionsCount = 8; + +// Generates mesh for a line +GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) +{ + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + Vec3f start = Vec3f::Zero(); + init_data.add_vertex(beg_pos); + Vec3f stop = Vec3f::UnitZ(); + init_data.add_vertex(end_pos); + + // indices + init_data.add_line(0, 1); + return init_data; +} + #define use_grabber_extension 1 GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) @@ -34,13 +57,9 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_type (CutConnectorType::Plug) , m_connector_style (size_t(CutConnectorStyle::Prizm)) , m_connector_shape_id (size_t(CutConnectorShape::Hexagon)) - , m_rotation_gizmo(GLGizmoRotate3D(parent, "", -1)) , m_rotation_matrix(Slic3r::Matrix3d::Identity()) + , m_connectors_group_id (3) { - m_rotation_gizmo.use_only_grabbers(); - m_group_id = 3; - m_connectors_group_id = 4; - m_modes = { _u8L("Planar")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") }; @@ -63,9 +82,8 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, std::string GLGizmoCut3D::get_tooltip() const { - std::string tooltip = m_rotation_gizmo.get_tooltip(); - if (tooltip.empty() && - (m_hover_id == m_group_id || m_grabbers[0].dragging)) { + std::string tooltip; + if (m_hover_id == Z) { double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm")); const BoundingBoxf3 tbb = transformed_bounding_box(); @@ -81,6 +99,10 @@ std::string GLGizmoCut3D::get_tooltip() const } return tooltip; } + if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { + std::string axis = m_hover_id == X ? "X" : "Y"; + return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L("°"); + } return tooltip; } @@ -88,14 +110,7 @@ std::string GLGizmoCut3D::get_tooltip() const bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Moving() && !cut_line_processing()) - return false; - - if (m_rotation_gizmo.on_mouse(mouse_event)) { - if (mouse_event.LeftUp()) - on_stop_dragging(); - update_clipper(); - return true; - } + return false; if (use_grabbers(mouse_event)) return true; @@ -203,7 +218,7 @@ void GLGizmoCut3D::put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_ void GLGizmoCut3D::update_clipper() { - const Vec3d& angles = m_rotation_gizmo.get_rotation(); + const Vec3d angles = Geometry::Transformation(m_rotation_m).get_rotation(); BoundingBoxf3 box = bounding_box(); double radius = box.radius(); @@ -239,7 +254,6 @@ void GLGizmoCut3D::update_clipper_on_render() void GLGizmoCut3D::set_center(const Vec3d& center) { set_center_pos(center); - m_rotation_gizmo.set_center(m_plane_center); update_clipper(); } @@ -348,27 +362,6 @@ void GLGizmoCut3D::render_move_center_input(int axis) } } -void GLGizmoCut3D::render_rotation_input(int axis) -{ - m_imgui->text(m_axis_names[axis] + ":"); - ImGui::SameLine(); - - Vec3d rotation = m_rotation_gizmo.get_rotation(); - double value = Geometry::rad2deg(rotation[axis]); - if (value > 360) - value -= 360; - - ImGui::PushItemWidth(0.3*m_control_width); - ImGui::InputDouble(("##rotate_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); - ImGui::SameLine(); - - if (double val = Geometry::deg2rad(value); val != rotation[axis]) { - rotation[axis] = val; - m_rotation_gizmo.set_rotation(rotation); - update_clipper(); - } -} - void GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type) { ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 2*m_label_width); @@ -433,7 +426,7 @@ void GLGizmoCut3D::render_cut_plane() const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( m_plane_center, - m_rotation_gizmo.get_rotation(), + Geometry::Transformation(m_rotation_m).get_rotation(), Vec3d::Ones(), Vec3d::Ones() ); @@ -495,50 +488,61 @@ void GLGizmoCut3D::render_cut_plane() shader->stop_using(); } -void GLGizmoCut3D::render_cut_center_graber() +void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) { - ::glDisable(GL_DEPTH_TEST); - Slic3r::ScopeGuard guard([]() { ::glEnable(GL_DEPTH_TEST); }); - const Vec3d& angles = m_rotation_gizmo.get_rotation(); - m_grabbers[0].angles = angles; - m_grabbers[0].color = GRABBER_COLOR; + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); -#if use_grabber_extension - // UI experiments with grabber - - m_grabbers[0].center = m_plane_center; - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); if (!shader) return; - auto color = m_hover_id == m_group_id ? complementary(GRABBER_COLOR) : GRABBER_COLOR; + glsafe(::glLineWidth(m_hover_id == X || m_hover_id == Y ? 3.0f : 1.0f)); + + ColorRGBA color = picking ? picking_decode(BASE_ID - Z) : + m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; + m_sphere.set_color(color); m_cone.set_color(color); const Camera& camera = wxGetApp().plater()->get_camera(); const Grabber& graber = m_grabbers.front(); - const Vec3d& center = graber.center; const BoundingBoxf3 box = bounding_box(); const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 6.0); - const double size = m_dragging ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + double size = m_dragging && m_hover_id == Z ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); - const Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); - const Vec3d offset = 1.25 * size * Vec3d::UnitZ(); + Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + Vec3d offset = 1.25 * size * Vec3d::UnitZ(); shader->start_using(); shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - const Transform3d view_matrix = camera.get_view_matrix() * graber.matrix * - Geometry::assemble_transform(center, angles); + const Transform3d view_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_rotation_m; const BoundingBoxf3 tbb = transformed_bounding_box(); + const double grabber_connection_len = std::min(0.75 * m_radius, 35.0) ; - if (tbb.min.z() <= 0.0) { - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), scale); + auto render_grabber_connection = [shader, view_matrix, camera, grabber_connection_len, this](bool render, const ColorRGBA& color) + { + if (!render) + return; + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, grabber_connection_len)); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + m_grabber_connection.set_color(color); + m_grabber_connection.render(); + }; + + // render Z grabber + + render_grabber_connection((!m_dragging && m_hover_id < 0) || m_hover_id == X || m_hover_id == Y, color); + + if ((!m_dragging && m_hover_id < 0 || m_hover_id == Z) && tbb.min.z() <= 0.0) { + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); @@ -553,8 +557,84 @@ void GLGizmoCut3D::render_cut_center_graber() m_sphere.render(); } - if (tbb.max.z() >= 0.0) { - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), scale); + if ((!m_dragging && m_hover_id < 0 || m_hover_id == Z) && tbb.max.z() >= 0.0) { + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), cone_scale); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_cone.render(); + } + + // render top sphere for X/Y grabbers + + offset = Vec3d(0.0, 0.0, grabber_connection_len); + size = m_dragging && (m_hover_id == X || m_hover_id == Y) ? + double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + if ((!m_dragging && m_hover_id < 0) || m_hover_id == X || m_hover_id == Y) + { + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), size * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_sphere.set_color(m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : + m_hover_id == X ? complementary(ColorRGBA::RED() ) : ColorRGBA::GRAY()); + m_sphere.render(); + } + + // render Y grabber + + size = m_dragging && m_hover_id == Y ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + if ((!m_dragging && m_hover_id < 0) || m_hover_id == Y) + { + if (picking) + color = picking_decode(BASE_ID - Y); + else + color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); + + render_grabber_connection(m_hover_id == Y, color); + + m_cone.set_color(color); + + offset = Vec3d(1.25 * size, 0.0, grabber_connection_len); + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_cone.render(); + + offset = Vec3d(-1.25 * size, 0.0, grabber_connection_len); + view_model_matrix = view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_cone.render(); + } + + // render X grabber + + size = m_dragging && m_hover_id == X ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + if ((!m_dragging && m_hover_id < 0) || m_hover_id == X) + { + if (picking) + color = picking_decode(BASE_ID - X); + else + color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); + + render_grabber_connection(m_hover_id == X, color); + + m_cone.set_color(color); + + offset = Vec3d(0.0, 1.25 * size, grabber_connection_len); + Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_cone.render(); + + offset = Vec3d(0.0, -1.25 * size, grabber_connection_len); + view_model_matrix = view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); @@ -562,68 +642,6 @@ void GLGizmoCut3D::render_cut_center_graber() } shader->stop_using(); - -#else - const BoundingBoxf3 box = bounding_box(); - - Vec3d grabber_center = m_plane_center; - grabber_center[Z] += float(box.radius()/2.0); // Margin - rotate_vec3d_around_center(grabber_center, angles, m_plane_center); - - m_grabbers[0].center = grabber_center; - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glLineWidth(m_hover_id == m_group_id ? 2.0f : 1.5f)); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_grabber_connection.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = ColorRGBA::YELLOW(); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - init_data.add_vertex((Vec3f)m_plane_center.cast()); - init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); - - // indices - init_data.add_line(0, 1); - - m_grabber_connection.init_from(std::move(init_data)); - - m_grabber_connection.render(); - - shader->stop_using(); - } - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glColor3f(1.0, 1.0, 0.0)); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center.data()); - ::glVertex3dv(m_grabbers[0].center.data()); - glsafe(::glEnd()); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - shader = wxGetApp().get_shader("gouraud_light"); - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - - m_grabbers[0].render(m_hover_id == m_group_id, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); - - shader->stop_using(); - } -#endif } void GLGizmoCut3D::render_cut_line() @@ -644,21 +662,9 @@ void GLGizmoCut3D::render_cut_line() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES m_cut_line.reset(); + m_cut_line.init_from(its_make_line((Vec3f)m_line_beg.cast(), (Vec3f)m_line_end.cast())); - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = GRABBER_COLOR; - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - init_data.add_vertex((Vec3f)m_line_beg.cast()); - init_data.add_vertex((Vec3f)m_line_end.cast()); - - // indices - init_data.add_line(0, 1); - - m_cut_line.init_from(std::move(init_data)); + m_cut_line.set_color(GRABBER_COLOR); m_cut_line.render(); shader->stop_using(); @@ -670,9 +676,6 @@ bool GLGizmoCut3D::on_init() m_grabbers.emplace_back(); m_shortcut_key = WXK_CONTROL_C; - if(!m_rotation_gizmo.init()) - return false; - return true; } @@ -683,8 +686,8 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) m_ar_plane_center, m_ar_rotations); set_center_pos(m_ar_plane_center, true); - m_rotation_gizmo.set_center(m_ar_plane_center); - m_rotation_gizmo.set_rotation(m_ar_rotations); + m_rotation_m = Geometry::rotation_transform(m_ar_rotations); + force_update_clipper_on_render = true; m_parent.request_extra_frame(); @@ -709,22 +712,18 @@ void GLGizmoCut3D::on_set_state() // initiate archived values m_ar_plane_center = m_plane_center; - m_ar_rotations = m_rotation_gizmo.get_rotation(); + m_ar_rotations = Geometry::Transformation(m_rotation_m).get_rotation(); m_parent.request_extra_frame(); } else m_c->object_clipper()->release(); - m_rotation_gizmo.set_center(m_plane_center); - m_rotation_gizmo.set_state(m_state); - force_update_clipper_on_render = m_state == On; } void GLGizmoCut3D::on_set_hover_id() { - m_rotation_gizmo.set_hover_id(m_hover_id < m_group_id ? m_hover_id: -1); } bool GLGizmoCut3D::on_is_activable() const @@ -734,21 +733,56 @@ bool GLGizmoCut3D::on_is_activable() const return m_parent.get_selection().is_single_full_instance(); } +Vec3d GLGizmoCut3D::mouse_position_in_local_plane(Axis axis, const Linef3& mouse_ray) const +{ + double half_pi = 0.5 * double(PI); + + Transform3d m = Transform3d::Identity(); + + switch (axis) + { + case X: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); + break; + } + case Y: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + break; + } + default: + case Z: + { + // no rotation applied + break; + } + } + + m = m * m_rotation_m.inverse(); + + m.translate(-m_plane_center); + + return transform(mouse_ray, m).intersect_plane(0.0); +} + void GLGizmoCut3D::on_dragging(const UpdateData& data) { - if (m_hover_id < m_group_id) + if (m_hover_id < 0) return; CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - if (m_hover_id == m_group_id) { + if (m_hover_id == Z) { #if use_grabber_extension Vec3d starting_box_center = m_plane_center - Vec3d::UnitZ();// some Margin - rotate_vec3d_around_center(starting_box_center, m_rotation_gizmo.get_rotation(), m_plane_center); + rotate_vec3d_around_center(starting_box_center, Geometry::Transformation(m_rotation_m).get_rotation(), m_plane_center); #else const Vec3d& starting_box_center = m_plane_center; #endif - const Vec3d& starting_drag_position = m_grabbers[0].center; + const Vec3d& starting_drag_position = m_plane_center; double projection = 0.0; Vec3d starting_vec = starting_drag_position - starting_box_center; @@ -775,7 +809,49 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) set_center(m_plane_center + shift); } - else if (m_hover_id > m_group_id && m_connector_mode == CutConnectorMode::Manual) + else if (m_hover_id == X || m_hover_id == Y) { + + Vec3d rotation = Vec3d::Zero(); + + const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((Axis)m_hover_id, data.mouse_ray)); + + const Vec2d orig_dir = Vec2d::UnitX(); + const Vec2d new_dir = mouse_pos.normalized(); + + double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); + if (cross2(orig_dir, new_dir) < 0.0) + theta = 2.0 * (double)PI - theta; + + const double len = mouse_pos.norm(); + + // snap to coarse snap region + if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { + const double step = 2.0 * double(PI) / double(SnapRegionsCount); + theta = step * std::round(theta / step); + } + else { + // snap to fine snap region (scale) + if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { + const double step = 2.0 * double(PI) / double(ScaleStepsCount); + theta = step * std::round(theta / step); + } + } + + if (theta == 2.0 * double(PI)) + theta = 0.0; + + if (m_hover_id == X) + theta += 0.5 * double(PI); + + rotation[m_hover_id] = theta; + + m_rotation_m = m_rotation_m * Geometry::rotation_transform(rotation); + + update_clipper(); + } + + + else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) { std::pair pos_and_normal; if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal)) @@ -786,17 +862,17 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) void GLGizmoCut3D::on_start_dragging() { - if (m_hover_id > m_group_id && m_connector_mode == CutConnectorMode::Manual) + if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move connector"), UndoRedo::SnapshotType::GizmoAction); } void GLGizmoCut3D::on_stop_dragging() { - if (m_hover_id < m_group_id) { + if (m_hover_id == X || m_hover_id == Y) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); - m_ar_rotations = m_rotation_gizmo.get_rotation(); + m_ar_rotations = Geometry::Transformation(m_rotation_m).get_rotation(); } - else if (m_hover_id == m_group_id) { + else if (m_hover_id == Z) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_plane_center = m_plane_center; } @@ -841,7 +917,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= m_c->selection_info()->get_sla_shift(); - const Vec3d& rotation = m_rotation_gizmo.get_rotation(); + const Vec3d rotation = Geometry::Transformation(m_rotation_m).get_rotation(); const auto move = Geometry::assemble_transform(-cut_center_offset); const auto rot_z = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, 0, -rotation.z())); const auto rot_y = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, -rotation.y(), 0)); @@ -879,9 +955,16 @@ bool GLGizmoCut3D::update_bb() m_bb_center = box.center(); set_center_pos(m_bb_center + m_center_offset, true); + m_radius = box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; + m_plane.reset(); m_cone.reset(); m_sphere.reset(); + m_grabber_connection.reset(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { m_selected.clear(); m_selected.resize(selection->model_object()->cut_connectors.size(), false); @@ -894,8 +977,8 @@ bool GLGizmoCut3D::update_bb() void GLGizmoCut3D::on_render() { - if (update_bb()) { - m_rotation_gizmo.set_center(m_plane_center); + bool updated_bb = update_bb(); + if (updated_bb) { update_clipper_on_render(); } @@ -903,6 +986,8 @@ void GLGizmoCut3D::on_render() m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); if (!m_sphere.is_initialized()) m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + if (!m_grabber_connection.is_initialized()) + m_grabber_connection.init_from(its_make_line(Vec3f::Zero(), Vec3f::UnitZ())); if (force_update_clipper_on_render) update_clipper_on_render(); @@ -915,11 +1000,9 @@ void GLGizmoCut3D::on_render() if (! m_connectors_editing) ::glEnable(GL_DEPTH_TEST); - if (!m_hide_cut_plane && ! m_connectors_editing) { - render_cut_center_graber(); + if (!m_hide_cut_plane && !m_connectors_editing) { render_cut_plane(); - if (m_hover_id < m_group_id && m_mode == size_t(CutMode::cutPlanar) && !cut_line_processing()) - m_rotation_gizmo.render(); + render_cut_center_graber(); } render_cut_line(); @@ -927,9 +1010,7 @@ void GLGizmoCut3D::on_render() void GLGizmoCut3D::on_render_for_picking() { - m_rotation_gizmo.render_for_picking(); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); - + render_cut_center_graber(true); render_connectors(true); } @@ -994,7 +1075,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) //v = std::clamp(v, 0.f, float(bottom+top)); if (m_imgui->button("Reset cutting plane")) { set_center(bounding_box().center()); - m_rotation_gizmo.set_rotation(Vec3d::Zero()); + m_rotation_m = Transform3d::Identity(); update_clipper(); } ////// @@ -1010,17 +1091,6 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) // render_move_center_input(axis); // m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); - // ImGui::AlignTextToFramePadding(); - // m_imgui->text(_L("Rotation")); - - // m_imgui->disabled_begin(m_rotation_gizmo.get_rotation() == Vec3d::Zero()); - // revert_rotation = render_revert_button("rotation"); - // m_imgui->disabled_end(); - - // for (Axis axis : {X, Y, Z}) - // render_rotation_input(axis); - // m_imgui->text(_L("°")); - // ImGui::Separator(); // double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; @@ -1053,7 +1123,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->disabled_end(); ImGui::SameLine(); m_imgui->disabled_begin(!m_keep_upper); - m_imgui->disabled_begin(is_approx(m_rotation_gizmo.get_rotation().x(), 0.) && is_approx(m_rotation_gizmo.get_rotation().y(), 0.)); + m_imgui->disabled_begin(is_approx(Geometry::Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Geometry::Transformation(m_rotation_m).get_rotation().y(), 0.)); m_imgui->checkbox(_L("Place on cut") + "##upper", m_rotate_upper); // #ysTODO implement place on cut instead of Flip? m_imgui->disabled_end(); m_imgui->disabled_end(); @@ -1116,14 +1186,17 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_connectors_editing = false; } + m_parent.request_extra_frame(); } ImGui::Separator(); m_imgui->text(m_has_invalid_connector ? wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.") : wxString()); - m_imgui->disabled_begin(!can_perform_cut()); - cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); + if (!m_connectors_editing) { + m_imgui->disabled_begin(!can_perform_cut()); + cut_clicked = m_imgui->button(_L("Perform cut")); + m_imgui->disabled_end(); + } m_imgui->end(); @@ -1147,7 +1220,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (revert_move) set_center(bounding_box().center()); if (revert_rotation) { - m_rotation_gizmo.set_rotation(Vec3d::Zero()); + m_rotation_m = Transform3d::Identity(); update_clipper(); } } @@ -1158,7 +1231,7 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c bool is_prizm_dowel = m_connector_type == CutConnectorType::Dowel && m_connector_style == size_t(CutConnectorStyle::Prizm); const Transform3d connector_trafo = Geometry::assemble_transform( is_prizm_dowel ? Vec3d(0.0, 0.0, -m_connector_depth_ratio) : Vec3d::Zero(), - m_rotation_gizmo.get_rotation(), + Geometry::Transformation(m_rotation_m).get_rotation(), Vec3d(0.5*m_connector_size, 0.5*m_connector_size, is_prizm_dowel ? 2 * m_connector_depth_ratio : m_connector_depth_ratio), Vec3d::Ones()); const Vec3d connector_bb = m_connector_mesh.transformed_bounding_box(connector_trafo).size(); @@ -1277,7 +1350,7 @@ void GLGizmoCut3D::render_connectors(bool picking) const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( Vec3d(pos.x(), pos.y(), pos.z()), - m_rotation_gizmo.get_rotation(), + Geometry::Transformation(m_rotation_m).get_rotation(), Vec3d(connector.radius, connector.radius, height), Vec3d::Ones() ); @@ -1312,7 +1385,7 @@ void GLGizmoCut3D::render_connectors(bool picking) bool GLGizmoCut3D::can_perform_cut() const { - if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower)) + if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; const BoundingBoxf3 tbb = transformed_bounding_box(true); @@ -1343,13 +1416,15 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) if(!mo) return; + Vec3d rotation = Geometry::Transformation(m_rotation_m).get_rotation(); + const bool has_connectors = !mo->cut_connectors.empty(); { // update connectors pos as offset of its center before cut performing if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); for (CutConnector& connector : mo->cut_connectors) { - connector.rotation = m_rotation_gizmo.get_rotation(); + connector.rotation = rotation; if (m_connector_type == CutConnectorType::Dowel) { if (m_connector_style == size_t(CutConnectorStyle::Prizm)) @@ -1359,7 +1434,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // culculate shift of the connector center regarding to the position on the cut plane #if use_grabber_extension Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); - rotate_vec3d_around_center(shifted_center, m_rotation_gizmo.get_rotation(), m_plane_center); + rotate_vec3d_around_center(shifted_center, rotation, m_plane_center); Vec3d norm = (shifted_center - m_plane_center).normalized(); #else Vec3d norm = (m_grabbers[0].center - m_plane_center).normalize(); @@ -1373,7 +1448,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } } - plater->cut(object_idx, instance_idx, cut_center_offset, m_rotation_gizmo.get_rotation(), + plater->cut(object_idx, instance_idx, cut_center_offset, rotation, only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | @@ -1500,7 +1575,7 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse Transform3d m = Transform3d::Identity(); m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir).toRotationMatrix(); - m_rotation_gizmo.set_rotation(Geometry::Transformation(m).get_rotation()); + m_rotation_m = m; set_center(m_plane_center + cross_dir * (cross_dir.dot(pt - m_plane_center))); @@ -1537,7 +1612,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi //std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); - connectors.emplace_back(hit, m_rotation_gizmo.get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio)); + connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio)); update_model_object(); m_selected.push_back(false); assert(m_selected.size() == connectors.size()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 76ec2f7ba..ea053d99f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -20,7 +20,7 @@ enum class SLAGizmoEventType : unsigned char; class GLGizmoCut3D : public GLGizmoBase { - GLGizmoRotate3D m_rotation_gizmo; + Transform3d m_rotation_m{ Transform3d::Identity() }; double m_snap_step{ 1.0 }; int m_connectors_group_id; @@ -35,6 +35,13 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_bb_center{ Vec3d::Zero() }; Vec3d m_center_offset{ Vec3d::Zero() }; + // values from RotationGizmo + float m_radius{ 0.0f }; + float m_snap_coarse_in_radius{ 0.0f }; + float m_snap_coarse_out_radius{ 0.0f }; + float m_snap_fine_in_radius{ 0.0f }; + float m_snap_fine_out_radius{ 0.0f }; + GLModel m_connector_shape; TriangleMesh m_connector_mesh; // workaround for using of the clipping plane normal @@ -45,6 +52,7 @@ class GLGizmoCut3D : public GLGizmoBase #if ENABLE_LEGACY_OPENGL_REMOVAL GLModel m_plane; + GLModel m_grabber_connection; GLModel m_cut_line; GLModel m_cone; GLModel m_sphere; @@ -138,6 +146,7 @@ protected: CommonGizmosDataID on_get_requirements() const override; void on_set_hover_id() override; bool on_is_activable() const override; + Vec3d mouse_position_in_local_plane(Axis axis, const Linef3& mouse_ray) const; void on_dragging(const UpdateData& data) override; void on_start_dragging() override; void on_stop_dragging() override; @@ -156,7 +165,6 @@ private: bool render_double_input(const std::string& label, double& value_in); bool render_slicer_double_input(const std::string& label, double& value_in); void render_move_center_input(int axis); - void render_rotation_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); bool render_revert_button(const std::string& label); void render_connect_type_radio_button(CutConnectorType type); @@ -167,7 +175,7 @@ private: bool cut_line_processing() const; void render_cut_plane(); - void render_cut_center_graber(); + void render_cut_center_graber(bool picking = false); void render_cut_line(); void perform_cut(const Selection& selection); void set_center_pos(const Vec3d& center_pos, bool force = false); From cd8e0d002ba30cd12c107d421aaeb128407a8f1f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 19 Jul 2022 16:58:27 +0200 Subject: [PATCH 49/97] Cut WIP: Added "Place o cut" --- src/libslic3r/Model.cpp | 38 +++++++++++++++------------- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 24 +++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 ++ 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 19b2f8f7d..36034d904 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1494,19 +1494,9 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const instances[instance]->get_mirror() ); - const auto cut_matrix = Geometry::assemble_transform( - -cut_center, - Vec3d::Zero(), - Vec3d::Ones(), - Vec3d::Ones() - ); + const auto cut_matrix = Geometry::assemble_transform(-cut_center); - const auto invert_cut_matrix = Geometry::assemble_transform( - cut_center, - cut_rotation, - Vec3d::Ones(), - Vec3d::Ones() - ); + const auto invert_cut_matrix = Geometry::assemble_transform(cut_center, cut_rotation); // Displacement (in instance coordinates) to be applied to place the upper parts Vec3d local_displace = Vec3d::Zero(); @@ -1522,11 +1512,22 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const if (!volume->is_model_part()) { if (volume->source.is_connector) { if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + + Transform3d m = attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper) ? + Geometry::rotation_transform(cut_rotation).inverse() * cut_matrix * instance_matrix * volume_matrix : + instance_matrix * volume_matrix; + volume->set_transformation(m); + ModelVolume* vol = upper->add_volume(*volume); // make a "hole" dipper vol->set_scaling_factor(Z, 1.1 * vol->get_scaling_factor(Z)); } if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + + Transform3d m = attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? + Geometry::rotation_transform(Geometry::deg2rad(180.0) * Vec3d::UnitX()) * Geometry::rotation_transform(cut_rotation).inverse() * cut_matrix * instance_matrix * volume_matrix : + instance_matrix * volume_matrix; + volume->set_transformation(m); ModelVolume* vol = lower->add_volume(*volume); if (attributes.has(ModelObjectCutAttribute::CreateDowels)) // make a "hole" dipper @@ -1567,10 +1568,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. TriangleMesh mesh(volume->mesh()); - mesh.transform(cut_matrix * instance_matrix * volume_matrix, true); - mesh.rotate(-cut_rotation.z(), Z); - mesh.rotate(-cut_rotation.y(), Y); - mesh.rotate(-cut_rotation.x(), X); + mesh.transform(Geometry::rotation_transform(cut_rotation).inverse() * cut_matrix * instance_matrix * volume_matrix, true); volume->reset_mesh(); // Reset volume transformation except for offset @@ -1590,7 +1588,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const } if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { - upper_mesh.transform(invert_cut_matrix); + if (!attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper)) + upper_mesh.transform(invert_cut_matrix); ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; @@ -1601,7 +1600,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const vol->set_material(volume->material_id(), *volume->material()); } if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { - lower_mesh.transform(invert_cut_matrix); + if (attributes.has(ModelObjectCutAttribute::PlaceOnCutLower)) + lower_mesh.transform(Geometry::assemble_transform(Vec3d::Zero(), Geometry::deg2rad(180.0)*Vec3d::UnitX())); + else + lower_mesh.transform(invert_cut_matrix); ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 72d9b33c1..6afa2efe8 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -304,7 +304,7 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER, }; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, CreateDowels }; +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels }; using ModelObjectCutAttributes = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5ea85964c..0d8eae28d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1121,10 +1121,17 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->disabled_begin(!connectors.empty()); m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); m_imgui->disabled_end(); - ImGui::SameLine(); + ImGui::SameLine(3 * m_label_width); + m_imgui->disabled_begin(!m_keep_upper); m_imgui->disabled_begin(is_approx(Geometry::Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Geometry::Transformation(m_rotation_m).get_rotation().y(), 0.)); - m_imgui->checkbox(_L("Place on cut") + "##upper", m_rotate_upper); // #ysTODO implement place on cut instead of Flip? + + if (m_imgui->checkbox(_L("Place on cut") + "##upper", m_place_on_cut_upper)) + m_rotate_upper = false; + ImGui::SameLine(); + if (m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper)) + m_place_on_cut_upper = false; + m_imgui->disabled_end(); m_imgui->disabled_end(); @@ -1134,11 +1141,18 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(2 * m_label_width); m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); m_imgui->disabled_end(); - ImGui::SameLine(); + ImGui::SameLine(3 * m_label_width); m_imgui->disabled_begin(!m_keep_lower); - m_imgui->checkbox(_L("Place on cut") + "##lower", m_rotate_lower); // #ysTODO implement place on cut instead of Flip? + + if (m_imgui->checkbox(_L("Place on cut") + "##lower", m_place_on_cut_lower)) + m_rotate_lower = false; + ImGui::SameLine(); + if (m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower)) + m_place_on_cut_lower = false; + m_imgui->disabled_end(); } @@ -1451,6 +1465,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) plater->cut(object_idx, instance_idx, cut_center_offset, rotation, only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | + only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ea053d99f..dbb1e6ae0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -61,6 +61,8 @@ class GLGizmoCut3D : public GLGizmoBase bool m_keep_upper{ true }; bool m_keep_lower{ true }; + bool m_place_on_cut_upper{ true }; + bool m_place_on_cut_lower{ true }; bool m_rotate_upper{ false }; bool m_rotate_lower{ false }; From 003acee218ce62869b79c1a80f958ac86925d398 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 20 Jul 2022 16:36:36 +0200 Subject: [PATCH 50/97] Cut WIP: Added snapping for the rotation of cut plane + Some code refactoring --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 376 ++++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 19 +- 2 files changed, 243 insertions(+), 152 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 0d8eae28d..5fd0b28d4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -27,12 +27,15 @@ namespace GUI { static const double Margin = 20.0; static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); +const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; +const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount; +const unsigned int ScaleLongEvery = 2; const float ScaleLongTooth = 0.1f; // in percent of radius const unsigned int SnapRegionsCount = 8; // Generates mesh for a line -GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) +static GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; @@ -40,9 +43,7 @@ GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) init_data.reserve_indices(2); // vertices - Vec3f start = Vec3f::Zero(); init_data.add_vertex(beg_pos); - Vec3f stop = Vec3f::UnitZ(); init_data.add_vertex(end_pos); // indices @@ -50,6 +51,118 @@ GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) return init_data; } +//! -- #ysFIXME those functions bodies are ported from GizmoRotation +// Generates mesh for a circle +static void init_from_circle(GLModel& model, double radius) +{ + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(ScaleStepsCount); + init_data.reserve_indices(ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + init_data.add_vertex(Vec3f(::cos(angle) * radius, ::sin(angle) * radius, 0.0f)); + init_data.add_index(i); + } + + model.init_from(std::move(init_data)); + model.set_color(ColorRGBA::WHITE()); +} + +// Generates mesh for a scale +static void init_from_scale(GLModel& model, double radius) +{ + const float out_radius_long = radius * (1.0f + ScaleLongTooth); + const float out_radius_short = radius * (1.0f + 0.5f * ScaleLongTooth); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * ScaleStepRad); + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * radius; + const float in_y = sina * radius; + const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; + const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; + + // vertices + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + + // indices + init_data.add_line(i * 2, i * 2 + 1); + } + + model.init_from(std::move(init_data)); + model.set_color(ColorRGBA::WHITE()); +} + +// Generates mesh for a snap_radii +static void init_from_snap_radii(GLModel& model, double radius) +{ + const float step = 2.0f * float(PI) / float(SnapRegionsCount); + const float in_radius = radius / 3.0f; + const float out_radius = 2.0f * in_radius; + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); + + // vertices + indices + for (unsigned int i = 0; i < ScaleStepsCount; ++i) { + const float angle = float(i * step); + const float cosa = ::cos(angle); + const float sina = ::sin(angle); + const float in_x = cosa * in_radius; + const float in_y = sina * in_radius; + const float out_x = cosa * out_radius; + const float out_y = sina * out_radius; + + // vertices + init_data.add_vertex(Vec3f(in_x, in_y, 0.0f)); + init_data.add_vertex(Vec3f(out_x, out_y, 0.0f)); + + // indices + init_data.add_line(i * 2, i * 2 + 1); + } + + model.init_from(std::move(init_data)); + model.set_color(ColorRGBA::WHITE()); +} + +// Generates mesh for a angle_arc +static void init_from_angle_arc(GLModel& model, double angle, double radius) +{ + model.reset(); + + const float step_angle = float(angle) / float(AngleResolution); + const float ex_radius = radius; + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(1 + AngleResolution); + init_data.reserve_indices(1 + AngleResolution); + + // vertices + indices + for (unsigned int i = 0; i <= AngleResolution; ++i) { + const float angle = float(i) * step_angle; + init_data.add_vertex(Vec3f(::cos(angle) * ex_radius, ::sin(angle) * ex_radius, 0.0f)); + init_data.add_index(i); + } + + model.init_from(std::move(init_data)); +} + +//! -- + #define use_grabber_extension 1 GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) @@ -101,7 +214,8 @@ std::string GLGizmoCut3D::get_tooltip() const } if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { std::string axis = m_hover_id == X ? "X" : "Y"; - return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L("°"); +// return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L("°"); + return axis + ": " + format(float(Geometry::rad2deg(m_angle)), 1) + _u8L("°"); } return tooltip; @@ -496,14 +610,9 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) if (!shader) return; - glsafe(::glLineWidth(m_hover_id == X || m_hover_id == Y ? 3.0f : 1.0f)); - ColorRGBA color = picking ? picking_decode(BASE_ID - Z) : m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; - m_sphere.set_color(color); - m_cone.set_color(color); - const Camera& camera = wxGetApp().plater()->get_camera(); const Grabber& graber = m_grabbers.front(); @@ -520,125 +629,105 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) shader->set_uniform("projection_matrix", camera.get_projection_matrix()); const Transform3d view_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_rotation_m; + const Transform3d view_grabber_connection_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, m_grabber_connection_len)); - const BoundingBoxf3 tbb = transformed_bounding_box(); - const double grabber_connection_len = std::min(0.75 * m_radius, 35.0) ; + auto render = [shader, this](GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix) { + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + model.set_color(color); + model.render(); + }; - auto render_grabber_connection = [shader, view_matrix, camera, grabber_connection_len, this](bool render, const ColorRGBA& color) + auto render_rotation_snapping = [shader, camera, this](Axis axis, const ColorRGBA& color) { - if (!render) - return; - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, grabber_connection_len)); + Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_start_dragging_m; + + if (axis == X) + view_model_matrix = view_model_matrix * Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-PI * Vec3d::UnitZ()); + else + view_model_matrix = view_model_matrix * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_grabber_connection.set_color(color); - m_grabber_connection.render(); + m_circle.render(); + m_scale.render(); + m_snap_radii.render(); + m_reference_radius.render(); + if (m_dragging) { + m_angle_arc.set_color(color); + m_angle_arc.render(); + } }; // render Z grabber - render_grabber_connection((!m_dragging && m_hover_id < 0) || m_hover_id == X || m_hover_id == Y, color); - - if ((!m_dragging && m_hover_id < 0 || m_hover_id == Z) && tbb.min.z() <= 0.0) { - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_cone.render(); - } + if ((!m_dragging && m_hover_id < 0)) + render(m_grabber_connection, color, view_grabber_connection_matrix); + render(m_sphere, color, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); + if (!m_dragging && m_hover_id < 0 || m_hover_id == Z) { - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones()); + const BoundingBoxf3 tbb = transformed_bounding_box(); + if (tbb.min.z() <= 0.0) + render(m_cone, color, view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_sphere.render(); - } - - if ((!m_dragging && m_hover_id < 0 || m_hover_id == Z) && tbb.max.z() >= 0.0) { - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), cone_scale); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_cone.render(); + if (tbb.max.z() >= 0.0) + render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), cone_scale)); } // render top sphere for X/Y grabbers - offset = Vec3d(0.0, 0.0, grabber_connection_len); - size = m_dragging && (m_hover_id == X || m_hover_id == Y) ? - double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); if ((!m_dragging && m_hover_id < 0) || m_hover_id == X || m_hover_id == Y) { - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), size * Vec3d::Ones()); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_sphere.set_color(m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : - m_hover_id == X ? complementary(ColorRGBA::RED() ) : ColorRGBA::GRAY()); - m_sphere.render(); - } - - // render Y grabber - - size = m_dragging && m_hover_id == Y ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); - cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); - if ((!m_dragging && m_hover_id < 0) || m_hover_id == Y) - { - if (picking) - color = picking_decode(BASE_ID - Y); - else - color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); - - render_grabber_connection(m_hover_id == Y, color); - - m_cone.set_color(color); - - offset = Vec3d(1.25 * size, 0.0, grabber_connection_len); - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_cone.render(); - - offset = Vec3d(-1.25 * size, 0.0, grabber_connection_len); - view_model_matrix = view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_cone.render(); + size = m_dragging ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : + m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::GRAY(); + render(m_sphere, color, view_matrix * Geometry::assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); } // render X grabber - size = m_dragging && m_hover_id == X ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); - cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); if ((!m_dragging && m_hover_id < 0) || m_hover_id == X) { + size = m_dragging && m_hover_id == X ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); if (picking) color = picking_decode(BASE_ID - X); else color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); - render_grabber_connection(m_hover_id == X, color); + if (m_hover_id == X) { + render(m_grabber_connection, color, view_grabber_connection_matrix); + render_rotation_snapping(X, color); + } - m_cone.set_color(color); + offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); + render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); + offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); + render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); + } - offset = Vec3d(0.0, 1.25 * size, grabber_connection_len); - Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale); + // render Y grabber - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_cone.render(); + if ((!m_dragging && m_hover_id < 0) || m_hover_id == Y) + { + size = m_dragging && m_hover_id == Y ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + if (picking) + color = picking_decode(BASE_ID - Y); + else + color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); - offset = Vec3d(0.0, -1.25 * size, grabber_connection_len); - view_model_matrix = view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale); + if (m_hover_id == Y) { + render(m_grabber_connection, color, view_grabber_connection_matrix); + render_rotation_snapping(Y, color); + } - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_cone.render(); + offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); + render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); + offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); + render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); } shader->stop_using(); @@ -818,35 +907,28 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) const Vec2d orig_dir = Vec2d::UnitX(); const Vec2d new_dir = mouse_pos.normalized(); + const double two_pi = 2.0 * PI; + double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); if (cross2(orig_dir, new_dir) < 0.0) - theta = 2.0 * (double)PI - theta; + theta = two_pi - theta; - const double len = mouse_pos.norm(); - - // snap to coarse snap region - if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { - const double step = 2.0 * double(PI) / double(SnapRegionsCount); - theta = step * std::round(theta / step); - } - else { - // snap to fine snap region (scale) - if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { - const double step = 2.0 * double(PI) / double(ScaleStepsCount); - theta = step * std::round(theta / step); - } - } - - if (theta == 2.0 * double(PI)) + if (theta == two_pi) theta = 0.0; if (m_hover_id == X) - theta += 0.5 * double(PI); + theta += 0.5 * PI; rotation[m_hover_id] = theta; m_rotation_m = m_rotation_m * Geometry::rotation_transform(rotation); + m_angle += (float)theta; + while (m_angle > two_pi) + m_angle -= two_pi; + if (m_angle < 0.0) + m_angle += two_pi; + update_clipper(); } @@ -862,15 +944,23 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) void GLGizmoCut3D::on_start_dragging() { + m_angle = 0.0; if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move connector"), UndoRedo::SnapshotType::GizmoAction); + + if (m_hover_id == X || m_hover_id == Y) + m_start_dragging_m = m_rotation_m; } void GLGizmoCut3D::on_stop_dragging() { if (m_hover_id == X || m_hover_id == Y) { + m_angle_arc.reset(); + m_angle = 0.0; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_rotations = Geometry::Transformation(m_rotation_m).get_rotation(); + + m_start_dragging_m = m_rotation_m; } else if (m_hover_id == Z) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); @@ -956,15 +1046,17 @@ bool GLGizmoCut3D::update_bb() set_center_pos(m_bb_center + m_center_offset, true); m_radius = box.radius(); - m_snap_coarse_in_radius = m_radius / 3.0f; - m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; - m_snap_fine_in_radius = m_radius; - m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; + m_grabber_connection_len = std::min(0.75 * m_radius, 35.0); + m_grabber_radius = m_grabber_connection_len * 0.85; m_plane.reset(); m_cone.reset(); m_sphere.reset(); m_grabber_connection.reset(); + m_circle.reset(); + m_scale.reset(); + m_snap_radii.reset(); + if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { m_selected.clear(); m_selected.resize(selection->model_object()->cut_connectors.size(), false); @@ -977,10 +1069,8 @@ bool GLGizmoCut3D::update_bb() void GLGizmoCut3D::on_render() { - bool updated_bb = update_bb(); - if (updated_bb) { + if (update_bb()) update_clipper_on_render(); - } if (!m_cone.is_initialized()) m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); @@ -989,6 +1079,19 @@ void GLGizmoCut3D::on_render() if (!m_grabber_connection.is_initialized()) m_grabber_connection.init_from(its_make_line(Vec3f::Zero(), Vec3f::UnitZ())); + if (!m_circle.is_initialized()) + init_from_circle(m_circle, m_grabber_radius); + if (!m_scale.is_initialized()) + init_from_scale(m_scale, m_grabber_radius); + if (!m_snap_radii.is_initialized()) + init_from_snap_radii(m_snap_radii, m_grabber_radius); + if (!m_reference_radius.is_initialized()) { + m_reference_radius.init_from(its_make_line(Vec3f::Zero(), m_grabber_connection_len * Vec3f::UnitX())); + m_reference_radius.set_color(ColorRGBA::WHITE()); + } + if (!m_angle_arc.is_initialized() || m_angle != 0.0) + init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); + if (force_update_clipper_on_render) update_clipper_on_render(); @@ -1076,35 +1179,9 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->button("Reset cutting plane")) { set_center(bounding_box().center()); m_rotation_m = Transform3d::Identity(); + m_angle_arc.reset(); update_clipper(); } - ////// - - // ImGui::AlignTextToFramePadding(); - // m_imgui->text(_L("Move center")); - - // m_imgui->disabled_begin(m_plane_center == bounding_box().center()); - // revert_move = render_revert_button("move"); - // m_imgui->disabled_end(); - - // for (Axis axis : {X, Y, Z}) - // render_move_center_input(axis); - // m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); - - // ImGui::Separator(); - - // double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; - // wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); - - // Vec3d tbb_sz = 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; - - // ImGui::AlignTextToFramePadding(); - // m_imgui->text(_L("Build size")); - // ImGui::SameLine(m_label_width); - // m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); } if (m_mode == size_t(CutMode::cutPlanar)) { @@ -1124,16 +1201,17 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::SameLine(3 * m_label_width); m_imgui->disabled_begin(!m_keep_upper); - m_imgui->disabled_begin(is_approx(Geometry::Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Geometry::Transformation(m_rotation_m).get_rotation().y(), 0.)); + m_imgui->disabled_begin(is_approx(Geometry::Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Geometry::Transformation(m_rotation_m).get_rotation().y(), 0.)); if (m_imgui->checkbox(_L("Place on cut") + "##upper", m_place_on_cut_upper)) m_rotate_upper = false; + m_imgui->disabled_end(); + ImGui::SameLine(); if (m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper)) m_place_on_cut_upper = false; m_imgui->disabled_end(); - m_imgui->disabled_end(); m_imgui->text(""); ImGui::SameLine(m_label_width); @@ -1158,8 +1236,10 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); if (m_imgui->button(_L("Add/Edit connectors"))) m_connectors_editing = true; + m_imgui->disabled_end(); } else { // connectors mode m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); // Connectors section @@ -1235,6 +1315,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) set_center(bounding_box().center()); if (revert_rotation) { m_rotation_m = Transform3d::Identity(); + m_angle_arc.reset(); update_clipper(); } } @@ -1592,6 +1673,7 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir).toRotationMatrix(); m_rotation_m = m; + m_angle_arc.reset(); set_center(m_plane_center + cross_dir * (cross_dir.dot(pt - m_plane_center))); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index dbb1e6ae0..b562396c1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -36,11 +36,13 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_center_offset{ Vec3d::Zero() }; // values from RotationGizmo - float m_radius{ 0.0f }; - float m_snap_coarse_in_radius{ 0.0f }; - float m_snap_coarse_out_radius{ 0.0f }; - float m_snap_fine_in_radius{ 0.0f }; - float m_snap_fine_out_radius{ 0.0f }; + double m_radius{ 0.0 }; + double m_grabber_radius{ 0.0 }; + double m_grabber_connection_len{ 0.0 }; + + // dragging angel in hovered axes + Transform3d m_start_dragging_m{ Transform3d::Identity() }; + double m_angle{ 0.0 }; GLModel m_connector_shape; TriangleMesh m_connector_mesh; @@ -56,6 +58,13 @@ class GLGizmoCut3D : public GLGizmoBase GLModel m_cut_line; GLModel m_cone; GLModel m_sphere; + + GLModel m_circle; + GLModel m_scale; + GLModel m_snap_radii; + GLModel m_reference_radius; + GLModel m_angle_arc; + Vec3d m_old_center; #endif // ENABLE_LEGACY_OPENGL_REMOVAL From 0fd29dfec7b6d5366a94b31652e3103e80a12d30 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 21 Jul 2022 17:04:37 +0200 Subject: [PATCH 51/97] Cut WIP: Suppress un-universal scaling for cut objects Added editing of the tolerance --- src/libslic3r/Model.cpp | 122 ++++++++++++++-------- src/libslic3r/Model.hpp | 27 +++-- src/slic3r/GUI/GUI_ObjectList.cpp | 7 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 17 ++- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 48 ++++++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 5 +- 7 files changed, 155 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 36034d904..b18756a5e 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -713,7 +713,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, ModelVolumeType t ModelVolume* v = new ModelVolume(this, other); if (type != ModelVolumeType::INVALID && v->type() != type) v->set_type(type); - v->source.is_connector = other.source.is_connector; + v->cut_info = other.cut_info; this->volumes.push_back(v); // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull. // v->center_geometry_after_creation(); @@ -1380,10 +1380,9 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes) { - // discard old connector markers vor volumes - for (ModelVolume* volume : volumes) { - volume->source.is_connector = false; - } + // discard old connector markers for volumes + for (ModelVolume* volume : volumes) + volume->cut_info.discard(); if (cut_connectors.empty()) return; @@ -1404,8 +1403,8 @@ void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttr Vec3d::Ones() )); + new_volume->cut_info = { true, connector.radius_tolerance, connector.height_tolerance }; new_volume->name = name + "-" + std::to_string(++connector_id); - new_volume->source.is_connector = true; } cut_id.increase_connectors_cnt(cut_connectors.size()); @@ -1481,27 +1480,47 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const dowels->input_file.clear(); } + using namespace Geometry; + // Because transformations are going to be applied to meshes directly, // we reset transformation of all instances and volumes, // except for translation and Z-rotation on instances, which are preserved // in the transformation matrix and not applied to the mesh transform. // const auto instance_matrix = instances[instance]->get_matrix(true); - const auto instance_matrix = Geometry::assemble_transform( + const auto instance_matrix = assemble_transform( Vec3d::Zero(), // don't apply offset instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), instances[instance]->get_scaling_factor(), instances[instance]->get_mirror() ); - const auto cut_matrix = Geometry::assemble_transform(-cut_center); - - const auto invert_cut_matrix = Geometry::assemble_transform(cut_center, cut_rotation); + const auto cut_matrix = rotation_transform(cut_rotation).inverse() * assemble_transform(-cut_center); + const auto invert_cut_matrix = assemble_transform(cut_center, cut_rotation); // Displacement (in instance coordinates) to be applied to place the upper parts Vec3d local_displace = Vec3d::Zero(); Vec3d local_dowels_displace = Vec3d::Zero(); + Vec3d rotate_z180 = deg2rad(180.0) * Vec3d::UnitX(); + + auto apply_tolerance = [](ModelVolume * vol) + { + Vec3d sf = vol->get_scaling_factor(); +/* + // correct Z offset in respect to the new size + Vec3d pos = vol->get_offset(); + pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance; + vol->set_offset(pos); +*/ + // make a "hole" wider + sf[X] *= (1 + vol->cut_info.radius_tolerance); + sf[Y] *= (1 + vol->cut_info.radius_tolerance); + // make a "hole" dipper + sf[Z] *= (1 + vol->cut_info.height_tolerance); + vol->set_scaling_factor(sf); + }; + for (ModelVolume* volume : volumes) { const auto volume_matrix = volume->get_matrix(); @@ -1510,43 +1529,33 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const volume->mmu_segmentation_facets.reset(); if (!volume->is_model_part()) { - if (volume->source.is_connector) { + if (volume->cut_info.is_connector) { + // ! Don't apply instance transformation for the conntectors. + // This transformation is already there if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - - Transform3d m = attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper) ? - Geometry::rotation_transform(cut_rotation).inverse() * cut_matrix * instance_matrix * volume_matrix : - instance_matrix * volume_matrix; - volume->set_transformation(m); - ModelVolume* vol = upper->add_volume(*volume); - // make a "hole" dipper - vol->set_scaling_factor(Z, 1.1 * vol->get_scaling_factor(Z)); + vol->set_transformation(volume_matrix); + apply_tolerance(vol); } if (attributes.has(ModelObjectCutAttribute::KeepLower)) { - - Transform3d m = attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? - Geometry::rotation_transform(Geometry::deg2rad(180.0) * Vec3d::UnitX()) * Geometry::rotation_transform(cut_rotation).inverse() * cut_matrix * instance_matrix * volume_matrix : - instance_matrix * volume_matrix; - volume->set_transformation(m); ModelVolume* vol = lower->add_volume(*volume); + vol->set_transformation(volume_matrix); + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) - // make a "hole" dipper - vol->set_scaling_factor(Z, 1.2 * vol->get_scaling_factor(Z)); + apply_tolerance(vol); else // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug vol->set_type(ModelVolumeType::MODEL_PART); } if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { // add one more solid part same as connector if this connector is a dowel - // But discard rotation and Z-offset for this volume - volume->set_rotation(Vec3d::Zero()); - Vec3d offset = volume->get_offset(); - offset[Z] = 0.0; - volume->set_offset(offset); - ModelVolume* vol = dowels->add_volume(*volume); vol->set_type(ModelVolumeType::MODEL_PART); + // But discard rotation and Z-offset for this volume + vol->set_rotation(Vec3d::Zero()); + vol->set_offset(Z, 0.0); + // Compute the displacement (in instance coordinates) to be applied to place the dowels local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); } @@ -1556,19 +1565,18 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // to the modifier volume transformation to preserve their shape properly. volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); + // #ysFIXME - add logic for the negative volumes/connectors if (attributes.has(ModelObjectCutAttribute::KeepUpper)) upper->add_volume(*volume); if (attributes.has(ModelObjectCutAttribute::KeepLower)) lower->add_volume(*volume); } } - else if (!volume->mesh().empty() -// && !volume->source.is_connector // we don't allow to cut a connectors - ) { + else if (!volume->mesh().empty()) { // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. TriangleMesh mesh(volume->mesh()); - mesh.transform(Geometry::rotation_transform(cut_rotation).inverse() * cut_matrix * instance_matrix * volume_matrix, true); + mesh.transform(cut_matrix * instance_matrix* volume_matrix, true); volume->reset_mesh(); // Reset volume transformation except for offset @@ -1588,8 +1596,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const } if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { - if (!attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper)) - upper_mesh.transform(invert_cut_matrix); + upper_mesh.transform(invert_cut_matrix); ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; @@ -1600,10 +1607,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const vol->set_material(volume->material_id(), *volume->material()); } if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { - if (attributes.has(ModelObjectCutAttribute::PlaceOnCutLower)) - lower_mesh.transform(Geometry::assemble_transform(Vec3d::Zero(), Geometry::deg2rad(180.0)*Vec3d::UnitX())); - else - lower_mesh.transform(invert_cut_matrix); + lower_mesh.transform(invert_cut_matrix); ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; @@ -1643,7 +1647,22 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const obj_instance->set_transformation(Geometry::Transformation()); obj_instance->set_offset(offset + displace); - obj_instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipUpper) ? Geometry::deg2rad(180.0) : 0.0, 0.0, i == instance ? 0.0 : rot_z)); + + Vec3d rotation = Vec3d::Zero(); + if (attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper)) { + Transform3d trafo = rotation_transform(cut_rotation).inverse(); + if (i != instance) + trafo = rotation_transform(rot_z * Vec3d::UnitZ()) * trafo; + rotation = Transformation(trafo).get_rotation(); + } + else if (attributes.has(ModelObjectCutAttribute::FlipUpper)) { + rotation = rotate_z180; + if (i != instance) + rotation[Z] = rot_z; + } + else if (i != instance) + rotation[Z] = rot_z/* * Vec3d::UnitZ()*/; + obj_instance->set_rotation(rotation); } res.push_back(upper); @@ -1666,7 +1685,22 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const const double rot_z = obj_instance->get_rotation().z(); obj_instance->set_transformation(Geometry::Transformation()); obj_instance->set_offset(offset); - obj_instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, i == instance ? 0.0 : rot_z)); + + Vec3d rotation = Vec3d::Zero(); + if (attributes.has(ModelObjectCutAttribute::PlaceOnCutLower)) { + Transform3d trafo = rotation_transform(rotate_z180) * rotation_transform(cut_rotation).inverse(); + if (i != instance) + trafo = rotation_transform(rot_z * Vec3d::UnitZ()) * trafo; + rotation = Transformation(trafo).get_rotation(); + } + else if (attributes.has(ModelObjectCutAttribute::FlipLower)) { + rotation = rotate_z180; + if (i != instance) + rotation[Z] = rot_z; + } + else if (i != instance) + rotation[Z] = rot_z; + obj_instance->set_rotation(rotation); } res.push_back(lower); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 6afa2efe8..3242dd255 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -225,18 +225,20 @@ struct CutConnector Vec3d rotation; float radius; float height; + float radius_tolerance;// [0.f : 1.f] + float height_tolerance;// [0.f : 1.f] bool failed = false; CutConnector() - : pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f) + : pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {} - CutConnector(Vec3d p, Vec3d rot, float r, float h, bool fl = false) - : pos(p), rotation(rot), radius(r), height(h), failed(fl) + CutConnector(Vec3d p, Vec3d rot, float r, float h, float rt, float ht, bool fl = false) + : pos(p), rotation(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), failed(fl) {} CutConnector(const CutConnector& rhs) : - CutConnector(rhs.pos, rhs.rotation, rhs.radius, rhs.height, rhs.failed) {} + CutConnector(rhs.pos, rhs.rotation, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.failed) {} bool operator==(const CutConnector& sp) const; @@ -251,7 +253,7 @@ struct CutConnector */ template inline void serialize(Archive& ar) { - ar(pos, rotation, radius, height, failed); + ar(pos, rotation, radius, height, radius_tolerance, height_tolerance, failed); } static constexpr size_t steps = 32; @@ -696,16 +698,25 @@ public: bool is_converted_from_meters{ false }; bool is_from_builtin_objects{ false }; - bool is_connector{ false }; - template void serialize(Archive& ar) { //FIXME Vojtech: Serialize / deserialize only if the Source is set. // likely testing input_file or object_idx would be sufficient. - ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects, is_connector); + ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects); } }; Source source; + // struct used by cut command + // It contains information about connetors + struct CutInfo + { + bool is_connector {false}; + float radius_tolerance;// [0.f : 1.f] + float height_tolerance;// [0.f : 1.f] + + void discard() { is_connector = false; } + } cut_info; + // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3b5af8cd9..23ee84a5c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2499,6 +2499,7 @@ void ObjectList::part_selection_changed() bool enable_manipulation {true}; bool disable_ss_manipulation {false}; + bool disable_ununiform_scale {false}; const auto item = GetSelection(); @@ -2543,6 +2544,7 @@ void ObjectList::part_selection_changed() disable_ss_manipulation = true; break; } + disable_ununiform_scale = !cut_objects.empty(); } } } @@ -2649,8 +2651,11 @@ void ObjectList::part_selection_changed() if (disable_ss_manipulation) wxGetApp().obj_manipul()->DisableScale(); - else + else { wxGetApp().obj_manipul()->Enable(enable_manipulation); + if (disable_ununiform_scale) + wxGetApp().obj_manipul()->DisableUnuniformScale(); + } } if (update_and_show_settings) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index b3f71687b..c89ee949a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -580,7 +580,11 @@ void ObjectManipulation::Enable(const bool enadle) { for (auto editor : m_editors) editor->Enable(enadle); - for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt }) + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt +#if ENABLE_WORLD_COORDINATE + ,m_reset_skew_button +#endif // ENABLE_WORLD_COORDINATE + }) win->Enable(enadle); } @@ -588,10 +592,19 @@ void ObjectManipulation::DisableScale() { for (auto editor : m_editors) editor->Enable(editor->has_opt_key("scale") || editor->has_opt_key("size") ? false : true); - for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_lock_bnt }) + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_lock_bnt +#if ENABLE_WORLD_COORDINATE + ,m_reset_skew_button +#endif // ENABLE_WORLD_COORDINATE + }) win->Enable(false); } +void ObjectManipulation::DisableUnuniformScale() +{ + m_lock_bnt->disable(); +} + void ObjectManipulation::update_ui_from_settings() { if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 936ac99d8..2a4b3e46a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -201,6 +201,7 @@ public: void Enable(const bool enadle = true); void Disable() { Enable(false); } void DisableScale(); + void DisableUnuniformScale(); void update_ui_from_settings(); bool use_colors() { return m_use_colors; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5fd0b28d4..417cf6037 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -430,12 +430,12 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i return old_val != value; } -bool GLGizmoCut3D::render_slicer_double_input(const std::string& label, double& value_in) +bool GLGizmoCut3D::render_slider_double_input(const std::string& label, double& value_in, int& tolerance_in) { ImGui::AlignTextToFramePadding(); m_imgui->text(label); ImGui::SameLine(m_label_width); - ImGui::PushItemWidth(m_control_width); + ImGui::PushItemWidth(m_control_width * 0.85f); float value = (float)value_in; if (m_imperial_units) @@ -443,15 +443,25 @@ bool GLGizmoCut3D::render_slicer_double_input(const std::string& label, double& float old_val = value; const BoundingBoxf3 bbox = bounding_box(); - const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0); - - m_imgui->slider_float(("##" + label).c_str(), &value, 1.0f, mean_size); - - ImGui::SameLine(); - m_imgui->text(m_imperial_units ? _L("in") : _L("mm")); + float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0); + float min_size = 1.f; + if (m_imperial_units) { + mean_size *= ObjectManipulation::mm_to_in; + min_size *= ObjectManipulation::mm_to_in; + } + std::string format = m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); + m_imgui->slider_float(("##" + label).c_str(), &value, min_size, mean_size, format.c_str()); value_in = (double)(value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0)); - return old_val != value; + + ImGui::SameLine(m_label_width + m_control_width + 3); + ImGui::PushItemWidth(m_control_width * 0.3f); + + float old_tolerance, tolerance = old_tolerance = (float)tolerance_in; + m_imgui->slider_float(("##tolerance_" + label).c_str(), &tolerance, 1.f, 20.f, "%.f %%", 1.f, true, _L("Tolerance")); + tolerance_in = (int)tolerance; + + return old_val != value || old_tolerance != tolerance; } void GLGizmoCut3D::render_move_center_input(int axis) @@ -1253,11 +1263,11 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->button(" " + _L("Reset") + " ##connectors")) reset_connectors(); m_imgui->disabled_end(); - +/* m_imgui->text(_L("Mode")); render_connect_mode_radio_button(CutConnectorMode::Auto); render_connect_mode_radio_button(CutConnectorMode::Manual); - +*/ m_imgui->text(_L("Type")); render_connect_type_radio_button(CutConnectorType::Plug); render_connect_type_radio_button(CutConnectorType::Dowel); @@ -1267,12 +1277,16 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) update_connector_shape(); - if (render_slicer_double_input(_u8L("Depth ratio"), m_connector_depth_ratio)) - for (auto& connector : connectors) + if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) + for (auto& connector : connectors) { connector.height = float(m_connector_depth_ratio); - if (render_slicer_double_input(_u8L("Size"), m_connector_size)) - for (auto& connector : connectors) + connector.height_tolerance = 0.01f * m_connector_depth_ratio; + } + if (render_slider_double_input(_u8L("Size"), m_connector_size, m_connector_size_tolerance)) + for (auto& connector : connectors) { connector.radius = float(m_connector_size * 0.5); + connector.radius_tolerance = 0.01f * m_connector_size_tolerance; + } m_imgui->disabled_end(); @@ -1710,7 +1724,9 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi //std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); - connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio)); + connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), + float(m_connector_size * 0.5), float(m_connector_depth_ratio), + float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance)); update_model_object(); m_selected.push_back(false); assert(m_selected.size() == connectors.size()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index b562396c1..91f8749e7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -81,6 +81,9 @@ class GLGizmoCut3D : public GLGizmoBase double m_connector_depth_ratio{ 3.0 }; double m_connector_size{ 2.5 }; + int m_connector_depth_ratio_tolerance{ 10 }; + int m_connector_size_tolerance{ 0 }; + float m_label_width{ 150.0 }; float m_control_width{ 200.0 }; bool m_imperial_units{ false }; @@ -174,7 +177,7 @@ private: void set_center(const Vec3d& center); bool render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); bool render_double_input(const std::string& label, double& value_in); - bool render_slicer_double_input(const std::string& label, double& value_in); + bool render_slider_double_input(const std::string& label, double& value_in, int& tolerance_in); void render_move_center_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); bool render_revert_button(const std::string& label); From e990254d52db00ad84d033586d7231a22d899a13 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 26 Jul 2022 16:31:01 +0200 Subject: [PATCH 52/97] Cut WIP: set attributes for each connector separately. + Allow select/deselect several connectors and apply size/depth for selected group of connectors --- src/libslic3r/Model.cpp | 12 +- src/libslic3r/Model.hpp | 105 ++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 191 +++++++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 8 +- 4 files changed, 172 insertions(+), 144 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b18756a5e..79273bbbe 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1378,7 +1378,7 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn return connector_mesh; } -void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes) +void ModelObject::apply_cut_connectors(const std::string& name) { // discard old connector markers for volumes for (ModelVolume* volume : volumes) @@ -1387,11 +1387,9 @@ void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttr if (cut_connectors.empty()) return; - indexed_triangle_set connector_mesh = get_connector_mesh(connector_attributes); - size_t connector_id = cut_id.connectors_cnt(); for (const CutConnector& connector : cut_connectors) { - TriangleMesh mesh = TriangleMesh(connector_mesh); + TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); // Mesh will be centered when loading. ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); @@ -1403,7 +1401,7 @@ void ModelObject::apply_cut_connectors(const std::string& name, CutConnectorAttr Vec3d::Ones() )); - new_volume->cut_info = { true, connector.radius_tolerance, connector.height_tolerance }; + new_volume->cut_info = { true, connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; new_volume->name = name + "-" + std::to_string(++connector_id); } cut_id.increase_connectors_cnt(cut_connectors.size()); @@ -1541,13 +1539,13 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const ModelVolume* vol = lower->add_volume(*volume); vol->set_transformation(volume_matrix); - if (attributes.has(ModelObjectCutAttribute::CreateDowels)) + if (volume->cut_info.connector_type == CutConnectorType::Dowel) apply_tolerance(vol); else // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug vol->set_type(ModelVolumeType::MODEL_PART); } - if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { + if (volume->cut_info.connector_type == CutConnectorType::Dowel) { // add one more solid part same as connector if this connector is a dowel ModelVolume* vol = dowels->add_volume(*volume); vol->set_type(ModelVolumeType::MODEL_PART); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3242dd255..a82c08136 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -219,48 +219,6 @@ private: friend class ModelObject; }; -struct CutConnector -{ - Vec3d pos; - Vec3d rotation; - float radius; - float height; - float radius_tolerance;// [0.f : 1.f] - float height_tolerance;// [0.f : 1.f] - bool failed = false; - - CutConnector() - : pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) - {} - - CutConnector(Vec3d p, Vec3d rot, float r, float h, float rt, float ht, bool fl = false) - : pos(p), rotation(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), failed(fl) - {} - - CutConnector(const CutConnector& rhs) : - CutConnector(rhs.pos, rhs.rotation, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.failed) {} - - bool operator==(const CutConnector& sp) const; - - bool operator!=(const CutConnector& sp) const { return !(sp == (*this)); } -/* - bool is_inside(const Vec3f& pt) const; - - bool get_intersections(const Vec3f& s, const Vec3f& dir, - std::array, 2>& out) const; - - indexed_triangle_set to_mesh() const; -*/ - template inline void serialize(Archive& ar) - { - ar(pos, rotation, radius, height, radius_tolerance, height_tolerance, failed); - } - - static constexpr size_t steps = 32; -}; - -using CutConnectors = std::vector; - enum class CutConnectorType : int { Plug , Dowel @@ -283,8 +241,8 @@ enum class CutConnectorShape : int { struct CutConnectorAttributes { CutConnectorType type{ CutConnectorType::Plug }; - CutConnectorStyle style{ CutConnectorStyle::Prizm}; - CutConnectorShape shape{CutConnectorShape::Circle}; + CutConnectorStyle style{ CutConnectorStyle::Prizm }; + CutConnectorShape shape{ CutConnectorShape::Circle }; CutConnectorAttributes() {} @@ -294,8 +252,55 @@ struct CutConnectorAttributes CutConnectorAttributes(const CutConnectorAttributes& rhs) : CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {} + + bool operator==(const CutConnectorAttributes& other) const; + + bool operator!=(const CutConnectorAttributes& other) const { return !(other == (*this)); } + + bool operator<(const CutConnectorAttributes& other) const { + return this->type < other.type || + (this->type == other.type && this->style < other.style) || + (this->type == other.type && this->style == other.style && this->shape < other.shape); + } + + template inline void serialize(Archive& ar) { + ar(type, style, shape); + } }; +struct CutConnector +{ + Vec3d pos; + Vec3d rotation; + float radius; + float height; + float radius_tolerance;// [0.f : 1.f] + float height_tolerance;// [0.f : 1.f] + CutConnectorAttributes attribs; + + CutConnector() + : pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) + {} + + CutConnector(Vec3d p, Vec3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes) + : pos(p), rotation(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes) + {} + + CutConnector(const CutConnector& rhs) : + CutConnector(rhs.pos, rhs.rotation, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {} + + bool operator==(const CutConnector& other) const; + + bool operator!=(const CutConnector& other) const { return !(other == (*this)); } + + template inline void serialize(Archive& ar) { + ar(pos, rotation, radius, height, radius_tolerance, height_tolerance, attribs); + } +}; + +using CutConnectors = std::vector; + + // Declared outside of ModelVolume, so it could be forward declared. enum class ModelVolumeType : int { INVALID = -1, @@ -436,7 +441,7 @@ public: size_t parts_count() const; ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); - void apply_cut_connectors(const std::string& name, CutConnectorAttributes connector_attributes); + void apply_cut_connectors(const std::string& name); // invalidate cut state for this and related objects from the whole model void invalidate_cut(); void synchronize_model_after_cut(); @@ -710,12 +715,14 @@ public: // It contains information about connetors struct CutInfo { - bool is_connector {false}; - float radius_tolerance;// [0.f : 1.f] - float height_tolerance;// [0.f : 1.f] + bool is_connector{ false }; + CutConnectorType connector_type{ CutConnectorType::Plug }; + float radius_tolerance;// [0.f : 1.f] + float height_tolerance;// [0.f : 1.f] void discard() { is_connector = false; } - } cut_info; + }; + CutInfo cut_info; // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 417cf6037..21917b1d6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -226,12 +226,15 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) if (mouse_event.Moving() && !cut_line_processing()) return false; - if (use_grabbers(mouse_event)) - return true; - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); Vec2d mouse_pos = mouse_coord.cast(); + if (use_grabbers(mouse_event)) { + if (m_hover_id >= m_connectors_group_id && mouse_event.LeftUp() && !mouse_event.ShiftDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + return true; + } + static bool pending_right_up = false; if (mouse_event.LeftDown()) { bool grabber_contains_mouse = (get_hover_id() != -1); @@ -407,6 +410,9 @@ bool GLGizmoCut3D::render_combo(const std::string& label, const std::vectorradio_button(m_connector_types[size_t(type)], m_connector_type == type)) { m_connector_type = type; update_connector_shape(); + return true; } + return false; } void GLGizmoCut3D::render_connect_mode_radio_button(CutConnectorMode mode) @@ -1268,31 +1276,63 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) render_connect_mode_radio_button(CutConnectorMode::Auto); render_connect_mode_radio_button(CutConnectorMode::Manual); */ + + if (m_selected_count == 1) + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) { + auto& connector = connectors[idx]; + m_connector_depth_ratio = connector.height; + m_connector_depth_ratio_tolerance = 100 * connector.height_tolerance; + m_connector_size = 2. * connector.radius; + m_connector_size_tolerance = 100 * connector.radius_tolerance; + m_connector_type = connector.attribs.type; + m_connector_style = size_t(connector.attribs.style); + m_connector_shape_id = size_t(connector.attribs.shape); + + break; + } + m_imgui->text(_L("Type")); - render_connect_type_radio_button(CutConnectorType::Plug); - render_connect_type_radio_button(CutConnectorType::Dowel); + bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); + type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); + if (type_changed) + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) + connectors[idx].attribs.type = CutConnectorType(m_connector_type); if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) - update_connector_shape(); - if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) - update_connector_shape(); + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) + connectors[idx].attribs.style = CutConnectorStyle(m_connector_style) ; + + if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) + connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); + + if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) { + auto& connector = connectors[idx]; + connector.height = float(m_connector_depth_ratio); + connector.height_tolerance = 0.01f * m_connector_depth_ratio_tolerance; + } - if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) - for (auto& connector : connectors) { - connector.height = float(m_connector_depth_ratio); - connector.height_tolerance = 0.01f * m_connector_depth_ratio; - } if (render_slider_double_input(_u8L("Size"), m_connector_size, m_connector_size_tolerance)) - for (auto& connector : connectors) { - connector.radius = float(m_connector_size * 0.5); - connector.radius_tolerance = 0.01f * m_connector_size_tolerance; - } + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) { + auto& connector = connectors[idx]; + connector.radius = float(m_connector_size * 0.5); + connector.radius_tolerance = 0.01f * m_connector_size_tolerance; + } m_imgui->disabled_end(); if (m_imgui->button(_L("Confirm connectors"))) { m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_connectors_editing = false; + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected_count = 0; } m_parent.request_extra_frame(); } @@ -1380,7 +1420,6 @@ void GLGizmoCut3D::render_connectors(bool picking) m_selected.resize(connectors.size(), false); } -#if ENABLE_LEGACY_OPENGL_REMOVAL GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; @@ -1388,19 +1427,8 @@ void GLGizmoCut3D::render_connectors(bool picking) shader->start_using(); ScopeGuard guard([shader]() { shader->stop_using(); }); -#else - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader) - shader->start_using(); - ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES ColorRGBA render_color; @@ -1417,13 +1445,12 @@ void GLGizmoCut3D::render_connectors(bool picking) for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; -// const bool& point_selected = m_selected[i]; double height = connector.height; // recalculate connector position to world position Vec3d pos = connector.pos + instance_offset; - if (m_connector_type == CutConnectorType::Dowel && - m_connector_style == size_t(CutConnectorStyle::Prizm)) { + if (connector.attribs.type == CutConnectorType::Dowel && + connector.attribs.style == CutConnectorStyle::Prizm) { pos -= height * normal; height *= 2; } @@ -1435,7 +1462,13 @@ void GLGizmoCut3D::render_connectors(bool picking) else { if (size_t(m_hover_id- m_connectors_group_id) == i) render_color = ColorRGBA::CYAN(); - else { // neither hover nor picking + else if (m_selected[i]) + render_color = ColorRGBA::DARK_GRAY(); + else // neither hover nor picking + render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); + + // ! #ysFIXME rework get_volume_transformation + if (0) { // else { // neither hover nor picking int mesh_id = -1; for (const ModelVolume* mv : mo->volumes) { ++mesh_id; @@ -1454,42 +1487,18 @@ void GLGizmoCut3D::render_connectors(bool picking) } } -#if ENABLE_GL_SHADERS_ATTRIBUTES - m_connector_shape.set_color(render_color); + m_shapes[connector.attribs].set_color(render_color); const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( - Vec3d(pos.x(), pos.y(), pos.z()), + pos, Geometry::Transformation(m_rotation_m).get_rotation(), - Vec3d(connector.radius, connector.radius, height), - Vec3d::Ones() + Vec3d(connector.radius, connector.radius, height) ); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - const_cast(&m_connector_shape)->set_color(-1, render_color); - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(pos.x(), pos.y(), pos.z())); - - const Vec3d& angles = m_rotation_gizmo.get_rotation(); - glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); - - glsafe(::glTranslated(0., 0., -0.5*connector.height)); - glsafe(::glScaled(connector.radius, connector.radius, height)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_connector_shape.render(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif //!ENABLE_GL_SHADERS_ATTRIBUTES + m_shapes[connector.attribs].render(); } - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif //!ENABLE_GL_SHADERS_ATTRIBUTES } bool GLGizmoCut3D::can_perform_cut() const @@ -1535,9 +1544,10 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) for (CutConnector& connector : mo->cut_connectors) { connector.rotation = rotation; - if (m_connector_type == CutConnectorType::Dowel) { - if (m_connector_style == size_t(CutConnectorStyle::Prizm)) + if (connector.attribs.type == CutConnectorType::Dowel) { + if (connector.attribs.style == CutConnectorStyle::Prizm) connector.height *= 2; + create_dowels_as_separate_object = true; } else { // culculate shift of the connector center regarding to the position on the cut plane @@ -1551,9 +1561,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) connector.pos += norm * (0.5 * connector.height); } } - mo->apply_cut_connectors(_u8L("Connector"), CutConnectorAttributes(CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); - if (m_connector_type == CutConnectorType::Dowel) - create_dowels_as_separate_object = true; + mo->apply_cut_connectors(_u8L("Connector")); } } @@ -1623,15 +1631,15 @@ void GLGizmoCut3D::reset_connectors() void GLGizmoCut3D::update_connector_shape() { - if (m_connector_shape.is_initialized()) - m_connector_shape.reset(); - - const indexed_triangle_set its = ModelObject::get_connector_mesh({ m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }); - m_connector_shape.init_from(its); + CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; + if (m_shapes.find(attribs) == m_shapes.end()) { + const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); + m_shapes[attribs].init_from(its); + } + const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); m_connector_mesh.clear(); m_connector_mesh = TriangleMesh(its); - } void GLGizmoCut3D::update_model_object() const @@ -1709,14 +1717,9 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - // left down without selection rectangle - place connector on the cut plane: - if (action == SLAGizmoEventType::LeftDown && /*!m_selection_rectangle.is_dragging() && */!shift_down && m_connectors_editing) { - // If any point is in hover state, this should initiate its move - return control back to GLCanvas: - if (m_hover_id != -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { + if (action == SLAGizmoEventType::LeftDown && !shift_down && m_connectors_editing) { + // If there is no selection and no hovering, add new point + if (m_hover_id == -1 && !control_down && !alt_down) { std::pair pos_and_normal; if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal)) { const Vec3d& hit = pos_and_normal.first; @@ -1726,12 +1729,15 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio), - float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance)); + float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance), + CutConnectorAttributes( CutConnectorType(m_connector_type), + CutConnectorStyle(m_connector_style), + CutConnectorShape(m_connector_shape_id))); update_model_object(); - m_selected.push_back(false); + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected.push_back(true); assert(m_selected.size() == connectors.size()); m_parent.set_as_dirty(); - m_wait_for_up_event = true; return true; } @@ -1739,6 +1745,23 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi } return true; } + else if (action == SLAGizmoEventType::LeftUp && !shift_down && m_connectors_editing) { + if (m_hover_id >= m_connectors_group_id) { + if (alt_down) { + m_selected[m_hover_id - m_connectors_group_id] = false; + --m_selected_count; + } + else { + if (!control_down) { + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected_count = 0; + } + m_selected[m_hover_id - m_connectors_group_id] = true; + ++m_selected_count; + } + return true; + } + } else if (action == SLAGizmoEventType::RightDown && !shift_down && m_connectors_editing) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id < m_connectors_group_id) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 91f8749e7..fecb3e846 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -12,6 +12,7 @@ namespace Slic3r { enum class CutConnectorType : int; class ModelVolume; +struct CutConnectorAttributes; namespace GUI { class Selection; @@ -44,7 +45,7 @@ class GLGizmoCut3D : public GLGizmoBase Transform3d m_start_dragging_m{ Transform3d::Identity() }; double m_angle{ 0.0 }; - GLModel m_connector_shape; + std::map m_shapes; TriangleMesh m_connector_mesh; // workaround for using of the clipping plane normal Vec3d m_clp_normal{ Vec3d::Ones() }; @@ -90,8 +91,7 @@ class GLGizmoCut3D : public GLGizmoBase bool force_update_clipper_on_render{false}; mutable std::vector m_selected; // which pins are currently selected - bool m_selection_empty = true; - bool m_wait_for_up_event = false; + int m_selected_count{ 0 }; bool m_has_invalid_connector{ false }; @@ -181,7 +181,7 @@ private: void render_move_center_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); bool render_revert_button(const std::string& label); - void render_connect_type_radio_button(CutConnectorType type); + bool render_connect_type_radio_button(CutConnectorType type); Transform3d get_volume_transformation(const ModelVolume* volume) const; void render_connectors(bool picking); From 27f7a8da0f12ec344c646ae82575d2b472099a58 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 27 Jul 2022 13:53:54 +0200 Subject: [PATCH 53/97] Cut WIP: Added shortcuts for "Edit connectors" ImGuiDialog + Added processing for the Ctrl+A ("Select All connectors") --- resources/icons/collapse_btn.svg | 13 +++++++++++ resources/icons/expand_btn.svg | 12 ++++++++++ src/imgui/imconfig.h | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 28 ++++++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 +++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- src/slic3r/GUI/ImGuiWrapper.cpp | 2 ++ 7 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 resources/icons/collapse_btn.svg create mode 100644 resources/icons/expand_btn.svg diff --git a/resources/icons/collapse_btn.svg b/resources/icons/collapse_btn.svg new file mode 100644 index 000000000..4ee221a44 --- /dev/null +++ b/resources/icons/collapse_btn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/icons/expand_btn.svg b/resources/icons/expand_btn.svg new file mode 100644 index 000000000..32d7f9959 --- /dev/null +++ b/resources/icons/expand_btn.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 9a29789d3..f9fdf575b 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -170,6 +170,8 @@ namespace ImGui const wchar_t LegendShells = 0x2616; const wchar_t LegendToolMarker = 0x2617; const wchar_t WarningMarkerSmall = 0x2618; + const wchar_t ExpandBtn = 0x2619; + const wchar_t CollapseBtn = 0x2620; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 21917b1d6..71a8bd5ba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -783,6 +783,17 @@ bool GLGizmoCut3D::on_init() m_grabbers.emplace_back(); m_shortcut_key = WXK_CONTROL_C; + // initiate info shortcuts + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + + 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(ctrl + _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"))); + return true; } @@ -1258,7 +1269,18 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->button(_L("Add/Edit connectors"))) m_connectors_editing = true; m_imgui->disabled_end(); - } else { // connectors mode + } + else { // connectors mode + if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) + m_show_shortcuts = !m_show_shortcuts; + + if (m_show_shortcuts) + for (const auto& shortcut : m_shortcuts ){ + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, shortcut.first); + ImGui::SameLine(m_label_width); + m_imgui->text(shortcut.second); + } + m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); // Connectors section ImGui::Separator(); @@ -1778,6 +1800,10 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi return true; } + else if (action == SLAGizmoEventType::SelectAll) { + std::fill(m_selected.begin(), m_selected.end(), true); + return true; + } return false; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index fecb3e846..170928a07 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -98,6 +98,9 @@ class GLGizmoCut3D : public GLGizmoBase Matrix3d m_rotation_matrix; Vec3d m_rotations{ Vec3d::Zero() }; + bool m_show_shortcuts{ false }; + std::vector> m_shortcuts; + enum class CutMode { cutPlanar , cutGrig diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 09276ef57..070417520 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -505,7 +505,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) #endif /* __APPLE__ */ { // Sla gizmo selects all support points - if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::SelectAll)) + if ((m_current == SlaSupports || m_current == Hollow || m_current == Cut) && gizmo_event(SLAGizmoEventType::SelectAll)) processed = true; break; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 8c27db56e..8b4befa77 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -76,6 +76,8 @@ static const std::map font_icons = { #endif // ENABLE_LEGEND_TOOLBAR_ICONS {ImGui::RevertButton , "undo" }, {ImGui::WarningMarkerSmall , "notification_warning" }, + {ImGui::ExpandBtn , "expand_btn" }, + {ImGui::CollapseBtn , "collapse_btn" }, }; static const std::map font_icons_large = { From 05c22604fbba81cb3ba0ec197017a1fdc66e80b9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 27 Jul 2022 15:21:37 +0200 Subject: [PATCH 54/97] Cut WIP: Suppress use connectors for SLA mode --- src/libslic3r/Model.cpp | 8 ++++++++ src/libslic3r/Model.hpp | 2 ++ src/slic3r/GUI/GUI_App.cpp | 7 +++++++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 16 ++++++++++------ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 79273bbbe..d62f56105 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2682,6 +2682,14 @@ bool model_has_multi_part_objects(const Model &model) return false; } +bool model_has_connectors(const Model &model) +{ + for (const ModelObject *model_object : model.objects) + if (!model_object->cut_connectors.empty()) + return true; + return false; +} + bool model_has_advanced_features(const Model &model) { auto config_is_advanced = [](const ModelConfig &config) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a82c08136..d056368bd 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1338,6 +1338,8 @@ extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const Mod // If the model has multi-part objects, then it is currently not supported by the SLA mode. // Either the model cannot be loaded, or a SLA printer has to be activated. bool model_has_multi_part_objects(const Model &model); +// If the model has objects with cut connectrs, then it is currently not supported by the SLA mode. +bool model_has_connectors(const Model& model); // If the model has advanced features, then it cannot be processed in simple mode. bool model_has_advanced_features(const Model &model); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 42e325de7..e4e5fffcf 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2869,6 +2869,13 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) caption); return false; } + if (model_has_connectors(model())) { + show_info(nullptr, + _L("SLA technology doesn't support cut with connectors") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 71a8bd5ba..490f67a60 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1176,6 +1176,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) bool cut_clicked = false; bool revert_move{ false }; bool revert_rotation{ false }; + bool fff_printer = wxGetApp().plater()->printer_technology() == ptFFF; if (! m_connectors_editing) { if (m_mode == size_t(CutMode::cutPlanar)) { @@ -1263,12 +1264,14 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->disabled_end(); } - ImGui::Separator(); + if (fff_printer) { + ImGui::Separator(); - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); - if (m_imgui->button(_L("Add/Edit connectors"))) - m_connectors_editing = true; - m_imgui->disabled_end(); + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); + if (m_imgui->button(_L("Add/Edit connectors"))) + m_connectors_editing = true; + m_imgui->disabled_end(); + } } else { // connectors mode if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) @@ -1361,7 +1364,8 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); - m_imgui->text(m_has_invalid_connector ? wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.") : wxString()); + if (fff_printer) + m_imgui->text(m_has_invalid_connector ? wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.") : wxString()); if (!m_connectors_editing) { m_imgui->disabled_begin(!can_perform_cut()); cut_clicked = m_imgui->button(_L("Perform cut")); From 31800bb85dccd3dc3161c20b297d5238ad934581 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 28 Jul 2022 14:23:51 +0200 Subject: [PATCH 55/97] GizmoScale: Suppress ununiversal scale for cut objects + Gizmos/GLGizmoRotate: Deleted changes which was made for GizmoCut, but aren't used any more --- src/slic3r/GUI/GUI_ObjectList.cpp | 7 ++++++- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 17 +++++------------ src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 5 ----- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 6 ++++++ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 5 +++++ src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 + 8 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 23ee84a5c..87992e958 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -12,6 +12,7 @@ #include "MainFrame.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "Gizmos/GLGizmoCut.hpp" +#include "Gizmos/GLGizmoScale.hpp" #include "OptionsGroup.hpp" #include "Tab.hpp" @@ -2503,6 +2504,8 @@ void ObjectList::part_selection_changed() const auto item = GetSelection(); + GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); + if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { og_name = _L("Group manipulation"); @@ -2579,7 +2582,6 @@ void ObjectList::part_selection_changed() info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam : info_type == InfoItemType::Cut ? GLGizmosManager::EType::Cut : GLGizmosManager::EType::MmuSegmentation; - GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); if (gizmos_mgr.get_current_type() != gizmo_type) gizmos_mgr.open_gizmo(gizmo_type); if (info_type == InfoItemType::Cut) { @@ -2656,6 +2658,9 @@ void ObjectList::part_selection_changed() if (disable_ununiform_scale) wxGetApp().obj_manipul()->DisableUnuniformScale(); } + + if (GLGizmoScale3D* scale = dynamic_cast(gizmos_mgr.get_gizmo(GLGizmosManager::Scale))) + scale->enable_ununiversal_scale(!disable_ununiform_scale); } if (update_and_show_settings) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c89ee949a..a0c0cb00a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -602,7 +602,7 @@ void ObjectManipulation::DisableScale() void ObjectManipulation::DisableUnuniformScale() { - m_lock_bnt->disable(); + m_lock_bnt->Enable(false); } void ObjectManipulation::update_ui_from_settings() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index d82fbd587..72bc955d0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -58,12 +58,6 @@ void GLGizmoRotate::set_angle(double angle) m_angle = angle; } -void GLGizmoRotate::set_center(const Vec3d& center) -{ - m_forced_center = center; - m_has_forced_center = true; -} - std::string GLGizmoRotate::get_tooltip() const { std::string axis; @@ -301,13 +295,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { m_bounding_box = selection.get_bounding_box(); - m_center = m_has_forced_center ? m_forced_center : m_bounding_box.center(); + m_center = m_bounding_box.center(); } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_first_volume(); m_bounding_box = v.transformed_convex_hull_bounding_box( v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); - m_center = v.world_matrix() * (m_has_forced_center ? m_forced_center : m_bounding_box.center()); + m_center = v.world_matrix() * m_bounding_box.center(); } else { m_bounding_box.reset(); @@ -318,7 +312,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) } const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); - m_center = inst_trafo.get_matrix_no_scaling_factor() * (m_has_forced_center ? m_forced_center : m_bounding_box.center()); + m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); } m_radius = Offset + m_bounding_box.radius(); @@ -867,7 +861,7 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { - if (mouse_event.Dragging() && m_dragging && !m_use_only_grabbers) { + if (mouse_event.Dragging() && m_dragging) { // Apply new temporary rotations #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; @@ -954,8 +948,7 @@ void GLGizmoRotate3D::on_start_dragging() void GLGizmoRotate3D::on_stop_dragging() { assert(0 <= m_hover_id && m_hover_id < 3); - if (!m_use_only_grabbers) - m_parent.do_rotate(L("Gizmo-Rotate")); + m_parent.do_rotate(L("Gizmo-Rotate")); m_gizmos[m_hover_id].stop_dragging(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 9db024b14..5f1de8151 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -34,7 +34,6 @@ private: float m_snap_coarse_out_radius{ 0.0f }; float m_snap_fine_in_radius{ 0.0f }; float m_snap_fine_out_radius{ 0.0f }; - bool m_has_forced_center{false}; Vec3d m_forced_center{ Vec3d::Zero() }; #if ENABLE_WORLD_COORDINATE BoundingBoxf3 m_bounding_box; @@ -70,7 +69,6 @@ public: double get_angle() const { return m_angle; } void set_angle(double angle); - void set_center(const Vec3d& center); std::string get_tooltip() const override; @@ -135,15 +133,12 @@ private: class GLGizmoRotate3D : public GLGizmoBase { std::array m_gizmos; - bool m_use_only_grabbers{ false }; public: GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); } - void set_center(const Vec3d& center) { m_gizmos[X].set_center(center); m_gizmos[Y].set_center(center); m_gizmos[Z].set_center(center); } - void use_only_grabbers() { m_use_only_grabbers = true; } std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 4312a6122..dae862f8f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -104,6 +104,12 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) return use_grabbers(mouse_event); } +void GLGizmoScale3D::enable_ununiversal_scale(bool enable) +{ + for (unsigned int i = 0; i < 6; ++i) + m_grabbers[i].enabled = enable; +} + void GLGizmoScale3D::data_changed() { #if ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index fb4ff09b6..a18b176ad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -85,6 +85,7 @@ public: bool on_mouse(const wxMouseEvent &mouse_event) override; void data_changed() override; + void enable_ununiversal_scale(bool enable); protected: virtual bool on_init() override; virtual std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 070417520..9e0138262 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -1042,6 +1042,11 @@ GLGizmoBase* GLGizmosManager::get_current() const return ((m_current == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[m_current].get(); } +GLGizmoBase* GLGizmosManager::get_gizmo(GLGizmosManager::EType type) const +{ + return ((type == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[type].get(); +} + GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& gizmo_name) const { std::vector selectable_idxs = get_selectable_idxs(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 2e9e6bb65..6e2a19bc0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -199,6 +199,7 @@ public: EType get_current_type() const { return m_current; } GLGizmoBase* get_current() const; + GLGizmoBase* get_gizmo(GLGizmosManager::EType type) const; EType get_gizmo_from_name(const std::string& gizmo_name) const; bool is_running() const; From a7930cdedd8c72511d5e0f22c7e8178a87a782b4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 28 Jul 2022 17:01:05 +0200 Subject: [PATCH 56/97] Cut WIP: Cut by line: Some rework for its behavior. Line can be drawn by : Shift + 1. LeftDown, Dragging, LeftUp 2. LeftDown, LeftUp, Move, LeftDown --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 75 +++++++++++----------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 490f67a60..da06551b8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -223,12 +223,23 @@ std::string GLGizmoCut3D::get_tooltip() const bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) { - if (mouse_event.Moving() && !cut_line_processing()) - return false; - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); Vec2d mouse_pos = mouse_coord.cast(); + if (mouse_event.ShiftDown() && mouse_event.LeftDown()) + return gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + if (cut_line_processing()) { + if (mouse_event.ShiftDown()) { + if (mouse_event.Moving()|| mouse_event.Dragging()) + return gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + if (mouse_event.LeftUp()) + return gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + } + discard_cut_line_processing(); + } + else if (mouse_event.Moving()) + return false; + if (use_grabbers(mouse_event)) { if (m_hover_id >= m_connectors_group_id && mouse_event.LeftUp() && !mouse_event.ShiftDown()) gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); @@ -281,11 +292,6 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return true; } } - else if (mouse_event.Moving()) { - // draw cut line - gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); - return true; - } else if (pending_right_up && mouse_event.RightUp()) { pending_right_up = false; return true; @@ -543,7 +549,6 @@ void GLGizmoCut3D::render_cut_plane() if (cut_line_processing()) return; -#if ENABLE_LEGACY_OPENGL_REMOVAL GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader == nullptr) return; @@ -554,7 +559,7 @@ void GLGizmoCut3D::render_cut_plane() glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( m_plane_center, @@ -564,14 +569,6 @@ void GLGizmoCut3D::render_cut_plane() ); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - const Vec3d& angles = m_rotation_gizmo.get_rotation(); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_plane_center.x(), m_plane_center.y(), m_plane_center.z())); - glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (!m_plane.is_initialized()) { GLModel::Geometry init_data; @@ -580,17 +577,12 @@ void GLGizmoCut3D::render_cut_plane() init_data.reserve_vertices(4); init_data.reserve_indices(6); - const BoundingBoxf3 bb = bounding_box(); - const float min_x = bb.min.x() - Margin - m_plane_center.x(); - const float max_x = bb.max.x() + Margin - m_plane_center.x(); - const float min_y = bb.min.y() - Margin - m_plane_center.y(); - const float max_y = bb.max.y() + Margin - m_plane_center.y(); - // vertices - init_data.add_vertex(Vec3f(min_x, min_y, 0.0)); - init_data.add_vertex(Vec3f(max_x, min_y, 0.0)); - init_data.add_vertex(Vec3f(max_x, max_y, 0.0)); - init_data.add_vertex(Vec3f(min_x, max_y, 0.0)); + float radius = (float)bounding_box().radius(); + init_data.add_vertex(Vec3f(-radius, -radius, 0.0)); + init_data.add_vertex(Vec3f( radius, -radius, 0.0)); + init_data.add_vertex(Vec3f( radius, radius, 0.0)); + init_data.add_vertex(Vec3f(-radius, radius, 0.0)); // indices init_data.add_triangle(0, 1, 2); @@ -600,19 +592,6 @@ void GLGizmoCut3D::render_cut_plane() } m_plane.render(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif //!ENABLE_GL_SHADERS_ATTRIBUTES -#else - // Draw the cutting plane - ::glBegin(GL_QUADS); - ::glColor4fv(PLANE_COLOR.data()); - ::glVertex3f(min_x, min_y, plane_center.z()); - ::glVertex3f(max_x, min_y, plane_center.z()); - ::glVertex3f(max_x, max_y, plane_center.z()); - ::glVertex3f(min_x, max_y, plane_center.z()); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); @@ -1685,6 +1664,11 @@ bool GLGizmoCut3D::cut_line_processing() const return m_line_beg != Vec3d::Zero(); } +void GLGizmoCut3D::discard_cut_line_processing() +{ + m_line_beg = m_line_end = Vec3d::Zero(); +} + bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position) { const float sla_shift = m_c->selection_info()->get_sla_shift(); @@ -1710,9 +1694,10 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse if (cut_line_processing()) { m_line_end = pt; - if (action == SLAGizmoEventType::LeftDown) { - Vec3d point = m_line_end; + if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) { Vec3d line_dir = m_line_end - m_line_beg; + if (line_dir.norm() < 3.0) + return true; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by line"), UndoRedo::SnapshotType::GizmoAction); Vec3d cross_dir = line_dir.cross(dir).normalized(); @@ -1725,7 +1710,7 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse set_center(m_plane_center + cross_dir * (cross_dir.dot(pt - m_plane_center))); - m_line_end = m_line_beg = Vec3d::Zero(); + discard_cut_line_processing(); } return true; } @@ -1738,7 +1723,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi return false; if ( m_hover_id < 0 && shift_down && ! m_connectors_editing && - (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::Moving) ) + (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::Moving) ) return process_cut_line(action, mouse_position); CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 170928a07..863461d50 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -190,6 +190,7 @@ private: bool can_perform_cut() const; bool cut_line_processing() const; + void discard_cut_line_processing(); void render_cut_plane(); void render_cut_center_graber(bool picking = false); From dda346b70af9a58e68aef626316ddb60398ead51 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 8 Aug 2022 14:58:13 +0200 Subject: [PATCH 57/97] After merge fixes --- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 4 +++- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 34 +++++++++++++-------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 - src/slic3r/GUI/MeshUtils.cpp | 8 +++---- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 5c1a7c346..5122ac320 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -403,7 +403,9 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { } if (mouse_event.LeftDown()) { - Selection &selection = m_parent.get_selection(); + Selection &selection = m_parent.get_selection(); + if (!selection.is_empty() && m_hover_id != -1 && + (m_grabbers.empty() || m_hover_id < static_cast(m_grabbers.size()))) { selection.setup_cache(); m_dragging = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 757f4e571..31a3276c1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -214,8 +214,8 @@ std::string GLGizmoCut3D::get_tooltip() const } if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { std::string axis = m_hover_id == X ? "X" : "Y"; -// return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L(""); - return axis + ": " + format(float(Geometry::rad2deg(m_angle)), 1) + _u8L(""); +// return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L("°"); + return axis + ": " + format(float(Geometry::rad2deg(m_angle)), 1) + _u8L("°"); } return tooltip; @@ -608,12 +608,10 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) return; #if ENABLE_GL_CORE_PROFILE - if (!OpenGLManager::get_gl_info().is_core_profile()) + if (!OpenGLManager::get_gl_info().is_core_profile()) #endif // ENABLE_GL_CORE_PROFILE - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_grabber_connection.is_initialized() || is_changed) { - m_grabber_connection.reset(); + glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); + ColorRGBA color = picking ? picking_decode(BASE_ID - Z) : m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; @@ -753,7 +751,7 @@ void GLGizmoCut3D::render_cut_line() const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cut_line.reset(); m_cut_line.init_from(its_make_line((Vec3f)m_line_beg.cast(), (Vec3f)m_line_end.cast())); @@ -822,26 +820,32 @@ void GLGizmoCut3D::on_set_state() } else m_c->object_clipper()->release(); -#endif // !ENABLE_GL_CORE_PROFILE + force_update_clipper_on_render = m_state == On; } #if ENABLE_RAYCAST_PICKING -void GLGizmoCut::on_register_raycasters_for_picking() +void GLGizmoCut3D::on_register_raycasters_for_picking() { // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account m_parent.set_raycaster_gizmos_on_top(true); } -void GLGizmoCut::on_unregister_raycasters_for_picking() +void GLGizmoCut3D::on_unregister_raycasters_for_picking() { m_parent.set_raycaster_gizmos_on_top(false); } #else +void GLGizmoCut3D::on_render_for_picking() +{ + render_cut_center_graber(true); + render_connectors(true); +} +#endif // ENABLE_RAYCAST_PICKING + void GLGizmoCut3D::on_set_hover_id() { } -#endif // ENABLE_RAYCAST_PICKING bool GLGizmoCut3D::on_is_activable() const { @@ -1139,12 +1143,6 @@ void GLGizmoCut3D::on_render() render_cut_line(); } -void GLGizmoCut3D::on_render_for_picking() -{ - render_cut_center_graber(true); - render_connectors(true); -} - void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) { static float last_y = 0.0f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 5fc43462a..3987081ef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -106,7 +106,6 @@ class GLGizmoCut3D : public GLGizmoBase , cutGrig //,cutRadial //,cutModular - std::vector volumes_trafos; }; enum class CutConnectorMode { diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index bd38354aa..bbf1f7652 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -127,11 +127,9 @@ void MeshClipper::render_contour() GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader != nullptr) { shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES m_model_expanded.set_color(color); m_model_expanded.render(); shader->stop_using(); @@ -418,7 +416,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& Vec3d direction; line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); - std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector hits = m_emesh.query_ray_hits(point, direction); if (hits.empty()) return false; // no intersection found @@ -434,8 +432,8 @@ bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Tr { point = trafo.inverse() * point; - std::vector hits = m_emesh.query_ray_hits(point, direction); - std::vector neg_hits = m_emesh.query_ray_hits(point, -direction); + std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector neg_hits = m_emesh.query_ray_hits(point, -direction); return !hits.empty() && !neg_hits.empty(); } From df8f7e1069c57f944cb0a57197b08d5b2b2fcee7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 10 Aug 2022 11:25:04 +0200 Subject: [PATCH 58/97] Cut WIP: Raycasters for picking are applied + Added snapping for rotation of the cut plane --- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 353 +++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 30 ++- 3 files changed, 280 insertions(+), 111 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 5122ac320..c3a443d0b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -404,14 +404,14 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { if (mouse_event.LeftDown()) { Selection &selection = m_parent.get_selection(); - if (!selection.is_empty() && m_hover_id != -1 && - (m_grabbers.empty() || m_hover_id < static_cast(m_grabbers.size()))) { + if (!selection.is_empty() && m_hover_id != -1 /* && + (m_grabbers.empty() || m_hover_id < static_cast(m_grabbers.size()))*/) { selection.setup_cache(); m_dragging = true; for (auto &grabber : m_grabbers) grabber.dragging = false; - if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) - m_grabbers[m_hover_id].dragging = true; +// if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) +// m_grabbers[m_hover_id].dragging = true; on_start_dragging(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 31a3276c1..8d38bb295 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -241,8 +241,14 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return false; if (use_grabbers(mouse_event)) { - if (m_hover_id >= m_connectors_group_id && mouse_event.LeftUp() && !mouse_event.ShiftDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + if (m_hover_id >= m_connectors_group_id) { + if (mouse_event.LeftDown()) { + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected_count = 0; + } + if (mouse_event.LeftUp() && !mouse_event.ShiftDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + } return true; } @@ -366,6 +372,11 @@ void GLGizmoCut3D::update_clipper() m_c->object_clipper()->set_range_and_pos(normal, offset, dist); put_connetors_on_cut_plane(normal, offset); + + if (m_raycasters.empty()) + on_register_raycasters_for_picking(); + else + update_raycasters_for_picking_transform(); } void GLGizmoCut3D::update_clipper_on_render() @@ -599,21 +610,15 @@ void GLGizmoCut3D::render_cut_plane() shader->stop_using(); } -void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) +void GLGizmoCut3D::render_cut_center_graber() { glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (!shader) return; -#if ENABLE_GL_CORE_PROFILE - if (!OpenGLManager::get_gl_info().is_core_profile()) -#endif // ENABLE_GL_CORE_PROFILE - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); - - ColorRGBA color = picking ? picking_decode(BASE_ID - Z) : - m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; + ColorRGBA color = m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; const Camera& camera = wxGetApp().plater()->get_camera(); const Grabber& graber = m_grabbers.front(); @@ -631,7 +636,6 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) shader->set_uniform("projection_matrix", camera.get_projection_matrix()); const Transform3d view_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_rotation_m; - const Transform3d view_grabber_connection_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, m_grabber_connection_len)); auto render = [shader, this](GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix) { shader->set_uniform("view_model_matrix", view_model_matrix); @@ -640,6 +644,29 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) model.render(); }; + auto render_grabber_connection = [shader, camera, view_matrix, this](const ColorRGBA& color) + { + shader->stop_using(); + GLShaderProgram* line_shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); + if (!line_shader) + return; + + line_shader->start_using(); + line_shader->set_uniform("emission_factor", 0.1f); + line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + const Transform3d trafo = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, m_grabber_connection_len)); + line_shader->set_uniform("view_model_matrix", trafo); + line_shader->set_uniform("normal_matrix", (Matrix3d)trafo.matrix().block(0, 0, 3, 3).inverse().transpose()); + line_shader->set_uniform("width", 0.2f); + + m_grabber_connection.set_color(color); + m_grabber_connection.render(); + + line_shader->stop_using(); + shader->start_using(); + }; + auto render_rotation_snapping = [shader, camera, this](Axis axis, const ColorRGBA& color) { Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_start_dragging_m; @@ -649,33 +676,48 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) else view_model_matrix = view_model_matrix * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + shader->stop_using(); + + GLShaderProgram* line_shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); + if (!line_shader) + return; + + line_shader->start_using(); + line_shader->set_uniform("emission_factor", 0.1f); + line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + line_shader->set_uniform("view_model_matrix", view_model_matrix); + line_shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + line_shader->set_uniform("width", 0.25f); m_circle.render(); m_scale.render(); m_snap_radii.render(); m_reference_radius.render(); if (m_dragging) { + line_shader->set_uniform("width", 1.5f); m_angle_arc.set_color(color); m_angle_arc.render(); } + + line_shader->stop_using(); + shader->start_using(); }; // render Z grabber if ((!m_dragging && m_hover_id < 0)) - render(m_grabber_connection, color, view_grabber_connection_matrix); - render(m_sphere, color, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); + render_grabber_connection(color); + render(m_sphere.model, color, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); if (!m_dragging && m_hover_id < 0 || m_hover_id == Z) { const BoundingBoxf3 tbb = transformed_bounding_box(); if (tbb.min.z() <= 0.0) - render(m_cone, color, view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); + render(m_cone.model, color, view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); if (tbb.max.z() >= 0.0) - render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), cone_scale)); + render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), cone_scale)); } // render top sphere for X/Y grabbers @@ -685,7 +727,7 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) size = m_dragging ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::GRAY(); - render(m_sphere, color, view_matrix * Geometry::assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); + render(m_sphere.model, color, view_matrix * Geometry::assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); } // render X grabber @@ -694,20 +736,17 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) { size = m_dragging && m_hover_id == X ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); - if (picking) - color = picking_decode(BASE_ID - X); - else - color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); + color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); if (m_hover_id == X) { - render(m_grabber_connection, color, view_grabber_connection_matrix); + render_grabber_connection(color); render_rotation_snapping(X, color); } offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); - render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); + render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); - render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); + render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); } // render Y grabber @@ -716,20 +755,17 @@ void GLGizmoCut3D::render_cut_center_graber(bool picking /* = false*/) { size = m_dragging && m_hover_id == Y ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); - if (picking) - color = picking_decode(BASE_ID - Y); - else - color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); + color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); if (m_hover_id == Y) { - render(m_grabber_connection, color, view_grabber_connection_matrix); + render_grabber_connection(color); render_rotation_snapping(Y, color); } offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); - render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); + render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); - render(m_cone, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); + render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); } shader->stop_using(); @@ -742,15 +778,15 @@ void GLGizmoCut3D::render_cut_line() glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glLineWidth(2.0f)); - GLShaderProgram* shader = wxGetApp().get_shader("flat"); + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("width", 0.25f); m_cut_line.reset(); m_cut_line.init_from(its_make_line((Vec3f)m_line_beg.cast(), (Vec3f)m_line_end.cast())); @@ -824,24 +860,122 @@ void GLGizmoCut3D::on_set_state() force_update_clipper_on_render = m_state == On; } -#if ENABLE_RAYCAST_PICKING void GLGizmoCut3D::on_register_raycasters_for_picking() { - // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account - m_parent.set_raycaster_gizmos_on_top(true); + assert(m_raycasters.empty()); + set_volumes_picking_state(false); + + init_picking_models(); + + if (m_connectors_editing) { + if (CommonGizmosDataObjects::SelectionInfo* si = m_c->selection_info()) { + const CutConnectors& connectors = si->model_object()->cut_connectors; + for (size_t i = 0; i < connectors.size(); ++i) + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i + m_connectors_group_id, *(m_shapes[connectors[i].attribs]).mesh_raycaster, Transform3d::Identity())); + } + } + else { + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Y, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Y, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_sphere.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_cone.mesh_raycaster, Transform3d::Identity())); + } + + update_raycasters_for_picking_transform(); } void GLGizmoCut3D::on_unregister_raycasters_for_picking() { - m_parent.set_raycaster_gizmos_on_top(false); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_raycasters.clear(); + set_volumes_picking_state(true); } -#else -void GLGizmoCut3D::on_render_for_picking() + +void GLGizmoCut3D::set_volumes_picking_state(bool state) { - render_cut_center_graber(true); - render_connectors(true); + std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + if (raycasters != nullptr) { + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList ids = selection.get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume* v = selection.get_volume(id); + auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr item) { return item->get_raycaster() == v->mesh_raycaster.get(); }); + if (it != raycasters->end()) + (*it)->set_active(state); + } + } +} + +void GLGizmoCut3D::update_raycasters_for_picking_transform() +{ + if (m_connectors_editing) { + CommonGizmosDataObjects::SelectionInfo* si = m_c->selection_info(); + if (!si) + return; + const ModelObject* mo = si->model_object(); + const CutConnectors& connectors = mo->cut_connectors; + if (connectors.empty()) + return; + auto inst_id = m_c->selection_info()->get_active_instance(); + if (inst_id < 0) + return; + + const Vec3d& instance_offset = mo->instances[inst_id]->get_offset(); + const float sla_shift = m_c->selection_info()->get_sla_shift(); + + const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); + const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; + + for (size_t i = 0; i < connectors.size(); ++i) { + const CutConnector& connector = connectors[i]; + + double height = connector.height; + // recalculate connector position to world position + Vec3d pos = connector.pos + instance_offset; + if (connector.attribs.type == CutConnectorType::Dowel && + connector.attribs.style == CutConnectorStyle::Prizm) { + pos -= height * normal; + height *= 2; + } + pos[Z] += sla_shift; + + m_raycasters[i]->set_transform(Geometry::assemble_transform( + pos, + Geometry::Transformation(m_rotation_m).get_rotation(), + Vec3d(connector.radius, connector.radius, height) + )); + } + } + else { + const Transform3d trafo = Geometry::translation_transform(m_plane_center) * m_rotation_m; + + const BoundingBoxf3 box = bounding_box(); + const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 6.0); + + double size = double(m_grabbers.front().get_half_size(mean_size)); + Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + + Vec3d offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); + m_raycasters[0]->set_transform(trafo * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), scale)); + offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); + m_raycasters[1]->set_transform(trafo * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), scale)); + + offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); + m_raycasters[2]->set_transform(trafo * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), scale)); + offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); + m_raycasters[3]->set_transform(trafo * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), scale)); + + offset = 1.25 * size * Vec3d::UnitZ(); + m_raycasters[4]->set_transform(trafo * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); + m_raycasters[5]->set_transform(trafo * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), scale)); + m_raycasters[6]->set_transform(trafo * Geometry::assemble_transform(offset, Vec3d::Zero(), scale)); + } } -#endif // ENABLE_RAYCAST_PICKING void GLGizmoCut3D::on_set_hover_id() { @@ -856,7 +990,7 @@ bool GLGizmoCut3D::on_is_activable() const Vec3d GLGizmoCut3D::mouse_position_in_local_plane(Axis axis, const Linef3& mouse_ray) const { - double half_pi = 0.5 * double(PI); + double half_pi = 0.5 * PI; Transform3d m = Transform3d::Identity(); @@ -882,8 +1016,7 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(Axis axis, const Linef3& mouse } } - m = m * m_rotation_m.inverse(); - + m = m * m_start_dragging_m.inverse(); m.translate(-m_plane_center); return transform(mouse_ray, m).intersect_plane(0.0); @@ -945,17 +1078,29 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) if (cross2(orig_dir, new_dir) < 0.0) theta = two_pi - theta; + const double len = mouse_pos.norm(); + // snap to coarse snap region + if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { + const double step = two_pi / double(SnapRegionsCount); + theta = step * std::round(theta / step); + } + else { + // snap to fine snap region (scale) + if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { + const double step = two_pi / double(ScaleStepsCount); + theta = step * std::round(theta / step); + } + } + if (theta == two_pi) theta = 0.0; - if (m_hover_id == X) theta += 0.5 * PI; rotation[m_hover_id] = theta; + m_rotation_m = m_start_dragging_m * Geometry::rotation_transform(rotation); - m_rotation_m = m_rotation_m * Geometry::rotation_transform(rotation); - - m_angle += (float)theta; + m_angle = theta; while (m_angle > two_pi) m_angle -= two_pi; if (m_angle < 0.0) @@ -964,13 +1109,13 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) update_clipper(); } - else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) { std::pair pos_and_normal; if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal)) return; connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; + update_raycasters_for_picking_transform(); } } @@ -1081,6 +1226,11 @@ bool GLGizmoCut3D::update_bb() m_grabber_connection_len = std::min(0.75 * m_radius, 35.0); m_grabber_radius = m_grabber_connection_len * 0.85; + m_snap_coarse_in_radius = m_grabber_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_grabber_connection_len * 0.85; + m_snap_fine_out_radius = m_grabber_connection_len * 1.15f; + m_plane.reset(); m_cone.reset(); m_sphere.reset(); @@ -1099,18 +1249,29 @@ bool GLGizmoCut3D::update_bb() return false; } +void GLGizmoCut3D::init_picking_models() +{ + if (!m_cone.model.is_initialized()) { + indexed_triangle_set its = its_make_cone(1.0, 1.0, PI / 12.0); + m_cone.model.init_from(its); + m_cone.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } + if (!m_sphere.model.is_initialized()) { + indexed_triangle_set its = its_make_sphere(1.0, PI / 12.0); + m_sphere.model.init_from(its); + m_sphere.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } +} + void GLGizmoCut3D::on_render() { if (update_bb()) update_clipper_on_render(); - if (!m_cone.is_initialized()) - m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); - if (!m_sphere.is_initialized()) - m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + init_picking_models(); + if (!m_grabber_connection.is_initialized()) m_grabber_connection.init_from(its_make_line(Vec3f::Zero(), Vec3f::UnitZ())); - if (!m_circle.is_initialized()) init_from_circle(m_circle, m_grabber_radius); if (!m_scale.is_initialized()) @@ -1124,10 +1285,12 @@ void GLGizmoCut3D::on_render() if (!m_angle_arc.is_initialized() || m_angle != 0.0) init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); - if (force_update_clipper_on_render) + if (force_update_clipper_on_render) { update_clipper_on_render(); + m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); + } - render_connectors(false); + render_connectors(); if (! m_connectors_editing) ::glDisable(GL_DEPTH_TEST); @@ -1265,8 +1428,10 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); - if (m_imgui->button(_L("Add/Edit connectors"))) + if (m_imgui->button(_L("Add/Edit connectors"))) { m_connectors_editing = true; + on_unregister_raycasters_for_picking(); + } m_imgui->disabled_end(); } } @@ -1353,6 +1518,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->button(_L("Confirm connectors"))) { m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_connectors_editing = false; + on_unregister_raycasters_for_picking(); std::fill(m_selected.begin(), m_selected.end(), false); m_selected_count = 0; } @@ -1422,9 +1588,9 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c return Geometry::assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix; } -void GLGizmoCut3D::render_connectors(bool picking) +void GLGizmoCut3D::render_connectors() { - if (picking && ! m_connectors_editing) + if (!m_connectors_editing) return; ::glEnable(GL_DEPTH_TEST); @@ -1443,7 +1609,7 @@ void GLGizmoCut3D::render_connectors(bool picking) m_selected.resize(connectors.size(), false); } - GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; @@ -1480,37 +1646,33 @@ void GLGizmoCut3D::render_connectors(bool picking) pos[Z] += sla_shift; // First decide about the color of the point. - if (picking) - render_color = picking_decode(BASE_ID - i - m_connectors_group_id); - else { - if (size_t(m_hover_id- m_connectors_group_id) == i) - render_color = ColorRGBA::CYAN(); - else if (m_selected[i]) - render_color = ColorRGBA::DARK_GRAY(); - else // neither hover nor picking - render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); + if (size_t(m_hover_id- m_connectors_group_id) == i) + render_color = ColorRGBA::CYAN(); + else if (m_selected[i]) + render_color = ColorRGBA::DARK_GRAY(); + else // neither hover nor picking + render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); - // ! #ysFIXME rework get_volume_transformation - if (0) { // else { // neither hover nor picking - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - ++mesh_id; - if (!mv->is_model_part()) - continue; + // ! #ysFIXME rework get_volume_transformation + if (0) { // else { // neither hover nor picking + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + ++mesh_id; + if (!mv->is_model_part()) + continue; - const Transform3d volume_trafo = get_volume_transformation(mv); + const Transform3d volume_trafo = get_volume_transformation(mv); - if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { - render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : /*ColorRGBA(0.5f, 0.5f, 0.5f, 1.f)*/ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); - break; - } - render_color = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); - m_has_invalid_connector = true; + if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { + render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : /*ColorRGBA(0.5f, 0.5f, 0.5f, 1.f)*/ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); + break; } + render_color = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); + m_has_invalid_connector = true; } } - m_shapes[connector.attribs].set_color(render_color); + m_shapes[connector.attribs].model.set_color(render_color); const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( pos, @@ -1520,7 +1682,7 @@ void GLGizmoCut3D::render_connectors(bool picking) shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - m_shapes[connector.attribs].render(); + m_shapes[connector.attribs].model.render(); } } @@ -1657,7 +1819,8 @@ void GLGizmoCut3D::update_connector_shape() CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; if (m_shapes.find(attribs) == m_shapes.end()) { const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); - m_shapes[attribs].init_from(its); + m_shapes[attribs].model.init_from(its); + m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));; } const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); @@ -1665,16 +1828,16 @@ void GLGizmoCut3D::update_connector_shape() m_connector_mesh = TriangleMesh(its); } -void GLGizmoCut3D::update_model_object() const +void GLGizmoCut3D::update_model_object() { -/* const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_first_volume(); - const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box();*/ const ModelObjectPtrs& mos = wxGetApp().model().objects; ModelObject* mo = m_c->selection_info()->model_object(); wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + + on_unregister_raycasters_for_picking(); + on_register_raycasters_for_picking(); } bool GLGizmoCut3D::cut_line_processing() const @@ -1763,10 +1926,10 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi CutConnectorAttributes( CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); - update_model_object(); std::fill(m_selected.begin(), m_selected.end(), false); m_selected.push_back(true); assert(m_selected.size() == connectors.size()); + update_model_object(); m_parent.set_as_dirty(); return true; @@ -1801,9 +1964,9 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi size_t connector_id = m_hover_id - m_connectors_group_id; connectors.erase(connectors.begin() + connector_id); - update_model_object(); m_selected.erase(m_selected.begin() + connector_id); assert(m_selected.size() == connectors.size()); + update_model_object(); m_parent.set_as_dirty(); return true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 3987081ef..1665b8549 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -41,11 +41,15 @@ class GLGizmoCut3D : public GLGizmoBase double m_grabber_radius{ 0.0 }; double m_grabber_connection_len{ 0.0 }; + double m_snap_coarse_in_radius{ 0.0 }; + double m_snap_coarse_out_radius{ 0.0 }; + double m_snap_fine_in_radius{ 0.0 }; + double m_snap_fine_out_radius{ 0.0 }; + // dragging angel in hovered axes Transform3d m_start_dragging_m{ Transform3d::Identity() }; double m_angle{ 0.0 }; - std::map m_shapes; TriangleMesh m_connector_mesh; // workaround for using of the clipping plane normal Vec3d m_clp_normal{ Vec3d::Ones() }; @@ -53,12 +57,14 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_line_beg{ Vec3d::Zero() }; Vec3d m_line_end{ Vec3d::Zero() }; -#if ENABLE_LEGACY_OPENGL_REMOVAL GLModel m_plane; GLModel m_grabber_connection; GLModel m_cut_line; - GLModel m_cone; - GLModel m_sphere; + + PickingModel m_sphere; + PickingModel m_cone; + std::map m_shapes; + std::vector> m_raycasters; GLModel m_circle; GLModel m_scale; @@ -67,7 +73,6 @@ class GLGizmoCut3D : public GLGizmoBase GLModel m_angle_arc; Vec3d m_old_center; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL bool m_keep_upper{ true }; bool m_keep_lower{ true }; @@ -168,12 +173,12 @@ protected: void on_start_dragging() override; void on_stop_dragging() override; void on_render() override; -#if ENABLE_RAYCAST_PICKING + virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; -#else - virtual void on_render_for_picking() override; -#endif // ENABLE_RAYCAST_PICKING + void set_volumes_picking_state(bool state); + void update_raycasters_for_picking_transform(); + void on_render_input_window(float x, float y, float bottom_limit) override; bool wants_enter_leave_snapshots() const override { return true; } @@ -191,21 +196,22 @@ private: bool render_revert_button(const std::string& label); bool render_connect_type_radio_button(CutConnectorType type); Transform3d get_volume_transformation(const ModelVolume* volume) const; - void render_connectors(bool picking); + void render_connectors(); bool can_perform_cut() const; bool cut_line_processing() const; void discard_cut_line_processing(); void render_cut_plane(); - void render_cut_center_graber(bool picking = false); + void render_cut_center_graber(); void render_cut_line(); void perform_cut(const Selection& selection); void set_center_pos(const Vec3d& center_pos, bool force = false); bool update_bb(); + void init_picking_models(); void reset_connectors(); void update_connector_shape(); - void update_model_object() const; + void update_model_object(); bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); }; From 7912613dc8e11db2ddea8018ce16b26a01756a3b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 12 Aug 2022 17:59:36 +0200 Subject: [PATCH 59/97] Cut WIP: Fixed crash on second "Perform cut" + some code cleaning --- src/libslic3r/Model.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 40 ++++++++++------------------ src/slic3r/GUI/Plater.cpp | 11 ++++---- 4 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d62f56105..44957cd92 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1545,7 +1545,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug vol->set_type(ModelVolumeType::MODEL_PART); } - if (volume->cut_info.connector_type == CutConnectorType::Dowel) { + if (dowels && volume->cut_info.connector_type == CutConnectorType::Dowel) { // add one more solid part same as connector if this connector is a dowel ModelVolume* vol = dowels->add_volume(*volume); vol->set_type(ModelVolumeType::MODEL_PART); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6b444eaec..ceb8d0b54 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3469,6 +3469,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && m_gizmos.get_current_type() != GLGizmosManager::Seam && + m_gizmos.get_current_type() != GLGizmosManager::Cut && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); m_dirty = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8d38bb295..481ceaa51 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -163,8 +163,6 @@ static void init_from_angle_arc(GLModel& model, double angle, double radius) //! -- -#define use_grabber_extension 1 - GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) , m_connector_type (CutConnectorType::Plug) @@ -1030,12 +1028,9 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; if (m_hover_id == Z) { -#if use_grabber_extension Vec3d starting_box_center = m_plane_center - Vec3d::UnitZ();// some Margin rotate_vec3d_around_center(starting_box_center, Geometry::Transformation(m_rotation_m).get_rotation(), m_plane_center); -#else - const Vec3d& starting_box_center = m_plane_center; -#endif + const Vec3d& starting_drag_position = m_plane_center; double projection = 0.0; @@ -1184,14 +1179,10 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= m_c->selection_info()->get_sla_shift(); - const Vec3d rotation = Geometry::Transformation(m_rotation_m).get_rotation(); const auto move = Geometry::assemble_transform(-cut_center_offset); - const auto rot_z = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, 0, -rotation.z())); - const auto rot_y = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0, -rotation.y(), 0)); - const auto rot_x = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(-rotation.x(), 0, 0)); - const auto move2 = Geometry::assemble_transform(m_plane_center); + const auto move2 = Geometry::assemble_transform(m_plane_center); - const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * rot_x * rot_y * rot_z * move; + const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * m_rotation_m.inverse() * move; const Selection& selection = m_parent.get_selection(); const Selection::IndicesList& idxs = selection.get_volume_idxs(); @@ -1207,7 +1198,10 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* volume->get_instance_mirror() ); - ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * instance_matrix * volume->get_volume_transformation().get_matrix())); + auto volume_travo = instance_matrix * volume->get_volume_transformation().get_matrix(); + auto volume_offset = Geometry::Transformation(volume_travo).get_offset(); + + ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * volume_travo)); } } return ret; @@ -1223,7 +1217,7 @@ bool GLGizmoCut3D::update_bb() set_center_pos(m_bb_center + m_center_offset, true); m_radius = box.radius(); - m_grabber_connection_len = std::min(0.75 * m_radius, 35.0); + m_grabber_connection_len = 0.75 * m_radius;// std::min(0.75 * m_radius, 35.0); m_grabber_radius = m_grabber_connection_len * 0.85; m_snap_coarse_in_radius = m_grabber_radius / 3.0f; @@ -1265,8 +1259,11 @@ void GLGizmoCut3D::init_picking_models() void GLGizmoCut3D::on_render() { - if (update_bb()) + if (update_bb() || force_update_clipper_on_render) { update_clipper_on_render(); + if (force_update_clipper_on_render) + m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); + } init_picking_models(); @@ -1285,11 +1282,6 @@ void GLGizmoCut3D::on_render() if (!m_angle_arc.is_initialized() || m_angle != 0.0) init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); - if (force_update_clipper_on_render) { - update_clipper_on_render(); - m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); - } - render_connectors(); if (! m_connectors_editing) @@ -1725,6 +1717,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) { // update connectors pos as offset of its center before cut performing if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { + m_selected.clear(); + Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); for (CutConnector& connector : mo->cut_connectors) { connector.rotation = rotation; @@ -1736,13 +1730,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } else { // culculate shift of the connector center regarding to the position on the cut plane -#if use_grabber_extension Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); rotate_vec3d_around_center(shifted_center, rotation, m_plane_center); Vec3d norm = (shifted_center - m_plane_center).normalized(); -#else - Vec3d norm = (m_grabbers[0].center - m_plane_center).normalize(); -#endif connector.pos += norm * (0.5 * connector.height); } } @@ -1758,7 +1748,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); - m_selected.clear(); } else { // the object is SLA-elevated and the plane is under it. @@ -1871,7 +1860,6 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse m_line_beg = pt; m_line_end = pt; return true; -// volumes_trafos[i] = model_object->volumes[i]->get_matrix(); } if (cut_line_processing()) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b35b68202..0bb9e5f3d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5949,18 +5949,17 @@ void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& // suppress to call selection update for Object List to avoid call of early Gizmos on/off update p->load_model_objects(new_objects, false, false); - Selection& selection = p->get_selection(); - size_t last_id = p->model.objects.size() - 1; - for (size_t i = 0; i < new_objects.size(); ++i) - selection.add_object((unsigned int)(last_id - i), i == 0); - this->allow_snapshots(); - // now process all updates of the 3d scene update(); // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call for (size_t idx = 0; idx < p->model.objects.size(); idx++) wxGetApp().obj_list()->update_info_items(idx); + + Selection& selection = p->get_selection(); + size_t last_id = p->model.objects.size() - 1; + for (size_t i = 0; i < new_objects.size(); ++i) + selection.add_object((unsigned int)(last_id - i), i == 0); } void Plater::export_gcode(bool prefer_removable) From edebda511c732966229447309243b7fbbbaad122 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 15 Aug 2022 14:16:05 +0200 Subject: [PATCH 60/97] Cut WIP: Set Circle connector shape as a default. Allow set connectors to any place of the cut plane. --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 481ceaa51..c0611b46d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -167,7 +167,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, : GLGizmoBase(parent, icon_filename, sprite_id) , m_connector_type (CutConnectorType::Plug) , m_connector_style (size_t(CutConnectorStyle::Prizm)) - , m_connector_shape_id (size_t(CutConnectorShape::Hexagon)) + , m_connector_shape_id (size_t(CutConnectorShape::Circle)) , m_rotation_matrix(Slic3r::Matrix3d::Identity()) , m_connectors_group_id (3) { @@ -1777,7 +1777,8 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair Vec3f normal; bool clipping_plane_was_hit = false; - const Transform3d volume_trafo = get_volume_transformation(mv); +// const Transform3d volume_trafo = get_volume_transformation(mv); + const Transform3d volume_trafo = mv->get_transformation().get_matrix(); m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * volume_trafo, camera, hit, normal, m_c->object_clipper()->get_clipping_plane(true), From a8919b1e915c890c45c279b831972ef07408c3ac Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 15 Aug 2022 16:12:16 +0200 Subject: [PATCH 61/97] Cut WIP: Set all dowels as a separate objects ObjectList: Don't show connectors for cut objects --- src/libslic3r/Model.cpp | 114 +++++++++++++++--------------- src/slic3r/GUI/GUI_ObjectList.cpp | 10 ++- 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 44957cd92..ffa74f79c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1446,37 +1446,26 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const cut_id.increase_check_sum(size_t(cut_obj_cnt)); } + auto clone_obj = [this](ModelObject** obj) { + (*obj) = ModelObject::new_clone(*this); + (*obj)->set_model(nullptr); + (*obj)->sla_support_points.clear(); + (*obj)->sla_drain_holes.clear(); + (*obj)->sla_points_status = sla::PointsStatus::NoPoints; + (*obj)->clear_volumes(); + (*obj)->input_file.clear(); + }; + // Clone the object to duplicate instances, materials etc. - ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; - ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; - ModelObject* dowels = attributes.has(ModelObjectCutAttribute::CreateDowels) ? ModelObject::new_clone(*this) : nullptr; + ModelObject* upper{ nullptr }; + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + clone_obj(&upper); - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - upper->set_model(nullptr); - upper->sla_support_points.clear(); - upper->sla_drain_holes.clear(); - upper->sla_points_status = sla::PointsStatus::NoPoints; - upper->clear_volumes(); - upper->input_file.clear(); - } + ModelObject* lower{ nullptr }; + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + clone_obj(&lower); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) { - lower->set_model(nullptr); - lower->sla_support_points.clear(); - lower->sla_drain_holes.clear(); - lower->sla_points_status = sla::PointsStatus::NoPoints; - lower->clear_volumes(); - lower->input_file.clear(); - } - - if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { - dowels->set_model(nullptr); - dowels->sla_support_points.clear(); - dowels->sla_drain_holes.clear(); - dowels->sla_points_status = sla::PointsStatus::NoPoints; - dowels->clear_volumes(); - dowels->input_file.clear(); - } + std::vector dowels; using namespace Geometry; @@ -1545,9 +1534,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug vol->set_type(ModelVolumeType::MODEL_PART); } - if (dowels && volume->cut_info.connector_type == CutConnectorType::Dowel) { + if (volume->cut_info.connector_type == CutConnectorType::Dowel && + attributes.has(ModelObjectCutAttribute::CreateDowels)) { + ModelObject* dowel{ nullptr }; + // Clone the object to duplicate instances, materials etc. + clone_obj(&dowel); + // add one more solid part same as connector if this connector is a dowel - ModelVolume* vol = dowels->add_volume(*volume); + ModelVolume* vol = dowel->add_volume(*volume); vol->set_type(ModelVolumeType::MODEL_PART); // But discard rotation and Z-offset for this volume @@ -1556,6 +1550,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // Compute the displacement (in instance coordinates) to be applied to place the dowels local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); + + dowels.push_back(dowel); } } else { @@ -1704,35 +1700,37 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const res.push_back(lower); } - if (attributes.has(ModelObjectCutAttribute::CreateDowels) && dowels->volumes.size() > 0) { - if (!dowels->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - dowels->center_around_origin(); - dowels->translate_instances(-dowels->origin_translation); - dowels->origin_translation = Vec3d::Zero(); + if (attributes.has(ModelObjectCutAttribute::CreateDowels) && dowels.size() > 0) { + for (auto dowel : dowels) { + if (!dowel->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { + dowel->center_around_origin(); + dowel->translate_instances(-dowel->origin_translation); + dowel->origin_translation = Vec3d::Zero(); + } + else { + dowel->invalidate_bounding_box(); + dowel->center_around_origin(); + } + dowel->name += "-Dowel-" + dowel->volumes[0]->name; + + // Reset instance transformation except offset and Z-rotation + for (size_t i = 0; i < instances.size(); ++i) { + auto& obj_instance = dowel->instances[i]; + const Vec3d offset = obj_instance->get_offset(); + Vec3d rotation = Vec3d::Zero(); + if (i != instance) + rotation[Z] = obj_instance->get_rotation().z(); + + const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), rotation) * local_dowels_displace; + + obj_instance->set_transformation(Geometry::Transformation()); + obj_instance->set_offset(offset + displace); + obj_instance->set_rotation(rotation); + } + + local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0)); + res.push_back(dowel); } - else { - dowels->invalidate_bounding_box(); - dowels->center_around_origin(); - } - - dowels->name += "-Dowels"; - - // Reset instance transformation except offset and Z-rotation - for (size_t i = 0; i < instances.size(); ++i) { - auto& obj_instance = dowels->instances[i]; - const Vec3d offset = obj_instance->get_offset(); - Vec3d rotation = Vec3d::Zero(); - if (i != instance) - rotation[Z] = obj_instance->get_rotation().z(); - - const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), rotation) * local_dowels_displace; - - obj_instance->set_transformation(Geometry::Transformation()); - obj_instance->set_offset(offset + displace); - obj_instance->set_rotation(rotation); - } - - res.push_back(dowels); } BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 55aab5d2d..cc6954665 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1882,9 +1882,12 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) break; case InfoItemType::Cut: + if (0) { // #ysFIXME_Cut cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove cut connectors")); (*m_objects)[obj_idx]->cut_connectors.clear(); + } else + Slic3r::GUI::show_error(nullptr, _L("Connectors cannot be deleted from cut object.")); break; case InfoItemType::MmuSegmentation: @@ -2583,7 +2586,7 @@ void ObjectList::part_selection_changed() } case InfoItemType::CustomSupports: case InfoItemType::CustomSeam: - case InfoItemType::Cut: +// case InfoItemType::Cut: case InfoItemType::MmuSegmentation: { GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports : @@ -2772,7 +2775,10 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio break; case InfoItemType::Cut : + if (0) // #ysFIXME_Cut should_show = !model_object->cut_connectors.empty(); + else + should_show = model_object->is_cut() && model_object->volumes.size() > 1; break; case InfoItemType::VariableLayerHeight : should_show = printer_technology() == ptFFF @@ -2827,7 +2833,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) update_info_items(obj_idx, nullptr, call_selection_changed); // add volumes to the object - if (model_object->volumes.size() > 1) { + if (model_object->volumes.size() > 1 && !model_object->is_cut()) { for (const ModelVolume* volume : model_object->volumes) { const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, from_u8(volume->name), From 5ec84adb14a9c19f0af64feefa4da79f655b4c23 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 17 Aug 2022 09:58:20 +0200 Subject: [PATCH 62/97] Cut WIP: Add only needed modifiers --- src/libslic3r/Model.cpp | 14 +++++++------- src/slic3r/GUI/GUI_ObjectList.cpp | 3 ++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ffa74f79c..293d31eae 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1380,10 +1380,6 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn void ModelObject::apply_cut_connectors(const std::string& name) { - // discard old connector markers for volumes - for (ModelVolume* volume : volumes) - volume->cut_info.discard(); - if (cut_connectors.empty()) return; @@ -1517,6 +1513,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const if (!volume->is_model_part()) { if (volume->cut_info.is_connector) { + volume->cut_info.discard(); + // ! Don't apply instance transformation for the conntectors. // This transformation is already there if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { @@ -1559,10 +1557,12 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // to the modifier volume transformation to preserve their shape properly. volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - // #ysFIXME - add logic for the negative volumes/connectors - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + // Some logic for the negative volumes/connectors. Add only needed modifiers + auto bb = volume->mesh().transformed_bounding_box(cut_matrix * volume->get_matrix()); + bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) upper->add_volume(*volume); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) + if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) lower->add_volume(*volume); } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index cc6954665..769b038fa 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2918,6 +2918,7 @@ bool ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i update_lock_icons_for_model(); return true; } + return false; } else if (del_subobject_from_object(obj_idx, sub_obj_idx, type)) { type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) : @@ -2942,7 +2943,7 @@ bool ObjectList::delete_from_model_and_list(const std::vector& it if (item->type&itObject) { bool was_cut = object(item->obj_idx)->is_cut(); if (!del_object(item->obj_idx)) - continue; + return false;// continue; m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx)); if (was_cut) update_lock_icons_for_model(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 1665b8549..f8f788138 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -77,7 +77,7 @@ class GLGizmoCut3D : public GLGizmoBase bool m_keep_upper{ true }; bool m_keep_lower{ true }; bool m_place_on_cut_upper{ true }; - bool m_place_on_cut_lower{ true }; + bool m_place_on_cut_lower{ false }; bool m_rotate_upper{ false }; bool m_rotate_lower{ false }; From 70a575198b9928f0d51f5dc527fa82f075c4d94a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 17 Aug 2022 13:39:51 +0200 Subject: [PATCH 63/97] Cut WIP: Fix for drawing of the cut line --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 26 +++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index f3c15c1e5..c260959a1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -212,8 +212,8 @@ std::string GLGizmoCut3D::get_tooltip() const } if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { std::string axis = m_hover_id == X ? "X" : "Y"; -// return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L(""); - return axis + ": " + format(float(Geometry::rad2deg(m_angle)), 1) + _u8L(""); +// return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L("°"); + return axis + ": " + format(float(Geometry::rad2deg(m_angle)), 1) + _u8L("°"); } return tooltip; @@ -582,7 +582,7 @@ void GLGizmoCut3D::render_cut_plane() if (!m_plane.is_initialized()) { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; +// init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; init_data.reserve_vertices(4); init_data.reserve_indices(6); @@ -600,6 +600,10 @@ void GLGizmoCut3D::render_cut_plane() m_plane.init_from(std::move(init_data)); } + if (can_perform_cut()) + m_plane.set_color({ 0.8f, 0.8f, 0.8f, 0.5f }); + else + m_plane.set_color({ 1.0f, 0.8f, 0.8f, 0.5f }); m_plane.render(); glsafe(::glEnable(GL_CULL_FACE)); @@ -894,6 +898,12 @@ void GLGizmoCut3D::on_unregister_raycasters_for_picking() set_volumes_picking_state(true); } +void GLGizmoCut3D::update_raycasters_for_picking() +{ + on_unregister_raycasters_for_picking(); + on_register_raycasters_for_picking(); +} + void GLGizmoCut3D::set_volumes_picking_state(bool state) { std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); @@ -1422,7 +1432,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); if (m_imgui->button(_L("Add/Edit connectors"))) { m_connectors_editing = true; - on_unregister_raycasters_for_picking(); + update_raycasters_for_picking(); } m_imgui->disabled_end(); } @@ -1510,7 +1520,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (m_imgui->button(_L("Confirm connectors"))) { m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); m_connectors_editing = false; - on_unregister_raycasters_for_picking(); + update_raycasters_for_picking(); std::fill(m_selected.begin(), m_selected.end(), false); m_selected_count = 0; } @@ -1826,8 +1836,7 @@ void GLGizmoCut3D::update_model_object() m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - on_unregister_raycasters_for_picking(); - on_register_raycasters_for_picking(); + update_raycasters_for_picking(); } bool GLGizmoCut3D::cut_line_processing() const @@ -1860,6 +1869,7 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse if (action == SLAGizmoEventType::LeftDown && !cut_line_processing()) { m_line_beg = pt; m_line_end = pt; + on_unregister_raycasters_for_picking(); return true; } @@ -1883,6 +1893,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse discard_cut_line_processing(); } + else if (action == SLAGizmoEventType::Moving) + this->set_dirty(); return true; } return false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index f8f788138..81a69e425 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -176,6 +176,7 @@ protected: virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; + void update_raycasters_for_picking(); void set_volumes_picking_state(bool state); void update_raycasters_for_picking_transform(); From cf144da4fe8dc0797096b4c352db2bde7cc0c96f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 18 Aug 2022 13:57:25 +0200 Subject: [PATCH 64/97] Cut WIP: Import/Export cut information to/from .3mf file + Fixed a crash during change object selection, when CutGizmo is On + Fixed Undo/Redo (was accidentally broken with 7912613dc8e11db2ddea8018ce16b26a01756a3b) --- src/libslic3r/Format/3mf.cpp | 110 +++++++++++++++++++++++++++ src/libslic3r/Model.hpp | 2 +- src/libslic3r/ObjectID.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 23 +++--- src/slic3r/GUI/Plater.cpp | 2 + 5 files changed, 131 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 5caff6d3a..4251110c4 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -77,6 +77,7 @@ const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; +const std::string CUT_INFORMATION_FILE = "Metadata/Prusa_Slicer_cut_information.xml"; static constexpr const char* MODEL_TAG = "model"; static constexpr const char* RESOURCES_TAG = "resources"; @@ -416,6 +417,7 @@ namespace Slic3r { typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; typedef std::map IdToLayerConfigRangesMap; + typedef std::map IdToCutObjectIdMap; typedef std::map> IdToSlaSupportPointsMap; typedef std::map> IdToSlaDrainHolesMap; @@ -443,6 +445,7 @@ namespace Slic3r { IdToGeometryMap m_geometries; CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; + IdToCutObjectIdMap m_cut_object_ids; IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; @@ -474,6 +477,7 @@ namespace Slic3r { bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); @@ -676,6 +680,10 @@ namespace Slic3r { // extract slic3r layer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); } + else if (boost::algorithm::iequals(name, CUT_INFORMATION_FILE)) { + // extract slic3r layer config ranges file + _extract_cut_information_from_archive(archive, stat, config_substitutions); + } else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { // extract slic3r layer config ranges file _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); @@ -766,6 +774,11 @@ namespace Slic3r { return false; } + // m_cut_object_ids are indexed by a 1 based model object index. + IdToCutObjectIdMap::iterator cut_object_id = m_cut_object_ids.find(object.second + 1); + if (cut_object_id != m_cut_object_ids.end()) + model_object->cut_id = std::move(cut_object_id->second); + // m_layer_heights_profiles are indexed by a 1 based model object index. IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); if (obj_layer_heights_profile != m_layer_heights_profiles.end()) @@ -944,6 +957,48 @@ namespace Slic3r { return true; } + void _3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading cut information data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree objects_tree; + pt::read_xml(iss, objects_tree); + + for (const auto& object : objects_tree.get_child("objects")) { + pt::ptree object_tree = object.second; + int obj_idx = object_tree.get(".id", -1); + if (obj_idx <= 0) { + add_error("Found invalid object id"); + continue; + } + + IdToCutObjectIdMap::iterator object_item = m_cut_object_ids.find(obj_idx); + if (object_item != m_cut_object_ids.end()) { + add_error("Found duplicated cut_object_id"); + continue; + } + + for (const auto& obj_cut_id : object_tree) { + if (obj_cut_id.first != "cut_id") + continue; + pt::ptree cut_id_tree = obj_cut_id.second; + ObjectID obj_id(cut_id_tree.get(".id")); + CutObjectBase cut_id(ObjectID(cut_id_tree.get(".id")), + cut_id_tree.get(".check_sum"), + cut_id_tree.get(".connectors_cnt")); + m_cut_object_ids.insert({ obj_idx, std::move(cut_id) }); + } + } + } + } + void _3MF_Importer::_extract_print_config_from_archive( mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, @@ -2219,6 +2274,7 @@ namespace Slic3r { bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); + bool _add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); @@ -2281,6 +2337,15 @@ namespace Slic3r { return false; } + // Adds file with information for object cut ("Metadata/Slic3r_PE_cut_information.txt"). + // All information for object cut of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_cut_information_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + // Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt"). // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // The index differes from the index of an object ID of an object instance of a 3MF file! @@ -2781,6 +2846,51 @@ namespace Slic3r { return true; } + bool _3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + pt::ptree tree; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) { + object_cnt++; + pt::ptree& obj_tree = tree.add("objects.object", ""); + + obj_tree.put(".id", object_cnt); + + // Store info for cut_id + pt::ptree& cut_id_tree = obj_tree.add("cut_id", ""); + + // store cut_id atributes + cut_id_tree.put(".id", object->cut_id.id().id); + cut_id_tree.put(".check_sum", object->cut_id.check_sum()); + cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt()); + } + + if (!tree.empty()) { + std::ostringstream oss; + pt::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string for a better preview + boost::replace_all(out, ">\n \n ", ">\n "); + boost::replace_all(out, ">", ">\n "); + // OR just + boost::replace_all(out, "><", ">\n<"); + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add cut information file to archive"); + return false; + } + } + + return true; + } + bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) { assert(is_decimal_separator_point()); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 9fa38ecd7..21b052337 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -351,7 +351,7 @@ public: // Holes to be drilled into the object so resin can flow out sla::DrainHoles sla_drain_holes; - // Connectors to be added into the object after cut + // Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform CutConnectors cut_connectors; CutObjectBase cut_id; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 42cd21699..452f620c4 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -89,7 +89,9 @@ private: friend class cereal::access; friend class Slic3r::UndoRedo::StackImpl; template void serialize(Archive &ar) { ar(m_id); } +protected: // #vbCHECKME && #ysFIXME ObjectBase(const ObjectID id) : m_id(id) {} +private: template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } }; @@ -141,6 +143,8 @@ public: // Constructor with ignored int parameter to assign an invalid ID, to be replaced // by an existing ID copied from elsewhere. CutObjectBase(int) : ObjectBase(-1) {} + // Constructor to initialize full information from 3mf + CutObjectBase(ObjectID id, size_t check_sum, size_t connectors_cnt) : ObjectBase(id), m_check_sum(check_sum), m_connectors_cnt(connectors_cnt) {} // The class tree will have virtual tables and type information. virtual ~CutObjectBase() = default; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index c260959a1..7b6c729f8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -627,7 +627,7 @@ void GLGizmoCut3D::render_cut_center_graber() const BoundingBoxf3 box = bounding_box(); - const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 6.0); + const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); double size = m_dragging && m_hover_id == Z ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); @@ -821,8 +821,8 @@ bool GLGizmoCut3D::on_init() void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) { - ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, //m_selected, - m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected, + // m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, m_ar_plane_center, m_ar_rotations); set_center_pos(m_ar_plane_center, true); @@ -835,8 +835,8 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const { - ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, //m_selected, - m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected, + // m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, m_ar_plane_center, m_ar_rotations); } @@ -1224,7 +1224,10 @@ bool GLGizmoCut3D::update_bb() m_max_pos = box.max; m_min_pos = box.min; m_bb_center = box.center(); - set_center_pos(m_bb_center + m_center_offset, true); + if (box.contains(m_center_offset)) + set_center_pos(m_bb_center + m_center_offset, true); + else + set_center_pos(m_bb_center, true); m_radius = box.radius(); m_grabber_connection_len = 0.75 * m_radius;// std::min(0.75 * m_radius, 35.0); @@ -1242,6 +1245,9 @@ bool GLGizmoCut3D::update_bb() m_circle.reset(); m_scale.reset(); m_snap_radii.reset(); + m_reference_radius.reset(); + + on_unregister_raycasters_for_picking(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { m_selected.clear(); @@ -1271,8 +1277,7 @@ void GLGizmoCut3D::on_render() { if (update_bb() || force_update_clipper_on_render) { update_clipper_on_render(); - if (force_update_clipper_on_render) - m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); + m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); } init_picking_models(); @@ -1725,11 +1730,11 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) const bool has_connectors = !mo->cut_connectors.empty(); { + Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); // update connectors pos as offset of its center before cut performing if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { m_selected.clear(); - Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); for (CutConnector& connector : mo->cut_connectors) { connector.rotation = rotation; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 27764a698..96df8892c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5962,6 +5962,8 @@ void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& // suppress to call selection update for Object List to avoid call of early Gizmos on/off update p->load_model_objects(new_objects, false, false); + this->allow_snapshots(); + // now process all updates of the 3d scene update(); // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), From a30a2547243c28dccb88de8776c8d3f26f427245 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 19 Sep 2022 11:14:29 +0200 Subject: [PATCH 65/97] After merge fix --- src/slic3r/GUI/ImGuiWrapper.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 9622da145..e9e5137be 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -56,6 +56,10 @@ static const std::map font_icons = { {ImGui::PreferencesHoverButton, "notification_preferences_hover"}, {ImGui::SliderFloatEditBtnIcon, "edit_button" }, {ImGui::SliderFloatEditBtnPressedIcon, "edit_button_pressed" }, + {ImGui::ExpandBtn , "expand_btn" }, + {ImGui::CollapseBtn , "collapse_btn" }, + {ImGui::RevertButton , "undo" }, + {ImGui::WarningMarkerSmall , "notification_warning" }, }; static const std::map font_icons_large = { @@ -73,10 +77,6 @@ static const std::map font_icons_large = { {ImGui::LegendShells , "legend_shells" }, {ImGui::LegendToolMarker , "legend_toolmarker" }, #endif // ENABLE_LEGEND_TOOLBAR_ICONS - {ImGui::RevertButton , "undo" }, - {ImGui::WarningMarkerSmall , "notification_warning" }, - {ImGui::ExpandBtn , "expand_btn" }, - {ImGui::CollapseBtn , "collapse_btn" }, {ImGui::CloseNotifButton , "notification_close" }, {ImGui::CloseNotifHoverButton , "notification_close_hover" }, {ImGui::EjectButton , "notification_eject_sd" }, From d7f55253cd636c228adee9a54468ef52d8f377b0 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 16 Aug 2022 13:24:57 +0200 Subject: [PATCH 66/97] Cut: allow enabling/disabling an island --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 45 +++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 10 ++ src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 4 + src/slic3r/GUI/MeshUtils.cpp | 177 ++++++++++++----------- src/slic3r/GUI/MeshUtils.hpp | 22 ++- 6 files changed, 150 insertions(+), 110 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 7b6c729f8..905e39db3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1117,7 +1117,8 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) { std::pair pos_and_normal; - if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal)) + Vec3d pos_world; + if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal, pos_world)) return; connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; update_raycasters_for_picking_transform(); @@ -1773,7 +1774,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // Return false if no intersection was found, true otherwise. -bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal) +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal, Vec3d& pos_world) { const float sla_shift = m_c->selection_info()->get_sla_shift(); @@ -1788,8 +1789,8 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair ++mesh_id; if (!mv->is_model_part()) continue; - Vec3f hit; Vec3f normal; + Vec3f hit; bool clipping_plane_was_hit = false; // const Transform3d volume_trafo = get_volume_transformation(mv); @@ -1806,6 +1807,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair // Return both the point and the facet normal. pos_and_normal = std::make_pair(hit_d, normal.cast()); + pos_world = hit.cast(); return true; } } @@ -1916,27 +1918,32 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - if (action == SLAGizmoEventType::LeftDown && !shift_down && m_connectors_editing) { + if (action == SLAGizmoEventType::LeftDown && !shift_down) { // If there is no selection and no hovering, add new point if (m_hover_id == -1 && !control_down && !alt_down) { std::pair pos_and_normal; - if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal)) { + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal, pos_world)) { const Vec3d& hit = pos_and_normal.first; - // The clipping plane was clicked, hit containts coordinates of the hit in world coords. - //std::cout << hit.x() << "\t" << hit.y() << "\t" << hit.z() << std::endl; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); - connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), - float(m_connector_size * 0.5), float(m_connector_depth_ratio), - float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance), - CutConnectorAttributes( CutConnectorType(m_connector_type), - CutConnectorStyle(m_connector_style), - CutConnectorShape(m_connector_shape_id))); - std::fill(m_selected.begin(), m_selected.end(), false); - m_selected.push_back(true); - assert(m_selected.size() == connectors.size()); - update_model_object(); - m_parent.set_as_dirty(); + if (m_connectors_editing) { + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); + + connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), + float(m_connector_size * 0.5), float(m_connector_depth_ratio), + float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance), + CutConnectorAttributes( CutConnectorType(m_connector_type), + CutConnectorStyle(m_connector_style), + CutConnectorShape(m_connector_shape_id))); + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected.push_back(true); + assert(m_selected.size() == connectors.size()); + update_model_object(); + m_parent.set_as_dirty(); + } else { + m_c->object_clipper()->pass_mouse_click(pos_world); + } return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 81a69e425..a7892a9cd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -139,7 +139,7 @@ public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; - bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair& pos_and_normal); + bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair& pos_and_normal, Vec3d& pos_world); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); /// diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 97f3cb9a8..a08836c28 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -494,6 +494,16 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou clipper->set_behaviour(fill_cut, contour_width); } +void ObjectClipper::pass_mouse_click(const Vec3d& pt) +{ + for (auto& clipper : m_clippers) + clipper->pass_mouse_click(pt); +} + +std::vector ObjectClipper::get_disabled_contours() const +{ + return std::vector(); +} void SupportsClipper::on_update() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 925408008..7c41a64b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -262,6 +262,10 @@ public: void set_position_by_ratio(double pos, bool keep_normal); void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos); void set_behavior(bool hide_clipped, bool fill_cut, double contour_width); + + void pass_mouse_click(const Vec3d& pt); + std::vector get_disabled_contours() const; + protected: diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index bbf1f7652..74fe82494 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -23,7 +23,7 @@ namespace GUI { void MeshClipper::set_behaviour(bool fill_cut, double contour_width) { if (fill_cut != m_fill_cut || contour_width != m_contour_width) - m_triangles_valid = false; + m_result.reset(); m_fill_cut = fill_cut; m_contour_width = contour_width; } @@ -34,7 +34,7 @@ void MeshClipper::set_plane(const ClippingPlane& plane) { if (m_plane != plane) { m_plane = plane; - m_triangles_valid = false; + m_result.reset(); } } @@ -43,7 +43,7 @@ void MeshClipper::set_limiting_plane(const ClippingPlane& plane) { if (m_limiting_plane != plane) { m_limiting_plane = plane; - m_triangles_valid = false; + m_result.reset(); } } @@ -53,7 +53,7 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) { if (m_mesh != &mesh) { m_mesh = &mesh; - m_triangles_valid = false; + m_result.reset(); } } @@ -61,7 +61,7 @@ void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) { if (m_negative_mesh != &mesh) { m_negative_mesh = &mesh; - m_triangles_valid = false; + m_result.reset(); } } @@ -71,7 +71,7 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo) { if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { m_trafo = trafo; - m_triangles_valid = false; + m_result.reset(); } } @@ -81,12 +81,9 @@ void MeshClipper::render_cut(const ColorRGBA& color) void MeshClipper::render_cut() #endif // ENABLE_LEGACY_OPENGL_REMOVAL { - if (! m_triangles_valid) + if (! m_result) recalculate_triangles(); #if ENABLE_LEGACY_OPENGL_REMOVAL - if (m_model.vertices_count() == 0 || m_model.indices_count() == 0) - return; - GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); if (curr_shader != nullptr) curr_shader->stop_using(); @@ -97,8 +94,10 @@ void MeshClipper::render_cut() const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - m_model.set_color(color); - m_model.render(); + for (CutIsland& isl : m_result->cut_islands) { + isl.model.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color); + isl.model.render(); + } shader->stop_using(); } @@ -117,7 +116,7 @@ void MeshClipper::render_contour(const ColorRGBA& color) void MeshClipper::render_contour() #endif // ENABLE_LEGACY_OPENGL_REMOVAL { - if (! m_triangles_valid) + if (! m_result) recalculate_triangles(); #if ENABLE_LEGACY_OPENGL_REMOVAL GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); @@ -130,8 +129,10 @@ void MeshClipper::render_contour() const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - m_model_expanded.set_color(color); - m_model_expanded.render(); + for (CutIsland& isl : m_result->cut_islands) { + isl.model_expanded.set_color(color); + isl.model_expanded.render(); + } shader->stop_using(); } @@ -145,8 +146,24 @@ void MeshClipper::render_contour() +void MeshClipper::pass_mouse_click(const Vec3d& point_in) +{ + if (! m_result || m_result->cut_islands.empty()) + return; + Vec3d point = m_result->trafo.inverse() * point_in; + Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y())); + + for (CutIsland& isl : m_result->cut_islands) { + if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) + isl.disabled = ! isl.disabled; + } +} + void MeshClipper::recalculate_triangles() { + m_result = ClipResult(); + + #if ENABLE_WORLD_COORDINATE const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast(); #else @@ -176,6 +193,8 @@ void MeshClipper::recalculate_triangles() tr.rotate(q); tr = m_trafo.get_matrix() * tr; + m_result->trafo = tr; + if (m_limiting_plane != ClippingPlane::ClipsNothing()) { // Now remove whatever ended up below the limiting plane (e.g. sinking objects). @@ -231,88 +250,72 @@ void MeshClipper::recalculate_triangles() tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting - std::vector triangles2d = m_fill_cut - ? triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.) - : std::vector(); #if ENABLE_LEGACY_OPENGL_REMOVAL - m_model.reset(); + std::vector triangles2d; - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(triangles2d.size()); - init_data.reserve_indices(triangles2d.size()); + for (const ExPolygon& exp : expolys) { + triangles2d.clear(); - // vertices + indices - for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - const size_t idx = it - triangles2d.cbegin(); - init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + m_result->cut_islands.push_back(CutIsland()); + CutIsland& isl = m_result->cut_islands.back(); + + if (m_fill_cut) { + triangles2d = triangulate_expolygon_2f(exp, m_trafo.get_matrix().matrix().determinant() < 0.); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(triangles2d.size()); + init_data.reserve_indices(triangles2d.size()); + + // vertices + indices + for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - triangles2d.cbegin(); + init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + } + + if (!init_data.is_empty()) + isl.model.init_from(std::move(init_data)); + } + + if (m_contour_width != 0.) { + triangles2d.clear(); + ExPolygons expolys_exp = offset_ex(exp, scale_(m_contour_width)); + expolys_exp = diff_ex(expolys_exp, ExPolygons({exp})); + triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.); + GLModel::Geometry init_data = GLModel::Geometry(); + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(triangles2d.size()); + init_data.reserve_indices(triangles2d.size()); + + // vertices + indices + for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - triangles2d.cbegin(); + init_data.add_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); + } + + if (!init_data.is_empty()) + isl.model_expanded.init_from(std::move(init_data)); + } + + isl.expoly = std::move(exp); + isl.expoly_bb = get_extents(exp); } - - if (!init_data.is_empty()) - m_model.init_from(std::move(init_data)); #else - m_vertex_array.release_geometry(); - for (auto it=triangles2d.cbegin(); it != triangles2d.cend(); it=it+3) { - m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); - const size_t idx = it - triangles2d.cbegin(); - m_vertex_array.push_triangle(idx, idx+1, idx+2); - } - m_vertex_array.finalize_geometry(true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - - triangles2d = {}; - if (m_contour_width != 0.) { - ExPolygons expolys_exp = offset_ex(expolys, scale_(m_contour_width)); - expolys_exp = diff_ex(expolys_exp, expolys); - triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.); - } - - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_model_expanded.reset(); - - init_data = GLModel::Geometry(); - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(triangles2d.size()); - init_data.reserve_indices(triangles2d.size()); - - // vertices + indices - for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - const size_t idx = it - triangles2d.cbegin(); - init_data.add_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); - //if (init_data./*format.*/index_type == GLModel::Geometry::EIndexType::USHORT) - // init_data.add_ushort_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); - //else - // init_data.add_uint_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); - } - - if (!init_data.is_empty()) - m_model_expanded.init_from(std::move(init_data)); -#else - m_vertex_array_expanded.release_geometry(); - for (auto it=triangles2d.cbegin(); it != triangles2d.cend(); it=it+3) { - m_vertex_array_expanded.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); - m_vertex_array_expanded.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); - m_vertex_array_expanded.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); - const size_t idx = it - triangles2d.cbegin(); - m_vertex_array_expanded.push_triangle(idx, idx+1, idx+2); - } - m_vertex_array_expanded.finalize_geometry(true); + #error NOT IMPLEMENTED #endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_triangles_valid = true; +#if ENABLE_LEGACY_OPENGL_REMOVAL +#else + #error NOT IMPLEMENTED +#endif // ENABLE_LEGACY_OPENGL_REMOVAL } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 5df4236a6..726f228ca 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -14,6 +14,7 @@ #endif // ENABLE_LEGACY_OPENGL_REMOVAL #include +#include #if ENABLE_RAYCAST_PICKING #include #endif // ENABLE_RAYCAST_PICKING @@ -112,6 +113,9 @@ public: void render_cut(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL + void pass_mouse_click(const Vec3d& pt); + + private: void recalculate_triangles(); @@ -121,12 +125,24 @@ private: ClippingPlane m_plane; ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing(); #if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel m_model; - GLModel m_model_expanded; + + struct CutIsland { + GLModel model; + GLModel model_expanded; + ExPolygon expoly; + BoundingBox expoly_bb; + bool disabled = false; + }; + struct ClipResult { + std::vector cut_islands; + Transform3d trafo; // this rotates the cut into world coords + }; + std::optional m_result; + #else + #error NOT IMLEMENTED GLIndexedVertexArray m_vertex_array; #endif // ENABLE_LEGACY_OPENGL_REMOVAL - bool m_triangles_valid = false; bool m_fill_cut = true; double m_contour_width = 0.; }; From 582eccd51bc93542a73eb9d0aac9924c88c3064c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 19 Sep 2022 10:58:20 +0200 Subject: [PATCH 67/97] Cut: turn off contour disabling for now --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 905e39db3..d235ad4c5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1942,7 +1942,9 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi update_model_object(); m_parent.set_as_dirty(); } else { - m_c->object_clipper()->pass_mouse_click(pos_world); + // Following would inform the clipper about the mouse click, so it can + // toggle the respective contour as disabled. + //m_c->object_clipper()->pass_mouse_click(pos_world); } return true; From 75f3d1bddbc5d39eaf360c63113ab0a0c8ba04cc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 19 Sep 2022 10:56:32 +0200 Subject: [PATCH 68/97] Cut: fix cutting plane when object is anisotropically scaled --- src/slic3r/GUI/MeshUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 74fe82494..e7c1d0fe9 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -177,7 +177,7 @@ void MeshClipper::recalculate_triangles() // Now do the cutting MeshSlicingParams slicing_params; - slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); + slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up_noscale.cast(), Vec3d::UnitZ())); ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); @@ -188,7 +188,7 @@ void MeshClipper::recalculate_triangles() // Triangulate and rotate the cut into world coords: Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), up); + q.setFromTwoVectors(Vec3d::UnitZ(), up_noscale.cast()); Transform3d tr = Transform3d::Identity(); tr.rotate(q); tr = m_trafo.get_matrix() * tr; From e93ff4d087ff048c32f5d09e85810b9e15bed9a0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 19 Sep 2022 17:53:01 +0200 Subject: [PATCH 69/97] WIP Cut: Fixed transformation of a cut plane and a clipper. + Fixed a picking of the scaled grabbers + Code refactoring --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 684 +++++++++++++-------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 23 +- 2 files changed, 355 insertions(+), 352 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index d235ad4c5..9e1183c07 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -4,11 +4,6 @@ #include -#include -#include -#include -#include - #include #include "slic3r/GUI/GUI_App.hpp" @@ -16,7 +11,6 @@ #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/AppConfig.hpp" -#include "libslic3r/Model.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" #include "imgui/imgui_internal.h" @@ -34,6 +28,8 @@ const unsigned int ScaleLongEvery = 2; const float ScaleLongTooth = 0.1f; // in percent of radius const unsigned int SnapRegionsCount = 8; +using namespace Geometry; + // Generates mesh for a line static GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) { @@ -51,6 +47,26 @@ static GLModel::Geometry its_make_line(Vec3f beg_pos, Vec3f end_pos) return init_data; } +// Generates mesh for a square plane +static GLModel::Geometry its_make_square_plane(float radius) +{ + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(-radius, -radius, 0.0)); + init_data.add_vertex(Vec3f(radius , -radius, 0.0)); + init_data.add_vertex(Vec3f(radius , radius , 0.0)); + init_data.add_vertex(Vec3f(-radius, radius , 0.0)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + return init_data; +} + //! -- #ysFIXME those functions bodies are ported from GizmoRotation // Generates mesh for a circle static void init_from_circle(GLModel& model, double radius) @@ -63,7 +79,7 @@ static void init_from_circle(GLModel& model, double radius) // vertices + indices for (unsigned int i = 0; i < ScaleStepsCount; ++i) { const float angle = float(i * ScaleStepRad); - init_data.add_vertex(Vec3f(::cos(angle) * radius, ::sin(angle) * radius, 0.0f)); + init_data.add_vertex(Vec3f(::cos(angle) * float(radius), ::sin(angle) * float(radius), 0.0f)); init_data.add_index(i); } @@ -74,8 +90,8 @@ static void init_from_circle(GLModel& model, double radius) // Generates mesh for a scale static void init_from_scale(GLModel& model, double radius) { - const float out_radius_long = radius * (1.0f + ScaleLongTooth); - const float out_radius_short = radius * (1.0f + 0.5f * ScaleLongTooth); + const float out_radius_long = float(radius) * (1.0f + ScaleLongTooth); + const float out_radius_short = float(radius) * (1.0f + 0.5f * ScaleLongTooth); GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; @@ -87,8 +103,8 @@ static void init_from_scale(GLModel& model, double radius) const float angle = float(i * ScaleStepRad); const float cosa = ::cos(angle); const float sina = ::sin(angle); - const float in_x = cosa * radius; - const float in_y = sina * radius; + const float in_x = cosa * float(radius); + const float in_y = sina * float(radius); const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; @@ -108,7 +124,7 @@ static void init_from_scale(GLModel& model, double radius) static void init_from_snap_radii(GLModel& model, double radius) { const float step = 2.0f * float(PI) / float(SnapRegionsCount); - const float in_radius = radius / 3.0f; + const float in_radius = float(radius) / 3.0f; const float out_radius = 2.0f * in_radius; GLModel::Geometry init_data; @@ -118,7 +134,7 @@ static void init_from_snap_radii(GLModel& model, double radius) // vertices + indices for (unsigned int i = 0; i < ScaleStepsCount; ++i) { - const float angle = float(i * step); + const float angle = float(i) * step; const float cosa = ::cos(angle); const float sina = ::sin(angle); const float in_x = cosa * in_radius; @@ -144,7 +160,7 @@ static void init_from_angle_arc(GLModel& model, double angle, double radius) model.reset(); const float step_angle = float(angle) / float(AngleResolution); - const float ex_radius = radius; + const float ex_radius = float(radius); GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 }; @@ -165,11 +181,10 @@ static void init_from_angle_arc(GLModel& model, double angle, double radius) GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) + , m_connectors_group_id (3) , m_connector_type (CutConnectorType::Plug) , m_connector_style (size_t(CutConnectorStyle::Prizm)) , m_connector_shape_id (size_t(CutConnectorShape::Circle)) - , m_rotation_matrix(Slic3r::Matrix3d::Identity()) - , m_connectors_group_id (3) { m_modes = { _u8L("Planar")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") @@ -212,8 +227,7 @@ std::string GLGizmoCut3D::get_tooltip() const } if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { std::string axis = m_hover_id == X ? "X" : "Y"; -// return axis + ": " + format(float(Geometry::rad2deg(Geometry::Transformation(m_rotation_m).get_rotation()[m_hover_id])), 1) + _u8L("°"); - return axis + ": " + format(float(Geometry::rad2deg(m_angle)), 1) + _u8L("°"); + return axis + ": " + format(float(rad2deg(m_angle)), 1) + _u8L("°"); } return tooltip; @@ -310,18 +324,9 @@ void GLGizmoCut3D::shift_cut_z(double delta) set_center(new_cut_center); } -void GLGizmoCut3D::rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center) +void GLGizmoCut3D::rotate_vec3d_around_plane_center(Vec3d&vec) { - if (m_rotations != angles) { - m_rotation_matrix = Eigen::AngleAxisd(angles[Z], Vec3d::UnitZ()) - * Eigen::AngleAxisd(angles[Y], Vec3d::UnitY()) - * Eigen::AngleAxisd(angles[X], Vec3d::UnitX()); - m_rotations = angles; - } - - vec -= center; - vec = m_rotation_matrix * vec; - vec += center; + vec = Transformation( assemble_transform(m_plane_center) * m_rotation_m * assemble_transform(-m_plane_center)).get_matrix() * vec; } void GLGizmoCut3D::put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset) @@ -345,17 +350,14 @@ void GLGizmoCut3D::put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_ void GLGizmoCut3D::update_clipper() { - const Vec3d angles = Geometry::Transformation(m_rotation_m).get_rotation(); BoundingBoxf3 box = bounding_box(); - double radius = box.radius(); - Vec3d beg, end = beg = m_plane_center; - beg[Z] = box.center().z() - radius;//box.min.z(); - end[Z] = box.center().z() + radius;//box.max.z(); + beg[Z] = box.center().z() - m_radius; + end[Z] = box.center().z() + m_radius; - rotate_vec3d_around_center(beg, angles, m_plane_center); - rotate_vec3d_around_center(end, angles, m_plane_center); + rotate_vec3d_around_plane_center(beg); + rotate_vec3d_around_plane_center(end); double dist = (m_plane_center - beg).norm(); @@ -460,20 +462,20 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, double& float value = (float)value_in; if (m_imperial_units) - value *= ObjectManipulation::mm_to_in; + value *= float(ObjectManipulation::mm_to_in); float old_val = value; const BoundingBoxf3 bbox = bounding_box(); float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0); float min_size = 1.f; if (m_imperial_units) { - mean_size *= ObjectManipulation::mm_to_in; - min_size *= ObjectManipulation::mm_to_in; + mean_size *= float(ObjectManipulation::mm_to_in); + min_size *= float(ObjectManipulation::mm_to_in); } std::string format = m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); m_imgui->slider_float(("##" + label).c_str(), &value, min_size, mean_size, format.c_str()); - value_in = (double)(value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0)); + value_in = (double)(value) * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); ImGui::SameLine(m_label_width + m_control_width + 3); ImGui::PushItemWidth(m_control_width * 0.3f); @@ -490,13 +492,13 @@ void GLGizmoCut3D::render_move_center_input(int axis) ImGui::AlignTextToFramePadding(); m_imgui->text(m_axis_names[axis]+":"); ImGui::SameLine(); - ImGui::PushItemWidth(0.3*m_control_width); + ImGui::PushItemWidth(0.3f*m_control_width); Vec3d move = m_plane_center; double in_val, value = in_val = move[axis]; if (m_imperial_units) value *= ObjectManipulation::mm_to_in; - ImGui::InputDouble(("##move_" + m_axis_names[axis]).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + ImGui::InputDouble(("##move_" + m_axis_names[axis]).c_str(), &value, 0.0, 0.0, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); double val = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); @@ -570,36 +572,11 @@ void GLGizmoCut3D::render_cut_plane() shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( - m_plane_center, - Geometry::Transformation(m_rotation_m).get_rotation(), - Vec3d::Ones(), - Vec3d::Ones() - ); + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; + shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - if (!m_plane.is_initialized()) { - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; -// init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - float radius = (float)bounding_box().radius(); - init_data.add_vertex(Vec3f(-radius, -radius, 0.0)); - init_data.add_vertex(Vec3f( radius, -radius, 0.0)); - init_data.add_vertex(Vec3f( radius, radius, 0.0)); - init_data.add_vertex(Vec3f(-radius, radius, 0.0)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_plane.init_from(std::move(init_data)); - } - if (can_perform_cut()) m_plane.set_color({ 0.8f, 0.8f, 0.8f, 0.5f }); else @@ -612,7 +589,12 @@ void GLGizmoCut3D::render_cut_plane() shader->stop_using(); } -void GLGizmoCut3D::render_cut_center_graber() +static float get_grabber_mean_size(const BoundingBoxf3& bb) +{ + return float((bb.size().x() + bb.size().y() + bb.size().z()) / 3.0); +} + +void GLGizmoCut3D::render_cut_center_grabber() { glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); @@ -623,12 +605,12 @@ void GLGizmoCut3D::render_cut_center_graber() ColorRGBA color = m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; const Camera& camera = wxGetApp().plater()->get_camera(); - const Grabber& graber = m_grabbers.front(); + const Grabber& grabber = m_grabbers.front(); const BoundingBoxf3 box = bounding_box(); - const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); - double size = m_dragging && m_hover_id == Z ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + const float mean_size = get_grabber_mean_size(box); + double size = m_dragging && m_hover_id == Z ? double(grabber.get_dragging_half_size(mean_size)) : double(grabber.get_half_size(mean_size)); Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); Vec3d offset = 1.25 * size * Vec3d::UnitZ(); @@ -637,7 +619,7 @@ void GLGizmoCut3D::render_cut_center_graber() shader->set_uniform("emission_factor", 0.1f); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - const Transform3d view_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_rotation_m; + const Transform3d view_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; auto render = [shader, this](GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix) { shader->set_uniform("view_model_matrix", view_model_matrix); @@ -657,7 +639,7 @@ void GLGizmoCut3D::render_cut_center_graber() line_shader->set_uniform("emission_factor", 0.1f); line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - const Transform3d trafo = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, m_grabber_connection_len)); + const Transform3d trafo = view_matrix * assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, m_grabber_connection_len)); line_shader->set_uniform("view_model_matrix", trafo); line_shader->set_uniform("normal_matrix", (Matrix3d)trafo.matrix().block(0, 0, 3, 3).inverse().transpose()); line_shader->set_uniform("width", 0.2f); @@ -674,9 +656,9 @@ void GLGizmoCut3D::render_cut_center_graber() Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_start_dragging_m; if (axis == X) - view_model_matrix = view_model_matrix * Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-PI * Vec3d::UnitZ()); + view_model_matrix = view_model_matrix * rotation_transform( 0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ()); else - view_model_matrix = view_model_matrix * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); + view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * rotation_transform(-0.5 * PI * Vec3d::UnitY()); shader->stop_using(); @@ -710,33 +692,33 @@ void GLGizmoCut3D::render_cut_center_graber() if ((!m_dragging && m_hover_id < 0)) render_grabber_connection(color); - render(m_sphere.model, color, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); + render(m_sphere.model, color, view_matrix * assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); if (!m_dragging && m_hover_id < 0 || m_hover_id == Z) { const BoundingBoxf3 tbb = transformed_bounding_box(); if (tbb.min.z() <= 0.0) - render(m_cone.model, color, view_matrix * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); + render(m_cone.model, color, view_matrix * assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); if (tbb.max.z() >= 0.0) - render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, Vec3d::Zero(), cone_scale)); + render(m_cone.model, color, view_matrix * assemble_transform(offset, Vec3d::Zero(), cone_scale)); } // render top sphere for X/Y grabbers if ((!m_dragging && m_hover_id < 0) || m_hover_id == X || m_hover_id == Y) { - size = m_dragging ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + size = m_dragging ? double(grabber.get_dragging_half_size(mean_size)) : double(grabber.get_half_size(mean_size)); color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::GRAY(); - render(m_sphere.model, color, view_matrix * Geometry::assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); + render(m_sphere.model, color, view_matrix * assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); } // render X grabber if ((!m_dragging && m_hover_id < 0) || m_hover_id == X) { - size = m_dragging && m_hover_id == X ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + size = m_dragging && m_hover_id == X ? double(grabber.get_dragging_half_size(mean_size)) : double(grabber.get_half_size(mean_size)); cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); @@ -746,16 +728,16 @@ void GLGizmoCut3D::render_cut_center_graber() } offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); + render(m_cone.model, color, view_matrix * assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); + render(m_cone.model, color, view_matrix * assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); } // render Y grabber if ((!m_dragging && m_hover_id < 0) || m_hover_id == Y) { - size = m_dragging && m_hover_id == Y ? double(graber.get_dragging_half_size(mean_size)) : double(graber.get_half_size(mean_size)); + size = m_dragging && m_hover_id == Y ? double(grabber.get_dragging_half_size(mean_size)) : double(grabber.get_half_size(mean_size)); cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); @@ -765,9 +747,9 @@ void GLGizmoCut3D::render_cut_center_graber() } offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); + render(m_cone.model, color, view_matrix * assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); + render(m_cone.model, color, view_matrix * assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); } shader->stop_using(); @@ -826,7 +808,7 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) m_ar_plane_center, m_ar_rotations); set_center_pos(m_ar_plane_center, true); - m_rotation_m = Geometry::rotation_transform(m_ar_rotations); + m_rotation_m = rotation_transform(m_ar_rotations); force_update_clipper_on_render = true; @@ -852,7 +834,7 @@ void GLGizmoCut3D::on_set_state() // initiate archived values m_ar_plane_center = m_plane_center; - m_ar_rotations = Geometry::Transformation(m_rotation_m).get_rotation(); + m_ar_rotations = Transformation(m_rotation_m).get_rotation(); m_parent.request_extra_frame(); } @@ -872,7 +854,7 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() if (m_connectors_editing) { if (CommonGizmosDataObjects::SelectionInfo* si = m_c->selection_info()) { const CutConnectors& connectors = si->model_object()->cut_connectors; - for (size_t i = 0; i < connectors.size(); ++i) + for (int i = 0; i < int(connectors.size()); ++i) m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i + m_connectors_group_id, *(m_shapes[connectors[i].attribs]).mesh_raycaster, Transform3d::Identity())); } } @@ -952,36 +934,36 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() } pos[Z] += sla_shift; - m_raycasters[i]->set_transform(Geometry::assemble_transform( + m_raycasters[i]->set_transform(assemble_transform( pos, - Geometry::Transformation(m_rotation_m).get_rotation(), + Transformation(m_rotation_m).get_rotation(), Vec3d(connector.radius, connector.radius, height) )); } } else { - const Transform3d trafo = Geometry::translation_transform(m_plane_center) * m_rotation_m; + const Transform3d trafo = translation_transform(m_plane_center) * m_rotation_m; const BoundingBoxf3 box = bounding_box(); - const double mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 6.0); + const float mean_size = get_grabber_mean_size(box); double size = double(m_grabbers.front().get_half_size(mean_size)); Vec3d scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); Vec3d offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); - m_raycasters[0]->set_transform(trafo * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), scale)); + m_raycasters[0]->set_transform(trafo * assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), scale)); offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); - m_raycasters[1]->set_transform(trafo * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), scale)); + m_raycasters[1]->set_transform(trafo * assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), scale)); offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); - m_raycasters[2]->set_transform(trafo * Geometry::assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), scale)); + m_raycasters[2]->set_transform(trafo * assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); - m_raycasters[3]->set_transform(trafo * Geometry::assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), scale)); + m_raycasters[3]->set_transform(trafo * assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), scale)); offset = 1.25 * size * Vec3d::UnitZ(); - m_raycasters[4]->set_transform(trafo * Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); - m_raycasters[5]->set_transform(trafo * Geometry::assemble_transform(-offset, PI * Vec3d::UnitX(), scale)); - m_raycasters[6]->set_transform(trafo * Geometry::assemble_transform(offset, Vec3d::Zero(), scale)); + m_raycasters[4]->set_transform(trafo * assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); + m_raycasters[5]->set_transform(trafo * assemble_transform(-offset, PI * Vec3d::UnitX(), scale)); + m_raycasters[6]->set_transform(trafo * assemble_transform(offset, Vec3d::Zero(), scale)); } } @@ -1039,7 +1021,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) if (m_hover_id == Z) { Vec3d starting_box_center = m_plane_center - Vec3d::UnitZ();// some Margin - rotate_vec3d_around_center(starting_box_center, Geometry::Transformation(m_rotation_m).get_rotation(), m_plane_center); + rotate_vec3d_around_plane_center(starting_box_center); const Vec3d& starting_drag_position = m_plane_center; double projection = 0.0; @@ -1103,7 +1085,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) theta += 0.5 * PI; rotation[m_hover_id] = theta; - m_rotation_m = m_start_dragging_m * Geometry::rotation_transform(rotation); + m_rotation_m = m_start_dragging_m * rotation_transform(rotation); m_angle = theta; while (m_angle > two_pi) @@ -1141,7 +1123,7 @@ void GLGizmoCut3D::on_stop_dragging() m_angle_arc.reset(); m_angle = 0.0; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); - m_ar_rotations = Geometry::Transformation(m_rotation_m).get_rotation(); + m_ar_rotations = Transformation(m_rotation_m).get_rotation(); m_start_dragging_m = m_rotation_m; } @@ -1190,8 +1172,8 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= m_c->selection_info()->get_sla_shift(); - const auto move = Geometry::assemble_transform(-cut_center_offset); - const auto move2 = Geometry::assemble_transform(m_plane_center); + const auto move = assemble_transform(-cut_center_offset); + const auto move2 = assemble_transform(m_plane_center); const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * m_rotation_m.inverse() * move; @@ -1202,17 +1184,16 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* // respect just to the solid parts for FFF and ignore pad and supports for SLA if (!volume->is_modifier && !volume->is_sla_pad() && !volume->is_sla_support()) { - const auto instance_matrix = Geometry::assemble_transform( + const auto instance_matrix = assemble_transform( Vec3d::Zero(), // don't apply offset volume->get_instance_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), volume->get_instance_scaling_factor(), volume->get_instance_mirror() ); - auto volume_travo = instance_matrix * volume->get_volume_transformation().get_matrix(); - auto volume_offset = Geometry::Transformation(volume_travo).get_offset(); + auto volume_trafo = instance_matrix * volume->get_volume_transformation().get_matrix(); - ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * volume_travo)); + ret.merge(volume->transformed_convex_hull_bounding_box(cut_matrix * volume_trafo)); } } return ret; @@ -1274,15 +1255,8 @@ void GLGizmoCut3D::init_picking_models() } } -void GLGizmoCut3D::on_render() +void GLGizmoCut3D::init_rendering_items() { - if (update_bb() || force_update_clipper_on_render) { - update_clipper_on_render(); - m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); - } - - init_picking_models(); - if (!m_grabber_connection.is_initialized()) m_grabber_connection.init_from(its_make_line(Vec3f::Zero(), Vec3f::UnitZ())); if (!m_circle.is_initialized()) @@ -1298,6 +1272,21 @@ void GLGizmoCut3D::on_render() if (!m_angle_arc.is_initialized() || m_angle != 0.0) init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); + if (!m_plane.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) + m_plane.init_from(its_make_square_plane(float(m_radius))); +} + +void GLGizmoCut3D::on_render() +{ + if (update_bb() || force_update_clipper_on_render) { + update_clipper_on_render(); + m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); + } + + init_picking_models(); + + init_rendering_items(); + render_connectors(); if (! m_connectors_editing) @@ -1308,27 +1297,41 @@ void GLGizmoCut3D::on_render() if (!m_hide_cut_plane && !m_connectors_editing) { render_cut_plane(); - render_cut_center_graber(); + render_cut_center_grabber(); } render_cut_line(); } -void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) +void GLGizmoCut3D::render_debug_block() +{ + m_imgui->begin(wxString("DEBUG")); + + 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) + m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); + m_imgui->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)); + + m_imgui->end(); +} + +void GLGizmoCut3D::adjust_window_position(float x, float y, float bottom_limit) { static float last_y = 0.0f; static float last_h = 0.0f; - m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - m_label_width = m_imgui->get_style_scaling() * 100.0f; - m_control_width = m_imgui->get_style_scaling() * 150.0f; - - // adjust window position to avoid overlap the view toolbar const float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if (last_h != win_h || last_y != y) { // ask canvas for another frame to render the window in the correct position m_imgui->set_requires_extra_frame(); @@ -1337,247 +1340,244 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) if (last_y != y) last_y = y; } +} +void GLGizmoCut3D::render_connectors_editing(CutConnectors &connectors) +{ + // add shortcuts panel + if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) + m_show_shortcuts = !m_show_shortcuts; + + if (m_show_shortcuts) + for (const auto&shortcut : m_shortcuts ){ + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, shortcut.first); + ImGui::SameLine(m_label_width); + m_imgui->text(shortcut.second); + } + + // Connectors section + + ImGui::Separator(); + + // WIP : Auto : Need to implement + // m_imgui->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_ORANGE_LIGHT, _L("Connectors")); + + m_imgui->disabled_begin(connectors.empty()); + ImGui::SameLine(m_label_width); + if (m_imgui->button(" " + _L("Reset") + " ##connectors")) + reset_connectors(); + m_imgui->disabled_end(); + + m_imgui->text(_L("Type")); + bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); + type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); + if (type_changed) + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) + connectors[idx].attribs.type = CutConnectorType(m_connector_type); + + if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) + connectors[idx].attribs.style = CutConnectorStyle(m_connector_style) ; + + if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) + connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); + + if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) + for (size_t idx = 0; idx < m_selected.size() ; idx++) + if (m_selected[idx]) { + auto&connector = connectors[idx]; + connector.height = float(m_connector_depth_ratio); + connector.height_tolerance = 0.01f * float(m_connector_depth_ratio_tolerance); + } + + if (render_slider_double_input(_u8L("Size"), m_connector_size, m_connector_size_tolerance)) + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) { + auto&connector = connectors[idx]; + connector.radius = float(m_connector_size * 0.5); + connector.radius_tolerance = 0.01f * float(m_connector_size_tolerance); + } + + if (m_imgui->button(_L("Confirm connectors"))) { + m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); + m_connectors_editing = false; + update_raycasters_for_picking(); + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected_count = 0; + } + + m_parent.request_extra_frame(); +} + +void GLGizmoCut3D::render_cut_plane_editing(CutConnectors &connectors) +{ + // WIP : cut plane mode // render_combo(_u8L("Mode"), m_modes, m_mode); - CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - bool cut_clicked = false; - bool revert_move{ false }; - bool revert_rotation{ false }; - bool fff_printer = wxGetApp().plater()->printer_technology() == ptFFF; + if (m_mode == size_t(CutMode::cutPlanar)) { + ImGui::AlignTextToFramePadding(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); + ImGui::Separator(); - if (! m_connectors_editing) { - if (m_mode == size_t(CutMode::cutPlanar)) { - ImGui::AlignTextToFramePadding(); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); - ImGui::Separator(); - - //////// - double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; - std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm"); - const BoundingBoxf3 tbb = transformed_bounding_box(); - double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; - double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; + double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; + std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm"); + const BoundingBoxf3 tbb = transformed_bounding_box(); - Vec3d tbb_sz = tbb.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; + Vec3d tbb_sz = tbb.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; - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Build size")); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Build size")); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); - //static float v = 0.; // TODO: connect to cutting plane position - m_imgui->text(_L("Cut position: ")); - render_move_center_input(Z); - //m_imgui->input_double(unit_str, v); - //v = std::clamp(v, 0.f, float(bottom+top)); - if (m_imgui->button("Reset cutting plane")) { - set_center(bounding_box().center()); - m_rotation_m = Transform3d::Identity(); - m_angle_arc.reset(); - update_clipper(); - } + m_imgui->text(_L("Cut position: ")); + render_move_center_input(Z); + + if (m_imgui->button("Reset cutting plane")) { + set_center(bounding_box().center()); + m_rotation_m = Transform3d::Identity(); + m_angle_arc.reset(); + update_clipper(); } - if (m_mode == size_t(CutMode::cutPlanar)) { - ImGui::Separator(); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("After cut") + ": "); - bool keep = true; - - ImGui::SameLine(m_label_width); - m_imgui->text(_L("Upper part")); - ImGui::SameLine(2 * m_label_width); - - m_imgui->disabled_begin(!connectors.empty()); - m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); - m_imgui->disabled_end(); - ImGui::SameLine(3 * m_label_width); - - m_imgui->disabled_begin(!m_keep_upper); - - m_imgui->disabled_begin(is_approx(Geometry::Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Geometry::Transformation(m_rotation_m).get_rotation().y(), 0.)); - if (m_imgui->checkbox(_L("Place on cut") + "##upper", m_place_on_cut_upper)) - m_rotate_upper = false; - m_imgui->disabled_end(); - - ImGui::SameLine(); - if (m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper)) - m_place_on_cut_upper = false; - - m_imgui->disabled_end(); - - m_imgui->text(""); - ImGui::SameLine(m_label_width); - m_imgui->text(_L("Lower part")); - ImGui::SameLine(2 * m_label_width); - - m_imgui->disabled_begin(!connectors.empty()); - - m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); - m_imgui->disabled_end(); - ImGui::SameLine(3 * m_label_width); - m_imgui->disabled_begin(!m_keep_lower); - - if (m_imgui->checkbox(_L("Place on cut") + "##lower", m_place_on_cut_lower)) - m_rotate_lower = false; - ImGui::SameLine(); - if (m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower)) - m_place_on_cut_lower = false; - - m_imgui->disabled_end(); - } - - if (fff_printer) { - ImGui::Separator(); - - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); - if (m_imgui->button(_L("Add/Edit connectors"))) { - m_connectors_editing = true; - update_raycasters_for_picking(); - } - m_imgui->disabled_end(); - } - } - else { // connectors mode - if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) - m_show_shortcuts = !m_show_shortcuts; - - if (m_show_shortcuts) - for (const auto& shortcut : m_shortcuts ){ - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, shortcut.first); - ImGui::SameLine(m_label_width); - m_imgui->text(shortcut.second); - } - - m_imgui->disabled_begin(!m_keep_lower || !m_keep_upper); - // Connectors section ImGui::Separator(); ImGui::AlignTextToFramePadding(); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Connectors")); + m_imgui->text(_L("After cut") + ": "); + bool keep = true; - m_imgui->disabled_begin(connectors.empty()); ImGui::SameLine(m_label_width); - if (m_imgui->button(" " + _L("Reset") + " ##connectors")) - reset_connectors(); + m_imgui->text(_L("Upper part")); + ImGui::SameLine(2 * m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); m_imgui->disabled_end(); -/* - m_imgui->text(_L("Mode")); - render_connect_mode_radio_button(CutConnectorMode::Auto); - render_connect_mode_radio_button(CutConnectorMode::Manual); -*/ + ImGui::SameLine(3 * m_label_width); - if (m_selected_count == 1) - for (size_t idx = 0; idx < m_selected.size(); idx++) - if (m_selected[idx]) { - auto& connector = connectors[idx]; - m_connector_depth_ratio = connector.height; - m_connector_depth_ratio_tolerance = 100 * connector.height_tolerance; - m_connector_size = 2. * connector.radius; - m_connector_size_tolerance = 100 * connector.radius_tolerance; - m_connector_type = connector.attribs.type; - m_connector_style = size_t(connector.attribs.style); - m_connector_shape_id = size_t(connector.attribs.shape); + m_imgui->disabled_begin(!m_keep_upper); - break; - } + m_imgui->disabled_begin(is_approx(Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Transformation(m_rotation_m).get_rotation().y(), 0.)); + if (m_imgui->checkbox(_L("Place on cut") + "##upper", m_place_on_cut_upper)) + m_rotate_upper = false; + m_imgui->disabled_end(); - m_imgui->text(_L("Type")); - bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); - type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); - if (type_changed) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) - connectors[idx].attribs.type = CutConnectorType(m_connector_type); - - if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) - connectors[idx].attribs.style = CutConnectorStyle(m_connector_style) ; - - if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) - connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); - - if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) { - auto& connector = connectors[idx]; - connector.height = float(m_connector_depth_ratio); - connector.height_tolerance = 0.01f * m_connector_depth_ratio_tolerance; - } - - if (render_slider_double_input(_u8L("Size"), m_connector_size, m_connector_size_tolerance)) - for (size_t idx = 0; idx < m_selected.size(); idx++) - if (m_selected[idx]) { - auto& connector = connectors[idx]; - connector.radius = float(m_connector_size * 0.5); - connector.radius_tolerance = 0.01f * m_connector_size_tolerance; - } + ImGui::SameLine(); + if (m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper)) + m_place_on_cut_upper = false; m_imgui->disabled_end(); - if (m_imgui->button(_L("Confirm connectors"))) { - m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); - m_connectors_editing = false; + m_imgui->text(""); + ImGui::SameLine(m_label_width); + m_imgui->text(_L("Lower part")); + ImGui::SameLine(2 * m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + + m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); + m_imgui->disabled_end(); + ImGui::SameLine(3 * m_label_width); + m_imgui->disabled_begin(!m_keep_lower); + + if (m_imgui->checkbox(_L("Place on cut") + "##lower", m_place_on_cut_lower)) + m_rotate_lower = false; + ImGui::SameLine(); + if (m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower)) + m_place_on_cut_lower = false; + + m_imgui->disabled_end(); + } + + if (wxGetApp().plater()->printer_technology() == ptFFF) { + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); + if (m_imgui->button(_L("Add/Edit connectors"))) { + m_connectors_editing = true; update_raycasters_for_picking(); - std::fill(m_selected.begin(), m_selected.end(), false); - m_selected_count = 0; } - m_parent.request_extra_frame(); + m_imgui->disabled_end(); } ImGui::Separator(); - if (fff_printer) + m_imgui->disabled_begin(!can_perform_cut()); + if((m_keep_upper || m_keep_lower) && m_imgui->button(_L("Perform cut"))) + perform_cut(m_parent.get_selection());; + m_imgui->disabled_end(); +} + +void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) +{ + m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + m_label_width = m_imgui->get_style_scaling() * 100.0f; + m_control_width = m_imgui->get_style_scaling() * 150.0f; + + if (m_selected_count == 1) + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) { + auto&connector = connectors[idx]; + m_connector_depth_ratio = connector.height; + m_connector_depth_ratio_tolerance = 100 * connector.height_tolerance; + m_connector_size = 2. * connector.radius; + m_connector_size_tolerance = 100 * connector.radius_tolerance; + m_connector_type = connector.attribs.type; + m_connector_style = size_t(connector.attribs.style); + m_connector_shape_id = size_t(connector.attribs.shape); + + break; + } +} + +void GLGizmoCut3D::render_input_window_warning() const +{ + if (wxGetApp().plater()->printer_technology() == ptFFF) m_imgui->text(m_has_invalid_connector ? wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.") : wxString()); - if (!m_connectors_editing) { - m_imgui->disabled_begin(!can_perform_cut()); - cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); - } + if (!m_keep_upper && !m_keep_lower) + m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid state. \nNo one part is selected for keep after cut")); +} + +void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) +{ + m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + adjust_window_position(x, y, bottom_limit); + + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + + init_input_window_data(connectors); + + if (m_connectors_editing) // connectors mode + render_connectors_editing(connectors); + else + render_cut_plane_editing(connectors); + + render_input_window_warning(); m_imgui->end(); - //////// - m_imgui->begin(wxString("DEBUG")); - 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) - m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); - m_imgui->checkbox("fill_cut", fill_cut); - m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); - m_c->object_clipper()->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, contour_width); - m_imgui->end(); - //////// - - if (cut_clicked && (m_keep_upper || m_keep_lower)) - perform_cut(m_parent.get_selection()); - - if (revert_move) - set_center(bounding_box().center()); - if (revert_rotation) { - m_rotation_m = Transform3d::Identity(); - m_angle_arc.reset(); - update_clipper(); - } + render_debug_block(); } // get volume transformation regarding to the "border". Border is related from the size of connectors Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) const { bool is_prizm_dowel = m_connector_type == CutConnectorType::Dowel && m_connector_style == size_t(CutConnectorStyle::Prizm); - const Transform3d connector_trafo = Geometry::assemble_transform( + const Transform3d connector_trafo = assemble_transform( is_prizm_dowel ? Vec3d(0.0, 0.0, -m_connector_depth_ratio) : Vec3d::Zero(), - Geometry::Transformation(m_rotation_m).get_rotation(), + Transformation(m_rotation_m).get_rotation(), Vec3d(0.5*m_connector_size, 0.5*m_connector_size, is_prizm_dowel ? 2 * m_connector_depth_ratio : m_connector_depth_ratio), Vec3d::Ones()); const Vec3d connector_bb = m_connector_mesh.transformed_bounding_box(connector_trafo).size(); @@ -1593,7 +1593,7 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c const Vec3d offset(vol_trans.x() * border_scale.x(), vol_trans.y() * border_scale.y(), vol_trans.z() * border_scale.z()); // scale and translate volume to suppress to put connectors too close to the border - return Geometry::assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix; + return assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix; } void GLGizmoCut3D::render_connectors() @@ -1636,7 +1636,7 @@ void GLGizmoCut3D::render_connectors() const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; - const Transform3d instance_trafo = Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix(); + const Transform3d instance_trafo = assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix(); m_has_invalid_connector = false; @@ -1682,9 +1682,9 @@ void GLGizmoCut3D::render_connectors() m_shapes[connector.attribs].model.set_color(render_color); - const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform( + const Transform3d view_model_matrix = camera.get_view_matrix() * assemble_transform( pos, - Geometry::Transformation(m_rotation_m).get_rotation(), + Transformation(m_rotation_m).get_rotation(), Vec3d(connector.radius, connector.radius, height) ); shader->set_uniform("view_model_matrix", view_model_matrix); @@ -1727,7 +1727,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) if(!mo) return; - Vec3d rotation = Geometry::Transformation(m_rotation_m).get_rotation(); + Vec3d rotation = Transformation(m_rotation_m).get_rotation(); const bool has_connectors = !mo->cut_connectors.empty(); { @@ -1747,7 +1747,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) else { // culculate shift of the connector center regarding to the position on the cut plane Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); - rotate_vec3d_around_center(shifted_center, rotation, m_plane_center); + rotate_vec3d_around_plane_center(shifted_center); Vec3d norm = (shifted_center - m_plane_center).normalized(); connector.pos += norm * (0.5 * connector.height); } @@ -1781,7 +1781,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; const Transform3d instance_trafo = sla_shift > 0.0 ? - Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); + assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); int mesh_id = -1; @@ -1861,8 +1861,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse const float sla_shift = m_c->selection_info()->get_sla_shift(); const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; - Transform3d inst_trafo = sla_shift > 0.0 ? - Geometry::assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : + Transform3d inst_trafo = sla_shift > 0.f ? + assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); @@ -1930,7 +1930,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); - connectors.emplace_back(hit, Geometry::Transformation(m_rotation_m).get_rotation(), + connectors.emplace_back(hit, Transformation(m_rotation_m).get_rotation(), float(m_connector_size * 0.5), float(m_connector_depth_ratio), float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance), CutConnectorAttributes( CutConnectorType(m_connector_type), diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index a7892a9cd..11c7b1abd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -2,11 +2,9 @@ #define slic3r_GLGizmoCut_hpp_ #include "GLGizmoBase.hpp" -#include "GLGizmoRotate.hpp" -#include "GLGizmoMove.hpp" #include "slic3r/GUI/GLModel.hpp" #include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Model.hpp" namespace Slic3r { @@ -100,9 +98,6 @@ class GLGizmoCut3D : public GLGizmoBase bool m_has_invalid_connector{ false }; - Matrix3d m_rotation_matrix; - Vec3d m_rotations{ Vec3d::Zero() }; - bool m_show_shortcuts{ false }; std::vector> m_shortcuts; @@ -150,7 +145,7 @@ public: bool on_mouse(const wxMouseEvent &mouse_event) override; void shift_cut_z(double delta); - void rotate_vec3d_around_center(Vec3d& vec, const Vec3d& angles, const Vec3d& center); + void rotate_vec3d_around_plane_center(Vec3d&vec); void put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset); void update_clipper(); void update_clipper_on_render(); @@ -174,6 +169,13 @@ protected: void on_stop_dragging() override; void on_render() override; + void render_debug_block(); + void adjust_window_position(float x, float y, float bottom_limit); + void render_connectors_editing(CutConnectors &connectors); + void render_cut_plane_editing(CutConnectors &connectors); + void init_input_window_data(CutConnectors &connectors); + void render_input_window_warning() const; + virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; void update_raycasters_for_picking(); @@ -204,12 +206,13 @@ private: void discard_cut_line_processing(); void render_cut_plane(); - void render_cut_center_graber(); + void render_cut_center_grabber(); void render_cut_line(); - void perform_cut(const Selection& selection); - void set_center_pos(const Vec3d& center_pos, bool force = false); + void perform_cut(const Selection&selection); + void set_center_pos(const Vec3d¢er_pos, bool force = false); bool update_bb(); void init_picking_models(); + void init_rendering_items(); void reset_connectors(); void update_connector_shape(); void update_model_object(); From 94685b5ad89386f1677185b461c4bb68a9b9d457 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 20 Sep 2022 16:38:24 +0200 Subject: [PATCH 70/97] WIP Cut: Fixed an adding/deleting of the connectors to the selection_info + more code refactoring --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 602 ++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 45 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- 3 files changed, 356 insertions(+), 293 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9e1183c07..2adebbbec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -254,10 +254,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) if (use_grabbers(mouse_event)) { if (m_hover_id >= m_connectors_group_id) { - if (mouse_event.LeftDown()) { - std::fill(m_selected.begin(), m_selected.end(), false); - m_selected_count = 0; - } + if (mouse_event.LeftDown() && !mouse_event.CmdDown()&& !mouse_event.AltDown()) + unselect_all_connectors(); if (mouse_event.LeftUp() && !mouse_event.ShiftDown()) gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); } @@ -1012,99 +1010,103 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(Axis axis, const Linef3& mouse return transform(mouse_ray, m).intersect_plane(0.0); } +void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data) +{ + Vec3d starting_box_center = m_plane_center - Vec3d::UnitZ(); // some Margin + rotate_vec3d_around_plane_center(starting_box_center); + + const Vec3d&starting_drag_position = m_plane_center; + double projection = 0.0; + + Vec3d starting_vec = starting_drag_position - starting_box_center; + if (starting_vec.norm() != 0.0) { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - starting_drag_position; + + starting_vec.normalize(); + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec); + } + if (wxGetKeyState(WXK_SHIFT)) + projection = m_snap_step * (double)std::round(projection / m_snap_step); + + const Vec3d shift = starting_vec * projection; + + // move cut plane center + set_center(m_plane_center + shift); +} + +void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) +{ + const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((Axis)m_hover_id, data.mouse_ray)); + + const Vec2d orig_dir = Vec2d::UnitX(); + const Vec2d new_dir = mouse_pos.normalized(); + + const double two_pi = 2.0 * PI; + + double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); + if (cross2(orig_dir, new_dir) < 0.0) + theta = two_pi - theta; + + const double len = mouse_pos.norm(); + // snap to coarse snap region + if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { + const double step = two_pi / double(SnapRegionsCount); + theta = step * std::round(theta / step); + } + // snap to fine snap region (scale) + else if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { + const double step = two_pi / double(ScaleStepsCount); + theta = step * std::round(theta / step); + } + + if (is_approx(theta, two_pi)) + theta = 0.0; + if (m_hover_id == X) + theta += 0.5 * PI; + + Vec3d rotation = Vec3d::Zero(); + rotation[m_hover_id] = theta; + m_rotation_m = m_start_dragging_m * rotation_transform(rotation); + + m_angle = theta; + while (m_angle > two_pi) + m_angle -= two_pi; + if (m_angle < 0.0) + m_angle += two_pi; + + update_clipper(); +} + +void GLGizmoCut3D::dragging_connector(const GLGizmoBase::UpdateData &data) +{ + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; + std::pair pos_and_normal; + Vec3d pos_world; + + if (unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal, pos_world)) { + connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; + update_raycasters_for_picking_transform(); + } +} + void GLGizmoCut3D::on_dragging(const UpdateData& data) { if (m_hover_id < 0) return; - - CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - - if (m_hover_id == Z) { - Vec3d starting_box_center = m_plane_center - Vec3d::UnitZ();// some Margin - rotate_vec3d_around_plane_center(starting_box_center); - - const Vec3d& starting_drag_position = m_plane_center; - double projection = 0.0; - - Vec3d starting_vec = starting_drag_position - starting_box_center; - if (starting_vec.norm() != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - starting_drag_position; - - starting_vec.normalize(); - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec); - } - if (wxGetKeyState(WXK_SHIFT)) - projection = m_snap_step * (double)std::round(projection / m_snap_step); - - Vec3d shift = starting_vec * projection; - - // move cut plane center - set_center(m_plane_center + shift); - } - - else if (m_hover_id == X || m_hover_id == Y) { - - Vec3d rotation = Vec3d::Zero(); - - const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((Axis)m_hover_id, data.mouse_ray)); - - const Vec2d orig_dir = Vec2d::UnitX(); - const Vec2d new_dir = mouse_pos.normalized(); - - const double two_pi = 2.0 * PI; - - double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0)); - if (cross2(orig_dir, new_dir) < 0.0) - theta = two_pi - theta; - - const double len = mouse_pos.norm(); - // snap to coarse snap region - if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) { - const double step = two_pi / double(SnapRegionsCount); - theta = step * std::round(theta / step); - } - else { - // snap to fine snap region (scale) - if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) { - const double step = two_pi / double(ScaleStepsCount); - theta = step * std::round(theta / step); - } - } - - if (theta == two_pi) - theta = 0.0; - if (m_hover_id == X) - theta += 0.5 * PI; - - rotation[m_hover_id] = theta; - m_rotation_m = m_start_dragging_m * rotation_transform(rotation); - - m_angle = theta; - while (m_angle > two_pi) - m_angle -= two_pi; - if (m_angle < 0.0) - m_angle += two_pi; - - update_clipper(); - } - + if (m_hover_id == Z) + dragging_grabber_z(data); + else if (m_hover_id == X || m_hover_id == Y) + dragging_grabber_xy(data); else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) - { - std::pair pos_and_normal; - Vec3d pos_world; - if (!unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal, pos_world)) - return; - connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; - update_raycasters_for_picking_transform(); - } + dragging_connector(data); } void GLGizmoCut3D::on_start_dragging() @@ -1215,10 +1217,10 @@ bool GLGizmoCut3D::update_bb() m_grabber_connection_len = 0.75 * m_radius;// std::min(0.75 * m_radius, 35.0); m_grabber_radius = m_grabber_connection_len * 0.85; - m_snap_coarse_in_radius = m_grabber_radius / 3.0f; - m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_coarse_in_radius = m_grabber_radius / 3.0; + m_snap_coarse_out_radius = m_snap_coarse_in_radius * 2.; m_snap_fine_in_radius = m_grabber_connection_len * 0.85; - m_snap_fine_out_radius = m_grabber_connection_len * 1.15f; + m_snap_fine_out_radius = m_grabber_connection_len * 1.15; m_plane.reset(); m_cone.reset(); @@ -1276,11 +1278,20 @@ void GLGizmoCut3D::init_rendering_items() m_plane.init_from(its_make_square_plane(float(m_radius))); } +void GLGizmoCut3D::render_clipper_cut() +{ + if (! m_connectors_editing) + ::glDisable(GL_DEPTH_TEST); + m_c->object_clipper()->render_cut(); + if (! m_connectors_editing) + ::glEnable(GL_DEPTH_TEST); +} + void GLGizmoCut3D::on_render() { if (update_bb() || force_update_clipper_on_render) { update_clipper_on_render(); - m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4f); + m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4); } init_picking_models(); @@ -1289,11 +1300,7 @@ void GLGizmoCut3D::on_render() render_connectors(); - if (! m_connectors_editing) - ::glDisable(GL_DEPTH_TEST); - m_c->object_clipper()->render_cut(); - if (! m_connectors_editing) - ::glEnable(GL_DEPTH_TEST); + render_clipper_cut(); if (!m_hide_cut_plane && !m_connectors_editing) { render_cut_plane(); @@ -1303,7 +1310,7 @@ void GLGizmoCut3D::on_render() render_cut_line(); } -void GLGizmoCut3D::render_debug_block() +void GLGizmoCut3D::render_debug_input_window() { m_imgui->begin(wxString("DEBUG")); @@ -1342,9 +1349,20 @@ void GLGizmoCut3D::adjust_window_position(float x, float y, float bottom_limit) } } -void GLGizmoCut3D::render_connectors_editing(CutConnectors &connectors) +void GLGizmoCut3D::unselect_all_connectors() +{ + std::fill(m_selected.begin(), m_selected.end(), false); + m_selected_count = 0; +} + +void GLGizmoCut3D::select_all_connectors() +{ + std::fill(m_selected.begin(), m_selected.end(), true); + m_selected_count = int(m_selected.size()); +} + +void GLGizmoCut3D::render_shortcuts() { - // add shortcuts panel if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) m_show_shortcuts = !m_show_shortcuts; @@ -1354,6 +1372,19 @@ void GLGizmoCut3D::render_connectors_editing(CutConnectors &connectors) ImGui::SameLine(m_label_width); m_imgui->text(shortcut.second); } +} + +void GLGizmoCut3D::apply_selected_connectors(std::function apply_fn) +{ + for (size_t idx = 0; idx < m_selected.size(); idx++) + if (m_selected[idx]) + apply_fn(idx); +} + +void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) +{ + // add shortcuts panel + render_shortcuts(); // Connectors section @@ -1377,48 +1408,67 @@ void GLGizmoCut3D::render_connectors_editing(CutConnectors &connectors) bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); if (type_changed) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) - connectors[idx].attribs.type = CutConnectorType(m_connector_type); + apply_selected_connectors([this, &connectors] (size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); }); if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) - connectors[idx].attribs.style = CutConnectorStyle(m_connector_style) ; + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) - connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) - for (size_t idx = 0; idx < m_selected.size() ; idx++) - if (m_selected[idx]) { - auto&connector = connectors[idx]; - connector.height = float(m_connector_depth_ratio); - connector.height_tolerance = 0.01f * float(m_connector_depth_ratio_tolerance); - } + apply_selected_connectors([this, &connectors](size_t idx) { + connectors[idx].height = float(m_connector_depth_ratio); + connectors[idx].height_tolerance = 0.01f * float(m_connector_depth_ratio_tolerance); + }); if (render_slider_double_input(_u8L("Size"), m_connector_size, m_connector_size_tolerance)) - for (size_t idx = 0; idx < m_selected.size(); idx++) - if (m_selected[idx]) { - auto&connector = connectors[idx]; - connector.radius = float(m_connector_size * 0.5); - connector.radius_tolerance = 0.01f * float(m_connector_size_tolerance); - } + apply_selected_connectors([this, &connectors](size_t idx) { + connectors[idx].radius = float(m_connector_size * 0.5); + connectors[idx].radius_tolerance = 0.01f * float(m_connector_size_tolerance); + }); if (m_imgui->button(_L("Confirm connectors"))) { m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); - m_connectors_editing = false; - update_raycasters_for_picking(); - std::fill(m_selected.begin(), m_selected.end(), false); - m_selected_count = 0; + unselect_all_connectors(); + set_connectors_editing(false); } m_parent.request_extra_frame(); } -void GLGizmoCut3D::render_cut_plane_editing(CutConnectors &connectors) +void GLGizmoCut3D::render_build_size() +{ + double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; + std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm"); + const BoundingBoxf3 tbb = transformed_bounding_box(); + + Vec3d tbb_sz = tbb.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; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Build size")); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); +} + +void GLGizmoCut3D::reset_cut_plane() +{ + set_center(bounding_box().center()); + m_rotation_m = Transform3d::Identity(); + m_angle_arc.reset(); + update_clipper(); +} + +void GLGizmoCut3D::set_connectors_editing(bool connectors_editing) +{ + m_connectors_editing = connectors_editing; + update_raycasters_for_picking(); +} + +void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) { // WIP : cut plane mode // render_combo(_u8L("Mode"), m_modes, m_mode); @@ -1426,95 +1476,60 @@ void GLGizmoCut3D::render_cut_plane_editing(CutConnectors &connectors) if (m_mode == size_t(CutMode::cutPlanar)) { ImGui::AlignTextToFramePadding(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); + ImGui::Separator(); - double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; - std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm"); - const BoundingBoxf3 tbb = transformed_bounding_box(); - - Vec3d tbb_sz = tbb.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; + render_build_size(); - ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("Build size")); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); - m_imgui->text(_L("Cut position: ")); render_move_center_input(Z); - if (m_imgui->button("Reset cutting plane")) { - set_center(bounding_box().center()); - m_rotation_m = Transform3d::Identity(); - m_angle_arc.reset(); - update_clipper(); - } + if (m_imgui->button("Reset cutting plane")) + reset_cut_plane(); ImGui::Separator(); + auto render_part_action_line = [this, connectors](const wxString& info_label, const wxString& label, const wxString& suffix, bool& keep_part, bool& place_on_cut_part, bool& rotate_part) { + bool keep = true; + + m_imgui->text(info_label); + ImGui::SameLine(m_label_width); + m_imgui->text(label); + + ImGui::SameLine(2 * m_label_width); + + m_imgui->disabled_begin(!connectors.empty()); + m_imgui->checkbox(_L("Keep") + suffix, connectors.empty() ? keep_part : keep); + m_imgui->disabled_end(); + + ImGui::SameLine(3 * m_label_width); + + m_imgui->disabled_begin(!keep_part); + if (m_imgui->checkbox(_L("Place on cut") + suffix, place_on_cut_part)) + rotate_part = false; + ImGui::SameLine(); + if (m_imgui->checkbox(_L("Flip") + suffix, rotate_part)) + place_on_cut_part = false; + m_imgui->disabled_end(); + }; + ImGui::AlignTextToFramePadding(); - m_imgui->text(_L("After cut") + ": "); - bool keep = true; - - ImGui::SameLine(m_label_width); - m_imgui->text(_L("Upper part")); - ImGui::SameLine(2 * m_label_width); - - m_imgui->disabled_begin(!connectors.empty()); - m_imgui->checkbox(_L("Keep") + "##upper", connectors.empty() ? m_keep_upper : keep); - m_imgui->disabled_end(); - ImGui::SameLine(3 * m_label_width); - - m_imgui->disabled_begin(!m_keep_upper); - - m_imgui->disabled_begin(is_approx(Transformation(m_rotation_m).get_rotation().x(), 0.) && is_approx(Transformation(m_rotation_m).get_rotation().y(), 0.)); - if (m_imgui->checkbox(_L("Place on cut") + "##upper", m_place_on_cut_upper)) - m_rotate_upper = false; - m_imgui->disabled_end(); - - ImGui::SameLine(); - if (m_imgui->checkbox(_L("Flip") + "##upper", m_rotate_upper)) - m_place_on_cut_upper = false; - - m_imgui->disabled_end(); - - m_imgui->text(""); - ImGui::SameLine(m_label_width); - m_imgui->text(_L("Lower part")); - ImGui::SameLine(2 * m_label_width); - - m_imgui->disabled_begin(!connectors.empty()); - - m_imgui->checkbox(_L("Keep") + "##lower", connectors.empty() ? m_keep_lower : keep); - m_imgui->disabled_end(); - ImGui::SameLine(3 * m_label_width); - m_imgui->disabled_begin(!m_keep_lower); - - if (m_imgui->checkbox(_L("Place on cut") + "##lower", m_place_on_cut_lower)) - m_rotate_lower = false; - ImGui::SameLine(); - if (m_imgui->checkbox(_L("Flip") + "##lower", m_rotate_lower)) - m_place_on_cut_lower = false; - - m_imgui->disabled_end(); + render_part_action_line(_L("After cut") + ": ", _L("Upper part"), "##upper", m_keep_upper, m_place_on_cut_upper, m_rotate_upper); + render_part_action_line("", _L("Lower part"), "##lower", m_keep_lower, m_place_on_cut_lower, m_rotate_lower); } if (wxGetApp().plater()->printer_technology() == ptFFF) { m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); - if (m_imgui->button(_L("Add/Edit connectors"))) { - m_connectors_editing = true; - update_raycasters_for_picking(); - } + if (m_imgui->button(_L("Add/Edit connectors"))) + set_connectors_editing(true); m_imgui->disabled_end(); } ImGui::Separator(); m_imgui->disabled_begin(!can_perform_cut()); - if((m_keep_upper || m_keep_lower) && m_imgui->button(_L("Perform cut"))) - perform_cut(m_parent.get_selection());; + if(m_imgui->button(_L("Perform cut"))) + perform_cut(m_parent.get_selection());; m_imgui->disabled_end(); } @@ -1550,7 +1565,7 @@ void GLGizmoCut3D::render_input_window_warning() const void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) { - m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->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); @@ -1560,15 +1575,15 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) init_input_window_data(connectors); if (m_connectors_editing) // connectors mode - render_connectors_editing(connectors); + render_connectors_input_window(connectors); else - render_cut_plane_editing(connectors); + render_cut_plane_input_window(connectors); render_input_window_warning(); m_imgui->end(); - render_debug_block(); + render_debug_input_window(); } // get volume transformation regarding to the "border". Border is related from the size of connectors @@ -1598,9 +1613,6 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c void GLGizmoCut3D::render_connectors() { - if (!m_connectors_editing) - return; - ::glEnable(GL_DEPTH_TEST); if (cut_line_processing() || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) @@ -1631,7 +1643,7 @@ void GLGizmoCut3D::render_connectors() const ModelInstance* mi = mo->instances[inst_id]; const Vec3d& instance_offset = mi->get_offset(); - const float sla_shift = m_c->selection_info()->get_sla_shift(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; @@ -1643,7 +1655,7 @@ void GLGizmoCut3D::render_connectors() for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; - double height = connector.height; + double height = double(connector.height); // recalculate connector position to world position Vec3d pos = connector.pos + instance_offset; if (connector.attribs.type == CutConnectorType::Dowel && @@ -1757,13 +1769,13 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } plater->cut(object_idx, instance_idx, cut_center_offset, rotation, - only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | - only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | - only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | - only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | - only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | - only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | - only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); + only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | + only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | + only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | + only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); } else { // the object is SLA-elevated and the plane is under it. @@ -1907,6 +1919,85 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse return false; } +bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_position) +{ + std::pair pos_and_normal; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal, pos_world)) { + const Vec3d& hit = pos_and_normal.first; + + if (m_connectors_editing) { + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); + + connectors.emplace_back(hit, Transformation(m_rotation_m).get_rotation(), + float(m_connector_size) * 0.5f, float(m_connector_depth_ratio), + float(m_connector_size_tolerance) * 0.01f, float(m_connector_depth_ratio_tolerance) * 0.01f, + CutConnectorAttributes( CutConnectorType(m_connector_type), + CutConnectorStyle(m_connector_style), + CutConnectorShape(m_connector_shape_id))); + unselect_all_connectors(); + m_selected.push_back(true); + assert(m_selected.size() == connectors.size()); + update_model_object(); + m_parent.set_as_dirty(); + } + else { + // Following would inform the clipper about the mouse click, so it can + // toggle the respective contour as disabled. + //m_c->object_clipper()->pass_mouse_click(pos_world); + } + + return true; + } + return false; +} + +bool GLGizmoCut3D::delete_selected_connectors(CutConnectors& connectors) +{ + if (connectors.empty()) + return false; + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete connector"), UndoRedo::SnapshotType::GizmoAction); + + // remove connectors + for (int i = int(connectors.size()) - 1; i >= 0; i--) + if (m_selected[i]) + connectors.erase(connectors.begin() + i); + // remove selections + m_selected.erase(std::remove_if(m_selected.begin(), m_selected.end(), [](const auto& selected) { + return selected; }), m_selected.end()); + + assert(m_selected.size() == connectors.size()); + update_model_object(); + m_parent.set_as_dirty(); + return true; +} + +void GLGizmoCut3D::select_connector(int idx, bool select) +{ + m_selected[idx] = select; + if (select) + ++m_selected_count; + else + --m_selected_count; +} + +bool GLGizmoCut3D::is_selection_changed(bool alt_down, bool control_down) +{ + if (m_hover_id >= m_connectors_group_id) { + if (alt_down) + select_connector(m_hover_id - m_connectors_group_id, false); + else { + if (!control_down) + unselect_all_connectors(); + select_connector(m_hover_id - m_connectors_group_id, true); + } + return true; + } + return false; +} + bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) @@ -1920,76 +2011,33 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (action == SLAGizmoEventType::LeftDown && !shift_down) { // If there is no selection and no hovering, add new point - if (m_hover_id == -1 && !control_down && !alt_down) { - std::pair pos_and_normal; - Vec3d pos_world; - if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal, pos_world)) { - const Vec3d& hit = pos_and_normal.first; - - if (m_connectors_editing) { - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); - - connectors.emplace_back(hit, Transformation(m_rotation_m).get_rotation(), - float(m_connector_size * 0.5), float(m_connector_depth_ratio), - float(0.01f * m_connector_size_tolerance), float(0.01f * m_connector_depth_ratio_tolerance), - CutConnectorAttributes( CutConnectorType(m_connector_type), - CutConnectorStyle(m_connector_style), - CutConnectorShape(m_connector_shape_id))); - std::fill(m_selected.begin(), m_selected.end(), false); - m_selected.push_back(true); - assert(m_selected.size() == connectors.size()); - update_model_object(); - m_parent.set_as_dirty(); - } else { - // Following would inform the clipper about the mouse click, so it can - // toggle the respective contour as disabled. - //m_c->object_clipper()->pass_mouse_click(pos_world); - } - - return true; - } - return false; - } + if (m_hover_id == -1 && !control_down && !alt_down) + return add_connector(connectors, mouse_position); return true; } - else if (action == SLAGizmoEventType::LeftUp && !shift_down && m_connectors_editing) { - if (m_hover_id >= m_connectors_group_id) { - if (alt_down) { - m_selected[m_hover_id - m_connectors_group_id] = false; - --m_selected_count; - } - else { - if (!control_down) { - std::fill(m_selected.begin(), m_selected.end(), false); - m_selected_count = 0; - } - m_selected[m_hover_id - m_connectors_group_id] = true; - ++m_selected_count; - } - return true; - } - } - else if (action == SLAGizmoEventType::RightDown && !shift_down && m_connectors_editing) { + if (!m_connectors_editing) + return false; + + if (action == SLAGizmoEventType::LeftUp && !shift_down) + return is_selection_changed(alt_down, control_down); + + if (action == SLAGizmoEventType::RightDown && !shift_down) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: if (m_hover_id < m_connectors_group_id) return false; + unselect_all_connectors(); + select_connector(m_hover_id - m_connectors_group_id, true); + return delete_selected_connectors(connectors); + } + + if (action == SLAGizmoEventType::Delete) + return delete_selected_connectors(connectors); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete connector"), UndoRedo::SnapshotType::GizmoAction); - - size_t connector_id = m_hover_id - m_connectors_group_id; - connectors.erase(connectors.begin() + connector_id); - m_selected.erase(m_selected.begin() + connector_id); - assert(m_selected.size() == connectors.size()); - update_model_object(); - m_parent.set_as_dirty(); - - return true; - } - else if (action == SLAGizmoEventType::SelectAll) { - std::fill(m_selected.begin(), m_selected.end(), true); + if (action == SLAGizmoEventType::SelectAll) { + select_all_connectors(); return true; } + return false; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 11c7b1abd..ac3113a0a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -155,26 +155,40 @@ public: BoundingBoxf3 transformed_bounding_box(bool revert_move = false) const; protected: - bool on_init() override; - void on_load(cereal::BinaryInputArchive& ar) override; - void on_save(cereal::BinaryOutputArchive& ar) const override; - std::string on_get_name() const override; - void on_set_state() override; + bool on_init() override; + void on_load(cereal::BinaryInputArchive&ar) override; + void on_save(cereal::BinaryOutputArchive&ar) const override; + std::string on_get_name() const override; + void on_set_state() override; CommonGizmosDataID on_get_requirements() const override; - void on_set_hover_id() override; - bool on_is_activable() const override; - Vec3d mouse_position_in_local_plane(Axis axis, const Linef3& mouse_ray) const; - void on_dragging(const UpdateData& data) override; - void on_start_dragging() override; - void on_stop_dragging() override; - void on_render() override; + void on_set_hover_id() override; + bool on_is_activable() const override; + Vec3d mouse_position_in_local_plane(Axis axis, const Linef3&mouse_ray) const; + void dragging_grabber_z(const GLGizmoBase::UpdateData &data); + void dragging_grabber_xy(const GLGizmoBase::UpdateData &data); + void dragging_connector(const GLGizmoBase::UpdateData &data); + void on_dragging(const UpdateData&data) override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_render() override; - void render_debug_block(); + void render_debug_input_window(); void adjust_window_position(float x, float y, float bottom_limit); - void render_connectors_editing(CutConnectors &connectors); - void render_cut_plane_editing(CutConnectors &connectors); + void unselect_all_connectors(); + void select_all_connectors(); + void render_shortcuts(); + void apply_selected_connectors(std::function apply_fn); + void render_connectors_input_window(CutConnectors &connectors); + void render_build_size(); + void reset_cut_plane(); + void set_connectors_editing(bool connectors_editing); + void render_cut_plane_input_window(CutConnectors &connectors); void init_input_window_data(CutConnectors &connectors); void render_input_window_warning() const; + bool add_connector(CutConnectors&connectors, const Vec2d&mouse_position); + bool delete_selected_connectors(CutConnectors&connectors); + void select_connector(int idx, bool select); + bool is_selection_changed(bool alt_down, bool control_down); virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; @@ -213,6 +227,7 @@ private: bool update_bb(); void init_picking_models(); void init_rendering_items(); + void render_clipper_cut(); void reset_connectors(); void update_connector_shape(); void update_model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 024b23819..3946e54e1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -551,7 +551,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) case WXK_BACK: case WXK_DELETE: { - if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete)) + if ((m_current == SlaSupports || m_current == Hollow || m_current == Cut) && gizmo_event(SLAGizmoEventType::Delete)) processed = true; break; From a6f94193d5282a6cd0726e487128ccaab07146b9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 20 Sep 2022 21:25:02 +0200 Subject: [PATCH 71/97] Cut: Fix transformations, make contour not scale with object --- src/libslic3r/ExPolygon.cpp | 7 +++++ src/libslic3r/ExPolygon.hpp | 1 + src/slic3r/GUI/MeshUtils.cpp | 60 ++++++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 7e22127cd..4de88101c 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -19,6 +19,13 @@ void ExPolygon::scale(double factor) hole.scale(factor); } +void ExPolygon::scale(double factor_x, double factor_y) +{ + contour.scale(factor_x, factor_y); + for (Polygon &hole : holes) + hole.scale(factor_x, factor_y); +} + void ExPolygon::translate(const Point &p) { contour.translate(p); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 3e42c8670..d13056f95 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -36,6 +36,7 @@ public: void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); + void scale(double factor_x, double factor_y); void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(const Point &vector); void rotate(double angle); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index e7c1d0fe9..b6f95eead 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -16,6 +16,8 @@ #include +#include + namespace Slic3r { namespace GUI { @@ -163,21 +165,13 @@ void MeshClipper::recalculate_triangles() { m_result = ClipResult(); - -#if ENABLE_WORLD_COORDINATE - const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast(); -#else - const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); -#endif // ENABLE_WORLD_COORDINATE - // Calculate clipping plane normal in mesh coordinates. - const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); - const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); - // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). - const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); + auto plane_mesh = Eigen::Hyperplane(m_plane.get_normal(), -m_plane.distance(Vec3d::Zero())).transform(m_trafo.get_matrix().inverse()); + const Vec3d up = plane_mesh.normal(); + const float height_mesh = -plane_mesh.offset(); // Now do the cutting MeshSlicingParams slicing_params; - slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up_noscale.cast(), Vec3d::UnitZ())); + slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); @@ -188,7 +182,7 @@ void MeshClipper::recalculate_triangles() // Triangulate and rotate the cut into world coords: Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), up_noscale.cast()); + q.setFromTwoVectors(Vec3d::UnitZ(), up); Transform3d tr = Transform3d::Identity(); tr.rotate(q); tr = m_trafo.get_matrix() * tr; @@ -240,7 +234,7 @@ void MeshClipper::recalculate_triangles() // it so it lies on our line. This will be the figure to subtract // from the cut. The coordinates must not overflow after the transform, // make the rectangle a bit smaller. - const coord_t size = (std::numeric_limits::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4; + const coord_t size = (std::numeric_limits::max()/2 - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4; Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})}; ep.front().rotate(angle); ep.front().translate(scale_(-e * a), scale_(-e * b)); @@ -280,10 +274,42 @@ void MeshClipper::recalculate_triangles() isl.model.init_from(std::move(init_data)); } - if (m_contour_width != 0.) { + if (m_contour_width != 0. && ! exp.contour.empty()) { triangles2d.clear(); - ExPolygons expolys_exp = offset_ex(exp, scale_(m_contour_width)); - expolys_exp = diff_ex(expolys_exp, ExPolygons({exp})); + + // The contours must not scale with the object. Check the scale factor + // in the respective directions, create a scaled copy of the ExPolygon + // offset it and then unscale the result again. + + Transform3d t = tr; + t.translation() = Vec3d::Zero(); + double scale_x = (t * Vec3d::UnitX()).norm(); + double scale_y = (t * Vec3d::UnitY()).norm(); + + // To prevent overflow after scaling, downscale the input if needed: + double extra_scale = 1.; + int32_t limit = int32_t(std::min(std::numeric_limits::max() / (2. * scale_x), std::numeric_limits::max() / (2. * scale_y))); + int32_t max_coord = 0; + for (const Point& pt : exp.contour) + max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y()))); + if (max_coord + m_contour_width >= limit) + extra_scale = 0.9 * double(limit) / max_coord; + + ExPolygon exp_copy = exp; + if (extra_scale != 1.) + exp_copy.scale(extra_scale); + exp_copy.scale(scale_x, scale_y); + + ExPolygons expolys_exp = offset_ex(exp_copy, scale_(m_contour_width)); + expolys_exp = diff_ex(expolys_exp, ExPolygons({exp_copy})); + + for (ExPolygon& e : expolys_exp) { + e.scale(1./scale_x, 1./scale_y); + if (extra_scale != 1.) + e.scale(1./extra_scale); + } + + triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.); GLModel::Geometry init_data = GLModel::Geometry(); init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; From e676d40df5c90ea2773f12b6aed312c628612887 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 21 Sep 2022 13:40:24 +0200 Subject: [PATCH 72/97] Cut WIP: Beatifications for input window dialog + Fixed rendering of the connectors, when cut plane is rotated for 270 deg by Y axis --- src/imgui/imconfig.h | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 89 ++++++++++++++-------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/ImGuiWrapper.cpp | 1 + 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index f9fdf575b..678c8fe20 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -172,6 +172,7 @@ namespace ImGui const wchar_t WarningMarkerSmall = 0x2618; const wchar_t ExpandBtn = 0x2619; const wchar_t CollapseBtn = 0x2620; + const wchar_t InfoMarkerSmall = 0x2621; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 2adebbbec..00a11ff37 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -327,7 +327,7 @@ void GLGizmoCut3D::rotate_vec3d_around_plane_center(Vec3d&vec) vec = Transformation( assemble_transform(m_plane_center) * m_rotation_m * assemble_transform(-m_plane_center)).get_matrix() * vec; } -void GLGizmoCut3D::put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset) +void GLGizmoCut3D::put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset) { ModelObject* mo = m_c->selection_info()->model_object(); if (CutConnectors& connectors = mo->cut_connectors; !connectors.empty()) { @@ -369,7 +369,7 @@ void GLGizmoCut3D::update_clipper() m_c->object_clipper()->set_range_and_pos(normal, offset, dist); - put_connetors_on_cut_plane(normal, offset); + put_connectors_on_cut_plane(normal, offset); if (m_raycasters.empty()) on_register_raycasters_for_picking(); @@ -487,7 +487,6 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, double& void GLGizmoCut3D::render_move_center_input(int axis) { - ImGui::AlignTextToFramePadding(); m_imgui->text(m_axis_names[axis]+":"); ImGui::SameLine(); ImGui::PushItemWidth(0.3f*m_control_width); @@ -637,7 +636,7 @@ void GLGizmoCut3D::render_cut_center_grabber() line_shader->set_uniform("emission_factor", 0.1f); line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - const Transform3d trafo = view_matrix * assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(1.0, 1.0, m_grabber_connection_len)); + const Transform3d trafo = view_matrix * scale_transform(Vec3d(1.0, 1.0, m_grabber_connection_len)); line_shader->set_uniform("view_model_matrix", trafo); line_shader->set_uniform("normal_matrix", (Matrix3d)trafo.matrix().block(0, 0, 3, 3).inverse().transpose()); line_shader->set_uniform("width", 0.2f); @@ -651,7 +650,7 @@ void GLGizmoCut3D::render_cut_center_grabber() auto render_rotation_snapping = [shader, camera, this](Axis axis, const ColorRGBA& color) { - Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(m_plane_center) * m_start_dragging_m; + Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_start_dragging_m; if (axis == X) view_model_matrix = view_model_matrix * rotation_transform( 0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ()); @@ -690,7 +689,7 @@ void GLGizmoCut3D::render_cut_center_grabber() if ((!m_dragging && m_hover_id < 0)) render_grabber_connection(color); - render(m_sphere.model, color, view_matrix * assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); + render(m_sphere.model, color, view_matrix * scale_transform(size)); if (!m_dragging && m_hover_id < 0 || m_hover_id == Z) { @@ -914,7 +913,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() return; const Vec3d& instance_offset = mo->instances[inst_id]->get_offset(); - const float sla_shift = m_c->selection_info()->get_sla_shift(); + const double sla_shift = double(m_c->selection_info()->get_sla_shift()); const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; @@ -922,7 +921,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; - double height = connector.height; + float height = connector.height; // recalculate connector position to world position Vec3d pos = connector.pos + instance_offset; if (connector.attribs.type == CutConnectorType::Dowel && @@ -932,11 +931,8 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() } pos[Z] += sla_shift; - m_raycasters[i]->set_transform(assemble_transform( - pos, - Transformation(m_rotation_m).get_rotation(), - Vec3d(connector.radius, connector.radius, height) - )); + const Transform3d scale_trafo = scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); + m_raycasters[i]->set_transform(translation_transform(pos) * m_rotation_m * scale_trafo); } } else { @@ -959,7 +955,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() m_raycasters[3]->set_transform(trafo * assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), scale)); offset = 1.25 * size * Vec3d::UnitZ(); - m_raycasters[4]->set_transform(trafo * assemble_transform(Vec3d::Zero(), Vec3d::Zero(), size * Vec3d::Ones())); + m_raycasters[4]->set_transform(trafo * scale_transform(size)); m_raycasters[5]->set_transform(trafo * assemble_transform(-offset, PI * Vec3d::UnitX(), scale)); m_raycasters[6]->set_transform(trafo * assemble_transform(offset, Vec3d::Zero(), scale)); } @@ -1400,7 +1396,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) m_imgui->disabled_begin(connectors.empty()); ImGui::SameLine(m_label_width); - if (m_imgui->button(" " + _L("Reset") + " ##connectors")) + if (m_imgui->button(wxString(ImGui::RevertButton) + _L("Reset") + " ##connectors")) reset_connectors(); m_imgui->disabled_end(); @@ -1428,6 +1424,8 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) connectors[idx].radius_tolerance = 0.01f * float(m_connector_size_tolerance); }); + ImGui::Separator(); + if (m_imgui->button(_L("Confirm connectors"))) { m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); unselect_all_connectors(); @@ -1475,34 +1473,43 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) if (m_mode == size_t(CutMode::cutPlanar)) { ImGui::AlignTextToFramePadding(); + m_imgui->text(wxString(ImGui::InfoMarkerSmall)); + ImGui::SameLine(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); ImGui::Separator(); render_build_size(); + ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Cut position: ")); + ImGui::SameLine(m_label_width); render_move_center_input(Z); - - if (m_imgui->button("Reset cutting plane")) + ImGui::SameLine(); + if (m_imgui->button(wxString(ImGui::RevertButton) + _L("Reset cutting plane"))) reset_cut_plane(); + if (wxGetApp().plater()->printer_technology() == ptFFF) { + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); + if (m_imgui->button(_L("Add/Edit connectors"))) + set_connectors_editing(true); + m_imgui->disabled_end(); + } + ImGui::Separator(); - auto render_part_action_line = [this, connectors](const wxString& info_label, const wxString& label, const wxString& suffix, bool& keep_part, bool& place_on_cut_part, bool& rotate_part) { + auto render_part_action_line = [this, connectors](const wxString& label, const wxString& suffix, bool& keep_part, bool& place_on_cut_part, bool& rotate_part) { bool keep = true; - - m_imgui->text(info_label); - ImGui::SameLine(m_label_width); + ImGui::AlignTextToFramePadding(); m_imgui->text(label); - ImGui::SameLine(2 * m_label_width); + ImGui::SameLine(m_label_width); m_imgui->disabled_begin(!connectors.empty()); m_imgui->checkbox(_L("Keep") + suffix, connectors.empty() ? keep_part : keep); m_imgui->disabled_end(); - ImGui::SameLine(3 * m_label_width); + ImGui::SameLine(2 * m_label_width); m_imgui->disabled_begin(!keep_part); if (m_imgui->checkbox(_L("Place on cut") + suffix, place_on_cut_part)) @@ -1513,23 +1520,16 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) m_imgui->disabled_end(); }; - ImGui::AlignTextToFramePadding(); - render_part_action_line(_L("After cut") + ": ", _L("Upper part"), "##upper", m_keep_upper, m_place_on_cut_upper, m_rotate_upper); - render_part_action_line("", _L("Lower part"), "##lower", m_keep_lower, m_place_on_cut_lower, m_rotate_lower); - } - - if (wxGetApp().plater()->printer_technology() == ptFFF) { - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); - if (m_imgui->button(_L("Add/Edit connectors"))) - set_connectors_editing(true); - m_imgui->disabled_end(); + m_imgui->text(_L("After cut") + ": "); + render_part_action_line( _L("Upper part"), "##upper", m_keep_upper, m_place_on_cut_upper, m_rotate_upper); + render_part_action_line( _L("Lower part"), "##lower", m_keep_lower, m_place_on_cut_lower, m_rotate_lower); } ImGui::Separator(); m_imgui->disabled_begin(!can_perform_cut()); if(m_imgui->button(_L("Perform cut"))) - perform_cut(m_parent.get_selection());; + perform_cut(m_parent.get_selection()); m_imgui->disabled_end(); } @@ -1557,8 +1557,8 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { - if (wxGetApp().plater()->printer_technology() == ptFFF) - m_imgui->text(m_has_invalid_connector ? wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.") : wxString()); + if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector) + m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.")); if (!m_keep_upper && !m_keep_lower) m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid state. \nNo one part is selected for keep after cut")); } @@ -1655,7 +1655,7 @@ void GLGizmoCut3D::render_connectors() for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; - double height = double(connector.height); + float height = connector.height; // recalculate connector position to world position Vec3d pos = connector.pos + instance_offset; if (connector.attribs.type == CutConnectorType::Dowel && @@ -1694,11 +1694,8 @@ void GLGizmoCut3D::render_connectors() m_shapes[connector.attribs].model.set_color(render_color); - const Transform3d view_model_matrix = camera.get_view_matrix() * assemble_transform( - pos, - Transformation(m_rotation_m).get_rotation(), - Vec3d(connector.radius, connector.radius, height) - ); + const Transform3d scale_trafo = scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * scale_trafo; shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); @@ -1741,6 +1738,12 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d rotation = Transformation(m_rotation_m).get_rotation(); + // FIXME experiments with transformations + const Vec3d recover_rot = Transformation(rotation_transform(rotation)).get_rotation(); + if (recover_rot != rotation) + printf("\n ERROR! wrong recovered rotation"); + /////////////// + const bool has_connectors = !mo->cut_connectors.empty(); { Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); @@ -1761,7 +1764,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); rotate_vec3d_around_plane_center(shifted_center); Vec3d norm = (shifted_center - m_plane_center).normalized(); - connector.pos += norm * (0.5 * connector.height); + connector.pos += norm * 0.5 * double(connector.height); } } mo->apply_cut_connectors(_u8L("Connector")); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ac3113a0a..5bc47f49c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -146,7 +146,7 @@ public: void shift_cut_z(double delta); void rotate_vec3d_around_plane_center(Vec3d&vec); - void put_connetors_on_cut_plane(const Vec3d& cp_normal, double cp_offset); + void put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset); void update_clipper(); void update_clipper_on_render(); void set_connectors_editing() { m_connectors_editing = true; } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e9e5137be..22451c60c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -60,6 +60,7 @@ static const std::map font_icons = { {ImGui::CollapseBtn , "collapse_btn" }, {ImGui::RevertButton , "undo" }, {ImGui::WarningMarkerSmall , "notification_warning" }, + {ImGui::InfoMarkerSmall , "notification_info" }, }; static const std::map font_icons_large = { From 0fcb7243b7da92dee35fd728e10a630b37bcbbb5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 21 Sep 2022 16:29:06 +0200 Subject: [PATCH 73/97] Cut WIP: Upgrade for reset_buttons in inpot_window --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 22 +++++++--------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 00a11ff37..498668896 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -526,29 +526,27 @@ void GLGizmoCut3D::render_connect_mode_radio_button(CutConnectorMode mode) m_connector_mode = mode; } -bool GLGizmoCut3D::render_revert_button(const std::string& label_id) +bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::string& tooltip) const { const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); - ImGui::SameLine(m_label_width); 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.4f, 0.4f, 0.4f, 1.0f }); - std::string label; - label += ImGui::RevertButton; - bool revert = ImGui::Button((label + "##" + label_id).c_str()); + std::string btn_label; + btn_label += ImGui::RevertButton; + const bool revert = ImGui::Button((btn_label +"##" + label_id).c_str()); ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) - m_imgui->tooltip(into_u8(_L("Revert")).c_str(), ImGui::GetFontSize() * 20.0f); + m_imgui->tooltip(tooltip.c_str(), ImGui::GetFontSize() * 20.0f); ImGui::PopStyleVar(); - ImGui::SameLine(); return revert; } @@ -1396,7 +1394,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) m_imgui->disabled_begin(connectors.empty()); ImGui::SameLine(m_label_width); - if (m_imgui->button(wxString(ImGui::RevertButton) + _L("Reset") + " ##connectors")) + if (render_reset_button("connectors", _u8L("Remove connectors"))) reset_connectors(); m_imgui->disabled_end(); @@ -1486,7 +1484,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::SameLine(m_label_width); render_move_center_input(Z); ImGui::SameLine(); - if (m_imgui->button(wxString(ImGui::RevertButton) + _L("Reset cutting plane"))) + if (render_reset_button("cut_plane", _u8L("Reset cutting plane"))) reset_cut_plane(); if (wxGetApp().plater()->printer_technology() == ptFFF) { @@ -1738,12 +1736,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d rotation = Transformation(m_rotation_m).get_rotation(); - // FIXME experiments with transformations - const Vec3d recover_rot = Transformation(rotation_transform(rotation)).get_rotation(); - if (recover_rot != rotation) - printf("\n ERROR! wrong recovered rotation"); - /////////////// - const bool has_connectors = !mo->cut_connectors.empty(); { Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 5bc47f49c..fc2347c64 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -210,7 +210,7 @@ private: bool render_slider_double_input(const std::string& label, double& value_in, int& tolerance_in); void render_move_center_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); - bool render_revert_button(const std::string& label); + bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; bool render_connect_type_radio_button(CutConnectorType type); Transform3d get_volume_transformation(const ModelVolume* volume) const; void render_connectors(); From 66e2c3b30a6208ea373fc6878c13c71f6363c7ad Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 26 Sep 2022 17:23:40 +0200 Subject: [PATCH 74/97] Cut WIP: Send to the cut() whole cut_matrix instead of cut_plane_pos and rotation angles + Fixed units inside input window + NotificationManager: Added info line for loaded object with cut parts + Next Code refactoring --- src/libslic3r/Model.cpp | 501 +++++++++++++------------ src/libslic3r/Model.hpp | 25 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 92 +++-- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/NotificationManager.cpp | 1 + src/slic3r/GUI/Plater.cpp | 4 +- src/slic3r/GUI/Plater.hpp | 2 +- 7 files changed, 325 insertions(+), 302 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 82b5fad57..3d245a7b1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1381,11 +1381,13 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn return connector_mesh; } -void ModelObject::apply_cut_connectors(const std::string& name) +void ModelObject::apply_cut_connectors(const std::string& new_name) { if (cut_connectors.empty()) return; + using namespace Geometry; + size_t connector_id = cut_id.connectors_cnt(); for (const CutConnector& connector : cut_connectors) { TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); @@ -1393,15 +1395,11 @@ void ModelObject::apply_cut_connectors(const std::string& name) ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); // Transform the new modifier to be aligned inside the instance - new_volume->set_transformation(Geometry::assemble_transform( - connector.pos, - connector.rotation, - Vec3d(connector.radius, connector.radius, connector.height), - Vec3d::Ones() - )); + new_volume->set_transformation(assemble_transform(connector.pos) * connector.rotation_m * + scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); new_volume->cut_info = { true, connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; - new_volume->name = name + "-" + std::to_string(++connector_id); + new_volume->name = new_name + "-" + std::to_string(++connector_id); } cut_id.increase_connectors_cnt(cut_connectors.size()); @@ -1426,14 +1424,8 @@ void ModelObject::synchronize_model_after_cut() } } -ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes) +void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes) { - if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) - return {}; - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; - - // initiate/update cut attributes for object if (cut_id.id().invalid()) cut_id.init(); { @@ -1444,25 +1436,241 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const if (cut_obj_cnt > 0) cut_id.increase_check_sum(size_t(cut_obj_cnt)); } +} - auto clone_obj = [this](ModelObject** obj) { - (*obj) = ModelObject::new_clone(*this); - (*obj)->set_model(nullptr); - (*obj)->sla_support_points.clear(); - (*obj)->sla_drain_holes.clear(); - (*obj)->sla_points_status = sla::PointsStatus::NoPoints; - (*obj)->clear_volumes(); - (*obj)->input_file.clear(); - }; +void ModelObject::clone_for_cut(ModelObject** obj) +{ + (*obj) = ModelObject::new_clone(*this); + (*obj)->set_model(nullptr); + (*obj)->sla_support_points.clear(); + (*obj)->sla_drain_holes.clear(); + (*obj)->sla_points_status = sla::PointsStatus::NoPoints; + (*obj)->clear_volumes(); + (*obj)->input_file.clear(); +} + +void ModelVolume::apply_tolerance() +{ + if (!cut_info.is_connector) + return; + + Vec3d sf = get_scaling_factor(); +/* + // correct Z offset in respect to the new size + Vec3d pos = vol->get_offset(); + pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance; + vol->set_offset(pos); +*/ + // make a "hole" wider + sf[X] *= 1. + double(cut_info.radius_tolerance); + sf[Y] *= 1. + double(cut_info.radius_tolerance); + + // make a "hole" dipper + sf[Z] *= 1. + double(cut_info.height_tolerance); + + set_scaling_factor(sf); +} + +void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + std::vector& dowels, Vec3d& local_dowels_displace) +{ + volume->cut_info.discard(); + + const auto volume_matrix = volume->get_matrix(); + + // ! Don't apply instance transformation for the conntectors. + // This transformation is already there + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + ModelVolume* vol = upper->add_volume(*volume); + vol->set_transformation(volume_matrix); + vol->apply_tolerance(); + } + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + ModelVolume* vol = lower->add_volume(*volume); + vol->set_transformation(volume_matrix); + + if (volume->cut_info.connector_type == CutConnectorType::Dowel) + vol->apply_tolerance(); + else + // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug + vol->set_type(ModelVolumeType::MODEL_PART); + } + if (volume->cut_info.connector_type == CutConnectorType::Dowel && + attributes.has(ModelObjectCutAttribute::CreateDowels)) { + ModelObject* dowel{ nullptr }; + // Clone the object to duplicate instances, materials etc. + clone_for_cut(&dowel); + + // add one more solid part same as connector if this connector is a dowel + ModelVolume* vol = dowel->add_volume(*volume); + vol->set_type(ModelVolumeType::MODEL_PART); + + // But discard rotation and Z-offset for this volume + vol->set_rotation(Vec3d::Zero()); + vol->set_offset(Z, 0.0); + + // Compute the displacement (in instance coordinates) to be applied to place the dowels + local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); + + dowels.push_back(dowel); + } +} + +void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +{ + const auto volume_matrix = volume->get_matrix(); + + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); + + // Some logic for the negative volumes/connectors. Add only needed modifiers + auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); + bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) + lower->add_volume(*volume); +} + +static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix) +{ + if (mesh.empty()) + return; + + mesh.transform(cut_matrix); + ModelVolume* vol = object->add_volume(mesh); + + vol->name = src_volume->name; + // Don't copy the config's ID. + vol->config.assign_config(src_volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != src_volume->config.id()); + vol->set_material(src_volume->material_id(), *src_volume->material()); +} + +void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace) +{ + const auto volume_matrix = volume->get_matrix(); + + using namespace Geometry; + + const Transformation cut_transformation = Transformation(cut_matrix); + const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * assemble_transform(-1 * cut_transformation.get_offset()); + + // Transform the mesh by the combined transformation matrix. + // Flip the triangles in case the composite transformation is left handed. + TriangleMesh mesh(volume->mesh()); + mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); + + volume->reset_mesh(); + // Reset volume transformation except for offset + const Vec3d offset = volume->get_offset(); + volume->set_transformation(Geometry::Transformation()); + volume->set_offset(offset); + + // Perform cut + + TriangleMesh upper_mesh, lower_mesh; + { + indexed_triangle_set upper_its, lower_its; + cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper_mesh = TriangleMesh(upper_its); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower_mesh = TriangleMesh(lower_its); + } + + // Add required cut parts to the objects + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + add_cut_volume(upper_mesh, upper, volume, cut_matrix); + + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { + add_cut_volume(lower_mesh, lower, volume, cut_matrix); + + // Compute the displacement (in instance coordinates) to be applied to place the upper parts + // The upper part displacement is set to half of the lower part bounding box + // this is done in hope at least a part of the upper part will always be visible and draggable + local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); + } +} + +static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance) +{ + if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) { + object->center_around_origin(); + object->translate_instances(-object->origin_translation); + object->origin_translation = Vec3d::Zero(); + } + else { + object->invalidate_bounding_box(); + object->center_around_origin(); + } +} + +static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, + bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero()) +{ + using namespace Geometry; + static Vec3d rotate_z180 = deg2rad(180.0) * Vec3d::UnitX(); + + // Reset instance transformation except offset and Z-rotation + + for (size_t i = 0; i < object->instances.size(); ++i) { + auto& obj_instance = object->instances[i]; + const Vec3d offset = obj_instance->get_offset(); + const double rot_z = obj_instance->get_rotation().z(); + + obj_instance->set_transformation(Transformation()); + + const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() : + assemble_transform(Vec3d::Zero(), obj_instance->get_rotation()) * local_displace; + obj_instance->set_offset(offset + displace); + + Vec3d rotation = Vec3d::Zero(); + if (!flip && !place_on_cut) { + if ( i != src_instance_idx) + rotation[Z] = rot_z; + } + else { + Transform3d rotation_matrix = Transform3d::Identity(); + if (flip) + rotation_matrix = rotation_transform(rotate_z180); + + if (place_on_cut) + rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse(); + + if (i != src_instance_idx) + rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix; + + rotation = Transformation(rotation_matrix).get_rotation(); + } + + obj_instance->set_rotation(rotation); + } +} + +ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes) +{ + if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) + return {}; + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + + // apply cut attributes for object + apply_cut_attributes(attributes); // Clone the object to duplicate instances, materials etc. ModelObject* upper{ nullptr }; if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - clone_obj(&upper); + clone_for_cut(&upper); ModelObject* lower{ nullptr }; if (attributes.has(ModelObjectCutAttribute::KeepLower)) - clone_obj(&lower); + clone_for_cut(&lower); std::vector dowels; @@ -1476,260 +1684,67 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const // const auto instance_matrix = instances[instance]->get_matrix(true); const auto instance_matrix = assemble_transform( Vec3d::Zero(), // don't apply offset - instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), + instances[instance]->get_rotation(), instances[instance]->get_scaling_factor(), instances[instance]->get_mirror() ); - const auto cut_matrix = rotation_transform(cut_rotation).inverse() * assemble_transform(-cut_center); - const auto invert_cut_matrix = assemble_transform(cut_center, cut_rotation); + const Transformation cut_transformation = Transformation(cut_matrix); + const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * assemble_transform(-1. * cut_transformation.get_offset()); // Displacement (in instance coordinates) to be applied to place the upper parts Vec3d local_displace = Vec3d::Zero(); Vec3d local_dowels_displace = Vec3d::Zero(); - Vec3d rotate_z180 = deg2rad(180.0) * Vec3d::UnitX(); - - auto apply_tolerance = [](ModelVolume * vol) - { - Vec3d sf = vol->get_scaling_factor(); -/* - // correct Z offset in respect to the new size - Vec3d pos = vol->get_offset(); - pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance; - vol->set_offset(pos); -*/ - // make a "hole" wider - sf[X] *= (1 + vol->cut_info.radius_tolerance); - sf[Y] *= (1 + vol->cut_info.radius_tolerance); - // make a "hole" dipper - sf[Z] *= (1 + vol->cut_info.height_tolerance); - vol->set_scaling_factor(sf); - }; - for (ModelVolume* volume : volumes) { - const auto volume_matrix = volume->get_matrix(); - volume->supported_facets.reset(); volume->seam_facets.reset(); volume->mmu_segmentation_facets.reset(); if (!volume->is_model_part()) { - if (volume->cut_info.is_connector) { - volume->cut_info.discard(); - - // ! Don't apply instance transformation for the conntectors. - // This transformation is already there - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - ModelVolume* vol = upper->add_volume(*volume); - vol->set_transformation(volume_matrix); - apply_tolerance(vol); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower)) { - ModelVolume* vol = lower->add_volume(*volume); - vol->set_transformation(volume_matrix); - - if (volume->cut_info.connector_type == CutConnectorType::Dowel) - apply_tolerance(vol); - else - // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug - vol->set_type(ModelVolumeType::MODEL_PART); - } - if (volume->cut_info.connector_type == CutConnectorType::Dowel && - attributes.has(ModelObjectCutAttribute::CreateDowels)) { - ModelObject* dowel{ nullptr }; - // Clone the object to duplicate instances, materials etc. - clone_obj(&dowel); - - // add one more solid part same as connector if this connector is a dowel - ModelVolume* vol = dowel->add_volume(*volume); - vol->set_type(ModelVolumeType::MODEL_PART); - - // But discard rotation and Z-offset for this volume - vol->set_rotation(Vec3d::Zero()); - vol->set_offset(Z, 0.0); - - // Compute the displacement (in instance coordinates) to be applied to place the dowels - local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); - - dowels.push_back(dowel); - } - } - else { - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - - // Some logic for the negative volumes/connectors. Add only needed modifiers - auto bb = volume->mesh().transformed_bounding_box(cut_matrix * volume->get_matrix()); - bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) - upper->add_volume(*volume); - if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) - lower->add_volume(*volume); - } - } - else if (!volume->mesh().empty()) { - // Transform the mesh by the combined transformation matrix. - // Flip the triangles in case the composite transformation is left handed. - TriangleMesh mesh(volume->mesh()); - mesh.transform(cut_matrix * instance_matrix* volume_matrix, true); - - volume->reset_mesh(); - // Reset volume transformation except for offset - const Vec3d offset = volume->get_offset(); - volume->set_transformation(Geometry::Transformation()); - volume->set_offset(offset); - - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - { - indexed_triangle_set upper_its, lower_its; - cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper_mesh = TriangleMesh(upper_its); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower_mesh = TriangleMesh(lower_its); - } - - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { - upper_mesh.transform(invert_cut_matrix); - - ModelVolume* vol = upper->add_volume(upper_mesh); - vol->name = volume->name; - // Don't copy the config's ID. - vol->config.assign_config(volume->config); - assert(vol->config.id().valid()); - assert(vol->config.id() != volume->config.id()); - vol->set_material(volume->material_id(), *volume->material()); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { - lower_mesh.transform(invert_cut_matrix); - - ModelVolume* vol = lower->add_volume(lower_mesh); - vol->name = volume->name; - // Don't copy the config's ID. - vol->config.assign_config(volume->config); - assert(vol->config.id().valid()); - assert(vol->config.id() != volume->config.id()); - vol->set_material(volume->material_id(), *volume->material()); - - // Compute the displacement (in instance coordinates) to be applied to place the upper parts - // The upper part displacement is set to half of the lower part bounding box - // this is done in hope at least a part of the upper part will always be visible and draggable - local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); - } + if (volume->cut_info.is_connector) + process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace); + else + process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); } + else if (!volume->mesh().empty()) + process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace); } + // Post-process cut parts + ModelObjectPtrs res; if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { - if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - upper->center_around_origin(); - upper->translate_instances(-upper->origin_translation); - upper->origin_translation = Vec3d::Zero(); - } - else { - upper->invalidate_bounding_box(); - upper->center_around_origin(); - } - // Reset instance transformation except offset and Z-rotation - for (size_t i = 0; i < instances.size(); ++i) { - auto& obj_instance = upper->instances[i]; - const Vec3d offset = obj_instance->get_offset(); - const double rot_z = obj_instance->get_rotation().z(); - const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), obj_instance->get_rotation()) * local_displace; + invalidate_translations(upper, instances[instance]); - obj_instance->set_transformation(Geometry::Transformation()); - obj_instance->set_offset(offset + displace); - - Vec3d rotation = Vec3d::Zero(); - if (attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper)) { - Transform3d trafo = rotation_transform(cut_rotation).inverse(); - if (i != instance) - trafo = rotation_transform(rot_z * Vec3d::UnitZ()) * trafo; - rotation = Transformation(trafo).get_rotation(); - } - else if (attributes.has(ModelObjectCutAttribute::FlipUpper)) { - rotation = rotate_z180; - if (i != instance) - rotation[Z] = rot_z; - } - else if (i != instance) - rotation[Z] = rot_z/* * Vec3d::UnitZ()*/; - obj_instance->set_rotation(rotation); - } + reset_instance_transformation(upper, instance, cut_matrix, + attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), + attributes.has(ModelObjectCutAttribute::FlipUpper), + local_displace); res.push_back(upper); } + if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { - if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - lower->center_around_origin(); - lower->translate_instances(-lower->origin_translation); - lower->origin_translation = Vec3d::Zero(); - } - else { - lower->invalidate_bounding_box(); - lower->center_around_origin(); - } - // Reset instance transformation except offset and Z-rotation - for (size_t i = 0; i < instances.size(); ++i) { - auto& obj_instance = lower->instances[i]; - const Vec3d offset = obj_instance->get_offset(); - const double rot_z = obj_instance->get_rotation().z(); - obj_instance->set_transformation(Geometry::Transformation()); - obj_instance->set_offset(offset); + invalidate_translations(lower, instances[instance]); - Vec3d rotation = Vec3d::Zero(); - if (attributes.has(ModelObjectCutAttribute::PlaceOnCutLower)) { - Transform3d trafo = rotation_transform(rotate_z180) * rotation_transform(cut_rotation).inverse(); - if (i != instance) - trafo = rotation_transform(rot_z * Vec3d::UnitZ()) * trafo; - rotation = Transformation(trafo).get_rotation(); - } - else if (attributes.has(ModelObjectCutAttribute::FlipLower)) { - rotation = rotate_z180; - if (i != instance) - rotation[Z] = rot_z; - } - else if (i != instance) - rotation[Z] = rot_z; - obj_instance->set_rotation(rotation); - } + reset_instance_transformation(lower, instance, cut_matrix, + attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), + attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower)); res.push_back(lower); } if (attributes.has(ModelObjectCutAttribute::CreateDowels) && dowels.size() > 0) { for (auto dowel : dowels) { - if (!dowel->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - dowel->center_around_origin(); - dowel->translate_instances(-dowel->origin_translation); - dowel->origin_translation = Vec3d::Zero(); - } - else { - dowel->invalidate_bounding_box(); - dowel->center_around_origin(); - } + invalidate_translations(dowel, instances[instance]); + dowel->name += "-Dowel-" + dowel->volumes[0]->name; - // Reset instance transformation except offset and Z-rotation - for (size_t i = 0; i < instances.size(); ++i) { - auto& obj_instance = dowel->instances[i]; - const Vec3d offset = obj_instance->get_offset(); - Vec3d rotation = Vec3d::Zero(); - if (i != instance) - rotation[Z] = obj_instance->get_rotation().z(); - - const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), rotation) * local_dowels_displace; - - obj_instance->set_transformation(Geometry::Transformation()); - obj_instance->set_offset(offset + displace); - obj_instance->set_rotation(rotation); - } + reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace); local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0)); res.push_back(dowel); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 21b052337..6981946fb 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -271,7 +271,7 @@ struct CutConnectorAttributes struct CutConnector { Vec3d pos; - Vec3d rotation; + Transform3d rotation_m; float radius; float height; float radius_tolerance;// [0.f : 1.f] @@ -279,22 +279,22 @@ struct CutConnector CutConnectorAttributes attribs; CutConnector() - : pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) + : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {} - CutConnector(Vec3d p, Vec3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes) - : pos(p), rotation(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes) + CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes) + : pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes) {} CutConnector(const CutConnector& rhs) : - CutConnector(rhs.pos, rhs.rotation, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {} + CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {} bool operator==(const CutConnector& other) const; bool operator!=(const CutConnector& other) const { return !(other == (*this)); } template inline void serialize(Archive& ar) { - ar(pos, rotation, radius, height, radius_tolerance, height_tolerance, attribs); + ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs); } }; @@ -445,8 +445,16 @@ public: // invalidate cut state for this and related objects from the whole model void invalidate_cut(); void synchronize_model_after_cut(); - ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); - void split(ModelObjectPtrs* new_objects); + void apply_cut_attributes(ModelObjectCutAttributes attributes); + void clone_for_cut(ModelObject **obj); + void process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + std::vector& dowels, Vec3d& local_dowels_displace); + void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); + void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace); + ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes); + void split(ModelObjectPtrs*new_objects); void merge(); // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. @@ -760,6 +768,7 @@ public: bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } t_model_material_id material_id() const { return m_material_id; } + void apply_tolerance(); void set_material_id(t_model_material_id material_id); ModelMaterial* material() const; void set_material(t_model_material_id material_id, const ModelMaterial &material); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 498668896..1260ee3bb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -800,10 +800,9 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) { ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected, // m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, - m_ar_plane_center, m_ar_rotations); + m_ar_plane_center, m_rotation_m); set_center_pos(m_ar_plane_center, true); - m_rotation_m = rotation_transform(m_ar_rotations); force_update_clipper_on_render = true; @@ -814,7 +813,7 @@ void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const { ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected, // m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id, - m_ar_plane_center, m_ar_rotations); + m_ar_plane_center, m_start_dragging_m); } std::string GLGizmoCut3D::on_get_name() const @@ -829,7 +828,7 @@ void GLGizmoCut3D::on_set_state() // initiate archived values m_ar_plane_center = m_plane_center; - m_ar_rotations = Transformation(m_rotation_m).get_rotation(); + m_start_dragging_m = m_rotation_m; m_parent.request_extra_frame(); } @@ -1119,8 +1118,6 @@ void GLGizmoCut3D::on_stop_dragging() m_angle_arc.reset(); m_angle = 0.0; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); - m_ar_rotations = Transformation(m_rotation_m).get_rotation(); - m_start_dragging_m = m_rotation_m; } else if (m_hover_id == Z) { @@ -1436,7 +1433,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) void GLGizmoCut3D::render_build_size() { double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; - std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm"); + wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm")); const BoundingBoxf3 tbb = transformed_bounding_box(); Vec3d tbb_sz = tbb.size(); @@ -1710,6 +1707,31 @@ bool GLGizmoCut3D::can_perform_cut() const return tbb.contains(m_plane_center); } +void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, const bool has_connectors, bool &create_dowels_as_separate_object) +{ + if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { + m_selected.clear(); + + for (CutConnector&connector : mo->cut_connectors) { + connector.rotation_m = m_rotation_m; + + if (connector.attribs.type == CutConnectorType::Dowel) { + if (connector.attribs.style == CutConnectorStyle::Prizm) + connector.height *= 2; + create_dowels_as_separate_object = true; + } + else { + // culculate shift of the connector center regarding to the position on the cut plane + Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); + rotate_vec3d_around_plane_center(shifted_center); + Vec3d norm = (shifted_center - m_plane_center).normalized(); + connector.pos += norm * 0.5 * double(connector.height); + } + } + mo->apply_cut_connectors(_u8L("Connector")); + } +} + void GLGizmoCut3D::perform_cut(const Selection& selection) { const int instance_idx = selection.get_instance_idx(); @@ -1717,53 +1739,29 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - // m_cut_z is the distance from the bed. Subtract possible SLA elevation. - const GLVolume* first_glvolume = selection.get_first_volume(); - const double object_cut_z = m_plane_center.z() - first_glvolume->get_sla_shift_z(); - - const Vec3d& instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset(); - - Vec3d cut_center_offset = m_plane_center - instance_offset; - cut_center_offset[Z] -= first_glvolume->get_sla_shift_z(); - Plater* plater = wxGetApp().plater(); + ModelObject* mo = plater->model().objects[object_idx]; + if (!mo) + return; + + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. + const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z(); + const double object_cut_z = m_plane_center.z() - sla_shift_z; + + const Vec3d instance_offset = mo->instances[instance_idx]->get_offset(); + Vec3d cut_center_offset = m_plane_center - instance_offset; + cut_center_offset[Z] -= sla_shift_z; - bool create_dowels_as_separate_object = false; if (0.0 < object_cut_z && can_perform_cut()) { - ModelObject* mo = plater->model().objects[object_idx]; - if(!mo) - return; - - Vec3d rotation = Transformation(m_rotation_m).get_rotation(); - + bool create_dowels_as_separate_object = false; const bool has_connectors = !mo->cut_connectors.empty(); { - Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane")); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); // update connectors pos as offset of its center before cut performing - if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { - m_selected.clear(); - - for (CutConnector& connector : mo->cut_connectors) { - connector.rotation = rotation; - - if (connector.attribs.type == CutConnectorType::Dowel) { - if (connector.attribs.style == CutConnectorStyle::Prizm) - connector.height *= 2; - create_dowels_as_separate_object = true; - } - else { - // culculate shift of the connector center regarding to the position on the cut plane - Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); - rotate_vec3d_around_plane_center(shifted_center); - Vec3d norm = (shifted_center - m_plane_center).normalized(); - connector.pos += norm * 0.5 * double(connector.height); - } - } - mo->apply_cut_connectors(_u8L("Connector")); - } + apply_connectors_in_model(mo, has_connectors, create_dowels_as_separate_object); } - plater->cut(object_idx, instance_idx, cut_center_offset, rotation, + plater->cut(object_idx, instance_idx, assemble_transform(cut_center_offset) * m_rotation_m, only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) | @@ -1925,7 +1923,7 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); - connectors.emplace_back(hit, Transformation(m_rotation_m).get_rotation(), + connectors.emplace_back(hit, m_rotation_m, float(m_connector_size) * 0.5f, float(m_connector_depth_ratio), float(m_connector_size_tolerance) * 0.01f, float(m_connector_depth_ratio_tolerance) * 0.01f, CutConnectorAttributes( CutConnectorType(m_connector_type), diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index fc2347c64..2df337b04 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -25,7 +25,6 @@ class GLGizmoCut3D : public GLGizmoBase // archived values Vec3d m_ar_plane_center { Vec3d::Zero() }; - Vec3d m_ar_rotations { Vec3d::Zero() }; Vec3d m_plane_center{ Vec3d::Zero() }; // data to check position of the cut palne center on gizmo activation @@ -216,6 +215,7 @@ private: void render_connectors(); bool can_perform_cut() const; + void apply_connectors_in_model(ModelObject* mo, const bool has_connectors, bool &create_dowels_as_separate_object); bool cut_line_processing() const; void discard_cut_line_processing(); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 2e74270da..16433bc79 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1228,6 +1228,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break; case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break; + case InfoItemType::Cut: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break; default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break; } } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7f351e0b9..4bf441aa1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5967,7 +5967,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCut selection.add_object((unsigned int)(last_id - i), i == 0); } -void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes) +void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes) { wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); auto* object = p->model.objects[obj_idx]; @@ -5977,7 +5977,7 @@ void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& this->suppress_snapshots(); wxBusyCursor wait; - const auto new_objects = object->cut(instance_idx, cut_center, cut_rotation, attributes); + const auto new_objects = object->cut(instance_idx, cut_matrix, attributes); model().delete_object(obj_idx); sidebar().obj_list()->delete_object_from_list(obj_idx); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 7e35e935f..9ee1af272 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -256,7 +256,7 @@ public: void toggle_layers_editing(bool enable); void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes); - void cut(size_t obj_idx, size_t instance_idx, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes); + void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes); void export_gcode(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); From e689be65dbb477e4bb97066df03e943bef357a96 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 27 Sep 2022 09:10:09 +0200 Subject: [PATCH 75/97] Code cleaning --- src/PrusaSlicer.cpp | 4 +- src/libslic3r/Model.cpp | 189 +++----------------------------------- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 26 +----- src/slic3r/GUI/Plater.hpp | 1 - 5 files changed, 19 insertions(+), 203 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index ecb157218..500028525 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -426,7 +426,9 @@ int CLI::run(int argc, char **argv) o->cut(Z, m_config.opt_float("cut"), &out); } #else - model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); +// model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); + model.objects.front()->cut(0, Geometry::assemble_transform(m_config.opt_float("cut")* Vec3d::UnitZ()), + ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); #endif model.delete_object(size_t(0)); } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3d245a7b1..d4c81299f 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1191,166 +1191,6 @@ size_t ModelObject::parts_count() const return num; } -ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes) -{ - if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) - return {}; - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; - - // Clone the object to duplicate instances, materials etc. - ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; - ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; - - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - upper->set_model(nullptr); - upper->sla_support_points.clear(); - upper->sla_drain_holes.clear(); - upper->sla_points_status = sla::PointsStatus::NoPoints; - upper->clear_volumes(); - upper->input_file.clear(); - } - - if (attributes.has(ModelObjectCutAttribute::KeepLower)) { - lower->set_model(nullptr); - lower->sla_support_points.clear(); - lower->sla_drain_holes.clear(); - lower->sla_points_status = sla::PointsStatus::NoPoints; - lower->clear_volumes(); - lower->input_file.clear(); - } - - // Because transformations are going to be applied to meshes directly, - // we reset transformation of all instances and volumes, - // except for translation and Z-rotation on instances, which are preserved - // in the transformation matrix and not applied to the mesh transform. - - // const auto instance_matrix = instances[instance]->get_matrix(true); - const auto instance_matrix = Geometry::assemble_transform( - Vec3d::Zero(), // don't apply offset - instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 0.0)), // don't apply Z-rotation - instances[instance]->get_scaling_factor(), - instances[instance]->get_mirror() - ); - - z -= instances[instance]->get_offset().z(); - - // Displacement (in instance coordinates) to be applied to place the upper parts - Vec3d local_displace = Vec3d::Zero(); - - for (ModelVolume *volume : volumes) { - const auto volume_matrix = volume->get_matrix(); - - volume->supported_facets.reset(); - volume->seam_facets.reset(); - volume->mmu_segmentation_facets.reset(); - - if (! volume->is_model_part()) { - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - - volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper->add_volume(*volume); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower->add_volume(*volume); - } - else if (! volume->mesh().empty()) { - // Transform the mesh by the combined transformation matrix. - // Flip the triangles in case the composite transformation is left handed. - TriangleMesh mesh(volume->mesh()); - mesh.transform(instance_matrix * volume_matrix, true); - volume->reset_mesh(); - // Reset volume transformation except for offset - const Vec3d offset = volume->get_offset(); - volume->set_transformation(Geometry::Transformation()); - volume->set_offset(offset); - - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - { - indexed_triangle_set upper_its, lower_its; - cut_mesh(mesh.its, float(z), &upper_its, &lower_its); - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper_mesh = TriangleMesh(upper_its); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower_mesh = TriangleMesh(lower_its); - } - - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && ! upper_mesh.empty()) { - ModelVolume* vol = upper->add_volume(upper_mesh); - vol->name = volume->name; - // Don't copy the config's ID. - vol->config.assign_config(volume->config); - assert(vol->config.id().valid()); - assert(vol->config.id() != volume->config.id()); - vol->set_material(volume->material_id(), *volume->material()); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && ! lower_mesh.empty()) { - ModelVolume* vol = lower->add_volume(lower_mesh); - vol->name = volume->name; - // Don't copy the config's ID. - vol->config.assign_config(volume->config); - assert(vol->config.id().valid()); - assert(vol->config.id() != volume->config.id()); - vol->set_material(volume->material_id(), *volume->material()); - - // Compute the displacement (in instance coordinates) to be applied to place the upper parts - // The upper part displacement is set to half of the lower part bounding box - // this is done in hope at least a part of the upper part will always be visible and draggable - local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); - } - } - } - - ModelObjectPtrs res; - - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { - if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - upper->center_around_origin(); - upper->translate_instances(-upper->origin_translation); - upper->origin_translation = Vec3d::Zero(); - } - - // Reset instance transformation except offset and Z-rotation - for (size_t i = 0; i < instances.size(); ++i) { - auto &instance = upper->instances[i]; - const Vec3d offset = instance->get_offset(); - const double rot_z = instance->get_rotation().z(); - const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), instance->get_rotation()) * local_displace; - - instance->set_transformation(Geometry::Transformation()); - instance->set_offset(offset + displace); - instance->set_rotation(Vec3d(0.0, 0.0, rot_z)); - } - - res.push_back(upper); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { - if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { - lower->center_around_origin(); - lower->translate_instances(-lower->origin_translation); - lower->origin_translation = Vec3d::Zero(); - } - - // Reset instance transformation except offset and Z-rotation - for (auto *instance : lower->instances) { - const Vec3d offset = instance->get_offset(); - const double rot_z = instance->get_rotation().z(); - instance->set_transformation(Geometry::Transformation()); - instance->set_offset(offset); - instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z)); - } - - res.push_back(lower); - } - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; - - return res; -} - indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) { indexed_triangle_set connector_mesh; @@ -1449,6 +1289,13 @@ void ModelObject::clone_for_cut(ModelObject** obj) (*obj)->input_file.clear(); } +void ModelVolume::reset_extra_facets() +{ + this->supported_facets.reset(); + this->seam_facets.reset(); + this->mmu_segmentation_facets.reset(); +} + void ModelVolume::apply_tolerance() { if (!cut_info.is_connector) @@ -1615,7 +1462,6 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero()) { using namespace Geometry; - static Vec3d rotate_z180 = deg2rad(180.0) * Vec3d::UnitX(); // Reset instance transformation except offset and Z-rotation @@ -1638,7 +1484,7 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan else { Transform3d rotation_matrix = Transform3d::Identity(); if (flip) - rotation_matrix = rotation_transform(rotate_z180); + rotation_matrix = rotation_transform(PI * Vec3d::UnitX()); if (place_on_cut) rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse(); @@ -1697,9 +1543,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, Vec3d local_dowels_displace = Vec3d::Zero(); for (ModelVolume* volume : volumes) { - volume->supported_facets.reset(); - volume->seam_facets.reset(); - volume->mmu_segmentation_facets.reset(); + volume->reset_extra_facets(); if (!volume->is_model_part()) { if (volume->cut_info.is_connector) @@ -1715,38 +1559,33 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectPtrs res; - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { - + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) { invalidate_translations(upper, instances[instance]); reset_instance_transformation(upper, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), attributes.has(ModelObjectCutAttribute::FlipUpper), local_displace); - res.push_back(upper); } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { - + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) { invalidate_translations(lower, instances[instance]); reset_instance_transformation(lower, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower)); - res.push_back(lower); } - if (attributes.has(ModelObjectCutAttribute::CreateDowels) && dowels.size() > 0) { + if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { for (auto dowel : dowels) { invalidate_translations(dowel, instances[instance]); - dowel->name += "-Dowel-" + dowel->volumes[0]->name; - reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace); - local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0)); + local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0)); + dowel->name += "-Dowel-" + dowel->volumes[0]->name; res.push_back(dowel); } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 6981946fb..16142ec05 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -439,7 +439,6 @@ public: size_t materials_count() const; size_t facets_count() const; size_t parts_count() const; - ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(const std::string& name); // invalidate cut state for this and related objects from the whole model @@ -768,6 +767,7 @@ public: bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } t_model_material_id material_id() const { return m_material_id; } + void reset_extra_facets(); void apply_tolerance(); void set_material_id(t_model_material_id material_id); ModelMaterial* material() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4bf441aa1..d09de9cf3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5943,31 +5943,7 @@ void Plater::toggle_layers_editing(bool enable) canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting")); } -void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes) -{ - wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); - auto *object = p->model.objects[obj_idx]; - - wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - - if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) - return; - - Plater::TakeSnapshot snapshot(this, _L("Cut by Plane")); - - wxBusyCursor wait; - const auto new_objects = object->cut(instance_idx, z, attributes); - - remove(obj_idx); - p->load_model_objects(new_objects); - - Selection& selection = p->get_selection(); - size_t last_id = p->model.objects.size() - 1; - for (size_t i = 0; i < new_objects.size(); ++i) - selection.add_object((unsigned int)(last_id - i), i == 0); -} - -void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes) +void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes) { wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); auto* object = p->model.objects[obj_idx]; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9ee1af272..1d7576266 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -255,7 +255,6 @@ public: void convert_unit(ConversionType conv_type); void toggle_layers_editing(bool enable); - void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes); void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes); void export_gcode(bool prefer_removable); From 0201a5055aaa1a52e95a21c852b71200f5fc9145 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 27 Sep 2022 13:54:44 +0200 Subject: [PATCH 76/97] =?UTF-8?q?Cut=20WIP:=20*=20Suppress=20to=20split=20?= =?UTF-8?q?cut=20objects=20*=20ObjectList:=20=20=20*=20Use=20another=20ico?= =?UTF-8?q?ns=20to=20mark=20the=20cut=20objects=20and=20connectors=C2=A0?= =?UTF-8?q?=20=20=20*=20For=20the=20cut=20object=20show=20parts,=20which?= =?UTF-8?q?=20are=20not=20connectors=20*=20Set=20different=20colors=20for?= =?UTF-8?q?=20the=20Plugs=20and=20Dowels=20*=20CutGizmo:=20=20=20*=20Inval?= =?UTF-8?q?idate=20CutGizmo=20after=20changes=20in=20ObjectList=20or=20per?= =?UTF-8?q?form=20a=20cut=20=20=20*=20CupPlane=20in=20Connectors=20mode:?= =?UTF-8?q?=20Unselect=20selection,=20when=20click=20on=20empty=20space=20?= =?UTF-8?q?=20=20*=20Connectors=20mode:=20Fixed=20performance=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/icons/cut_connectors.svg | 26 +++++++++++++++ src/libslic3r/Model.cpp | 26 +++++++++++---- src/libslic3r/Model.hpp | 19 +++++++++-- src/slic3r/GUI/GUI_ObjectList.cpp | 44 +++++++++++++------------- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 40 +++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + src/slic3r/GUI/NotificationManager.cpp | 2 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 4 +-- src/slic3r/GUI/ObjectDataViewModel.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 4 +++ 10 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 resources/icons/cut_connectors.svg diff --git a/resources/icons/cut_connectors.svg b/resources/icons/cut_connectors.svg new file mode 100644 index 000000000..8cd03aa06 --- /dev/null +++ b/resources/icons/cut_connectors.svg @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d4c81299f..79c54748a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1191,11 +1191,21 @@ size_t ModelObject::parts_count() const return num; } +bool ModelObject::has_connectors() const +{ + assert(is_cut()); + for (const ModelVolume* v : this->volumes) + if (v->cut_info.is_connector) + return true; + + return false; +} + indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) { indexed_triangle_set connector_mesh; - int sectorCount; + int sectorCount {1}; switch (CutConnectorShape(connector_attributes.shape)) { case CutConnectorShape::Triangle: sectorCount = 3; @@ -1238,7 +1248,7 @@ void ModelObject::apply_cut_connectors(const std::string& new_name) new_volume->set_transformation(assemble_transform(connector.pos) * connector.rotation_m * scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); - new_volume->cut_info = { true, connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; + new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; new_volume->name = new_name + "-" + std::to_string(++connector_id); } cut_id.increase_connectors_cnt(cut_connectors.size()); @@ -1298,7 +1308,8 @@ void ModelVolume::reset_extra_facets() void ModelVolume::apply_tolerance() { - if (!cut_info.is_connector) + assert(cut_info.is_connector); + if (cut_info.is_processed) return; Vec3d sf = get_scaling_factor(); @@ -1321,7 +1332,8 @@ void ModelVolume::apply_tolerance() void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, std::vector& dowels, Vec3d& local_dowels_displace) { - volume->cut_info.discard(); + assert(volume->cut_info.is_connector); + volume->cut_info.set_processed(); const auto volume_matrix = volume->get_matrix(); @@ -1546,10 +1558,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, volume->reset_extra_facets(); if (!volume->is_model_part()) { - if (volume->cut_info.is_connector) - process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace); - else + if (volume->cut_info.is_processed) process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); + else + process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace); } else if (!volume->mesh().empty()) process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 16142ec05..cc2c612fe 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -477,6 +477,7 @@ public: int get_repaired_errors_count(const int vol_idx = -1) const; bool is_cut() const { return cut_id.id().valid(); } + bool has_connectors() const; private: friend class Model; @@ -723,14 +724,26 @@ public: struct CutInfo { bool is_connector{ false }; + bool is_processed{ true }; CutConnectorType connector_type{ CutConnectorType::Plug }; - float radius_tolerance;// [0.f : 1.f] - float height_tolerance;// [0.f : 1.f] + float radius_tolerance{ 0.f };// [0.f : 1.f] + float height_tolerance{ 0.f };// [0.f : 1.f] - void discard() { is_connector = false; } + CutInfo() = default; + CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance) : + is_connector(true), + is_processed(false), + connector_type(type), + radius_tolerance(rad_tolerance), + height_tolerance(h_tolerance) + {} + + void set_processed() { is_processed = true; } }; CutInfo cut_info; + bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } + // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } #if ENABLE_RAYCAST_PICKING diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6227c5130..05eb02778 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1881,13 +1881,8 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) mv->seam_facets.reset(); break; - case InfoItemType::Cut: - if (0) { // #ysFIXME_Cut - cnv->get_gizmos_manager().reset_all_states(); - Plater::TakeSnapshot(plater, _L("Remove cut connectors")); - (*m_objects)[obj_idx]->cut_connectors.clear(); - } else - Slic3r::GUI::show_error(nullptr, _L("Connectors cannot be deleted from cut object.")); + case InfoItemType::CutConnectors: + show_error(nullptr, _L("Connectors cannot be deleted from cut object.")); break; case InfoItemType::MmuSegmentation: @@ -2422,9 +2417,12 @@ bool ObjectList::is_splittable(bool to_objects) auto obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return false; - if ((*m_objects)[obj_idx]->volumes.size() > 1) + const ModelObject* object = (*m_objects)[obj_idx]; + if (object->is_cut()) + return false; + if (object->volumes.size() > 1) return true; - return (*m_objects)[obj_idx]->volumes[0]->is_splittable(); + return object->volumes[0]->is_splittable(); } return false; } @@ -2595,19 +2593,13 @@ void ObjectList::part_selection_changed() } case InfoItemType::CustomSupports: case InfoItemType::CustomSeam: -// case InfoItemType::Cut: case InfoItemType::MmuSegmentation: { GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports : info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam : - info_type == InfoItemType::Cut ? GLGizmosManager::EType::Cut : GLGizmosManager::EType::MmuSegmentation; if (gizmos_mgr.get_current_type() != gizmo_type) gizmos_mgr.open_gizmo(gizmo_type); - if (info_type == InfoItemType::Cut) { - GLGizmoCut3D* cut = dynamic_cast(gizmos_mgr.get_current()); - cut->set_connectors_editing(); - } break; } case InfoItemType::Sinking: { break; } @@ -2762,7 +2754,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio for (InfoItemType type : {InfoItemType::CustomSupports, InfoItemType::CustomSeam, - InfoItemType::Cut, + InfoItemType::CutConnectors, InfoItemType::MmuSegmentation, InfoItemType::Sinking, InfoItemType::VariableLayerHeight}) { @@ -2783,11 +2775,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio }); break; - case InfoItemType::Cut : - if (0) // #ysFIXME_Cut - should_show = !model_object->cut_connectors.empty(); - else - should_show = model_object->is_cut() && model_object->volumes.size() > 1; + case InfoItemType::CutConnectors: + should_show = model_object->is_cut() && model_object->has_connectors() && model_object->volumes.size() > 1; break; case InfoItemType::VariableLayerHeight : should_show = printer_technology() == ptFFF @@ -2841,9 +2830,20 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) update_info_items(obj_idx, nullptr, call_selection_changed); + bool can_add_volumes = model_object->volumes.size() > 1; + if (can_add_volumes && model_object->is_cut()) { + int no_connectors_cnt = 0; + for (const ModelVolume* v : model_object->volumes) + if (!v->is_cut_connector()) + no_connectors_cnt++; + can_add_volumes = no_connectors_cnt > 1; + } + // add volumes to the object - if (model_object->volumes.size() > 1 && !model_object->is_cut()) { + if (can_add_volumes) { for (const ModelVolume* volume : model_object->volumes) { + if (model_object->is_cut() && volume->is_cut_connector()) + continue; const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, from_u8(volume->name), volume->type(), diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 1260ee3bb..97714ea62 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -18,9 +18,17 @@ namespace Slic3r { namespace GUI { -static const double Margin = 20.0; static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); +// connector colors +static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW(); +static const ColorRGBA DOWEL_COLOR = ColorRGBA::DARK_YELLOW(); +static const ColorRGBA HOVERED_PLAG_COLOR = ColorRGBA::CYAN(); +static const ColorRGBA HOVERED_DOWEL_COLOR = ColorRGBA(0.0f, 0.5f, 0.5f, 1.0f); +static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); +static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::DARK_GRAY(); +static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount; @@ -1426,8 +1434,6 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) unselect_all_connectors(); set_connectors_editing(false); } - - m_parent.request_extra_frame(); } void GLGizmoCut3D::render_build_size() @@ -1455,10 +1461,22 @@ void GLGizmoCut3D::reset_cut_plane() update_clipper(); } +void GLGizmoCut3D::invalidate_cut_plane() +{ + m_rotation_m = Transform3d::Identity(); + m_plane_center = Vec3d::Zero(); + m_min_pos = Vec3d::Zero(); + m_max_pos = Vec3d::Zero(); + m_bb_center = Vec3d::Zero(); + m_center_offset = Vec3d::Zero(); +} + void GLGizmoCut3D::set_connectors_editing(bool connectors_editing) { m_connectors_editing = connectors_editing; update_raycasters_for_picking(); + + m_parent.request_extra_frame(); } void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) @@ -1661,13 +1679,14 @@ void GLGizmoCut3D::render_connectors() pos[Z] += sla_shift; // First decide about the color of the point. - if (size_t(m_hover_id- m_connectors_group_id) == i) - render_color = ColorRGBA::CYAN(); + if (!m_connectors_editing) + render_color = CONNECTOR_DEF_COLOR; + else if (size_t(m_hover_id - m_connectors_group_id) == i) + render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; else if (m_selected[i]) - render_color = ColorRGBA::DARK_GRAY(); + render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; else // neither hover nor picking - render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); - + render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR; // ! #ysFIXME rework get_volume_transformation if (0) { // else { // neither hover nor picking int mesh_id = -1; @@ -1773,6 +1792,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) else { // the object is SLA-elevated and the plane is under it. } + + invalidate_cut_plane(); } @@ -2005,7 +2026,8 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (action == SLAGizmoEventType::LeftDown && !shift_down) { // If there is no selection and no hovering, add new point if (m_hover_id == -1 && !control_down && !alt_down) - return add_connector(connectors, mouse_position); + if (!add_connector(connectors, mouse_position)) + unselect_all_connectors(); return true; } if (!m_connectors_editing) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 2df337b04..fe8422865 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -149,6 +149,7 @@ public: void update_clipper(); void update_clipper_on_render(); void set_connectors_editing() { m_connectors_editing = true; } + void invalidate_cut_plane(); BoundingBoxf3 bounding_box() const; BoundingBoxf3 transformed_bounding_box(bool revert_move = false) const; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 16433bc79..594e0716c 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1228,7 +1228,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break; case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break; - case InfoItemType::Cut: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break; + case InfoItemType::CutConnectors: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break; default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break; } } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 61d5bf6ec..4c592b878 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -38,7 +38,7 @@ static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char WarningIcon[] = "exclamation"; static constexpr char WarningManifoldIcon[] = "exclamation_manifold"; -static constexpr char LockIcon[] = "lock_closed"; +static constexpr char LockIcon[] = "cut_"; struct InfoItemAtributes { std::string name; @@ -49,7 +49,7 @@ const std::map INFO_ITEMS{ // info_item Type info_item Name info_item BitmapName { InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, }, { InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, }, - { InfoItemType::Cut, {L("Cut connectors"), "cut_" }, }, + { InfoItemType::CutConnectors, {L("Cut connectors"), "cut_connectors" }, }, { InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, }, { InfoItemType::Sinking, {L("Sinking"), "sinking"}, }, { InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, }, diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 87be7a4e2..bc9144803 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -51,7 +51,7 @@ enum class InfoItemType Undef, CustomSupports, CustomSeam, - Cut, + CutConnectors, MmuSegmentation, Sinking, VariableLayerHeight diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d09de9cf3..bb3f6e41a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -97,6 +97,7 @@ #include "MsgDialog.hpp" #include "ProjectDirtyStateManager.hpp" #include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification +#include "Gizmos/GLGizmoCut.hpp" #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -2980,6 +2981,9 @@ void Plater::priv::object_list_changed() const bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() == ModelInstancePVS_Inside; sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits); + + // invalidate CutGizmo after changes in ObjectList + static_cast(q->canvas3D()->get_gizmos_manager().get_gizmo(GLGizmosManager::Cut))->invalidate_cut_plane(); } void Plater::priv::select_all() From d1c871758b72ec7a683df01de4907169978b4785 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 29 Sep 2022 12:26:08 +0200 Subject: [PATCH 77/97] Cut WIP: * ObjectDataViewModel: Respect to the volume id, when adding the new volume to the object * 3mf : Save/Load info about connectors --- src/libslic3r/Format/3mf.cpp | 90 ++++++++++++++++++++------ src/libslic3r/Model.hpp | 4 +- src/slic3r/GUI/GUI_ObjectList.hpp | 5 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 38 +++-------- src/slic3r/GUI/ObjectDataViewModel.hpp | 12 ++-- 5 files changed, 93 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 4251110c4..f2056b8bd 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -409,6 +409,19 @@ namespace Slic3r { VolumeMetadataList volumes; }; + struct CutObjectInfo + { + struct Connector + { + int volume_id; + int type; + float r_tolerance; + float h_tolerance; + }; + CutObjectBase id; + std::vector connectors; + }; + // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. typedef std::map IdToModelObjectMap; typedef std::map IdToAliasesMap; @@ -417,7 +430,7 @@ namespace Slic3r { typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; typedef std::map IdToLayerConfigRangesMap; - typedef std::map IdToCutObjectIdMap; + typedef std::map IdToCutObjectInfoMap; typedef std::map> IdToSlaSupportPointsMap; typedef std::map> IdToSlaDrainHolesMap; @@ -445,7 +458,7 @@ namespace Slic3r { IdToGeometryMap m_geometries; CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; - IdToCutObjectIdMap m_cut_object_ids; + IdToCutObjectInfoMap m_cut_object_infos; IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; @@ -774,11 +787,6 @@ namespace Slic3r { return false; } - // m_cut_object_ids are indexed by a 1 based model object index. - IdToCutObjectIdMap::iterator cut_object_id = m_cut_object_ids.find(object.second + 1); - if (cut_object_id != m_cut_object_ids.end()) - model_object->cut_id = std::move(cut_object_id->second); - // m_layer_heights_profiles are indexed by a 1 based model object index. IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); if (obj_layer_heights_profile != m_layer_heights_profiles.end()) @@ -831,6 +839,19 @@ namespace Slic3r { if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) return false; + + // Apply cut information for object if any was loaded + // m_cut_object_ids are indexed by a 1 based model object index. + IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1); + if (cut_object_info != m_cut_object_infos.end()) { + model_object->cut_id = cut_object_info->second.id; + + for (auto connector : cut_object_info->second.connectors) { + assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size())); + model_object->volumes[connector.volume_id]->cut_info = + ModelVolume::CutInfo(CutConnectorType(connector.type), connector.r_tolerance, connector.h_tolerance, true); + } + } } // If instances contain a single volume, the volume offset should be 0,0,0 @@ -979,22 +1000,39 @@ namespace Slic3r { continue; } - IdToCutObjectIdMap::iterator object_item = m_cut_object_ids.find(obj_idx); - if (object_item != m_cut_object_ids.end()) { + IdToCutObjectInfoMap::iterator object_item = m_cut_object_infos.find(obj_idx); + if (object_item != m_cut_object_infos.end()) { add_error("Found duplicated cut_object_id"); continue; } - for (const auto& obj_cut_id : object_tree) { - if (obj_cut_id.first != "cut_id") - continue; - pt::ptree cut_id_tree = obj_cut_id.second; - ObjectID obj_id(cut_id_tree.get(".id")); - CutObjectBase cut_id(ObjectID(cut_id_tree.get(".id")), - cut_id_tree.get(".check_sum"), - cut_id_tree.get(".connectors_cnt")); - m_cut_object_ids.insert({ obj_idx, std::move(cut_id) }); + CutObjectBase cut_id; + std::vector connectors; + + for (const auto& obj_cut_info : object_tree) { + if (obj_cut_info.first == "cut_id") { + pt::ptree cut_id_tree = obj_cut_info.second; + cut_id = CutObjectBase(ObjectID( cut_id_tree.get(".id")), + cut_id_tree.get(".check_sum"), + cut_id_tree.get(".connectors_cnt")); + } + if (obj_cut_info.first == "connectors") { + pt::ptree cut_connectors_tree = obj_cut_info.second; + for (const auto& cut_connector : cut_connectors_tree) { + if (cut_connector.first != "connector") + continue; + pt::ptree connector_tree = cut_connector.second; + CutObjectInfo::Connector connector = {connector_tree.get(".volume_id"), + connector_tree.get(".type"), + connector_tree.get(".r_tolerance"), + connector_tree.get(".h_tolerance")}; + connectors.emplace_back(connector); + } + } } + + CutObjectInfo cut_info {cut_id, connectors}; + m_cut_object_infos.insert({ obj_idx, cut_info }); } } } @@ -2865,6 +2903,18 @@ namespace Slic3r { cut_id_tree.put(".id", object->cut_id.id().id); cut_id_tree.put(".check_sum", object->cut_id.check_sum()); cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt()); + + int volume_idx = -1; + for (const ModelVolume* volume : object->volumes) { + ++volume_idx; + if (volume->is_cut_connector()) { + pt::ptree& connectors_tree = obj_tree.add("connectors.connector", ""); + connectors_tree.put(".volume_id", volume_idx); + connectors_tree.put(".type", int(volume->cut_info.connector_type)); + connectors_tree.put(".r_tolerance", volume->cut_info.radius_tolerance); + connectors_tree.put(".h_tolerance", volume->cut_info.height_tolerance); + } + } } if (!tree.empty()) { @@ -2876,6 +2926,10 @@ namespace Slic3r { boost::replace_all(out, ">\n \n ", ">\n "); + boost::replace_all(out, ">\n ", ">\n "); + boost::replace_all(out, ">\n ", ">\n "); boost::replace_all(out, ">", ">\n "); // OR just boost::replace_all(out, "><", ">\n<"); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index cc2c612fe..01cd37414 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -730,9 +730,9 @@ public: float height_tolerance{ 0.f };// [0.f : 1.f] CutInfo() = default; - CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance) : + CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false) : is_connector(true), - is_processed(false), + is_processed(processed), connector_type(type), radius_tolerance(rad_tolerance), height_tolerance(h_tolerance) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 79f8e74c6..3ae0d0533 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -288,6 +288,9 @@ public: void changed_object(const int obj_idx = -1) const; void part_selection_changed(); + // Add object's volumes to the list + // Return selected items, if add_to_selection is defined + wxDataViewItemArray add_volumes_to_object_in_list(size_t obj_idx, std::function add_to_selection = nullptr); // Add object to the list void add_object_to_list(size_t obj_idx, bool call_selection_changed = true); // Delete object from the list @@ -392,7 +395,7 @@ public: void toggle_printable_state(); void set_extruder_for_selected_items(const int extruder) const ; - wxDataViewItemArray reorder_volumes_and_get_selection(int obj_idx, std::function add_to_selection = nullptr); + wxDataViewItemArray reorder_volumes_and_get_selection(size_t obj_idx, std::function add_to_selection = nullptr); void apply_volumes_order(); bool has_paint_on_segmentation(); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 4c592b878..0475fe395 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -373,13 +373,12 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node, con UpdateBitmapForNode(node); } -wxDataViewItem ObjectDataViewModel::Add(const wxString &name, - const int extruder, +wxDataViewItem ObjectDataViewModel::AddObject(const wxString &name, + const wxString& extruder, const std::string& warning_icon_name, const bool has_lock) { - const wxString extruder_str = extruder == 0 ? _L("default") : wxString::Format("%d", extruder); - auto root = new ObjectDataViewModelNode(name, extruder_str); + auto root = new ObjectDataViewModelNode(name, extruder); // Add warning icon if detected auto-repaire UpdateBitmapForNode(root, warning_icon_name, has_lock); @@ -394,37 +393,20 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, + const int volume_idx, const Slic3r::ModelVolumeType volume_type, - const std::string& warning_icon_name/* = std::string()*/, - const int extruder/* = 0*/, - const bool create_frst_child/* = true*/) + const std::string& warning_icon_name, + const wxString& extruder) { ObjectDataViewModelNode *root = static_cast(parent_item.GetID()); if (!root) return wxDataViewItem(0); - wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - // get insertion position according to the existed Layers and/or Instances Items int insert_position = get_root_idx(root, itLayerRoot); if (insert_position < 0) insert_position = get_root_idx(root, itInstanceRoot); - if (create_frst_child && root->m_volumes_cnt == 0) - { - const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; - const auto node = new ObjectDataViewModelNode(root, root->m_name, type, extruder_str, 0); - UpdateBitmapForNode(node, root->warning_icon_name(), root->has_lock()); - - insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); - // notify control - const wxDataViewItem child((void*)node); - ItemAdded(parent_item, child); - - root->m_volumes_cnt++; - if (insert_position >= 0) insert_position++; - } - - const auto node = new ObjectDataViewModelNode(root, name, volume_type, extruder_str, root->m_volumes_cnt); + const auto node = new ObjectDataViewModelNode(root, name, volume_type, extruder, volume_idx); UpdateBitmapForNode(node, warning_icon_name, root->has_lock() && volume_type < ModelVolumeType::PARAMETER_MODIFIER); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); @@ -631,14 +613,12 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, const t_layer_height_range& layer_range, - const int extruder/* = 0*/, + const wxString& extruder, const int index /* = -1*/) { ObjectDataViewModelNode *parent_node = static_cast(parent_item.GetID()); if (!parent_node) return wxDataViewItem(0); - wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - // get LayerRoot node ObjectDataViewModelNode *layer_root_node; wxDataViewItem layer_root_item; @@ -655,7 +635,7 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_ } // Add layer node - ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder); if (index < 0) layer_root_node->Append(layer_node); else diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index bc9144803..55dbaafe2 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -271,16 +271,16 @@ public: ObjectDataViewModel(); ~ObjectDataViewModel(); - wxDataViewItem Add( const wxString &name, - const int extruder, + wxDataViewItem AddObject( const wxString &name, + const wxString& extruder, const std::string& warning_icon_name, const bool has_lock); wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, + const int volume_idx, const Slic3r::ModelVolumeType volume_type, - const std::string& warning_icon_name = std::string(), - const int extruder = 0, - const bool create_frst_child = true); + const std::string& warning_icon_name, + const wxString& extruder); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); @@ -288,7 +288,7 @@ public: wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, const t_layer_height_range& layer_range, - const int extruder = 0, + const wxString& extruder, const int index = -1); size_t GetItemIndexForFirstVolume(ObjectDataViewModelNode* node_parent); wxDataViewItem Delete(const wxDataViewItem &item); From a8440db5ec6cff6319a8bb7b9824a416d1414be4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 30 Sep 2022 14:07:17 +0200 Subject: [PATCH 78/97] Cut WIP: * ObjectList & Selection: Show Connectors in the Scene, when CutConnectors Item is selected * ObjectList: refactoring: extract the adding of volumes to the add_volumes_to_object_in_list() * If some connector is selected on 3dScene -> select all connectors of this object * GLGizmoScale3D : check if grabber is enabled, when do rendering + GLGizmoCut: refactoring : split render_cut_plane_grabbers to several functions --- src/slic3r/GUI/GUI_ObjectList.cpp | 235 +++++++++++++--------- src/slic3r/GUI/GUI_ObjectList.hpp | 2 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 204 +++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 7 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 7 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 13 +- src/slic3r/GUI/Selection.cpp | 22 ++ src/slic3r/GUI/Selection.hpp | 2 + 10 files changed, 268 insertions(+), 232 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 05eb02778..d3f1d1bf2 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -403,6 +403,13 @@ MeshErrorsInfo ObjectList::get_mesh_errors_info(const int obj_idx, const int vol if (obj_idx < 0) return { {}, {} }; // hide tooltip + const ModelObject* object = (*m_objects)[obj_idx]; + if (vol_idx != -1 && vol_idx >= int(object->volumes.size())) { + if (sidebar_info) + *sidebar_info = _L("Wrong volume index "); + return { {}, {} }; // hide tooltip + } + const TriangleMeshStats& stats = vol_idx == -1 ? (*m_objects)[obj_idx]->get_object_stl_stats() : (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats(); @@ -2075,30 +2082,11 @@ void ObjectList::split() volume->split(nozzle_dmrs_cnt); + (*m_objects)[obj_idx]->input_file.clear(); + wxBusyCursor wait; - auto model_object = (*m_objects)[obj_idx]; - - auto parent = m_objects_model->GetTopParent(item); - if (parent) - m_objects_model->DeleteVolumeChildren(parent); - else - parent = item; - - for (const ModelVolume* volume : model_object->volumes) { - const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name), - volume->type(),// is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, - get_warning_icon_name(volume->mesh().stats()), - volume->config.has("extruder") ? volume->config.extruder() : 0, - false); - // add settings to the part, if it has those - add_settings_item(vol_item, &volume->config.get()); - } - - model_object->input_file.clear(); - - if (parent == item) - Expand(parent); + add_volumes_to_object_in_list(obj_idx); changed_object(obj_idx); // update printable state for new volumes on canvas3D @@ -2524,7 +2512,14 @@ void ObjectList::part_selection_changed() GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); - if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { + if (item && m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) { + og_name = _L("Cut Connectors information"); + + update_and_show_manipulations = true; + enable_manipulation = false; + disable_ununiform_scale = true; + } + else if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { og_name = _L("Group manipulation"); const Selection& selection = scene_selection(); @@ -2575,11 +2570,12 @@ void ObjectList::part_selection_changed() const wxDataViewItem parent = m_objects_model->GetParent(item); const ItemType parent_type = m_objects_model->GetItemType(parent); obj_idx = m_objects_model->GetObjectIdByItem(item); + ModelObject* object = (*m_objects)[obj_idx]; if (parent == wxDataViewItem(nullptr) || type == itInfo) { og_name = _L("Object manipulation"); - m_config = &(*m_objects)[obj_idx]->config; + m_config = &object->config; update_and_show_manipulations = true; if (type == itInfo) { @@ -2602,23 +2598,23 @@ void ObjectList::part_selection_changed() gizmos_mgr.open_gizmo(gizmo_type); break; } - case InfoItemType::Sinking: { break; } + case InfoItemType::Sinking: default: { break; } } } else - disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + disable_ss_manipulation = object->is_cut(); } else { if (type & itSettings) { if (parent_type & itObject) { og_name = _L("Object Settings to modify"); - m_config = &(*m_objects)[obj_idx]->config; + m_config = &object->config; } else if (parent_type & itVolume) { og_name = _L("Part Settings to modify"); volume_id = m_objects_model->GetVolumeIdByItem(parent); - m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + m_config = &object->volumes[volume_id]->config; } else if (parent_type & itLayer) { og_name = _L("Layer range Settings to modify"); @@ -2629,17 +2625,17 @@ void ObjectList::part_selection_changed() else if (type & itVolume) { og_name = _L("Part manipulation"); volume_id = m_objects_model->GetVolumeIdByItem(item); - m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + m_config = &object->volumes[volume_id]->config; update_and_show_manipulations = true; - enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); + enable_manipulation = !(object->is_cut() && object->volumes[volume_id]->is_cut_connector()); } else if (type & itInstance) { og_name = _L("Instance manipulation"); update_and_show_manipulations = true; // fill m_config by object's values - m_config = &(*m_objects)[obj_idx]->config; - disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + m_config = &object->config; + disable_ss_manipulation = object->is_cut(); } else if (type & (itLayerRoot|itLayer)) { og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range"); @@ -2658,7 +2654,6 @@ void ObjectList::part_selection_changed() wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " "); if (item) { - // wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_info(obj_idx, volume_id)); } @@ -2817,43 +2812,70 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio } } +static wxString extruder2str(int extruder) +{ + return extruder == 0 ? _L("default") : wxString::Format("%d", extruder); +} +static bool can_add_volumes_to_object(const ModelObject* object) +{ + bool can = object->volumes.size() > 1; + + if (can && object->is_cut()) { + int no_connectors_cnt = 0; + for (const ModelVolume* v : object->volumes) + if (!v->is_cut_connector()) + no_connectors_cnt++; + can = no_connectors_cnt > 1; + } + + return can; +} + +wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, std::function add_to_selection/* = nullptr*/) +{ + wxDataViewItem object_item = m_objects_model->GetItemById(int(obj_idx)); + m_objects_model->DeleteVolumeChildren(object_item); + + wxDataViewItemArray items; + + const ModelObject* object = (*m_objects)[obj_idx]; + // add volumes to the object + if (can_add_volumes_to_object(object)) { + int volume_idx{ -1 }; + for (const ModelVolume* volume : object->volumes) { + ++volume_idx; + if (object->is_cut() && volume->is_cut_connector()) + continue; + const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, + from_u8(volume->name), + volume_idx, + volume->type(), + get_warning_icon_name(volume->mesh().stats()), + extruder2str(volume->config.has("extruder") ? volume->config.extruder() : 0)); + add_settings_item(vol_item, &volume->config.get()); + + if (add_to_selection && add_to_selection(volume)) + items.Add(vol_item); + } + Expand(object_item); + } + + return items; +} void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; const wxString& item_name = from_u8(model_object->name); - const auto item = m_objects_model->Add(item_name, - model_object->config.has("extruder") ? model_object->config.extruder() : 0, + const auto item = m_objects_model->AddObject(item_name, + extruder2str(model_object->config.has("extruder") ? model_object->config.extruder() : 0), get_warning_icon_name(model_object->mesh().stats()), model_object->is_cut()); update_info_items(obj_idx, nullptr, call_selection_changed); - bool can_add_volumes = model_object->volumes.size() > 1; - if (can_add_volumes && model_object->is_cut()) { - int no_connectors_cnt = 0; - for (const ModelVolume* v : model_object->volumes) - if (!v->is_cut_connector()) - no_connectors_cnt++; - can_add_volumes = no_connectors_cnt > 1; - } - - // add volumes to the object - if (can_add_volumes) { - for (const ModelVolume* volume : model_object->volumes) { - if (model_object->is_cut() && volume->is_cut_connector()) - continue; - const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, - from_u8(volume->name), - volume->type(), - get_warning_icon_name(volume->mesh().stats()), - volume->config.has("extruder") ? volume->config.extruder() : 0, - false); - add_settings_item(vol_item, &volume->config.get()); - } - Expand(item); - } + add_volumes_to_object_in_list(obj_idx); // add instances to the object, if it has those if (model_object->instances.size()>1) @@ -3327,7 +3349,7 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, const auto layer_item = m_objects_model->AddLayersChild(layers_item, range, - config.opt_int("extruder"), + extruder2str(config.opt_int("extruder")), layer_idx); add_settings_item(layer_item, &config); } @@ -3421,6 +3443,24 @@ bool ObjectList::is_selected(const ItemType type) const return false; } +bool ObjectList::is_connectors_item_selected() const +{ + const wxDataViewItem& item = GetSelection(); + if (item) + return m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors; + + return false; +} + +bool ObjectList::is_connectors_item_selected(const wxDataViewItemArray& sels) const +{ + for (auto item : sels) + if (m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) + return true; + + return false; +} + int ObjectList::get_selected_layers_range_idx() const { const wxDataViewItem& item = GetSelection(); @@ -3547,11 +3587,18 @@ void ObjectList::update_selections() else { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); - if (gl_vol->volume_idx() >= 0) + if (gl_vol->volume_idx() >= 0) { // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids // are not associated with ModelVolumes, but they are temporarily generated by the backend // (for example, SLA supports or SLA pad). - sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); + int obj_idx = gl_vol->object_idx(); + int vol_idx = gl_vol->volume_idx(); + assert(obj_idx >= 0 && vol_idx >= 0); + if (object(obj_idx)->volumes[vol_idx]->is_cut_connector()) + sels.Add(m_objects_model->GetInfoItemByType(m_objects_model->GetItemById(obj_idx), InfoItemType::CutConnectors)); + else + sels.Add(m_objects_model->GetItemByVolumeId(obj_idx, vol_idx)); + } } m_selection_mode = smVolume; } } @@ -3602,7 +3649,7 @@ void ObjectList::update_selections() if (sels.size() == 0 || m_selection_mode & smSettings) m_selection_mode = smUndef; - if (fix_cut_selection(sels)) { + if (fix_cut_selection(sels) || is_connectors_item_selected(sels)) { m_prevent_list_events = true; // If some part is selected, unselect all items except of selected parts of the current object @@ -3616,7 +3663,7 @@ void ObjectList::update_selections() update_selections_on_canvas(); // to update the toolbar and info sizer - if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { + if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject || is_connectors_item_selected()) { auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT); event.SetEventObject(this); wxPostEvent(this, event); @@ -3662,16 +3709,29 @@ void ObjectList::update_selections_on_canvas() volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); } else if (type == itInfo) { - // When selecting an info item, select one instance of the - // respective object - a gizmo may want to be opened. - int inst_idx = selection.get_instance_idx(); - int scene_obj_idx = selection.get_object_idx(); - mode = Selection::Instance; - // select first instance, unless an instance of the object is already selected - if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx) - inst_idx = 0; - std::vector idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); - volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) { + mode = Selection::Volume; + + // When selecting CutConnectors info item, select all object volumes, which are marked as a connector + const ModelObject* obj = object(obj_idx); + for (unsigned int vol_idx = 0; vol_idx < obj->volumes.size(); vol_idx++) + if (obj->volumes[vol_idx]->is_cut_connector()) { + std::vector idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + } + } + else { + // When selecting an info item, select one instance of the + // respective object - a gizmo may want to be opened. + int inst_idx = selection.get_instance_idx(); + int scene_obj_idx = selection.get_object_idx(); + mode = Selection::Instance; + // select first instance, unless an instance of the object is already selected + if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx) + inst_idx = 0; + std::vector idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + } } else { @@ -4569,33 +4629,14 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const wxGetApp().plater()->update(); } -wxDataViewItemArray ObjectList::reorder_volumes_and_get_selection(int obj_idx, std::function add_to_selection/* = nullptr*/) +wxDataViewItemArray ObjectList::reorder_volumes_and_get_selection(size_t obj_idx, std::function add_to_selection/* = nullptr*/) { - wxDataViewItemArray items; + (*m_objects)[obj_idx]->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); - ModelObject* object = (*m_objects)[obj_idx]; - if (object->volumes.size() <= 1) - return items; + wxDataViewItemArray items = add_volumes_to_object_in_list(obj_idx, std::move(add_to_selection)); - object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); + changed_object(int(obj_idx)); - wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); - m_objects_model->DeleteVolumeChildren(object_item); - - for (const ModelVolume* volume : object->volumes) { - wxDataViewItem vol_item = m_objects_model->AddVolumeChild(object_item, from_u8(volume->name), - volume->type(), - get_warning_icon_name(volume->mesh().stats()), - volume->config.has("extruder") ? volume->config.extruder() : 0, - false); - // add settings to the part, if it has those - add_settings_item(vol_item, &volume->config.get()); - - if (add_to_selection && add_to_selection(volume)) - items.Add(vol_item); - } - - changed_object(obj_idx); return items; } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 3ae0d0533..cb498f87e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -343,6 +343,8 @@ public: void init_objects(); bool multiple_selection() const ; bool is_selected(const ItemType type) const; + bool is_connectors_item_selected() const; + bool is_connectors_item_selected(const wxDataViewItemArray& sels) const; int get_selected_layers_range_idx() const; void set_selected_layers_range_idx(const int range_idx) { m_selected_layers_range_idx = range_idx; } void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index e54a4cf56..a2d687db0 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -763,7 +763,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_enabled = true; } - else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { + else if (obj_list->is_connectors_item_selected() || obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { reset_settings_value(); m_new_move_label_string = L("Translate"); m_new_rotate_label_string = L("Rotate"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 97714ea62..71a9c5dcf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -597,114 +597,109 @@ static float get_grabber_mean_size(const BoundingBoxf3& bb) return float((bb.size().x() + bb.size().y() + bb.size().z()) / 3.0); } -void GLGizmoCut3D::render_cut_center_grabber() +void GLGizmoCut3D::render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix) +{ + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader) { + shader->start_using(); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", wxGetApp().plater()->get_camera().get_projection_matrix()); + + model.set_color(color); + model.render(); + + shader->stop_using(); + } +} + +void GLGizmoCut3D::render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width) +{ + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); + if (shader) { + shader->start_using(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", wxGetApp().plater()->get_camera().get_projection_matrix()); + shader->set_uniform("width", width); + + line_model.set_color(color); + line_model.render(); + + shader->stop_using(); + } +} + +void GLGizmoCut3D::render_rotation_snapping(Axis axis, const ColorRGBA& color) +{ + GLShaderProgram* line_shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); + if (!line_shader) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_start_dragging_m; + + if (axis == X) + view_model_matrix = view_model_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ()); + else + view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * rotation_transform(-0.5 * PI * Vec3d::UnitY()); + + line_shader->start_using(); + line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + line_shader->set_uniform("view_model_matrix", view_model_matrix); + line_shader->set_uniform("width", 0.25f); + + m_circle.render(); + m_scale.render(); + m_snap_radii.render(); + m_reference_radius.render(); + if (m_dragging) { + line_shader->set_uniform("width", 1.5f); + m_angle_arc.set_color(color); + m_angle_arc.render(); + } + + line_shader->stop_using(); +} + +void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix) +{ + const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, m_grabber_connection_len)); + + render_line(m_grabber_connection, color, line_view_matrix, 0.2f); +}; + +void GLGizmoCut3D::render_cut_plane_grabbers() { glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (!shader) - return; - ColorRGBA color = m_hover_id == Z ? complementary(GRABBER_COLOR) : GRABBER_COLOR; - const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_matrix = wxGetApp().plater()->get_camera().get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; + const Grabber& grabber = m_grabbers.front(); + const float mean_size = get_grabber_mean_size(bounding_box()); - const BoundingBoxf3 box = bounding_box(); - - const float mean_size = get_grabber_mean_size(box); double size = m_dragging && m_hover_id == Z ? double(grabber.get_dragging_half_size(mean_size)) : double(grabber.get_half_size(mean_size)); Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); Vec3d offset = 1.25 * size * Vec3d::UnitZ(); - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - - const Transform3d view_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; - - auto render = [shader, this](GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix) { - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - model.set_color(color); - model.render(); - }; - - auto render_grabber_connection = [shader, camera, view_matrix, this](const ColorRGBA& color) - { - shader->stop_using(); - GLShaderProgram* line_shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); - if (!line_shader) - return; - - line_shader->start_using(); - line_shader->set_uniform("emission_factor", 0.1f); - line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - - const Transform3d trafo = view_matrix * scale_transform(Vec3d(1.0, 1.0, m_grabber_connection_len)); - line_shader->set_uniform("view_model_matrix", trafo); - line_shader->set_uniform("normal_matrix", (Matrix3d)trafo.matrix().block(0, 0, 3, 3).inverse().transpose()); - line_shader->set_uniform("width", 0.2f); - - m_grabber_connection.set_color(color); - m_grabber_connection.render(); - - line_shader->stop_using(); - shader->start_using(); - }; - - auto render_rotation_snapping = [shader, camera, this](Axis axis, const ColorRGBA& color) - { - Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_start_dragging_m; - - if (axis == X) - view_model_matrix = view_model_matrix * rotation_transform( 0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ()); - else - view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * rotation_transform(-0.5 * PI * Vec3d::UnitY()); - - shader->stop_using(); - - GLShaderProgram* line_shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); - if (!line_shader) - return; - - line_shader->start_using(); - line_shader->set_uniform("emission_factor", 0.1f); - line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - - line_shader->set_uniform("view_model_matrix", view_model_matrix); - line_shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - line_shader->set_uniform("width", 0.25f); - - m_circle.render(); - m_scale.render(); - m_snap_radii.render(); - m_reference_radius.render(); - if (m_dragging) { - line_shader->set_uniform("width", 1.5f); - m_angle_arc.set_color(color); - m_angle_arc.render(); - } - - line_shader->stop_using(); - shader->start_using(); - }; - // render Z grabber if ((!m_dragging && m_hover_id < 0)) - render_grabber_connection(color); - render(m_sphere.model, color, view_matrix * scale_transform(size)); + render_grabber_connection(color, view_matrix); + render_model(m_sphere.model, color, view_matrix * scale_transform(size)); if (!m_dragging && m_hover_id < 0 || m_hover_id == Z) { const BoundingBoxf3 tbb = transformed_bounding_box(); if (tbb.min.z() <= 0.0) - render(m_cone.model, color, view_matrix * assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); + render_model(m_cone.model, color, view_matrix * assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); if (tbb.max.z() >= 0.0) - render(m_cone.model, color, view_matrix * assemble_transform(offset, Vec3d::Zero(), cone_scale)); + render_model(m_cone.model, color, view_matrix * assemble_transform(offset, Vec3d::Zero(), cone_scale)); } // render top sphere for X/Y grabbers @@ -714,7 +709,7 @@ void GLGizmoCut3D::render_cut_center_grabber() size = m_dragging ? double(grabber.get_dragging_half_size(mean_size)) : double(grabber.get_half_size(mean_size)); color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::GRAY(); - render(m_sphere.model, color, view_matrix * assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); + render_model(m_sphere.model, color, view_matrix * assemble_transform(m_grabber_connection_len * Vec3d::UnitZ(), Vec3d::Zero(), size * Vec3d::Ones())); } // render X grabber @@ -726,14 +721,14 @@ void GLGizmoCut3D::render_cut_center_grabber() color = m_hover_id == X ? complementary(ColorRGBA::RED()) : ColorRGBA::RED(); if (m_hover_id == X) { - render_grabber_connection(color); + render_grabber_connection(color, view_matrix); render_rotation_snapping(X, color); } offset = Vec3d(0.0, 1.25 * size, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); + render_model(m_cone.model, color, view_matrix * assemble_transform(offset, -0.5 * PI * Vec3d::UnitX(), cone_scale)); offset = Vec3d(0.0, -1.25 * size, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); + render_model(m_cone.model, color, view_matrix * assemble_transform(offset, 0.5 * PI * Vec3d::UnitX(), cone_scale)); } // render Y grabber @@ -745,17 +740,15 @@ void GLGizmoCut3D::render_cut_center_grabber() color = m_hover_id == Y ? complementary(ColorRGBA::GREEN()) : ColorRGBA::GREEN(); if (m_hover_id == Y) { - render_grabber_connection(color); + render_grabber_connection(color, view_matrix); render_rotation_snapping(Y, color); } offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); + render_model(m_cone.model, color, view_matrix * assemble_transform(offset, 0.5 * PI * Vec3d::UnitY(), cone_scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); - render(m_cone.model, color, view_matrix * assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); + render_model(m_cone.model, color, view_matrix * assemble_transform(offset, -0.5 * PI * Vec3d::UnitY(), cone_scale)); } - - shader->stop_using(); } void GLGizmoCut3D::render_cut_line() @@ -766,23 +759,10 @@ void GLGizmoCut3D::render_cut_line() glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); + m_cut_line.reset(); + m_cut_line.init_from(its_make_line((Vec3f)m_line_beg.cast(), (Vec3f)m_line_end.cast())); - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("width", 0.25f); - - m_cut_line.reset(); - m_cut_line.init_from(its_make_line((Vec3f)m_line_beg.cast(), (Vec3f)m_line_end.cast())); - - m_cut_line.set_color(GRABBER_COLOR); - m_cut_line.render(); - - shader->stop_using(); - } + render_line(m_cut_line, GRABBER_COLOR, wxGetApp().plater()->get_camera().get_view_matrix(), 0.25f); } bool GLGizmoCut3D::on_init() @@ -1303,7 +1283,7 @@ void GLGizmoCut3D::on_render() if (!m_hide_cut_plane && !m_connectors_editing) { render_cut_plane(); - render_cut_center_grabber(); + render_cut_plane_grabbers(); } render_cut_line(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index fe8422865..4335da35a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -221,7 +221,11 @@ private: void discard_cut_line_processing(); void render_cut_plane(); - void render_cut_center_grabber(); + void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix); + void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width); + void render_rotation_snapping(Axis axis, const ColorRGBA& color); + void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); + void render_cut_plane_grabbers(); void render_cut_line(); void perform_cut(const Selection&selection); void set_center_pos(const Vec3d¢er_pos, bool force = false); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index dbe0ba734..12c783c43 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -81,12 +81,7 @@ std::string GLGizmoMove3D::on_get_name() const bool GLGizmoMove3D::on_is_activable() const { const Selection& selection = m_parent.get_selection(); - if (selection.is_any_volume() || selection.is_any_modifier()) { - if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) - return !m_parent.get_model()->objects[obj_idx]->is_cut(); - } - - return !selection.is_empty(); + return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty(); } void GLGizmoMove3D::on_start_dragging() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 99d5eb881..80d6f1aa0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -869,12 +869,7 @@ std::string GLGizmoRotate3D::on_get_name() const bool GLGizmoRotate3D::on_is_activable() const { const Selection& selection = m_parent.get_selection(); - if (selection.is_any_volume() || selection.is_any_modifier()) { - if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) - return !m_parent.get_model()->objects[obj_idx]->is_cut(); - } - - return !selection.is_empty(); + return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty(); } void GLGizmoRotate3D::on_start_dragging() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 9adfa2241..9b49cf0b4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -168,12 +168,7 @@ std::string GLGizmoScale3D::on_get_name() const bool GLGizmoScale3D::on_is_activable() const { const Selection& selection = m_parent.get_selection(); - if (selection.is_any_volume() || selection.is_any_modifier()) { - if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) - return !m_parent.get_model()->objects[obj_idx]->is_cut(); - } - - return !selection.is_empty() && !selection.is_wipe_tower(); + return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty() && !selection.is_wipe_tower(); } void GLGizmoScale3D::on_start_dragging() @@ -460,7 +455,7 @@ void GLGizmoScale3D::on_render() // draw grabbers render_grabbers(grabber_mean_size); } - else if (m_hover_id == 0 || m_hover_id == 1) { + else if ((m_hover_id == 0 || m_hover_id == 1) && m_grabbers[0].enabled && m_grabbers[1].enabled) { #if ENABLE_LEGACY_OPENGL_REMOVAL // draw connections #if ENABLE_GL_CORE_PROFILE @@ -505,7 +500,7 @@ void GLGizmoScale3D::on_render() shader->stop_using(); } } - else if (m_hover_id == 2 || m_hover_id == 3) { + else if ((m_hover_id == 2 || m_hover_id == 3) && m_grabbers[2].enabled && m_grabbers[3].enabled) { #if ENABLE_LEGACY_OPENGL_REMOVAL // draw connections #if ENABLE_GL_CORE_PROFILE @@ -550,7 +545,7 @@ void GLGizmoScale3D::on_render() shader->stop_using(); } } - else if (m_hover_id == 4 || m_hover_id == 5) { + else if ((m_hover_id == 4 || m_hover_id == 5) && m_grabbers[4].enabled && m_grabbers[5].enabled) { #if ENABLE_LEGACY_OPENGL_REMOVAL // draw connections #if ENABLE_GL_CORE_PROFILE diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c6ddd1fa3..a3828f53d 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -510,6 +510,28 @@ void Selection::volumes_changed(const std::vector &map_volume_old_to_new this->set_bounding_boxes_dirty(); } +bool Selection::is_any_connector() const +{ + const int obj_idx = get_object_idx(); + + if ((is_any_volume() || is_any_modifier() || is_mixed()) && // some solid_part AND/OR modifier is selected + obj_idx >= 0 && m_model->objects[obj_idx]->is_cut()) { + const ModelVolumePtrs& obj_volumes = m_model->objects[obj_idx]->volumes; + for (size_t vol_idx = 0; vol_idx < obj_volumes.size(); vol_idx++) + if (obj_volumes[vol_idx]->is_cut_connector()) + for (const GLVolume* v : *m_volumes) + if (v->object_idx() == obj_idx && v->volume_idx() == (int)vol_idx && v->selected) + return true; + } + return false; +} + +bool Selection::is_any_cut_volume() const +{ + const int obj_idx = get_object_idx(); + return is_any_volume() && obj_idx >= 0 && m_model->objects[obj_idx]->is_cut(); +} + bool Selection::is_single_full_instance() const { if (m_type == SingleFullInstance) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index cebea29e0..97a85a2dc 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -320,6 +320,8 @@ public: bool is_single_volume() const { return m_type == SingleVolume; } bool is_multiple_volume() const { return m_type == MultipleVolume; } bool is_any_volume() const { return is_single_volume() || is_multiple_volume(); } + bool is_any_connector() const; + bool is_any_cut_volume() const; bool is_mixed() const { return m_type == Mixed; } bool is_from_single_instance() const { return get_instance_idx() != -1; } bool is_from_single_object() const; From 58c7d8b1881127cafd971fa14dcf2fcc9ef9c5eb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 3 Oct 2022 17:18:58 +0200 Subject: [PATCH 79/97] CutGizmo: Connectors mode: Implemented Rectangular selection of connectors + some code refactoring --- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 86 ++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 7 ++ src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 22 ++--- 6 files changed, 83 insertions(+), 43 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 09766bbac..43624964a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -210,7 +210,10 @@ public: #if ENABLE_RAYCAST_PICKING void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); } void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); } -#endif // ENABLE_RAYCAST_PICKING +#endif // ENABLE_RAYCAST_PICKING + + virtual bool is_in_editing_mode() const { return false; } + virtual bool is_selection_rectangle_dragging() const { return false; } protected: virtual bool on_init() = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 71a9c5dcf..775cd32cc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -28,6 +28,7 @@ static const ColorRGBA HOVERED_DOWEL_COLOR = ColorRGBA(0.0f, 0.5f, 0.5f, 1.0f); static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::DARK_GRAY(); static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); +static const ColorRGBA CONNECTOR_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; @@ -1287,6 +1288,8 @@ void GLGizmoCut3D::on_render() } render_cut_line(); + + m_selection_rectangle.render(m_parent); } void GLGizmoCut3D::render_debug_input_window() @@ -1622,17 +1625,7 @@ void GLGizmoCut3D::render_connectors() m_selected.resize(connectors.size(), false); } - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - - ScopeGuard guard([shader]() { shader->stop_using(); }); - - const Camera& camera = wxGetApp().plater()->get_camera(); - - ColorRGBA render_color; + ColorRGBA render_color = CONNECTOR_DEF_COLOR; const ModelInstance* mi = mo->instances[inst_id]; const Vec3d& instance_offset = mi->get_offset(); @@ -1656,11 +1649,11 @@ void GLGizmoCut3D::render_connectors() pos -= height * normal; height *= 2; } - pos[Z] += sla_shift; + pos += sla_shift * Vec3d::UnitZ(); // First decide about the color of the point. if (!m_connectors_editing) - render_color = CONNECTOR_DEF_COLOR; + render_color = CONNECTOR_ERR_COLOR; else if (size_t(m_hover_id - m_connectors_group_id) == i) render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; else if (m_selected[i]) @@ -1678,22 +1671,19 @@ void GLGizmoCut3D::render_connectors() const Transform3d volume_trafo = get_volume_transformation(mv); if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { - render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : /*ColorRGBA(0.5f, 0.5f, 0.5f, 1.f)*/ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); + render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : CONNECTOR_ERR_COLOR; break; } - render_color = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); + render_color = CONNECTOR_ERR_COLOR; m_has_invalid_connector = true; } } - m_shapes[connector.attribs].model.set_color(render_color); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * + scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); - const Transform3d scale_trafo = scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); - const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * scale_trafo; - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - - m_shapes[connector.attribs].model.render(); + render_model(m_shapes[connector.attribs].model, render_color, view_model_matrix); } } @@ -1992,6 +1982,29 @@ bool GLGizmoCut3D::is_selection_changed(bool alt_down, bool control_down) return false; } +void GLGizmoCut3D::process_selection_rectangle(CutConnectors &connectors) +{ + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + + ModelObject* mo = m_c->selection_info()->model_object(); + int active_inst = m_c->selection_info()->get_active_instance(); + + // First collect positions of all the points in world coordinates. + Transformation trafo = mo->instances[active_inst]->get_transformation(); + trafo.set_offset(trafo.get_offset() + double(m_c->selection_info()->get_sla_shift()) * Vec3d::UnitZ()); + + std::vector points; + for (const CutConnector&connector : connectors) + points.push_back(connector.pos + trafo.get_offset()); + + // Now ask the rectangle which of the points are inside. + std::vector points_idxs = m_selection_rectangle.contains(points); + m_selection_rectangle.stop_dragging(); + + for (size_t idx : points_idxs) + select_connector(int(idx), rectangle_status == GLSelectionRectangle::EState::Select); +} + bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) @@ -2001,20 +2014,43 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::Moving) ) return process_cut_line(action, mouse_position); + if (!m_connectors_editing) + return false; + CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - if (action == SLAGizmoEventType::LeftDown && !shift_down) { + if (action == SLAGizmoEventType::LeftDown) { + if (shift_down || alt_down) { + // left down with shift - show the selection rectangle: + if (m_hover_id == -1) + m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + } + else // If there is no selection and no hovering, add new point if (m_hover_id == -1 && !control_down && !alt_down) if (!add_connector(connectors, mouse_position)) unselect_all_connectors(); return true; } - if (!m_connectors_editing) - return false; if (action == SLAGizmoEventType::LeftUp && !shift_down) return is_selection_changed(alt_down, control_down); + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + process_selection_rectangle(connectors); + return true; + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); + return true; + } + return false; + } if (action == SLAGizmoEventType::RightDown && !shift_down) { // If any point is in hover state, this should initiate its move - return control back to GLCanvas: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 4335da35a..355c9230e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -2,6 +2,7 @@ #define slic3r_GLGizmoCut_hpp_ #include "GLGizmoBase.hpp" +#include "slic3r/GUI/GLSelectionRectangle.hpp" #include "slic3r/GUI/GLModel.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Model.hpp" @@ -95,6 +96,8 @@ class GLGizmoCut3D : public GLGizmoBase mutable std::vector m_selected; // which pins are currently selected int m_selected_count{ 0 }; + GLSelectionRectangle m_selection_rectangle; + bool m_has_invalid_connector{ false }; bool m_show_shortcuts{ false }; @@ -136,6 +139,9 @@ public: bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair& pos_and_normal, Vec3d& pos_world); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + bool is_in_editing_mode() const override { return m_connectors_editing; } + bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); } + /// /// Drag of plane /// @@ -189,6 +195,7 @@ protected: bool delete_selected_connectors(CutConnectors&connectors); void select_connector(int idx, bool select); bool is_selection_changed(bool alt_down, bool control_down); + void process_selection_rectangle(CutConnectors &connectors); virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index aa8cdda04..0fdc3b2db 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -31,7 +31,7 @@ public: void data_changed() override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(); - bool is_selection_rectangle_dragging() const { + bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index ed48b6e5e..136276803 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -62,8 +62,8 @@ public: void delete_selected_points(bool force = false); //ClippingPlane get_sla_clipping_plane() const; - bool is_in_editing_mode() const { return m_editing_mode; } - bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); } + bool is_in_editing_mode() const override { return m_editing_mode; } + bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); } bool has_backend_supports() const; void reslice_SLA_supports(bool postpone_error_messages = false) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 3946e54e1..add1eb94c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -610,20 +610,11 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) if (evt.GetEventType() == wxEVT_KEY_UP) { - if (m_current == SlaSupports || m_current == Hollow) + if (m_current == SlaSupports || m_current == Hollow || m_current == Cut) { - bool is_editing = true; - bool is_rectangle_dragging = false; - - if (m_current == SlaSupports) { - GLGizmoSlaSupports* gizmo = dynamic_cast(get_current()); - is_editing = gizmo->is_in_editing_mode(); - is_rectangle_dragging = gizmo->is_selection_rectangle_dragging(); - } - else { - GLGizmoHollow* gizmo = dynamic_cast(get_current()); - is_rectangle_dragging = gizmo->is_selection_rectangle_dragging(); - } + GLGizmoBase* gizmo = get_current(); + const bool is_editing = m_current == Hollow ? true : gizmo->is_in_editing_mode(); + const bool is_rectangle_dragging = gizmo->is_selection_rectangle_dragging(); if (keyCode == WXK_SHIFT) { @@ -645,7 +636,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) else if (evt.GetEventType() == wxEVT_KEY_DOWN) { if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) - && dynamic_cast(get_current())->is_in_editing_mode()) + && get_current()->is_in_editing_mode()) { // m_parent.set_cursor(GLCanvas3D::Cross); processed = true; @@ -662,6 +653,9 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) { case WXK_NUMPAD_UP: case WXK_UP: { do_move(1.0); break; } case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } + case WXK_SHIFT : case WXK_ALT: { + processed = get_current()->is_in_editing_mode(); + } default: { break; } } } else if (m_current == Simplify && keyCode == WXK_ESCAPE) { From 74a32e3261f672e67457ec06944dddb27cf8f4b8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 Oct 2022 10:03:55 +0200 Subject: [PATCH 80/97] Cut: Bug fixing and Improvements * CutGizmo: Fixed a label scale * Fixed deselection of selected connectors, when moving the camera * Implemented update of the settings for selected connectors * Connector selection: Ctrl shortcut is changed to Shift to compatibility of the selection/deselection with rectangle selection --- src/libslic3r/Model.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 217 +++++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 17 ++- 3 files changed, 170 insertions(+), 67 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 01cd37414..0d54ebc1f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -222,11 +222,13 @@ private: enum class CutConnectorType : int { Plug , Dowel + , Undef }; enum class CutConnectorStyle : int { Prizm , Frustum + , Undef //,Claw }; @@ -235,6 +237,7 @@ enum class CutConnectorShape : int { , Square , Hexagon , Circle + , Undef //,D-shape }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 775cd32cc..c90211d42 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -37,6 +37,9 @@ const unsigned int ScaleLongEvery = 2; const float ScaleLongTooth = 0.1f; // in percent of radius const unsigned int SnapRegionsCount = 8; +const float UndefFloat = -999.f; +const std::string UndefLabel = " "; + using namespace Geometry; // Generates mesh for a line @@ -274,8 +277,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) static bool pending_right_up = false; if (mouse_event.LeftDown()) { bool grabber_contains_mouse = (get_hover_id() != -1); - bool control_down = mouse_event.CmdDown(); - if ((!control_down || grabber_contains_mouse) && + const bool shift_down = mouse_event.ShiftDown(); + if ((!shift_down || grabber_contains_mouse) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) return true; } @@ -285,14 +288,14 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) // don't allow dragging objects with the Sla gizmo on return true; } - else if (!control_down && + if (!control_down && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { // the gizmo got the event and took some action, no need to do // anything more here m_parent.set_as_dirty(); return true; } - else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) { + if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) { // CTRL has been pressed while already dragging -> stop current action if (mouse_event.LeftIsDown()) gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); @@ -427,7 +430,7 @@ bool GLGizmoCut3D::render_combo(const std::string& label, const std::vectortext(m_imperial_units ? _L("in") : _L("mm")); value_in = value * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); - return old_val != value; + return !is_approx(old_val, value); } -bool GLGizmoCut3D::render_slider_double_input(const std::string& label, double& value_in, int& tolerance_in) +bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in) { ImGui::AlignTextToFramePadding(); m_imgui->text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width * 0.85f); - float value = (float)value_in; + float value = value_in; if (m_imperial_units) value *= float(ObjectManipulation::mm_to_in); float old_val = value; + constexpr float UndefMinVal = -0.1f; + const BoundingBoxf3 bbox = bounding_box(); float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0); - float min_size = 1.f; + float min_size = value_in < 0.f ? UndefMinVal : 2.f; if (m_imperial_units) { mean_size *= float(ObjectManipulation::mm_to_in); min_size *= float(ObjectManipulation::mm_to_in); } - std::string format = m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); + std::string format = value_in < 0.f ? UndefLabel : + m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); m_imgui->slider_float(("##" + label).c_str(), &value, min_size, mean_size, format.c_str()); - value_in = (double)(value) * (m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); + value_in = value * float(m_imperial_units ? ObjectManipulation::in_to_mm : 1.0); ImGui::SameLine(m_label_width + m_control_width + 3); ImGui::PushItemWidth(m_control_width * 0.3f); - float old_tolerance, tolerance = old_tolerance = (float)tolerance_in; - m_imgui->slider_float(("##tolerance_" + label).c_str(), &tolerance, 1.f, 20.f, "%.f %%", 1.f, true, _L("Tolerance")); - tolerance_in = (int)tolerance; + float old_tolerance, tolerance = old_tolerance = tolerance_in * 100.f; + std::string format_t = tolerance_in < 0.f ? UndefLabel : "%.f %%"; + float min_tolerance = tolerance_in < 0.f ? UndefMinVal : 0.f; - return old_val != value || old_tolerance != tolerance; + m_imgui->slider_float(("##tolerance_" + label).c_str(), &tolerance, min_tolerance, 20.f, format_t.c_str(), 1.f, true, _L("Tolerance")); + tolerance_in = tolerance * 0.01f; + + return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance); } void GLGizmoCut3D::render_move_center_input(int axis) @@ -772,15 +781,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 ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString 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(ctrl + _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(_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"))); return true; } @@ -1214,7 +1224,7 @@ bool GLGizmoCut3D::update_bb() on_unregister_raycasters_for_picking(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { - m_selected.clear(); + clear_selection(); m_selected.resize(selection->model_object()->cut_connectors.size(), false); } @@ -1235,6 +1245,8 @@ void GLGizmoCut3D::init_picking_models() m_sphere.model.init_from(its); m_sphere.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } + if (m_shapes.empty()) + init_connector_shapes(); } void GLGizmoCut3D::init_rendering_items() @@ -1335,6 +1347,7 @@ void GLGizmoCut3D::unselect_all_connectors() { std::fill(m_selected.begin(), m_selected.end(), false); m_selected_count = 0; + validate_connector_settings(); } void GLGizmoCut3D::select_all_connectors() @@ -1361,6 +1374,8 @@ void GLGizmoCut3D::apply_selected_connectors(std::function app for (size_t idx = 0; idx < m_selected.size(); idx++) if (m_selected[idx]) apply_fn(idx); + + update_raycasters_for_picking_transform(); } void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) @@ -1399,15 +1414,19 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) - apply_selected_connectors([this, &connectors](size_t idx) { - connectors[idx].height = float(m_connector_depth_ratio); - connectors[idx].height_tolerance = 0.01f * float(m_connector_depth_ratio_tolerance); + apply_selected_connectors([this, &connectors](size_t idx) { + if (m_connector_depth_ratio > 0) + connectors[idx].height = m_connector_depth_ratio; + if (m_connector_depth_ratio_tolerance >= 0) + connectors[idx].height_tolerance = m_connector_depth_ratio_tolerance; }); if (render_slider_double_input(_u8L("Size"), m_connector_size, m_connector_size_tolerance)) - apply_selected_connectors([this, &connectors](size_t idx) { - connectors[idx].radius = float(m_connector_size * 0.5); - connectors[idx].radius_tolerance = 0.01f * float(m_connector_size_tolerance); + apply_selected_connectors([this, &connectors](size_t idx) { + if (m_connector_size > 0) + connectors[idx].radius = 0.5f * m_connector_size; + if (m_connector_size_tolerance >= 0) + connectors[idx].radius_tolerance = m_connector_size_tolerance; }); ImGui::Separator(); @@ -1529,26 +1548,84 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) m_imgui->disabled_end(); } +void GLGizmoCut3D::validate_connector_settings() +{ + if (m_connector_depth_ratio < 0.f) + m_connector_depth_ratio = 3.f; + if (m_connector_depth_ratio_tolerance < 0.f) + m_connector_depth_ratio_tolerance = 0.1f; + if (m_connector_size < 0.f) + m_connector_size = 2.5f; + if (m_connector_size_tolerance < 0.f) + m_connector_size_tolerance = 0.f; + + if (m_connector_type == CutConnectorType::Undef) + m_connector_type = CutConnectorType::Plug; + if (m_connector_style == size_t(CutConnectorStyle::Undef)) + m_connector_style = size_t(CutConnectorStyle::Prizm); + if (m_connector_shape_id == size_t(CutConnectorShape::Undef)) + m_connector_shape_id = size_t(CutConnectorShape::Circle); +} + void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) { m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - m_label_width = m_imgui->get_style_scaling() * 100.0f; - m_control_width = m_imgui->get_style_scaling() * 150.0f; + m_label_width = m_imgui->get_font_size() * 6.f; + m_control_width = m_imgui->get_font_size() * 9.f; - if (m_selected_count == 1) + if (m_connectors_editing && m_selected_count > 0) { + float depth_ratio { UndefFloat }; + float depth_ratio_tolerance { UndefFloat }; + float radius { UndefFloat }; + float radius_tolerance { UndefFloat }; + CutConnectorType type { CutConnectorType::Undef }; + CutConnectorStyle style { CutConnectorStyle::Undef }; + CutConnectorShape shape { CutConnectorShape::Undef }; + + bool is_init = false; for (size_t idx = 0; idx < m_selected.size(); idx++) if (m_selected[idx]) { - auto&connector = connectors[idx]; - m_connector_depth_ratio = connector.height; - m_connector_depth_ratio_tolerance = 100 * connector.height_tolerance; - m_connector_size = 2. * connector.radius; - m_connector_size_tolerance = 100 * connector.radius_tolerance; - m_connector_type = connector.attribs.type; - m_connector_style = size_t(connector.attribs.style); - m_connector_shape_id = size_t(connector.attribs.shape); + const CutConnector& connector = connectors[idx]; + if (!is_init) { + depth_ratio = connector.height; + depth_ratio_tolerance = connector.height_tolerance; + radius = connector.radius; + radius_tolerance = connector.radius_tolerance; + type = connector.attribs.type; + style = connector.attribs.style; + shape = connector.attribs.shape; - break; + if (m_selected_count == 1) + break; + is_init = true; + } + else { + if (!is_approx(depth_ratio, connector.height)) + depth_ratio = UndefFloat; + if (!is_approx(depth_ratio_tolerance, connector.height_tolerance)) + depth_ratio_tolerance = UndefFloat; + if (!is_approx(radius,connector.radius)) + radius = UndefFloat; + if (!is_approx(radius_tolerance, connector.radius_tolerance)) + radius_tolerance = UndefFloat; + + if (type != connector.attribs.type) + type = CutConnectorType::Undef; + if (style != connector.attribs.style) + style = CutConnectorStyle::Undef; + if (shape != connector.attribs.shape) + shape = CutConnectorShape::Undef; + } } + + m_connector_depth_ratio = depth_ratio; + m_connector_depth_ratio_tolerance = depth_ratio_tolerance; + m_connector_size = 2.f * radius; + m_connector_size_tolerance = radius_tolerance; + m_connector_type = type; + m_connector_style = size_t(style); + m_connector_shape_id = size_t(shape); + } } void GLGizmoCut3D::render_input_window_warning() const @@ -1621,7 +1698,7 @@ void GLGizmoCut3D::render_connectors() const CutConnectors& connectors = mo->cut_connectors; if (connectors.size() != m_selected.size()) { // #ysFIXME - m_selected.clear(); + clear_selection(); m_selected.resize(connectors.size(), false); } @@ -1699,7 +1776,7 @@ bool GLGizmoCut3D::can_perform_cut() const void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, const bool has_connectors, bool &create_dowels_as_separate_object) { if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { - m_selected.clear(); + clear_selection(); for (CutConnector&connector : mo->cut_connectors) { connector.rotation_m = m_rotation_m; @@ -1810,21 +1887,34 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair return false; } +void GLGizmoCut3D::clear_selection() +{ + m_selected.clear(); + m_selected_count = 0; +} + void GLGizmoCut3D::reset_connectors() { m_c->selection_info()->model_object()->cut_connectors.clear(); update_model_object(); - m_selected.clear(); + clear_selection(); +} + +void GLGizmoCut3D::init_connector_shapes() +{ + for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug}) + for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prizm}) + for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { + const CutConnectorAttributes attribs = { type, style, shape }; + const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); + m_shapes[attribs].model.init_from(its); + m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } } void GLGizmoCut3D::update_connector_shape() { CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; - if (m_shapes.find(attribs) == m_shapes.end()) { - const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); - m_shapes[attribs].model.init_from(its); - m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));; - } const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); m_connector_mesh.clear(); @@ -1913,15 +2003,16 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p if (m_connectors_editing) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); + unselect_all_connectors(); connectors.emplace_back(hit, m_rotation_m, - float(m_connector_size) * 0.5f, float(m_connector_depth_ratio), - float(m_connector_size_tolerance) * 0.01f, float(m_connector_depth_ratio_tolerance) * 0.01f, + m_connector_size * 0.5f, m_connector_depth_ratio, + m_connector_size_tolerance, m_connector_depth_ratio_tolerance, CutConnectorAttributes( CutConnectorType(m_connector_type), CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id))); - unselect_all_connectors(); m_selected.push_back(true); + m_selected_count = 1; assert(m_selected.size() == connectors.size()); update_model_object(); m_parent.set_as_dirty(); @@ -1951,6 +2042,7 @@ bool GLGizmoCut3D::delete_selected_connectors(CutConnectors& connectors) // remove selections m_selected.erase(std::remove_if(m_selected.begin(), m_selected.end(), [](const auto& selected) { return selected; }), m_selected.end()); + m_selected_count = 0; assert(m_selected.size() == connectors.size()); update_model_object(); @@ -1967,13 +2059,13 @@ void GLGizmoCut3D::select_connector(int idx, bool select) --m_selected_count; } -bool GLGizmoCut3D::is_selection_changed(bool alt_down, bool control_down) +bool GLGizmoCut3D::is_selection_changed(bool alt_down, bool shift_down) { if (m_hover_id >= m_connectors_group_id) { if (alt_down) select_connector(m_hover_id - m_connectors_group_id, false); else { - if (!control_down) + if (!shift_down) unselect_all_connectors(); select_connector(m_hover_id - m_connectors_group_id, true); } @@ -2026,15 +2118,18 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); } else - // If there is no selection and no hovering, add new point - if (m_hover_id == -1 && !control_down && !alt_down) - if (!add_connector(connectors, mouse_position)) - unselect_all_connectors(); + // If there is no selection and no hovering, add new point + if (m_hover_id == -1 && !shift_down && !alt_down) + if (!add_connector(connectors, mouse_position)) + m_ldown_mouse_position = mouse_position; return true; } - if (action == SLAGizmoEventType::LeftUp && !shift_down) - return is_selection_changed(alt_down, control_down); + if (action == SLAGizmoEventType::LeftUp) { + if ((m_ldown_mouse_position - mouse_position).norm() < 5.) + unselect_all_connectors(); + return is_selection_changed(alt_down, shift_down); + } // left up with selection rectangle - select points inside the rectangle: if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 355c9230e..c3916e4a8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -55,6 +55,8 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_line_beg{ Vec3d::Zero() }; Vec3d m_line_end{ Vec3d::Zero() }; + Vec2d m_ldown_mouse_position{ Vec2d::Zero() }; + GLModel m_plane; GLModel m_grabber_connection; GLModel m_cut_line; @@ -82,11 +84,11 @@ class GLGizmoCut3D : public GLGizmoBase bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; - double m_connector_depth_ratio{ 3.0 }; - double m_connector_size{ 2.5 }; + float m_connector_depth_ratio{ 3.f }; + float m_connector_size{ 2.5f }; - int m_connector_depth_ratio_tolerance{ 10 }; - int m_connector_size_tolerance{ 0 }; + float m_connector_depth_ratio_tolerance{ 0.1f }; + float m_connector_size_tolerance{ 0.f }; float m_label_width{ 150.0 }; float m_control_width{ 200.0 }; @@ -194,7 +196,7 @@ protected: bool add_connector(CutConnectors&connectors, const Vec2d&mouse_position); bool delete_selected_connectors(CutConnectors&connectors); void select_connector(int idx, bool select); - bool is_selection_changed(bool alt_down, bool control_down); + bool is_selection_changed(bool alt_down, bool shift_down); void process_selection_rectangle(CutConnectors &connectors); virtual void on_register_raycasters_for_picking() override; @@ -214,7 +216,7 @@ private: void set_center(const Vec3d& center); bool render_combo(const std::string& label, const std::vector& lines, size_t& selection_idx); bool render_double_input(const std::string& label, double& value_in); - bool render_slider_double_input(const std::string& label, double& value_in, int& tolerance_in); + bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in); void render_move_center_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; @@ -240,8 +242,11 @@ private: void init_picking_models(); void init_rendering_items(); void render_clipper_cut(); + void clear_selection(); void reset_connectors(); + void init_connector_shapes(); void update_connector_shape(); + void validate_connector_settings(); void update_model_object(); bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); }; From 85af9b93f1e886ea0f80d890d97ac967da772cb9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 Oct 2022 16:26:19 +0200 Subject: [PATCH 81/97] Cut: Fixes and improvements for object's context menu * Disable or delete some menu items, which are inappropriate for cut objects * For cut objects added menu item "Invalidate cut info" to disconnect related cut parts of initial object * If just one part is kept after cut performance, than don't apply a cut info for this object. + CutGizmo: Fixed selection of the mode An object has connectors -> Connectors mode An object doesn't has connectors -> CutPlane mode --- src/libslic3r/Model.cpp | 8 +++++- src/libslic3r/ObjectID.hpp | 2 +- src/slic3r/GUI/GUI_Factories.cpp | 22 +++++++++++++++++ src/slic3r/GUI/GUI_Factories.hpp | 1 + src/slic3r/GUI/GUI_ObjectList.cpp | 34 +++++++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 13 +++++----- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 1 + src/slic3r/GUI/Plater.cpp | 18 +++++++------- 10 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 270ccb1be..597152c06 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1261,7 +1261,9 @@ void ModelObject::invalidate_cut() { for (ModelObject* obj : m_model->objects) if (obj != this && obj->cut_id.is_equal(this->cut_id)) - obj->cut_id.ivalidate(); + obj->cut_id.invalidate(); + // invalidate own cut_id + this->cut_id.invalidate(); } void ModelObject::synchronize_model_after_cut() @@ -1276,6 +1278,10 @@ void ModelObject::synchronize_model_after_cut() void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes) { + // we don't save cut information, if result will not contains all parts of initial object + if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || !attributes.has(ModelObjectCutAttribute::KeepLower)) + return; + if (cut_id.id().invalid()) cut_id.init(); { diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 452f620c4..f907b03e1 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -161,7 +161,7 @@ public: return *this; } - void ivalidate() { + void invalidate() { set_invalid_id(); m_check_sum = 1; m_connectors_cnt = 0; diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 3cfc55adc..5164c691e 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -685,6 +685,24 @@ wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu) return menu_item_printable; } +void MenuFactory::append_menu_item_invalidate_cut_info(wxMenu* menu) +{ + const wxString menu_name = _L("Invalidate cut info"); + + bool is_cut = obj_list()->has_selected_cut_object(); + + auto menu_item_id = menu->FindItem(menu_name); + if (menu_item_id != wxNOT_FOUND) { + // Delete old menu item if selected object isn't cut + if (!is_cut) + menu->Destroy(menu_item_id); + } + else if (is_cut) + append_menu_item(menu, wxID_ANY, menu_name, "", + [](wxCommandEvent&) { obj_list()->invalidate_cut_info_for_selection(); }, "", menu, + []() { return true; }, m_parent); +} + void MenuFactory::append_menu_items_osx(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _L("Rename"), "", @@ -821,6 +839,8 @@ void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* ModelObjectPtrs objects; for (int obj_idx : obj_idxs) { ModelObject* object = obj_list()->object(obj_idx); + if (object->is_cut()) + return false; if (vol_idxs.empty()) { for (ModelVolume* volume : object->volumes) if (volume_respects_conversion(volume, conver_type)) @@ -1021,6 +1041,7 @@ wxMenu* MenuFactory::object_menu() append_menu_item_settings(&m_object_menu); append_menu_item_change_extruder(&m_object_menu); update_menu_items_instance_manipulation(mtObjectFFF); + append_menu_item_invalidate_cut_info(&m_object_menu); return &m_object_menu; } @@ -1030,6 +1051,7 @@ wxMenu* MenuFactory::sla_object_menu() append_menu_items_convert_unit(&m_sla_object_menu, 11); append_menu_item_settings(&m_sla_object_menu); update_menu_items_instance_manipulation(mtObjectSLA); + append_menu_item_invalidate_cut_info(&m_sla_object_menu); return &m_sla_object_menu; } diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index bbbc00d42..7190edb64 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -89,6 +89,7 @@ private: wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); wxMenuItem* append_menu_item_printable(wxMenu* menu); + void append_menu_item_invalidate_cut_info(wxMenu *menu); void append_menu_items_osx(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); wxMenuItem* append_menu_item_simplify(wxMenu* menu); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d3f1d1bf2..cced147f8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2443,9 +2443,41 @@ bool ObjectList::can_split_instances() return selection.is_multiple_full_instance() || selection.is_single_full_instance(); } +bool ObjectList::has_selected_cut_object() const +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.IsEmpty()) + return false; + + for (wxDataViewItem item : sels) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0 && object(obj_idx)->is_cut()) + return true; + } + + return false; +} + +void ObjectList::invalidate_cut_info_for_selection() +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.IsEmpty()) + return; + + for (wxDataViewItem item : sels) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0 && object(obj_idx)->is_cut()) + object(obj_idx)->invalidate_cut(); + } + + update_lock_icons_for_model(); +} + bool ObjectList::can_merge_to_multipart_object() const { - if (printer_technology() == ptSLA) + if (printer_technology() == ptSLA || has_selected_cut_object()) return false; wxDataViewItemArray sels; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index cb498f87e..3dd02ab8d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -277,6 +277,8 @@ public: bool is_splittable(bool to_objects); bool selected_instances_of_same_object(); bool can_split_instances(); + bool has_selected_cut_object() const; + void invalidate_cut_info_for_selection(); bool can_merge_to_multipart_object() const; bool can_merge_to_single_object() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index c90211d42..d75132542 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1226,6 +1226,7 @@ bool GLGizmoCut3D::update_bb() if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { clear_selection(); m_selected.resize(selection->model_object()->cut_connectors.size(), false); + m_connectors_editing = !m_selected.empty(); } return true; @@ -1333,12 +1334,12 @@ void GLGizmoCut3D::adjust_window_position(float x, float y, float bottom_limit) ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if (last_h != win_h || last_y != y) { + if (!is_approx(last_h, win_h) || !is_approx(last_y, y)) { // ask canvas for another frame to render the window in the correct position m_imgui->set_requires_extra_frame(); - if (last_h != win_h) + if (!is_approx(last_h, win_h)) last_h = win_h; - if (last_y != y) + if (!is_approx(last_y, y)) last_y = y; } } @@ -1773,9 +1774,9 @@ bool GLGizmoCut3D::can_perform_cut() const return tbb.contains(m_plane_center); } -void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, const bool has_connectors, bool &create_dowels_as_separate_object) +void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object) { - if (has_connectors && m_connector_mode == CutConnectorMode::Manual) { + if (m_connector_mode == CutConnectorMode::Manual) { clear_selection(); for (CutConnector&connector : mo->cut_connectors) { @@ -1824,7 +1825,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); // update connectors pos as offset of its center before cut performing - apply_connectors_in_model(mo, has_connectors, create_dowels_as_separate_object); + apply_connectors_in_model(mo, create_dowels_as_separate_object); } plater->cut(object_idx, instance_idx, assemble_transform(cut_center_offset) * m_rotation_m, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index c3916e4a8..21d7c64f8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -225,7 +225,7 @@ private: void render_connectors(); bool can_perform_cut() const; - void apply_connectors_in_model(ModelObject* mo, const bool has_connectors, bool &create_dowels_as_separate_object); + void apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object); bool cut_line_processing() const; void discard_cut_line_processing(); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 0475fe395..e2fca5fae 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -1807,6 +1807,7 @@ void ObjectDataViewModel::UpdateLockIcon(const wxDataViewItem& item, bool has_lo for (const wxDataViewItem& child : children) UpdateLockIcon(child, has_lock); } + ItemChanged(item); } } // namespace GUI diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 12a654175..7c7fbc989 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4786,7 +4786,8 @@ bool Plater::priv::can_split(bool to_objects) const bool Plater::priv::can_scale_to_print_volume() const { const BuildVolume::Type type = this->bed.build_volume().type(); - return !view3D->get_canvas3d()->get_selection().is_empty() && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle); + return !sidebar->obj_list()->has_selected_cut_object() && + !view3D->get_canvas3d()->get_selection().is_empty() && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle); } bool Plater::priv::layers_height_allowed() const @@ -4801,16 +4802,19 @@ bool Plater::priv::layers_height_allowed() const bool Plater::priv::can_mirror() const { - return get_selection().is_from_single_instance(); + return !sidebar->obj_list()->has_selected_cut_object() && get_selection().is_from_single_instance(); } bool Plater::priv::can_replace_with_stl() const { - return get_selection().get_volume_idxs().size() == 1; + return !sidebar->obj_list()->has_selected_cut_object() && get_selection().get_volume_idxs().size() == 1; } bool Plater::priv::can_reload_from_disk() const { + if (sidebar->obj_list()->has_selected_cut_object()) + return false; + #if ENABLE_RELOAD_FROM_DISK_REWORK // collect selected reloadable ModelVolumes std::vector> selected_volumes = reloadable_volumes(model, get_selection()); @@ -4939,9 +4943,7 @@ bool Plater::priv::can_increase_instances() const || q->canvas3D()->get_gizmos_manager().is_in_editing_mode()) return false; - int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) - && !model.objects[obj_idx]->is_cut(); + return !sidebar->obj_list()->has_selected_cut_object(); } bool Plater::priv::can_decrease_instances() const @@ -4950,9 +4952,7 @@ bool Plater::priv::can_decrease_instances() const || q->canvas3D()->get_gizmos_manager().is_in_editing_mode()) return false; - int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1) - && !model.objects[obj_idx]->is_cut(); + return !sidebar->obj_list()->has_selected_cut_object(); } bool Plater::priv::can_split_to_objects() const From 64c57faf8faf0a5b9375f75f36db07a5834ebb30 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Oct 2022 10:46:33 +0200 Subject: [PATCH 82/97] Cut Improvements: Fixed Undo/Redo for cut performance + ObjectList: Fixed items update after Invalidate cut information + CutGizmo: Fixed wrong mode selection after delete object and that add new --- src/libslic3r/Model.cpp | 6 ++-- src/libslic3r/Model.hpp | 18 ++++++++---- src/libslic3r/ObjectID.hpp | 9 +++++- src/slic3r/GUI/GUI_ObjectList.cpp | 42 ++++++++++++++++++++-------- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 19 +++++++------ src/slic3r/GUI/Plater.cpp | 11 ++++---- 7 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 597152c06..54d4fcab7 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1259,11 +1259,9 @@ void ModelObject::apply_cut_connectors(const std::string& new_name) void ModelObject::invalidate_cut() { - for (ModelObject* obj : m_model->objects) - if (obj != this && obj->cut_id.is_equal(this->cut_id)) - obj->cut_id.invalidate(); - // invalidate own cut_id this->cut_id.invalidate(); + for (ModelVolume* volume : this->volumes) + volume->invalidate_cut_info(); } void ModelObject::synchronize_model_after_cut() diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0d54ebc1f..ee611b006 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -444,7 +444,7 @@ public: size_t parts_count() const; static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(const std::string& name); - // invalidate cut state for this and related objects from the whole model + // invalidate cut state for this object and its connectors/volumes void invalidate_cut(); void synchronize_model_after_cut(); void apply_cut_attributes(ModelObjectCutAttributes attributes); @@ -742,10 +742,16 @@ public: {} void set_processed() { is_processed = true; } + void invalidate() { is_connector = false; } + + template inline void serialize(Archive& ar) { + ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); + } }; CutInfo cut_info; bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } + void invalidate_cut_info() { cut_info.invalidate(); } // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } @@ -954,7 +960,8 @@ private: ObjectBase(other), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets) + supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), + cut_info(other.cut_info) { assert(this->id().valid()); assert(this->config.id().valid()); @@ -974,7 +981,8 @@ private: } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : - name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation) + name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation), + cut_info(other.cut_info) { assert(this->id().valid()); assert(this->config.id().valid()); @@ -1016,7 +1024,7 @@ private: } template void load(Archive &ar) { bool has_convex_hull; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); cereal::load_by_value(ar, supported_facets); cereal::load_by_value(ar, seam_facets); cereal::load_by_value(ar, mmu_segmentation_facets); @@ -1032,7 +1040,7 @@ private: } template void save(Archive &ar) const { bool has_convex_hull = m_convex_hull.get() != nullptr; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, mmu_segmentation_facets); diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index f907b03e1..6be19c88f 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -156,7 +156,7 @@ public: this->m_check_sum = rhs.check_sum(); this->m_connectors_cnt = rhs.connectors_cnt() ; } - CutObjectBase operator=(const CutObjectBase& other) { + CutObjectBase& operator=(const CutObjectBase& other) { this->copy(other); return *this; } @@ -179,6 +179,13 @@ public: size_t connectors_cnt() const { return m_connectors_cnt; } void increase_connectors_cnt(size_t connectors_cnt) { m_connectors_cnt += connectors_cnt; } + +private: + friend class cereal::access; + template void serialize(Archive& ar) { + ar(cereal::base_class(this)); + ar(m_check_sum, m_connectors_cnt); + } }; // Unique object / instance ID for the wipe tower. diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index cced147f8..663290a8b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2458,19 +2458,37 @@ bool ObjectList::has_selected_cut_object() const return false; } - void ObjectList::invalidate_cut_info_for_selection() { - wxDataViewItemArray sels; - GetSelections(sels); - if (sels.IsEmpty()) + const wxDataViewItem item = GetSelection(); + if (item) { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx >= 0) + invalidate_cut_info_for_object(size_t(obj_idx)); + } +} + +void ObjectList::invalidate_cut_info_for_object(size_t obj_idx) +{ + ModelObject* init_obj = object(int(obj_idx)); + if (!init_obj->is_cut()) return; - for (wxDataViewItem item : sels) { - const int obj_idx = m_objects_model->GetObjectIdByItem(item); - if (obj_idx >= 0 && object(obj_idx)->is_cut()) - object(obj_idx)->invalidate_cut(); - } + take_snapshot(_L("Invalidate cut info")); + + auto invalidate_cut = [this](size_t obj_idx) { + object(int(obj_idx))->invalidate_cut(); + update_info_items(obj_idx); + add_volumes_to_object_in_list(obj_idx); + }; + + // invalidate cut for related objects (which have the same cut_id) + for (size_t idx = 0; idx < m_objects->size(); idx++) + if (ModelObject* obj = object(idx); obj != init_obj && obj->cut_id.is_equal(init_obj->cut_id)) + invalidate_cut(idx); + + // invalidate own cut information + invalidate_cut(size_t(obj_idx)); update_lock_icons_for_model(); } @@ -2970,8 +2988,6 @@ void ObjectList::update_lock_icons_for_model() bool ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx) { -// take_snapshot(_(L("Delete Selected Item"))); // #ysFIXME - delete this redundant snapshot after test - if (type & (itObject | itVolume | itInstance)) { if (type & itObject) { bool was_cut = object(obj_idx)->is_cut(); @@ -2983,7 +2999,7 @@ bool ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i } return false; } - else if (del_subobject_from_object(obj_idx, sub_obj_idx, type)) { + if (del_subobject_from_object(obj_idx, sub_obj_idx, type)) { type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) : delete_instance_from_list(obj_idx, sub_obj_idx); return true; @@ -3189,6 +3205,8 @@ void ObjectList::remove() if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes) continue; parent = delete_item(item); + if (parent == item && m_objects_model->GetItemType(item) & itObject) // Object wasn't deleted + break; } } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 3dd02ab8d..eb718b48d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -279,6 +279,7 @@ public: bool can_split_instances(); bool has_selected_cut_object() const; void invalidate_cut_info_for_selection(); + void invalidate_cut_info_for_object(size_t obj_idx); bool can_merge_to_multipart_object() const; bool can_merge_to_single_object() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index d75132542..459c6390c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1223,11 +1223,10 @@ bool GLGizmoCut3D::update_bb() on_unregister_raycasters_for_picking(); - if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) { - clear_selection(); + clear_selection(); + if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) m_selected.resize(selection->model_object()->cut_connectors.size(), false); - m_connectors_editing = !m_selected.empty(); - } + m_connectors_editing = !m_selected.empty(); return true; } @@ -1811,6 +1810,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) if (!mo) return; + // deactivate CutGizmo and than perform a cut + m_parent.reset_all_gizmos(); + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z(); const double object_cut_z = m_plane_center.z() - sla_shift_z; @@ -1820,13 +1822,12 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) cut_center_offset[Z] -= sla_shift_z; if (0.0 < object_cut_z && can_perform_cut()) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); + bool create_dowels_as_separate_object = false; const bool has_connectors = !mo->cut_connectors.empty(); - { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); - // update connectors pos as offset of its center before cut performing - apply_connectors_in_model(mo, create_dowels_as_separate_object); - } + // update connectors pos as offset of its center before cut performing + apply_connectors_in_model(mo, create_dowels_as_separate_object); plater->cut(object_idx, instance_idx, assemble_transform(cut_center_offset) * m_rotation_m, only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7c7fbc989..2051192ee 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3020,8 +3020,6 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx) dialog.SetButtonLabel(wxID_YES, _L("Delete object")); if (dialog.ShowModal() == wxID_CANCEL) return false; - // unmark all related CutParts of initial object - obj->invalidate_cut(); } wxString snapshot_label = _L("Delete Object"); @@ -3029,6 +3027,10 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx) snapshot_label += ": " + wxString::FromUTF8(obj->name.c_str()); Plater::TakeSnapshot snapshot(q, snapshot_label); m_worker.cancel_all(); + + if (obj->is_cut()) + sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx); + model.delete_object(obj_idx); update(); object_list_changed(); @@ -5940,9 +5942,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - this->suppress_snapshots(); wxBusyCursor wait; - + const auto new_objects = object->cut(instance_idx, cut_matrix, attributes); model().delete_object(obj_idx); @@ -5951,8 +5952,6 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat // suppress to call selection update for Object List to avoid call of early Gizmos on/off update p->load_model_objects(new_objects, false, false); - this->allow_snapshots(); - // now process all updates of the 3d scene update(); // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), From 13e4e85e3d2b53382915909c46b2340abd88aac1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Oct 2022 15:23:31 +0200 Subject: [PATCH 83/97] Cut bug fixing: Model: fixed looks_like_imperial_units(). This function respects to cut status now. To be detected as looks_like_imperial_units, all parts of cat object have to be looks_like_imperial_units(). ObjectList: Fixed update after adding/deleting of the modifiers for cut object GUI_Factories: Fixed a place of the "Invalidate cut info" item in object menu --- src/libslic3r/Model.cpp | 19 ++++++++++++++++--- src/slic3r/GUI/GUI_Factories.cpp | 11 ++++------- src/slic3r/GUI/GUI_ObjectList.cpp | 17 +++++++++++++---- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 54d4fcab7..56f3af2d1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -464,12 +464,25 @@ static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3; bool Model::looks_like_imperial_units() const { - if (this->objects.size() == 0) + if (this->objects.empty()) return false; for (ModelObject* obj : this->objects) - if (obj->get_object_stl_stats().volume < volume_threshold_inches) - return true; + if (obj->get_object_stl_stats().volume < volume_threshold_inches) { + if (!obj->is_cut()) + return true; + bool all_cut_parts_look_like_imperial_units = true; + for (ModelObject* obj_other : this->objects) { + if (obj_other == obj) + continue; + if (obj_other->cut_id.is_equal(obj->cut_id) && obj_other->get_object_stl_stats().volume >= volume_threshold_inches) { + all_cut_parts_look_like_imperial_units = false; + break; + } + } + if (all_cut_parts_look_like_imperial_units) + return true; + } return false; } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 5164c691e..ff9113c68 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -689,15 +689,12 @@ void MenuFactory::append_menu_item_invalidate_cut_info(wxMenu* menu) { const wxString menu_name = _L("Invalidate cut info"); - bool is_cut = obj_list()->has_selected_cut_object(); - auto menu_item_id = menu->FindItem(menu_name); - if (menu_item_id != wxNOT_FOUND) { + if (menu_item_id != wxNOT_FOUND) // Delete old menu item if selected object isn't cut - if (!is_cut) - menu->Destroy(menu_item_id); - } - else if (is_cut) + menu->Destroy(menu_item_id); + + if (obj_list()->has_selected_cut_object()) append_menu_item(menu, wxID_ANY, menu_name, "", [](wxCommandEvent&) { obj_list()->invalidate_cut_info_for_selection(); }, "", menu, []() { return true; }, m_parent); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 663290a8b..d9cc1b1ca 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1728,6 +1728,9 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // update printable state on canvas wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); + if (model_object.is_cut()) + update_info_items(obj_idx); + selection_changed(); } @@ -1858,11 +1861,13 @@ bool ObjectList::del_subobject_item(wxDataViewItem& item) // If last volume item with warning was deleted, unmark object item if (type & itVolume) { + add_volumes_to_object_in_list(obj_idx); const std::string& icon_name = get_warning_icon_name(object(obj_idx)->get_object_stl_stats()); m_objects_model->UpdateWarningIcon(parent, icon_name); } + else + m_objects_model->Delete(item); - m_objects_model->Delete(item); update_info_items(obj_idx); return true; @@ -2677,7 +2682,8 @@ void ObjectList::part_selection_changed() volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &object->volumes[volume_id]->config; update_and_show_manipulations = true; - enable_manipulation = !(object->is_cut() && object->volumes[volume_id]->is_cut_connector()); + const ModelVolume* volume = object->volumes[volume_id]; + enable_manipulation = !(object->is_cut() && (volume->is_cut_connector() || volume->is_model_part())); } else if (type & itInstance) { og_name = _L("Instance manipulation"); @@ -2874,8 +2880,11 @@ static bool can_add_volumes_to_object(const ModelObject* object) if (can && object->is_cut()) { int no_connectors_cnt = 0; for (const ModelVolume* v : object->volumes) - if (!v->is_cut_connector()) + if (!v->is_cut_connector()) { + if (!v->is_model_part()) + return true; no_connectors_cnt++; + } can = no_connectors_cnt > 1; } @@ -3031,7 +3040,7 @@ bool ObjectList::delete_from_model_and_list(const std::vector& it if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) continue; if (item->type&itVolume) { - m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); + add_volumes_to_object_in_list(item->obj_idx); ModelObject* obj = object(item->obj_idx); if (obj->volumes.size() == 1) { wxDataViewItem parent = m_objects_model->GetItemById(item->obj_idx); From 5922bf2910e824d160b9e3223f1ec4d6b128858e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Oct 2022 16:59:40 +0200 Subject: [PATCH 84/97] Cut small improvements: * Disable revert icon if cut_plane position wasn't changed * Hide CutGizmo for Simple mode. * Fixed update of bounding box after changing scale during Z axis --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 11 ++++++++++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 459c6390c..9965e4846 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -968,6 +968,11 @@ bool GLGizmoCut3D::on_is_activable() const return m_parent.get_selection().is_single_full_instance(); } +bool GLGizmoCut3D::on_is_selectable() const +{ + return wxGetApp().get_mode() != comSimple; +} + Vec3d GLGizmoCut3D::mouse_position_in_local_plane(Axis axis, const Linef3& mouse_ray) const { double half_pi = 0.5 * PI; @@ -1194,7 +1199,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* bool GLGizmoCut3D::update_bb() { const BoundingBoxf3 box = bounding_box(); - if (m_max_pos != box.max && m_min_pos != box.min) { + if (m_max_pos != box.max || m_min_pos != box.min) { m_max_pos = box.max; m_min_pos = box.min; m_bb_center = box.center(); @@ -1501,8 +1506,12 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::SameLine(m_label_width); render_move_center_input(Z); ImGui::SameLine(); + + const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && bounding_box().center() == m_plane_center; + m_imgui->disabled_begin(is_cut_plane_init); if (render_reset_button("cut_plane", _u8L("Reset cutting plane"))) reset_cut_plane(); + m_imgui->disabled_end(); if (wxGetApp().plater()->printer_technology() == ptFFF) { m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 21d7c64f8..86e14705b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -171,6 +171,7 @@ protected: CommonGizmosDataID on_get_requirements() const override; void on_set_hover_id() override; bool on_is_activable() const override; + bool on_is_selectable() const override; Vec3d mouse_position_in_local_plane(Axis axis, const Linef3&mouse_ray) const; void dragging_grabber_z(const GLGizmoBase::UpdateData &data); void dragging_grabber_xy(const GLGizmoBase::UpdateData &data); From 3a21f156c00144cf9a74ad8ac350901f9ea75352 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 Oct 2022 14:13:50 +0200 Subject: [PATCH 85/97] Cut Improvements/Bug Fixing * Context menu: Suppress "Simplify model" for cut object * CutGizmo: * Disable gizmo for dowel object * Invalidate cut plane position after update of Bounding box * Suppress Frustum style for connectors with Dowel type * Rectangle selection: Fixed processing on LeftUp * Selection on Canvas: Suppress to move NEGATIVE_VOLUME if it's a connector * Model:cut: Fixed a bug in add_cut_volume(). Cut info wasn't copied to the new volume --- src/libslic3r/Model.cpp | 1 + src/slic3r/GUI/GLCanvas3D.cpp | 3 ++- src/slic3r/GUI/GUI_Factories.cpp | 3 +++ src/slic3r/GUI/GUI_ObjectList.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 36 ++++++++++++++++++++++++---- src/slic3r/GUI/Plater.cpp | 9 +++---- 6 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 56f3af2d1..aa21c3ade 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1424,6 +1424,7 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV assert(vol->config.id().valid()); assert(vol->config.id() != src_volume->config.id()); vol->set_material(src_volume->material_id(), *src_volume->material()); + vol->cut_info = src_volume->cut_info; } void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2b5875815..c83e3f970 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3529,7 +3529,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) const int volume_idx = get_first_hover_volume_idx(); BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); volume_bbox.offset(1.0); - if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) { + const bool is_cut_connector_selected = m_selection.is_any_connector(); + if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) { m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; // The dragging operation is initiated. m_mouse.drag.move_volume_idx = volume_idx; diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index ff9113c68..6ffe19218 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1080,6 +1080,9 @@ wxMenu* MenuFactory::multi_selection_menu() wxDataViewItemArray sels; obj_list()->GetSelections(sels); + if (sels.IsEmpty()) + return nullptr; + for (const wxDataViewItem& item : sels) if (!(list_model()->GetItemType(item) & (itVolume | itObject | itInstance))) // show this menu only for Objects(Instances mixed with Objects)/Volumes selection diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d9cc1b1ca..ac9602985 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3806,6 +3806,8 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); + if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) + selection.remove_all(); if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); else diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9965e4846..793ab1e51 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -963,9 +963,30 @@ void GLGizmoCut3D::on_set_hover_id() bool GLGizmoCut3D::on_is_activable() const { + const Selection& selection = m_parent.get_selection(); + const int object_idx = selection.get_object_idx(); + if (object_idx < 0) + return false; + + bool is_dowel_object = false; + if (const ModelObject* mo = wxGetApp().plater()->model().objects[object_idx]; mo->is_cut()) { + int solid_connector_cnt = 0; + int connectors_cnt = 0; + for (const ModelVolume* volume : mo->volumes) { + if (volume->is_cut_connector()) { + connectors_cnt++; + if (volume->is_model_part()) + solid_connector_cnt++; + } + if (connectors_cnt > 1) + break; + } + is_dowel_object = connectors_cnt == 1 && solid_connector_cnt == 1; + } + // This is assumed in GLCanvas3D::do_rotate, do not change this // without updating that function too. - return m_parent.get_selection().is_single_full_instance(); + return selection.is_single_full_instance() && !is_dowel_object; } bool GLGizmoCut3D::on_is_selectable() const @@ -1200,6 +1221,9 @@ bool GLGizmoCut3D::update_bb() { const BoundingBoxf3 box = bounding_box(); if (m_max_pos != box.max || m_min_pos != box.min) { + + invalidate_cut_plane(); + m_max_pos = box.max; m_min_pos = box.min; m_bb_center = box.center(); @@ -1412,8 +1436,14 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) if (type_changed) apply_selected_connectors([this, &connectors] (size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); }); + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel); + if (type_changed && m_connector_type == CutConnectorType::Dowel) { + m_connector_style = size_t(CutConnectorStyle::Prizm); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + } if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + m_imgui->disabled_end(); if (render_combo(_u8L("Shape"), m_connector_shapes, m_connector_shape_id)) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); @@ -1850,8 +1880,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) else { // the object is SLA-elevated and the plane is under it. } - - invalidate_cut_plane(); } @@ -2136,7 +2164,7 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi return true; } - if (action == SLAGizmoEventType::LeftUp) { + if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle.is_dragging()) { if ((m_ldown_mouse_position - mouse_position).norm() < 5.) unselect_all_connectors(); return is_selection_changed(alt_down, shift_down); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2051192ee..15ed60a99 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2976,9 +2976,6 @@ void Plater::priv::object_list_changed() const bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() == ModelInstancePVS_Inside; sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits); - - // invalidate CutGizmo after changes in ObjectList - static_cast(q->canvas3D()->get_gizmos_manager().get_gizmo(GLGizmosManager::Cut))->invalidate_cut_plane(); } void Plater::priv::select_all() @@ -4930,8 +4927,12 @@ bool Plater::priv::can_fix_through_netfabb() const bool Plater::priv::can_simplify() const { + const int obj_idx = get_selected_object_idx(); // is object for simplification selected - if (get_selected_object_idx() < 0) return false; + // cut object can't be simplify + if (obj_idx < 0 || model.objects[obj_idx]->is_cut()) + return false; + // is already opened? if (q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::EType::Simplify) From 2880704de9060ace26b695b851af60682acc5fb6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 19 Oct 2022 17:33:58 +0200 Subject: [PATCH 86/97] Cut improvements/ bug fixing: * Wrong position of grabber is fixed * OSX specific: ObjectList: Fixed update of the info items after cut * Show info line, when Cut plane is invisible + Fixed non-Win build: added missed include --- src/libslic3r/ObjectID.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 76 +++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 44 +++++---------- src/slic3r/GUI/ObjectDataViewModel.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 7 ++- 6 files changed, 82 insertions(+), 52 deletions(-) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 6be19c88f..4f34572d2 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -2,6 +2,7 @@ #define slic3r_ObjectID_hpp_ #include +#include namespace Slic3r { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 793ab1e51..56dd56462 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -568,6 +568,42 @@ bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::s return revert; } +void GLGizmoCut3D::render_cut_plane_line() +{ + if (cut_line_processing()) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + const Camera& camera = wxGetApp().plater()->get_camera(); + + Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); + unit_dir.normalize(); + + Vec3d camera_dir = camera.get_dir_forward(); + camera_dir.normalize(); + + if (std::abs(unit_dir.dot(camera_dir)) <= 0.025) { + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); + if (shader) { + m_circle.reset(); + init_from_circle(m_circle, m_radius * 1.25); + + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; + + shader->start_using(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("width", 0.1f); + + m_circle.render(); + + shader->stop_using(); + } + } +} + void GLGizmoCut3D::render_cut_plane() { if (cut_line_processing()) @@ -986,7 +1022,7 @@ bool GLGizmoCut3D::on_is_activable() const // This is assumed in GLCanvas3D::do_rotate, do not change this // without updating that function too. - return selection.is_single_full_instance() && !is_dowel_object; + return selection.is_single_full_instance() && !is_dowel_object && !m_parent.is_layers_editing_enabled(); } bool GLGizmoCut3D::on_is_selectable() const @@ -1153,7 +1189,15 @@ void GLGizmoCut3D::on_stop_dragging() void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/) { - if (force || transformed_bounding_box(true).contains(center_pos)) { + bool can_set_center_pos = force || transformed_bounding_box(true).contains(center_pos); + if (!can_set_center_pos) { + const double old_dist = (m_bb_center - m_plane_center).norm(); + const double new_dist = (m_bb_center - center_pos).norm(); + // check if forcing is reasonable + if ( new_dist < old_dist) + can_set_center_pos = true; + } + if (can_set_center_pos) { m_plane_center = center_pos; m_center_offset = m_plane_center - m_bb_center; } @@ -1178,17 +1222,20 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* // #ysFIXME !!! BoundingBoxf3 ret; - const ModelObject* mo = m_c->selection_info()->model_object(); + const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); + if (!sel_info) + return ret; + const ModelObject* mo = sel_info->model_object(); if (!mo) return ret; - const int instance_idx = m_c->selection_info()->get_active_instance(); + const int instance_idx = sel_info->get_active_instance(); if (instance_idx < 0) return ret; const ModelInstance* mi = mo->instances[instance_idx]; const Vec3d& instance_offset = mi->get_offset(); Vec3d cut_center_offset = m_plane_center - instance_offset; - cut_center_offset[Z] -= m_c->selection_info()->get_sla_shift(); + cut_center_offset[Z] -= sel_info->get_sla_shift(); const auto move = assemble_transform(-cut_center_offset); const auto move2 = assemble_transform(m_plane_center); @@ -1330,6 +1377,8 @@ void GLGizmoCut3D::on_render() render_cut_line(); + render_cut_plane_line(); + m_selection_rectangle.render(m_parent); } @@ -1935,7 +1984,7 @@ void GLGizmoCut3D::clear_selection() void GLGizmoCut3D::reset_connectors() { m_c->selection_info()->model_object()->cut_connectors.clear(); - update_model_object(); + update_raycasters_for_picking(); clear_selection(); } @@ -1960,17 +2009,6 @@ void GLGizmoCut3D::update_connector_shape() m_connector_mesh = TriangleMesh(its); } -void GLGizmoCut3D::update_model_object() -{ - const ModelObjectPtrs& mos = wxGetApp().model().objects; - ModelObject* mo = m_c->selection_info()->model_object(); - wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); - - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - - update_raycasters_for_picking(); -} - bool GLGizmoCut3D::cut_line_processing() const { return m_line_beg != Vec3d::Zero(); @@ -2053,7 +2091,7 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p m_selected.push_back(true); m_selected_count = 1; assert(m_selected.size() == connectors.size()); - update_model_object(); + update_raycasters_for_picking(); m_parent.set_as_dirty(); } else { @@ -2084,7 +2122,7 @@ bool GLGizmoCut3D::delete_selected_connectors(CutConnectors& connectors) m_selected_count = 0; assert(m_selected.size() == connectors.size()); - update_model_object(); + update_raycasters_for_picking(); m_parent.set_as_dirty(); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 86e14705b..941d01660 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -237,6 +237,7 @@ private: void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); void render_cut_plane_grabbers(); void render_cut_line(); + void render_cut_plane_line(); void perform_cut(const Selection&selection); void set_center_pos(const Vec3d¢er_pos, bool force = false); bool update_bb(); @@ -248,7 +249,6 @@ private: void init_connector_shapes(); void update_connector_shape(); void validate_connector_settings(); - void update_model_object(); bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); }; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index e2fca5fae..50577771b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -34,6 +34,14 @@ void ObjectDataViewModelNode::init_container() #endif //__WXGTK__ } +void ObjectDataViewModelNode::invalidate_container() +{ +#ifndef __WXGTK__ + if (this->GetChildCount() == 0) + this->m_container = false; +#endif //__WXGTK__ +} + static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char WarningIcon[] = "exclamation"; @@ -724,10 +732,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) delete node_parent; ret_item = wxDataViewItem(obj_node); -#ifndef __WXGTK__ - if (obj_node->GetChildCount() == 0) - obj_node->m_container = false; -#endif //__WXGTK__ + obj_node->invalidate_container(); ItemDeleted(ret_item, wxDataViewItem(node_parent)); return ret_item; } @@ -743,10 +748,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) delete node_parent; ret_item = wxDataViewItem(obj_node); -#ifndef __WXGTK__ - if (obj_node->GetChildCount() == 0) - obj_node->m_container = false; -#endif //__WXGTK__ + obj_node->invalidate_container(); ItemDeleted(ret_item, wxDataViewItem(node_parent)); return ret_item; } @@ -768,10 +770,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) node_parent->m_volumes_cnt = 0; delete last_child_node; -#ifndef __WXGTK__ - if (node_parent->GetChildCount() == 0) - node_parent->m_container = false; -#endif //__WXGTK__ + node_parent->invalidate_container(); ItemDeleted(parent, wxDataViewItem(last_child_node)); wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); @@ -806,10 +805,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) // set m_containet to FALSE if parent has no child if (node_parent) { -#ifndef __WXGTK__ - if (node_parent->GetChildCount() == 0) - node_parent->m_container = false; -#endif //__WXGTK__ + node_parent->invalidate_container(); ret_item = parent; } @@ -851,10 +847,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par parent_node->set_printable_icon(last_inst_printable); ItemDeleted(parent_item, inst_root_item); ItemChanged(parent_item); -#ifndef __WXGTK__ - if (parent_node->GetChildCount() == 0) - parent_node->m_container = false; -#endif //__WXGTK__ + parent_node->invalidate_container(); } // update object_node printable property @@ -899,10 +892,7 @@ void ObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) ItemDeleted(parent, item); } - // set m_containet to FALSE if parent has no child -#ifndef __WXGTK__ - root->m_container = false; -#endif //__WXGTK__ + root->invalidate_container(); } void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) @@ -932,11 +922,7 @@ void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) ItemDeleted(parent, item); } root->m_volumes_cnt = 0; - - // set m_containet to FALSE if parent has no child -#ifndef __WXGTK__ - root->m_container = false; -#endif //__WXGTK__ + root->invalidate_container(); } void ObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent) diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 55dbaafe2..6f2d1519c 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -128,7 +128,9 @@ public: } void init_container(); - bool IsContainer() const + void invalidate_container(); + + bool IsContainer() const { return m_container; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 15ed60a99..d3bd704d2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4470,7 +4470,10 @@ void Plater::priv::on_action_split_volumes(SimpleEvent&) void Plater::priv::on_action_layersediting(SimpleEvent&) { - view3D->enable_layers_editing(!view3D->is_layers_editing_enabled()); + const bool enable_layersediting = !view3D->is_layers_editing_enabled(); + view3D->enable_layers_editing(enable_layersediting); + if (enable_layersediting) + view3D->get_canvas3d()->reset_all_gizmos(); notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled()); } @@ -4513,7 +4516,7 @@ void Plater::priv::on_right_click(RBtnEvent& evt) selection.is_single_full_object() || selection.is_multiple_full_instance(); #if ENABLE_WORLD_COORDINATE - const bool is_part = selection.is_single_volume_or_modifier(); + const bool is_part = selection.is_single_volume_or_modifier() && ! selection.is_any_connector(); #else const bool is_part = selection.is_single_volume() || selection.is_single_modifier(); #endif // ENABLE_WORLD_COORDINATE From 7bb0b7eefc3dc4bc5fdb10da5c3afa469a2843e1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 20 Oct 2022 16:34:21 +0200 Subject: [PATCH 87/97] Cut bug fixing: Fixed a place of connectors after several cutting + Added info about camera direction to a DEBUG window + Code factoring (deleted unused code) + Fixed build warnings --- src/libslic3r/Model.cpp | 6 ++- src/slic3r/GUI/Field.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 11 +++--- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 52 ++++++++++++++++--------- src/slic3r/GUI/SavePresetDialog.cpp | 2 +- src/slic3r/GUI/UnsavedChangesDialog.hpp | 2 +- 6 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index aa21c3ade..3c4e00454 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1232,6 +1232,8 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn case CutConnectorShape::Hexagon: sectorCount = 6; break; + default: + break; } if (connector_attributes.style == CutConnectorStyle::Prizm) @@ -1395,11 +1397,11 @@ void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttri void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) { - const auto volume_matrix = volume->get_matrix(); + const auto volume_matrix = instance_matrix * volume->get_matrix(); // Modifiers are not cut, but we still need to add the instance transformation // to the modifier volume transformation to preserve their shape properly. - volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); + volume->set_transformation(Geometry::Transformation(volume_matrix)); // Some logic for the negative volumes/connectors. Add only needed modifiers auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index e674b52d0..bedb3718e 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -33,7 +33,7 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/) // Style_NoTrailingZeroes does not work on OSX. It also does not work correctly with some locales on Windows. // return wxNumberFormatter::ToString(value, max_precision, wxNumberFormatter::Style_NoTrailingZeroes); - wxString s = wxNumberFormatter::ToString(value, value < 0.0001 ? 10 : max_precision, wxNumberFormatter::Style_None); + wxString s = wxNumberFormatter::ToString(value, std::abs(value) < 0.0001 ? 10 : max_precision, wxNumberFormatter::Style_None); // The following code comes from wxNumberFormatter::RemoveTrailingZeroes(wxString& s) // with the exception that here one sets the decimal separator explicitely to dot. diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index ac9602985..5be1efed1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2990,9 +2990,9 @@ void ObjectList::delete_instance_from_list(const size_t obj_idx, const size_t in void ObjectList::update_lock_icons_for_model() { - for (int obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx) + for (size_t obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx) if (!(*m_objects)[obj_idx]->is_cut()) - m_objects_model->UpdateLockIcon(m_objects_model->GetItemById(obj_idx), false); + m_objects_model->UpdateLockIcon(m_objects_model->GetItemById(int(obj_idx)), false); } bool ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx) @@ -4088,15 +4088,16 @@ bool ObjectList::fix_cut_selection(wxDataViewItemArray& sels) bool is_instance_selection = m_objects_model->GetItemType(item) & itInstance; - int obj_idx = m_objects_model->GetObjectIdByItem(item); + int object_idx = m_objects_model->GetObjectIdByItem(item); int inst_idx = is_instance_selection ? m_objects_model->GetInstanceIdByItem(item) : 0; - if (auto obj = object(obj_idx); obj->is_cut()) { + if (auto obj = object(object_idx); obj->is_cut()) { sels.Clear(); auto cut_id = obj->cut_id; - for (int obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx) { + int objects_cnt = int((*m_objects).size()); + for (int obj_idx = 0; obj_idx < objects_cnt; ++obj_idx) { auto object = (*m_objects)[obj_idx]; if (object->is_cut() && object->cut_id.has_same_id(cut_id)) sels.Add(is_instance_selection ? m_objects_model->GetItemByInstanceId(obj_idx, inst_idx) : m_objects_model->GetItemById(obj_idx)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 56dd56462..fcb5cc247 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -578,13 +578,9 @@ void GLGizmoCut3D::render_cut_plane_line() const Camera& camera = wxGetApp().plater()->get_camera(); - Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); - unit_dir.normalize(); + const Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); - Vec3d camera_dir = camera.get_dir_forward(); - camera_dir.normalize(); - - if (std::abs(unit_dir.dot(camera_dir)) <= 0.025) { + if (std::abs(unit_dir.dot(camera.get_dir_forward())) <= 0.025) { GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); if (shader) { m_circle.reset(); @@ -665,7 +661,6 @@ void GLGizmoCut3D::render_line(GLModel& line_model, const ColorRGBA& color, Tran if (shader) { shader->start_using(); - const Camera& camera = wxGetApp().plater()->get_camera(); shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", wxGetApp().plater()->get_camera().get_projection_matrix()); shader->set_uniform("width", width); @@ -734,11 +729,11 @@ void GLGizmoCut3D::render_cut_plane_grabbers() // render Z grabber - if ((!m_dragging && m_hover_id < 0)) + if (!m_dragging && m_hover_id < 0) render_grabber_connection(color, view_matrix); render_model(m_sphere.model, color, view_matrix * scale_transform(size)); - if (!m_dragging && m_hover_id < 0 || m_hover_id == Z) + if ((!m_dragging && m_hover_id < 0) || m_hover_id == Z) { const BoundingBoxf3 tbb = transformed_bounding_box(); if (tbb.min.z() <= 0.0) @@ -867,9 +862,9 @@ void GLGizmoCut3D::on_set_state() m_parent.request_extra_frame(); } - else + else { m_c->object_clipper()->release(); - + } force_update_clipper_on_render = m_state == On; } @@ -1398,6 +1393,34 @@ void GLGizmoCut3D::render_debug_input_window() if (auto oc = m_c->object_clipper()) oc->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, double(contour_width)); + ImGui::Separator(); + + // Camera editing + + auto get_label = [](Vec3d dir) { + wxString str = "x=" + double_to_string(dir.x(), 2) + + ", y=" + double_to_string(dir.y(), 2) + + ", z=" + double_to_string(dir.z(), 2); + return str; + }; + + const Camera& camera = wxGetApp().plater()->get_camera(); + + Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); + m_imgui->text("Unit dir: "); + ImGui::SameLine(m_label_width); + m_imgui->text(get_label(unit_dir)); + + Vec3d camera_dir = camera.get_dir_forward(); + m_imgui->text("Camera dir: "); + ImGui::SameLine(m_label_width); + m_imgui->text(get_label(camera_dir)); + + m_imgui->text("Unit2Camera: "); + double proj = unit_dir.dot(camera_dir); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(std::abs(proj) <= 0.025 ? ImGuiWrapper::COL_ORANGE_LIGHT : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), double_to_string(proj, 2)); + m_imgui->end(); } @@ -2021,13 +2044,6 @@ void GLGizmoCut3D::discard_cut_line_processing() bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position) { - const float sla_shift = m_c->selection_info()->get_sla_shift(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; - Transform3d inst_trafo = sla_shift > 0.f ? - assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : - mi->get_transformation().get_matrix(); - const Camera& camera = wxGetApp().plater()->get_camera(); Vec3d pt; diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index 57aa5da98..40f981722 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -163,7 +163,7 @@ void SavePresetDialog::Item::update() if (m_valid_type == ValidationType::Valid && existing) { if (m_preset_name == m_presets->get_selected_preset_name()) { - if (!rename && m_presets->get_edited_preset().is_dirty || + if ((!rename && m_presets->get_edited_preset().is_dirty) || m_parent->get_preset_bundle()) // means that we save modifications from the DiffDialog info_line = _L("Save preset modifications to existing user profile"); else diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 69f808451..a0b53dadf 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -414,7 +414,7 @@ public: std::string get_left_preset_name(Preset::Type type); std::string get_right_preset_name(Preset::Type type); - std::vector get_selected_options(Preset::Type type) const { return std::move(m_tree->options(type, true)); } + std::vector get_selected_options(Preset::Type type) const { return m_tree->options(type, true); } protected: void on_dpi_changed(const wxRect& suggested_rect) override; From d7db5bde1ab04a534e824651af75cf82f6d5709b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 21 Oct 2022 09:06:00 +0200 Subject: [PATCH 88/97] Fixed z-fighting between cut contours and cut plane --- src/slic3r/GUI/MeshUtils.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index b6f95eead..d8a9474b3 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -243,6 +243,8 @@ void MeshClipper::recalculate_triangles() } tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting + Transform3d tr2 = tr; + tr2.pretranslate(0.002 * m_plane.get_normal().normalized()); #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -318,9 +320,9 @@ void MeshClipper::recalculate_triangles() // vertices + indices for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) { - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); const size_t idx = it - triangles2d.cbegin(); init_data.add_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); } From 98d7fe335b7cd912dfa12b1374b15a82193c548e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 21 Oct 2022 16:07:41 +0200 Subject: [PATCH 89/97] Cut WIP: experiments with detection of the position for CutPlaneLine Note: It still doesn't work properly + CurGizmo: Fixed a check of new center position in function set_center_pos(). --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 134 ++++++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 +- 2 files changed, 94 insertions(+), 43 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index fcb5cc247..d8973228a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -224,7 +224,7 @@ std::string GLGizmoCut3D::get_tooltip() const if (m_hover_id == Z) { double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm")); - const BoundingBoxf3 tbb = transformed_bounding_box(); + const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center); if (tbb.max.z() >= 0.0) { double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; tooltip += format(top, 2) + " " + unit_str + " (" + _u8L("Top part") + ")"; @@ -568,6 +568,82 @@ bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::s return revert; } +static Vec2d ndc_to_ss(const Vec3d& ndc, const std::array& viewport) { + const double half_w = 0.5 * double(viewport[2]); + const double half_h = 0.5 * double(viewport[3]); + return { half_w * ndc.x() + double(viewport[0]) + half_w, half_h * ndc.y() + double(viewport[1]) + half_h }; +}; +static Vec3d clip_to_ndc(const Vec4d& clip) { + return Vec3d(clip.x(), clip.y(), clip.z()) / clip.w(); +} +static Vec4d world_to_clip(const Vec3d& world, const Matrix4d& projection_view_matrix) { + return projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0); +} +static Vec2d world_to_ss(const Vec3d& world, const Matrix4d& projection_view_matrix, const std::array& viewport) { + return ndc_to_ss(clip_to_ndc(world_to_clip(world, projection_view_matrix)), viewport); +} + +static wxString get_label(Vec3d vec) +{ + wxString str = "x=" + double_to_string(vec.x(), 2) + + ", y=" + double_to_string(vec.y(), 2) + + ", z=" + double_to_string(vec.z(), 2); + return str; +} + +static wxString get_label(Vec2d vec) +{ + wxString str = "x=" + double_to_string(vec.x(), 2) + + ", y=" + double_to_string(vec.y(), 2); + return str; +} + +bool GLGizmoCut3D::can_render_cut_plane_line(bool render_values_in_debug/* = false*/) +{ + const Camera& camera = wxGetApp().plater()->get_camera(); + + Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); + + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + + const Vec2d screen_coord = world_to_ss(m_plane_center, projection_view_matrix, camera.get_viewport()); + + const Vec3d mouse_dir = m_parent.mouse_ray(Point(screen_coord.x(), screen_coord.y())).unit_vector(); + + const Vec3d camera_dir = camera.get_dir_forward(); +// double proj = unit_dir.dot(camera_dir); + const double proj = unit_dir.dot(mouse_dir); + const bool can_render = std::abs(proj) <= 0.01; // 0.25 + + if (render_values_in_debug) { + ImGui::Separator(); + + m_imgui->text("Unit dir: "); + ImGui::SameLine(m_label_width); + m_imgui->text(get_label(unit_dir)); + + m_imgui->text("Camera dir: "); + ImGui::SameLine(m_label_width); + m_imgui->text(get_label(camera_dir)); + + // m_imgui->text("screen_coord: "); + // ImGui::SameLine(m_label_width); + // m_imgui->text(get_label(screen_coord)); + + m_imgui->text("Mouse dir: "); + ImGui::SameLine(m_label_width); + m_imgui->text(get_label(mouse_dir)); + +// m_imgui->text("Unit2Camera: "); + m_imgui->text("Unit2Mouse: "); + double proj = unit_dir.dot(/*camera_dir*/mouse_dir); + ImGui::SameLine(m_label_width); + m_imgui->text_colored(can_render ? ImGuiWrapper::COL_ORANGE_LIGHT : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), double_to_string(proj, 2)); + } + + return can_render; +} + void GLGizmoCut3D::render_cut_plane_line() { if (cut_line_processing()) @@ -576,16 +652,13 @@ void GLGizmoCut3D::render_cut_plane_line() glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - const Camera& camera = wxGetApp().plater()->get_camera(); - - const Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); - - if (std::abs(unit_dir.dot(camera.get_dir_forward())) <= 0.025) { + if (can_render_cut_plane_line()) { GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); if (shader) { - m_circle.reset(); - init_from_circle(m_circle, m_radius * 1.25); + GLModel circle; + init_from_circle(circle, m_radius * 1.25); + const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; shader->start_using(); @@ -593,7 +666,7 @@ void GLGizmoCut3D::render_cut_plane_line() shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("width", 0.1f); - m_circle.render(); + circle.render(); shader->stop_using(); } @@ -735,7 +808,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers() if ((!m_dragging && m_hover_id < 0) || m_hover_id == Z) { - const BoundingBoxf3 tbb = transformed_bounding_box(); + const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center); if (tbb.min.z() <= 0.0) render_model(m_cone.model, color, view_matrix * assemble_transform(-offset, PI * Vec3d::UnitX(), cone_scale)); @@ -1184,7 +1257,9 @@ void GLGizmoCut3D::on_stop_dragging() void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/) { - bool can_set_center_pos = force || transformed_bounding_box(true).contains(center_pos); + const BoundingBoxf3 tbb = transformed_bounding_box(center_pos); + + bool can_set_center_pos = force || (tbb.max.z() > -1. && tbb.min.z() < 1.); if (!can_set_center_pos) { const double old_dist = (m_bb_center - m_plane_center).norm(); const double new_dist = (m_bb_center - center_pos).norm(); @@ -1212,7 +1287,7 @@ BoundingBoxf3 GLGizmoCut3D::bounding_box() const return ret; } -BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false*/) const +BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center, bool revert_move /*= false*/) const { // #ysFIXME !!! BoundingBoxf3 ret; @@ -1229,11 +1304,11 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(bool revert_move /*= false* const ModelInstance* mi = mo->instances[instance_idx]; const Vec3d& instance_offset = mi->get_offset(); - Vec3d cut_center_offset = m_plane_center - instance_offset; + Vec3d cut_center_offset = plane_center - instance_offset; cut_center_offset[Z] -= sel_info->get_sla_shift(); const auto move = assemble_transform(-cut_center_offset); - const auto move2 = assemble_transform(m_plane_center); + const auto move2 = assemble_transform(plane_center); const auto cut_matrix = (revert_move ? move2 : Transform3d::Identity()) * m_rotation_m.inverse() * move; @@ -1393,33 +1468,8 @@ void GLGizmoCut3D::render_debug_input_window() if (auto oc = m_c->object_clipper()) oc->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, double(contour_width)); - ImGui::Separator(); - // Camera editing - - auto get_label = [](Vec3d dir) { - wxString str = "x=" + double_to_string(dir.x(), 2) + - ", y=" + double_to_string(dir.y(), 2) + - ", z=" + double_to_string(dir.z(), 2); - return str; - }; - - const Camera& camera = wxGetApp().plater()->get_camera(); - - Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); - m_imgui->text("Unit dir: "); - ImGui::SameLine(m_label_width); - m_imgui->text(get_label(unit_dir)); - - Vec3d camera_dir = camera.get_dir_forward(); - m_imgui->text("Camera dir: "); - ImGui::SameLine(m_label_width); - m_imgui->text(get_label(camera_dir)); - - m_imgui->text("Unit2Camera: "); - double proj = unit_dir.dot(camera_dir); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(std::abs(proj) <= 0.025 ? ImGuiWrapper::COL_ORANGE_LIGHT : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), double_to_string(proj, 2)); + can_render_cut_plane_line(true); m_imgui->end(); } @@ -1549,7 +1599,7 @@ 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")); - const BoundingBoxf3 tbb = transformed_bounding_box(); + const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center); Vec3d tbb_sz = tbb.size(); wxString size = "X: " + double_to_string(tbb_sz.x() * koef, 2) + unit_str + @@ -1880,7 +1930,7 @@ bool GLGizmoCut3D::can_perform_cut() const if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; - const BoundingBoxf3 tbb = transformed_bounding_box(true); + const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center, true); return tbb.contains(m_plane_center); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 941d01660..95cd10fa8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -160,7 +160,7 @@ public: void invalidate_cut_plane(); BoundingBoxf3 bounding_box() const; - BoundingBoxf3 transformed_bounding_box(bool revert_move = false) const; + BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, bool revert_move = false) const; protected: bool on_init() override; @@ -237,6 +237,7 @@ private: void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); void render_cut_plane_grabbers(); void render_cut_line(); + bool can_render_cut_plane_line(bool render_values = false); void render_cut_plane_line(); void perform_cut(const Selection&selection); void set_center_pos(const Vec3d¢er_pos, bool force = false); From ae2166778605efd022ef78f1d803fb78be12912c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 24 Oct 2022 16:57:02 +0200 Subject: [PATCH 90/97] Cut WIP: First implementation for detection of the invalid connectors position Implemented cases: * overlap of some connectors * check if some connector position is outside of clipper --- src/libslic3r/libslic3r.h | 4 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 110 ++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 58 ++++++++++++ src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 2 + src/slic3r/GUI/MeshUtils.cpp | 36 ++++++++ src/slic3r/GUI/MeshUtils.hpp | 2 + 7 files changed, 158 insertions(+), 55 deletions(-) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 2285c29a6..054ccd4ea 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -262,9 +262,9 @@ constexpr inline T lerp(const T& a, const T& b, Number t) } template -constexpr inline bool is_approx(Number value, Number test_value) +constexpr inline bool is_approx(Number value, Number test_value, Number precision = EPSILON) { - return std::fabs(double(value) - double(test_value)) < double(EPSILON); + return std::fabs(double(value) - double(test_value)) < double(precision); } // A meta-predicate which is true for integers wider than or equal to coord_t diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index d8973228a..7695a902d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1845,6 +1845,32 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c return assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix; } +bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +{ + // check if connector pos is out of clipping plane + if (m_c->object_clipper() && !m_c->object_clipper()->containes(cur_pos)) + return true; + + const CutConnector& cur_connector = connectors[idx]; + const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m * + scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); + const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); + + if (!bounding_box().contains(cur_tbb)) + return true; + + for (size_t i = 0; i < connectors.size(); ++i) { + if (i == idx) + continue; + const CutConnector& connector = connectors[i]; + + if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) + return true; + } + + return false; +} + void GLGizmoCut3D::render_connectors() { ::glEnable(GL_DEPTH_TEST); @@ -1872,8 +1898,6 @@ void GLGizmoCut3D::render_connectors() const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; - const Transform3d instance_trafo = assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix(); - m_has_invalid_connector = false; for (size_t i = 0; i < connectors.size(); ++i) { @@ -1881,16 +1905,14 @@ void GLGizmoCut3D::render_connectors() float height = connector.height; // recalculate connector position to world position - Vec3d pos = connector.pos + instance_offset; - if (connector.attribs.type == CutConnectorType::Dowel && - connector.attribs.style == CutConnectorStyle::Prizm) { - pos -= height * normal; - height *= 2; - } - pos += sla_shift * Vec3d::UnitZ(); + Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // First decide about the color of the point. - if (!m_connectors_editing) + if (is_conflict_for_connector(i, connectors, pos)) { + m_has_invalid_connector = true; + render_color = CONNECTOR_ERR_COLOR; + } + else if (!m_connectors_editing) render_color = CONNECTOR_ERR_COLOR; else if (size_t(m_hover_id - m_connectors_group_id) == i) render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; @@ -1898,26 +1920,13 @@ void GLGizmoCut3D::render_connectors() render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; else // neither hover nor picking render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR; - // ! #ysFIXME rework get_volume_transformation - if (0) { // else { // neither hover nor picking - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - ++mesh_id; - if (!mv->is_model_part()) - continue; - - const Transform3d volume_trafo = get_volume_transformation(mv); - - if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) { - render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : CONNECTOR_ERR_COLOR; - break; - } - render_color = CONNECTOR_ERR_COLOR; - m_has_invalid_connector = true; - } - } const Camera& camera = wxGetApp().plater()->get_camera(); + if (connector.attribs.type == CutConnectorType::Dowel && + connector.attribs.style == CutConnectorStyle::Prizm) { + pos -= height * normal; + height *= 2; + } const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); @@ -1930,8 +1939,8 @@ bool GLGizmoCut3D::can_perform_cut() const if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; - const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center, true); - return tbb.contains(m_plane_center); + const auto clipper = m_c->object_clipper(); + return clipper && clipper->has_valid_contour(); } void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object) @@ -1982,7 +1991,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= sla_shift_z; - if (0.0 < object_cut_z && can_perform_cut()) { + if (0.0 < object_cut_z) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); bool create_dowels_as_separate_object = false; @@ -2014,7 +2023,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; - const Transform3d instance_trafo = sla_shift > 0.0 ? + const Transform3d instance_trafo = sla_shift > 0.f ? assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); @@ -2138,33 +2147,28 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_position) { + if (!m_connectors_editing) + return false; + std::pair pos_and_normal; Vec3d pos_world; if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal, pos_world)) { const Vec3d& hit = pos_and_normal.first; - if (m_connectors_editing) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); + unselect_all_connectors(); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); - unselect_all_connectors(); - - connectors.emplace_back(hit, m_rotation_m, - m_connector_size * 0.5f, m_connector_depth_ratio, - m_connector_size_tolerance, m_connector_depth_ratio_tolerance, - CutConnectorAttributes( CutConnectorType(m_connector_type), - CutConnectorStyle(m_connector_style), - CutConnectorShape(m_connector_shape_id))); - m_selected.push_back(true); - m_selected_count = 1; - assert(m_selected.size() == connectors.size()); - update_raycasters_for_picking(); - m_parent.set_as_dirty(); - } - else { - // Following would inform the clipper about the mouse click, so it can - // toggle the respective contour as disabled. - //m_c->object_clipper()->pass_mouse_click(pos_world); - } + connectors.emplace_back(hit, m_rotation_m, + m_connector_size * 0.5f, m_connector_depth_ratio, + m_connector_size_tolerance, m_connector_depth_ratio_tolerance, + CutConnectorAttributes( CutConnectorType(m_connector_type), + CutConnectorStyle(m_connector_style), + CutConnectorShape(m_connector_shape_id))); + m_selected.push_back(true); + m_selected_count = 1; + assert(m_selected.size() == connectors.size()); + update_raycasters_for_picking(); + m_parent.set_as_dirty(); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 95cd10fa8..e33c5c897 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -223,6 +223,7 @@ private: bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; bool render_connect_type_radio_button(CutConnectorType type); Transform3d get_volume_transformation(const ModelVolume* volume) const; + bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); void render_connectors(); bool can_perform_cut() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index a08836c28..435d8dc6e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -456,6 +456,64 @@ void ObjectClipper::render_cut() const } } +bool ObjectClipper::containes(Vec3d point) const +{ + if (m_clp_ratio == 0.) + return false; + const SelectionInfo* sel_info = get_pool()->selection_info(); + int sel_instance_idx = sel_info->get_active_instance(); + if (sel_instance_idx < 0) + return false; + const ModelObject* mo = sel_info->model_object(); + const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); + + size_t clipper_id = 0; + for (const ModelVolume* mv : mo->volumes) { + const Geometry::Transformation vol_trafo = mv->get_transformation(); + Geometry::Transformation trafo = inst_trafo * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + auto& clipper = m_clippers[clipper_id]; + clipper->set_plane(*m_clp); + clipper->set_transformation(trafo); + clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + if (clipper->contains(point)) + return true; + + ++clipper_id; + } + return false; +} + +bool ObjectClipper::has_valid_contour() const +{ + if (m_clp_ratio == 0.) + return false; + const SelectionInfo* sel_info = get_pool()->selection_info(); + int sel_instance_idx = sel_info->get_active_instance(); + if (sel_instance_idx < 0) + return false; + const ModelObject* mo = sel_info->model_object(); + const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); + + size_t clipper_id = 0; + for (const ModelVolume* mv : mo->volumes) { + const Geometry::Transformation vol_trafo = mv->get_transformation(); + Geometry::Transformation trafo = inst_trafo * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + auto& clipper = m_clippers[clipper_id]; + clipper->set_plane(*m_clp); + clipper->set_transformation(trafo); + clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + if (clipper->has_valid_contour()) + return true; + + ++clipper_id; + } + return false; +} + void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) { const ModelObject* mo = get_pool()->selection_info()->model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 7c41a64b9..79483e403 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -266,6 +266,8 @@ public: void pass_mouse_click(const Vec3d& pt); std::vector get_disabled_contours() const; + bool containes(Vec3d point) const; + bool has_valid_contour() const; protected: diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index d8a9474b3..f711ecf03 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -146,6 +146,42 @@ void MeshClipper::render_contour() #endif // ENABLE_LEGACY_OPENGL_REMOVAL } +bool MeshClipper::contains(Vec3d point) +{ + if (!m_result) + recalculate_triangles(); + + for (CutIsland& isl : m_result->cut_islands) { + BoundingBoxf3 bb = isl.model_expanded.get_bounding_box(); + + // instead of using of standard bb.contains(point) + // because of precision (Note, that model_expanded is pretranslate(0.003 * normal.normalized())) + constexpr double pres = 0.01; + bool ret = (point.x() > bb.min.x() || is_approx(point.x(), bb.min.x(), pres)) && (point.x() < bb.max.x() || is_approx(point.x(), bb.max.x(), pres)) + && (point.y() > bb.min.y() || is_approx(point.y(), bb.min.y(), pres)) && (point.y() < bb.max.y() || is_approx(point.y(), bb.max.y(), pres)) + && (point.z() > bb.min.z() || is_approx(point.z(), bb.min.z(), pres)) && (point.z() < bb.max.z() || is_approx(point.z(), bb.max.z(), pres)); + if (ret) { + // when we detected, that model_expanded's bb contains a point, then check if its polygon contains this point + Vec3d point_inv = m_result->trafo.inverse() * point; + Point pt = Point(scale_(point_inv.x()), scale_(point_inv.y())); + if (isl.expoly.contains(pt)) + return true; + } + } + return false; +} + +bool MeshClipper::has_valid_contour() +{ + if (!m_result) + recalculate_triangles(); + + for (CutIsland& isl : m_result->cut_islands) + if (isl.model_expanded.get_bounding_box().defined) + return true; + + return false; +} void MeshClipper::pass_mouse_click(const Vec3d& point_in) diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 726f228ca..186b74feb 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -115,6 +115,8 @@ public: void pass_mouse_click(const Vec3d& pt); + bool contains(Vec3d point); + bool has_valid_contour(); private: void recalculate_triangles(); From 18edc71254076326ad470280e3f8115607eedd86 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 25 Oct 2022 15:54:52 +0200 Subject: [PATCH 91/97] Cut WIP: Code refactoring for https://github.com/Prusa-Development/PrusaSlicerPrivate/commit/ae2166778605efd022ef78f1d803fb78be12912c + ObjectList: Fixed list of the types for "Change type" dialog, when object is cut. + CutGizmo: * Warning line is extended for information about invalid connectors * Fixed a crash on undo/Redo, when cutGizmo is active --- src/slic3r/GUI/GUI_ObjectList.cpp | 16 +++++-- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 59 ++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 13 ++++++ src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 54 ++-------------------- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 2 +- src/slic3r/GUI/MeshUtils.cpp | 38 ++++----------- src/slic3r/GUI/MeshUtils.hpp | 4 +- 7 files changed, 82 insertions(+), 104 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5be1efed1..4c83e1f0d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4155,10 +4155,11 @@ void ObjectList::change_part_type() if (obj_idx < 0) return; const ModelVolumeType type = volume->type(); + const ModelObject* obj = object(obj_idx); if (type == ModelVolumeType::MODEL_PART) { int model_part_cnt = 0; - for (auto vol : (*m_objects)[obj_idx]->volumes) { + for (auto vol : obj->volumes) { if (vol->type() == ModelVolumeType::MODEL_PART) ++model_part_cnt; } @@ -4169,9 +4170,18 @@ void ObjectList::change_part_type() } } - const wxString names[] = { _L("Part"), _L("Negative Volume"), _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") }; - auto new_type = ModelVolumeType(wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), wxArrayString(5, names), int(type))); + const bool is_cut_object = obj->is_cut(); + wxArrayString names; + names.Alloc(is_cut_object ? 3 : 5); + if (!is_cut_object) + for (const wxString& type : { _L("Part"), _L("Negative Volume") }) + names.Add(type); + for (const wxString& type : { _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") } ) + names.Add(type); + + const int type_shift = is_cut_object ? 2 : 0; + auto new_type = ModelVolumeType(type_shift + wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, int(type) - type_shift)); if (new_type == type || new_type == ModelVolumeType::INVALID) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 7695a902d..802cd3006 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -928,6 +928,7 @@ void GLGizmoCut3D::on_set_state() { if (m_state == On) { update_bb(); + m_connectors_editing = !m_selected.empty(); // initiate archived values m_ar_plane_center = m_plane_center; @@ -937,6 +938,7 @@ void GLGizmoCut3D::on_set_state() } else { m_c->object_clipper()->release(); + m_selected.clear(); } force_update_clipper_on_render = m_state == On; } @@ -1257,16 +1259,20 @@ void GLGizmoCut3D::on_stop_dragging() void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool force/* = false*/) { - const BoundingBoxf3 tbb = transformed_bounding_box(center_pos); - - bool can_set_center_pos = force || (tbb.max.z() > -1. && tbb.min.z() < 1.); + bool can_set_center_pos = force; if (!can_set_center_pos) { - const double old_dist = (m_bb_center - m_plane_center).norm(); - const double new_dist = (m_bb_center - center_pos).norm(); - // check if forcing is reasonable - if ( new_dist < old_dist) + const BoundingBoxf3 tbb = transformed_bounding_box(center_pos); + if (tbb.max.z() > -1. && tbb.min.z() < 1.) can_set_center_pos = true; + else { + const double old_dist = (m_bb_center - m_plane_center).norm(); + const double new_dist = (m_bb_center - center_pos).norm(); + // check if forcing is reasonable + if (new_dist < old_dist) + can_set_center_pos = true; + } } + if (can_set_center_pos) { m_plane_center = center_pos; m_center_offset = m_plane_center - m_bb_center; @@ -1299,7 +1305,7 @@ BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center, if (!mo) return ret; const int instance_idx = sel_info->get_active_instance(); - if (instance_idx < 0) + if (instance_idx < 0 || mo->instances.empty()) return ret; const ModelInstance* mi = mo->instances[instance_idx]; @@ -1372,7 +1378,6 @@ bool GLGizmoCut3D::update_bb() clear_selection(); if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) m_selected.resize(selection->model_object()->cut_connectors.size(), false); - m_connectors_editing = !m_selected.empty(); return true; } @@ -1791,8 +1796,18 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { - if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector) - m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected.")); + if (wxGetApp().plater()->printer_technology() == ptFFF && m_has_invalid_connector) { + wxString out = wxString(ImGui::WarningMarkerSmall) + _L("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), + 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), + m_info_stats.outside_bb); + if (m_info_stats.is_overlap) + out += "\n - " + _L("Some connectors are overlapped"); + m_imgui->text(out); + } if (!m_keep_upper && !m_keep_lower) m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Invalid state. \nNo one part is selected for keep after cut")); } @@ -1848,24 +1863,30 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) { // check if connector pos is out of clipping plane - if (m_c->object_clipper() && !m_c->object_clipper()->containes(cur_pos)) + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) { + m_info_stats.outside_cut_contour++; return true; + } const CutConnector& cur_connector = connectors[idx]; const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m * scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); - if (!bounding_box().contains(cur_tbb)) + if (!bounding_box().contains(cur_tbb)) { + m_info_stats.outside_bb++; return true; + } for (size_t i = 0; i < connectors.size(); ++i) { if (i == idx) continue; const CutConnector& connector = connectors[i]; - if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) + if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius)) { + m_info_stats.is_overlap = true; return true; + } } return false; @@ -1899,6 +1920,7 @@ void GLGizmoCut3D::render_connectors() const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; m_has_invalid_connector = false; + m_info_stats.invalidate(); for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; @@ -1970,6 +1992,8 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel void GLGizmoCut3D::perform_cut(const Selection& selection) { + if (!can_perform_cut()) + return; const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); @@ -1985,13 +2009,13 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // m_cut_z is the distance from the bed. Subtract possible SLA elevation. const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z(); - const double object_cut_z = m_plane_center.z() - sla_shift_z; const Vec3d instance_offset = mo->instances[instance_idx]->get_offset(); Vec3d cut_center_offset = m_plane_center - instance_offset; cut_center_offset[Z] -= sla_shift_z; - if (0.0 < object_cut_z) { + // perform cut + { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); bool create_dowels_as_separate_object = false; @@ -2008,9 +2032,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); } - else { - // the object is SLA-elevated and the plane is under it. - } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index e33c5c897..e0fc4d3e8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -74,6 +74,19 @@ class GLGizmoCut3D : public GLGizmoBase Vec3d m_old_center; + struct InvalidConnectorsStatistics + { + unsigned int outside_cut_contour; + unsigned int outside_bb; + bool is_overlap; + + void invalidate() { + outside_cut_contour = 0; + outside_bb = 0; + is_overlap = false; + } + } m_info_stats; + bool m_keep_upper{ true }; bool m_keep_lower{ true }; bool m_place_on_cut_upper{ true }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 435d8dc6e..9734f1b77 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -456,62 +456,14 @@ void ObjectClipper::render_cut() const } } -bool ObjectClipper::containes(Vec3d point) const +bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const { - if (m_clp_ratio == 0.) - return false; - const SelectionInfo* sel_info = get_pool()->selection_info(); - int sel_instance_idx = sel_info->get_active_instance(); - if (sel_instance_idx < 0) - return false; - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - const Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - if (clipper->contains(point)) - return true; - - ++clipper_id; - } - return false; + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const std::unique_ptr& cl) { return cl->is_projection_inside_cut(point); }); } bool ObjectClipper::has_valid_contour() const { - if (m_clp_ratio == 0.) - return false; - const SelectionInfo* sel_info = get_pool()->selection_info(); - int sel_instance_idx = sel_info->get_active_instance(); - if (sel_instance_idx < 0) - return false; - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - const Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - if (clipper->has_valid_contour()) - return true; - - ++clipper_id; - } - return false; + return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const std::unique_ptr& cl) { return cl->has_valid_contour(); }); } void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 79483e403..f8ead27f9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -266,7 +266,7 @@ public: void pass_mouse_click(const Vec3d& pt); std::vector get_disabled_contours() const; - bool containes(Vec3d point) const; + bool is_projection_inside_cut(const Vec3d& point_in) const; bool has_valid_contour() const; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index f711ecf03..5ec066f44 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -146,41 +146,23 @@ void MeshClipper::render_contour() #endif // ENABLE_LEGACY_OPENGL_REMOVAL } -bool MeshClipper::contains(Vec3d point) +bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const { - if (!m_result) - recalculate_triangles(); + if (!m_result || m_result->cut_islands.empty()) + return false; + Vec3d point = m_result->trafo.inverse() * point_in; + Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y())); - for (CutIsland& isl : m_result->cut_islands) { - BoundingBoxf3 bb = isl.model_expanded.get_bounding_box(); - - // instead of using of standard bb.contains(point) - // because of precision (Note, that model_expanded is pretranslate(0.003 * normal.normalized())) - constexpr double pres = 0.01; - bool ret = (point.x() > bb.min.x() || is_approx(point.x(), bb.min.x(), pres)) && (point.x() < bb.max.x() || is_approx(point.x(), bb.max.x(), pres)) - && (point.y() > bb.min.y() || is_approx(point.y(), bb.min.y(), pres)) && (point.y() < bb.max.y() || is_approx(point.y(), bb.max.y(), pres)) - && (point.z() > bb.min.z() || is_approx(point.z(), bb.min.z(), pres)) && (point.z() < bb.max.z() || is_approx(point.z(), bb.max.z(), pres)); - if (ret) { - // when we detected, that model_expanded's bb contains a point, then check if its polygon contains this point - Vec3d point_inv = m_result->trafo.inverse() * point; - Point pt = Point(scale_(point_inv.x()), scale_(point_inv.y())); - if (isl.expoly.contains(pt)) - return true; - } + for (const CutIsland& isl : m_result->cut_islands) { + if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d)) + return true; } return false; } -bool MeshClipper::has_valid_contour() +bool MeshClipper::has_valid_contour() const { - if (!m_result) - recalculate_triangles(); - - for (CutIsland& isl : m_result->cut_islands) - if (isl.model_expanded.get_bounding_box().defined) - return true; - - return false; + return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); }); } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 186b74feb..9db2ed1b1 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -115,8 +115,8 @@ public: void pass_mouse_click(const Vec3d& pt); - bool contains(Vec3d point); - bool has_valid_contour(); + bool is_projection_inside_cut(const Vec3d& point) const; + bool has_valid_contour() const; private: void recalculate_triangles(); From 9b0a69e50e102b05e0fc5af32cb8a90ebd003f71 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 26 Oct 2022 15:26:35 +0200 Subject: [PATCH 92/97] CutGizmo: Fixed grabbers hovering after merge with master + Added possibility to use circle cut plane + Deleted unused code --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 100 +++++---------------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 +- 2 files changed, 17 insertions(+), 86 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 7e3b709d6..14b31d7c5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -599,81 +599,6 @@ static wxString get_label(Vec2d vec) return str; } -bool GLGizmoCut3D::can_render_cut_plane_line(bool render_values_in_debug/* = false*/) -{ - const Camera& camera = wxGetApp().plater()->get_camera(); - - Vec3d unit_dir = m_rotation_m * Vec3d::UnitZ(); - - const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); - - const Vec2d screen_coord = world_to_ss(m_plane_center, projection_view_matrix, camera.get_viewport()); - - const Vec3d mouse_dir = m_parent.mouse_ray(Point(screen_coord.x(), screen_coord.y())).unit_vector(); - - const Vec3d camera_dir = camera.get_dir_forward(); -// double proj = unit_dir.dot(camera_dir); - const double proj = unit_dir.dot(mouse_dir); - const bool can_render = std::abs(proj) <= 0.01; // 0.25 - - if (render_values_in_debug) { - ImGui::Separator(); - - m_imgui->text("Unit dir: "); - ImGui::SameLine(m_label_width); - m_imgui->text(get_label(unit_dir)); - - m_imgui->text("Camera dir: "); - ImGui::SameLine(m_label_width); - m_imgui->text(get_label(camera_dir)); - - // m_imgui->text("screen_coord: "); - // ImGui::SameLine(m_label_width); - // m_imgui->text(get_label(screen_coord)); - - m_imgui->text("Mouse dir: "); - ImGui::SameLine(m_label_width); - m_imgui->text(get_label(mouse_dir)); - -// m_imgui->text("Unit2Camera: "); - m_imgui->text("Unit2Mouse: "); - double proj = unit_dir.dot(/*camera_dir*/mouse_dir); - ImGui::SameLine(m_label_width); - m_imgui->text_colored(can_render ? ImGuiWrapper::COL_ORANGE_LIGHT : ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()), double_to_string(proj, 2)); - } - - return can_render; -} - -void GLGizmoCut3D::render_cut_plane_line() -{ - if (cut_line_processing()) - return; - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - - if (can_render_cut_plane_line()) { - GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); - if (shader) { - GLModel circle; - init_from_circle(circle, m_radius * 1.25); - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; - - shader->start_using(); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("width", 0.1f); - - circle.render(); - - shader->stop_using(); - } - } -} - void GLGizmoCut3D::render_cut_plane() { if (cut_line_processing()) @@ -697,7 +622,8 @@ void GLGizmoCut3D::render_cut_plane() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (can_perform_cut()) - m_plane.set_color({ 0.8f, 0.8f, 0.8f, 0.5f }); +// m_plane.set_color({ 0.8f, 0.8f, 0.8f, 0.5f }); + m_plane.set_color({ 0.9f, 0.9f, 0.9f, 0.5f }); else m_plane.set_color({ 1.0f, 0.8f, 0.8f, 0.5f }); m_plane.render(); @@ -947,7 +873,8 @@ void GLGizmoCut3D::on_set_state() void GLGizmoCut3D::on_register_raycasters_for_picking() { assert(m_raycasters.empty()); - set_volumes_picking_state(false); + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); init_picking_models(); @@ -977,7 +904,8 @@ void GLGizmoCut3D::on_unregister_raycasters_for_picking() { m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); m_raycasters.clear(); - set_volumes_picking_state(true); + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(false); } void GLGizmoCut3D::update_raycasters_for_picking() @@ -1418,8 +1346,12 @@ void GLGizmoCut3D::init_rendering_items() if (!m_angle_arc.is_initialized() || m_angle != 0.0) init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); - if (!m_plane.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) - m_plane.init_from(its_make_square_plane(float(m_radius))); + if (!m_plane.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { + if (m_cut_plane_as_circle) + m_plane.init_from(its_make_frustum_dowel(2. * m_radius, 0.3, 180)); + else + m_plane.init_from(its_make_square_plane(float(m_radius))); + } } void GLGizmoCut3D::render_clipper_cut() @@ -1453,8 +1385,6 @@ void GLGizmoCut3D::on_render() render_cut_line(); - render_cut_plane_line(); - m_selection_rectangle.render(m_parent); } @@ -1474,8 +1404,10 @@ void GLGizmoCut3D::render_debug_input_window() if (auto oc = m_c->object_clipper()) oc->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, double(contour_width)); - // Camera editing - can_render_cut_plane_line(true); + ImGui::Separator(); + + if (m_imgui->checkbox(_L("Render cut plane as circle"), m_cut_plane_as_circle)) + m_plane.reset(); m_imgui->end(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index e0fc4d3e8..35b6a92ce 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -96,6 +96,7 @@ class GLGizmoCut3D : public GLGizmoBase bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; + bool m_cut_plane_as_circle{ false }; float m_connector_depth_ratio{ 3.f }; float m_connector_size{ 2.5f }; @@ -251,8 +252,6 @@ private: void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); void render_cut_plane_grabbers(); void render_cut_line(); - bool can_render_cut_plane_line(bool render_values = false); - void render_cut_plane_line(); void perform_cut(const Selection&selection); void set_center_pos(const Vec3d¢er_pos, bool force = false); bool update_bb(); From a856cb29e191baef50f489c7bd5f8fb0a2845376 Mon Sep 17 00:00:00 2001 From: brightstonesong <114596206+brightstonesong@users.noreply.github.com> Date: Thu, 29 Sep 2022 00:23:34 +0900 Subject: [PATCH 93/97] Update PrusaSlicer_ko_KR.po filling in blanks and rewriting. --- .../localization/ko/PrusaSlicer_ko_KR.po | 165 ++++++++++-------- 1 file changed, 91 insertions(+), 74 deletions(-) diff --git a/resources/localization/ko/PrusaSlicer_ko_KR.po b/resources/localization/ko/PrusaSlicer_ko_KR.po index d4667650b..f16c5e1ff 100644 --- a/resources/localization/ko/PrusaSlicer_ko_KR.po +++ b/resources/localization/ko/PrusaSlicer_ko_KR.po @@ -73,7 +73,7 @@ msgid "" msgstr "" "Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, " "Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and " -"numerous others. 한국어 번역 울산에테르, 밤송이직박구리" +"numerous others. 한국어 번역 울산에테르, 밤송이직박구리,brightstone song" #: src/slic3r/GUI/AboutDialog.cpp:310 msgid "Copy Version Info" @@ -93,13 +93,13 @@ msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:84 #, boost-format msgid "PrusaSlicer has encountered a fatal error: \"%1%\"" -msgstr "" +msgstr "슬라이서에 치명적인 오류가 발생했습니다" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:85 msgid "" "Please save your project and restart PrusaSlicer. We would be glad if you " "reported the issue." -msgstr "" +msgstr "작업물을 저장하시고 슬라이서를 재시작하시기 바랍니다. 이 이슈를 보고해주시면 감사하겠습니다." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:162 #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:204 @@ -109,27 +109,27 @@ msgstr "슬라이스 완료" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:199 #, boost-format msgid "Masked SLA file exported to %1%" -msgstr "마스크 된 SLA 파일을 %1%로 내보냅니" +msgstr "마스크 된 SLA 파일을 %1%로 내보냅니다." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:286 msgid "Access violation" -msgstr "" +msgstr "접근 위반" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:288 msgid "Illegal instruction" -msgstr "" +msgstr "잘못된 명령" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:290 msgid "Divide by zero" -msgstr "" +msgstr "0" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:292 msgid "Overflow" -msgstr "" +msgstr "오버플로우" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:294 msgid "Underflow" -msgstr "" +msgstr "언더플로우" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:297 msgid "Floating reserved operand" @@ -137,7 +137,7 @@ msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:300 msgid "Stack overflow" -msgstr "" +msgstr "스택 오버플로우" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:659 #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:726 @@ -147,7 +147,7 @@ msgstr "포스트 프로세싱 스크립트" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:690 #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:710 msgid "Unknown error occured during exporting G-code." -msgstr "" +msgstr "G고드 제작중 알 수 없는 오류가 발생" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:695 #, boost-format @@ -189,7 +189,7 @@ msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:715 #, boost-format msgid "G-code file exported to %1%" -msgstr "%1%로 내보낸 G 코드 파일" +msgstr "%1%로 G코드 파일이 저장되었습니다." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:729 msgid "Copying of the temporary G-code to the output G-code failed" @@ -218,13 +218,13 @@ msgstr "노즐 직경" #: src/slic3r/GUI/BedShapeDialog.cpp:49 msgid "Size in X and Y of the rectangular plate." -msgstr "사각 플레이트 X 및 Y 크기." +msgstr "사각형 베드의 X와 Y크기." #: src/slic3r/GUI/BedShapeDialog.cpp:58 msgid "" "Distance of the 0,0 G-code coordinate from the front left corner of the " "rectangle." -msgstr "사각 전면 왼쪽 모서리에서 원저(0, 0) G-code 좌표 거리입니다." +msgstr "전면부 왼쪽 모서리부터 원점까지의(0, 0) G-code 좌표 거리입니다." #: src/slic3r/GUI/BedShapeDialog.cpp:64 src/slic3r/GUI/ConfigWizard.cpp:262 #: src/slic3r/GUI/ConfigWizard.cpp:1476 src/slic3r/GUI/ConfigWizard.cpp:1490 @@ -281,7 +281,7 @@ msgstr "mm" msgid "" "Diameter of the print bed. It is assumed that origin (0,0) is located in the " "center." -msgstr "인쇄 배드의 직경. 원점 (0,0) 은 중재봉선에 있다고 가정합니다." +msgstr "인쇄 배드의 직경. 원점 (0,0) 은 중앙에 있다고 가정됩니다." #: src/slic3r/GUI/BedShapeDialog.cpp:79 msgid "Rectangular" @@ -303,7 +303,7 @@ msgstr "모양" #: src/slic3r/GUI/BedShapeDialog.cpp:203 msgid "Load shape from STL..." -msgstr "STL파일 로드." +msgstr "STL파일 로드하기." #: src/slic3r/GUI/BedShapeDialog.cpp:249 src/slic3r/GUI/GCodeViewer.cpp:3709 #: src/slic3r/GUI/MainFrame.cpp:2147 @@ -325,7 +325,7 @@ msgstr "제거" #: src/slic3r/GUI/BedShapeDialog.cpp:317 src/slic3r/GUI/BedShapeDialog.cpp:388 msgid "Not found:" -msgstr "" +msgstr "찾지 못함" #: src/slic3r/GUI/BedShapeDialog.cpp:344 msgid "Model" @@ -346,25 +346,25 @@ msgstr "오류! 잘못된 모델" #: src/slic3r/GUI/BedShapeDialog.cpp:533 msgid "The selected file contains no geometry." -msgstr "선택한 파일에 없는 형상이 있습니다." +msgstr "선택한 파일에는 형상이 존재하지 않습니다." #: src/slic3r/GUI/BedShapeDialog.cpp:537 msgid "" "The selected file contains several disjoint areas. This is not supported." msgstr "" -"선택한 파일은 여러개의 분리 된 영역을 포함 되어 있어 지원 되지 않습니다." +"이 파일은 몇몇 끊어진 부분이 있습니다. 지원이 불가능합니다." #: src/slic3r/GUI/BedShapeDialog.cpp:552 msgid "Choose a file to import bed texture from (PNG/SVG):" -msgstr "(PNG /SVG)에서 배드 텍스처를 가져올 파일을 선택합니다." +msgstr "베드 텍스처를 가져올 (PNG /SVG)파일을 선택하십시오." #: src/slic3r/GUI/BedShapeDialog.cpp:574 msgid "Choose an STL file to import bed model from:" -msgstr "다음에서 베드 모델을 가져올 STL 파일을 선택합니다:" +msgstr "베드 모델을 가져올 STL 파일을 선택하십시오:" #: src/slic3r/GUI/BedShapeDialog.hpp:95 src/slic3r/GUI/ConfigWizard.cpp:1396 msgid "Bed Shape" -msgstr "배드 모양" +msgstr "베드 모양" #: src/slic3r/GUI/BonjourDialog.cpp:55 msgid "Network lookup" @@ -376,7 +376,7 @@ msgstr "주소" #: src/slic3r/GUI/BonjourDialog.cpp:73 msgid "Hostname" -msgstr "호스트이름" +msgstr "호스트 이름" #: src/slic3r/GUI/BonjourDialog.cpp:74 msgid "Service name" @@ -388,7 +388,7 @@ msgstr "옥토프린트 버전" #: src/slic3r/GUI/BonjourDialog.cpp:224 msgid "Searching for devices" -msgstr "디바이스 검색" +msgstr "디바이스 검색중" #: src/slic3r/GUI/BonjourDialog.cpp:231 msgid "Finished" @@ -406,7 +406,7 @@ msgstr "이 값은 시스템 값과 같습니다" msgid "" "Value was changed and is not equal to the system value or the last saved " "preset" -msgstr "값이 변경 되었고, 시스템 값 또는 마지막으로 저장된 설정값과 다릅니다." +msgstr "수치가 변경 되었고, 시스템 값 또는 마지막으로 저장된 설정값과 다릅니다." #: src/slic3r/GUI/ButtonsDescription.cpp:62 msgid "Buttons And Text Colors Description" @@ -418,6 +418,9 @@ msgid "" "\n" "The layer height will be reset to 0.01." msgstr "" +"레이어 높이가 유호하지 않습니다.\n" +"\n" +"높이가 0.01로 재설정됩니다." #: src/slic3r/GUI/ConfigManipulation.cpp:50 #: src/slic3r/GUI/GUI_ObjectLayers.cpp:29 src/slic3r/GUI/Tab.cpp:1449 @@ -431,6 +434,9 @@ msgid "" "\n" "The first layer height will be reset to 0.01." msgstr "" +"첫 레이어 높이가 유호하지 않습니다.\n" +"\n" +"첫 레이어 높이가 0.01로 재설정됩니다." #: src/slic3r/GUI/ConfigManipulation.cpp:62 src/libslic3r/PrintConfig.cpp:1227 msgid "First layer height" @@ -446,14 +452,21 @@ msgid "" "- Ensure vertical shell thickness enabled\n" "- Detect thin walls disabled" msgstr "" +"꽃병 모드는 다음과 같은 설정이 필요합니다:\n" +"- 외벽 1\n" +"- 상부 레이어 없음\n" +"- 내부 밀도 0%\n" +"- 서포트 없음\n" +"- 외벽 두께 보장 활성화\n" +"- 얇은 외벽 감지 비활성화" #: src/slic3r/GUI/ConfigManipulation.cpp:90 msgid "Shall I adjust those settings in order to enable Spiral Vase?" -msgstr "나선형 꽃병을 활성화하기 위해 이러한 설정을 조정해야 합니까?" +msgstr "꽃병 모드를 활성화하기 위해 이 설정들을 변경하시겠습니까?" #: src/slic3r/GUI/ConfigManipulation.cpp:91 msgid "Spiral Vase" -msgstr "스파이럴 바이스" +msgstr "꾳병 모드" #: src/slic3r/GUI/ConfigManipulation.cpp:121 msgid "" @@ -463,14 +476,14 @@ msgid "" "(both support_material_extruder and support_material_interface_extruder need " "to be set to 0)." msgstr "" -"와이프 타워(프라임 타워)는 현재 비수용성 지원만 지원합니다.\n" -"공구 교환을 트리거하지 않고 현재 압출기로 인쇄된 경우\n" -"(support_material_extruder support_material_interface_extruder 모두 0으로 설" +"와이프 타워(프라임 타워)는 현재 비수용성 서포트만 지원합니다.\n" +"툴체인지를 사용하지 않고 현재 노즐로 출력시\n" +"(support_material_extruder support_material_interface_extruder를 모두 0으로 설" "정해야 합니다)." #: src/slic3r/GUI/ConfigManipulation.cpp:125 msgid "Shall I adjust those settings in order to enable the Wipe Tower?" -msgstr "와이프 타워를 활성화하기 위해 이러한 설정을 조정해야 합니까?" +msgstr "와이프 타워를 활성화하기 위해 이 설정들을 변경하시겠습니까?" #: src/slic3r/GUI/ConfigManipulation.cpp:126 #: src/slic3r/GUI/ConfigManipulation.cpp:146 @@ -482,37 +495,37 @@ msgid "" "For the Wipe Tower to work with the soluble supports, the support layers\n" "need to be synchronized with the object layers." msgstr "" -"와이프 타워(프라임 타워)가 가용성 지지체와 함께 작동 하려면 서포트 레이어를 " +"와이프 타워(프라임 타워)를 수용성 서포트와 함께 사용하기 위해서는 서포트 레이어를 " "객체(object) 레이어와 동기화 해야 합니다." #: src/slic3r/GUI/ConfigManipulation.cpp:145 msgid "Shall I synchronize support layers in order to enable the Wipe Tower?" -msgstr "와이프 타워를 활성화하기 위해 지원 레이어를 동기화해야 합니까?" +msgstr "와이프 타워를 사용하기 위해 서포트 레이어 설정을 변경하시겠습니까?" #: src/slic3r/GUI/ConfigManipulation.cpp:164 msgid "" "Supports work better, if the following feature is enabled:\n" "- Detect bridging perimeters" msgstr "" -"다음 기능이 활성화된 경우 더 나은 작업을 지원합니다.\n" -"- 브리징 경계를 감지" +"서포트는 다음 기능이 활성화되어 있으면 더 효율적으로 생성됩니다.\n" +"- 브릿징 동작 감지" #: src/slic3r/GUI/ConfigManipulation.cpp:167 msgid "Shall I adjust those settings for supports?" -msgstr "지원에 대한 설정을 조정해야 합니까?" +msgstr "서포트 세팅을 변경하시겠습니까?" #: src/slic3r/GUI/ConfigManipulation.cpp:168 msgid "Support Generator" -msgstr "서포트 생성" +msgstr "서포트 생성기" #: src/slic3r/GUI/ConfigManipulation.cpp:195 #, boost-format msgid "The %1% infill pattern is not supposed to work at 100%% density." -msgstr "%1% 채우기 패턴은 100%% 밀도로 작동하도록 되어 있지 않습니다." +msgstr "%1% 패턴은 100%% 밀도로 작동하도록 되어 있지 않습니다." #: src/slic3r/GUI/ConfigManipulation.cpp:198 msgid "Shall I switch to rectilinear fill pattern?" -msgstr "직선 채우기 패턴으로 전환해야 합니까?" +msgstr "직선 패턴으로 전환하시겠습니까?" #: src/slic3r/GUI/ConfigManipulation.cpp:199 #: src/slic3r/GUI/GUI_Factories.cpp:55 src/slic3r/GUI/GUI_Factories.cpp:128 @@ -525,19 +538,19 @@ msgstr "직선 채우기 패턴으로 전환해야 합니까?" #: src/libslic3r/PrintConfig.cpp:1493 src/libslic3r/PrintConfig.cpp:1512 #: src/libslic3r/PrintConfig.cpp:2333 src/libslic3r/PrintConfig.cpp:2350 msgid "Infill" -msgstr "인필(채움)" +msgstr "인필" #: src/slic3r/GUI/ConfigManipulation.cpp:336 msgid "Head penetration should not be greater than the head width." -msgstr "헤드 관통은 헤드 폭 보다 크지 않아야 합니다." +msgstr "헤드 접촉길이는 헤드의 지름보다 클 수 없습니다." #: src/slic3r/GUI/ConfigManipulation.cpp:338 msgid "Invalid Head penetration" -msgstr "잘못된 헤드 관통" +msgstr "헤드 관통 불가" #: src/slic3r/GUI/ConfigManipulation.cpp:349 msgid "Pinhead diameter should be smaller than the pillar diameter." -msgstr "핀헤드 지름은 기둥 지름 보다 작아야 합니다." +msgstr "핀헤드 지름은 기둥 지름보다 클 수 ." #: src/slic3r/GUI/ConfigManipulation.cpp:351 msgid "Invalid pinhead diameter" @@ -596,7 +609,7 @@ msgstr "프린터" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:75 src/slic3r/GUI/Tab.cpp:1366 msgid "vendor" -msgstr "제조 회사" +msgstr "제조사" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:75 msgid "version" @@ -641,7 +654,7 @@ msgstr "대체 노즐:" #: src/slic3r/GUI/ConfigWizard.cpp:330 msgid "All standard" -msgstr "모두 표준설정" +msgstr "전부 표준설정으로" #: src/slic3r/GUI/ConfigWizard.cpp:330 msgid "Standard" @@ -680,12 +693,12 @@ msgid "" "Hello, welcome to %s! This %s helps you with the initial configuration; just " "a few settings and you will be ready to print." msgstr "" -"안녕하세요 ,%s에 오신 것을 환영 합니다! 이 %s는 초기 구성에 도움이 됩니다. " -"몇 가지 설정만으로 인쇄 준비가 될 것입니다." +"안녕하세요 ,%s에 오신 것을 환영 합니다! 이 %s는 초기 설정에 도움이 됩니다. " +"몇 가지 설정 후 프린팅할 준비가 될 것입니다." #: src/slic3r/GUI/ConfigWizard.cpp:495 msgid "Remove user profiles (a snapshot will be taken beforehand)" -msgstr "" +msgstr "유저 프로필 제거(제거 전 스냅샷이 생성될 것입니다.)" #: src/slic3r/GUI/ConfigWizard.cpp:498 msgid "" @@ -696,7 +709,7 @@ msgstr "" #: src/slic3r/GUI/ConfigWizard.cpp:550 #, c-format, boost-format msgid "%s Family" -msgstr "%s의 가족들" +msgstr "%s사 제품들" #: src/slic3r/GUI/ConfigWizard.cpp:640 msgid "Printer:" @@ -729,7 +742,7 @@ msgstr "필라멘트" #: src/slic3r/GUI/ConfigWizard.cpp:752 msgid "SLA materials" -msgstr "" +msgstr "SLA 레진" #: src/slic3r/GUI/ConfigWizard.cpp:755 #, boost-format @@ -754,19 +767,19 @@ msgstr "" #: src/slic3r/GUI/ConfigWizard.cpp:1175 msgid "Custom Printer Setup" -msgstr "사용자 지정 프린터 설정" +msgstr "커스텀 프린터 설정" #: src/slic3r/GUI/ConfigWizard.cpp:1175 msgid "Custom Printer" -msgstr "사용자 정의 프린터" +msgstr "커스텀 프린터" #: src/slic3r/GUI/ConfigWizard.cpp:1177 msgid "Define a custom printer profile" -msgstr "사용자 정의 프린터 프로필" +msgstr "커스텀 프린터 세팅 입력" #: src/slic3r/GUI/ConfigWizard.cpp:1179 msgid "Custom profile name:" -msgstr "사용자 정의 프로필 명칭:" +msgstr "커스텀 프로필 명칭:" #: src/slic3r/GUI/ConfigWizard.cpp:1206 msgid "Automatic updates" @@ -788,9 +801,9 @@ msgid "" "application startup (never during program usage). This is only a " "notification mechanisms, no automatic installation is done." msgstr "" -"활성화 된 경우 %s은 온라인의 새 버전을 확인합니다. 새 버전을 사용할 수 있게 " -"되면, 다음 응용 프로그램 시작시 알림이 표시됩니다 (프로그램 사용 중에는 절대" -"로 사용하지 마십시오).이것은 단순한 알림 일뿐 자동으로 설치가 되지 않습니다." +"활성화시 %s는 최신버전을 온라인에서 확인합니다. 새 버전이 있을시 " +"프로그램이 켜질 때 알림이 표시됩니다 (켜져있을 때는 알림이" +"표시되지 않습니다).자동으로 설치는 되지 않습니다." #: src/slic3r/GUI/ConfigWizard.cpp:1224 src/slic3r/GUI/Preferences.cpp:175 msgid "Update built-in Presets automatically" @@ -877,7 +890,7 @@ msgstr "" #: src/slic3r/GUI/ConfigWizard.cpp:1279 msgid "Simple mode" -msgstr "단순 모드" +msgstr "초보자 모드" #: src/slic3r/GUI/ConfigWizard.cpp:1280 msgid "Advanced mode" @@ -889,20 +902,20 @@ msgstr "전문가 모드" #: src/slic3r/GUI/ConfigWizard.cpp:1287 msgid "The size of the object can be specified in inches" -msgstr "" +msgstr "물체의 크기를 인치로 표시 가능합니다" #: src/slic3r/GUI/ConfigWizard.cpp:1288 msgid "Use inches" -msgstr "" +msgstr "인치 사용" #: src/slic3r/GUI/ConfigWizard.cpp:1322 msgid "Other Vendors" -msgstr "다른 공급 업체" +msgstr "다른 벤더" #: src/slic3r/GUI/ConfigWizard.cpp:1326 #, c-format, boost-format msgid "Pick another vendor supported by %s" -msgstr "%s가 지원하는 다른 공급 업체를 선택하십시오:" +msgstr "%s가 지원하는 다른 벤더를 선택하십시오:" #: src/slic3r/GUI/ConfigWizard.cpp:1357 msgid "Firmware Type" @@ -914,7 +927,7 @@ msgstr "펌웨어" #: src/slic3r/GUI/ConfigWizard.cpp:1361 msgid "Choose the type of firmware used by your printer." -msgstr "프린터에 업로드 할 펌웨어를 선택하세요." +msgstr "프린터가 사용하는 펌웨어를 선택하세요." #: src/slic3r/GUI/ConfigWizard.cpp:1396 msgid "Bed Shape and Size" @@ -922,13 +935,13 @@ msgstr "배드 모양과 크기" #: src/slic3r/GUI/ConfigWizard.cpp:1399 msgid "Set the shape of your printer's bed." -msgstr "프린터 배드모양을 설정하세요." +msgstr "프린터의 배드 모양을 설정하세요." #: src/slic3r/GUI/ConfigWizard.cpp:1433 src/slic3r/GUI/Field.cpp:255 #: src/slic3r/GUI/Field.cpp:324 src/slic3r/GUI/Field.cpp:1561 #: src/slic3r/GUI/GUI_ObjectLayers.cpp:435 msgid "Invalid numeric input." -msgstr "숫자 입력이 잘못 되었습니다." +msgstr "잘못된 수치." #: src/slic3r/GUI/ConfigWizard.cpp:1457 msgid "Filament and Nozzle Diameters" @@ -936,11 +949,11 @@ msgstr "필라멘트와 노즐 크기" #: src/slic3r/GUI/ConfigWizard.cpp:1457 msgid "Print Diameters" -msgstr "인쇄 직경" +msgstr "노즐,필라멘트 직경" #: src/slic3r/GUI/ConfigWizard.cpp:1472 msgid "Enter the diameter of your printer's hot end nozzle." -msgstr "핫 엔드 노즐 직경을 입력하십시오." +msgstr "노즐 직경을 입력하십시오." #: src/slic3r/GUI/ConfigWizard.cpp:1475 msgid "Nozzle Diameter:" @@ -948,15 +961,15 @@ msgstr "노즐 직경:" #: src/slic3r/GUI/ConfigWizard.cpp:1485 msgid "Enter the diameter of your filament." -msgstr "필라멘트의 직경을 입력하십시오." +msgstr "필라멘트 직경을 입력하십시오." #: src/slic3r/GUI/ConfigWizard.cpp:1486 msgid "" "Good precision is required, so use a caliper and do multiple measurements " "along the filament, then compute the average." msgstr "" -"정밀도가 필요하므로 캘리퍼를 사용하여 필라멘트를 따라 여러 번 측정 한 다음 평" -"균을 계산하십시오." +"정확한 수치가 필요하므로 버니어 캘리퍼로 여러번 측정하여" +"평균값을 입력하십시오." #: src/slic3r/GUI/ConfigWizard.cpp:1489 msgid "Filament Diameter:" @@ -964,7 +977,7 @@ msgstr "필라멘트 직경:" #: src/slic3r/GUI/ConfigWizard.cpp:1547 msgid "Nozzle and Bed Temperatures" -msgstr "" +msgstr "노즐, 베드 온도" #: src/slic3r/GUI/ConfigWizard.cpp:1547 msgid "Temperatures" @@ -992,14 +1005,14 @@ msgstr "°C" msgid "" "Enter the bed temperature needed for getting your filament to stick to your " "heated bed." -msgstr "필라멘트가 핫배드에 접착하는데 필요한 온도를 입력하십시오." +msgstr "필라멘트가 온열배드에 안착하는데 필요한 온도를 입력하십시오." #: src/slic3r/GUI/ConfigWizard.cpp:1578 msgid "" "A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have " "no heated bed." msgstr "" -"보통은 PLA의 경우 60 ° C이고 ABS의 경우 110 ° C입니다. 핫배드가 없는 경우에" +"보통 PLA는 60 ° C이고 ABS는 110 ° C입니다. 온열배드가 없는 경우에" "는 0으로 두십시오." #: src/slic3r/GUI/ConfigWizard.cpp:1581 @@ -1008,7 +1021,7 @@ msgstr "배드 온도 :" #: src/slic3r/GUI/ConfigWizard.cpp:2043 src/slic3r/GUI/ConfigWizard.cpp:2915 msgid "SLA Materials" -msgstr "SLA 재료" +msgstr "SLA 레진" #: src/slic3r/GUI/ConfigWizard.cpp:2097 msgid "FFF Technology Printers" @@ -1024,6 +1037,8 @@ msgid "" "Following printer profiles has no default filament: %1%Please select one " "manually." msgstr "" +"프린터 프로필에 기본 필라멘트가 없습니다: %1%하나를 " +"선택하십시오." #: src/slic3r/GUI/ConfigWizard.cpp:2339 #, boost-format @@ -1031,6 +1046,8 @@ msgid "" "Following printer profiles has no default material: %1%Please select one " "manually." msgstr "" +"프린터 프로필에 기본 레진이 없습니다: %1%하나를 " +"선택하십시오." #: src/slic3r/GUI/ConfigWizard.cpp:2340 src/slic3r/GUI/ConfigWizard.cpp:2438 #: src/slic3r/GUI/DoubleSlider.cpp:2522 src/slic3r/GUI/DoubleSlider.cpp:2543 From a68dcb68a34fa977d35bd3e28679482e63330066 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 26 Oct 2022 16:24:36 +0200 Subject: [PATCH 94/97] Localization: Updated NL dictionary + Fixed format issue from https://github.com/Prusa-Development/PrusaSlicerPrivate/commit/a856cb29e191baef50f489c7bd5f8fb0a2845376 --- resources/localization/ko/PrusaSlicer.mo | Bin 316508 -> 318843 bytes .../localization/ko/PrusaSlicer_ko_KR.po | 2 +- resources/localization/nl/PrusaSlicer.mo | Bin 469637 -> 499948 bytes resources/localization/nl/PrusaSlicer_nl.po | 2921 +++++++++-------- 4 files changed, 1583 insertions(+), 1340 deletions(-) diff --git a/resources/localization/ko/PrusaSlicer.mo b/resources/localization/ko/PrusaSlicer.mo index cfb41c85060cc3ad4b9368ceebef3eb0580f03fa..a1c7f87565f2f76630b151f684737eb5f0358764 100644 GIT binary patch delta 68750 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 Sv#QM=yUNd1>!GcZ$NvE-F0CB^ diff --git a/resources/localization/ko/PrusaSlicer_ko_KR.po b/resources/localization/ko/PrusaSlicer_ko_KR.po index f16c5e1ff..cd5e0fb4e 100644 --- a/resources/localization/ko/PrusaSlicer_ko_KR.po +++ b/resources/localization/ko/PrusaSlicer_ko_KR.po @@ -93,7 +93,7 @@ msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:84 #, boost-format msgid "PrusaSlicer has encountered a fatal error: \"%1%\"" -msgstr "슬라이서에 치명적인 오류가 발생했습니다" +msgstr "슬라이서에 치명적인 오류가 발생했습니다: \"%1%\"" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:85 msgid "" diff --git a/resources/localization/nl/PrusaSlicer.mo b/resources/localization/nl/PrusaSlicer.mo index 097521c94d98dcdbbe2bab6afab0b64380cc630f..2d12b74c611d65ef092e67308a5c505dc6ce74e6 100644 GIT binary patch delta 130782 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 H_fr4>ZEs7{ delta 103187 zcmXWkcc9MIAHea)y;lgys+4i~h0P|8`gX#D`%#PbJEAGc^cnB}S<9JyjkxYC~ z;Z`pEiAAyZ*er=+I0~=9Sm;pov;b@yRiZ;MI&$kbKrMa z9Dl=HSm3D;k>W_O6BY1M`cE{b;7r?KPQ2Y4I1~%uLwE&FLPPo*+QCXBgozE&ZD>bd zpzoi;O#B6nP~N9Q1e>7k^ueTScn1X?fevH>ZpHa{5^sAZ%&^U~VdTTnwH%KQ;5jUk zmPovUcD(VqEQz!D5!%s)=fj%6j}H73bfEj6&yq~!r*Mo5zW7IcFfl1SkPYpi0D8Y9 znoLzoltfVTVWq-1#DrTF0d_~25sfz@baHlSI% zJ-QEF>w~d=8tvdWwBZZLCQ4j7Ib1J^wpR&_Kn=`|jgk~x+w0Mh-img3S9CNwvuDr_ zW}pqti7rArTo&(dMBDiQZFdLy{E6uI@&3=~gp%he7_y7;#${8&3s;~cER3maMPF!) z4&=I6?|`Y$V=DCM42Po6jYbFX1lrzHu|5M?`efo23Xb?qbbqf$XY@JR;WualKcdf_ zMH{|==0wKS&|x0*xkAxm==-J7WUGX(ef@a92^MhwUq`_PZ;TK0M_;%TZSa1yt26CX+^PvqC zMYm@~bRezc^`7W}Zbx_1kXU~leg9c>A~U12(S8N|J^I3L z=nVft8_Y5-%qScBUO`L^6z#Yo7R0*f^BvH3y2X0$Y3%<(T)2e`zBoGGn1JTMM6|<~ z(GC_wmq*v4Gv10e{3*J|htLkcMsws88v0-2^^5598PnPShA!vyu!aTE1EmnUzpJ1P zHA4r`D%uGhSg+_UXvYIF6Ys&w_#B$lo3Iuh!cv%LMzB_rf*&?Fq8}CyxGPPBOAG}_=F z=+gXySun?}ED4VKL{7AR8Jatn$9i#eU?tI|DI4!ML?hN7?XP>do=n^l9~g#?@NqPh z&!P=Yj!uitioOz^8(kQEJ-R&lCK{nNXfAAweuS61|M$iV$D=<+|3qh){$dz$R&-!_ z(Scrp4lomqP$@LD)zOf*L361m`rh4`iNnzMr{ERt|CcEk`nS++_aPdpFVN5)MLYNv z4c*09zx1V0zY^`RBs$X?(Rye*P0*!mg(a~Anp49tX@_$uIMUVVjJM$i+>Tpt{L5Jq zw_(dyvLtHa>sTAVisqjklCv$=;rdv77B|Lvn^&_WT2W78AN&Z{V#zt|f3yA2oG_x_ z(0YlvA(Rc!ldcPTklc!y_#pb+OuQObqM_f8e(WAcCv*XQ?(%uzxpL@pEzqxWz2~w2 zZTLYh_~69&;KKOeJLmvDkN3Ytv-&?Ya{1?n5SKwiUnN=#JuezWTVN{tW4${T<9^>H z1=skIc;mU~40O%r#QJNoz7oxax6%Fm0h$y0&<>BFx$rHToaeAH{*7jTy#-;yEzo|G zH&QSu2B8l;hmLd>dQ#0rzm|W59#p5$NM&Cb%tSww>Y-WP9S!l#@%{r?j`~D=64#>b zHeHlzH<`Gdf+KkXZRiE`$Xtmg-7YMTHC_wX`=GmJG`bYeq6423uP?<+>YLGAID$s( zJi3$_i$lEt&UgP8r(i=LVkJC?4&aj4Lr6=ZFIGmAsxdm#uINnrpphAlnK&I&ON4`| ze}J}MZAp0lI&=azVlMapU9ZLuo3P>m*lcHf(@|@^?_Ix zpO5vo(Fq(uL;oup$%|-i6Cf&VUsE*H|4Q!5n68#(<=t-=D zzoSc5@y#%kYtb{l8#<6)I2;F~C*IeXTAEc^68*f6wQ+lrf&<9%R!E{E(ProfZ$n4? zEMAY((E%PqzYqL`ET(cDUIq2L;Qh>rXq8i_MF z0E?~(0~?1Os4qrmat=MgO0NwAY>DoMPH4vi&>XlQjoicNnm>zo;cV>W_y6o~hX#6~ z1L=>xFan*)cyukFjm|)4JP%E}6=+9qqmkT-&){J+a(Au^IWP=8=pM(KxF}U;{~e-` zN|tDj^&t`k&}~x|9eGo9S9HM|H~?LWS!l^ z(-e(N&uAaComvT0d>0+S1Mi0!J%!evLqk0e?O+)i zp|{bFHplu0=!8B&pF0ri$B?B>CVr$~=o24=f#gP$qaeP7MbQ^Gpfmpveg3m}{W!X> zf5cgs_roy2rRYGmqV0WzMs#=d`;_dz^pC=gBIuXNy66jC(FSfpbD=*vy%w6?-O>Alurxk} z&iD`cSj>O0$r*{&?K9PM(!naDHq1;E4Q=%+i+nE7d+Xr?Fcg{ z8EuaqJP)8VnU6Jb8#;i0F%t{!4A0d;BX&DF(9t*oU%=Uzb61wcJGcbxZ}4vRe2kN`wg&)`@b~>Lp}m+@C9@Y7eqIpA>V^`d>ZYr_-7#^H87KU6KszC&>7A{L%aiT z!C%lM>~bKye-jq-`~Te(9Kf@w3nT}+B%fe${0(iez~@;K6|o6c!a?XRnStiYYINy7 zMU(3smce{qgn`sY+v$s?Z~~Tb|1YCp1D~RwcE6z^%z7|b2;D}N(d=)CRqfhr7tmx%I~+R9iC%AtE=?=Uiha-- z_QwwR6dK~s@d`YKta0K`tc7L237eGJ`x|Dx}gJQCh(gAVX6bSWpr`YJRx4jy6uJAi-Vh0L$RQF3j<=u*5E-HOAge~wP9?Wrt@o_I6fgln;^ z`#;||VI~dGk==p5FbZA!=g=HkiFWiU8nN%u(Eo~eq!BT!j<WBdGaZm zI~TAq7CXZP-T!?lxEABRfNRkgzQ%@_?Z*&-7U(W$k2P>4&cmfR1v~x}a^e^|upe zntc7x0o{u()sxYs(Ou|n`4t`Dzi5Qg{zzs?G^3E`k1&(YXtoZC^(WDzc~SJk=*f6L z{aiS3ilYNZRZeH!r!q57C#>&fiCgTBn6XfCi=pLSl^GX**P?;i~Sj9Ru_v= z?~NXlR5rs5oY5k2`DUJNsBiw@wv=on1>{of=CehMy3 zUEo_TrjiAFas6L3WIg^1Sv>^PsXvA$(O4{mlhF>BVs>1Q?w0q^-Sb7fe+F&uZ>;40 z&&OY=cvLpUYB&r%Lgz(4MmzWk4RQ7?X{nhOM`u(H9Y|etcU*^Vzm8~b-Gb)8 zq3^B0q(|yz3NFFN@qxqWH=7f<853z~shMp-L%jo?!B^;j{=k>;vh*;JCFsoG#%Z_{ z-KMu?q@|K~IDSukYDQWz70O4lh8M=5Yd8a4g4xj}m`QytI?&J2Q2&62_D{5fT$hCF zh0%dlKnL0oooF}oxtr1C9db!BJn%%k@e&%!rDy}|(1C128~6O!LtXr;XSS58(}1CT|$%!`PMjbZm{^<1<)2Us~$O-i%4N+j$C( z=pxp`-1*Z|CtoWxDPP5jxEgC>^8z86MxX;(fJSB$8o>i-=>J07D_Jlt(GvSa=b;DH zcLmx1uKjOZFxfJ$2y1*bI^)u4($tLgW>}7TM>H26M7Po7==)RA&yLqIE3QB%xH`HK zD^q_Djr1>9u>XA_yO@pbb2L?Qksi!aaB+ zmMwU?k(rkI!{p%N zVTO~i0@s(K`}qJGsUzsxU%)-V7{o`KGA1J=g_SdK_uUM9`I<4M#no0j+)pTZY$ zRJkyL0_EBNExAyld>HXSY)^e0I-`B)Ous;r^P6ba3L!$J(6w!j&gdrez`7S5&{#D4 z=S0`yW7K!x0_;#R85;VnV#w;tD}|2gprO414e0~XXVL7Pk9M>KUAnF4w)-EtwtLa% zK11LC5?zY3=zw!p4xbUFk`!FiTIh@$V`^VxbLt(@flNj_dJ{8oJ-Wt+;`Q_B(*1`W z@v)E~l1e*Vv;;DfuOKSVF78LpQ=m!Ju{hBu>2 zF#;XnqiDy^qe-{|ugCY$wa;BE%)AtOlvhQU{8r3O|A|2qJn2TFXZS+2p>^no&ki(n z-=G8d6>T_2?T~btXk;p&k!Xo_FbG}R;b{9wbZM8N?Qg`?@Bi$k;5U}zXcqq#>;J@h zt~#N^E70dEqYX7icR~Aj{Z?e#Cx)Q|9gep544PZh&;#yeY=E!VVgDQ2lU(rN$fz4a z-UKU9?~Q)r8HY7-9Xi7^=#u5F7oMw(u5mB)`UG@I=11SdOzOwb&xo}8;oz!PpZ#z0 zb>l)cd??;nh7M>q+F_0c;eHjggMR4A_Yj)hvt#{3^!e|w9_DTsc26sGqW!QnCb2mF z&q7rS-{ZAds8L9go6s3eM33f8m^uN`hO#yektvPd?}di^@p%13^nBQfw*Lb<@T;4I zdL48ZBnMFN$S<{V@}tMmty< zuOCF)%hECoxD@()Z!AXtiN`6JMDx%Ww_!ItkFH(2R^k3b=suo@?uy;$+NZS+?^QzE z=^Y(|PG~VY^N-Mh|AsC}-Zt!iN78_T9rs5=I|&=&Dzt;2(44rcZRof?+HexR{tEj3 z-gx~#G;&3*4@=V+ZLdGt-fVONA79V@x5Eou@YAnIyKqpH#N5;?pa(`h%z-Vj6?Ve9 zSx7!K%XhU8xpNvbsHb-b&u2$ZzOLQ82KU=r3MAx88P#ryruftCG2)YY) zVqW+E847-!UPQM~!R{dLqY=0X9l&sO0He_DISze)4f@;;bfzcK_fMk}_z%tc zJU4{h&>Zu-{~x7bsHdQ7^b*>@8Z=a!(2&22=E`>5fk&|`F7FXc^b8-X-O%@*LnApA zUE;au5-vk0vL2JQC~T!L41Y&I1qbvBA3`(m1?t<-FBmu9n3focqoTi|NjSK77{GJ5 znEEj^LQmfmz9~&d&x6@G0Joq^Te=VX--he-2_x=;&8hc5M>s3`DmsIOmLMdv!sx`VRE{`_MIh6rJHXwBw0b8fT)rXbWE9{{NK1gIxFl z9eMu&@q|LxY9tz=F=$f05bwW;cJykzz6gD8S-id#D^ULkGx1mS;K_Gq$f4?()BS%V zg;ID2nuJedUz~~V<8$ahF2ws81H(Y`VMDH$MBC|yM&xUBU}w>Y{EfDM$z35=Dn_rx zq#gF8;7N7|K7`ZIj`I!*GrA&L9t~Y1G^Ab7_xqq74MKBe1Ul2#(WLwsoxpMQ2tJK2 zO~b+Le_w1dI5gM^-B!KP0gT2nm_!G-2u-E~X!0FJ2k=ky(z`f(OJa=s~guU6SqSj6TPen06o8PlVpWuc@ye9)A2b z>;AOFDe7mj9UgojOr-RPw8XvC2jT$y0=Hw+2h$RBG3Us%#B%rlatfY+H;oEEbSgAD zd`<3)MrJDZ#NF5$%RUsm7bj7F8z06t52qz=#}DxPliJV;2uY4|aQ04iM3$LR~wh~w2Va&wmCWWMY150@w&9TH}_P_hJ9)!Tk=&Cnd`guZ_V`ra6H8!kehTaBIz?@wX> zyAQwOf-n4nexJ`ZHH@%4djD25Dep%QpvTY;mucuWT!Cfq9dy9oq64`Q>v>-Y+qyIk z;(BfLvtZ5(tbIiaAH@s5q1)}MY2hSmgVm`&gpPbU=D|&9M?26697Q|)79HTf=nV5s z4}U_cgr11Epc6`>6P{wh23EuyYtRpwkI{y9#rr4F8UKI|Abm#YAU}GtRmKc#f<~%E zyxs}xQ@;fr;EQOxE3q;rKcrxTXVDR*&rD17$6}a{kD~(|kABQfjJ}Aj>1$|*YtZC- z7oEVFSpN+@>x;|^&y_|8QXM%N`Tai%hNcTTkfG?BK8A*H3Od7OXvjCC&+SE*<`^28 z{4a**E1?5zh2^jtI^&1YFF2FY{?=eu_y2whW_{V0LPJf_@AJK|4&EDG6z_k9Hgpc_ z;ngpP8FoWEz6p)UKs18)qi6p}%)~L+1LxtV?*G3ixE4EK2}kDzypDR4*&*~#pu6H} zbjGit-YT z=$clI^+sp|EzypMt_b9CR|fM)XmbRR#4Cg(JCfUB_;ZbUj}|wiGgP9i|Qz^aT7JomtLh!Tjg|uE9(!gN?8?I)Jh0c`_M`;ag}spQ7#l zfG%Oy<>C6(%h~_Vq&gRTpedRQz0rf@ZY+A{$`i|&<6>qALnz? znXX53;zKldzCd@u@0iEW|E#OSx7vcynrN1GK?gDbQyrn%{tTMcA4k8Ao{Q#Q6AqqI zScT`VNB?LxD*AGCtI~hsI0dsk>)N!$7%Y#aa4A;CJ?M-t;+8D@hX`+n`lsu{%)XEQ z6V0cyoLR5mJZ>9Sv?X>rt$H?r_lpuI+_cM(1E^* zo^)TJ$@VvzT=}+!+^K?wz6Cn){^)PRJc};ntan1PzmcTi40oeT@h3X6OWzF*mqQz9 zhITLr9l&^Ww=72ouoE-!6jsDb-U|_^fkrHePH;RLp%>5-H@TRCFT5Z9IeN|eVL+|X zncj#F>>+e%W}!>?h$1N()R@ zVI916 zJNw`4pUDMB_AVN-udpZ(9J9-)`;8b+iY(d{Y zg6+vfI3LR)WG~~VF{oB!P zIT%gmQRotmMcbQ-=0DXkESb8%-_f9Haghk-Oh+if4c8Qr!+kF)=~ zQ<%;L*XT6bV51Y^hs^EK5x;;oFdJ=nCHf(?E#5zaM&um&T>8ln$%4^x==Fxt4(QVL zOU4I=q6fnh=-SLj2l95T{|{Z7!)PRaLI?0KnhTkyLI-uR3iY;V@{L4u>1Fhs`4p?- z1vH716~768Jg$i*S!cWj$D%KuLT6m~+t5L6^nhxOyRZ)y#){vC?+0zL8ubBa2Q$!6 zzl!dX57C_1g?yh#CO)HJvYbU{_#fI}zVE|>RnZ)&gKo=4XsEBpde{>k&?Gcs3(=0( zpzUr(L%%K752F$L7B6%EU!>rOa{mxID1eT%1eU{!=)k&1??gKojV{&W=r^P3(beel z`_PG;LOV=59ZtSN=r*m5CFwuWoq}ioNOUGs(U~kpJ6IR%JJ6;05`FFuw1Z2}gb$^% z=s?<|As>X^pA_#ekM(`A{xc?hpumrzf$C_zN30Kz^_j80Ce{zd`uSKd{8MotU;0bPf&6H)U5zGBCfZOXbmR@tPp|ga00&}i zoR99FgV+Ic{~Ff(CbZ-G(Ea`hx^&OswKzXX!O!DU=!eD+Xh&(kg>6&-%TRBL4)ktx zfDfPzJ%mPNQmoI3^)+ZjK13(5GhRQ8>#3i{3Yc7YHZ-^g4dnqe>ApvI#lP4LGky;< zX^A%68I4dc?1;BxcYF(*WA;D7Ptn@pbJSO04{Urcy#E5SG|9xv6zq5r8mjg94t{`k zFz$Sa(B$ZJG?Xu)ky?hHbX(ANj^kbU2fFS0{TU)O0}L*X=HCh zcgaX};L8%}$<#@;p9|M<;csk*O|zt@lI$5Yv{TU;tVNeZ%oHQ)aRiQ$ekVlC zS7005gb!lY>|utFpb?xBeF^P&0Xl&d=m6hM-Dm&rpx}r;LpwN$?$dMVnij~Bo=Vc1 z=!eve_%Tk!F4*VNFo6{~m->e|9`DQe= zkA4`9MjK9|5t)ESWEMJrx6$Xepbfo`c5oP7vLDc7K8Iz~_=C&kVL-L=g(YYk?URrF zZ&u&S1si@0oxucjq%-1!ub^wX5*^TbGzqt(1NZ`c{ugv$=g>93fVP)4e|n-B7Q}wo z1H0nd{Oo^|AxD8Q@&eIJbYPXxZ!nF~hOb2rq*mwv?n0j%fhO?;G;*`!^+o7*dmB^V z4{!|i+y&DUerP?Pq~Hs0p)=fwHv9oP)4k|go&=s8dYOXKzEb3@T3 z8G}Y(5}N&U(A;?ojd*e=1-H|FEQ80}Xs8@IkoxFA+MyA+1%2-cG&d%r9le2e zv=uAhKJ>kR(In1yRj5})2AWJXqu`9YpfkA{O`g$cNGG8)dJX+9xDNfa`xHI=$C-Ek9#wXDaz0)|{@BcZk4j(>Aw1E%M7x$qr9LB16Jeu#C^wf{# zn@5MDGoOt{a0NQ>&(P#N6U`_bmbx%H;Zm6T?|-UOaKufq9CkrxJlY5F8FWCi&`{2c z_uob%^)Z&hBj|wBi-g^9Df<59Xf6~*->-}gxH%@h(UpSx_SSgg9(3f7pbbpJ$~YfA zqW7XpaSDAdQ8etLtI&y*#MGZ~(1~=%c6bMN#5d9Y62;j6jyPAbkSzJp2AiPS+yhOn zhtUzgh;E}L=zc$fPT*v$pG6z~3k`AB%#e)v&~08CTVOTxD8D~5nV$Mn?g}pW;wdzH ze~o4o4>P_JomoY+XbgkD&wn5nb!Q1m*@bR6F;K^$x|*wt{~cA5$uGe&=B8`wmS};$YgZ; z&POBgHYRQO6AF&>1o~<97dpdh$_LA#*;^ZZu}QQGI^f&U5D!7ucsx3w_t61-f^NS9 z=u#a+2XwJK``IrXsACM&s3SvNg#4}MoKJ<&f&p+^J0 zjpF)-%p7wyPEY*?^A%0fQ~w>{?`T79nuY=PYZj7i6i%bVSJ2Scyf)l#gyvEY^!jjY ziVvepunFCsJ22^mPbnC>6WAE@G!I{wJD}H}!L|5q%8nP=5*QU^(jJ(Ik5V zO~(CL8Bd@M=4}(+Z->tCHq69H=s@0#_jjQECQnlEV93=rM4|?|JvyNS8I7*pl;|w< zK$(L%@lEu(&Da`0Lzkq;^Oa1@#} zW8(Gk=o-I-CgX=_!`sn=W*?eMUqye2*UzKvq_q$4Wy75A|J)QDVIlOza%e-f(HEMb z9ks-M*bc|yQf$SxtkxlXV|uV-cy0+A>Xm5DY)2>fGkUOP?-U}K59`o>q9lc?*c-dz z(`abFir2qKBT=YxNV4MSK&zrl(J0nCpu3|tcEG#Qq}+f$_c1!5gXk_ef=Ne|wM$68 zJm`bP&?B@O8i|ha`i*F)2cho`L)ZEtw4BlQ3}plMhGSEC2dw>T1u-W>W(&Y<9$o;Fb`^b0d8h|PKK z8uWm=8I9OTG>Mm>Idll!Ri|V9?^w@yOUSjWk;o+z6(|^@A(%=g^u@8UJ{1k+%jk3S z(C-OvpdViEqXYRAjo^>SKbn|GzcobiLjN$qD{l)HMVGF0O7>r63N~CD&EDqd09vCV z?15(Y184}JKnJt|9l$%d8TX)(eD3zJYo?(SniG8;dr)76_Lt)hcA5MC3JX{ZZLlla zVShA~L(zRb2A$zlGy<=qA6}c$1`nane~TVKe_{h7k$*rqSv%eta_B)c$6m(N_y2Vi zobg_~0*|5{oJVJnb6_~Z3ZMh1j6JahX5vfeL^j3yjqVD&rYky;k!ZxmqZ54=jqH-U z*#BiHY~zA6_ywKmzvuyyXHXbW3AE$-=$F+_*cb1^XK@eSfqe&uC3qk0_!D%kzrcxj z6zzD(-J!pUcPB$fFLJ^Cy&R3i=jb;21`XvU_XLZh$<`>=d!T3hFigj3=n~9AL%aly z*<$-LWiA!}~G0nL;HBHHN0A{@%VHK0~}w_F^GqwolJ!CoW6E?9!&-2Yh~4Bv7mU~aPYFdCxoM}`?+ zGAhipaI_}+h2whkW3@l}ec>TALX*&tFT&Bd0n1_S(cv@WX8fM|Vr*jCPI)M-{azfz z04_uaJQ5C*IzeLyc&avT)e-s;0-+)cr2rAeu(q&b9BZJO$qHBMGu^_I045@O;7!HU5*#R^YgJY z*IOs2h2)rtHnaZSwzqvTI2WDiU+BS+^QDknHL(r#N3bJ)ghrs? z%b}goIEVU4G=k$^2?Ko<9l)wkPbNO1P@M}0(X%<{?DW*XVp#z*sXvCraW1}y?_+bk z`PGn|bJ2mliALfe8i6xtcz5lET2h7+v!e6Bv$BEn@yfQuUJNN&3lXHXm zPpiU#m2Gv1#C7Ozy&u3E@x+>tQ;pwFPpsp;X>YUtPgCf)E_^L+us(b^yon9Co_#|& zDqEmQv=D3KRvd$Wpa<528^h8}!iLo6;VL|W!*KGZ^u*hkzBxpABi>8B_LgK=i-lX# zQ@?s$WNUirzh)nZl^Aiycfwi!(7U0dYu-zJY9(r+OR)hBefIa$`Q-%;D%^q%KS)pP z!N0LLZvQYmSLUOT>^I}BT%VMr@HvIw@Hl?*zx2dKT>f!-Vk$1%7S^=cC*fx}58_j4 z{OlGp@YAZfLm2r%FoxPbRw-@{)*JAu6qHZ?zrnfII|zd4C)gx2TqCgm(dx|#a(#c=V8}e`$e!P&gc3)==RNf zF!fCSiXVMDYKAq7BOh6xe9@Ft9bj@Ey2lgXgf#>jY%>8B9-$l`m zYNFShVGZmO>tmzSuz>r25e2^UG#{o`c)Wt3$()?crErrzZc9!2Yv({=yzy` zzsGvkBOwBL(dVv0BU1vO#j=>R!POKz>CVLma(*4Q)8*(4OJI4dkDh$DVQCzLzV{lo z$4%%&avTi@R37|>dJ%jbGmeELeg!(AoyXY!uGs-Dm;=9IP0W2foYB`}CiT0~{XY>+ z($}yQZjbd}usHPsC&Ke}(C2!h$@WmZz6EXnYb=NPPbNbWH98qOya8?aG0en;=r^H{ z(6j$XGy?x&MJ#eEoc(RF1ogYn2t13I;S_Xf=Auim9G&^wvHngn7Cu3PXma<%D=~RzyphDRTv!lq z?1u z&4tI&hGwHnu@Puw84R5p08YG!%XRIrIQp5bJNFUr=^p zUibe$6zur2A44*h$70l*qvt?>%!4D)nT$sVFh5@39sL@Oz|UyX<^3tNTLc|YJ#;`l z(6zrCQ~&>;V=1I@VIrDLlhFM=7oGY4@D|*Su6ebe!}a=j3H58SHnu_Ee-s_~^Vk&U zqf2=L-G14B3A>@uFYJFq(~1jb>y7coaJ-fJBj`-NMz`T<^uWpYYsl)d=!|=z9gaq4 z_8hwX7NIjjDC@L6+2_zvtg$F z(Q{!qI+1bc0B6SQ%VT|OyuUxzzeT^o{gb5Ne!ukhu)p)5GbxN7B-PNRsDmbDA2d=U zF%zd?CwvP%qSOBf2U%rw=DjczhoHOYc{CT^LMN8oOTh^Ij)v@?SkHMbtaW}gWQEaz zRf_dy=mFFjU7AtoMCPC!EJO$TK6>(fg-+;O^t0keZ0P=Pa6X(|Bhb*zN0VwDR>!UA z%zs5s$lQO1nOucF-w5rn3z~fQqDlG~=EA4&TAYse;}_TsJN)H<*?)5>*zh9s2z?Wc z#1S+yKcX}E1D(NtxHJntHUB#d^vw(5xpn9gyn{yM2XtV0{|S+*iuI}Y!e?*_&Y=H9 z(SOqu`*1D#r`e}2hA*A-|Dz+Ww_u#p@n`%I$7jh%e2Vv{Wu*Sp+a^6D^<8lznzWrV zGExJ*7oFGxXk;FX*C)s8GcehR8}lfbEMKGFaQ;9;+aha5>d%6`&~3O9U7Ag?{yy5l zZgkfiik?N2E7v6%sgtfMR;GS4deA+KZqwH;$w;PN+{y(*_C>t$U-ZgsVPI9!WNL|? zfc-GFEzyo&#p1XQUGqcHZ_#b`JDQwn*~5Udp%c!RJ(-a@!^?5O7hA^%x}yW?7atrS zeHk6dDl|EF#OoJvAoT({h#*V&2>RKu{nCuo`S1horTz~(;9WVx``;!hm~4e|Wu%VK za_G!%Mng3QP0E+iWZH&y{4IK<7RsHGIv<*#OLjX}#ZfpFU&q?m__DBc!!e!uXzY#2 zM=4}dIEcj=`QO-*dg046Qa{V-j}9P5-i*{AQtM!2>Qk{M?!_9IHD5;RpIE4e|50Cq zKDQ=+M(UsIKZ{kUw=WR-dk7gQzyD96DK~atd(2fZBlYd}MqEaHI<~_WS7fAqs`We? zfrID)l=I49Eu2ifFS^Zs#uixqs*r5=U_a{PunV5T1@8a4g)&kH#+T7k=)V66O`3D@ z`UUhH$hbP(zYINSu88$wv0fH)alIP4YZ^z};Rj<&=8(RUnpNVBlUB@JJ9+9bZw8K=ff36LRK$F532Xk?Ya%~;vRJEkK-o%29t(r ze$lYTU*R_Dmln%No%Q?Cr1}nByMNJrTq!ebx8CUXdKf*TpGH3`7NH%lMI*QwJ%HXr zBls!0jlard|GN+W=7QVh^5UW4s_4gY7xYLSh)r+``YE;-&GJ9cNR=)T-tU19@BwtU z%tt5iE>^$;XwGCQ8QQ5(GMSP3&eo9&Ww|i|-F~yt0lbTu_(k-u=+&jdKpUdlurIoe zA4dl~8Pjkh8u~5h_k%rX&YVCeawbW^5M5q6SP=75zZ%_kRnTO-2@Tn8=mGQ?8u}N} z17$H9$#>)R&(S3~g+}%a8j+l3!u9g#M3dDhc!qb2H*Q8pcn2EdA!rC^qceC59r;IS z!#|+URUgfT-snU|#OqI^16YdYz&0dF`TrlKV220ejpJy@&Y&G$L_?dmTu7qK zX!U6GXgBn^+tK%jqq|^S^ci$XC!+&cjL*9NS5YtmP0NSST#qhE7qp=s$ibKx81D~? z_lKb~8i5Y<1+>H0(6wKVMrb8g#NBAfFW?VYv_eMWPWOL~iW!M7a2UE~*H;QN>4Jv( z7IgcJ#WFYzGjS6-z^~%{pRpzNMCFhRtuOF|%{!iz^_gpY} zene;V54u(vRYMk+MVFu<+HrgI`QGTj?m`E4Kl=V?^ts2;-7pD_;Iil&=yR)*6l`Dv zy0#yp2gn(8`&>~i%%}o7fa=la=y}i$P0pd{%nzEZWf|bbvF_T`(Kn1<5t> z##S`sA7W+PhaN16>fuZ;gyu$hbRgHp>s`^1_DA2pA4}mFG_s4(NGw4IvJQRz9i*LP zVjl%ht`q1s`U@Rtt{Nc{`OpR%VGFz!y}uBh=~6VbYvc9xXbx>fm*``3z@MW_`W@O& z&YBL0{a2KNp(ulXdR0RkZiaT;4h{8CG;|ZunJhpXUWU$aGiKs;^!+pFK+dDjCu)U$ zvZDjZr|$pS6l}N`y5{$yBYXlqV5XxpUX5n;hIoA|ruIMD(a&fO{E2q>A3CsXwZni4 zqWxr|OIa6F|NT#=c;hCl$&LH40xm*3+>1u&0NUVDH0gdqL;nl9)@RXwsPsJ6rCzyi zck@Fq-ekde3z`{BL#K90hg4a4>2 zSd4n!MqvQe(Ft_NzSs|4!p-Ojdl;MJIjn{Cla0d~4nafw7?#AR(fzw3Uf+P`#0T;E z=V-2+i1qVm$LUSNQG6NNVL3E{Ezlfkhc03FXtF;Avv?@FCSx!YCu3Dyg@*Jv+EA9J z;q1?Wp7}-58P>x+*aHt^*=FJW^lQWOHPQFmpabZP&G_$66E{<^<5yb{2^Q%MG!pM# z7uI|`I`X~fj1Qra_z8_rmX=}WxzUMSg(g{fw4G+?uDBjO54xZqUbo<-?*9iU*zjX$ zw!eVBFbf^=oOpdP+QIU8eG3}$kI<8EFZ%uwG^tOZ@Be{L;NN)tl2+mRRqFoFq+kQ( z(37xsthe(4>b=mB--3qjUM!A}pzpnk?&oFb`yZe)KZJIC5^d+lSpNf!;6+UR|9|Ff z9bPDmo^)l<7g|Kyp$&D5^*(4MZbv(M09}eC8p&tSH@f7XwT}D$ zRDAF}+EGTE@L+Cqr1{Y$DT6j#8SS_Y8i}rGNB5)8jX^tl5^e9fcz*`EWDC%weWOh> zJg}1szIXr~=~rl|PoZmi7VY3qG}Kw!hWCnKYGCN|mC=DUL?hA?jYyYx{|+>A!_juf zCMh_gr_hnUfS!b}pdHSQu0WsLh&J#M+TrJDL#NOVe?W8KT)dy_`mlYkMsuh>+D;pE z;K^ZKxL-kzwdSMxz~#MbG-@(cD;tKKEg~zYFd7Ao||1c>i>)|Bf!< zMZCoQe?|Kc!XjwWlt3d<4jpM7G$O6g(BBXpfOhmCdf+6{flo)5 zLw^3vKBeH7#3NV{|He!#(;*}E7m}^94E0B`EH1=SxDzYjZ)nFwI)>-!pu3_kR>fqj zzk%k~UUUGTVe0$;R}@T!ljw|3qcgsMhWye_VY?JW>ow7wXoBWWJ9MUf(RS`cBQ*rG zU=sbFFecW=qWz5T#Qyhz>0EGRGtvG1GP<^Jqa*zUU6OtA`qAiFwBt)Uhmc>6wo@=# zC|V?1JX$(hzBBvZ7b?dK)uXl1T&R!Ev}v?G`r&b7tPhDk6nzq%&@^kR)Yq)+HmZx42t7B7i0K?FveF~lNLfnn7;ivduw~W+Z z;k4@>em=MyTX6j-HpNmmr0U7U?G(JQ7}w%=*brask&*fj3J&9K)JyivNc|4S1a$v5 z?G<)Me@x97o!L@!W}DDZ@4!qvjz%{9#*kx$@pAWn-FTr58q(X)2S>*1lhKBkq5FI@ z+VFn#xgXG^P469^D~b-FDtf;uy2QQE_U}fQatvm5|4*b~l1xRjeP(n{bTPWNE76YD zqch%*uI)GIw)_KqE^$*Z2fDQR&?GK|=0q8Ech$nw-~Y9y;0!yX4cv+@$slwlOVE$Y z4d~3jz^eEYI)I{mLc^8O1F9jG#@oM8WQ;tDjGKaSUb=)?YZM0xs#2a2FcRTpbw zHyn=>(3A19n?u75(1F~7HaH?)pNYe%Z$aO$(J!2c1JILf5v;t)Zi)Scdv=G_)_G?=454dl#MPA#@49LkFIo z>>p-U8l6!W?16Wp4{kw2cMu)GS#<5Q-xhYk)#%bS#$wnJO|BuBiKC-0p&f3*)MpLW zr=I+qf@@vt_Rzrn=nP&!XYd-j{WhZKL$NzT2c^*gS4W?3jW*mH&504{FQ1RYlGNWB z5JG+u9q{j%`uxv&XZ&+Mn#GOKnYNDgK4`~xqTBDDcz-M!+F9tqvI<+{IjoCK28N{> zffcAPL6i2gcs=V~PLTaqpMoRqfNqmMXo&B^YWNTu(q-sO)}f*P5?kRv=zA>&1v^Lk zqLCSd4e$|kSFA%Ldj!dUKEEj#lJhtLvkwk`Q!xo^Q~w3;z%qA-lkr)!p^q^We~R@y z_k<*@jUM5Bus;q!2e=Jg>H}z`uNcDqcO=Cr_(E-TO`D?o_6BT%qtOFuJ$gQDL7)2{ zI>WE97XE@mu=Kql*# zySuiX+O};gwQZi-y5B!*^*(2woBPgMv#i-^(;QSs_uB9URQZoPvH!K--w`nrV|6wK zuR<-h&8Rs)g?aFe4X5j3dJu{l%Ic^oY=l}Hovo8lyJ911=+B}SZH%sFs?vJ6(W)(v z>d7=z%U7f3_yDQ_Pf;18b~7hfYE*-QQTckKIx+#3ZXK#Y$59QqZl8Zbf5P#)n{=L> z+~_DQk1D7>s-Uhm!DQ6QwHp86PTVAWm;M!*W_i&ao_T?f^%4pt9p zjg3dW3l=zG538CRWjuviY!|T;K0r0BY9I4fYk_LucvSlNHoP1)=Q~ilUVYFMng6hH8dMgJ=%s^&AU(;_oI4t47EmXqDJT~s=)84-4v;x zF%@bg3!(DWKy{!E=EA<{{QmDv+^C`>s3AIu+8(!T{4>;Ucx~e&_BSIC3)S;PsBN1E zw_-4AEqz6m8^gY3tyR{>k-uJ z`3|bVUr|#PagbRHQBgfEi<+`3I1yW-Gi4rblp)e!lQ1S~4ill?V%f1Aw!t*G!^Yo5 zt(k;F%t++N0K%0}@A;mn^fNIl?#Imd9s@DOP_rmKq1@=TI23c@7R-PztqF&jo)p8> z#1BFhybLSi8Ps|4&v4U#f>@MrQ%tLLsC?n5A^(UOG2sZOAs$z8ZnTO!pcYv#)SOO0 z4e5OQd^Ktgw_+tcgsLF^NMk`%!<(TRI1#hpHY|+KP-`dMD3iVfX3_h<9XA^Cxu^qW z2lm0sxEL#qj_6v8pHV$nGR7RC$5AKRb5wksv8Ljjn4WMQ%!WfzQ?lK91J$sfQu{yC zIJ1rFV*|qdQ5o*oc-MIIht&S49+pQH&>OSkD%42aMJ+1d31;NtTT`Gv@tIKDt~_d_ z8>92@e|vGGeLonrN~fZRdL?T0ZbQxCe$rr!k82#|64f{?tM`|D{ zemHV(oHkvl=N_;ecQ@LOt5Z3weX64{?mq2O*ipnP}`^j>KvGj zn!^34HE;nn#P={izC*p7GR-g}*w*Tq$BpLb0%|C4qmI_ssFp?uvj+yMp`lm;yI>IR zKpkMuF(SUj5d4H%JHa!}RFy@gYld1IZLyu+|7*EXi<8YVt27XGVuheqbwAV`F2Q_w z7d6C5W}8J=3^mk^Q6n-DGvQv$fv-^MQ_L|_P!=_HgVFi#|1IK18Ln6(&NXwE4b{Vz zsBJdGhEJmk{E1qOY37-ux+ZGGCZO`IMJ?((sO|azH6^L%n+D`W=jZ>capS{-)~J!_ zh&qV+qZZ#x)Z8vbjl_1;T)szb)2s_jxh zBHD&ZcoWsqe{J{=s-QRv%}^&o&1ET6MdeZFK{M2x55XEZ7PYn>Ti;?D!aq2XOqE5)Ti%o?&u@2!vsHvQ3&Qq9n@#P zgiDM;OFX6rrHIh}u8+g9Gpc9bu{y?HYW_a2CF*QHidqwIQ4LMK%;YbKIv*;bMzkwx z4a`7|%o^0tZ^fG4`k=y13vQCGG}~_QYO{F4FarrspmxD0EP=__m{r~c)qn{$d>EDf z9XjdOnkj068j;qh#X195;whYop6=^Ri(;=gLzNt_@*ogZp!WvzJTv+e&W9SpI;gY1 z3zo$`s1x)kYRDsOG)HzU)Vbn^+8sftHBb(D?R#8ZxKTmONxn7ML!c(Y& z=^ko(eZ~(okHWSW`N?#Loe)PnC)bCng6Zma6 z35uY4*bsB$1k|EBjsf@*)!;Zg%!kh$s6|;G1FD%pQ2xKTwpQ5j33 zT3QQLU>6%d7`4i$p+9a!6?74GFug#n4cAFiQ9M-s0Mzqbs3SWR)t~|B{P#a*=!S^Z zSP}Q5MkMwr(~tmExQMkHs=(IP0jLg4LrvK-%!ijy`C^_nukF;RHBklCk+$f3{vW|j zX(A?}=KdzCz{qFJOCt%Yf#p!!su60jwnrUIJy1hC6IJkP)Ce9&t(6D%`D^S*_z!BW zbv(=d*O55-tjV|vRq-)Y#%rjGAEH|R1>0k+a5MBCtVMVlYVo~A4SCFS<|Oq)ZRf(M zHBt^oU=36U?ww=*>i~LAgpSte=gp$YffWeXLlv+FwQY8xGG0Ty6YirwK0}og`GV;{ zCe(-&!W38$wP-uzc)_2QA2+V)sW|?#p=FfDoTu5 z2xmYIZCzBudtfmfidw8E?DKo5k@NiErV=;RE}M+YupHros1q*Mm59zS76?U+$P3g^ z=DTY4cX7-^xC++AF&Gt}<7oVZddCdBW>)_;R0m%o9p?AHTsNO^@?&})G{I~*7WIU~x4QE6RZFbZUHns76Pz@S{>hT!VoX$Ya`2tk>L)Z#$pw>k3TP9z1boPHkZuG5F zS5%LCVJ_T;s^DK#kG|S)q}yhZ#zE!FkLo}Xtd1oy2hKsQp$n)UUq_9=Yb=f5(W6CI z?2Z|#ny7u-3f1z#s3Dz+`VhJt)w6S`p1wzI-yf*$m-Mb#ocXX2;TotZn1*`WEwkY> zsKtBdF8f~fx8Ic9ojw)>Qa9`nuSE+W*k*O(4tJ~BPZgW7I2FaW#S@GSd$JL-2jUd59< zfB)E|-}A(jcLO!_KT-QX)>G4=c&Gy{n}-|i!w^&hmY^y;hHA(o)X+wMW(vrNT5MTR zpL7bK=Cn8}T_sdQJEAI>Ze5R>$`hz`S5a%m^Vuf&iJHs!&&?3#L=ANaYKmH**2+NC zZkUFFcnsCU_gDtqFU(7&<|24a0JF0=7u`0%SV|GIe)CjdjHLxeDA&XEWdIEJqzDMWx|3-gn zR(mSc(Bwlco?56gy)_QPakv*_zcW2JhuUUeQ3YpuZw{L3=W*bfw_9_F$ZMHN^c_3619rp6hl^J9+J1b|mUdpM{#cO{fCypfbKf&0T~q=7Y*VsOO>9+Ng9b zQ62H1rf?r>5kIuge_;l~@xHSEHB?2unuDYnYKUfGX55P*_!=`}-f!mZ)f_eF3#=zm z+wOxk$#+w6u(cUR;rU2ZN2j2+`%(`#nu6n~ljDmS4D+K)Qn&Z8>4 zj>Yg9YVl zQwROA7b?R{>qS($XkI?fYdSBcC)^SBGMb5ceh!uI7plRzynURJu7a5e_fS~-e+f5Q zJSVUSzQshCHG+>bcV$tZ3!0*aZXv42hf(|e6&Apth(69Ir?#jmorl_nhfs_9E^4=V z`}nwYBIZDk64d5K+oe0|1l)?M@G|DcxNaY3C__*ghoXAC3RUoBRQj)|hUM}#4Xuw# zKLE8`mZGL89F^{eFF*g(l$%tM%m|D??fZ483T~i23r37=GUmo0!cDA`QLFtBs>gRw z6?jK66=p`2R|9k5Ak;|hKrQaiQ9P#NG*L|fp{SO1M74YdYRLAYGQ359^owSm7eUQ! zb5sKcqbgj6>hT3sz8~n1fzf@Ow`T~dBYi#GXvkNidVI?I3Dpq47-j@Yp^oIp2ehX304P%mQ%6I^k@jB{+e1kgEf1(DL6zrD%KrD~CK)$cwFOZXv_!4uZm6Dw+4zkZneaZ;k$eI*#MkWem#C5ZirSVD zlbI3nL*>hg8sYpn97B?^|Ft?V5-|?l$$gxk@0p64(+8+IeqnuwK7_xbrr&S*FJx3_4YSwD6VAh1dXxr}S|(M9))higQysm03((-B^a5%4OO#G9yXoHD%`iL|K2oW)uYQxh(W8rcr${Qf@= zHz|miXx)Tb3zt#b?;WZ^kps=DjfKv;0ksSEqUQb#YSG<9oeQr}75zr-rWhGaJ=su; zuPO#<|99Y~C(c9d=R_Gz&r+f0I4!CtL8v({fcvlnszRSk6wJw%02R)Y*;G^p6>fv- zNMF>{PO+{)j~2&XZq(w-sDw{ZtMdc3h6Zcnh^w z{>fq*(gM|ho~RKRn}z+a#kHIW4dE_SLr$Q2eiL1gPg_suCpoa1?>gaufBhi)J zjMOO9!89M0e<^B&*4g+&sD_`g;d9n29&Xg4Td0gLF$h1RKc>!MUKS-#yP+{^#Kxia z{Q^|O4q#KffZ7GQbDAC(u$D%Ra8=ZK(ipYoJTth_8i<|Cv@jiNp9i7lGCyi9bU=M7 z9*SCwi!c+e$9{MRRdLiYj0{YFo`j^?W5N{Vr4kZlW4~5B2#!US2cTZmo=1)cx??@(^^>2(Tf z4O~Etz%A4&eTL~VLO!#4GojKIMg4dDLQxGGnBR=hMATYXhnmtYsD|%FP2CCWEzIvB z;te-?DP$;Mde9P;v7NO$YB6~*1Sg|b`(4b6i3*wznPqVT;a;dU6s3^qKt4=CxEg8^ z_CSqTfApw;x!h#NbEu4eY=WS|J}z&<6)-hcMitZpH8NqSwX+#DVy7?%Ud1sOt%#57 z5{BV3Y#;37{7U8;MJbn&yHJ$<-Ka$V@Nq4*B27(z}mQ>ypQuAnT%G!td$`cmmVBMP05vtKF+UjNLk4==m*Xv zzG`Jt&IgQ2`j=IFoPR&?rK))wZuV3&738e$<9z==0ktphVMDA{!wlI@Y)v?4O&`~N zoR5QWP%R(lpWXP>_Hi8}d>OT-=G5UWNrlPl`Z&Lsv}8RFGQz?2eViXaY}LT*3eOL2 zGLj)eLm%hodb3~#!jDiVU#v#vc^=euYKD5x&qMA1E2vc+v9VcH2{9hw+^B;l6jjb3 z`+Oy8_nbt|6OZc>H`(s$c*r-DK2(v>yHOC}zW_SPPRhHSt|h z`+O>Du`WZMF9%VJ_6}y(SbgFq5fSN|nSzU=-hwqz@g1=vjzsN}@0c6YH#gr%G(x>( zMqpt)h8ii~7UrZ2Mx758QQNp7Y8!S%=jVU=*as6(EnSQn;(e$Vf5yZZr=|Hwl@WEI zgxGL()Pd6tH8OqB7bl|fg`q}r6KaGHquvEi(W4{t6E`aGJ1U`1E7Ow%r~;~^3hs#c zahMGs#B_wOVN>*KZ9cd(M;&0TQ5AJT^?V>|B&MR?0ZUr5|F!rw6QMh7VYI8j^nzdtuw+L{(s zLd|hQRL{qwK1i%Ut>SPjfnQKVS+JdHNJG?A^+&CRnbzy5HI%5mSu0sl`D>y^Xs(AF z^=KJtNH(I5(gUcTok0!p1MG%TJNWQ>;228Gft@;<186B$Bm5pU_oX|ThE>G8gxjMU zyd2e$Q>YQ}eBj2Po2Z>lL`GD&xD7Y6;eIwe*M_&Ew$WAe!+)(Yx|jxMMs=hHYPC19 z;nt{*cE_69|3kS6B;pQg4kLFpbC?9RCUT=c2BQjUY;A@5{NK@ryP}4)4{Ah*pz@Em zhM`7g5vrV37)kqoqjSRt2%F$AD&uJzzJlt>9UFdO!|ze)exoXm)Xf+hqY+MG!)a0Z zv!E(2fZDFb6sCSx8E&-iE28GS4XOtNP>XN0jo*MO=&1FAjlW~VAFw^o|6n<6-`x!L zZdA`tp?1whR0H3kM-P5;qYohN9wtLE>_xaT>U=nj>gjpZqPvMjFjh~qy(*y^+62|J zk?0J)H4L@?7g@KV&V^Gw+5g%e*NITZ_ozkn71h(2y-dcWsC^uST0FrvT+xObqxO3z zRCxnY+jk~vWH+OF9*&xV*BF8yd$Ipha8snWN!S?GlYXd!WDzRke(N37YX6DaKFRu+ zInRO32^T~)WHAoL&8Uv$>1(F65UOKktkpc+XowqOS!|9PstuS2kDxOAMh#h%e)cmT zY9E(HXyya8shrAJkm6*YB*Q6pIii(m)y+~Zo$jXx24QB!djHD_J}%^W5} zo&C8`Lth5fvsS2d-BBYp5i{cw%!-##C#QRm$rprbPzBV>tRC|BU%Z{{gPy1c3_}gs zavY7@?DJxS&8n`1nvy!GZQ8=x**@=wYVdGWIWw&*Q2Dl752N$n|2=0T?pR--diWW& zjUo&&8Ka{Pn#4Ao(wZ5SE*~miaT{L=)xdhFDQJnx-y4;FFgm~gV=OnyIK#RGm2jhV z7pmol?elY}0&d&qPi^=u>OA?5s@Qv|F*<7K6QLGq0M^7@L)rg(xfxGHbu2&3G-L^C z2sh#mJYvJ1;b#9%LKPT>nQ$3u4O~Vo*5{}OdXF%39Ss$q5_P`hN4+~Lk6_g*V|605 z?^~l@LK9F2$O8NaccE7I4O9VdP^;NJ(k!mj*5at4Zi-6Z9dqMoRK7#j8`gIodlPAt z*8)4f^~!S0&4$%zyX+awAqfcF^KRX)GGggC%t&9jxi%}X{_nkYgG9j z-*M(9jkOr6=Z$Q*k2TD?4K+2FQ6DItp;md+@x~<9^wvD6hLl20K|O1CqsKL#n?N2c z$MJX(Bhca|6U<1onCRpDRNPclK~GR~`x8^4dy+W;15t}K2vuHw)OpYxwYaCEj^u@y z5d$Wx9QJ<|Zgle1!(!MCwTgG3=I#KhfN!W1&^^U0z6_|I=S3~1(x|l)ikjJ&JxLQG1Lc+P}Hh!g?fvPMeXN( z*a_dD_HCoNW}AAjBH=I_et}vO5$E~1mSaLxx%*KgbsIIZe^BW>?)heJ5~GGH1*%5@ z*bMWbUc2)#J?=+M$qUpRrd(jo^vu>msJSkW8jZ^=(j7Jpfts{Qh5Vv{*Kw z=H@J_;9EBQ%=#H?5Fc@&S$uU-JsO1?^3|vYUP6ETjcS13BJ&c;jH)LuYDB7HOzr

UN18fw?{!yGsbHI=7OQ+W1FYS5!rPa2ig*O_*UNf5_y;M>ot) z_{M4<=bz)mSY!UYpf#$;OEDYX#=ID7E&E?REW6fJP#ygV*R$dN7?bc&)Z9%)HEcbq zVf(NHp2rm!yv{7Pm)3XIFV1kCMFS@Rn7#|NX|f&KhI-tmZJ`a4d{=@P}}So4o3eCKCUCU7*$d0 zjbUlSOfdf#BvdwlM=ZDyaU{=D{P-`U04l|<3uma(NsE&=rdD{PT zxT&rOJI#?g1vOM_Q3u5l>jO+r_zyO~^t;R|ABkF&kFhjYQ1Mk+uIfaigK%gBrpMHv9s$CjOv?GX5T804iN>R0Av6a9jI)h>f3$YRE>^NF2qq zcnwvbYcKm>iy=NYB`_WOV|&bj<57$1unm7jt?Eqs%t+u#`Jt%|fVG~r#2io{)s1GLVQ5i3y=Kd2Z zU7RCkZKOptD31+?q8eTgwPw1bdOi*{1)i1MsGx96gRg9YI7dx_%%}`iY`B9BPqE?k zHhkHJ-`Q}2W2WM4sQe+Q3R~Fl7!&rmmfMIkHo-~ZW}8_iumREFU;JOee93sG~u)rR+?&h87S z#d_0*AE45`L>(|6P~}B9X{IzD79yMtb6_XTr~SW_o7Q+AwHrcDnMKzIwQqZ&w&`e8 z0dr9ezKt5vr}lZY)8^#!!yw`-p+<5bM#M3w@+Mjrq4V#5w{WAD9z*ru8fvxPLoLQv zxCWz~F{^t!D&1q$kiJHZl=oQ^p8zWo{s+~74yXqAwhqBYgvXs_|F`7kEfG3^YK5DF zWFAf-9O0ag^N&hqpoaVts-WMfojrsk6M9;!jFP>a!h#Y{nT)OL=K z8nOJ=66jC30tTX|BR6Hd_=#uKNIbr37Ta%B4`W|575kyWfv82A2h(B=)R22nYheuP zJXwJ2a4+V^_Sem~U@Ngb;eLWc{<_HWL4%Ltc zsFuD(EvjgD&54*4wdk^^gKB#J`?JbOA*u*G_?-5uC$)9zO}}^Zz{@#>Uk+t`BhNmHonjPSApG#&=Ac) z?ej&bman$%x6d!3*2-N>hY=r`a3=ghxH77zWgnW|&9vu)#JDZ+*E2oA@pSni2g zI}1JBEFoeWj>LLTO+iml+w8kF@iV^bWt-+fjX?9~X6{GhYQoDop` zqAEI#YWPLeT6&0@npddB_Xl-i#&~1*e>!edP-awue5i`bpuUx=gWA_&SOuS=7FGVY zCSRzvCaR*QsGjz)@qIBf;Ss2cx1kz*9-V*x_mmrrzzC%{~Ub7MDbkJ`VF zQ2RULd(*>cs3DJ!8rn>#kqbsOs5WZxwnMFfKGx~z$wb5^Zq(!ZsG<9SYDk0+rhs^; z0@I=zS_GB8EM~>Js6{yrH8q=1Jv@%ecL&qr7gV`^A5BNHd}RNt#TAKA#-^A9d!hF6 zTGS#tf_kakL#6+PnyT2JOpp9gA5;pUc1>+m&wHWHmoZov*P+UJgBrnTpV|K^DBfq& zqg1FK=ERX$7`3QQpfaArLHHWWV#hCL#CD<1i&LmYcLlTKU2EL0<~%56?SvZXSsrfm zo?eCO@p)85&#XS*OoLLQdXy2Bv9JwSz#zhnZTuuuhZdq5v;j5byHO)|2DJ$9V+!>A z;6?=||8DkuCe&idZVg5iSixG~#sads)KpzYb>JbY=WkFQiA}Yd z^MBAEv!QlL71We=Lg(}U0B)3F5^5^up$cAynuurWp-!r+s3H7->Pa+jw=+`ysB}e8 z=R+uJRky%E9Ecj?)u{6KVOG3~+GUX=n0)>b`2FWyLUVmA00%dMb0%jt4bRbAAL>&_&b`-b3~L z1FGe|zGmo>q2@j}`eQj%y7s92gHUrm-o{V2u0l0W0e0NX{{*0QcXi?m}UD+@hfVsvqduv>5h6Sjl*=*@7lnP zD!75#K7UX{87aD1Ovz9!&WT#>RcyFDs=;GWQ?nk`kt3)Px{KOHzil{43{y{D)QHqa z=kNczbE66tpn9?owLedzhW-v}Ex2Nug5#nNs%)qst%=$t?NKArAN3Acf!e<7Fbf_> zrTd0j#F1mM|JCF8vCLxm2i5ZcRE7C$xEv~DBUDd2p@w=Gswaz3`F5d3ARIL^&uut* zY}1gms1YfGdR{j+`#%pi1Bg(8J1`?&!CV+2j>(u0GZ3zVYJdk-(G*mHYi;->YHd71 zO;yCWW`z7u_2k3?*dABoDi1dr;+pZ?t{>PN^>@DAV({lnt~UoMf3-Cv_?&BrX(dsAzT2pCQ6~o zt%K@lSNnW2`f2~qNb74QPOx+p(xcx*WAzcA@g0L9O;T z=#TyZW^Dzd%B_z|-vOg({}1FwJs5}DPD@bRaTjV)UPm1ypD`G-q&3ewqtZ`6Eyks& zk=u_Nphz|f@~8^hqE_)R%!d0g zExxzmB!Oo2mqSfmJJea<6*U$8Q6n%3HG+!*+5c6z*-M00W1I{ofgfsUv!D)=VyK}l zgNLyV2IzT4Gt@OuBQXM1;at>WU5;8?>rqp39ChA&LoH(OOzeME6g!g{veeeRsQB`z z0vn+k(gwAdI-%Z{qfm=*25QkQMx|Sa8i6g?6nEn=Op)2`YUM@4P$M75lf`7HjhgF* zmM5IDh+{woGM>Sw92I4f-93HUGFQ8WQebgfRfEtlR*-ZthQ6rcG)xbii^2-=K zuIk*Vf+pxJGE~clq2_2TYVpiQjnI1Q8B{?p?DH>Jm~hk_Zs(t3grL?;>YQegmPDfuYA zhRK56&YxawLd|KUqGs;nUttG&XLVU+5f8OH4)mU>57?? zun20-YoPXZ8`N4Dh^27?s-o+t5qn~vzd<$ZH){V!DsIxnLY0>sHL_Vz^#vDa|LZ|5 zJ>Uw36R1?fwheVPT8u@I`l(pU>C+UF}#bH3er1y$fj)JVC@nC+R&nhDj@La67}QE$Pv zsO{((&5bIWhT7M2@i(r+{J6HPnX?zD?HM)16qp8euoOfsvJliurv<9KzBYacYDDH^ z1KeT5i9_A`PqpyxzqwHXOECZsq8joXb&!0w@$Pb_A(=28@#Qfs_CPkCS0&OA69V~KE(%k4o}uF2hyyX?Eel#e6MM~32j}= z?fgmSH5^EM`PydDoWrJsv($0B2H;ezhY9L(l5wJSMXiO!^~~ZtjarP4P^@2^Jlt8P#?!XS}Qg- z-`lNjVis9AhLgTQQ@8UE7=xObm(*)4OME6zbGP&NeLZm}5qq&a_G)2{$hX*zaQc>J z=%-_9!oID{OC%%et+)US<6bO+zfn_AxHX>{Y2XOdRGe&MIuy{>d_4EG;-(P~&R}&6 zYG=;=VW>GjfLaSLP$Tpk_1Q0Od-EBtBx?ILLhXiusPr3ALw^)=;w@~BaXOf(=!G=g z`OsAc7Qu^+K9j>HGJ6x;STA4uZ%F|X0% zsBIUgulXjjIMyco4y$2kKeI*_qS75hP0=@N;{N8tZC>r81AW;I8sY+|3{_Ak zR~OW$;hESE*Pt4jVSrhLtFQrK|AEEGxYaW`s+6Av>}kpmYJu7Sb$9d%R}7;YAK z$Z&e1BeW|K`m8nyweL@&=JGx2lS=#%X4RL+n1lzS8a5HN9T%d`^kb;CvEa7GYR>c~l+^+e!8Z{E-Mw^65#+VPEIZ;zJ2Rq;e9D_y2x}D$W z9ge*Tw;pFIdW_oMiN~AQbsyAJjzn!^&unhg!*Em&B26&+H79D$+F(Pr%Pzb{IN2n( z^9#hk;S%B(Pc~&TsHsS_Si6oAvT;+1h?2M$XP~xUjV0#e^nDyec&#oQHa;P|4`Wc_()GOcaLWd_^HVctHk#Gmc$1l$#kjUMrge!_M}(2IzN+&reh$2;7vn}kp8QCEoU-4z03#7zi;-~~M#KaA+5cy`IZDJSoO-}~0%~;7Y@>dtk(q^> zqQ-~J{@sk)W`|IV_CBh@7>CXCq&S{%AXdhms0yPWF`p0Opw>p<5s#^`6cI|$1l`yY zb|rK7O1!19aK3VQ4NfA-0ZH{sE=|rP*X4qHDaD6 z+^E8h_Q4@k#xvF{sFAsglkgGhgG7%LX3j67ojxCrWGZGq~* zc&wuL|6*=@i1>;c+TZv9qnvW{7g2m~h$(RFY16QEsMqUuOo_KpC*mK>i8;=gk7lh= z&%{d-{m)RJhU1A!Rp|Wr|8Z_g6Y&8xcLgq*@BM0E8p4xNt9u7( zF&;qm=sIdnf7|$gOD4V$D!vJ-BcrVgP;xHNxy@qPQebmVOi#j(V-7z0V>xX1Z@iwj-*Z37A0pe+f4_I(OR#mr;x61#0_vKQI*~Ld{(cjEp5w`6^-@Y=s)y zzNpn4kDVm>2&%V*hJS^FB65W>NGf+!S@<4Mp{I0@lKrSQtN| zdYI#hX>eUs13IGCP7l;ntU@*Ptc|~k>R`;Lb{jwSnC(=A2pyp{P#KzG5$udzaT8X< zbkEGosz0iMLof%9M>X^?Iu)YU%1uns>`b9+hUz zcRRla$a9w)4OOQP=Ff1apoVCMbv3HR`%yhRj@p)&QRjm1NAqoZO4PyB3+vzn)V6$u z%Kz1dU7yUzMMLuQ^B?BMl?}Bz%b|MQ3H7#Gf?CCwP^i2B34-_xO{ zs1E8gVh2otOVQcqs1GDhQ9X|T)13V|F@SJ$8y<@ueCQ^g+fKFNx1JHx9b32My-+Qj9+y;frZiE%h#!=A!`3m@$%*0e{*w| z2rovUzPGRQ*J2GL_&OCWM>Xs_X2i@9eVu~qU@+mas0zYS4GZw`b^e&GCn}uFZOR*g z83>=V{zOexI$wVO$wF>IeSMw%{szkv&JxMjnFIE=A9CZnQcl#PD@KhOa{PWCQA@ zvK=GfDLjwisJUAZ(+ufN+(+0qmanr7kD<=~yO39%(qA|GCj> zK8~vR7OKLB7!#kOj@B=z)$J9>*Lhw0qh3yhFbG?qcF%Oojhj#-@e%_uM_ga$@0uE* zJ_pRi0PX*4+~gI=I_b8dw%HR@&!3~F;2Uc3L`!HYijP`kMbHOJU~CLQ?fZJD z5gUyfv8kvj-i%sX;iv=X4m!X8^D8&1FlHiO=gjs)eJ;p=%2*dQH9asT_D8Mi1*lcL z8r6`^sG;A9Iv?Jnj^?O|eVvzFD%2;f5Y)DAm6-jnA)7;lPPl!j#d8Z)@oQ8MA|){u zrbRU*8)`)I+W4ZViprrXZid=T{ZTKaDK@;yh7VY;C1L-o!Z$={NL)$H&?P}tlo}PE z0W}pRurt;~jld~Xx+|y=xQ(j#A#$F%zS-wLP*die%#5Hfs$qd1Zd6b(YVJc(J+6RS zZ0%8tX)eCNlUN(KB=>dxnl9EqW(s2Z`8qG7gs6|T`g1O0z~{p`NEe9Z=~|yCx$le*x4~l|T)BC)CJ{KyA|s73b#HH5BI#t5i%Zd6ZVpbnzss5vf++BLmVJ)DY~>v`5q zn3C`@)Z%=BeE#J#8aL{}2h@=LL{$_iwHe}AsI`#*RdA4v&x>kEVa$!?QAhAF)UH}+ z!<$ee5RTeK_pKjOv;Wm&-!!Ht@lZn>jH;*vsv#9n1=c`qua>Bx?T=c;(@-5*g_?qm zsKt2)i(vEs(~R{V~%69=ZB{xy|-gvmt3HTc|^#18g!i?6OsKrtU(_$IaHtK}V+YZ&x zO{g_<3VE%%9-}%GIlU<_7Ak*YR0o4mBjKsSjVi8(%Fqf`U`JHNBhU{gqgM4M)Eu5f zrMr#!@DpaiEP-Zd8=-pK95pf>P>ZfFYEkz`rrP5gXdi_0ls}qtjbLI5CSnlr9>+~3rMay8-p2tBJxh3lzE?}@_1WuYux9jUl69a+NjZrl&xna&?qI|^<{ zfg5RzeyG%05b%~np)_b9z59FBpn#;L)lbXZC2eD#73ZNho~81CsQ7|ykPpLJfV2tu zicZ{Z@?7Vhe?Z|nZ5riqjUr<%B9_vsX?$g*Rl0b=xjIq6axw(iQBiyeMn$deNoDhG z&t6clt{~#7)99(F|CUrOY)XU9QD!FUxJp^!d=(|$TdPm1fC6wPW}G$+?x^TX@__@mG+{58jM7HdRPJn z((-AZY_t@4fwGO8YT`PHZhl(Cj!8W^Y8gn1rnQ#75i?Ujqr2IeqzfOkP z^xzql@$W}n7jYVu9wfd$jY>kL6$djSL#c2*1*M?^z7=pq!KS2LOJQmG`g?sKU3)dq zcY31{UJ2DHZ-U7f{ZG`{#kM;uak|RUr!V->h80(qJ{P4aD@mV{`-P0dZ9CvT#PzZL zC0hSN-Svj%<>q-w?*Cq;$fwKmiq54W!wTD3{@ZxY=aQ5f1lq4zpUCrgtjQq^T64mW zO zhvx$bcctRF)R&pYd-Ih-!>j8CmFhZ1=3OM(Nus#)Xf_Qh!IV_9=_1n@w{7qw;{51Y zGCP;|c~+aetB9XXIT?AD)#fWid}&jK^ZzdxrMirgCyZWXqrjY0mQE{%>kU0BMh1O) zn$P_~?ibO(tE9PUdzXv2pA=A)N_6eEjnX&G-RW6NtVYA4(x^>Tc7W%%?UW@U?j_}( z)-N@SO`)Ox87dW)pKvEyUXqNu@{r*!8TOD?S7PFOP+)1>yH>XHO;i|xG`DP{l=hRo zk4ByicFGjL-x+b{e>jm-C}=sYyJ!nig8X(X1`~IR=lkh#O51~qjKUJ)!idYxm#(5T z`Vx)oN@I0hwejC+Kn3d9Oyk11FJR+mQho^!^ShIl{HEY341Xy*1-~gMp?wXQ;@HNF!HMR4XnYi>6K9=~Z#24jW z7k|+2ifuF&^@CxNsBky(Ybw%P?cQaZZS9?2Drx>2Gc8IQ!@fryx(88Z2 z*42!LG$B04j#N+L3lKlUHZ~UJeB^mo^71!Iu0o97cglE-V<>kw=@aq%0(oliyaVam zdei@QwkN7&9fglzB*t)mk6x9)Yg90VUXLZy8Ad_Z8`8WcKAeJ2*6cTXQ!qI z=||f1Dd|iK(&!)5#o|lb9k_m`vera zf+6ZaWkWFBR(6N`!ra$nXcy3f^)&7Yab>8uERBCaPiNW@ZAM=Hq}p|wGLK+nMywca zw4)KEUuxZgp6K#s1ib7he5L_6_{vD)1@vZ$?OiAtFL2+HxC!KIXVWWxDjK1y42{q= zoBTU??#JTRm4Q6JX~0>^7{&AwIn7-d$X^B+B_XIrj^ODW**m4Zw$Yz5bd=SLg< ze|6ygB9%s?m$hkJu=B|-fhQ&LJ8XG}=*T`Q<*z=SpMe@m{C3LgMp?ZmJDm8Zq$!Gj zFE5_gWOVs42-hk*NU3ax3sBce?(fm|XgnK2-oIBkquxNjE~p8WjG*xUUbSpEghZ8? z#&o24%s2caq9NfaKLN^j8MBV>+6Mf!yPXU6Q!cqzRih+Lw)hLr11$7WGZE5c)G)LQb_QbsQQ>7y>s3)VtW+vBbj(#8(eD&oG`#_V7U z#;Mg*n8YsfGX(3=;9gWXm$YNam%@%lR(hY2@JRAUAkTa|?5kat?1&^x>h3G0o zL0;^F!!)7{ar!n=v@!B<8*EyHM3Gb)K?3#~(%5A^UE>3>t+V;g^niYkyV zgz~3hH5zq|XH7{{M{ibLvxsObNth=%2* z^}4q5+>ZiYk^T?Q|6VyMa4q5YcBq&9r?Ed6mBTc40BIJ`&^5Mk$tly*iULEapcM&p z{hf`g~<2&WnUqs4yahC$g2!)BG~Ge|Q#?GW27vArwB4_`laj!bhq6l8w`U z+-ogy5eUyVaUNGsBFEV|ZEkz10(R5NfqdPvtyNrM{U6sf8m8+Zg+Jgc6=kHPU|mlr zYoRUgG$WOR#!aH!pY;9~&)ccd#K$MiAN_OT(PY*YNJJYRJYXcI(z?SWJjb&Lc1R~M zq&=ym7+>QkY>tgr-cTC(3{TTAU+y#fr_nbU&5xu_%E(ox?8rR3MWdX{u0Q8~E|uOS zQ3XP?a3>YswUu0@!2{D$lb| zr1{M=T|+3#sec5CbcNHa27JY3(S`Al|GDzp#hB8L(igiAKl;*J#8x3d&;}JrqCDfU?B-MWFs&6cmG=>H3F?)2cCKo=!s|k-0YsYf|uWY)j!| z$lSw@N=u&4ChiDRvVi!~A6K$ha<~~Mf0cGjxW#iv55{ZeQ7mNPC;YQb5n`n?7 z!eO-ZA{i>1|7lneI&{-xhrTzC z#s1HaUYv|eld|N z8fnsTpU;lC=NK)@N5xg>$#GhL!WMRd9xbB6DfDL z9+|T4+L78rd<&*0hV5V&;og)#$c{v1@~qT%)*VUYOXUseVO|P&N5U$$C-r!ytGgX` z-RGi!8Kf;>D|$&mi^+46_y??o(c~RNzPRL>PNP=QfK9g2w!}>$F310;f%zXuVYT?` zNr6pCn4PcX^sp*%y6)OWY~a2j4eekXr)RZDw~1kWPl46RznR8Nq{2Yblq2k>j5Jib z$?l3pj9ymCt6)d)g@+7fiOfYrS3CFBH8iwR*INqCPlg>dDuBXQ$K;%=7gNyG=2wKX|3mHYtRUeB?rYK7eN3~hHb=R(lnv+Q}iy7FI~wP+VsRlB;VgF8tM7*DOWnuETgQ< zd|e^#5OI%4-;8qpUNiLmPiGfHKMI;jV|=N!K5^X%$Dp+b$oP!H0_a%=+oM7}UqoCl zHOTf%g=QuFa$8;j%1TT66^z_?8km!3f3F1OZK{T7{P|Zqt~3;Mogoh*ac&C8&%+UR z$cvJurX2+pkbrc$YLe#>1+BLYC`C`tQ>m`!RCdQ!n1;MdDQgqy+mdD*UnhCig?LYG z0zp(*{67`!V}WcTLv{*ZO%GbAFycz0{%TSktY;VKBl7nmemHS?sk|c%KTO=;D@6B{ zJ&MuGMtRq`|3!y~@^xC@SrsPp95S7!f;6PfKIx*kok4OA|>UeA$KA6!+rT2db1!rVPbJ4mj z*`Y2=A(sfZBvU~O3a23xDSSI$eF!hmkZ|?kc|_7)#Z1I~tPLAlZ{IJC}6K`gyJ`mp#}x&T55Ya7c&xn>^~LP zrm{{{+LdR^D5o&@ZA>=T1v`q{Z2HtZ59j$f&Ho7k`N;hDnnJ6t5%=6yR*wf?s4P7h z-jl8fjmc&k*azRya9!=VUv4YPz!WxMu5}fsoc-jlN}0Mw(1=5U+<=Pz zUR(atpq{pZzt8x|BUdFx=`vr>DCqCCkBUy)kqV(Pov2jT2pYbV@^+CvDGi)PLw@2h zJ^%msbKjnXgDLPHg}flV!Zzq0_gU?t^rfK3q+4Z&axdw-Q}I{QMqy5SQ)n)}Y7^It za!!(ODrv{tk(o*(5|iJ#=!jGQaE9uCg`EX>RoAojb7b!X3GRAuO@JcBA-KD=cz{C) z#7KfmkOIYuv(XkPRtiN5>_UMSmlld!3x(oZ+~xiznbUu6@4eqQ&$F`D8hPiPS?lba zz#)9DY;VCl9bj7s>IBm;5|m|J0%)wZaI=Da0AM?Q>9J)5vg=!s7n2v%~Ee>2I?Y1_nMCC)Q($VHNhRC@%VXG(6Z5*Vr7=!(4!wk5>1Mppuy zNvxlsp8+l(rA(B=Nt53__D?8A-KtZtjex*&K<_cKaTMnnF|0)zYV1bxoFqpNVnkNTpR9G$>cIU+*6ZpU z=7;}i`J+C9n``yS)PCc&GpA5?=lG&XFbr}JRbPM{pV4}eND7?Jz8L$2XH?OXA@U=J zdYp?-dJ~+6gq79a8-}~sheBE;4M~>bGnKh1KFt`#vFD&;Et!|IKLzFul9kuwADAm@ z0l&y!S7(FmQ2f4(f&T~Q68NXV=eP9K z@5U(yPXCr1Fit70!tsEf-)nRuNL-XE59@JATy^5NB&$kv{nXxG3n>ClPH@7QGehVE zIH}zF`u`h45!j3LW$r}cnk4%ja+meCCRus=MDPS5M}mg5z8XNw^?a*g6WM@%C2?t$ z)Dm!xL2?GZDIrjvy+~I1n>9X_=(ir04FIjrg-VUX99rbBpu+*%!pH#0wGg|=dIa|7 z3UXU7VL>Ho5ElcHZ?qsu^s`isek>%u0b@Jsxx{Pz;=)h5C|BVa1d$_B$oQ6Yg zJ^ES%cOh{X*8i4xD(#E?kQz=9HxNQ^DDW)vMF{PJ=n~>ZRxlSy{O{t}{7Ngw!L}>} zyi`|_^<^ApvkpZU!M-FUZj)f6wlph%i?L_K-UnOLL|7g?#2vym5L+G+4g$M2G2gPz zi%&rai3|cOv3_5d$|Nd8$rBOw^#NH8*n0F8*^4wJxH{E0q(c@R68TPTvfhn(NNgs#$ZuZF952eJE8XL*sjvP zHH=AAdmSR*Gmj#91!7V_?j}BGsIoALuQRq_zo6&43412|UVt};eD%RU!^dU)c3{}3 z$FL4%HUNLsphm32wXI9Ef>&BqvZDFTqP%4Tqca3DL+}llZM0J#k<=%1z!z~dx4 zy#C+dP!q*tfPaSFe8B!xbPFeu>HyA%fPrtjB$eNMV)p1Iyo_x!SjnXl1n!~_r;Dw@ zE>33#=(*%>X=0&~|NaTL1^(RGox}@mUIxslKu3 zPhs!K{F=GHCJmGXfQyvZ*S83W~^gqbQ#dl~zvBuzN0GN-Y4M{c@LW3ao zNM26(wI`@7^A99^LnR0GZd@bwA?r8zCzpJXo($#$?Nni%zr?pLSjS3ZJOX$yjz>te zgOP{z=b)^Fz$Z9f)#?P;Z9SUc$XM+S75s?0Gohh`!4u?3w+vMs;*VE#+ zT6u~+qjhDlBV$4lmW?pYX3T-gOqh%(BmXedZx7{X)|bS3*0)I0mNcpG8wTu2)-E9X zG5aJfu`M-CESMrLFq+Y(i&RvTQJ>fa=$6SF|0gIK09+K1!EmkzfwJ14)$l&81U|x+ zO3!GVtN|QMy$i9whS2ZqcaeA=^GS5mu$lOM5AIC#nHYb8y%&NaqnPWY;^5b?$O}Mn z$pZl|0bc>K5`aBihJ_LcS)a58T;v1ECPFm19M+EPV$Q%wsYPsnKuvNrrQoX)AP?Re zFc#@TLXoThPf&ouB&m~MO&Jr;ROxg5Gg>&A^nose<6uQAqTZ<55d|?AwGEs zk%T-7F3LC@`7ek3K2xV?z>7d+6Fy;(I*Gmj9T7Q*ts7OhA?A*DB^(mFS;sNw)Q(Q2 zsLkxRv9FpexnPu(AN&LKzW#y389H&EM8yRV=Bo$@*G>%8#1dFag0-a`sj>%x%793@M1SNLZfvIR*TV&suEF86sEk?+RW3dXWzDk-9Lzzg0#hFgj>g zIzY1$v_Lw6WP@Nmf;O@K1p6Z9UBtwaWB|SyutlL?4`H9Q0lOWe6_~#>cgN=s?DBh% zeZm{>q~#c{;y-R34jzN*-TJQ?!#&JK3Pw7u_UgfEqlljb;UOYi4J3PcmPoJ|`CU>t?7{KBMA zS}(E=-5S>Iu_u?2+LiIxuCsor{^C=RQHO*tz#c$1Mlyd+AsrzmvPBr#EE-V3URqN_ zTa^)@VgR+En$`gHB6+Odg&X*{!WP2*C%v%uslGYD+ptZ?Hi(!{AzA@Uk#WS=SKptN zU^?OxLNG)^LgXsR`Ddtpc>wx|%12?VO;CTpMSAEF?4@H{*cZe$1l?Y$sm1ytNkvjn z%vZ!*)MKa*&P}i*G`_M{-$MT4=QS*2A@CaIb$#9brH&!!!eF}@GDX#vTWyl453$FX zYoQyhMU-QH!af=Tjj*rOY7eOW3jRS_kla7N@yhy|CP)L@+k%dCCwL)XTUoc$SA^*M zvhGa9>9oC05{*QE5a8+H?FC~v^G=BGBk>*V!<4wG4?Mw<{C-(T{F$~S8)vc~kSl zd|tAz!U%+94vM+K9LHPVWme zuQ+Q1T~o-;XI-52WNZV8?@dQU9?{V)=;uQ4IOH2aB8+`P6PTnW;ak9uqo@v`NOQp4 zjMZA%764|l7n#KsI)r`)2{vnYJ|bZ$_P^t|L3J}Aom?*I)vH2R){rNqd{Q?hY_36| zI{-eJ4$u{t)`ERz*3C)w74vg#dniD6ApRVCb**qDDVV3Opgg3J)lLi{jApN39; zL=fqsa}#{>5Zh5ZyNvy7?aU+jr0z7T^TPPw(v&1|*vb>U8e?5Zj${4;eKjR_hiXK+ zfs>c@b~^A2e$ybGg8f7GO_`(U#8Y$ySkGiGiarfQ5`G6<;4yNCt zZ;SKc|AnNNI0rG`LuxBAUot)=NeBcRC`Nkhh3Q&=9`SnSMsz1m-d3*0n1Hc0b0}n< zGq+;qGn9T?VJXrc5|hx?WBdq-PE-?2%wrPlBw2O{Rwr%~`&(2$Q4=r1e++h!JLncB zE&us?RQ{#LIKZyLD31oM!~B4Rr>QKK^>IiP)w66y(u!1i7U2B&=f$TE^Lq$1X3R(b z6)}~G`5Id%baNq`j~pUhb>i3R!2gbUBypFun-(2v0On|75?*pn<&!0pz&@O{O{F4o z&yGQ22SJaSe`FRZMNA3yA}@6=2k3H0^ul(5bqx}WOrhFHhEEP)PoG34!LJ#HKCo^{ zrgcSYOa*tT<_YAMpm;$Ssxf@>J<0m8k0iDe$vfir6=b^*vzd7w z)h>kG-{k3mu47^$oWDN-)wDHk02bmr%mBIwxQCy=k}3!xkqr2!gmfW% zeg{}&83aFoTUqx>+N7@sA<;wgeJoF?%?jj%VGxP8GMW+iCH{Yq6t&-HRJQ_sAAnbr zup7E%43Vt>Ml*K?t1g`!%dqje46{2e3_irf(qqpeqk*xxLC&<1CjEcmkhxKA~%LM~Djh~z0p{;+B zeKO;?JOqm4TvFR`S9>#+eJTQ*K&%OX+ekD7z>n$2K-N>Wr1n~7AAk*#i?jrICbOFS zCQ?W*a1P-k(uMIJU0A1L7Ma56qOl2QFo;yqs_QUMB=JG* z-{({iNXtbIuxPa+O09apj*W8WI}frzjAsMshB^*cQAU9i{L(>xWV`yBJcZj z)c+UYHL2(l#-G$toGYO}Ks6%)7s*E#L~1~6owDyvi~|xyiSLDfd*at9*)N#yVo$(7 zf>|U#zK7_d0}}5NHT3_Uvd#K%ptj0BElo&uqF%yZda({^UoYQKQ! zUF}*2Fy4T%G%*QybYTOr`_X@iPkP=aXNHvg4raeP3X}!LpQDTfC;&3a#f9HWKu)9V zO~MlZ?FZ0C9}byP5PwD&L_UV#I3<##;aj72K=#duJ*-`o`{&mS=eq>W1We=r<0?rm zqtA!)AApx3peh8qQh;OcUmV2t3 zc$>sEs8~K?or=JzBz+5k+yv#(KndtZasv_zAQZqXGLm(28K&U9nLh?cBn#_e>URRX zPuV}En?+Mle-sHtn*OWhB}mqhYFf~lKN&dzYePlVAp0EKZ1yXO>j&u^tlxmMlq9nu zmK~o*#P&)==A}!KQ}8kI`5apYNQB_qN^a*#6e%!@>|#G2@RS6Uq60qz5=PK+$W>%s zLSPY?U)C!360<_j{V={KS=R(ll)0oe?R8W4I< zpif?5FPyB^k05B`^DAU7fDys$lc^+V!zhA%5oC%HJ6j3-t;w&@$*J<8f;>13rmCBa zevCyBsGv?CE2;J%*JSifc8d?;QwS1Y5`PTBZy2YDO)e${{F#i{0(@>k>TkwQI_fXy zAE{O41iTIak4QRMf!af;0g2jzdY*v$=x3u7IZE=IwD%EUB3n70hhUvZme3~lFDn0w z#K*DSAl`-jDKWv4S6+WJNF1o33jp~6!*z7+sH}~GOM*y1rqSv#04&4aoqaSUL~`jd z{zk%+V9kWseS8{f+j}IrC4Q|(fh&o9j9F`Swg~ZQ1)T}2}$Ar4+d}?`nj~S z0ZB$f$i#k7Uo8{xTLj>Fd^<}^m5|_70Gr>@=(m~m&yYEYugGxiZj+??laxEC+6nMw z2>eOX8G!C#U7e&uHE<~j((&lr1gOX}X(93ozg#2^CE-*$=#TvqP4Y;6Yif~su@}K_ zTw=%Qe`A8l=U1L2T{MxK;L^!jUzs40{P-r$B)bamVf8Pi?N3dT-RMstM}flx2S!eJBN8LNE8vuOR_90NaBhA9H_zeg^m{wzni3$G##7f;f^= zRPr_RE__|+uLIN@-=@Ts)v5$jWH_-RC-K>&MP0?W6!|uSfh71X#mOxS=AGK+SK2o5 zI3`tc`C5}Wfj0k!tsG>Uur5iQPoAMG_%8w@6fYd#3)rS=H;aJP2-{SC2diI7Www(< zQ8+D!;rDu;{Qum37jYI@PC#)gPRD*eiMQeRW3q%N*!ROPin(P{b$%Zp+nZ64ZtX`e z@)p~1Mm?~u`94(eDJ04PToV$9SU*$Xa;#@Es?nX{By9(=jLh-uA2UQ6P`SuD5*%S& z75fz`wZO;?W;HPPfiVnQN7mUjPGS;n!eSWgL}ue8QiR~Nyv&*BZ=imra8p#MI0B7TwoW*cHs< z#Fu8aN!%a*s^Au2-a-6bZ2RyXOl;!o{|3q@Y-Va(*24Iig67r^1hOB*Xa!&e#sdHz z;a3?jk-Aj$C;Qf{uj99s$LKrvR`195{K8!mteS$xg|s@ zD4D(lgyC$`2{#E0K(}D)4}tLt_9^Ss#ERqqYZ&%VHQ_g3r>*^dx-2IyKd%=Au0ya0I&Xbf7-NLN8Jqu!GD+lwdTrRqWU5 z-8h0@F~(_#`Q$JK&BV{coUk2ZLlTZvfLyS;&i-6ZwMqt7ISoc2?Uz65=@l zt3i8395Km9?AQFru;W8yEWWL9zDC44RR5E~< zj-yW4PEaV$RY{Z+r%^C{j8SAF!0#CE86OyaLX6>L)WvUww)r9J0*Mj4{P3GBzDOtZ zok-FQqU|8)B2HukV(17#_tSa)IC*tnUDp zpt^djV@PrXVkfYRG{ZKXxL(Y~NHz=GMvi42I*|?dy=Q$Hy~vj&AB1ldMb%@hfz(l{ ze+vG>U~x+OAKfih58 zD!?idn}IW}ihq9Wp~QFQI7D37KEtmcB=(^H7Tqq$<&mHN9%&UfXy-Tuy~8>$K~o`? z5!UA*Fqn1!L^z%cO5zIsDOlGhX=D6Fwvcol`aO(lU>DFMeabpJN0LhmYXat9@{bAv zaLfq!elPUJi!UMqpI_#dW#1rYcG z{Rq8VPt>;uaRVWrAvNb;2e$8%CBCg7kCUBgEV|7k4yI)_Aoc?t_yI!00r?HTkD2SE zt4o4u?BhuImc)(07TFDv{7Pa1zRMs`i1>-}{l64J6KP)%hWi90kZ5jFoZnCa-)Xx= zRg_VT1mkJ%8P)G%zefFkRPrBb;ux)1%solEm_pB!SR^<5<&x|kOhtA>Lc}0w6>M+e ze1N$bpx=^Yh*r>#ISpet`l68DsmZbtw;b@?=tOpsz$fYPy-Vl%G0!FTDC-eYYz6}I za|zC(s7AFDaj349d9a-UA+MhKK4tC$mDwMVKh!=;3y9J9E|4xoOgw&HV!Oh66#fe& z2C}JBasK`!KZHW0Gsy>1^*G&cQosV7dpW>TvX561YqiR%R9Q_EPC?h7BEBbn1*C?v zoA zUm(06Kam3LLjb>mem(tz!J1UonK`>hZSayPP zD8PLZZ9z95rwIfVX8s6b{k03>0N>DxR&bWZS!dKr6~eDH`_28bozYa=Jqg_kh?S)6IT<2uinyU9Wxt$i8*?1Z zwG(;J#Vh81`K1y^Vg3fEiP|y=+6iz7y0Zj1^}=;jVvDiQCqZg_HsbSu3Og{1M1xg? zs%k0X6{(KrgbC&Md)JSoig{sKc5($%_s_Zl-*;{60w| zNKlppzmh=Yi3YWR$Xx(WE5QdO^vP^U>|(6L_8bg9NTg7|ReEHDiMAL`IV%AHGK^Heoo5MvP7<>yWqvo%o*ibz(G! z{cdc_&^aJ^9uhSeI}~&ccAq>0?4lAD-7)s7AwCkKHvadC^Xi=#g8o^OZ-U=J5?@e2 z32X|93OFC7N|Dt#R-zK0JOFquezC`kR{u=;2rR-kP6Yb0x0E%NTi2qIe zTY}Sy@fALm)Tb-!oe-Ep>}M2}HCdj%=n`rJT$pwiWwZe7HEo&!NE6vB$P4y`Sijc} zO#^fQm4^e6no$gl%?cc()gB$#P zyTVjtpUyiWAo4L4H`6ozO*<2e{xr#I;d`86Y3I5@c#pJL%0H3_jO0@+u_oj#pbU`G5ZVe~r zQ~CSfvjoi5_Dv;O4yy6VcxDdGDnc`BZ}7~*^lutiHC9o<+X*ka7e>LXC=(nQo$wm|CTfOKgWM9)lASM z*?{jRMn*;h2xL*bT^gK@n7-(5$bYr;Hv)RA6K zly^|PCq6vVYcJmK$P{2*KI+J1Up(sA?XXK7cbstBL(e#Brm}y%;yB>6S6y>N7-`Bj ztyijbo4PGZHLumEREU-Pj$^UC{;p%5X+MALnC7;lUOJALcH{>~H;;AB<6Ljg$>+>k z$G+or=1E~sjCR&>1PmE6*weaic$A$b)>+hHw~lj02U_8yoWb^rQO-93cHv3R*gPfv zd!nX=>-jZ|jERno_q1-)q||>m+FQSIwlb`u>zzgI&KsO7Tz0B0&aNr!5kEQey6jGS zoXwrqt^LkG``CVG3x{3dpmVFk{&2{7)9v&Ywo3fw%wcEx&Dk%|n*WD$pjGUmGn-X( zk1Nosbjg{=9{;B^yTh)2#aTVT9&^We(P`Cq;4Ectz3&{E)ebRSZya_=fa^$r^*Ft& zuyrScE71NmqpPcFHOS%0V4ch1I%b#5<@(8CFU;er7GMV!b4_;IeM-6-8&=D*uFBTY zvaUn+)pD*G4%@2Wnh{`!)Nqw?m=U4z;RC!@(GXWlyKOC3tZAQW;0krv?HakRxa^_L zT*plNZd=y~r#0{s*E4(Er>;Lu`*kWUxD5x_>Eytrmmaxl;Z|YaJcrE}neIhGGRNn}1|Jd-TFu@59 zOs+`;@#H8P=I!bAM#Os~!u#~~7V`}7#tsSX%WX^y$(Psbjq?28K1och%0t{at%if$ z*=<(u9gf0Q#bNI60>Zp;J$i@tjkg{SbI;2Ha&$D{p?%~1>P7dDhn=TTob}yschz*> zSkDk|U%n0qjq-H&Mp^qtxQki2M!4H%P8R0%2%BESS`$XNtLN<*8t>`v3G+rpd&4}f zLYfAJd3$^Nv-kA&dV9s&*G9O@yB&q?>|@-coRFG7*1eT&>v8Unh2zBW?v8A)k9X&C zq%7QuuE%Ey}OV+sSCOaql-Wqo~mtA{3M65UYjI`E` z4eluW;YPR1nSFM?Kg_vLS7%B@c8`;Ymu*KcM6&D|2pWNbJ?6Bj1aNjcR zfj_wmIPC_z-9wz#j^{>zHDRwi&_27@y~b&OIN+`oXtzJ@e&)2p&$(~8bMVv%D(T^& z#n+Ig_4vv&B*+up%VWh{bQiJ?Uv!spOS@&&GvFU%#Y^tIS;Bfm_w<$uu3x)3UrGf> zh4m=atbT2)&n0)YqDjEwUy?^t-4_2Xi{Q6t)wXp=kjFZ5$!*(XF1ug*Tb|o)!w$La zuHgu%@4GV-@3?c<^xYt@Cx%-b9}^qhE8H95jq>xkslgi?=hu$gH830arXT? z94=?=x#!NxQ8J}=TGjq```dwkyYu_oVb9$+4QEfU-Sn-yp2NR(bj;w`@GyJPJ9oxh z*7rG#*>=aAMx~4yeSjQEbZp`^jI_4RG7H&9f{jh7?a@_?A6!PCL6bcj*O>JwsgbYZfhLyTP3^$;VS^*qF=or<=3pwEGL ztopT#0#@HzMx~_Y%Tat+%P4A{tz{Im9JTpUqP9`QYF*oCXfLX53@Q*nUm8cy0lR&e z@i>M3u%9u_VLgun=6$^J+`oR199xhTe8I?T+CRRxH!4272ai}g zD59c#ejKx-oIYyuzJk{vuu+DVwPj79RW-Lp= zqs>FlLgTIQ=|-48TiIFDry4n}SJRCXDU+oQyB7oIru5s0Rw8hL~ zWteK@wTH|z(z=o?ixwJXl9K$j(3qIQi01VfYe%dwhPv!q-xv{QriRTL{?8Xx#Cl^| z^Z(*?MfP7!gH{gNwV zV!d(R_&C32;okUvULT3q**>tz*x|Ic|6qJ%2W~S0+;+~NjFKtsj)#n1t`vM@=+Qf# z?+5nKBSu@t|5}}W>!>l=VNW=2#5wG&Cykd5+jH6oF|6$ujQsZU3&!9yR)^c1U5ndB zly&a5k;z(nhikd&rjfxed(SA0{{!Pxfc^f3G1_HsdSkRRtxrA=%6UDXiW3%{=KECVdACit8UE zZ>cM6nhn=^y`POOWDr>&Bvz9aOIIO}qj5Jod3TD@&)rbn_0B3lVRXKy{ zv9o_{-gDS_s+c>R_Kxc2F~h1<&#YfrUNrJ-}URL+6jZq7HRAkE%a*_`~J@6UOsnB86S5J-Hc z*wdz&ZJqYkndU;L)ozaIwYJYO$2;o>S?%YTAK3#fa}3{&=bG8A%=65C*7$j5C5-m& zd7Prvev!HSKfvaf=C#~*`t4@EEOwI, YEAR. +# msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-05 15:33+0200\n" -"PO-Revision-Date: 2021-12-14 09:56+0100\n" +"POT-Creation-Date: 2022-08-12 10:25+0200\n" +"PO-Revision-Date: 2022-09-27 15:39+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.0\n" -"X-Poedit-Basepath: .\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.1.1\n" #: src/slic3r/GUI/AboutDialog.cpp:45 src/slic3r/GUI/AboutDialog.cpp:305 msgid "Portions copyright" @@ -20,7 +24,7 @@ msgstr "Gedeeltelijk auteursrecht" #: src/slic3r/GUI/AboutDialog.cpp:141 src/slic3r/GUI/AboutDialog.cpp:269 msgid "Copyright" -msgstr "Copyright" +msgstr "Auteursrecht" #. TRN "Slic3r _is licensed under the_ License" #: src/slic3r/GUI/AboutDialog.cpp:143 @@ -79,14 +83,14 @@ msgid "" "If you are sure you have enough RAM on your system, this may also be a bug " "and we would be glad if you reported it." msgstr "" -"Fout in %s. Mogelijk komt dit door een tekort aan RAM geheugen. Als u er " -"zeker van bent genoeg RAM geheugen te hebben, kan dit een andere oorzaak " -"hebben. We stellen het op prijs als u het aan ons rapporteert." +"%s heeft een fout opgelopen. Mogelijk komt dit door een tekort aan RAM " +"geheugen. Als u zeker weet genoeg RAM geheugen te hebben, kan dit een andere " +"oorzaak hebben. We stellen het op prijs als u het aan ons rapporteert." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:84 #, boost-format msgid "PrusaSlicer has encountered a fatal error: \"%1%\"" -msgstr "" +msgstr "PrusaSlicer heeft een fatale fout opgelopen: \"%1%\"" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:85 msgid "" @@ -142,7 +146,7 @@ msgstr "Uitvoeren van nabewerkingsscripts" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:690 #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:710 msgid "Unknown error occured during exporting G-code." -msgstr "Onbekende error opgetreden tijdens exporteren van de G-code." +msgstr "Onbekende fout opgetreden tijdens exporteren van de G-code." #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:695 #, boost-format @@ -196,11 +200,11 @@ msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:715 #, boost-format msgid "G-code file exported to %1%" -msgstr "G-code-bestand geëxporteerd naar %1%" +msgstr ".gcode-bestand geëxporteerd naar %1%" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:729 msgid "Copying of the temporary G-code to the output G-code failed" -msgstr "Fout bij het exporteren naar de output-G-code" +msgstr "Fout bij het exporteren naar output-G-code" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:751 #, boost-format @@ -251,36 +255,36 @@ msgstr "" #: src/libslic3r/PrintConfig.cpp:628 src/libslic3r/PrintConfig.cpp:678 #: src/libslic3r/PrintConfig.cpp:809 src/libslic3r/PrintConfig.cpp:820 #: src/libslic3r/PrintConfig.cpp:838 src/libslic3r/PrintConfig.cpp:1019 -#: src/libslic3r/PrintConfig.cpp:1231 src/libslic3r/PrintConfig.cpp:1298 -#: src/libslic3r/PrintConfig.cpp:1308 src/libslic3r/PrintConfig.cpp:1588 -#: src/libslic3r/PrintConfig.cpp:1782 src/libslic3r/PrintConfig.cpp:1843 -#: src/libslic3r/PrintConfig.cpp:1861 src/libslic3r/PrintConfig.cpp:1879 -#: src/libslic3r/PrintConfig.cpp:1942 src/libslic3r/PrintConfig.cpp:1952 -#: src/libslic3r/PrintConfig.cpp:2066 src/libslic3r/PrintConfig.cpp:2075 -#: src/libslic3r/PrintConfig.cpp:2094 src/libslic3r/PrintConfig.cpp:2115 -#: src/libslic3r/PrintConfig.cpp:2127 src/libslic3r/PrintConfig.cpp:2135 -#: src/libslic3r/PrintConfig.cpp:2176 src/libslic3r/PrintConfig.cpp:2184 -#: src/libslic3r/PrintConfig.cpp:2194 src/libslic3r/PrintConfig.cpp:2202 -#: src/libslic3r/PrintConfig.cpp:2210 src/libslic3r/PrintConfig.cpp:2272 -#: src/libslic3r/PrintConfig.cpp:2502 src/libslic3r/PrintConfig.cpp:2572 -#: src/libslic3r/PrintConfig.cpp:2589 src/libslic3r/PrintConfig.cpp:2690 -#: src/libslic3r/PrintConfig.cpp:2699 src/libslic3r/PrintConfig.cpp:2749 -#: src/libslic3r/PrintConfig.cpp:2901 src/libslic3r/PrintConfig.cpp:2989 -#: src/libslic3r/PrintConfig.cpp:2996 src/libslic3r/PrintConfig.cpp:3003 -#: src/libslic3r/PrintConfig.cpp:3017 src/libslic3r/PrintConfig.cpp:3041 -#: src/libslic3r/PrintConfig.cpp:3051 src/libslic3r/PrintConfig.cpp:3061 -#: src/libslic3r/PrintConfig.cpp:3341 src/libslic3r/PrintConfig.cpp:3382 -#: src/libslic3r/PrintConfig.cpp:3542 src/libslic3r/PrintConfig.cpp:3551 -#: src/libslic3r/PrintConfig.cpp:3560 src/libslic3r/PrintConfig.cpp:3570 -#: src/libslic3r/PrintConfig.cpp:3635 src/libslic3r/PrintConfig.cpp:3645 -#: src/libslic3r/PrintConfig.cpp:3657 src/libslic3r/PrintConfig.cpp:3677 -#: src/libslic3r/PrintConfig.cpp:3687 src/libslic3r/PrintConfig.cpp:3697 -#: src/libslic3r/PrintConfig.cpp:3715 src/libslic3r/PrintConfig.cpp:3730 -#: src/libslic3r/PrintConfig.cpp:3744 src/libslic3r/PrintConfig.cpp:3755 -#: src/libslic3r/PrintConfig.cpp:3768 src/libslic3r/PrintConfig.cpp:3813 -#: src/libslic3r/PrintConfig.cpp:3823 src/libslic3r/PrintConfig.cpp:3832 -#: src/libslic3r/PrintConfig.cpp:3842 src/libslic3r/PrintConfig.cpp:3858 -#: src/libslic3r/PrintConfig.cpp:3882 +#: src/libslic3r/PrintConfig.cpp:1230 src/libslic3r/PrintConfig.cpp:1297 +#: src/libslic3r/PrintConfig.cpp:1307 src/libslic3r/PrintConfig.cpp:1587 +#: src/libslic3r/PrintConfig.cpp:1781 src/libslic3r/PrintConfig.cpp:1842 +#: src/libslic3r/PrintConfig.cpp:1860 src/libslic3r/PrintConfig.cpp:1878 +#: src/libslic3r/PrintConfig.cpp:1941 src/libslic3r/PrintConfig.cpp:1951 +#: src/libslic3r/PrintConfig.cpp:2065 src/libslic3r/PrintConfig.cpp:2074 +#: src/libslic3r/PrintConfig.cpp:2093 src/libslic3r/PrintConfig.cpp:2114 +#: src/libslic3r/PrintConfig.cpp:2126 src/libslic3r/PrintConfig.cpp:2134 +#: src/libslic3r/PrintConfig.cpp:2175 src/libslic3r/PrintConfig.cpp:2183 +#: src/libslic3r/PrintConfig.cpp:2193 src/libslic3r/PrintConfig.cpp:2201 +#: src/libslic3r/PrintConfig.cpp:2209 src/libslic3r/PrintConfig.cpp:2271 +#: src/libslic3r/PrintConfig.cpp:2501 src/libslic3r/PrintConfig.cpp:2571 +#: src/libslic3r/PrintConfig.cpp:2588 src/libslic3r/PrintConfig.cpp:2689 +#: src/libslic3r/PrintConfig.cpp:2698 src/libslic3r/PrintConfig.cpp:2748 +#: src/libslic3r/PrintConfig.cpp:2900 src/libslic3r/PrintConfig.cpp:2988 +#: src/libslic3r/PrintConfig.cpp:2995 src/libslic3r/PrintConfig.cpp:3002 +#: src/libslic3r/PrintConfig.cpp:3016 src/libslic3r/PrintConfig.cpp:3040 +#: src/libslic3r/PrintConfig.cpp:3050 src/libslic3r/PrintConfig.cpp:3060 +#: src/libslic3r/PrintConfig.cpp:3340 src/libslic3r/PrintConfig.cpp:3381 +#: src/libslic3r/PrintConfig.cpp:3541 src/libslic3r/PrintConfig.cpp:3550 +#: src/libslic3r/PrintConfig.cpp:3559 src/libslic3r/PrintConfig.cpp:3569 +#: src/libslic3r/PrintConfig.cpp:3634 src/libslic3r/PrintConfig.cpp:3644 +#: src/libslic3r/PrintConfig.cpp:3656 src/libslic3r/PrintConfig.cpp:3676 +#: src/libslic3r/PrintConfig.cpp:3686 src/libslic3r/PrintConfig.cpp:3696 +#: src/libslic3r/PrintConfig.cpp:3714 src/libslic3r/PrintConfig.cpp:3729 +#: src/libslic3r/PrintConfig.cpp:3743 src/libslic3r/PrintConfig.cpp:3754 +#: src/libslic3r/PrintConfig.cpp:3767 src/libslic3r/PrintConfig.cpp:3812 +#: src/libslic3r/PrintConfig.cpp:3822 src/libslic3r/PrintConfig.cpp:3831 +#: src/libslic3r/PrintConfig.cpp:3841 src/libslic3r/PrintConfig.cpp:3857 +#: src/libslic3r/PrintConfig.cpp:3881 msgid "mm" msgstr "mm" @@ -342,7 +346,7 @@ msgstr "Model" #: src/slic3r/GUI/BedShapeDialog.cpp:508 msgid "Choose an STL file to import bed shape from:" -msgstr "Kies een STL-bestand om te importeren als bedvorm:" +msgstr "Kies een .STL-bestand om te importeren als bedvorm:" #: src/slic3r/GUI/BedShapeDialog.cpp:514 src/slic3r/GUI/BedShapeDialog.cpp:562 #: src/slic3r/GUI/BedShapeDialog.cpp:584 @@ -370,7 +374,7 @@ msgstr "Kies een bestand om te importeren als bedtextuur (PNG/SVG):" #: src/slic3r/GUI/BedShapeDialog.cpp:574 msgid "Choose an STL file to import bed model from:" -msgstr "Kies een STL-bestand om te importeren als bedvorm:" +msgstr "Kies een .STL-bestand om te importeren als bedvorm:" #: src/slic3r/GUI/BedShapeDialog.hpp:95 src/slic3r/GUI/ConfigWizard.cpp:1396 msgid "Bed Shape" @@ -402,7 +406,7 @@ msgstr "Zoeken naar apparaten" #: src/slic3r/GUI/BonjourDialog.cpp:231 msgid "Finished" -msgstr "Klaar" +msgstr "Gereed" #: src/slic3r/GUI/ButtonsDescription.cpp:42 msgid "Revert color to default" @@ -450,7 +454,7 @@ msgstr "" "\n" "De laagdikte wordt ingesteld op 0,01." -#: src/slic3r/GUI/ConfigManipulation.cpp:62 src/libslic3r/PrintConfig.cpp:1227 +#: src/slic3r/GUI/ConfigManipulation.cpp:62 src/libslic3r/PrintConfig.cpp:1226 msgid "First layer height" msgstr "Laagdikte eerste laag" @@ -515,7 +519,7 @@ msgstr "" #: src/slic3r/GUI/ConfigManipulation.cpp:145 msgid "Shall I synchronize support layers in order to enable the Wipe Tower?" msgstr "" -"Moeten de supportlagen gesynchroniseerd worden met de overage lagen om het " +"Moeten de supportlagen gesynchroniseerd worden met de overige lagen om het " "afveegblok te activeren?" #: src/slic3r/GUI/ConfigManipulation.cpp:164 @@ -549,11 +553,11 @@ msgstr "Moet dit aangepast worden naar het rechtlijnig patroon?" #: src/slic3r/GUI/Plater.cpp:460 src/slic3r/GUI/Tab.cpp:1503 #: src/slic3r/GUI/Tab.cpp:1505 src/libslic3r/PrintConfig.cpp:474 #: src/libslic3r/PrintConfig.cpp:715 src/libslic3r/PrintConfig.cpp:739 -#: src/libslic3r/PrintConfig.cpp:1094 src/libslic3r/PrintConfig.cpp:1108 -#: src/libslic3r/PrintConfig.cpp:1145 src/libslic3r/PrintConfig.cpp:1394 -#: src/libslic3r/PrintConfig.cpp:1404 src/libslic3r/PrintConfig.cpp:1473 -#: src/libslic3r/PrintConfig.cpp:1493 src/libslic3r/PrintConfig.cpp:1512 -#: src/libslic3r/PrintConfig.cpp:2333 src/libslic3r/PrintConfig.cpp:2350 +#: src/libslic3r/PrintConfig.cpp:1093 src/libslic3r/PrintConfig.cpp:1107 +#: src/libslic3r/PrintConfig.cpp:1144 src/libslic3r/PrintConfig.cpp:1393 +#: src/libslic3r/PrintConfig.cpp:1403 src/libslic3r/PrintConfig.cpp:1472 +#: src/libslic3r/PrintConfig.cpp:1492 src/libslic3r/PrintConfig.cpp:1511 +#: src/libslic3r/PrintConfig.cpp:2332 src/libslic3r/PrintConfig.cpp:2349 msgid "Infill" msgstr "Vulling" @@ -651,7 +655,7 @@ msgstr "varianten" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:93 #, c-format, boost-format msgid "Incompatible with this %s" -msgstr "Niet geschikt voor deze %s" +msgstr "Niet compatibel met deze %s" #: src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 msgid "Activate" @@ -686,7 +690,7 @@ msgstr "Alles" #: src/slic3r/GUI/ConfigWizard.cpp:332 src/slic3r/GUI/ConfigWizard.cpp:652 #: src/slic3r/GUI/DoubleSlider.cpp:2030 src/slic3r/GUI/Plater.cpp:432 #: src/slic3r/GUI/Plater.cpp:579 src/slic3r/GUI/Preferences.cpp:436 -#: src/libslic3r/PrintConfig.cpp:1287 +#: src/libslic3r/PrintConfig.cpp:1286 msgid "None" msgstr "Geen" @@ -715,15 +719,15 @@ msgstr "" #: src/slic3r/GUI/ConfigWizard.cpp:495 msgid "Remove user profiles (a snapshot will be taken beforehand)" -msgstr "Verwijder gebruikersprofielen (vooraf wordt een opname gemaakt)" +msgstr "Verwijder gebruikersprofielen (vooraf wordt een snapshot gemaakt)" #: src/slic3r/GUI/ConfigWizard.cpp:498 msgid "" "Perform desktop integration (Sets this binary to be searchable by the " "system)." msgstr "" -"Voer desktopintegratie uit (stel in op binary om zodat deze door het systeem " -"bezocht kan worden)." +"Voer desktopintegratie uit (stel in op binair zodat deze door het systeem " +"gezocht kan worden)." #: src/slic3r/GUI/ConfigWizard.cpp:550 #, c-format, boost-format @@ -826,7 +830,7 @@ msgid "" "application startup (never during program usage). This is only a " "notification mechanisms, no automatic installation is done." msgstr "" -"%s controleert op nieuwe versie online als dit is geactiveerd. Als een " +"%s controleert naar nieuwe versies online als dit is ingeschakeld. Als een " "nieuwe versie beschikbaar komt, wordt bij de volgende keer opstarten een " "melding getoond (nooit tijdens gebruik). Dit is slechts een melding." @@ -842,8 +846,8 @@ msgid "" "When a new preset version becomes available it is offered at application " "startup." msgstr "" -"%s download updates op ingebouwde presets in de achtergrond als dit is " -"geactiveerd. De updates worden in een tijdelijke locatie opgeslagen. Wanneer " +"Als dit aanstaat download %s updates op ingebouwde presets in de " +"achtergrond. De updates worden in een tijdelijke locatie opgeslagen. Wanneer " "een nieuwe preset beschikbaar komt, wordt een melding getoond tijdens het " "opstarten." @@ -864,7 +868,7 @@ msgstr "" "update wordt toegepast." #: src/slic3r/GUI/ConfigWizard.cpp:1243 src/slic3r/GUI/GUI_Factories.cpp:726 -#: src/slic3r/GUI/Plater.cpp:3582 +#: src/slic3r/GUI/Plater.cpp:3569 msgid "Reload from disk" msgstr "Herlaad van schijf" @@ -872,7 +876,8 @@ msgstr "Herlaad van schijf" msgid "" "Export full pathnames of models and parts sources into 3mf and amf files" msgstr "" -"Exporteer volledige padnaam van modellen en bronnen in 3MF en AMF bestanden" +"Exporteer volledige padnaam van modellen en bronnen in .3MF- en .AMF-" +"bestanden" #: src/slic3r/GUI/ConfigWizard.cpp:1250 msgid "" @@ -882,7 +887,7 @@ msgid "" "using an open file dialog." msgstr "" "Sta toe om bestanden automatisch te vinden bij het herladen van de schijf " -"als dit is geactiveerd.\n" +"als dit is ingeschakeld.\n" "Als dit niet is geactiveerd vraagt het programma elke bestand apart te " "selecteren." @@ -892,11 +897,11 @@ msgstr "Bestandsassociatie" #: src/slic3r/GUI/ConfigWizard.cpp:1261 src/slic3r/GUI/Preferences.cpp:157 msgid "Associate .3mf files to PrusaSlicer" -msgstr "Open .3mf-bestanden met PrusaSlicer" +msgstr "Open .3MF-bestanden met PrusaSlicer" #: src/slic3r/GUI/ConfigWizard.cpp:1262 src/slic3r/GUI/Preferences.cpp:164 msgid "Associate .stl files to PrusaSlicer" -msgstr "Open .stl-bestanden met PrusaSlicer" +msgstr "Open .STL-bestanden met PrusaSlicer" #: src/slic3r/GUI/ConfigWizard.cpp:1272 msgid "View mode" @@ -1024,8 +1029,8 @@ msgid "Extrusion Temperature:" msgstr "Extrusietemperatuur:" #: src/slic3r/GUI/ConfigWizard.cpp:1568 src/slic3r/GUI/ConfigWizard.cpp:1582 -#: src/libslic3r/PrintConfig.cpp:417 src/libslic3r/PrintConfig.cpp:1207 -#: src/libslic3r/PrintConfig.cpp:1262 src/libslic3r/PrintConfig.cpp:2811 +#: src/libslic3r/PrintConfig.cpp:417 src/libslic3r/PrintConfig.cpp:1206 +#: src/libslic3r/PrintConfig.cpp:1261 src/libslic3r/PrintConfig.cpp:2810 msgid "°C" msgstr "°C" @@ -1158,15 +1163,15 @@ msgstr "Select alle standaardprinters" #: src/slic3r/GUI/ConfigWizard.cpp:2861 msgid "< &Back" -msgstr "< &Terug" +msgstr "< Terug" #: src/slic3r/GUI/ConfigWizard.cpp:2862 msgid "&Next >" -msgstr "&Volgende >" +msgstr "Volgende >" #: src/slic3r/GUI/ConfigWizard.cpp:2863 msgid "&Finish" -msgstr "&Voltooi" +msgstr "Voltooi" #: src/slic3r/GUI/ConfigWizard.cpp:2864 #: src/slic3r/GUI/DesktopIntegrationDialog.cpp:490 @@ -1191,7 +1196,7 @@ msgid "Filament Profiles Selection" msgstr "Filament profielselectie" #: src/slic3r/GUI/ConfigWizard.cpp:2912 src/slic3r/GUI/ConfigWizard.cpp:2915 -#: src/slic3r/GUI/GUI_ObjectList.cpp:3790 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3781 msgid "Type:" msgstr "Type:" @@ -1205,7 +1210,7 @@ msgstr "Configuratie-assistent" #: src/slic3r/GUI/ConfigWizard.cpp:3037 msgid "Configuration &Assistant" -msgstr "Configuratie-&assistent" +msgstr "Configuratie-assistent" #: src/slic3r/GUI/ConfigWizard.cpp:3039 msgid "Configuration Wizard" @@ -1213,7 +1218,7 @@ msgstr "Configuratie-assistent" #: src/slic3r/GUI/ConfigWizard.cpp:3040 msgid "Configuration &Wizard" -msgstr "Configuratie-&assistent" +msgstr "Configuratie-assistent" #: src/slic3r/GUI/DesktopIntegrationDialog.cpp:232 msgid "" @@ -1245,7 +1250,7 @@ msgstr "" "aangemaakt." #: src/slic3r/GUI/DesktopIntegrationDialog.cpp:459 -#: src/slic3r/GUI/GUI_App.cpp:2245 +#: src/slic3r/GUI/GUI_App.cpp:2244 msgid "Desktop Integration" msgstr "Desktopintegratie" @@ -1671,7 +1676,7 @@ msgstr "" #: src/slic3r/GUI/ExtraRenderers.cpp:316 src/slic3r/GUI/GUI_ObjectList.cpp:538 #: src/slic3r/GUI/GUI_ObjectList.cpp:550 src/slic3r/GUI/GUI_ObjectList.cpp:979 #: src/slic3r/GUI/GUI_ObjectList.cpp:1966 -#: src/slic3r/GUI/GUI_ObjectList.cpp:4291 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4282 #: src/slic3r/GUI/ObjectDataViewModel.cpp:250 #: src/slic3r/GUI/ObjectDataViewModel.cpp:352 #: src/slic3r/GUI/ObjectDataViewModel.cpp:376 @@ -1688,10 +1693,10 @@ msgid "Set extruder change for every" msgstr "Stel toolwissel in voor elke" #: src/slic3r/GUI/ExtruderSequenceDialog.cpp:60 -#: src/libslic3r/PrintConfig.cpp:661 src/libslic3r/PrintConfig.cpp:1407 -#: src/libslic3r/PrintConfig.cpp:2104 src/libslic3r/PrintConfig.cpp:2279 -#: src/libslic3r/PrintConfig.cpp:2355 src/libslic3r/PrintConfig.cpp:2608 -#: src/libslic3r/PrintConfig.cpp:2656 src/libslic3r/PrintConfig.cpp:2675 +#: src/libslic3r/PrintConfig.cpp:661 src/libslic3r/PrintConfig.cpp:1406 +#: src/libslic3r/PrintConfig.cpp:2103 src/libslic3r/PrintConfig.cpp:2278 +#: src/libslic3r/PrintConfig.cpp:2354 src/libslic3r/PrintConfig.cpp:2607 +#: src/libslic3r/PrintConfig.cpp:2655 src/libslic3r/PrintConfig.cpp:2674 msgid "layers" msgstr "lagen" @@ -1703,7 +1708,7 @@ msgstr "Willekeurige volgorde" msgid "If enabled, random sequence of the selected extruders will be used." msgstr "" "Willekeurige volgorde van de geselecteerde extruders wordt toegestaan als " -"dit aan staat." +"dit is ingeschakeld." #: src/slic3r/GUI/ExtruderSequenceDialog.cpp:172 msgid "Allow next color repetition" @@ -1712,8 +1717,8 @@ msgstr "Sta volgende kleur volgorde toe" #: src/slic3r/GUI/ExtruderSequenceDialog.cpp:174 msgid "If enabled, a repetition of the next random color will be allowed." msgstr "" -"Een herhaling van de volgende willekeurige kleur wordt toegestaan als dit " -"aan staat." +"Een herhaling van de volgende willekeurige kleur wordt toegestaan als dit is " +"ingeschakeld." #: src/slic3r/GUI/ExtruderSequenceDialog.cpp:177 msgid "Set extruder(tool) sequence" @@ -1867,7 +1872,7 @@ msgstr "Firmwarebestand:" #: src/slic3r/GUI/FirmwareDialog.cpp:813 msgid "Select a file" -msgstr "" +msgstr "Selecteer een bestand" #: src/slic3r/GUI/FirmwareDialog.cpp:815 #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:297 @@ -1951,7 +1956,7 @@ msgstr "Voeg toe" msgid "Add one or more custom shapes" msgstr "Voeg een of meer aangepaste vormen toe" -#: src/slic3r/GUI/GalleryDialog.cpp:118 src/slic3r/GUI/GalleryDialog.cpp:506 +#: src/slic3r/GUI/GalleryDialog.cpp:118 src/slic3r/GUI/GalleryDialog.cpp:508 #: src/slic3r/GUI/GLCanvas3D.cpp:4490 src/slic3r/GUI/GUI_Factories.cpp:444 #: src/slic3r/GUI/Tab.cpp:3748 msgid "Delete" @@ -1963,11 +1968,11 @@ msgstr "" "Verwijder een of meer aangepaste vormen. U kunt geen standaardvormen " "verwijderen" -#: src/slic3r/GUI/GalleryDialog.cpp:400 +#: src/slic3r/GUI/GalleryDialog.cpp:402 msgid "Choose one or more files (STL, OBJ):" msgstr "Kies een of meer bestanden (STL, OBJ):" -#: src/slic3r/GUI/GalleryDialog.cpp:440 +#: src/slic3r/GUI/GalleryDialog.cpp:442 #, boost-format msgid "" "It looks like selected %1%-file has an error or is destructed.\n" @@ -1977,19 +1982,19 @@ msgstr "" "vernietigd.\n" "Het bestand kan niet geladen worden" -#: src/slic3r/GUI/GalleryDialog.cpp:451 +#: src/slic3r/GUI/GalleryDialog.cpp:453 msgid "Choose one PNG file:" msgstr "Kies een PNG-bestand:" -#: src/slic3r/GUI/GalleryDialog.cpp:464 +#: src/slic3r/GUI/GalleryDialog.cpp:466 msgid "Replacing of the PNG" msgstr "Herplaats de PNG" -#: src/slic3r/GUI/GalleryDialog.cpp:508 +#: src/slic3r/GUI/GalleryDialog.cpp:510 msgid "Change thumbnail" msgstr "Verander miniatuur" -#: src/slic3r/GUI/GalleryDialog.cpp:549 src/slic3r/GUI/GalleryDialog.cpp:554 +#: src/slic3r/GUI/GalleryDialog.cpp:551 src/slic3r/GUI/GalleryDialog.cpp:556 #, boost-format msgid "Loading of the \"%1%\"" msgstr "Laden van de \"%1%\"" @@ -2127,7 +2132,7 @@ msgid "Duration" msgstr "Duur" #: src/slic3r/GUI/GCodeViewer.cpp:3609 src/slic3r/GUI/GUI_Preview.cpp:1049 -#: src/libslic3r/PrintConfig.cpp:2906 +#: src/libslic3r/PrintConfig.cpp:2905 msgid "Travel" msgstr "Beweging" @@ -2211,9 +2216,9 @@ msgstr "Normale modus" msgid "Stealth mode" msgstr "Stille modus" -#: src/slic3r/GUI/GCodeViewer.cpp:3766 src/libslic3r/PrintConfig.cpp:1185 -#: src/libslic3r/PrintConfig.cpp:1203 src/libslic3r/PrintConfig.cpp:1213 -#: src/libslic3r/PrintConfig.cpp:1258 +#: src/slic3r/GUI/GCodeViewer.cpp:3766 src/libslic3r/PrintConfig.cpp:1184 +#: src/libslic3r/PrintConfig.cpp:1202 src/libslic3r/PrintConfig.cpp:1212 +#: src/libslic3r/PrintConfig.cpp:1257 msgid "First layer" msgstr "Eerste laag" @@ -2320,7 +2325,7 @@ msgstr "Variabele laagdikte - adaptief" #: src/slic3r/GUI/GLCanvas3D.cpp:1281 msgid "Variable layer height - Smooth all" -msgstr "Variable laagdikte - egaliseer alles" +msgstr "Variabele laagdikte - egaliseer alles" #: src/slic3r/GUI/GLCanvas3D.cpp:1688 msgid "Mirror Object" @@ -2419,7 +2424,7 @@ msgid "Add..." msgstr "Voeg toe..." #: src/slic3r/GUI/GLCanvas3D.cpp:4499 src/slic3r/GUI/KBShortcutsDialog.cpp:96 -#: src/slic3r/GUI/Plater.cpp:5509 src/slic3r/GUI/Tab.cpp:4155 +#: src/slic3r/GUI/Plater.cpp:5496 src/slic3r/GUI/Tab.cpp:4155 msgid "Delete all" msgstr "Verwijder alles" @@ -2508,7 +2513,7 @@ msgid "Selection-Remove from rectangle" msgstr "Selectie - Verwijder van boxselectie" #: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:50 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:160 src/libslic3r/PrintConfig.cpp:4447 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:160 src/libslic3r/PrintConfig.cpp:4446 msgid "Cut" msgstr "Snij door" @@ -2624,7 +2629,7 @@ msgstr "Bol" #: src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp:55 #: src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp:124 #: src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp:68 -#: src/libslic3r/PrintConfig.cpp:1168 +#: src/libslic3r/PrintConfig.cpp:1167 msgid "Triangles" msgstr "Facetten" @@ -2770,7 +2775,7 @@ msgid "Quality" msgstr "Kwaliteit" #: src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp:34 -#: src/libslic3r/PrintConfig.cpp:3874 +#: src/libslic3r/PrintConfig.cpp:3873 msgid "Closing distance" msgstr "Sluitafstand" @@ -2877,7 +2882,7 @@ msgstr "Verplaats" #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:543 #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:562 #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:578 -#: src/libslic3r/PrintConfig.cpp:4501 +#: src/libslic3r/PrintConfig.cpp:4500 msgid "Rotate" msgstr "Roteer" @@ -2894,7 +2899,7 @@ msgstr "Toepassen" #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:216 #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:563 #: src/slic3r/GUI/GUI_ObjectManipulation.cpp:579 -#: src/libslic3r/PrintConfig.cpp:4516 +#: src/libslic3r/PrintConfig.cpp:4515 msgid "Scale" msgstr "Verschaal" @@ -3033,7 +3038,7 @@ msgid "Minimal points distance" msgstr "Minimale puntafstand" #: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:46 -#: src/libslic3r/PrintConfig.cpp:3704 +#: src/libslic3r/PrintConfig.cpp:3703 msgid "Support points density" msgstr "Dichtheid van supportpunten" @@ -3310,7 +3315,7 @@ msgstr "Afsluiten, ik zal mijn data nu verplaatsen" msgid "Start the application" msgstr "Start het programma" -#: src/slic3r/GUI/GUI_App.cpp:699 +#: src/slic3r/GUI/GUI_App.cpp:698 #, c-format, boost-format msgid "" "%s has encountered an error. It was likely caused by running out of memory. " @@ -3319,17 +3324,17 @@ msgid "" "\n" "The application will now terminate." msgstr "" -"%s veroorzaakte een fout. Dit komt mogelijk door een geheugentekort. Als u " -"zeker weet dat u genoeg RAM-geheugen heeft, kan dit ook een andere fout " +"%s heeft een fout opgelopen. Dit komt mogelijk door een geheugentekort. Als " +"u zeker weet genoeg RAM geheugen te hebben, kan dit ook een andere fout " "zijn. We waarderen het als u dit meldt.\n" "\n" "Het programma zal nu afsluiten." -#: src/slic3r/GUI/GUI_App.cpp:702 +#: src/slic3r/GUI/GUI_App.cpp:701 msgid "Fatal error" msgstr "Fatale fout" -#: src/slic3r/GUI/GUI_App.cpp:706 +#: src/slic3r/GUI/GUI_App.cpp:705 msgid "" "PrusaSlicer has encountered a localization error. Please report to " "PrusaSlicer team, what language was active and in which scenario this issue " @@ -3337,22 +3342,22 @@ msgid "" "\n" "The application will now terminate." msgstr "" -"PrusaSlicer is een vertalingsprobleem tegengekomen. Gelieve te melden bij " -"het PrusaSlicer team; welke taal actief is en in welk scenario dit gebeurde. " -"Hartelijk dank.\n" +"PrusaSlicer heeft een vertalingsprobleem opgelopen. We zouden het waarderen " +"als u dit meldt bij het PrusaSlicer team; welke taal actief is en in welk " +"scenario dit gebeurde. Hartelijk dank.\n" "\n" "Het programma zal nu sluiten." -#: src/slic3r/GUI/GUI_App.cpp:709 +#: src/slic3r/GUI/GUI_App.cpp:708 msgid "Critical error" msgstr "Kritische fout" -#: src/slic3r/GUI/GUI_App.cpp:714 +#: src/slic3r/GUI/GUI_App.cpp:713 #, boost-format msgid "Internal error: %1%" msgstr "Interne fout: %1%" -#: src/slic3r/GUI/GUI_App.cpp:909 src/slic3r/GUI/GUI_App.cpp:1007 +#: src/slic3r/GUI/GUI_App.cpp:908 src/slic3r/GUI/GUI_App.cpp:1006 msgid "" "Error parsing PrusaSlicer config file, it is probably corrupted. Try to " "manually delete the file to recover from the error. Your user profiles will " @@ -3362,7 +3367,7 @@ msgstr "" "Probeer het bestand handmatig te verwijderen om de fout te herstellen. Uw " "gebruikersprofielen worden niet beïnvloed." -#: src/slic3r/GUI/GUI_App.cpp:915 src/slic3r/GUI/GUI_App.cpp:1013 +#: src/slic3r/GUI/GUI_App.cpp:914 src/slic3r/GUI/GUI_App.cpp:1012 msgid "" "Error parsing PrusaGCodeViewer config file, it is probably corrupted. Try to " "manually delete the file to recover from the error." @@ -3370,12 +3375,12 @@ msgstr "" "Fout tijdens het lezen van PrusaGCodeViewer-configuratiebestand. Het is " "mogelijk beschadigd. Probeer het handmatig te verwijderen." -#: src/slic3r/GUI/GUI_App.cpp:962 +#: src/slic3r/GUI/GUI_App.cpp:961 #, boost-format msgid "You are opening %1% version %2%." msgstr "U opent %1%, versie %2%." -#: src/slic3r/GUI/GUI_App.cpp:965 +#: src/slic3r/GUI/GUI_App.cpp:964 #, boost-format msgid "" "The active configuration was created by %1% %2%,\n" @@ -3394,7 +3399,7 @@ msgstr "" "In dat geval wordt uw actieve configuratie opgeslagen voor het importeren " "van de nieuwe configuratie." -#: src/slic3r/GUI/GUI_App.cpp:973 +#: src/slic3r/GUI/GUI_App.cpp:972 #, boost-format msgid "" "An existing configuration was found in %3%\n" @@ -3403,22 +3408,22 @@ msgid "" "Shall this configuration be imported?" msgstr "" "Een bestaande configuratie is gevonden in %3%,\n" -"aangemaakt door %1% %2%.\n" +"aangemaakt door b>%1% %2%.\n" "Moet de nieuwe configuratie worden geïmporteerd?" -#: src/slic3r/GUI/GUI_App.cpp:981 +#: src/slic3r/GUI/GUI_App.cpp:980 msgid "Import" msgstr "Importeer" -#: src/slic3r/GUI/GUI_App.cpp:982 +#: src/slic3r/GUI/GUI_App.cpp:981 msgid "Don't import" msgstr "Niet importeren" -#: src/slic3r/GUI/GUI_App.cpp:990 +#: src/slic3r/GUI/GUI_App.cpp:989 msgid "Continue and import newer configuration?" -msgstr "" +msgstr "Doorgaan en nieuwere configuraties importeren?" -#: src/slic3r/GUI/GUI_App.cpp:1051 +#: src/slic3r/GUI/GUI_App.cpp:1050 msgid "" "You are running a 32 bit build of PrusaSlicer on 64-bit Windows.\n" "32 bit build of PrusaSlicer will likely not be able to utilize all the RAM " @@ -3434,7 +3439,7 @@ msgstr "" "https://www.prusa3d.com/prusaslicer/.\n" "Wilt u doorgaan?" -#: src/slic3r/GUI/GUI_App.cpp:1134 +#: src/slic3r/GUI/GUI_App.cpp:1133 #, c-format, boost-format msgid "" "%s\n" @@ -3443,46 +3448,46 @@ msgstr "" "%s\n" "Wilt u doorgaan?" -#: src/slic3r/GUI/GUI_App.cpp:1136 src/slic3r/GUI/GUI_App.cpp:3101 +#: src/slic3r/GUI/GUI_App.cpp:1135 src/slic3r/GUI/GUI_App.cpp:3100 #: src/slic3r/GUI/Plater.cpp:1726 src/slic3r/GUI/UnsavedChangesDialog.cpp:889 msgid "Remember my choice" msgstr "Onthoud mijn keuze" -#: src/slic3r/GUI/GUI_App.cpp:1178 +#: src/slic3r/GUI/GUI_App.cpp:1177 msgid "Loading configuration" -msgstr "" +msgstr "Configuratie aan het laden" -#: src/slic3r/GUI/GUI_App.cpp:1209 +#: src/slic3r/GUI/GUI_App.cpp:1208 #, boost-format msgid "New release version %1% is available." msgstr "Nieuwe release versie %1% is beschikbaar." -#: src/slic3r/GUI/GUI_App.cpp:1210 +#: src/slic3r/GUI/GUI_App.cpp:1209 msgid "See Download page." msgstr "Zie downloadpagina." -#: src/slic3r/GUI/GUI_App.cpp:1224 +#: src/slic3r/GUI/GUI_App.cpp:1223 #, boost-format msgid "New prerelease version %1% is available." msgstr "Nieuwe pre-release versie %1% is beschikbaar." -#: src/slic3r/GUI/GUI_App.cpp:1225 +#: src/slic3r/GUI/GUI_App.cpp:1224 msgid "See Releases page." msgstr "Zie Release-pagina." -#: src/slic3r/GUI/GUI_App.cpp:1262 +#: src/slic3r/GUI/GUI_App.cpp:1261 msgid "Preparing settings tabs" msgstr "Instellingentab voorbereiden" -#: src/slic3r/GUI/GUI_App.cpp:1336 src/slic3r/GUI/Preferences.cpp:287 +#: src/slic3r/GUI/GUI_App.cpp:1335 src/slic3r/GUI/Preferences.cpp:287 msgid "Restore window position on start" -msgstr "" +msgstr "Herstel vensterpositie na opstarten" + +#: src/slic3r/GUI/GUI_App.cpp:1337 +msgid "PrusaSlicer started after a crash" +msgstr "PrusaSlicer is opgestart na een crash" #: src/slic3r/GUI/GUI_App.cpp:1338 -msgid "PrusaSlicer started after a crash" -msgstr "" - -#: src/slic3r/GUI/GUI_App.cpp:1339 #, boost-format msgid "" "PrusaSlicer crashed last time when attempting to set window position.\n" @@ -3494,24 +3499,34 @@ msgid "" "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " "Otherwise, the application will most likely crash again next time." msgstr "" +"PrusaSlicer is de vorige keer gecrasht tijdens een poging om de " +"vensterpositie in te stellen.\n" +"Sorry voor het ongemak. Het gebeurt helaas met sommige meer-schermen " +"opstellingen.\n" +"Exacte reden van de crash: \"%1%\".\n" +"For meer informatie, zie onze Github probleemtracker: \"%2%\" en \"%3%\"\n" +"\n" +"Overweeg om \"%4%\" in voorkeuren uit te zetten om dit probleem te " +"voorkomen. Waarschijnlijk zal anders het programma de volgende keer weer " +"crashen." + +#: src/slic3r/GUI/GUI_App.cpp:1350 +#, boost-format +msgid "Disable \"%1%\"" +msgstr "\"%1%\" uitschakelen" #: src/slic3r/GUI/GUI_App.cpp:1351 #, boost-format -msgid "Disable \"%1%\"" -msgstr "" - -#: src/slic3r/GUI/GUI_App.cpp:1352 -#, boost-format msgid "Leave \"%1%\" enabled" -msgstr "" +msgstr "Laat \"%1%\" aanstaan" -#: src/slic3r/GUI/GUI_App.cpp:1679 +#: src/slic3r/GUI/GUI_App.cpp:1678 msgid "" "You have the following presets with saved options for \"Print Host upload\"" msgstr "" "Je hebt de volgende presets opgeslagen voor de printhost-uploadwachtrij" -#: src/slic3r/GUI/GUI_App.cpp:1683 +#: src/slic3r/GUI/GUI_App.cpp:1682 msgid "" "But since this version of PrusaSlicer we don't show this information in " "Printer Settings anymore.\n" @@ -3519,9 +3534,9 @@ msgid "" msgstr "" "Maar sinds deze versie van PrusaSlicer, wordt de informatie niet meer " "getoond in de printerinstellingen.\n" -"Instellingen zijn beschikbaar in de fysieke printerinstellingen." +"Instellingen zijn beschikbaar in de fysieke-printerinstellingen." -#: src/slic3r/GUI/GUI_App.cpp:1685 +#: src/slic3r/GUI/GUI_App.cpp:1684 msgid "" "By default new Printer devices will be named as \"Printer N\" during its " "creation.\n" @@ -3531,138 +3546,138 @@ msgstr "" "Let op: deze naam kan later worden aangepast in de fysieke-" "printerinstellingen" -#: src/slic3r/GUI/GUI_App.cpp:1689 src/slic3r/GUI/PhysicalPrinterDialog.cpp:722 +#: src/slic3r/GUI/GUI_App.cpp:1688 src/slic3r/GUI/PhysicalPrinterDialog.cpp:722 msgid "Information" msgstr "Informatie" -#: src/slic3r/GUI/GUI_App.cpp:1702 src/slic3r/GUI/GUI_App.cpp:1713 +#: src/slic3r/GUI/GUI_App.cpp:1701 src/slic3r/GUI/GUI_App.cpp:1712 msgid "Recreating" msgstr "Opnieuw aanmaken" -#: src/slic3r/GUI/GUI_App.cpp:1716 +#: src/slic3r/GUI/GUI_App.cpp:1715 msgid "Loading of current presets" msgstr "Laden van huidige presets" -#: src/slic3r/GUI/GUI_App.cpp:1721 +#: src/slic3r/GUI/GUI_App.cpp:1720 msgid "Loading of a mode view" msgstr "Laden van de weergavemodus" -#: src/slic3r/GUI/GUI_App.cpp:1859 +#: src/slic3r/GUI/GUI_App.cpp:1858 msgid "Choose one file (3MF/AMF):" -msgstr "Kies een 3MF- of AMF-bestand:" +msgstr "Kies een .3MF- of .AMF-bestand:" -#: src/slic3r/GUI/GUI_App.cpp:1871 -msgid "Choose one or more files (STL/3MF/STEP/OBJ/AMF/PRUSA):" -msgstr "Kies één of meer STL-, 3MF-, STEP-, OBJ-, AMF- of PRUSA- bestanden:" +#: src/slic3r/GUI/GUI_App.cpp:1870 +msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "Kies één of meer .STL-, .OBJ-, .AMF-, .3MF-, of .PRUSA-bestanden:" -#: src/slic3r/GUI/GUI_App.cpp:1883 +#: src/slic3r/GUI/GUI_App.cpp:1882 msgid "Choose one file (GCODE/.GCO/.G/.ngc/NGC):" -msgstr "Kies een bestand (gcode/.GCO/.G/.ngc/NGC):" +msgstr "Kies een bestand (.gcode/.GCO/.G/.ngc):" -#: src/slic3r/GUI/GUI_App.cpp:1894 +#: src/slic3r/GUI/GUI_App.cpp:1893 msgid "Changing of an application language" msgstr "Veranderen van de taal van het programma" -#: src/slic3r/GUI/GUI_App.cpp:2033 +#: src/slic3r/GUI/GUI_App.cpp:2032 msgid "Select the language" msgstr "Taalselectie" -#: src/slic3r/GUI/GUI_App.cpp:2033 +#: src/slic3r/GUI/GUI_App.cpp:2032 msgid "Language" msgstr "Wijzig taal (change language)" -#: src/slic3r/GUI/GUI_App.cpp:2182 +#: src/slic3r/GUI/GUI_App.cpp:2181 msgid "modified" msgstr "aangepast" -#: src/slic3r/GUI/GUI_App.cpp:2236 +#: src/slic3r/GUI/GUI_App.cpp:2235 #, c-format, boost-format msgid "Run %s" msgstr "Voer %s uit" -#: src/slic3r/GUI/GUI_App.cpp:2240 +#: src/slic3r/GUI/GUI_App.cpp:2239 msgid "&Configuration Snapshots" msgstr "Configuratiesnapshots" -#: src/slic3r/GUI/GUI_App.cpp:2240 +#: src/slic3r/GUI/GUI_App.cpp:2239 msgid "Inspect / activate configuration snapshots" msgstr "Inspecteer/activeer configuratiesnapshots" -#: src/slic3r/GUI/GUI_App.cpp:2241 +#: src/slic3r/GUI/GUI_App.cpp:2240 msgid "Take Configuration &Snapshot" msgstr "Neem configuratiesnapshot" -#: src/slic3r/GUI/GUI_App.cpp:2241 +#: src/slic3r/GUI/GUI_App.cpp:2240 msgid "Capture a configuration snapshot" msgstr "Neem een configuratiesnapshot op" -#: src/slic3r/GUI/GUI_App.cpp:2242 +#: src/slic3r/GUI/GUI_App.cpp:2241 msgid "Check for Configuration Updates" msgstr "Controleer op configuratie-updates" -#: src/slic3r/GUI/GUI_App.cpp:2242 +#: src/slic3r/GUI/GUI_App.cpp:2241 msgid "Check for configuration updates" msgstr "Controleer op configuratie-updates" -#: src/slic3r/GUI/GUI_App.cpp:2249 +#: src/slic3r/GUI/GUI_App.cpp:2248 msgid "&Preferences" msgstr "Voorkeuren" -#: src/slic3r/GUI/GUI_App.cpp:2255 +#: src/slic3r/GUI/GUI_App.cpp:2254 msgid "Application preferences" msgstr "Programmavoorkeuren" -#: src/slic3r/GUI/GUI_App.cpp:2260 src/slic3r/GUI/wxExtensions.cpp:708 +#: src/slic3r/GUI/GUI_App.cpp:2259 src/slic3r/GUI/wxExtensions.cpp:708 msgid "Simple" msgstr "Eenvoudig" -#: src/slic3r/GUI/GUI_App.cpp:2260 +#: src/slic3r/GUI/GUI_App.cpp:2259 msgid "Simple View Mode" msgstr "Eenvoudige weergave" -#: src/slic3r/GUI/GUI_App.cpp:2262 src/slic3r/GUI/wxExtensions.cpp:710 +#: src/slic3r/GUI/GUI_App.cpp:2261 src/slic3r/GUI/wxExtensions.cpp:710 msgctxt "Mode" msgid "Advanced" msgstr "Geavanceerd" -#: src/slic3r/GUI/GUI_App.cpp:2262 +#: src/slic3r/GUI/GUI_App.cpp:2261 msgid "Advanced View Mode" msgstr "Geavanceerde weergave" -#: src/slic3r/GUI/GUI_App.cpp:2263 src/slic3r/GUI/wxExtensions.cpp:711 +#: src/slic3r/GUI/GUI_App.cpp:2262 src/slic3r/GUI/wxExtensions.cpp:711 msgid "Expert" msgstr "Expert" -#: src/slic3r/GUI/GUI_App.cpp:2263 +#: src/slic3r/GUI/GUI_App.cpp:2262 msgid "Expert View Mode" msgstr "Expertweergave" -#: src/slic3r/GUI/GUI_App.cpp:2268 +#: src/slic3r/GUI/GUI_App.cpp:2267 msgid "Mode" msgstr "Modus" -#: src/slic3r/GUI/GUI_App.cpp:2268 +#: src/slic3r/GUI/GUI_App.cpp:2267 #, c-format, boost-format msgid "%s View Mode" msgstr "%s-weergavemodus" -#: src/slic3r/GUI/GUI_App.cpp:2271 +#: src/slic3r/GUI/GUI_App.cpp:2270 msgid "&Language" msgstr "Wijzig taal (change language)" -#: src/slic3r/GUI/GUI_App.cpp:2274 +#: src/slic3r/GUI/GUI_App.cpp:2273 msgid "Flash Printer &Firmware" msgstr "Flash printer firmware" -#: src/slic3r/GUI/GUI_App.cpp:2274 +#: src/slic3r/GUI/GUI_App.cpp:2273 msgid "Upload a firmware image into an Arduino based printer" msgstr "Upload een firmwarebestand op een Arduino-gebaseerde printer" -#: src/slic3r/GUI/GUI_App.cpp:2294 +#: src/slic3r/GUI/GUI_App.cpp:2293 msgid "Taking a configuration snapshot" msgstr "Neemt een configuratiesnapshot" -#: src/slic3r/GUI/GUI_App.cpp:2295 +#: src/slic3r/GUI/GUI_App.cpp:2294 msgid "" "Some presets are modified and the unsaved changes will not be captured by " "the configuration snapshot." @@ -3670,32 +3685,32 @@ msgstr "" "Sommige presets zijn aangepast en onopgeslagen instellingen worden niet " "meegenomen bij de configuratiesnapshot." -#: src/slic3r/GUI/GUI_App.cpp:2296 +#: src/slic3r/GUI/GUI_App.cpp:2295 msgid "Snapshot name" msgstr "Snapshotnaam" -#: src/slic3r/GUI/GUI_App.cpp:2312 +#: src/slic3r/GUI/GUI_App.cpp:2311 msgid "Loading a configuration snapshot" msgstr "Laad een configuratiesnapshot" -#: src/slic3r/GUI/GUI_App.cpp:2321 +#: src/slic3r/GUI/GUI_App.cpp:2320 #, boost-format msgid "Continue to activate a configuration snapshot %1%?" msgstr "Doorgaan om configuratiesnapshot %1% te activeren?" -#: src/slic3r/GUI/GUI_App.cpp:2335 +#: src/slic3r/GUI/GUI_App.cpp:2334 msgid "Failed to activate configuration snapshot." msgstr "Activeren van configuratiesnapshot mislukt." -#: src/slic3r/GUI/GUI_App.cpp:2354 +#: src/slic3r/GUI/GUI_App.cpp:2353 msgid "Restart application" msgstr "Herstart programma" -#: src/slic3r/GUI/GUI_App.cpp:2388 +#: src/slic3r/GUI/GUI_App.cpp:2387 msgid "Language selection" msgstr "Taalselectie" -#: src/slic3r/GUI/GUI_App.cpp:2391 +#: src/slic3r/GUI/GUI_App.cpp:2390 msgid "" "Switching the language will trigger application restart.\n" "You will lose content of the plater." @@ -3703,89 +3718,89 @@ msgstr "" "Het veranderen van de taal zorgt dat het programma opnieuw opstart.\n" "U verliest de geladen inhoud zoals getoond in de modelweergave." -#: src/slic3r/GUI/GUI_App.cpp:2393 src/slic3r/GUI/Preferences.cpp:582 +#: src/slic3r/GUI/GUI_App.cpp:2392 src/slic3r/GUI/Preferences.cpp:582 msgid "Do you want to proceed?" msgstr "" "Weet u zeker dat u door wilt gaan?\n" "Do you want to proceed?" -#: src/slic3r/GUI/GUI_App.cpp:2420 +#: src/slic3r/GUI/GUI_App.cpp:2419 msgid "&Configuration" msgstr "Configuratie" -#: src/slic3r/GUI/GUI_App.cpp:2534 src/slic3r/GUI/GUI_App.cpp:2595 +#: src/slic3r/GUI/GUI_App.cpp:2533 src/slic3r/GUI/GUI_App.cpp:2594 msgid "The preset modifications are successfully saved" msgid_plural "The presets modifications are successfully saved" msgstr[0] "De preset-aanpassing is succesvol opgeslagen" msgstr[1] "De presets-aanpassing is succesvol opgeslagen" -#: src/slic3r/GUI/GUI_App.cpp:2598 +#: src/slic3r/GUI/GUI_App.cpp:2597 msgid "For new project all modifications will be reseted" msgstr "Voor nieuwe projecten worden alle aanpassingen gereset" -#: src/slic3r/GUI/GUI_App.cpp:2636 +#: src/slic3r/GUI/GUI_App.cpp:2635 msgid "Loading a new project while the current project is modified." msgstr "Laad een nieuw project terwijl het huidige project is aangepast." -#: src/slic3r/GUI/GUI_App.cpp:2639 +#: src/slic3r/GUI/GUI_App.cpp:2638 msgid "Project is loading" msgstr "Project is aan het laden" -#: src/slic3r/GUI/GUI_App.cpp:2639 +#: src/slic3r/GUI/GUI_App.cpp:2638 msgid "Opening new project while some presets are unsaved." msgstr "Openen van nieuw project terwijl sommige presets niet opgeslagen zijn." -#: src/slic3r/GUI/GUI_App.cpp:2658 +#: src/slic3r/GUI/GUI_App.cpp:2657 msgid "The uploads are still ongoing" msgstr "De uploads zijn nog bezig" -#: src/slic3r/GUI/GUI_App.cpp:2658 +#: src/slic3r/GUI/GUI_App.cpp:2657 msgid "Stop them and continue anyway?" msgstr "Stop ze en ga toch door?" -#: src/slic3r/GUI/GUI_App.cpp:2662 +#: src/slic3r/GUI/GUI_App.cpp:2661 msgid "Ongoing uploads" msgstr "Lopende uploads" -#: src/slic3r/GUI/GUI_App.cpp:2876 +#: src/slic3r/GUI/GUI_App.cpp:2875 msgid "It's impossible to print multi-part object(s) with SLA technology." msgstr "" "Het is niet mogelijk meerdelige objecten te printen met de SLA-technologie." -#: src/slic3r/GUI/GUI_App.cpp:2877 src/slic3r/GUI/Jobs/SLAImportJob.cpp:235 -#: src/slic3r/GUI/Plater.cpp:2459 +#: src/slic3r/GUI/GUI_App.cpp:2876 src/slic3r/GUI/Jobs/SLAImportJob.cpp:235 +#: src/slic3r/GUI/Plater.cpp:2448 msgid "Please check your object list before preset changing." msgstr "Controleer de objectenlijst voor het wijzigen van de preset." -#: src/slic3r/GUI/GUI_App.cpp:2901 +#: src/slic3r/GUI/GUI_App.cpp:2900 msgid "Configuration is editing from ConfigWizard" msgstr "Configuratie is aangepast van de configuratiewizard" -#: src/slic3r/GUI/GUI_App.cpp:2926 +#: src/slic3r/GUI/GUI_App.cpp:2925 msgid "Select a gcode file:" -msgstr "Selecteer een gcode-bestand:" +msgstr "Selecteer een .gcode-bestand:" -#: src/slic3r/GUI/GUI_App.cpp:3100 src/slic3r/GUI/GUI_App.cpp:3123 +#: src/slic3r/GUI/GUI_App.cpp:3099 src/slic3r/GUI/GUI_App.cpp:3122 msgid "Open hyperlink in default browser?" msgstr "Open hyperlinks in de standaardbrowser?" -#: src/slic3r/GUI/GUI_App.cpp:3100 src/slic3r/GUI/GUI_App.cpp:3123 +#: src/slic3r/GUI/GUI_App.cpp:3099 src/slic3r/GUI/GUI_App.cpp:3122 msgid "PrusaSlicer: Open hyperlink" msgstr "PrusaSlicer: Open hyperlink" -#: src/slic3r/GUI/GUI_App.cpp:3105 src/slic3r/GUI/Preferences.cpp:382 +#: src/slic3r/GUI/GUI_App.cpp:3104 src/slic3r/GUI/Preferences.cpp:382 msgid "Suppress to open hyperlink in browser" msgstr "Hyperlinks openen in browser uitzetten" -#: src/slic3r/GUI/GUI_App.cpp:3107 src/slic3r/GUI/Plater.cpp:1732 +#: src/slic3r/GUI/GUI_App.cpp:3106 src/slic3r/GUI/Plater.cpp:1732 msgid "PrusaSlicer will remember your choice." msgstr "PrusaSlicer onthoudt uw keuze." -#: src/slic3r/GUI/GUI_App.cpp:3108 +#: src/slic3r/GUI/GUI_App.cpp:3107 msgid "You will not be asked about it again on hyperlinks hovering." -msgstr "" +msgstr "U wordt niet opnieuw gevraagd over het bewegen over hyperlinks." -#: src/slic3r/GUI/GUI_App.cpp:3109 src/slic3r/GUI/Plater.cpp:1736 +#: src/slic3r/GUI/GUI_App.cpp:3108 src/slic3r/GUI/Plater.cpp:1736 #, boost-format msgid "" "Visit \"Preferences\" and check \"%1%\"\n" @@ -3794,7 +3809,7 @@ msgstr "" "Ga naar Voorkeuren en controleer \"%1%\"\n" "om uw keuze te wijzigen." -#: src/slic3r/GUI/GUI_App.cpp:3111 src/slic3r/GUI/Plater.cpp:1738 +#: src/slic3r/GUI/GUI_App.cpp:3110 src/slic3r/GUI/Plater.cpp:1738 #: src/slic3r/GUI/UnsavedChangesDialog.cpp:906 msgid "PrusaSlicer: Don't ask me again" msgstr "PrusaSlicer: vraag het niet nogmaals" @@ -3812,12 +3827,12 @@ msgstr "Fatale fout, uitzondering gevonden: %1%" #: src/libslic3r/PrintConfig.cpp:286 src/libslic3r/PrintConfig.cpp:403 #: src/libslic3r/PrintConfig.cpp:446 src/libslic3r/PrintConfig.cpp:455 #: src/libslic3r/PrintConfig.cpp:707 src/libslic3r/PrintConfig.cpp:774 -#: src/libslic3r/PrintConfig.cpp:782 src/libslic3r/PrintConfig.cpp:1228 -#: src/libslic3r/PrintConfig.cpp:1315 src/libslic3r/PrintConfig.cpp:1540 -#: src/libslic3r/PrintConfig.cpp:1932 src/libslic3r/PrintConfig.cpp:1999 -#: src/libslic3r/PrintConfig.cpp:2233 src/libslic3r/PrintConfig.cpp:2819 -#: src/libslic3r/PrintConfig.cpp:2827 src/libslic3r/PrintConfig.cpp:2887 -#: src/libslic3r/PrintConfig.cpp:2896 src/libslic3r/PrintConfig.cpp:3067 +#: src/libslic3r/PrintConfig.cpp:782 src/libslic3r/PrintConfig.cpp:1227 +#: src/libslic3r/PrintConfig.cpp:1314 src/libslic3r/PrintConfig.cpp:1539 +#: src/libslic3r/PrintConfig.cpp:1931 src/libslic3r/PrintConfig.cpp:1998 +#: src/libslic3r/PrintConfig.cpp:2232 src/libslic3r/PrintConfig.cpp:2818 +#: src/libslic3r/PrintConfig.cpp:2826 src/libslic3r/PrintConfig.cpp:2886 +#: src/libslic3r/PrintConfig.cpp:2895 src/libslic3r/PrintConfig.cpp:3066 msgid "Layers and Perimeters" msgstr "Lagen en perimeters" @@ -3825,26 +3840,26 @@ msgstr "Lagen en perimeters" #: src/slic3r/GUI/GUI_Preview.cpp:249 src/slic3r/GUI/Tab.cpp:1547 #: src/slic3r/GUI/Tab.cpp:1549 src/libslic3r/ExtrusionEntity.cpp:340 #: src/libslic3r/ExtrusionEntity.cpp:372 src/libslic3r/PrintConfig.cpp:669 -#: src/libslic3r/PrintConfig.cpp:2064 src/libslic3r/PrintConfig.cpp:2073 -#: src/libslic3r/PrintConfig.cpp:2082 src/libslic3r/PrintConfig.cpp:2092 -#: src/libslic3r/PrintConfig.cpp:2101 src/libslic3r/PrintConfig.cpp:2523 -#: src/libslic3r/PrintConfig.cpp:2529 src/libslic3r/PrintConfig.cpp:2537 -#: src/libslic3r/PrintConfig.cpp:2550 src/libslic3r/PrintConfig.cpp:2560 -#: src/libslic3r/PrintConfig.cpp:2568 src/libslic3r/PrintConfig.cpp:2586 -#: src/libslic3r/PrintConfig.cpp:2603 src/libslic3r/PrintConfig.cpp:2624 -#: src/libslic3r/PrintConfig.cpp:2637 src/libslic3r/PrintConfig.cpp:2654 -#: src/libslic3r/PrintConfig.cpp:2672 src/libslic3r/PrintConfig.cpp:2687 -#: src/libslic3r/PrintConfig.cpp:2697 src/libslic3r/PrintConfig.cpp:2706 -#: src/libslic3r/PrintConfig.cpp:2717 src/libslic3r/PrintConfig.cpp:2731 -#: src/libslic3r/PrintConfig.cpp:2747 src/libslic3r/PrintConfig.cpp:2755 -#: src/libslic3r/PrintConfig.cpp:2756 src/libslic3r/PrintConfig.cpp:2765 -#: src/libslic3r/PrintConfig.cpp:2779 src/libslic3r/PrintConfig.cpp:2787 -#: src/libslic3r/PrintConfig.cpp:2801 +#: src/libslic3r/PrintConfig.cpp:2063 src/libslic3r/PrintConfig.cpp:2072 +#: src/libslic3r/PrintConfig.cpp:2081 src/libslic3r/PrintConfig.cpp:2091 +#: src/libslic3r/PrintConfig.cpp:2100 src/libslic3r/PrintConfig.cpp:2522 +#: src/libslic3r/PrintConfig.cpp:2528 src/libslic3r/PrintConfig.cpp:2536 +#: src/libslic3r/PrintConfig.cpp:2549 src/libslic3r/PrintConfig.cpp:2559 +#: src/libslic3r/PrintConfig.cpp:2567 src/libslic3r/PrintConfig.cpp:2585 +#: src/libslic3r/PrintConfig.cpp:2602 src/libslic3r/PrintConfig.cpp:2623 +#: src/libslic3r/PrintConfig.cpp:2636 src/libslic3r/PrintConfig.cpp:2653 +#: src/libslic3r/PrintConfig.cpp:2671 src/libslic3r/PrintConfig.cpp:2686 +#: src/libslic3r/PrintConfig.cpp:2696 src/libslic3r/PrintConfig.cpp:2705 +#: src/libslic3r/PrintConfig.cpp:2716 src/libslic3r/PrintConfig.cpp:2730 +#: src/libslic3r/PrintConfig.cpp:2746 src/libslic3r/PrintConfig.cpp:2754 +#: src/libslic3r/PrintConfig.cpp:2755 src/libslic3r/PrintConfig.cpp:2764 +#: src/libslic3r/PrintConfig.cpp:2778 src/libslic3r/PrintConfig.cpp:2786 +#: src/libslic3r/PrintConfig.cpp:2800 msgid "Support material" msgstr "Support" #: src/slic3r/GUI/GUI_Factories.cpp:59 src/slic3r/GUI/GUI_Factories.cpp:135 -#: src/libslic3r/PrintConfig.cpp:3023 src/libslic3r/PrintConfig.cpp:3031 +#: src/libslic3r/PrintConfig.cpp:3022 src/libslic3r/PrintConfig.cpp:3030 msgid "Wipe options" msgstr "Afveegopties" @@ -3854,41 +3869,41 @@ msgstr "Basisplaat en support" #: src/slic3r/GUI/GUI_Factories.cpp:129 src/slic3r/GUI/GUI_Preview.cpp:245 #: src/slic3r/GUI/Tab.cpp:1513 src/libslic3r/ExtrusionEntity.cpp:336 -#: src/libslic3r/ExtrusionEntity.cpp:364 src/libslic3r/PrintConfig.cpp:1556 -#: src/libslic3r/PrintConfig.cpp:1562 src/libslic3r/PrintConfig.cpp:1576 -#: src/libslic3r/PrintConfig.cpp:1586 src/libslic3r/PrintConfig.cpp:1594 -#: src/libslic3r/PrintConfig.cpp:1596 +#: src/libslic3r/ExtrusionEntity.cpp:364 src/libslic3r/PrintConfig.cpp:1555 +#: src/libslic3r/PrintConfig.cpp:1561 src/libslic3r/PrintConfig.cpp:1575 +#: src/libslic3r/PrintConfig.cpp:1585 src/libslic3r/PrintConfig.cpp:1593 +#: src/libslic3r/PrintConfig.cpp:1595 msgid "Ironing" msgstr "Strijken" -#: src/slic3r/GUI/GUI_Factories.cpp:130 src/libslic3r/PrintConfig.cpp:1279 -#: src/libslic3r/PrintConfig.cpp:1280 src/libslic3r/PrintConfig.cpp:1295 -#: src/libslic3r/PrintConfig.cpp:1305 +#: src/slic3r/GUI/GUI_Factories.cpp:130 src/libslic3r/PrintConfig.cpp:1278 +#: src/libslic3r/PrintConfig.cpp:1279 src/libslic3r/PrintConfig.cpp:1294 +#: src/libslic3r/PrintConfig.cpp:1304 msgid "Fuzzy Skin" msgstr "Oneffen oppervlak" #: src/slic3r/GUI/GUI_Factories.cpp:132 src/slic3r/GUI/GUI_Preview.cpp:220 #: src/slic3r/GUI/Tab.cpp:1581 src/libslic3r/PrintConfig.cpp:506 -#: src/libslic3r/PrintConfig.cpp:762 src/libslic3r/PrintConfig.cpp:1322 -#: src/libslic3r/PrintConfig.cpp:1513 src/libslic3r/PrintConfig.cpp:1595 -#: src/libslic3r/PrintConfig.cpp:1989 src/libslic3r/PrintConfig.cpp:2321 -#: src/libslic3r/PrintConfig.cpp:2374 src/libslic3r/PrintConfig.cpp:2872 +#: src/libslic3r/PrintConfig.cpp:762 src/libslic3r/PrintConfig.cpp:1321 +#: src/libslic3r/PrintConfig.cpp:1512 src/libslic3r/PrintConfig.cpp:1594 +#: src/libslic3r/PrintConfig.cpp:1988 src/libslic3r/PrintConfig.cpp:2320 +#: src/libslic3r/PrintConfig.cpp:2373 src/libslic3r/PrintConfig.cpp:2871 msgid "Speed" msgstr "Snelheid" #: src/slic3r/GUI/GUI_Factories.cpp:133 src/slic3r/GUI/Tab.cpp:1620 #: src/slic3r/GUI/Tab.cpp:2301 src/libslic3r/PrintConfig.cpp:792 -#: src/libslic3r/PrintConfig.cpp:1466 src/libslic3r/PrintConfig.cpp:1966 -#: src/libslic3r/PrintConfig.cpp:2342 src/libslic3r/PrintConfig.cpp:2616 -#: src/libslic3r/PrintConfig.cpp:2644 +#: src/libslic3r/PrintConfig.cpp:1465 src/libslic3r/PrintConfig.cpp:1965 +#: src/libslic3r/PrintConfig.cpp:2341 src/libslic3r/PrintConfig.cpp:2615 +#: src/libslic3r/PrintConfig.cpp:2643 msgid "Extruders" msgstr "Extruders" #: src/slic3r/GUI/GUI_Factories.cpp:134 src/libslic3r/PrintConfig.cpp:750 -#: src/libslic3r/PrintConfig.cpp:860 src/libslic3r/PrintConfig.cpp:1214 -#: src/libslic3r/PrintConfig.cpp:1474 src/libslic3r/PrintConfig.cpp:1975 -#: src/libslic3r/PrintConfig.cpp:2362 src/libslic3r/PrintConfig.cpp:2625 -#: src/libslic3r/PrintConfig.cpp:2859 +#: src/libslic3r/PrintConfig.cpp:860 src/libslic3r/PrintConfig.cpp:1213 +#: src/libslic3r/PrintConfig.cpp:1473 src/libslic3r/PrintConfig.cpp:1974 +#: src/libslic3r/PrintConfig.cpp:2361 src/libslic3r/PrintConfig.cpp:2624 +#: src/libslic3r/PrintConfig.cpp:2858 msgid "Extrusion Width" msgstr "Extrusiebreedte" @@ -3903,48 +3918,48 @@ msgstr "Skirt en brim" #: src/slic3r/GUI/Tab.cpp:1646 src/slic3r/GUI/Tab.cpp:2028 #: src/slic3r/GUI/Tab.cpp:2399 src/slic3r/GUI/Tab.cpp:4726 #: src/libslic3r/PrintConfig.cpp:259 src/libslic3r/PrintConfig.cpp:494 -#: src/libslic3r/PrintConfig.cpp:1415 src/libslic3r/PrintConfig.cpp:1502 -#: src/libslic3r/PrintConfig.cpp:1549 src/libslic3r/PrintConfig.cpp:2499 -#: src/libslic3r/PrintConfig.cpp:2509 src/libslic3r/PrintConfig.cpp:3047 -#: src/libslic3r/PrintConfig.cpp:3082 src/libslic3r/PrintConfig.cpp:3093 -#: src/libslic3r/PrintConfig.cpp:3108 src/libslic3r/PrintConfig.cpp:3121 -#: src/libslic3r/PrintConfig.cpp:3130 src/libslic3r/PrintConfig.cpp:3142 -#: src/libslic3r/PrintConfig.cpp:3339 +#: src/libslic3r/PrintConfig.cpp:1414 src/libslic3r/PrintConfig.cpp:1501 +#: src/libslic3r/PrintConfig.cpp:1548 src/libslic3r/PrintConfig.cpp:2498 +#: src/libslic3r/PrintConfig.cpp:2508 src/libslic3r/PrintConfig.cpp:3046 +#: src/libslic3r/PrintConfig.cpp:3081 src/libslic3r/PrintConfig.cpp:3092 +#: src/libslic3r/PrintConfig.cpp:3107 src/libslic3r/PrintConfig.cpp:3120 +#: src/libslic3r/PrintConfig.cpp:3129 src/libslic3r/PrintConfig.cpp:3141 +#: src/libslic3r/PrintConfig.cpp:3338 msgid "Advanced" msgstr "Geavanceerd" #: src/slic3r/GUI/GUI_Factories.cpp:140 src/slic3r/GUI/Plater.cpp:428 #: src/slic3r/GUI/Tab.cpp:4660 src/slic3r/GUI/Tab.cpp:4661 -#: src/libslic3r/PrintConfig.cpp:3533 src/libslic3r/PrintConfig.cpp:3540 -#: src/libslic3r/PrintConfig.cpp:3549 src/libslic3r/PrintConfig.cpp:3558 -#: src/libslic3r/PrintConfig.cpp:3568 src/libslic3r/PrintConfig.cpp:3578 -#: src/libslic3r/PrintConfig.cpp:3615 src/libslic3r/PrintConfig.cpp:3622 -#: src/libslic3r/PrintConfig.cpp:3633 src/libslic3r/PrintConfig.cpp:3643 -#: src/libslic3r/PrintConfig.cpp:3652 src/libslic3r/PrintConfig.cpp:3665 -#: src/libslic3r/PrintConfig.cpp:3675 src/libslic3r/PrintConfig.cpp:3684 -#: src/libslic3r/PrintConfig.cpp:3694 src/libslic3r/PrintConfig.cpp:3705 -#: src/libslic3r/PrintConfig.cpp:3713 +#: src/libslic3r/PrintConfig.cpp:3532 src/libslic3r/PrintConfig.cpp:3539 +#: src/libslic3r/PrintConfig.cpp:3548 src/libslic3r/PrintConfig.cpp:3557 +#: src/libslic3r/PrintConfig.cpp:3567 src/libslic3r/PrintConfig.cpp:3577 +#: src/libslic3r/PrintConfig.cpp:3614 src/libslic3r/PrintConfig.cpp:3621 +#: src/libslic3r/PrintConfig.cpp:3632 src/libslic3r/PrintConfig.cpp:3642 +#: src/libslic3r/PrintConfig.cpp:3651 src/libslic3r/PrintConfig.cpp:3664 +#: src/libslic3r/PrintConfig.cpp:3674 src/libslic3r/PrintConfig.cpp:3683 +#: src/libslic3r/PrintConfig.cpp:3693 src/libslic3r/PrintConfig.cpp:3704 +#: src/libslic3r/PrintConfig.cpp:3712 msgid "Supports" msgstr "Support" #: src/slic3r/GUI/GUI_Factories.cpp:141 src/slic3r/GUI/Plater.cpp:575 #: src/slic3r/GUI/Tab.cpp:4701 src/slic3r/GUI/Tab.cpp:4702 -#: src/slic3r/GUI/Tab.cpp:4774 src/libslic3r/PrintConfig.cpp:3721 -#: src/libslic3r/PrintConfig.cpp:3728 src/libslic3r/PrintConfig.cpp:3742 -#: src/libslic3r/PrintConfig.cpp:3753 src/libslic3r/PrintConfig.cpp:3763 -#: src/libslic3r/PrintConfig.cpp:3785 src/libslic3r/PrintConfig.cpp:3796 -#: src/libslic3r/PrintConfig.cpp:3803 src/libslic3r/PrintConfig.cpp:3810 -#: src/libslic3r/PrintConfig.cpp:3821 src/libslic3r/PrintConfig.cpp:3830 -#: src/libslic3r/PrintConfig.cpp:3839 +#: src/slic3r/GUI/Tab.cpp:4774 src/libslic3r/PrintConfig.cpp:3720 +#: src/libslic3r/PrintConfig.cpp:3727 src/libslic3r/PrintConfig.cpp:3741 +#: src/libslic3r/PrintConfig.cpp:3752 src/libslic3r/PrintConfig.cpp:3762 +#: src/libslic3r/PrintConfig.cpp:3784 src/libslic3r/PrintConfig.cpp:3795 +#: src/libslic3r/PrintConfig.cpp:3802 src/libslic3r/PrintConfig.cpp:3809 +#: src/libslic3r/PrintConfig.cpp:3820 src/libslic3r/PrintConfig.cpp:3829 +#: src/libslic3r/PrintConfig.cpp:3838 msgid "Pad" msgstr "Basisplaat" #: src/slic3r/GUI/GUI_Factories.cpp:142 src/slic3r/GUI/Tab.cpp:4719 #: src/slic3r/GUI/Tab.cpp:4720 src/libslic3r/SLA/Hollowing.cpp:73 #: src/libslic3r/SLA/Hollowing.cpp:85 src/libslic3r/SLA/Hollowing.cpp:105 -#: src/libslic3r/SLA/Hollowing.cpp:114 src/libslic3r/PrintConfig.cpp:3849 -#: src/libslic3r/PrintConfig.cpp:3856 src/libslic3r/PrintConfig.cpp:3866 -#: src/libslic3r/PrintConfig.cpp:3875 +#: src/libslic3r/SLA/Hollowing.cpp:114 src/libslic3r/PrintConfig.cpp:3848 +#: src/libslic3r/PrintConfig.cpp:3855 src/libslic3r/PrintConfig.cpp:3865 +#: src/libslic3r/PrintConfig.cpp:3874 msgid "Hollowing" msgstr "Uithollen" @@ -4037,13 +4052,13 @@ msgstr "Repareer met NetFabb" #: src/slic3r/GUI/GUI_Factories.cpp:715 msgid "Export as STL" -msgstr "Exporteer als STL-bestand" +msgstr "Exporteer als .STL-bestand" #: src/slic3r/GUI/GUI_Factories.cpp:726 msgid "Reload the selected volumes from disk" msgstr "Herlaad de geselecteerde volumes vanaf schijf" -#: src/slic3r/GUI/GUI_Factories.cpp:733 src/slic3r/GUI/Plater.cpp:3568 +#: src/slic3r/GUI/GUI_Factories.cpp:733 src/slic3r/GUI/Plater.cpp:3555 msgid "Replace with STL" msgstr "Vervang met STL" @@ -4057,7 +4072,7 @@ msgstr "Stel extruder in voor de geselecteerde items" #: src/slic3r/GUI/GUI_Factories.cpp:778 src/slic3r/Utils/Repetier.cpp:126 #: src/slic3r/Utils/Repetier.cpp:209 src/libslic3r/PrintConfig.cpp:634 -#: src/libslic3r/PrintConfig.cpp:2739 +#: src/libslic3r/PrintConfig.cpp:2738 msgid "Default" msgstr "Standaard" @@ -4069,24 +4084,24 @@ msgstr "Verschaal tot printvolume" msgid "Scale the selected object to fit the print volume" msgstr "Verschaal het geselecteerde object tot deze in het printvolume past" -#: src/slic3r/GUI/GUI_Factories.cpp:835 src/slic3r/GUI/Plater.cpp:5655 +#: src/slic3r/GUI/GUI_Factories.cpp:835 src/slic3r/GUI/Plater.cpp:5642 msgid "Convert from imperial units" msgstr "Converteer naar Engelse eenheden" -#: src/slic3r/GUI/GUI_Factories.cpp:836 src/slic3r/GUI/Plater.cpp:5656 +#: src/slic3r/GUI/GUI_Factories.cpp:836 src/slic3r/GUI/Plater.cpp:5643 msgid "Revert conversion from imperial units" msgstr "Conversie van Engelse eenheden ongedaan maken" -#: src/slic3r/GUI/GUI_Factories.cpp:837 src/slic3r/GUI/Plater.cpp:5657 +#: src/slic3r/GUI/GUI_Factories.cpp:837 src/slic3r/GUI/Plater.cpp:5644 msgid "Convert from meters" msgstr "Converteer vanaf meters" -#: src/slic3r/GUI/GUI_Factories.cpp:838 src/slic3r/GUI/Plater.cpp:5657 +#: src/slic3r/GUI/GUI_Factories.cpp:838 src/slic3r/GUI/Plater.cpp:5644 msgid "Revert conversion from meters" msgstr "Omrekenen van meters terugdraaien" -#: src/slic3r/GUI/GUI_Factories.cpp:859 src/slic3r/GUI/GUI_ObjectList.cpp:2135 -#: src/libslic3r/PrintConfig.cpp:4492 +#: src/slic3r/GUI/GUI_Factories.cpp:859 src/slic3r/GUI/GUI_ObjectList.cpp:2133 +#: src/libslic3r/PrintConfig.cpp:4491 msgid "Merge" msgstr "Samenvoegen" @@ -4147,7 +4162,7 @@ msgid "Split the selected object into individual parts" msgstr "Splits de geselecteerde objecten in individuele onderdelen" #: src/slic3r/GUI/GUI_Factories.cpp:944 src/slic3r/GUI/GUI_Factories.cpp:954 -#: src/slic3r/GUI/GUI_Factories.cpp:975 src/libslic3r/PrintConfig.cpp:4521 +#: src/slic3r/GUI/GUI_Factories.cpp:975 src/libslic3r/PrintConfig.cpp:4520 msgid "Split" msgstr "Splits" @@ -4265,7 +4280,7 @@ msgstr "Resterende fouten" #: src/slic3r/GUI/GUI_ObjectList.cpp:436 msgid "Right button click the icon to fix STL through Netfabb" msgstr "" -"Rechtermuisklik op het pictogram om het STL-bestand met NetFabb te repareren" +"Rechtermuisklik op het pictogram om het .STL-bestand met NetFabb te repareren" #: src/slic3r/GUI/GUI_ObjectList.cpp:482 msgid "Right button click the icon to change the object settings" @@ -4296,7 +4311,7 @@ msgid "Rename Sub-object" msgstr "Hernoem subobject" #: src/slic3r/GUI/GUI_ObjectList.cpp:1242 -#: src/slic3r/GUI/GUI_ObjectList.cpp:4006 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3997 msgid "Instances to Separated Objects" msgstr "Zet instanties om in objecten" @@ -4344,7 +4359,7 @@ msgstr "Laad bewerker" msgid "Loading" msgstr "Aan het laden" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1540 src/slic3r/GUI/Plater.cpp:2417 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1540 src/slic3r/GUI/Plater.cpp:2406 msgid "Loading file" msgstr "Bestand laden" @@ -4386,7 +4401,7 @@ msgstr "Verplaats objecten naar bed" #: src/slic3r/GUI/GUI_ObjectList.cpp:1856 msgid "Remove variable layer height" -msgstr "Verwijder variable laagdikte" +msgstr "Verwijder variabele laagdikte" #: src/slic3r/GUI/GUI_ObjectList.cpp:1877 msgid "Delete Settings" @@ -4427,69 +4442,69 @@ msgstr "" msgid "Split to Parts" msgstr "Splits naar onderdelen" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2142 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2140 msgid "Merged" msgstr "Samengevoegd" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2237 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2228 msgid "Merge all parts to the one single object" msgstr "Voeg alle delen samen tot een enkel object" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2269 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2260 msgid "Add Layers" msgstr "Voeg lagen toe" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2438 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2429 msgid "Group manipulation" msgstr "Groep bewerken" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2453 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2444 msgid "Object manipulation" msgstr "Object bewerken" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2486 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2477 msgid "Object Settings to modify" msgstr "Objectinstellingen om te bewerken" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2490 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2481 msgid "Part Settings to modify" msgstr "Onderdeelinstellingen om te bewerken" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2495 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2486 msgid "Layer range Settings to modify" msgstr "Laagbereikinstellingen om te bewerken" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2501 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2492 msgid "Part manipulation" msgstr "Onderdeel bewerken" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2507 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2498 msgid "Instance manipulation" msgstr "Instantie bewerken" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2514 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2505 msgid "Height ranges" msgstr "Hoogtebereik" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2514 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2505 msgid "Settings for height range" msgstr "Instellingen voor hoogtebereik" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2750 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2741 msgid "Delete Selected Item" msgstr "Verwijder geselecteerd item" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2943 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2934 msgid "Delete Selected" msgstr "Verwijder selectie" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3019 -#: src/slic3r/GUI/GUI_ObjectList.cpp:3047 -#: src/slic3r/GUI/GUI_ObjectList.cpp:3067 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3010 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3038 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3058 msgid "Add Height Range" msgstr "Voeg hoogtebereik toe" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3113 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3104 msgid "" "Cannot insert a new layer range after the current layer range.\n" "The next layer range is too thin to be split to two\n" @@ -4499,7 +4514,7 @@ msgstr "" "Het volgende laagbereik is te dun om in tweeën te splitsen\n" "zonder over de minimale laagdikte heen te gaan." -#: src/slic3r/GUI/GUI_ObjectList.cpp:3117 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3108 msgid "" "Cannot insert a new layer range between the current and the next layer " "range.\n" @@ -4511,7 +4526,7 @@ msgstr "" "Het gat tussen het huidige en volgende laagbereik is kleiner dan\n" "de minimum toegestane laagdikte." -#: src/slic3r/GUI/GUI_ObjectList.cpp:3122 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3113 msgid "" "Cannot insert a new layer range after the current layer range.\n" "Current layer range overlaps with the next layer range." @@ -4519,144 +4534,144 @@ msgstr "" "Kan geen nieuw laagbereik toevoegen na het huidige laagbereik.\n" "Het huidige laagbereik overlapt met het volgende laagbereik." -#: src/slic3r/GUI/GUI_ObjectList.cpp:3181 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3172 msgid "Edit Height Range" msgstr "Bewerk hoogtebereik" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3500 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3491 msgid "Selection-Remove from list" msgstr "Selectie - Verwijder van lijst" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3512 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3503 msgid "Selection-Add from list" msgstr "Selectie - Voeg toe aan lijst" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3649 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3640 msgid "Object or Instance" msgstr "Object of instantie" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3650 -#: src/slic3r/GUI/GUI_ObjectList.cpp:3789 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3641 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3780 msgid "Part" msgstr "Onderdeel" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3650 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3641 msgid "Layer" msgstr "Laag" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3652 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3643 msgid "Unsupported selection" msgstr "Niet-ondersteunde selectie" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3653 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3644 #, c-format, boost-format msgid "You started your selection with %s Item." msgstr "De selectie is gestart met item %s." -#: src/slic3r/GUI/GUI_ObjectList.cpp:3654 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3645 #, c-format, boost-format msgid "In this mode you can select only other %s Items%s" msgstr "In deze modus kunt u alleen andere %s items %s selecteren" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3657 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3648 msgid "of a current Object" msgstr "van het huidige object" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3662 -#: src/slic3r/GUI/GUI_ObjectList.cpp:3737 src/slic3r/GUI/Plater.cpp:181 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3653 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3728 src/slic3r/GUI/Plater.cpp:181 msgid "Info" msgstr "Info" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3784 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3775 msgid "You can't change a type of the last solid part of the object." msgstr "" "U kunt het type van het laatste onderdeel van een object niet wijzigen." -#: src/slic3r/GUI/GUI_ObjectList.cpp:3789 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3780 msgid "Negative Volume" msgstr "Negatief volume" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3789 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3780 msgid "Modifier" msgstr "Modificator" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3789 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3780 msgid "Support Blocker" msgstr "Supportblokkering" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3789 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3780 msgid "Support Enforcer" msgstr "Supportforcering" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3790 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3781 msgid "Select type of part" msgstr "Selecteer onderdeeltype" -#: src/slic3r/GUI/GUI_ObjectList.cpp:3795 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3786 msgid "Change Part Type" msgstr "Wijzig onderdeeltype" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4028 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4019 msgid "Enter new name" msgstr "Voer nieuwe naam in" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4028 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4019 msgid "Renaming" msgstr "Hernoemen" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4091 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4082 msgid "Repairing model" msgstr "Model repareren" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4120 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4111 msgid "Fix through NetFabb" msgstr "Repareer met NetFabb" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4123 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4114 msgid "Fixing through NetFabb" msgstr "Repareren met NetFabb" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4153 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4144 msgid "The following model was repaired successfully" msgid_plural "The following models were repaired successfully" msgstr[0] "Het volgende model is succesvol gerepareerd" msgstr[1] "De volgende modellen zijn succesvol gerepareerd" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4159 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4150 msgid "Folowing model repair failed" msgid_plural "Folowing models repair failed" msgstr[0] "Volgende model repareren mislukt" msgstr[1] "Volgende modellen repareren mislukt" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4164 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4155 msgid "Repairing was canceled" msgstr "Repareren is stopgezet" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4276 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4267 msgid "Change Extruders" msgstr "Wijzig extruders" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4416 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4407 msgid "Set Printable group" msgstr "Stel printbare groep in" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4416 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4407 msgid "Set Unprintable group" msgstr "Stel onprintbare groep in" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4418 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4409 msgid "Set Printable" msgstr "Stel in op printbaar" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4418 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4409 msgid "Set Unprintable" msgstr "Stel in op niet-printbaar" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4419 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4410 msgid "Set Printable Instance" msgstr "Stel printbare instanties in" -#: src/slic3r/GUI/GUI_ObjectList.cpp:4419 +#: src/slic3r/GUI/GUI_ObjectList.cpp:4410 msgid "Set Unprintable Instance" msgstr "Stel instantie in op niet-printbaar" @@ -4797,7 +4812,7 @@ msgstr "Weergave" msgid "Height" msgstr "Hoogte" -#: src/slic3r/GUI/GUI_Preview.cpp:219 src/libslic3r/PrintConfig.cpp:3001 +#: src/slic3r/GUI/GUI_Preview.cpp:219 src/libslic3r/PrintConfig.cpp:3000 msgid "Width" msgstr "Breedte" @@ -4842,14 +4857,14 @@ msgid "Internal infill" msgstr "Inwendige vulling" #: src/slic3r/GUI/GUI_Preview.cpp:243 src/libslic3r/ExtrusionEntity.cpp:334 -#: src/libslic3r/ExtrusionEntity.cpp:360 src/libslic3r/PrintConfig.cpp:2361 -#: src/libslic3r/PrintConfig.cpp:2373 +#: src/libslic3r/ExtrusionEntity.cpp:360 src/libslic3r/PrintConfig.cpp:2360 +#: src/libslic3r/PrintConfig.cpp:2372 msgid "Solid infill" msgstr "Dichte vulling" #: src/slic3r/GUI/GUI_Preview.cpp:244 src/libslic3r/ExtrusionEntity.cpp:335 -#: src/libslic3r/ExtrusionEntity.cpp:362 src/libslic3r/PrintConfig.cpp:2858 -#: src/libslic3r/PrintConfig.cpp:2871 +#: src/libslic3r/ExtrusionEntity.cpp:362 src/libslic3r/PrintConfig.cpp:2857 +#: src/libslic3r/PrintConfig.cpp:2870 msgid "Top solid infill" msgstr "Bovenste dichte vulling" @@ -4859,7 +4874,7 @@ msgid "Bridge infill" msgstr "Brugvulling" #: src/slic3r/GUI/GUI_Preview.cpp:247 src/libslic3r/ExtrusionEntity.cpp:338 -#: src/libslic3r/ExtrusionEntity.cpp:368 src/libslic3r/PrintConfig.cpp:1321 +#: src/libslic3r/ExtrusionEntity.cpp:368 src/libslic3r/PrintConfig.cpp:1320 msgid "Gap fill" msgstr "Gatenvulling" @@ -4869,7 +4884,7 @@ msgid "Skirt/Brim" msgstr "Skirt/Brim" #: src/slic3r/GUI/GUI_Preview.cpp:250 src/libslic3r/ExtrusionEntity.cpp:341 -#: src/libslic3r/ExtrusionEntity.cpp:374 src/libslic3r/PrintConfig.cpp:2705 +#: src/libslic3r/ExtrusionEntity.cpp:374 src/libslic3r/PrintConfig.cpp:2704 msgid "Support material interface" msgstr "Supportinterface" @@ -4982,15 +4997,15 @@ msgstr "Er is een onverwachte fout opgetreden" #: src/slic3r/GUI/Jobs/RotoptimizeJob.hpp:21 msgid "Best surface quality" -msgstr "" +msgstr "Beste oppervlaktekwaliteit" #: src/slic3r/GUI/Jobs/RotoptimizeJob.hpp:23 msgid "Optimize object rotation for best surface quality." -msgstr "" +msgstr "Optimaliseer objectrotatie voor beste oppervlaktekwaliteit." #: src/slic3r/GUI/Jobs/RotoptimizeJob.hpp:24 msgid "Reduced overhang slopes" -msgstr "" +msgstr "Gereduceerde overhanghellingen" #: src/slic3r/GUI/Jobs/RotoptimizeJob.hpp:26 msgid "" @@ -4999,14 +5014,18 @@ msgid "" "Note that this method will try to find the best surface of the object for " "touching the print bed if no elevation is set." msgstr "" +"Optimaliseer objectrotatie voor een minimale hoeveelheid overhangingen die " +"support nodig hebben.\n" +"Let op dat deze methode zoekt naar het beste vlak van het object om op het " +"printbed te leggen als geen verhoging ingesteld is." #: src/slic3r/GUI/Jobs/RotoptimizeJob.hpp:30 msgid "Lowest Z height" -msgstr "" +msgstr "Laagste Z-hoogte" #: src/slic3r/GUI/Jobs/RotoptimizeJob.hpp:32 msgid "Rotate the model to have the lowest z height for faster print time." -msgstr "" +msgstr "Roteer het model voor een minimale hoogte voor snellere printtijd." #: src/slic3r/GUI/Jobs/RotoptimizeJob.cpp:59 msgid "Searching for optimal orientation" @@ -5074,7 +5093,7 @@ msgstr "Succesvol geïmporteerd." #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:187 msgid "The file does not exist." -msgstr "" +msgstr "Het bestand bestaat niet." #: src/slic3r/GUI/Jobs/SLAImportJob.cpp:221 msgid "" @@ -5084,11 +5103,11 @@ msgstr "" "Het geïmporteerde SLA-archief bevat geen presets. De huidige SLA-presets " "worden gebruikt als oplossing." -#: src/slic3r/GUI/Jobs/SLAImportJob.cpp:234 src/slic3r/GUI/Plater.cpp:2458 +#: src/slic3r/GUI/Jobs/SLAImportJob.cpp:234 src/slic3r/GUI/Plater.cpp:2447 msgid "You cannot load SLA project with a multi-part object on the bed" msgstr "U kunt geen SLA-project laden met een meerdelig object op het bed" -#: src/slic3r/GUI/Jobs/SLAImportJob.cpp:236 src/slic3r/GUI/Plater.cpp:2460 +#: src/slic3r/GUI/Jobs/SLAImportJob.cpp:236 src/slic3r/GUI/Plater.cpp:2449 msgid "Attention!" msgstr "Attentie!" @@ -5101,42 +5120,44 @@ msgid "New project, clear plater" msgstr "Start nieuw project, verwijder modellen" #: src/slic3r/GUI/KBShortcutsDialog.cpp:78 -msgid "Open project AMF/3MF with config, clear plater" -msgstr "Open AMF- of 3MF-project met configuratie, verwijder huidige modellen" +msgid "Open project STL/OBJ/AMF/3MF with config, clear plater" +msgstr "" +"Open .STL-, .OBJ-, .AMF- of .3MF-project met configuratie, verwijder huidige " +"modellen" #: src/slic3r/GUI/KBShortcutsDialog.cpp:79 msgid "Save project (3mf)" -msgstr "3MF-project opslaan" +msgstr ".3MF-project opslaan" #: src/slic3r/GUI/KBShortcutsDialog.cpp:80 msgid "Save project as (3mf)" -msgstr "3MF-project opslaan als" +msgstr ".3MF-project opslaan als" #: src/slic3r/GUI/KBShortcutsDialog.cpp:81 msgid "(Re)slice" msgstr "(Her)slice" #: src/slic3r/GUI/KBShortcutsDialog.cpp:83 -msgid "Import STL/3MF/STEP/OBJ/AMF without config, keep plater" +msgid "Import STL/OBJ/AMF/3MF without config, keep plater" msgstr "" -"Importeer STL-, 3MF-, STEP-, OBJ- of AMF-bestanden zonder configuratie en " +"Importeer .STL-, .OBJ-, .AMF- of .3MF-bestanden zonder configuratie en " "behoud modellen" #: src/slic3r/GUI/KBShortcutsDialog.cpp:84 msgid "Import Config from ini/amf/3mf/gcode" -msgstr "Importeer configuratie van INI-, AMF-, 3MF- of gcode-bestand" +msgstr "Importeer configuratie van .INI-, .AMF-, .3MF- of .gcode-bestand" #: src/slic3r/GUI/KBShortcutsDialog.cpp:85 msgid "Load Config from ini/amf/3mf/gcode and merge" msgstr "" -"Laad configuratie van INI-, AMF-, 3MF- of gcode-bestanden en voeg samen" +"Laad configuratie van .INI-, .AMF-, .3MF- of .gcode-bestanden en voeg samen" #: src/slic3r/GUI/KBShortcutsDialog.cpp:87 src/slic3r/GUI/Plater.cpp:913 -#: src/slic3r/GUI/Plater.cpp:6543 src/libslic3r/PrintConfig.cpp:4392 +#: src/slic3r/GUI/Plater.cpp:6530 src/libslic3r/PrintConfig.cpp:4391 msgid "Export G-code" -msgstr "Exporteer gcode-bestand" +msgstr "Exporteer .gcode-bestand" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:88 src/slic3r/GUI/Plater.cpp:6544 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:88 src/slic3r/GUI/Plater.cpp:6531 msgid "Send G-code" msgstr "Stuur G-code" @@ -5409,7 +5430,8 @@ msgstr "Klap de zijbalk in/uit" #: src/slic3r/GUI/KBShortcutsDialog.cpp:166 msgid "Show/Hide 3Dconnexion devices settings dialog, if enabled" msgstr "" -"Toon/verberg 3DConnexion-apparaten-instellingenvenster als dit aanstaat" +"Toon/verberg 3DConnexion-apparaten-instellingenvenster als dit is " +"ingeschakeld" #: src/slic3r/GUI/KBShortcutsDialog.cpp:169 #: src/slic3r/GUI/KBShortcutsDialog.cpp:172 @@ -5527,7 +5549,7 @@ msgstr "Toon/verberg legenda en geschatte printtijd" msgid "Show/Hide G-code window" msgstr "Toon/verberg G-code venster" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 src/slic3r/GUI/Plater.cpp:4480 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:230 src/slic3r/GUI/Plater.cpp:4467 #: src/slic3r/GUI/Tab.cpp:2829 msgid "Preview" msgstr "Sliceweergave" @@ -5667,7 +5689,7 @@ msgid "Printer Settings" msgstr "Printerinstellingen" #: src/slic3r/GUI/MainFrame.cpp:632 src/slic3r/GUI/Plater.cpp:1719 -#: src/slic3r/GUI/Plater.cpp:2850 +#: src/slic3r/GUI/Plater.cpp:2839 msgid "Untitled" msgstr "Zonder titel" @@ -5694,7 +5716,7 @@ msgstr "Download de laatste softwareversie vanuit uw browser" #: src/slic3r/GUI/MainFrame.cpp:1081 #, c-format, boost-format msgid "%s &Website" -msgstr "%s-&website" +msgstr "%s-website" #: src/slic3r/GUI/MainFrame.cpp:1082 #, c-format, boost-format @@ -5729,7 +5751,7 @@ msgstr "Rapporteer een fout op %s" #: src/slic3r/GUI/MainFrame.cpp:1095 src/slic3r/GUI/MainFrame.cpp:1098 #, c-format, boost-format msgid "&About %s" -msgstr "&Over %s" +msgstr "Over %s" #: src/slic3r/GUI/MainFrame.cpp:1095 src/slic3r/GUI/MainFrame.cpp:1098 msgid "Show about dialog" @@ -5761,8 +5783,8 @@ msgstr "Isometrisch aanzicht" #. TRN To be shown in the main menu View->Top #. TRN To be shown in Print Settings "Top solid layers" -#: src/slic3r/GUI/MainFrame.cpp:1125 src/libslic3r/PrintConfig.cpp:2886 -#: src/libslic3r/PrintConfig.cpp:2895 +#: src/slic3r/GUI/MainFrame.cpp:1125 src/libslic3r/PrintConfig.cpp:2885 +#: src/libslic3r/PrintConfig.cpp:2894 msgid "Top" msgstr "Bovenkant" @@ -5790,7 +5812,7 @@ msgstr "Voorkant" msgid "Front View" msgstr "Vooraanzicht" -#: src/slic3r/GUI/MainFrame.cpp:1132 src/libslic3r/PrintConfig.cpp:2243 +#: src/slic3r/GUI/MainFrame.cpp:1132 src/libslic3r/PrintConfig.cpp:2242 msgid "Rear" msgstr "Achterzijde" @@ -5859,8 +5881,8 @@ msgid "Save current project file as" msgstr "Projectbestand opslaan als" #: src/slic3r/GUI/MainFrame.cpp:1208 -msgid "Import STL/3MF/STEP/OBJ/AM&F" -msgstr "Importeer STL-, 3MF-, STEP-, OBJ- of AMF-bestanden" +msgid "Import STL/OBJ/AM&F/3MF" +msgstr "Importeer .STL-, .OBJ-, .AMF- of .3MF-bestanden" #: src/slic3r/GUI/MainFrame.cpp:1208 msgid "Load a model" @@ -5916,7 +5938,7 @@ msgstr "Exporteer G-code" #: src/slic3r/GUI/MainFrame.cpp:1234 msgid "Export current plate as G-code" -msgstr "Exporteer modellen als gcode-bestand" +msgstr "Exporteer modellen als .gcode-bestand" #: src/slic3r/GUI/MainFrame.cpp:1238 src/slic3r/GUI/MainFrame.cpp:1587 msgid "S&end G-code" @@ -5940,7 +5962,7 @@ msgstr "Exporteer modellen als STL" #: src/slic3r/GUI/MainFrame.cpp:1246 msgid "Export current plate as STL" -msgstr "Exporteer modellen als STL-bestand" +msgstr "Exporteer modellen als .STL-bestand" #: src/slic3r/GUI/MainFrame.cpp:1249 msgid "Export Plate as STL &Including Supports" @@ -5948,7 +5970,7 @@ msgstr "Exporteer modellen inclusief supports als STL" #: src/slic3r/GUI/MainFrame.cpp:1249 msgid "Export current plate as STL including supports" -msgstr "Exporteer modellen met support als STL-bestand" +msgstr "Exporteer modellen met support als .STL-bestand" #: src/slic3r/GUI/MainFrame.cpp:1257 src/slic3r/GUI/MainFrame.cpp:1538 msgid "Export &Toolpaths as OBJ" @@ -5956,7 +5978,7 @@ msgstr "Exporteer toolpaden als OBJ" #: src/slic3r/GUI/MainFrame.cpp:1257 src/slic3r/GUI/MainFrame.cpp:1538 msgid "Export toolpaths as OBJ" -msgstr "Exporteer toolpaden als OBJ-bestand" +msgstr "Exporteer toolpaden als .OBJ-bestand" #: src/slic3r/GUI/MainFrame.cpp:1261 msgid "Export &Config" @@ -6000,7 +6022,7 @@ msgstr "Snel slicen" #: src/slic3r/GUI/MainFrame.cpp:1280 msgid "Slice a file into a G-code" -msgstr "Slice naar een gcode-bestand" +msgstr "Slice naar een .gcode-bestand" #: src/slic3r/GUI/MainFrame.cpp:1286 msgid "Quick Slice and Save As" @@ -6008,7 +6030,7 @@ msgstr "Snel slicen en opslaan als" #: src/slic3r/GUI/MainFrame.cpp:1286 msgid "Slice a file into a G-code, save as" -msgstr "Slice naar gcode-bestand, opslaan als" +msgstr "Slice naar .gcode-bestand, opslaan als" #: src/slic3r/GUI/MainFrame.cpp:1292 msgid "Repeat Last Quick Slice" @@ -6020,7 +6042,7 @@ msgstr "Herhaal laatste snelle slice" #: src/slic3r/GUI/MainFrame.cpp:1300 msgid "(Re)Slice No&w" -msgstr "(&Her)slice nu" +msgstr "(Her)slice nu" #: src/slic3r/GUI/MainFrame.cpp:1300 msgid "Start new slicing process" @@ -6028,11 +6050,11 @@ msgstr "Start nieuw sliceproces" #: src/slic3r/GUI/MainFrame.cpp:1304 msgid "&Repair STL file" -msgstr "&Repareer STL-bestand" +msgstr "Repareer .STL-bestand" #: src/slic3r/GUI/MainFrame.cpp:1304 msgid "Automatically repair an STL file" -msgstr "Automatisch een STL-bestand repareren" +msgstr "Automatisch een .STL-bestand repareren" #: src/slic3r/GUI/MainFrame.cpp:1308 msgid "&G-code Preview" @@ -6231,15 +6253,15 @@ msgstr "Toon" #: src/slic3r/GUI/MainFrame.cpp:1463 src/slic3r/GUI/MainFrame.cpp:1564 msgid "&Help" -msgstr "&Help" +msgstr "Help" #: src/slic3r/GUI/MainFrame.cpp:1525 msgid "&Open G-code" -msgstr "&Open G-code" +msgstr "Open G-code" #: src/slic3r/GUI/MainFrame.cpp:1541 msgid "Open &PrusaSlicer" -msgstr "Open &PrusaSlicer" +msgstr "Open PrusaSlicer" #: src/slic3r/GUI/MainFrame.cpp:1586 msgid "E&xport" @@ -6255,7 +6277,7 @@ msgstr "Materiaalinstellingentab" #: src/slic3r/GUI/MainFrame.cpp:1613 msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" -msgstr "Kies een STL-, OBJ-, AMF-, 3MF- of PRUSA-bestand om te slicen:" +msgstr "Kies een .STL-, .OBJ-, .AMF-, .3MF- of .PRUSA-bestand om te slicen:" #: src/slic3r/GUI/MainFrame.cpp:1625 msgid "No previously sliced file." @@ -6288,10 +6310,10 @@ msgstr "G-code" #: src/slic3r/GUI/MainFrame.cpp:1680 msgid "Save zip file as:" -msgstr "ZIP-bestand opslaan als:" +msgstr ".ZIP-bestand opslaan als:" -#: src/slic3r/GUI/MainFrame.cpp:1689 src/slic3r/GUI/Plater.cpp:3329 -#: src/slic3r/GUI/Plater.cpp:6064 src/slic3r/GUI/Tab.cpp:1663 +#: src/slic3r/GUI/MainFrame.cpp:1689 src/slic3r/GUI/Plater.cpp:3316 +#: src/slic3r/GUI/Plater.cpp:6051 src/slic3r/GUI/Tab.cpp:1663 #: src/slic3r/GUI/Tab.cpp:4727 msgid "Slicing" msgstr "Slicen" @@ -6313,17 +6335,17 @@ msgstr "Slicen klaar!" #: src/slic3r/GUI/MainFrame.cpp:1733 msgid "Select the STL file to repair:" -msgstr "Selecteer het STL-bestand om te repareren:" +msgstr "Selecteer het .STL-bestand om te repareren:" #: src/slic3r/GUI/MainFrame.cpp:1743 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" -msgstr "OBJ-bestand opslaan als:" +msgstr ".OBJ-bestand opslaan als:" #: src/slic3r/GUI/MainFrame.cpp:1754 msgid "Your file was repaired." msgstr "Het bestand is gerepareerd." -#: src/slic3r/GUI/MainFrame.cpp:1754 src/libslic3r/PrintConfig.cpp:4497 +#: src/slic3r/GUI/MainFrame.cpp:1754 src/libslic3r/PrintConfig.cpp:4496 msgid "Repair" msgstr "Repareer" @@ -6406,7 +6428,7 @@ msgstr "%s fout" #: src/slic3r/GUI/MsgDialog.cpp:228 #, c-format, boost-format msgid "%s has encountered an error" -msgstr "%s heeft een fout veroorzaakt" +msgstr "%s heeft een fout opgelopen" #: src/slic3r/GUI/MsgDialog.cpp:247 #, c-format, boost-format @@ -6506,22 +6528,22 @@ msgstr "Annuleer upload" #, c-format, boost-format msgid "%1$d object was loaded with custom supports." msgid_plural "%1$d objects were loaded with custom supports." -msgstr[0] "%1$d object is geladen met aangepaste supports." -msgstr[1] "%1$d objecten zijn geladen met aangepaste supports." +msgstr[0] "%1$d object is geladen met custom supports." +msgstr[1] "%1$d objecten zijn geladen met custom supports." #: src/slic3r/GUI/NotificationManager.cpp:997 #, c-format, boost-format msgid "%1$d object was loaded with custom seam." msgid_plural "%1$d objects were loaded with custom seam." -msgstr[0] "%1$d object is geladen met aangepaste naad." -msgstr[1] "%1$d objecten zijn geladen met aangepaste naad." +msgstr[0] "%1$d object is geladen met een custom naad." +msgstr[1] "%1$d objecten zijn geladen met een custom naad." #: src/slic3r/GUI/NotificationManager.cpp:998 #, c-format, boost-format msgid "%1$d object was loaded with multimaterial painting." msgid_plural "%1$d objects were loaded with multimaterial painting." -msgstr[0] "%1$d object is geladen met multi-material inkleuring." -msgstr[1] "%1$d objecten zijn geladen met multi-material inkleuring." +msgstr[0] "%1$d object is geladen met multi-materiaal schilderingen." +msgstr[1] "%1$d objecten zijn geladen met multi-materiaal schilderingen." #: src/slic3r/GUI/NotificationManager.cpp:999 #, c-format, boost-format @@ -6534,8 +6556,8 @@ msgstr[1] "%1$d objecten zijn geladen met variabele laagdikte." #, c-format, boost-format msgid "%1$d object was loaded with partial sinking." msgid_plural "%1$d objects were loaded with partial sinking." -msgstr[0] "%1$d object is geladen met een (gedeeltelijke) verzakking." -msgstr[1] "%1$d objecten zijn geladen met een (gedeeltelijke) verzakking." +msgstr[0] "%1$d object is geladen met gedeeltelijk verzakken." +msgstr[1] "%1$d objecten zijn geladen met gedeeltelijk verzakken." #: src/slic3r/GUI/NotificationManager.cpp:1113 msgid "Slicing finished." @@ -6560,7 +6582,7 @@ msgstr "Fout:" #: src/slic3r/GUI/NotificationManager.cpp:1459 #: src/slic3r/GUI/NotificationManager.cpp:1486 #: src/slic3r/GUI/NotificationManager.cpp:1494 -#: src/slic3r/GUI/NotificationManager.cpp:1505 src/slic3r/GUI/Plater.cpp:3197 +#: src/slic3r/GUI/NotificationManager.cpp:1505 src/slic3r/GUI/Plater.cpp:3184 msgid "WARNING:" msgstr "Waarschuwing:" @@ -6595,7 +6617,7 @@ msgstr "Lagen" msgid "Range" msgstr "Bereik" -#: src/slic3r/GUI/OpenGLManager.cpp:258 +#: src/slic3r/GUI/OpenGLManager.cpp:257 #, c-format, boost-format msgid "" "PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" @@ -6604,11 +6626,11 @@ msgstr "" "PrusaSlicer vereist een grafische driver die OpenGL 2.0 kan draaien,\n" "terwijl OpenGL-versie %s, render %s, leverancier %s is gedetecteerd." -#: src/slic3r/GUI/OpenGLManager.cpp:261 +#: src/slic3r/GUI/OpenGLManager.cpp:260 msgid "You may need to update your graphics card driver." msgstr "U moet mogelijk uw grafische kaart updaten." -#: src/slic3r/GUI/OpenGLManager.cpp:264 +#: src/slic3r/GUI/OpenGLManager.cpp:263 msgid "" "As a workaround, you may run PrusaSlicer with a software rendered 3D " "graphics by running prusa-slicer.exe with the --sw-renderer parameter." @@ -6617,11 +6639,11 @@ msgstr "" "modelweergave door prusa-slicer.exe te draaien met de --sw-renderer " "parameter." -#: src/slic3r/GUI/OpenGLManager.cpp:266 +#: src/slic3r/GUI/OpenGLManager.cpp:265 msgid "Unsupported OpenGL version" msgstr "Niet-ondersteunde OpenGL-versie" -#: src/slic3r/GUI/OpenGLManager.cpp:274 +#: src/slic3r/GUI/OpenGLManager.cpp:273 #, c-format, boost-format msgid "" "Unable to load the following shaders:\n" @@ -6630,7 +6652,7 @@ msgstr "" "Kan de volgende sjablonen niet laden:\n" "%s" -#: src/slic3r/GUI/OpenGLManager.cpp:275 +#: src/slic3r/GUI/OpenGLManager.cpp:274 msgid "Error loading shaders" msgstr "Fout bij het laden van de sjablonen" @@ -6732,7 +6754,7 @@ msgstr "De ingevoerde naam is leeg. Kan niet opgeslagen worden." #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:634 msgid "You have to enter a printer name." -msgstr "" +msgstr "U moet een printernaam invoeren." #: src/slic3r/GUI/PhysicalPrinterDialog.cpp:642 #, boost-format @@ -6825,8 +6847,8 @@ msgstr "Aantal toolwisselingen" msgid "Select what kind of support do you need" msgstr "Selecteer welk type support nodig is" -#: src/slic3r/GUI/Plater.cpp:433 src/libslic3r/PrintConfig.cpp:2559 -#: src/libslic3r/PrintConfig.cpp:3614 +#: src/slic3r/GUI/Plater.cpp:433 src/libslic3r/PrintConfig.cpp:2558 +#: src/libslic3r/PrintConfig.cpp:3613 msgid "Support on build plate only" msgstr "Support alleen op het bed" @@ -6864,12 +6886,12 @@ msgstr "Onder het object" msgid "Around object" msgstr "Rondom het object" -#: src/slic3r/GUI/Plater.cpp:894 src/slic3r/GUI/Plater.cpp:6544 +#: src/slic3r/GUI/Plater.cpp:894 src/slic3r/GUI/Plater.cpp:6531 msgid "Send to printer" msgstr "Stuur naar printer" -#: src/slic3r/GUI/Plater.cpp:914 src/slic3r/GUI/Plater.cpp:3329 -#: src/slic3r/GUI/Plater.cpp:6067 +#: src/slic3r/GUI/Plater.cpp:914 src/slic3r/GUI/Plater.cpp:3316 +#: src/slic3r/GUI/Plater.cpp:6054 msgid "Slice now" msgstr "Slice nu" @@ -6923,8 +6945,8 @@ msgstr "Filament in extruder %1%" msgid "(including spool)" msgstr "(inclusief spoel)" -#: src/slic3r/GUI/Plater.cpp:1411 src/libslic3r/PrintConfig.cpp:1068 -#: src/libslic3r/PrintConfig.cpp:3408 src/libslic3r/PrintConfig.cpp:3409 +#: src/slic3r/GUI/Plater.cpp:1411 src/libslic3r/PrintConfig.cpp:1067 +#: src/libslic3r/PrintConfig.cpp:3407 src/libslic3r/PrintConfig.cpp:3408 msgid "Cost" msgstr "Kosten" @@ -6955,7 +6977,7 @@ msgstr "Wilt u de wijzigingen opslaan naar \"%1%\"?" #: src/slic3r/GUI/Plater.cpp:1730 src/slic3r/GUI/Preferences.cpp:222 msgid "Ask for unsaved changes in project" -msgstr "" +msgstr "Vraag naar niet-opgeslagen wijzigingen in dit project" #: src/slic3r/GUI/Plater.cpp:1733 msgid "" @@ -6963,6 +6985,9 @@ msgid "" "- Closing PrusaSlicer,\n" "- Loading or creating a new project" msgstr "" +"U wordt hier niet opnieuw over gevraagd bij:\n" +"- het sluiten van PrusaSlicer;\n" +"- het laden of openen van een nieuw project" #: src/slic3r/GUI/Plater.cpp:2198 #, c-format, boost-format @@ -6978,7 +7003,7 @@ msgstr "" msgid "Ejecting of device %s(%s) has failed." msgstr "Uitwerpen van apparat %s(%s) mislukt." -#: src/slic3r/GUI/Plater.cpp:2222 src/slic3r/GUI/Plater.cpp:5108 +#: src/slic3r/GUI/Plater.cpp:2222 src/slic3r/GUI/Plater.cpp:5095 msgid "New Project" msgstr "Nieuw project" @@ -6986,7 +7011,7 @@ msgstr "Nieuw project" msgid "Expand sidebar" msgstr "Zijbalk uitklappen" -#: src/slic3r/GUI/Plater.cpp:2518 +#: src/slic3r/GUI/Plater.cpp:2507 msgid "" "The preset below was temporarily installed on the active instance of " "PrusaSlicer" @@ -7000,12 +7025,12 @@ msgstr[1] "" "De vorige presets zijn tijdelijk geïnstalleerd op de actieve instantie van " "PrusaSlicer" -#: src/slic3r/GUI/Plater.cpp:2548 +#: src/slic3r/GUI/Plater.cpp:2537 #, boost-format msgid "Failed loading file \"%1%\" due to an invalid configuration." msgstr "Laden van bestand \"%1%\" mislukt dankzij een ongeldige configuratie." -#: src/slic3r/GUI/Plater.cpp:2568 +#: src/slic3r/GUI/Plater.cpp:2557 #, c-format, boost-format msgid "" "Object size from file %s appears to be zero.\n" @@ -7020,11 +7045,11 @@ msgstr[1] "" "Objectengrootte van bestand %s blijken nul te zijn.\n" "Het object is verwijderd van het model" -#: src/slic3r/GUI/Plater.cpp:2572 +#: src/slic3r/GUI/Plater.cpp:2561 msgid "The size of the object is zero" msgstr "De afmetingen van het object zijn nul" -#: src/slic3r/GUI/Plater.cpp:2585 +#: src/slic3r/GUI/Plater.cpp:2574 #, c-format, boost-format msgid "" "The dimensions of the object from file %s seem to be defined in meters.\n" @@ -7043,15 +7068,15 @@ msgstr[1] "" "De gebruikte eenheid van PrusaSlicer is millimeters. Wilt u de afmetingen " "van het object verschalen?" -#: src/slic3r/GUI/Plater.cpp:2589 src/slic3r/GUI/Plater.cpp:2611 +#: src/slic3r/GUI/Plater.cpp:2578 src/slic3r/GUI/Plater.cpp:2600 msgid "The object is too small" msgstr "Het object is te klein" -#: src/slic3r/GUI/Plater.cpp:2590 src/slic3r/GUI/Plater.cpp:2612 +#: src/slic3r/GUI/Plater.cpp:2579 src/slic3r/GUI/Plater.cpp:2601 msgid "Apply to all the remaining small objects being loaded." msgstr "Pas toe op alle resterende kleine objecten die worden geladen." -#: src/slic3r/GUI/Plater.cpp:2607 +#: src/slic3r/GUI/Plater.cpp:2596 #, c-format, boost-format msgid "" "The dimensions of the object from file %s seem to be defined in inches.\n" @@ -7070,7 +7095,7 @@ msgstr[1] "" "De gebruikte eenheid van PrusaSlicer is millimeters. Wilt u de afmetingen " "van het object verschalen?" -#: src/slic3r/GUI/Plater.cpp:2625 +#: src/slic3r/GUI/Plater.cpp:2614 msgid "" "This file contains several objects positioned at multiple heights.\n" "Instead of considering them as multiple objects, should \n" @@ -7081,11 +7106,11 @@ msgstr "" "Moet het bestand worden geladen als één object met meerdere onderdelen\n" "in plaats van deze te beschouwen als meerdere objecten?" -#: src/slic3r/GUI/Plater.cpp:2628 src/slic3r/GUI/Plater.cpp:2683 +#: src/slic3r/GUI/Plater.cpp:2617 src/slic3r/GUI/Plater.cpp:2672 msgid "Multi-part object detected" msgstr "Meerdelig object gedetecteerd" -#: src/slic3r/GUI/Plater.cpp:2636 +#: src/slic3r/GUI/Plater.cpp:2625 msgid "" "This file cannot be loaded in a simple mode. Do you want to switch to an " "advanced mode?" @@ -7093,11 +7118,11 @@ msgstr "" "Dit bestand kan niet geladen worden in eenvoudige modus. Wilt u overstappen " "op geavanceerde modus?" -#: src/slic3r/GUI/Plater.cpp:2637 +#: src/slic3r/GUI/Plater.cpp:2626 msgid "Detected advanced data" msgstr "Geavanceerde data gedetecteerd" -#: src/slic3r/GUI/Plater.cpp:2657 +#: src/slic3r/GUI/Plater.cpp:2646 #, c-format, boost-format msgid "" "You can't to add the object(s) from %s because of one or some of them " @@ -7106,7 +7131,7 @@ msgstr "" "U kan geen objecten toevoegen van %s, omdat sommige daarvan meerdelig kunnen " "zijn" -#: src/slic3r/GUI/Plater.cpp:2680 +#: src/slic3r/GUI/Plater.cpp:2669 msgid "" "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" @@ -7116,7 +7141,7 @@ msgstr "" "Moeten deze objecten beschouwd worden als één object\n" "met meerdere onderdelen, of als meerdere objecten?" -#: src/slic3r/GUI/Plater.cpp:2799 +#: src/slic3r/GUI/Plater.cpp:2788 msgid "" "Your object appears to be too large, so it was automatically scaled down to " "fit your print bed." @@ -7124,39 +7149,39 @@ msgstr "" "Het object is te groot. Daarom is het automatisch verschaald tot de grootte " "van het printbed." -#: src/slic3r/GUI/Plater.cpp:2800 +#: src/slic3r/GUI/Plater.cpp:2789 msgid "Object too large?" msgstr "Object te groot?" -#: src/slic3r/GUI/Plater.cpp:2878 +#: src/slic3r/GUI/Plater.cpp:2867 msgid "Export STL file:" -msgstr "Exporteer STL-bestand:" +msgstr "Exporteer .STL-bestand:" -#: src/slic3r/GUI/Plater.cpp:2885 +#: src/slic3r/GUI/Plater.cpp:2874 msgid "Export AMF file:" -msgstr "Exporteer AMF-bestand:" +msgstr "Exporteer .AMF-bestand:" -#: src/slic3r/GUI/Plater.cpp:2891 +#: src/slic3r/GUI/Plater.cpp:2880 msgid "Save file as:" msgstr "Bestand opslaan als:" -#: src/slic3r/GUI/Plater.cpp:2897 +#: src/slic3r/GUI/Plater.cpp:2886 msgid "Export OBJ file:" -msgstr "Exporteer OBJ-bestand:" +msgstr "Exporteer .OBJ-bestand:" -#: src/slic3r/GUI/Plater.cpp:2995 +#: src/slic3r/GUI/Plater.cpp:2984 msgid "Delete Object" msgstr "Verwijder object" -#: src/slic3r/GUI/Plater.cpp:3007 +#: src/slic3r/GUI/Plater.cpp:2996 msgid "Delete All Objects" msgstr "Verwijder alle objecten" -#: src/slic3r/GUI/Plater.cpp:3035 +#: src/slic3r/GUI/Plater.cpp:3024 msgid "Reset Project" msgstr "Reset project" -#: src/slic3r/GUI/Plater.cpp:3118 +#: src/slic3r/GUI/Plater.cpp:3107 msgid "" "The selected object couldn't be split because it contains only one solid " "part." @@ -7164,103 +7189,103 @@ msgstr "" "Het geselecteerde object kan niet gesplitst worden omdat het maar één " "onderdeel bevat." -#: src/slic3r/GUI/Plater.cpp:3125 +#: src/slic3r/GUI/Plater.cpp:3114 msgid "All non-solid parts (modifiers) were deleted" msgstr "Alle niet-solide onderdelen (bewerkers) zijn verwijderd" -#: src/slic3r/GUI/Plater.cpp:3127 +#: src/slic3r/GUI/Plater.cpp:3116 msgid "Split to Objects" msgstr "Splits op naar objecten" -#: src/slic3r/GUI/Plater.cpp:3179 +#: src/slic3r/GUI/Plater.cpp:3166 msgid "" "An object has custom support enforcers which will not be used because " "supports are disabled." msgstr "" -"Een object heeft aangepaste supportforcering die niet gebruikt worden omdat " +"Een object heeft custom supportforcering die niet gebruikt worden omdat " "supports uit staan." -#: src/slic3r/GUI/Plater.cpp:3181 +#: src/slic3r/GUI/Plater.cpp:3168 msgid "Enable supports for enforcers only" msgstr "Sta supports voor forceringen alleen toe" -#: src/slic3r/GUI/Plater.cpp:3310 src/slic3r/GUI/Plater.cpp:4176 +#: src/slic3r/GUI/Plater.cpp:3297 src/slic3r/GUI/Plater.cpp:4163 msgid "Invalid data" msgstr "Ongeldige data" -#: src/slic3r/GUI/Plater.cpp:3380 +#: src/slic3r/GUI/Plater.cpp:3367 msgid "Another export job is currently running." msgstr "Een andere export loopt op dit moment." -#: src/slic3r/GUI/Plater.cpp:3466 +#: src/slic3r/GUI/Plater.cpp:3453 msgid "Replace from:" msgstr "Vervangen door:" -#: src/slic3r/GUI/Plater.cpp:3484 +#: src/slic3r/GUI/Plater.cpp:3471 msgid "Unable to replace with more than one volume" msgstr "Niet mogelijk om te vervangen met meer dan één volume" -#: src/slic3r/GUI/Plater.cpp:3484 src/slic3r/GUI/Plater.cpp:3563 +#: src/slic3r/GUI/Plater.cpp:3471 src/slic3r/GUI/Plater.cpp:3550 msgid "Error during replace" msgstr "Fout tijdens vervangen" -#: src/slic3r/GUI/Plater.cpp:3555 +#: src/slic3r/GUI/Plater.cpp:3542 msgid "Select the new file" msgstr "Selecteer het nieuwe bestand" -#: src/slic3r/GUI/Plater.cpp:3563 +#: src/slic3r/GUI/Plater.cpp:3550 msgid "File for the replace wasn't selected" msgstr "Vervangbestand is niet geselecteerd" -#: src/slic3r/GUI/Plater.cpp:3654 +#: src/slic3r/GUI/Plater.cpp:3641 msgid "Please select the file to reload" msgstr "Selecteer het bestand om te herladen" -#: src/slic3r/GUI/Plater.cpp:3685 src/slic3r/GUI/Plater.cpp:5243 +#: src/slic3r/GUI/Plater.cpp:3672 src/slic3r/GUI/Plater.cpp:5230 msgid "The selected file" msgstr "Het geselecteerde bestand" -#: src/slic3r/GUI/Plater.cpp:3686 +#: src/slic3r/GUI/Plater.cpp:3673 msgid "differs from the original file" msgstr "verschilt ten opzichte van het originele bestand" -#: src/slic3r/GUI/Plater.cpp:3686 +#: src/slic3r/GUI/Plater.cpp:3673 msgid "Do you want to replace it" msgstr "Wilt u het vervangen" -#: src/slic3r/GUI/Plater.cpp:3703 src/slic3r/GUI/Plater.cpp:3709 +#: src/slic3r/GUI/Plater.cpp:3690 src/slic3r/GUI/Plater.cpp:3696 msgid "Reload from:" msgstr "Herladen van:" -#: src/slic3r/GUI/Plater.cpp:3812 +#: src/slic3r/GUI/Plater.cpp:3799 msgid "Unable to reload:" msgstr "Niet in staat om te herladen:" -#: src/slic3r/GUI/Plater.cpp:3817 +#: src/slic3r/GUI/Plater.cpp:3804 msgid "Error during reload" msgstr "Fout tijdens herladen" -#: src/slic3r/GUI/Plater.cpp:3835 +#: src/slic3r/GUI/Plater.cpp:3822 msgid "Reload all from disk" msgstr "Herlaad alles van schijf" -#: src/slic3r/GUI/Plater.cpp:4130 +#: src/slic3r/GUI/Plater.cpp:4117 msgid "There are active warnings concerning sliced models:" msgstr "Er zijn actieve waarschuwingen wat betreft de slice:" -#: src/slic3r/GUI/Plater.cpp:4141 +#: src/slic3r/GUI/Plater.cpp:4128 msgid "generated warnings" msgstr "gegeven waarschuwingen" -#: src/slic3r/GUI/Plater.cpp:4472 +#: src/slic3r/GUI/Plater.cpp:4459 msgid "3D editor view" msgstr "3D-bewerkingsweergave" -#: src/slic3r/GUI/Plater.cpp:4893 +#: src/slic3r/GUI/Plater.cpp:4880 msgid "Undo / Redo is processing" msgstr "Ongedaan maken / opnieuw doen wordt verwerkt" -#: src/slic3r/GUI/Plater.cpp:4895 +#: src/slic3r/GUI/Plater.cpp:4882 #, boost-format msgid "" "Switching the printer technology from %1% to %2%.\n" @@ -7271,21 +7296,21 @@ msgstr "" "Sommige %1% presets zijn aangepast. Deze gaan verloren bij het wijzigen van " "het soort printer." -#: src/slic3r/GUI/Plater.cpp:5092 +#: src/slic3r/GUI/Plater.cpp:5079 msgid "Creating a new project while the current project is modified." msgstr "Een nieuw project aanmaken terwijl het huidige project is aangepast." -#: src/slic3r/GUI/Plater.cpp:5095 +#: src/slic3r/GUI/Plater.cpp:5082 msgid "Creating a new project while some presets are modified." msgstr "Een nieuw project aanmaken terwijl sommige presets zijn aangepast." -#: src/slic3r/GUI/Plater.cpp:5096 +#: src/slic3r/GUI/Plater.cpp:5083 msgid "You can keep presets modifications to the new project or discard them" msgstr "" "U kunt de aanpassingen in de preset behouden bij het nieuwe project, of deze " "verwijderen" -#: src/slic3r/GUI/Plater.cpp:5097 +#: src/slic3r/GUI/Plater.cpp:5084 msgid "" "You can keep presets modifications to the new project, discard them or save " "changes as new presets.\n" @@ -7296,126 +7321,126 @@ msgstr "" "Let op dat als de wijzigingen worden opgeslagen, deze niet bewaard worden in " "het nieuwe project" -#: src/slic3r/GUI/Plater.cpp:5103 +#: src/slic3r/GUI/Plater.cpp:5090 msgid "Creating a new project" msgstr "Maak een nieuw project aan" -#: src/slic3r/GUI/Plater.cpp:5137 +#: src/slic3r/GUI/Plater.cpp:5124 msgid "Load Project" msgstr "Laad project" -#: src/slic3r/GUI/Plater.cpp:5167 src/slic3r/GUI/Plater.cpp:5432 +#: src/slic3r/GUI/Plater.cpp:5154 src/slic3r/GUI/Plater.cpp:5419 msgid "Import Object" msgstr "Importeer object" -#: src/slic3r/GUI/Plater.cpp:5171 +#: src/slic3r/GUI/Plater.cpp:5158 msgid "Import Objects" msgstr "Importeer objecten" -#: src/slic3r/GUI/Plater.cpp:5243 +#: src/slic3r/GUI/Plater.cpp:5230 msgid "does not contain valid gcode." msgstr "bevat geen geldige G-code." -#: src/slic3r/GUI/Plater.cpp:5244 +#: src/slic3r/GUI/Plater.cpp:5231 msgid "Error while loading .gcode file" -msgstr "Probleem bij het laden van het gcode-bestand" +msgstr "Probleem bij het laden van het .gcode-bestand" -#: src/slic3r/GUI/Plater.cpp:5297 +#: src/slic3r/GUI/Plater.cpp:5284 #, c-format, boost-format msgid "%s - Drop project file" msgstr "%s - Plaats projectbestand" -#: src/slic3r/GUI/Plater.cpp:5304 +#: src/slic3r/GUI/Plater.cpp:5291 msgid "Open as project" msgstr "Open als project" -#: src/slic3r/GUI/Plater.cpp:5305 +#: src/slic3r/GUI/Plater.cpp:5292 msgid "Import geometry only" msgstr "Importeer alleen het model" -#: src/slic3r/GUI/Plater.cpp:5306 +#: src/slic3r/GUI/Plater.cpp:5293 msgid "Import config only" msgstr "Importeer alleen de configuratie" -#: src/slic3r/GUI/Plater.cpp:5309 +#: src/slic3r/GUI/Plater.cpp:5296 msgid "Select an action to apply to the file" msgstr "Selecteer een commando om toe te passen op het bestand" -#: src/slic3r/GUI/Plater.cpp:5314 +#: src/slic3r/GUI/Plater.cpp:5301 msgid "Action" -msgstr "Commando" +msgstr "Actie" -#: src/slic3r/GUI/Plater.cpp:5330 +#: src/slic3r/GUI/Plater.cpp:5317 msgid "Don't show again" msgstr "Laat niet meer zien" -#: src/slic3r/GUI/Plater.cpp:5371 +#: src/slic3r/GUI/Plater.cpp:5358 msgid "You can open only one .gcode file at a time." -msgstr "Je kunt maar één gcode-bestand tegelijk openen." +msgstr "Je kunt maar één .gcode-bestand tegelijk openen." -#: src/slic3r/GUI/Plater.cpp:5372 +#: src/slic3r/GUI/Plater.cpp:5359 msgid "Drag and drop G-code file" msgstr "Versleep en plaats G-code-bestand" -#: src/slic3r/GUI/Plater.cpp:5454 +#: src/slic3r/GUI/Plater.cpp:5441 msgid "Load File" msgstr "Laad bestand" -#: src/slic3r/GUI/Plater.cpp:5459 +#: src/slic3r/GUI/Plater.cpp:5446 msgid "Load Files" msgstr "Laad bestanden" -#: src/slic3r/GUI/Plater.cpp:5509 +#: src/slic3r/GUI/Plater.cpp:5496 msgid "All objects will be removed, continue?" msgstr "Alle objecten worden verwijderd. Doorgaan?" -#: src/slic3r/GUI/Plater.cpp:5520 +#: src/slic3r/GUI/Plater.cpp:5507 msgid "Delete Selected Objects" msgstr "Verwijder geselecteerde objecten" -#: src/slic3r/GUI/Plater.cpp:5529 +#: src/slic3r/GUI/Plater.cpp:5516 msgid "Increase Instances" msgstr "Verhoog aantal instanties" -#: src/slic3r/GUI/Plater.cpp:5563 +#: src/slic3r/GUI/Plater.cpp:5550 msgid "Decrease Instances" msgstr "Verlaag aantal instanties" -#: src/slic3r/GUI/Plater.cpp:5614 +#: src/slic3r/GUI/Plater.cpp:5601 msgid "Enter the number of copies:" msgstr "Voer het aantal kopieën in:" -#: src/slic3r/GUI/Plater.cpp:5615 +#: src/slic3r/GUI/Plater.cpp:5602 msgid "Copies of the selected object" msgstr "Kopieën van het geselecteerde object" -#: src/slic3r/GUI/Plater.cpp:5619 +#: src/slic3r/GUI/Plater.cpp:5606 #, c-format, boost-format msgid "Set numbers of copies to %d" msgstr "Stel aantal kopieën in voor %d" -#: src/slic3r/GUI/Plater.cpp:5697 +#: src/slic3r/GUI/Plater.cpp:5684 msgid "Cut by Plane" msgstr "Snij met behulp van vlak" -#: src/slic3r/GUI/Plater.cpp:5757 +#: src/slic3r/GUI/Plater.cpp:5744 msgid "Save G-code file as:" -msgstr "G-code-bestand opslaan als:" +msgstr ".gcode-bestand opslaan als:" -#: src/slic3r/GUI/Plater.cpp:5757 +#: src/slic3r/GUI/Plater.cpp:5744 msgid "Save SL1 / SL1S file as:" msgstr "SL1 / SL1S bestand opslaan als:" -#: src/slic3r/GUI/Plater.cpp:5766 +#: src/slic3r/GUI/Plater.cpp:5753 msgid "The provided file name is not valid." msgstr "De gegeven naam is niet geldig." -#: src/slic3r/GUI/Plater.cpp:5767 +#: src/slic3r/GUI/Plater.cpp:5754 msgid "The following characters are not allowed by a FAT file system:" msgstr "" "De volgende karakters worden niet toegestaan in een FAT-bestandssysteem:" -#: src/slic3r/GUI/Plater.cpp:5957 +#: src/slic3r/GUI/Plater.cpp:5944 msgid "" "The plater is empty.\n" "Do you want to save the project?" @@ -7423,15 +7448,15 @@ msgstr "" "Het bed is leeg.\n" "Wilt u toch het project opslaan?" -#: src/slic3r/GUI/Plater.cpp:5957 +#: src/slic3r/GUI/Plater.cpp:5944 msgid "Save project" msgstr "Project opslaan" -#: src/slic3r/GUI/Plater.cpp:6543 +#: src/slic3r/GUI/Plater.cpp:6530 msgid "Export" msgstr "Exporteer" -#: src/slic3r/GUI/Plater.cpp:6575 +#: src/slic3r/GUI/Plater.cpp:6562 msgid "" "Custom supports, seams and multimaterial painting were removed after " "repairing the mesh." @@ -7439,7 +7464,7 @@ msgstr "" "Aangepaste supports, naden en multi-material schilderingen zijn verwijderd " "bij het repareren van de mesh." -#: src/slic3r/GUI/Plater.cpp:6689 +#: src/slic3r/GUI/Plater.cpp:6676 msgid "Paste From Clipboard" msgstr "Plak van klembord" @@ -7488,7 +7513,7 @@ msgstr "" #: src/slic3r/GUI/Preferences.cpp:148 msgid "Export sources full pathnames to 3mf and amf" -msgstr "Exporteer de volledige padnamen naar 3MF- en AMF-bestanden" +msgstr "Exporteer de volledige padnamen naar .3MF- en .AMF-bestanden" #: src/slic3r/GUI/Preferences.cpp:150 msgid "" @@ -7501,13 +7526,13 @@ msgstr "" #: src/slic3r/GUI/Preferences.cpp:159 msgid "If enabled, sets PrusaSlicer as default application to open .3mf files." msgstr "" -"Als dit aanstaat wordt PrusaSlicer als standaardprogramma ingesteld om 3MF-" +"Als dit aanstaat wordt PrusaSlicer als standaardprogramma ingesteld om .3MF-" "bestanden te openen." #: src/slic3r/GUI/Preferences.cpp:166 msgid "If enabled, sets PrusaSlicer as default application to open .stl files." msgstr "" -"Als dit aanstaat wordt PrusaSlicer als standaardprogramma ingesteld om STL-" +"Als dit aanstaat wordt PrusaSlicer als standaardprogramma ingesteld om .STL-" "bestanden te openen." #: src/slic3r/GUI/Preferences.cpp:177 @@ -7536,7 +7561,7 @@ msgstr "" #: src/slic3r/GUI/Preferences.cpp:190 msgid "Show incompatible print and filament presets" -msgstr "Toon niet geschikte print- en filamentpresets" +msgstr "Toon niet compatibele print- en filamentpresets" #: src/slic3r/GUI/Preferences.cpp:192 msgid "" @@ -7544,7 +7569,7 @@ msgid "" "even if they are marked as incompatible with the active printer" msgstr "" "Als dit aan staat worden de print- en filamentpresets getoond in de presets-" -"editor, zelfs als ze als niet geschikt voor de actieve printer zijn " +"editor, zelfs als ze als niet compatibel met de actieve printer zijn " "gemarkeerd" #: src/slic3r/GUI/Preferences.cpp:200 @@ -7591,6 +7616,9 @@ msgid "" "- Closing PrusaSlicer,\n" "- Loading or creating a new project" msgstr "" +"Vraag altijd naar niet-opgeslagen wijzigingen in het project als:\n" +"- PrusaSlicer afgesloten wordt,\n" +"- Een nieuw project wordt geladen of aangemaakt" #: src/slic3r/GUI/Preferences.cpp:233 #: src/slic3r/GUI/UnsavedChangesDialog.cpp:897 @@ -7598,6 +7626,8 @@ msgid "" "Ask to save unsaved changes in presets when closing the application or when " "loading a new project" msgstr "" +"Vraag om niet-opgeslagen wijziging op te slaan in presets bij het sluiten " +"van het programma of het laden van een nieuw project" #: src/slic3r/GUI/Preferences.cpp:235 msgid "" @@ -7605,26 +7635,37 @@ msgid "" "- Closing PrusaSlicer while some presets are modified,\n" "- Loading a new project while some presets are modified" msgstr "" +"Vraag altijd naar niet-opgeslagen wijzigingen in presets als:\n" +"- PrusaSlicer afgesloten wordt terwijl presets zijn gewijzigd,\n" +"- Een nieuw project wordt geladen terwijl sommige presets zijn gewijzigd" #: src/slic3r/GUI/Preferences.cpp:242 #: src/slic3r/GUI/UnsavedChangesDialog.cpp:896 msgid "Ask for unsaved changes in presets when selecting new preset" msgstr "" +"Vraag naar niet-opgeslagen wijzigingen in presets bij het selecteren van een " +"nieuwe preset" #: src/slic3r/GUI/Preferences.cpp:244 msgid "" "Always ask for unsaved changes in presets when selecting new preset or " "resetting a preset" msgstr "" +"Vraag altijd naar niet-opgeslagen wijzigingen in presets bij het resetten " +"van een preset of het selecteren van een nieuwe preset" #: src/slic3r/GUI/Preferences.cpp:249 #: src/slic3r/GUI/UnsavedChangesDialog.cpp:895 msgid "Ask for unsaved changes in presets when creating new project" msgstr "" +"Vraag naar niet-opgeslagen wijzigingen in presets bij het aanmaken van een " +"nieuw project" #: src/slic3r/GUI/Preferences.cpp:251 msgid "Always ask for unsaved changes in presets when creating new project" msgstr "" +"Vraag altijd naar niet-opgeslagen wijzigingen in presets bij het aanmaken " +"van een new project" #: src/slic3r/GUI/Preferences.cpp:258 msgid "Associate .gcode files to PrusaSlicer G-code Viewer" @@ -7640,16 +7681,16 @@ msgstr "" #: src/slic3r/GUI/Preferences.cpp:268 msgid "Use Retina resolution for the 3D scene" -msgstr "Gebruik hoge resolutie voor de 3D-scène" +msgstr "Gebruik hoge resolutie voor de modelweergave" #: src/slic3r/GUI/Preferences.cpp:270 msgid "" "If enabled, the 3D scene will be rendered in Retina resolution. If you are " "experiencing 3D performance problems, disabling this option may help." msgstr "" -"Als dit is ingeschakeld zal de 3D-scène worden gerenderd in hoge resolutie. " -"Als u problemen ondervindt met de prestaties kan het uitschakelen van deze " -"optie mogelijk helpen." +"Als dit is ingeschakeld zal de modelweergave worden gerenderd in hoge " +"resolutie. Als u problemen ondervindt met de prestaties kan het uitschakelen " +"van deze optie mogelijk helpen." #: src/slic3r/GUI/Preferences.cpp:280 src/slic3r/GUI/Preferences.cpp:282 msgid "Show splash screen" @@ -7658,6 +7699,8 @@ msgstr "Toon startscherm" #: src/slic3r/GUI/Preferences.cpp:289 msgid "If enabled, PrusaSlicer will be open at the position it was closed" msgstr "" +"Als dit is ingeschakeld zal PrusaSlicer openen op de positie waarop die is " +"afgesloten" #: src/slic3r/GUI/Preferences.cpp:295 msgid "Clear Undo / Redo stack on new project" @@ -7730,8 +7773,8 @@ msgid "" "in preview, apply to the whole gcode." msgstr "" "Sta toe om wijzigingen van de opeenvolgende schuif in de voorbeeldweergave " -"alleen toe te passen op de toplaag. Als dit uitstaat worden wijzigingen " -"toegepast op de hele G-code." +"alleen toe te passen op de toplaag. Als dit is uitgeschakeld worden " +"wijzigingen toegepast op de hele G-code." #: src/slic3r/GUI/Preferences.cpp:375 msgid "Show sidebar collapse/expand button" @@ -7743,11 +7786,12 @@ msgid "" "right corner of the 3D Scene" msgstr "" "Als dit is ingeschakeld zal de knop om de zijbalk in te klappen getoond " -"worden in de rechterbovenhoek van de 3D-bewerkingsweergave" +"worden in de rechterbovenhoek van de modelweergave" #: src/slic3r/GUI/Preferences.cpp:384 msgid "If enabled, PrusaSlicer will not open hyperlinks in your browser." msgstr "" +"Als dit is ingeschakeld zal PrusaSlicer hyperlinks niet openen in uw browser." #: src/slic3r/GUI/Preferences.cpp:391 msgid "Use colors for axes values in Manipulation panel" @@ -7758,8 +7802,8 @@ msgid "" "If enabled, the axes names and axes values will be colorized according to " "the axes colors. If disabled, old UI will be used." msgstr "" -"Als dit aanstaat worden namen en waarden van assen gekleurd volgens de kleur " -"van de as. Als dit uitstaat wordt de oude interface gebruikt." +"Als dit is ingeschakeld worden namen en waarden van assen gekleurd volgens " +"de kleur van de as. Als dit uitstaat wordt de oude interface gebruikt." #: src/slic3r/GUI/Preferences.cpp:399 msgid "Order object volumes by types" @@ -7772,8 +7816,8 @@ msgid "" "Enforcer. If disabled, you can reorder Model Parts, Negative Volumes and " "Modifiers. But one of the model parts have to be on the first place." msgstr "" -"Als dit aanstaat worden volumes altijd gesorteerd binnen een object. De " -"juiste volgorde is Model - Negatieve volumes - Modificators - " +"Als dit is ingeschakeld worden volumes altijd gesorteerd binnen een object. " +"De juiste volgorde is Model - Negatieve volumes - Modificators - " "Supportblokkering - Supportforcering. Als dit uitstaat kan u de onderdelen " "zelf schikken. Een van de modellen moet op de eerste plaats staan." @@ -7786,8 +7830,8 @@ msgid "" "If enabled, Settings Tabs will be placed as menu items. If disabled, old UI " "will be used." msgstr "" -"Als dit aanstaat worden instellingentabs geplaatst als menu-onderdelen. Als " -"dit uitstaat wordt de oude interface gebruikt." +"Als dit is ingeschakeld worden instellingentabs geplaatst als menu-" +"onderdelen. Als dit uitstaat wordt de oude interface gebruikt." #: src/slic3r/GUI/Preferences.cpp:419 msgid "Show \"Tip of the day\" notification after start" @@ -7795,7 +7839,7 @@ msgstr "Toon \"Tip van de dag\" melding bij het starten" #: src/slic3r/GUI/Preferences.cpp:421 msgid "If enabled, useful hints are displayed at startup." -msgstr "Als dit aanstaat worden handige tips getoond bij het starten." +msgstr "Als dit is ingeschakeld worden handige tips getoond bij het starten." #: src/slic3r/GUI/Preferences.cpp:427 msgid "Notify about new releases" @@ -7833,7 +7877,8 @@ msgstr "Gebruik omgevingskaart" #: src/slic3r/GUI/Preferences.cpp:478 msgid "If enabled, renders object using the environment map." -msgstr "Als dit aanstaat worden objecten gerenderd met de omgevingskaart." +msgstr "" +"Als dit is ingeschakeld worden objecten gerenderd met de omgevingskaart." #: src/slic3r/GUI/Preferences.cpp:491 msgid "Dark mode (experimental)" @@ -7847,8 +7892,8 @@ msgstr "Gebruik donkere modus" msgid "" "If enabled, UI will use Dark mode colors. If disabled, old UI will be used." msgstr "" -"Als dit aan staat worden kleuren van de donkere modus gebruikt. De oude " -"kleuren worden gebruikt als dit uit staat." +"Als dit is ingeschakeld worden kleuren van de donkere modus gebruikt. De " +"oude kleuren worden gebruikt als dit uit staat." #: src/slic3r/GUI/Preferences.cpp:507 msgid "Use system menu for application" @@ -7860,7 +7905,7 @@ msgid "" "but on some combination of display scales it can looks ugly. If disabled, " "old UI will be used." msgstr "" -"Als dit aan staat worden de standaard Windows-menukleuren gebruikt,\n" +"Als dit is ingeschakeld worden de standaard Windows-menukleuren gebruikt,\n" "maar op sommige combinaties van displaygroottes kan die lelijk lijken. Als " "dit uitstaat wordt de oude interface gebruikt." @@ -7923,7 +7968,7 @@ msgstr "Presets van de gebruiker" #: src/slic3r/GUI/PresetComboBoxes.cpp:302 msgid "Incompatible presets" -msgstr "Ongeschikte presets" +msgstr "Niet compatibele presets" #: src/slic3r/GUI/PresetComboBoxes.cpp:337 #, boost-format @@ -8268,11 +8313,11 @@ msgstr "Fout: geen ramming" #: src/slic3r/GUI/RammingChart.cpp:90 src/slic3r/GUI/WipeTowerDialog.cpp:114 #: src/libslic3r/PrintConfig.cpp:951 src/libslic3r/PrintConfig.cpp:995 -#: src/libslic3r/PrintConfig.cpp:1010 src/libslic3r/PrintConfig.cpp:3266 -#: src/libslic3r/PrintConfig.cpp:3275 src/libslic3r/PrintConfig.cpp:3284 -#: src/libslic3r/PrintConfig.cpp:3425 src/libslic3r/PrintConfig.cpp:3433 -#: src/libslic3r/PrintConfig.cpp:3441 src/libslic3r/PrintConfig.cpp:3448 -#: src/libslic3r/PrintConfig.cpp:3456 src/libslic3r/PrintConfig.cpp:3464 +#: src/libslic3r/PrintConfig.cpp:1010 src/libslic3r/PrintConfig.cpp:3265 +#: src/libslic3r/PrintConfig.cpp:3274 src/libslic3r/PrintConfig.cpp:3283 +#: src/libslic3r/PrintConfig.cpp:3424 src/libslic3r/PrintConfig.cpp:3432 +#: src/libslic3r/PrintConfig.cpp:3440 src/libslic3r/PrintConfig.cpp:3447 +#: src/libslic3r/PrintConfig.cpp:3455 src/libslic3r/PrintConfig.cpp:3463 msgid "s" msgstr "s" @@ -8281,7 +8326,7 @@ msgid "Volumetric speed" msgstr "Volumetrische snelheid" #: src/slic3r/GUI/RammingChart.cpp:95 src/libslic3r/PrintConfig.cpp:908 -#: src/libslic3r/PrintConfig.cpp:1801 +#: src/libslic3r/PrintConfig.cpp:1800 msgid "mm³/s" msgstr "mm³/s" @@ -8322,7 +8367,7 @@ msgid "" "Preset with name \"%1%\" already exists and is incompatible with selected " "printer." msgstr "" -"Preset met de naam \"%1%\" bestaat al en is incompatibel met de " +"Preset met de naam \"%1%\" bestaat al en is niet compatibel met de " "geselecteerde printer." #: src/slic3r/GUI/SavePresetDialog.cpp:137 @@ -8460,7 +8505,7 @@ msgid "" msgstr "" "Als we uw hardware, besturingssysteem, etc. kennen, zal dit ons erg helpen " "bij de ontwikkeling en prioritering, omdat we dan beter weten waar de focus " -"ligt, en efficiënter de tijd besteden op onderdelen die veel tijd kosten." +"ligt, en efficiënter de tijd besteden op delen die veel tijd kosten." #: src/slic3r/GUI/SendSystemInfoDialog.cpp:588 msgid "Is it safe?" @@ -8530,19 +8575,19 @@ msgstr "Kopieer van klembord" #: src/slic3r/GUI/Tab.cpp:114 src/libslic3r/PrintConfig.cpp:564 msgid "Compatible printers" -msgstr "Geschikte printers" +msgstr "Compatibele printers" #: src/slic3r/GUI/Tab.cpp:115 msgid "Select the printers this profile is compatible with." -msgstr "Selecteer de printers die geschikt voor dit profiel zijn." +msgstr "Selecteer de printers die compatibel zijn met dit profiel." #: src/slic3r/GUI/Tab.cpp:120 src/libslic3r/PrintConfig.cpp:579 msgid "Compatible print profiles" -msgstr "Geschikte printprofielen" +msgstr "Compatibele printprofielen" #: src/slic3r/GUI/Tab.cpp:121 msgid "Select the print profiles this profile is compatible with." -msgstr "Selecteer de printprofielen die geschikt voor dit profiel zijn." +msgstr "Selecteer de printprofielen die compatibel zijn met dit profiel." #: src/slic3r/GUI/Tab.cpp:216 msgid "Compare this preset with some another" @@ -8671,7 +8716,7 @@ msgstr "Verticale shells" msgid "Horizontal shells" msgstr "Horizontale shells" -#: src/slic3r/GUI/Tab.cpp:1466 src/libslic3r/PrintConfig.cpp:2386 +#: src/slic3r/GUI/Tab.cpp:1466 src/libslic3r/PrintConfig.cpp:2385 msgid "Solid layers" msgstr "Dichte lagen" @@ -8725,7 +8770,7 @@ msgstr "Automatische snelheid (geavanceerd)" #: src/slic3r/GUI/Tab.cpp:1615 msgid "Pressure equalizer (experimental)" -msgstr "" +msgstr "Drukverdeler (experimenteel)" #: src/slic3r/GUI/Tab.cpp:1619 msgid "Multiple Extruders" @@ -8753,7 +8798,7 @@ msgstr "Overige" #: src/slic3r/GUI/Tab.cpp:1674 msgid "Arachne perimeter generator" -msgstr "" +msgstr "Arachne-perimetergeneratie" #: src/slic3r/GUI/Tab.cpp:1682 src/slic3r/GUI/Tab.cpp:4731 msgid "Output options" @@ -8771,7 +8816,7 @@ msgstr "Extruderruimte" msgid "Output file" msgstr "Outputbestand" -#: src/slic3r/GUI/Tab.cpp:1709 src/libslic3r/PrintConfig.cpp:2011 +#: src/slic3r/GUI/Tab.cpp:1709 src/libslic3r/PrintConfig.cpp:2010 msgid "Post-processing scripts" msgstr "Scripts voor nabewerking" @@ -8839,8 +8884,8 @@ msgstr "Bed" msgid "Cooling" msgstr "Koeling" -#: src/slic3r/GUI/Tab.cpp:2001 src/libslic3r/PrintConfig.cpp:1913 -#: src/libslic3r/PrintConfig.cpp:2963 +#: src/slic3r/GUI/Tab.cpp:2001 src/libslic3r/PrintConfig.cpp:1912 +#: src/libslic3r/PrintConfig.cpp:2962 msgid "Enable" msgstr "Toestaan" @@ -8874,13 +8919,13 @@ msgstr "Ramming-instellingen" #: src/slic3r/GUI/Tab.cpp:2086 src/slic3r/GUI/Tab.cpp:2407 #: src/slic3r/GUI/Tab.cpp:4261 src/libslic3r/GCode.cpp:733 -#: src/libslic3r/PrintConfig.cpp:2469 +#: src/libslic3r/PrintConfig.cpp:2468 msgid "Custom G-code" msgstr "Custom G-code" #: src/slic3r/GUI/Tab.cpp:2087 src/slic3r/GUI/Tab.cpp:2408 -#: src/libslic3r/GCode.cpp:707 src/libslic3r/PrintConfig.cpp:2419 -#: src/libslic3r/PrintConfig.cpp:2434 +#: src/libslic3r/GCode.cpp:707 src/libslic3r/PrintConfig.cpp:2418 +#: src/libslic3r/PrintConfig.cpp:2433 msgid "Start G-code" msgstr "Start G-code" @@ -8906,7 +8951,7 @@ msgid "" "The Physical Printer profiles are being stored into PrusaSlicer/" "physical_printer directory." msgstr "" -"Let op dat alle parameters van deze groep zijn verplaatst naar de fysieke " +"Let op dat alle parameters van deze groep zijn verplaatst naar de fysieke-" "printerinstellingen (zie wijzigingslogboek).\n" "\n" "Een nieuw fysieke printerprofiel wordt aangemaakt door te klikken op het " @@ -8942,7 +8987,7 @@ msgstr "" "eerste extruder?" #: src/slic3r/GUI/Tab.cpp:2335 src/slic3r/GUI/Tab.cpp:2779 -#: src/libslic3r/PrintConfig.cpp:1877 +#: src/libslic3r/PrintConfig.cpp:1876 msgid "Nozzle diameter" msgstr "Nozzlediameter" @@ -8952,12 +8997,12 @@ msgid "Before layer change G-code" msgstr "G-code die komt vóór de laagwisseling" #: src/slic3r/GUI/Tab.cpp:2438 src/libslic3r/GCode.cpp:710 -#: src/libslic3r/PrintConfig.cpp:1603 +#: src/libslic3r/PrintConfig.cpp:1602 msgid "After layer change G-code" msgstr "G-code die komt na de laagwisseling" #: src/slic3r/GUI/Tab.cpp:2448 src/libslic3r/GCode.cpp:711 -#: src/libslic3r/PrintConfig.cpp:2846 +#: src/libslic3r/PrintConfig.cpp:2845 msgid "Tool change G-code" msgstr "Toolwisseling G-code" @@ -8970,7 +9015,7 @@ msgid "Color Change G-code" msgstr "Kleurwissel G-code" #: src/slic3r/GUI/Tab.cpp:2477 src/libslic3r/GCode.cpp:714 -#: src/libslic3r/PrintConfig.cpp:2460 +#: src/libslic3r/PrintConfig.cpp:2459 msgid "Pause Print G-code" msgstr "Pauzeer print G-code" @@ -8999,11 +9044,11 @@ msgid "Exposure" msgstr "Belichtingstijd" #: src/slic3r/GUI/Tab.cpp:2619 src/slic3r/GUI/Tab.cpp:2706 -#: src/libslic3r/PrintConfig.cpp:1632 src/libslic3r/PrintConfig.cpp:1667 -#: src/libslic3r/PrintConfig.cpp:1684 src/libslic3r/PrintConfig.cpp:1701 -#: src/libslic3r/PrintConfig.cpp:1717 src/libslic3r/PrintConfig.cpp:1727 -#: src/libslic3r/PrintConfig.cpp:1737 src/libslic3r/PrintConfig.cpp:1750 -#: src/libslic3r/PrintConfig.cpp:1760 +#: src/libslic3r/PrintConfig.cpp:1631 src/libslic3r/PrintConfig.cpp:1666 +#: src/libslic3r/PrintConfig.cpp:1683 src/libslic3r/PrintConfig.cpp:1700 +#: src/libslic3r/PrintConfig.cpp:1716 src/libslic3r/PrintConfig.cpp:1726 +#: src/libslic3r/PrintConfig.cpp:1736 src/libslic3r/PrintConfig.cpp:1749 +#: src/libslic3r/PrintConfig.cpp:1759 msgid "Machine limits" msgstr "Machinelimieten" @@ -9058,7 +9103,7 @@ msgstr "Positie (voor multi-extruderprinters)" #: src/slic3r/GUI/Tab.cpp:2812 msgid "Only lift Z" -msgstr "Beweeg alleen Z omhoog" +msgstr "Beweeg alleen omhoog" #: src/slic3r/GUI/Tab.cpp:2825 msgid "" @@ -9178,31 +9223,31 @@ msgstr "Stel in" #: src/slic3r/GUI/Tab.cpp:3952 msgid "Find" -msgstr "Zoeken" +msgstr "Zoek" #: src/slic3r/GUI/Tab.cpp:3953 msgid "Replace with" -msgstr "" +msgstr "Vervang met" #: src/slic3r/GUI/Tab.cpp:4042 msgid "Regular expression" -msgstr "" +msgstr "Reguliere uitdrukking" #: src/slic3r/GUI/Tab.cpp:4046 msgid "Case insensitive" -msgstr "" +msgstr "Hoofdletterongevoelig" #: src/slic3r/GUI/Tab.cpp:4050 msgid "Whole word" -msgstr "Alleen hele woorden" +msgstr "Geheel woord" #: src/slic3r/GUI/Tab.cpp:4054 msgid "Match single line" -msgstr "" +msgstr "Laat overeenkomen met enkele lijn" #: src/slic3r/GUI/Tab.cpp:4157 msgid "Are you sure you want to delete all substitutions?" -msgstr "" +msgstr "Weet je zeker dat je alle substituties wilt verwijderen?" #: src/slic3r/GUI/Tab.cpp:4289 msgid "" @@ -9402,11 +9447,11 @@ msgstr "" "\"%1%\" is uitgeschakeld omdat \"%2%\" aanstaat in \"%3%\"-categorie.\n" "Om \"%1%\" aan te zetten moet \"%2%\" uit staan" -#: src/slic3r/GUI/Tab.cpp:4774 src/libslic3r/PrintConfig.cpp:3693 +#: src/slic3r/GUI/Tab.cpp:4774 src/libslic3r/PrintConfig.cpp:3692 msgid "Object elevation" msgstr "Objectverhoging" -#: src/slic3r/GUI/Tab.cpp:4774 src/libslic3r/PrintConfig.cpp:3795 +#: src/slic3r/GUI/Tab.cpp:4774 src/libslic3r/PrintConfig.cpp:3794 msgid "Pad around object" msgstr "Basisplaat rondom object" @@ -9462,12 +9507,16 @@ msgid "" "You will not be asked about the unsaved changes in presets the next time you " "create new project" msgstr "" +"U wordt niet gevraagd naar niet-opgeslagen wijzigingen in presets de " +"volgende keer als u een nieuw project aanmaakt" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:899 msgid "" "You will not be asked about the unsaved changes in presets the next time you " "switch a preset" msgstr "" +"U wordt niet gevraagd naar niet-opgeslagen wijzigingen in presets de " +"volgende keer als u wisselt van preset" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:900 msgid "" @@ -9476,6 +9525,10 @@ msgid "" "- Closing PrusaSlicer while some presets are modified,\n" "- Loading a new project while some presets are modified" msgstr "" +"U wordt niet gevraagd naar niet-opgeslagen wijzigingen in presets de " +"volgende keer als:\n" +"- PrusaSlicer afgesloten wordt terwijl sommige presets zijn aangepast;\n" +"- een nieuw project wordt geladen terwijl sommige presets zijn aangepast" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:903 msgid "PrusaSlicer will remember your action." @@ -9546,7 +9599,7 @@ msgid "" "Preset \"%1%\" is not compatible with the new printer profile and it has the " "following unsaved changes:" msgstr "" -"Preset \"%1%\" is niet geschikt voor het nieuwe printerprofiel en heeft de " +"Preset \"%1%\" is niet compatibel met het nieuwe printerprofiel en heeft de " "volgende niet-opgeslagen wijzigingen:" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:1235 @@ -9555,7 +9608,7 @@ msgid "" "Preset \"%1%\" is not compatible with the new print profile and it has the " "following unsaved changes:" msgstr "" -"Preset \"%1%\" is niet geschikt voor het nieuwe printprofiel en heeft de " +"Preset \"%1%\" is niet compatibel met het nieuwe printprofiel en heeft de " "volgende niet-opgeslagen wijzigingen:" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:1283 @@ -9565,11 +9618,11 @@ msgstr "Aantal extruders" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:1456 msgid "Select presets to compare" -msgstr "" +msgstr "Selecteer presets om te vergelijken" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:1505 msgid "Show all presets (including incompatible)" -msgstr "Toon alle presets (inclusief incompatibele)" +msgstr "Toon alle presets (inclusief niet compatibele)" #: src/slic3r/GUI/UnsavedChangesDialog.cpp:1520 msgid "Left Preset Value" @@ -9627,7 +9680,7 @@ msgstr "Nieuwe versie:" #: src/slic3r/GUI/UpdateDialogs.cpp:52 msgid "Changelog & Download" -msgstr "" +msgstr "Wijzigingslogboek downloaden" #: src/slic3r/GUI/UpdateDialogs.cpp:59 src/slic3r/GUI/UpdateDialogs.cpp:133 #: src/slic3r/GUI/UpdateDialogs.cpp:191 @@ -9727,7 +9780,7 @@ msgstr "%s afsluiten" #: src/slic3r/GUI/UpdateDialogs.cpp:213 #, c-format, boost-format msgid "%s configuration is incompatible" -msgstr "%s configuratie is niet geschikt" +msgstr "%s configuratie is niet compatibel" #: src/slic3r/GUI/UpdateDialogs.cpp:216 #, c-format, boost-format @@ -9741,7 +9794,7 @@ msgid "" "the initial configuration. Doing so will create a backup snapshot of the " "existing configuration before installing files compatible with this %s." msgstr "" -"Deze versie van %s is niet geschikt voor de huidig geïnstalleerde " +"Deze versie van %s is niet compatibel voor de huidig geïnstalleerde " "configuratiebundels.\n" "Dit kan mogelijk ontstaan als resultaat van het draaien van een ouder %s na " "het gebruik van een nieuwere.\n" @@ -9758,7 +9811,7 @@ msgstr "Deze %s versie: %s" #: src/slic3r/GUI/UpdateDialogs.cpp:230 msgid "Incompatible bundles:" -msgstr "Ongeschikte bundels:" +msgstr "Niet compatibele bundels:" #: src/slic3r/GUI/UpdateDialogs.cpp:246 msgid "Re-configure" @@ -9994,27 +10047,27 @@ msgstr "Opslaan van mesh in 3MF-container mislukt." #: src/slic3r/Utils/FixModelByWin10.cpp:378 msgid "Export of a temporary 3mf file failed" -msgstr "Exporteren van tijdelijk 3MF-bestand mislukt" +msgstr "Exporteren van tijdelijk .3MF-bestand mislukt" #: src/slic3r/Utils/FixModelByWin10.cpp:394 msgid "Import of the repaired 3mf file failed" -msgstr "Importeren van het gerepareerde 3MF-bestand mislukt" +msgstr "Importeren van het gerepareerde .3MF-bestand mislukt" #: src/slic3r/Utils/FixModelByWin10.cpp:396 msgid "Repaired 3MF file does not contain any object" -msgstr "Gerepareerd 3MF-bestand bevat geen object" +msgstr "Gerepareerd .3MF-bestand bevat geen object" #: src/slic3r/Utils/FixModelByWin10.cpp:398 msgid "Repaired 3MF file contains more than one object" -msgstr "Gerepareerd 3MF-bestand bevat meer dan één object" +msgstr "Gerepareerd .3MF-bestand bevat meer dan één object" #: src/slic3r/Utils/FixModelByWin10.cpp:400 msgid "Repaired 3MF file does not contain any volume" -msgstr "Gerepareerd 3MF-bestand bevat geen volume" +msgstr "Gerepareerd .3MF-bestand bevat geen volume" #: src/slic3r/Utils/FixModelByWin10.cpp:402 msgid "Repaired 3MF file contains more than one volume" -msgstr "Gerepareerd 3MF-bestand bevat meer dan één volume" +msgstr "Gerepareerd .3MF-bestand bevat meer dan één volume" #: src/slic3r/Utils/FixModelByWin10.cpp:412 msgid "Model repair finished" @@ -10144,7 +10197,7 @@ msgstr "" #: src/slic3r/Utils/Process.cpp:157 msgid "Open G-code file:" -msgstr "Open G-code bestand:" +msgstr "Open .gcode-bestand:" #: src/slic3r/Utils/Repetier.cpp:84 msgid "Connection to Repetier works correctly." @@ -10191,24 +10244,26 @@ msgstr "" #: src/slic3r/Config/Snapshot.cpp:584 msgid "Taking a configuration snapshot failed." -msgstr "" +msgstr "Configuratiesnapshot nemen is mislukt." #: src/slic3r/Config/Snapshot.cpp:598 msgid "" "PrusaSlicer has encountered an error while taking a configuration snapshot." msgstr "" +"PrusaSlicer heeft een fout opgelopen bij het maken van een " +"configuratiesnapshot." #: src/slic3r/Config/Snapshot.cpp:599 msgid "PrusaSlicer error" -msgstr "" +msgstr "PrusaSlicer-fout" #: src/slic3r/Config/Snapshot.cpp:601 msgid "Continue" -msgstr "" +msgstr "Doorgaan" #: src/slic3r/Config/Snapshot.cpp:601 msgid "Abort" -msgstr "" +msgstr "Stoppen" #: src/libslic3r/GCode.cpp:539 msgid "There is an object with no extrusions in the first layer." @@ -10258,7 +10313,7 @@ msgstr "" #: src/libslic3r/GCode.cpp:1200 src/libslic3r/GCode.cpp:1211 msgid "No extrusions were generated for objects." -msgstr "" +msgstr "Geen extrusies werden gegenereerd voor de objecten." #: src/libslic3r/GCode.cpp:1406 msgid "" @@ -10286,8 +10341,8 @@ msgid "" "The selected 3mf file has been saved with a newer version of %1% and is not " "compatible." msgstr "" -"Het geselecteerde 3MF-bestand is opgeslagen in een nieuwere versie van %1% " -"en is niet geschikt." +"Het geselecteerde .3MF-bestand is opgeslagen in een nieuwere versie van %1% " +"en is niet compatibel." #: src/libslic3r/Format/3mf.cpp:1746 msgid "" @@ -10319,8 +10374,8 @@ msgid "" "The selected amf file has been saved with a newer version of %1% and is not " "compatible." msgstr "" -"Het geselecteerde AMF-bestand is opgeslagen in een nieuwere versie van %1% " -"en is niet geschikt." +"Het geselecteerde .AMF-bestand is opgeslagen in een nieuwere versie van %1% " +"en is niet compatibel." #: src/libslic3r/GCode/PostProcessor.cpp:289 #, boost-format @@ -10335,10 +10390,10 @@ msgid "" msgstr "" "Nabewerkscript %1% gaf een fout.\n" "\n" -"Het nabewerkscript zou de het gcode-bestand %2% moeten aanpassen, maar het " +"Het nabewerkscript zou de het .gcode-bestand %2% moeten aanpassen, maar het " "bestand is verwijderd en waarschijnlijk opgeslagen onder een andere naam.\n" "Pas alstublieft het nabewerkscript aan om de G-code aan te passen en " -"raadpleeg de handleiding over de benoeming van het nabewerkte gcode-" +"raadpleeg de handleiding over de benoeming van het nabewerkte .gcode-" "bestand.\n" #: src/libslic3r/miniz_extension.cpp:91 @@ -10650,18 +10705,25 @@ msgid "" "each layer to prevent loss of floating point accuracy. Add \"G92 E0\" to " "layer_gcode." msgstr "" +"Relatieve extruderwaarden vereist het resetten van de extruderpositie op " +"elke laag om decimale onnauwkeurigheid te voorkomen. Voeg \"G92 E0\" toe aan " +"layer_gcode." #: src/libslic3r/Print.cpp:679 msgid "" "\"G92 E0\" was found in before_layer_gcode, which is incompatible with " "absolute extruder addressing." msgstr "" +"\"G92 E0\" gevonden in before_layer_gcode, wat niet compatibel is met " +"absolute positionering." #: src/libslic3r/Print.cpp:681 msgid "" "\"G92 E0\" was found in layer_gcode, which is incompatible with absolute " "extruder addressing." msgstr "" +"\"G92 E0\" gevonden in layer_gcode, wat niet compatibel is met absolute " +"positionering." #: src/libslic3r/Print.cpp:823 msgid "Infilling layers" @@ -10849,18 +10911,20 @@ msgid "" "Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the " "following format: \"XxY, XxY, ...\"" msgstr "" -"Afbeeldingsgrootte wordt opgeslagen in de .gcode en .sl1 / .sl1s bestanden " -"in het formaat: \"XxY, XxY, ...\"" +"Afbeeldingsgrootte wordt opgeslagen in de .gcode en .sl1/.sl1s-bestanden in " +"het formaat: \"XxY, XxY, ...\"" #: src/libslic3r/PrintConfig.cpp:275 msgid "Format of G-code thumbnails" -msgstr "" +msgstr "Bestandstype van G-code-voorbeelden" #: src/libslic3r/PrintConfig.cpp:276 msgid "" "Format of G-code thumbnails: PNG for best quality, JPG for smallest size, " "QOI for low memory firmware" msgstr "" +"Bestandstype van G-code-voorbeelden: PNG voor de beste kwaliteit, JPG voor " +"kleinste bestand, QOI voor firmware met weinig geheugen" #: src/libslic3r/PrintConfig.cpp:287 msgid "" @@ -10893,7 +10957,7 @@ msgid "" "name and password into the URL in the following format: https://username:" "password@your-octopi-address/" msgstr "" -"PrusaSlicer kan G-code-bestanden uploaden naar een printerhost. Dit tekstvak " +"PrusaSlicer kan .gcode-bestanden uploaden naar een printerhost. Dit tekstvak " "bevat de hostnaam, IP-adres of URL van de printerhostinstantie. Als de " "printerhost achter HAProxy met basis-authorisatie aan staat, kan toegang " "worden verkregen door gebruikersnaam en wachtwoord in te voeren in bij de " @@ -10908,8 +10972,8 @@ msgid "" "Slic3r can upload G-code files to a printer host. This field should contain " "the API Key or the password required for authentication." msgstr "" -"PrusaSlicer kan gcode-bestanden naar een printerhost uploaden. Dit veld moet " -"de API-key of het wachtwoord voor authenticatie bevatten." +"PrusaSlicer kan .gcode-bestanden naar een printerhost uploaden. Dit veld " +"moet de API-key of het wachtwoord voor authenticatie bevatten." #: src/libslic3r/PrintConfig.cpp:322 msgid "Name of the printer" @@ -10961,7 +11025,7 @@ msgstr "API-sleutel" #: src/libslic3r/PrintConfig.cpp:374 msgid "HTTP digest" -msgstr "HTTP authenticatie" +msgstr "HTTP weergave" #: src/libslic3r/PrintConfig.cpp:394 msgid "Avoid crossing perimeters" @@ -10998,7 +11062,7 @@ msgstr "" msgid "mm or % (zero to disable)" msgstr "mm of % (stel in op 0 om uit te zetten)" -#: src/libslic3r/PrintConfig.cpp:414 src/libslic3r/PrintConfig.cpp:2808 +#: src/libslic3r/PrintConfig.cpp:414 src/libslic3r/PrintConfig.cpp:2807 msgid "Other layers" msgstr "Overige lagen" @@ -11076,10 +11140,10 @@ msgstr "" "op 0, wordt de acceleratie-instelling voor bruggen uitgezet." #: src/libslic3r/PrintConfig.cpp:467 src/libslic3r/PrintConfig.cpp:638 -#: src/libslic3r/PrintConfig.cpp:1188 src/libslic3r/PrintConfig.cpp:1197 -#: src/libslic3r/PrintConfig.cpp:1397 src/libslic3r/PrintConfig.cpp:1690 -#: src/libslic3r/PrintConfig.cpp:1741 src/libslic3r/PrintConfig.cpp:1752 -#: src/libslic3r/PrintConfig.cpp:1762 src/libslic3r/PrintConfig.cpp:1960 +#: src/libslic3r/PrintConfig.cpp:1187 src/libslic3r/PrintConfig.cpp:1196 +#: src/libslic3r/PrintConfig.cpp:1396 src/libslic3r/PrintConfig.cpp:1689 +#: src/libslic3r/PrintConfig.cpp:1740 src/libslic3r/PrintConfig.cpp:1751 +#: src/libslic3r/PrintConfig.cpp:1761 src/libslic3r/PrintConfig.cpp:1959 msgid "mm/s²" msgstr "mm/s²" @@ -11097,11 +11161,11 @@ msgstr "" "automatisch berekend, anders wordt de opgegeven hoek voor alle bruggen " "gebruikt. 180° staat gelijk aan 0°." -#: src/libslic3r/PrintConfig.cpp:478 src/libslic3r/PrintConfig.cpp:1098 -#: src/libslic3r/PrintConfig.cpp:2251 src/libslic3r/PrintConfig.cpp:2261 -#: src/libslic3r/PrintConfig.cpp:2552 src/libslic3r/PrintConfig.cpp:2793 -#: src/libslic3r/PrintConfig.cpp:3010 src/libslic3r/PrintConfig.cpp:3113 -#: src/libslic3r/PrintConfig.cpp:3667 src/libslic3r/PrintConfig.cpp:3788 +#: src/libslic3r/PrintConfig.cpp:478 src/libslic3r/PrintConfig.cpp:1097 +#: src/libslic3r/PrintConfig.cpp:2250 src/libslic3r/PrintConfig.cpp:2260 +#: src/libslic3r/PrintConfig.cpp:2551 src/libslic3r/PrintConfig.cpp:2792 +#: src/libslic3r/PrintConfig.cpp:3009 src/libslic3r/PrintConfig.cpp:3112 +#: src/libslic3r/PrintConfig.cpp:3666 src/libslic3r/PrintConfig.cpp:3787 msgid "°" msgstr "°" @@ -11113,11 +11177,11 @@ msgstr "Ventilatorsnelheid voor bruggen" msgid "This fan speed is enforced during all bridges and overhangs." msgstr "Deze ventilatorsnelheid wordt aangehouden bij bruggen en overhanging." -#: src/libslic3r/PrintConfig.cpp:486 src/libslic3r/PrintConfig.cpp:1110 -#: src/libslic3r/PrintConfig.cpp:1578 src/libslic3r/PrintConfig.cpp:1770 -#: src/libslic3r/PrintConfig.cpp:1833 src/libslic3r/PrintConfig.cpp:2084 -#: src/libslic3r/PrintConfig.cpp:2143 src/libslic3r/PrintConfig.cpp:3292 -#: src/libslic3r/PrintConfig.cpp:3581 src/libslic3r/PrintConfig.cpp:3707 +#: src/libslic3r/PrintConfig.cpp:486 src/libslic3r/PrintConfig.cpp:1109 +#: src/libslic3r/PrintConfig.cpp:1577 src/libslic3r/PrintConfig.cpp:1769 +#: src/libslic3r/PrintConfig.cpp:1832 src/libslic3r/PrintConfig.cpp:2083 +#: src/libslic3r/PrintConfig.cpp:2142 src/libslic3r/PrintConfig.cpp:3291 +#: src/libslic3r/PrintConfig.cpp:3580 src/libslic3r/PrintConfig.cpp:3706 msgid "%" msgstr "%" @@ -11148,14 +11212,14 @@ msgstr "Printsnelheid voor bruggen." #: src/libslic3r/PrintConfig.cpp:508 src/libslic3r/PrintConfig.cpp:916 #: src/libslic3r/PrintConfig.cpp:924 src/libslic3r/PrintConfig.cpp:933 #: src/libslic3r/PrintConfig.cpp:941 src/libslic3r/PrintConfig.cpp:968 -#: src/libslic3r/PrintConfig.cpp:987 src/libslic3r/PrintConfig.cpp:1325 -#: src/libslic3r/PrintConfig.cpp:1515 src/libslic3r/PrintConfig.cpp:1597 -#: src/libslic3r/PrintConfig.cpp:1673 src/libslic3r/PrintConfig.cpp:1707 -#: src/libslic3r/PrintConfig.cpp:1719 src/libslic3r/PrintConfig.cpp:1729 -#: src/libslic3r/PrintConfig.cpp:1792 src/libslic3r/PrintConfig.cpp:1851 -#: src/libslic3r/PrintConfig.cpp:1991 src/libslic3r/PrintConfig.cpp:2218 -#: src/libslic3r/PrintConfig.cpp:2227 src/libslic3r/PrintConfig.cpp:2758 -#: src/libslic3r/PrintConfig.cpp:2908 src/libslic3r/PrintConfig.cpp:2918 +#: src/libslic3r/PrintConfig.cpp:987 src/libslic3r/PrintConfig.cpp:1324 +#: src/libslic3r/PrintConfig.cpp:1514 src/libslic3r/PrintConfig.cpp:1596 +#: src/libslic3r/PrintConfig.cpp:1672 src/libslic3r/PrintConfig.cpp:1706 +#: src/libslic3r/PrintConfig.cpp:1718 src/libslic3r/PrintConfig.cpp:1728 +#: src/libslic3r/PrintConfig.cpp:1791 src/libslic3r/PrintConfig.cpp:1850 +#: src/libslic3r/PrintConfig.cpp:1990 src/libslic3r/PrintConfig.cpp:2217 +#: src/libslic3r/PrintConfig.cpp:2226 src/libslic3r/PrintConfig.cpp:2757 +#: src/libslic3r/PrintConfig.cpp:2907 src/libslic3r/PrintConfig.cpp:2917 msgid "mm/s" msgstr "mm/s" @@ -11236,7 +11300,7 @@ msgstr "Hoogte waarbij de filamentwissel plaatsvindt." #: src/libslic3r/PrintConfig.cpp:570 msgid "Compatible printers condition" -msgstr "Voorwaarden geschikte printers" +msgstr "Voorwaarden compatibele printers" #: src/libslic3r/PrintConfig.cpp:571 msgid "" @@ -11246,11 +11310,11 @@ msgid "" msgstr "" "Een waar/niet waar aanduiding die gebruik maakt van configuratiewaarden van " "een actief printerprofiel. Als deze aanduiding op waar staat, wordt dit " -"profiel beschouwd als geschikt voor het actieve printerprofiel." +"profiel beschouwd als compatibel voor het actieve printerprofiel." #: src/libslic3r/PrintConfig.cpp:585 msgid "Compatible print profiles condition" -msgstr "Voorwaarden geschikte printprofielen" +msgstr "Voorwaarden compatibele printprofielen" #: src/libslic3r/PrintConfig.cpp:586 msgid "" @@ -11260,7 +11324,7 @@ msgid "" msgstr "" "Een waar/niet waar aanduiding die gebruik maakt van configuratiewaarden van " "een actief printprofiel. Als deze aanduiding op waar staat, wordt dit " -"profiel beschouwd als geschikt voor het actieve printprofiel." +"profiel beschouwd als compatibel met het actieve printprofiel." #: src/libslic3r/PrintConfig.cpp:603 msgid "Complete individual objects" @@ -11335,8 +11399,8 @@ msgstr "" msgid "Default print profile" msgstr "Standaard printprofiel" -#: src/libslic3r/PrintConfig.cpp:652 src/libslic3r/PrintConfig.cpp:3511 -#: src/libslic3r/PrintConfig.cpp:3522 +#: src/libslic3r/PrintConfig.cpp:652 src/libslic3r/PrintConfig.cpp:3510 +#: src/libslic3r/PrintConfig.cpp:3521 msgid "" "Default print profile associated with the current printer profile. On " "selection of the current printer profile, this print profile will be " @@ -11422,8 +11486,8 @@ msgstr "" "Vullingspatroon voor bovenste lagen. Dit heeft alleen invloed op de bovenste " "zichtbare laag en niet de aangrenzende horizontale dichte shells." -#: src/libslic3r/PrintConfig.cpp:726 src/libslic3r/PrintConfig.cpp:1165 -#: src/libslic3r/PrintConfig.cpp:2723 src/libslic3r/PrintConfig.cpp:2740 +#: src/libslic3r/PrintConfig.cpp:726 src/libslic3r/PrintConfig.cpp:1164 +#: src/libslic3r/PrintConfig.cpp:2722 src/libslic3r/PrintConfig.cpp:2739 msgid "Rectilinear" msgstr "Rechtlijnig" @@ -11431,24 +11495,24 @@ msgstr "Rechtlijnig" msgid "Monotonic" msgstr "Monotoon" -#: src/libslic3r/PrintConfig.cpp:728 src/libslic3r/PrintConfig.cpp:1166 +#: src/libslic3r/PrintConfig.cpp:728 src/libslic3r/PrintConfig.cpp:1165 msgid "Aligned Rectilinear" msgstr "Parallel rechtlijnig" -#: src/libslic3r/PrintConfig.cpp:729 src/libslic3r/PrintConfig.cpp:1172 -#: src/libslic3r/PrintConfig.cpp:2741 +#: src/libslic3r/PrintConfig.cpp:729 src/libslic3r/PrintConfig.cpp:1171 +#: src/libslic3r/PrintConfig.cpp:2740 msgid "Concentric" msgstr "Concentrisch" -#: src/libslic3r/PrintConfig.cpp:730 src/libslic3r/PrintConfig.cpp:1176 +#: src/libslic3r/PrintConfig.cpp:730 src/libslic3r/PrintConfig.cpp:1175 msgid "Hilbert Curve" msgstr "Hilbert-kromme" -#: src/libslic3r/PrintConfig.cpp:731 src/libslic3r/PrintConfig.cpp:1177 +#: src/libslic3r/PrintConfig.cpp:731 src/libslic3r/PrintConfig.cpp:1176 msgid "Archimedean Chords" msgstr "Archimedes-spiraal" -#: src/libslic3r/PrintConfig.cpp:732 src/libslic3r/PrintConfig.cpp:1178 +#: src/libslic3r/PrintConfig.cpp:732 src/libslic3r/PrintConfig.cpp:1177 msgid "Octagram Spiral" msgstr "Octagramspiraal" @@ -11481,13 +11545,13 @@ msgstr "" "percentage, wordt dit berekend over de laagdikte." #: src/libslic3r/PrintConfig.cpp:754 src/libslic3r/PrintConfig.cpp:865 -#: src/libslic3r/PrintConfig.cpp:1219 src/libslic3r/PrintConfig.cpp:1422 -#: src/libslic3r/PrintConfig.cpp:1479 src/libslic3r/PrintConfig.cpp:1506 -#: src/libslic3r/PrintConfig.cpp:1980 src/libslic3r/PrintConfig.cpp:2366 -#: src/libslic3r/PrintConfig.cpp:2540 src/libslic3r/PrintConfig.cpp:2629 -#: src/libslic3r/PrintConfig.cpp:2864 src/libslic3r/PrintConfig.cpp:3086 -#: src/libslic3r/PrintConfig.cpp:3101 src/libslic3r/PrintConfig.cpp:3135 -#: src/libslic3r/PrintConfig.cpp:3147 +#: src/libslic3r/PrintConfig.cpp:1218 src/libslic3r/PrintConfig.cpp:1421 +#: src/libslic3r/PrintConfig.cpp:1478 src/libslic3r/PrintConfig.cpp:1505 +#: src/libslic3r/PrintConfig.cpp:1979 src/libslic3r/PrintConfig.cpp:2365 +#: src/libslic3r/PrintConfig.cpp:2539 src/libslic3r/PrintConfig.cpp:2628 +#: src/libslic3r/PrintConfig.cpp:2863 src/libslic3r/PrintConfig.cpp:3085 +#: src/libslic3r/PrintConfig.cpp:3100 src/libslic3r/PrintConfig.cpp:3134 +#: src/libslic3r/PrintConfig.cpp:3146 msgid "mm or %" msgstr "mm of %" @@ -11502,10 +11566,10 @@ msgstr "" "perimeters. Als dit ingesteld is op 0, wordt een automatische snelheid " "genomen." -#: src/libslic3r/PrintConfig.cpp:766 src/libslic3r/PrintConfig.cpp:1241 -#: src/libslic3r/PrintConfig.cpp:1252 src/libslic3r/PrintConfig.cpp:2325 -#: src/libslic3r/PrintConfig.cpp:2378 src/libslic3r/PrintConfig.cpp:2709 -#: src/libslic3r/PrintConfig.cpp:2878 +#: src/libslic3r/PrintConfig.cpp:766 src/libslic3r/PrintConfig.cpp:1240 +#: src/libslic3r/PrintConfig.cpp:1251 src/libslic3r/PrintConfig.cpp:2324 +#: src/libslic3r/PrintConfig.cpp:2377 src/libslic3r/PrintConfig.cpp:2708 +#: src/libslic3r/PrintConfig.cpp:2877 msgid "mm/s or %" msgstr "mm/s of %" @@ -11572,7 +11636,7 @@ msgid "Extruder Color" msgstr "Extruderkleur" #: src/libslic3r/PrintConfig.cpp:827 src/libslic3r/PrintConfig.cpp:890 -#: src/libslic3r/PrintConfig.cpp:3363 +#: src/libslic3r/PrintConfig.cpp:3362 msgid "This is only used in the Slic3r interface as a visual help." msgstr "" "Dit wordt alleen gebruikt in de PrusaSlicer-interface als een visueel " @@ -11666,11 +11730,11 @@ msgstr "" "ventilator aangezet worden en wordt de snelheid berekend door te " "interpoleren tussen de minimale en maximale snelheid." -#: src/libslic3r/PrintConfig.cpp:882 src/libslic3r/PrintConfig.cpp:2313 +#: src/libslic3r/PrintConfig.cpp:882 src/libslic3r/PrintConfig.cpp:2312 msgid "approximate seconds" msgstr "geschat aantal seconden" -#: src/libslic3r/PrintConfig.cpp:889 src/libslic3r/PrintConfig.cpp:3362 +#: src/libslic3r/PrintConfig.cpp:889 src/libslic3r/PrintConfig.cpp:3361 msgid "Color" msgstr "Kleur" @@ -11682,7 +11746,7 @@ msgstr "Filamentopmerkingen" msgid "You can put your notes regarding the filament here." msgstr "Hier kunt u opmerkingen over het filament plaatsen." -#: src/libslic3r/PrintConfig.cpp:904 src/libslic3r/PrintConfig.cpp:1798 +#: src/libslic3r/PrintConfig.cpp:904 src/libslic3r/PrintConfig.cpp:1797 msgid "Max volumetric speed" msgstr "Maximale volumetrische snelheid" @@ -11851,8 +11915,8 @@ msgstr "" "daarom een schuifmaat en doe meerdere metingen over het hele filament. " "Bereken vervolgens het gemiddelde." -#: src/libslic3r/PrintConfig.cpp:1024 src/libslic3r/PrintConfig.cpp:3401 -#: src/libslic3r/PrintConfig.cpp:3402 +#: src/libslic3r/PrintConfig.cpp:1024 src/libslic3r/PrintConfig.cpp:3400 +#: src/libslic3r/PrintConfig.cpp:3401 msgid "Density" msgstr "Dichtheid" @@ -11880,15 +11944,15 @@ msgstr "Filamenttype" msgid "The filament material type for use in custom G-codes." msgstr "Het filamenttype voor het gebruik van de custom G-codes." -#: src/libslic3r/PrintConfig.cpp:1062 +#: src/libslic3r/PrintConfig.cpp:1061 msgid "Soluble material" msgstr "Oplosbaar materiaal" -#: src/libslic3r/PrintConfig.cpp:1063 +#: src/libslic3r/PrintConfig.cpp:1062 msgid "Soluble material is most likely used for a soluble support." msgstr "Oplosbaar materiaal wordt vaak gebruikt voor oplosbaar support." -#: src/libslic3r/PrintConfig.cpp:1069 +#: src/libslic3r/PrintConfig.cpp:1068 msgid "" "Enter your filament cost per kg here. This is only for statistical " "information." @@ -11896,15 +11960,15 @@ msgstr "" "Voer hier de filamentkosten per kilogram in. Dit is alleen voor statistische " "informatie." -#: src/libslic3r/PrintConfig.cpp:1070 +#: src/libslic3r/PrintConfig.cpp:1069 msgid "money/kg" msgstr "€/kg" -#: src/libslic3r/PrintConfig.cpp:1075 +#: src/libslic3r/PrintConfig.cpp:1074 msgid "Spool weight" msgstr "Spoelgewicht" -#: src/libslic3r/PrintConfig.cpp:1076 +#: src/libslic3r/PrintConfig.cpp:1075 msgid "" "Enter weight of the empty filament spool. One may weigh a partially consumed " "filament spool before printing and one may compare the measured weight with " @@ -11916,19 +11980,19 @@ msgstr "" "berekende gewicht van de filamentspoel om te weten te komen of de " "hoeveelheid filament op de spoel voldoende is om de print te voltooien." -#: src/libslic3r/PrintConfig.cpp:1080 +#: src/libslic3r/PrintConfig.cpp:1079 msgid "g" msgstr "g" -#: src/libslic3r/PrintConfig.cpp:1089 src/libslic3r/PrintConfig.cpp:3506 +#: src/libslic3r/PrintConfig.cpp:1088 src/libslic3r/PrintConfig.cpp:3505 msgid "(Unknown)" msgstr "(Onbekend)" -#: src/libslic3r/PrintConfig.cpp:1093 +#: src/libslic3r/PrintConfig.cpp:1092 msgid "Fill angle" msgstr "Vullingshoek" -#: src/libslic3r/PrintConfig.cpp:1095 +#: src/libslic3r/PrintConfig.cpp:1094 msgid "" "Default base angle for infill orientation. Cross-hatching will be applied to " "this. Bridges will be infilled using the best direction Slic3r can detect, " @@ -11938,64 +12002,64 @@ msgstr "" "geprint. Bruggen worden geprint met de optimale richting. Deze instelling " "zal die richting niet beïnvloeden." -#: src/libslic3r/PrintConfig.cpp:1107 +#: src/libslic3r/PrintConfig.cpp:1106 msgid "Fill density" msgstr "Vullingsdichtheid" -#: src/libslic3r/PrintConfig.cpp:1109 +#: src/libslic3r/PrintConfig.cpp:1108 msgid "Density of internal infill, expressed in the range 0% - 100%." msgstr "" "Dichtheid van inwendige vulling, uitgedrukt in een percentage (0 - 100%)" -#: src/libslic3r/PrintConfig.cpp:1144 +#: src/libslic3r/PrintConfig.cpp:1143 msgid "Fill pattern" msgstr "Vullingspatroon" -#: src/libslic3r/PrintConfig.cpp:1146 +#: src/libslic3r/PrintConfig.cpp:1145 msgid "Fill pattern for general low-density infill." msgstr "Vulpatroon voor algemene lagere-dichtheidsvulling." -#: src/libslic3r/PrintConfig.cpp:1167 src/libslic3r/PrintConfig.cpp:2772 +#: src/libslic3r/PrintConfig.cpp:1166 src/libslic3r/PrintConfig.cpp:2771 msgid "Grid" msgstr "Raster" -#: src/libslic3r/PrintConfig.cpp:1169 +#: src/libslic3r/PrintConfig.cpp:1168 msgid "Stars" msgstr "Sterren" -#: src/libslic3r/PrintConfig.cpp:1170 +#: src/libslic3r/PrintConfig.cpp:1169 msgid "Cubic" msgstr "Kubisch" -#: src/libslic3r/PrintConfig.cpp:1171 +#: src/libslic3r/PrintConfig.cpp:1170 msgid "Line" msgstr "Lijn" -#: src/libslic3r/PrintConfig.cpp:1173 src/libslic3r/PrintConfig.cpp:2725 +#: src/libslic3r/PrintConfig.cpp:1172 src/libslic3r/PrintConfig.cpp:2724 msgid "Honeycomb" msgstr "Honingraat" -#: src/libslic3r/PrintConfig.cpp:1174 +#: src/libslic3r/PrintConfig.cpp:1173 msgid "3D Honeycomb" msgstr "3D-honingraat" -#: src/libslic3r/PrintConfig.cpp:1175 +#: src/libslic3r/PrintConfig.cpp:1174 msgid "Gyroid" msgstr "Gyroïde" -#: src/libslic3r/PrintConfig.cpp:1179 +#: src/libslic3r/PrintConfig.cpp:1178 msgid "Adaptive Cubic" msgstr "Adaptief kubisch" -#: src/libslic3r/PrintConfig.cpp:1180 +#: src/libslic3r/PrintConfig.cpp:1179 msgid "Support Cubic" msgstr "Ondersteunend kubisch" -#: src/libslic3r/PrintConfig.cpp:1181 +#: src/libslic3r/PrintConfig.cpp:1180 msgid "Lightning" -msgstr "Belichting" +msgstr "Bliksem" -#: src/libslic3r/PrintConfig.cpp:1186 +#: src/libslic3r/PrintConfig.cpp:1185 msgid "" "This is the acceleration your printer will use for first layer. Set zero to " "disable acceleration control for first layer." @@ -12003,11 +12067,11 @@ msgstr "" "Deze acceleratie zal uw printer gebruiken voor de eerste laag. Als dit " "ingesteld is op 0, wordt de standaard acceleratie gebruikt." -#: src/libslic3r/PrintConfig.cpp:1194 +#: src/libslic3r/PrintConfig.cpp:1193 msgid "First object layer over raft interface" msgstr "Eerste laag van het object boven de raft-interface" -#: src/libslic3r/PrintConfig.cpp:1195 +#: src/libslic3r/PrintConfig.cpp:1194 msgid "" "This is the acceleration your printer will use for first layer of object " "above raft interface. Set zero to disable acceleration control for first " @@ -12017,11 +12081,11 @@ msgstr "" "raft-interface. Stel in op 0 om acceleratiecontrole uit te schakelen voor de " "eerste laag boven de raft-interface." -#: src/libslic3r/PrintConfig.cpp:1204 +#: src/libslic3r/PrintConfig.cpp:1203 msgid "First layer bed temperature" msgstr "Bedtemperatuur eerste laag" -#: src/libslic3r/PrintConfig.cpp:1205 +#: src/libslic3r/PrintConfig.cpp:1204 msgid "" "Heated build plate temperature for the first layer. Set this to zero to " "disable bed temperature control commands in the output." @@ -12029,7 +12093,7 @@ msgstr "" "Bedtemperatuur voor de eerste laag. Als dit ingesteld is op 0, worden " "bedtemperatuur-commando's weggelaten in de output." -#: src/libslic3r/PrintConfig.cpp:1215 +#: src/libslic3r/PrintConfig.cpp:1214 msgid "" "Set this to a non-zero value to set a manual extrusion width for first " "layer. You can use this to force fatter extrudates for better adhesion. If " @@ -12042,7 +12106,7 @@ msgstr "" "wordt dit berekend over de laagdikte van de eerste laag. Als dit is " "ingesteld op 0, wordt de standaard extrusiebreedte gebruikt." -#: src/libslic3r/PrintConfig.cpp:1229 +#: src/libslic3r/PrintConfig.cpp:1228 msgid "" "When printing with very low layer heights, you might still want to print a " "thicker bottom layer to improve adhesion and tolerance for non perfect build " @@ -12052,11 +12116,11 @@ msgstr "" "printen om bedhechting en tolerantie te verbeteren op niet-perfecte " "printbedden." -#: src/libslic3r/PrintConfig.cpp:1237 +#: src/libslic3r/PrintConfig.cpp:1236 msgid "First layer speed" msgstr "Snelheid eerste laag" -#: src/libslic3r/PrintConfig.cpp:1238 +#: src/libslic3r/PrintConfig.cpp:1237 msgid "" "If expressed as absolute value in mm/s, this speed will be applied to all " "the print moves of the first layer, regardless of their type. If expressed " @@ -12067,11 +12131,11 @@ msgstr "" "van het type. Als dit is uitgedrukt als percentage, wordt dit berekend over " "de standaardsnelheid." -#: src/libslic3r/PrintConfig.cpp:1248 +#: src/libslic3r/PrintConfig.cpp:1247 msgid "Speed of object first layer over raft interface" msgstr "Snelheid van de eerste laag boven de raft-interface" -#: src/libslic3r/PrintConfig.cpp:1249 +#: src/libslic3r/PrintConfig.cpp:1248 msgid "" "If expressed as absolute value in mm/s, this speed will be applied to all " "the print moves of the first object layer above raft interface, regardless " @@ -12083,11 +12147,11 @@ msgstr "" "interface, onafhankelijk van het type. Als dit is uitgedrukt als percentage " "(bijv. 40%) worden de standaard snelheden verschaald." -#: src/libslic3r/PrintConfig.cpp:1259 +#: src/libslic3r/PrintConfig.cpp:1258 msgid "First layer nozzle temperature" msgstr "Nozzletemperatuur eerste laag" -#: src/libslic3r/PrintConfig.cpp:1260 +#: src/libslic3r/PrintConfig.cpp:1259 msgid "" "Nozzle temperature for the first layer. If you want to control temperature " "manually during print, set this to zero to disable temperature control " @@ -12097,11 +12161,11 @@ msgstr "" "wijzigen in de print, stel dit dan in op 0 om temperatuurregeling uit te " "zetten in de G-code." -#: src/libslic3r/PrintConfig.cpp:1268 +#: src/libslic3r/PrintConfig.cpp:1267 msgid "Full fan speed at layer" msgstr "Volledige ventilatorsnelheid op laag" -#: src/libslic3r/PrintConfig.cpp:1269 +#: src/libslic3r/PrintConfig.cpp:1268 msgid "" "Fan speed will be ramped up linearly from zero at layer " "\"disable_fan_first_layers\" to maximum at layer \"full_fan_speed_layer\". " @@ -12115,23 +12179,23 @@ msgstr "" "\"disable_fan_first_layers\" in welk geval de ventilator zal draaien op de " "maximaal toegestane snelheid op laag \"disable_fan_first_layers\" + 1." -#: src/libslic3r/PrintConfig.cpp:1281 +#: src/libslic3r/PrintConfig.cpp:1280 msgid "Fuzzy skin type." msgstr "Type oneffen oppervlak." -#: src/libslic3r/PrintConfig.cpp:1288 +#: src/libslic3r/PrintConfig.cpp:1287 msgid "Outside walls" msgstr "Alleen buitenwanden" -#: src/libslic3r/PrintConfig.cpp:1289 +#: src/libslic3r/PrintConfig.cpp:1288 msgid "All walls" msgstr "Alle wanden" -#: src/libslic3r/PrintConfig.cpp:1294 +#: src/libslic3r/PrintConfig.cpp:1293 msgid "Fuzzy skin thickness" msgstr "Dikte van oneffen oppervlak" -#: src/libslic3r/PrintConfig.cpp:1296 +#: src/libslic3r/PrintConfig.cpp:1295 msgid "" "The maximum distance that each skin point can be offset (both ways), " "measured perpendicular to the perimeter wall." @@ -12139,11 +12203,11 @@ msgstr "" "De maximale afstand die elke punt kan hebben (naar beide kanten), haaks " "gemeten op de perimeterwand." -#: src/libslic3r/PrintConfig.cpp:1304 +#: src/libslic3r/PrintConfig.cpp:1303 msgid "Fuzzy skin point distance" msgstr "Puntafstand van oneffen oppervlak" -#: src/libslic3r/PrintConfig.cpp:1306 +#: src/libslic3r/PrintConfig.cpp:1305 msgid "" "Perimeters will be split into multiple segments by inserting Fuzzy skin " "points. Lowering the Fuzzy skin point distance will increase the number of " @@ -12153,11 +12217,11 @@ msgstr "" "punten voor oneffen oppervlak. Verlagen van de afstand zorgt voor een " "verhoging van het aantal willekeurige punten op de perimeterwand." -#: src/libslic3r/PrintConfig.cpp:1314 +#: src/libslic3r/PrintConfig.cpp:1313 msgid "Fill gaps" msgstr "Vul gaten" -#: src/libslic3r/PrintConfig.cpp:1316 +#: src/libslic3r/PrintConfig.cpp:1315 msgid "" "Enables filling of gaps between perimeters and between the inner most " "perimeters and infill." @@ -12165,7 +12229,7 @@ msgstr "" "Toestaan van het vullen van gaten tussen perimeters en de binnenste " "perimeter en vulling." -#: src/libslic3r/PrintConfig.cpp:1323 +#: src/libslic3r/PrintConfig.cpp:1322 msgid "" "Speed for filling small gaps using short zigzag moves. Keep this reasonably " "low to avoid too much shaking and resonance issues. Set zero to disable gaps " @@ -12175,11 +12239,11 @@ msgstr "" "Houd deze waarde laag om schudden te voorkomen (wat resulteert in " "resonantieproblemen). Als dit is ingesteld op 0, worden gaten niet gevuld." -#: src/libslic3r/PrintConfig.cpp:1331 +#: src/libslic3r/PrintConfig.cpp:1330 msgid "Verbose G-code" msgstr "Opmerkingen in G-code" -#: src/libslic3r/PrintConfig.cpp:1332 +#: src/libslic3r/PrintConfig.cpp:1331 msgid "" "Enable this to get a commented G-code file, with each line explained by a " "descriptive text. If you print from SD card, the additional weight of the " @@ -12189,11 +12253,11 @@ msgstr "" "commando's wordt een opmerking geplaatst. Als u print vanaf een SD-kaart, " "kan de extra grootte van het bestand de firmware vertragen." -#: src/libslic3r/PrintConfig.cpp:1339 +#: src/libslic3r/PrintConfig.cpp:1338 msgid "G-code flavor" msgstr "G-code-variant" -#: src/libslic3r/PrintConfig.cpp:1340 +#: src/libslic3r/PrintConfig.cpp:1339 msgid "" "Some G/M-code commands, including temperature control and others, are not " "universal. Set this option to your printer's firmware to get a compatible " @@ -12201,18 +12265,18 @@ msgid "" "extrusion value at all." msgstr "" "Sommige G- en M-commando's zijn niet universeel. Stel deze optie in om een " -"geschikte uitvoer te krijgen voor uw printer. De 'Geen extrusie'-instelling " -"kan gebruikt worden om te printen zonder materiaal te extruderen." +"compatibele uitvoer te krijgen voor uw printer. De 'Geen extrusie'-" +"instelling kan gebruikt worden om te printen zonder materiaal te extruderen." -#: src/libslic3r/PrintConfig.cpp:1367 +#: src/libslic3r/PrintConfig.cpp:1366 msgid "No extrusion" msgstr "Geen extrusie" -#: src/libslic3r/PrintConfig.cpp:1372 +#: src/libslic3r/PrintConfig.cpp:1371 msgid "Label objects" msgstr "Label objecten" -#: src/libslic3r/PrintConfig.cpp:1373 +#: src/libslic3r/PrintConfig.cpp:1372 msgid "" "Enable this to add comments into the G-Code labeling print moves with what " "object they belong to, which is useful for the Octoprint CancelObject " @@ -12221,22 +12285,22 @@ msgid "" msgstr "" "Schakel dit in om opmerkingen in de G-code toe te voegen voor bewegingen die " "behoren tot een object. Dit is handig voor de OctoPrint CancelObject-plugin. " -"Deze instelling is NIET geschikt voor een multi-materialsetup met één " +"Deze instelling is NIET compatibel met een multi-materialsetup met één " "extruder en 'Afvegen in object' en 'Afvegen in vulling'." -#: src/libslic3r/PrintConfig.cpp:1380 +#: src/libslic3r/PrintConfig.cpp:1379 msgid "G-code substitutions" -msgstr "" +msgstr "G-code-substituties" -#: src/libslic3r/PrintConfig.cpp:1381 +#: src/libslic3r/PrintConfig.cpp:1380 msgid "Find / replace patterns in G-code lines and substitute them." -msgstr "" +msgstr "Zoek / vervang patronen in G-coderegels en substitueer ze." -#: src/libslic3r/PrintConfig.cpp:1386 +#: src/libslic3r/PrintConfig.cpp:1385 msgid "High extruder current on filament swap" msgstr "Hoge stroomsterkte bij extruder voor filamentwissel" -#: src/libslic3r/PrintConfig.cpp:1387 +#: src/libslic3r/PrintConfig.cpp:1386 msgid "" "It may be beneficial to increase the extruder motor current during the " "filament exchange sequence to allow for rapid ramming feed rates and to " @@ -12247,7 +12311,7 @@ msgstr "" "maken en om weerstand te overwinnen tijdens het laden van filament met een " "misvormde kop." -#: src/libslic3r/PrintConfig.cpp:1395 +#: src/libslic3r/PrintConfig.cpp:1394 msgid "" "This is the acceleration your printer will use for infill. Set zero to " "disable acceleration control for infill." @@ -12255,11 +12319,11 @@ msgstr "" "Deze acceleratie zal uw printer gebruiken voor de vulling. Als dit is " "ingesteld op 0, wordt de acceleratiecontrole uitgeschakeld." -#: src/libslic3r/PrintConfig.cpp:1403 +#: src/libslic3r/PrintConfig.cpp:1402 msgid "Combine infill every" msgstr "Combineer vulling elke" -#: src/libslic3r/PrintConfig.cpp:1405 +#: src/libslic3r/PrintConfig.cpp:1404 msgid "" "This feature allows to combine infill and speed up your print by extruding " "thicker infill layers while preserving thin perimeters, thus accuracy." @@ -12268,15 +12332,15 @@ msgstr "" "de vullingslagen stapsgewijs dikker te maken, terwijl de laagdikte van " "perimeters behouden wordt." -#: src/libslic3r/PrintConfig.cpp:1408 +#: src/libslic3r/PrintConfig.cpp:1407 msgid "Combine infill every n layers" msgstr "Combineer vulling elke n lagen" -#: src/libslic3r/PrintConfig.cpp:1414 +#: src/libslic3r/PrintConfig.cpp:1413 msgid "Length of the infill anchor" msgstr "Lengte van de vullingsbevestiging" -#: src/libslic3r/PrintConfig.cpp:1416 +#: src/libslic3r/PrintConfig.cpp:1415 msgid "" "Connect an infill line to an internal perimeter with a short segment of an " "additional perimeter. If expressed as percentage (example: 15%) it is " @@ -12298,35 +12362,35 @@ msgstr "" "deze parameter, maar niet langer dan \"anchor_length_max\". Stel in op 0 om " "uit te zetten." -#: src/libslic3r/PrintConfig.cpp:1432 +#: src/libslic3r/PrintConfig.cpp:1431 msgid "0 (no open anchors)" msgstr "0 (geen losse bevestiging)" -#: src/libslic3r/PrintConfig.cpp:1433 src/libslic3r/PrintConfig.cpp:1456 +#: src/libslic3r/PrintConfig.cpp:1432 src/libslic3r/PrintConfig.cpp:1455 msgid "1 mm" msgstr "1 mm" -#: src/libslic3r/PrintConfig.cpp:1434 src/libslic3r/PrintConfig.cpp:1457 +#: src/libslic3r/PrintConfig.cpp:1433 src/libslic3r/PrintConfig.cpp:1456 msgid "2 mm" msgstr "2 mm" -#: src/libslic3r/PrintConfig.cpp:1435 src/libslic3r/PrintConfig.cpp:1458 +#: src/libslic3r/PrintConfig.cpp:1434 src/libslic3r/PrintConfig.cpp:1457 msgid "5 mm" msgstr "5 mm" -#: src/libslic3r/PrintConfig.cpp:1436 src/libslic3r/PrintConfig.cpp:1459 +#: src/libslic3r/PrintConfig.cpp:1435 src/libslic3r/PrintConfig.cpp:1458 msgid "10 mm" msgstr "10 mm" -#: src/libslic3r/PrintConfig.cpp:1437 src/libslic3r/PrintConfig.cpp:1460 +#: src/libslic3r/PrintConfig.cpp:1436 src/libslic3r/PrintConfig.cpp:1459 msgid "1000 (unlimited)" msgstr "1000 (ongelimiteerd)" -#: src/libslic3r/PrintConfig.cpp:1442 +#: src/libslic3r/PrintConfig.cpp:1441 msgid "Maximum length of the infill anchor" msgstr "Maximale lengte van de vullingsbevestiging" -#: src/libslic3r/PrintConfig.cpp:1444 +#: src/libslic3r/PrintConfig.cpp:1443 msgid "" "Connect an infill line to an internal perimeter with a short segment of an " "additional perimeter. If expressed as percentage (example: 15%) it is " @@ -12347,19 +12411,19 @@ msgstr "" "perimetersegment wordt gelimiteerd tot \"infill_anchor\", maar niet langer " "dan deze parameter. Stel in op 0 om uit te zetten." -#: src/libslic3r/PrintConfig.cpp:1455 +#: src/libslic3r/PrintConfig.cpp:1454 msgid "0 (not anchored)" msgstr "0 (niet bevestigd)" -#: src/libslic3r/PrintConfig.cpp:1465 +#: src/libslic3r/PrintConfig.cpp:1464 msgid "Infill extruder" msgstr "Vullingsextruder" -#: src/libslic3r/PrintConfig.cpp:1467 +#: src/libslic3r/PrintConfig.cpp:1466 msgid "The extruder to use when printing infill." msgstr "De extruder die gebruikt wordt voor het printen van de vulling." -#: src/libslic3r/PrintConfig.cpp:1475 +#: src/libslic3r/PrintConfig.cpp:1474 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill. If " "left zero, default extrusion width will be used if set, otherwise 1.125 x " @@ -12373,11 +12437,11 @@ msgstr "" "printen en het onderdeel sterker maken met deze optie. Als dit is uitgedrukt " "als percentage, wordt dit berekend over de laagdikte." -#: src/libslic3r/PrintConfig.cpp:1486 +#: src/libslic3r/PrintConfig.cpp:1485 msgid "Infill before perimeters" msgstr "Vulling vóór perimeters" -#: src/libslic3r/PrintConfig.cpp:1487 +#: src/libslic3r/PrintConfig.cpp:1486 msgid "" "This option will switch the print order of perimeters and infill, making the " "latter first." @@ -12385,11 +12449,11 @@ msgstr "" "Deze optie verandert de printvolgorde van perimeters en vulling; de " "laatstgenoemde eerst." -#: src/libslic3r/PrintConfig.cpp:1492 +#: src/libslic3r/PrintConfig.cpp:1491 msgid "Only infill where needed" msgstr "Alleen vulling waar nodig" -#: src/libslic3r/PrintConfig.cpp:1494 +#: src/libslic3r/PrintConfig.cpp:1493 msgid "" "This option will limit infill to the areas actually needed for supporting " "ceilings (it will act as internal support material). If enabled, slows down " @@ -12399,11 +12463,11 @@ msgstr "" "ondersteuning van bovenvlakken (het fungeert als inwendig support). Let op: " "deze optie vertraagt de G-code-generatie." -#: src/libslic3r/PrintConfig.cpp:1501 +#: src/libslic3r/PrintConfig.cpp:1500 msgid "Infill/perimeters overlap" msgstr "Overlapping van vulling/perimeters" -#: src/libslic3r/PrintConfig.cpp:1503 +#: src/libslic3r/PrintConfig.cpp:1502 msgid "" "This setting applies an additional overlap between infill and perimeters for " "better bonding. Theoretically this shouldn't be needed, but backlash might " @@ -12415,25 +12479,25 @@ msgstr "" "maar terugslag kan zorgen voor gaten. Als dit is uitgedrukt als percentage, " "wordt dit berekend over de extrusiebreedte van de perimeters." -#: src/libslic3r/PrintConfig.cpp:1514 +#: src/libslic3r/PrintConfig.cpp:1513 msgid "Speed for printing the internal fill. Set to zero for auto." msgstr "" "Printsnelheid voor vulling. Als dit ingesteld is op 0, wordt de snelheid " "automatisch berekend." -#: src/libslic3r/PrintConfig.cpp:1522 +#: src/libslic3r/PrintConfig.cpp:1521 msgid "Inherits profile" msgstr "Afgeleid profiel" -#: src/libslic3r/PrintConfig.cpp:1523 +#: src/libslic3r/PrintConfig.cpp:1522 msgid "Name of the profile, from which this profile inherits." msgstr "Profielnaam waar profiel op is gebaseerd." -#: src/libslic3r/PrintConfig.cpp:1536 +#: src/libslic3r/PrintConfig.cpp:1535 msgid "Interface shells" msgstr "Interfaceshells" -#: src/libslic3r/PrintConfig.cpp:1537 +#: src/libslic3r/PrintConfig.cpp:1536 msgid "" "Force the generation of solid shells between adjacent materials/volumes. " "Useful for multi-extruder prints with translucent materials or manual " @@ -12443,67 +12507,67 @@ msgstr "" "volumes. Dit is handig voor multi-extruderprints met transparante materialen " "of handmatig oplosbaar support." -#: src/libslic3r/PrintConfig.cpp:1545 +#: src/libslic3r/PrintConfig.cpp:1544 msgid "Maximum width of a segmented region" msgstr "Maximale breedte van een gesegmenteerd gebied" -#: src/libslic3r/PrintConfig.cpp:1546 +#: src/libslic3r/PrintConfig.cpp:1545 msgid "Maximum width of a segmented region. Zero disables this feature." msgstr "" "Maximale breedte van een gesegmenteerd gebied. Stel in op 0 om uit te " "schakelen." -#: src/libslic3r/PrintConfig.cpp:1547 src/libslic3r/PrintConfig.cpp:2158 -#: src/libslic3r/PrintConfig.cpp:2167 +#: src/libslic3r/PrintConfig.cpp:1546 src/libslic3r/PrintConfig.cpp:2157 +#: src/libslic3r/PrintConfig.cpp:2166 msgid "mm (zero to disable)" msgstr "mm (stel in op 0 om uit te schakelen)" -#: src/libslic3r/PrintConfig.cpp:1554 +#: src/libslic3r/PrintConfig.cpp:1553 msgid "Enable ironing" msgstr "Sta strijken toe" -#: src/libslic3r/PrintConfig.cpp:1555 +#: src/libslic3r/PrintConfig.cpp:1554 msgid "" "Enable ironing of the top layers with the hot print head for smooth surface" msgstr "" "Sta strijken van de toplagen toe met het hete hotend voor een gladder " "oppervlak" -#: src/libslic3r/PrintConfig.cpp:1561 src/libslic3r/PrintConfig.cpp:1563 +#: src/libslic3r/PrintConfig.cpp:1560 src/libslic3r/PrintConfig.cpp:1562 msgid "Ironing Type" msgstr "Strijktype" -#: src/libslic3r/PrintConfig.cpp:1568 +#: src/libslic3r/PrintConfig.cpp:1567 msgid "All top surfaces" msgstr "Alle bovenvlakken" -#: src/libslic3r/PrintConfig.cpp:1569 +#: src/libslic3r/PrintConfig.cpp:1568 msgid "Topmost surface only" msgstr "Alleen bovenste vlak" -#: src/libslic3r/PrintConfig.cpp:1570 +#: src/libslic3r/PrintConfig.cpp:1569 msgid "All solid surfaces" msgstr "Alle dichte vlakken" -#: src/libslic3r/PrintConfig.cpp:1575 +#: src/libslic3r/PrintConfig.cpp:1574 msgid "Flow rate" msgstr "Debiet" -#: src/libslic3r/PrintConfig.cpp:1577 +#: src/libslic3r/PrintConfig.cpp:1576 msgid "Percent of a flow rate relative to object's normal layer height." msgstr "" "Percentage van het debiet ten opzichte van de standaard laagdikte van het " "model." -#: src/libslic3r/PrintConfig.cpp:1585 +#: src/libslic3r/PrintConfig.cpp:1584 msgid "Spacing between ironing passes" msgstr "Ruimte tussen strijkpassages" -#: src/libslic3r/PrintConfig.cpp:1587 +#: src/libslic3r/PrintConfig.cpp:1586 msgid "Distance between ironing lines" msgstr "Afstand tussen strijkpaden" -#: src/libslic3r/PrintConfig.cpp:1604 +#: src/libslic3r/PrintConfig.cpp:1603 msgid "" "This custom code is inserted at every layer change, right after the Z move " "and before the extruder moves to the first layer point. Note that you can " @@ -12514,11 +12578,11 @@ msgstr "" "beweging en voor de extruder naar het volgende punt beweegt. Hier kunt u " "variabelen gebruiken voor alle instellingen zoals 'layer_num' en 'layer_z'." -#: src/libslic3r/PrintConfig.cpp:1615 +#: src/libslic3r/PrintConfig.cpp:1614 msgid "Supports remaining times" msgstr "Ondersteunt resterende tijd" -#: src/libslic3r/PrintConfig.cpp:1616 +#: src/libslic3r/PrintConfig.cpp:1615 msgid "" "Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute " "intervals into the G-code to let the firmware show accurate remaining time. " @@ -12530,155 +12594,155 @@ msgstr "" "nu herkent de Prusa i3 MK3 de M73-commando's. Ook ondersteunt de i3 MK3 " "firmware M73 Qxx Sxx voor de stille modus." -#: src/libslic3r/PrintConfig.cpp:1624 +#: src/libslic3r/PrintConfig.cpp:1623 msgid "Supports stealth mode" msgstr "Ondersteunt stille modus" -#: src/libslic3r/PrintConfig.cpp:1625 +#: src/libslic3r/PrintConfig.cpp:1624 msgid "The firmware supports stealth mode" msgstr "De firmware ondersteunt stille modus" -#: src/libslic3r/PrintConfig.cpp:1630 +#: src/libslic3r/PrintConfig.cpp:1629 msgid "How to apply limits" msgstr "Hoe limieten toe te voegen" -#: src/libslic3r/PrintConfig.cpp:1631 +#: src/libslic3r/PrintConfig.cpp:1630 msgid "Purpose of Machine Limits" msgstr "Doel van de machinelimieten" -#: src/libslic3r/PrintConfig.cpp:1633 +#: src/libslic3r/PrintConfig.cpp:1632 msgid "How to apply the Machine Limits" msgstr "Hoe machinelimieten toe te voegen" -#: src/libslic3r/PrintConfig.cpp:1638 +#: src/libslic3r/PrintConfig.cpp:1637 msgid "Emit to G-code" msgstr "Invoegen in de G-code" -#: src/libslic3r/PrintConfig.cpp:1639 +#: src/libslic3r/PrintConfig.cpp:1638 msgid "Use for time estimate" msgstr "Gebruik om tijd te schatten" -#: src/libslic3r/PrintConfig.cpp:1640 +#: src/libslic3r/PrintConfig.cpp:1639 msgid "Ignore" msgstr "Negeren" -#: src/libslic3r/PrintConfig.cpp:1663 +#: src/libslic3r/PrintConfig.cpp:1662 msgid "Maximum feedrate X" msgstr "Maximale snelheid van de X-as" -#: src/libslic3r/PrintConfig.cpp:1664 +#: src/libslic3r/PrintConfig.cpp:1663 msgid "Maximum feedrate Y" msgstr "Maximale snelheid van de Y-as" -#: src/libslic3r/PrintConfig.cpp:1665 +#: src/libslic3r/PrintConfig.cpp:1664 msgid "Maximum feedrate Z" msgstr "Maximale snelheid van de Z-as" -#: src/libslic3r/PrintConfig.cpp:1666 +#: src/libslic3r/PrintConfig.cpp:1665 msgid "Maximum feedrate E" msgstr "Maximale extrusiesnelheid" -#: src/libslic3r/PrintConfig.cpp:1669 +#: src/libslic3r/PrintConfig.cpp:1668 msgid "Maximum feedrate of the X axis" msgstr "Maximale snelheid van de X-as" -#: src/libslic3r/PrintConfig.cpp:1670 +#: src/libslic3r/PrintConfig.cpp:1669 msgid "Maximum feedrate of the Y axis" msgstr "Maximale snelheid van de Y-as" -#: src/libslic3r/PrintConfig.cpp:1671 +#: src/libslic3r/PrintConfig.cpp:1670 msgid "Maximum feedrate of the Z axis" msgstr "Maximale snelheid van de Z-as" -#: src/libslic3r/PrintConfig.cpp:1672 +#: src/libslic3r/PrintConfig.cpp:1671 msgid "Maximum feedrate of the E axis" msgstr "Maximale extrusiesnelheid" -#: src/libslic3r/PrintConfig.cpp:1680 +#: src/libslic3r/PrintConfig.cpp:1679 msgid "Maximum acceleration X" msgstr "Maximale acceleratie X" -#: src/libslic3r/PrintConfig.cpp:1681 +#: src/libslic3r/PrintConfig.cpp:1680 msgid "Maximum acceleration Y" msgstr "Maximale acceleratie Y" -#: src/libslic3r/PrintConfig.cpp:1682 +#: src/libslic3r/PrintConfig.cpp:1681 msgid "Maximum acceleration Z" msgstr "Maximale acceleratie Z" -#: src/libslic3r/PrintConfig.cpp:1683 +#: src/libslic3r/PrintConfig.cpp:1682 msgid "Maximum acceleration E" msgstr "Maximale acceleratie E" -#: src/libslic3r/PrintConfig.cpp:1686 +#: src/libslic3r/PrintConfig.cpp:1685 msgid "Maximum acceleration of the X axis" msgstr "Maximale acceleratie van de X-as" -#: src/libslic3r/PrintConfig.cpp:1687 +#: src/libslic3r/PrintConfig.cpp:1686 msgid "Maximum acceleration of the Y axis" msgstr "Maximale acceleratie van de Y-as" -#: src/libslic3r/PrintConfig.cpp:1688 +#: src/libslic3r/PrintConfig.cpp:1687 msgid "Maximum acceleration of the Z axis" msgstr "Maximale acceleratie van de Z-as" -#: src/libslic3r/PrintConfig.cpp:1689 +#: src/libslic3r/PrintConfig.cpp:1688 msgid "Maximum acceleration of the E axis" msgstr "Maximale extrusie-acceleratie" -#: src/libslic3r/PrintConfig.cpp:1697 +#: src/libslic3r/PrintConfig.cpp:1696 msgid "Maximum jerk X" msgstr "Maximale ruk X" -#: src/libslic3r/PrintConfig.cpp:1698 +#: src/libslic3r/PrintConfig.cpp:1697 msgid "Maximum jerk Y" msgstr "Maximale ruk Y" -#: src/libslic3r/PrintConfig.cpp:1699 +#: src/libslic3r/PrintConfig.cpp:1698 msgid "Maximum jerk Z" msgstr "Maximale ruk Z" -#: src/libslic3r/PrintConfig.cpp:1700 +#: src/libslic3r/PrintConfig.cpp:1699 msgid "Maximum jerk E" msgstr "Maximale ruk E" -#: src/libslic3r/PrintConfig.cpp:1703 +#: src/libslic3r/PrintConfig.cpp:1702 msgid "Maximum jerk of the X axis" msgstr "Maximale ruk van de X-as" -#: src/libslic3r/PrintConfig.cpp:1704 +#: src/libslic3r/PrintConfig.cpp:1703 msgid "Maximum jerk of the Y axis" msgstr "Maximale ruk van de Y-as" -#: src/libslic3r/PrintConfig.cpp:1705 +#: src/libslic3r/PrintConfig.cpp:1704 msgid "Maximum jerk of the Z axis" msgstr "Maximale ruk van de Z-as" -#: src/libslic3r/PrintConfig.cpp:1706 +#: src/libslic3r/PrintConfig.cpp:1705 msgid "Maximum jerk of the E axis" msgstr "Maximale extrusie-ruk" -#: src/libslic3r/PrintConfig.cpp:1716 +#: src/libslic3r/PrintConfig.cpp:1715 msgid "Minimum feedrate when extruding" msgstr "Minimale snelheid tijdens extruderen" -#: src/libslic3r/PrintConfig.cpp:1718 +#: src/libslic3r/PrintConfig.cpp:1717 msgid "Minimum feedrate when extruding (M205 S)" msgstr "Minimale snelheid tijdens extruderen (M205 S)" -#: src/libslic3r/PrintConfig.cpp:1726 +#: src/libslic3r/PrintConfig.cpp:1725 msgid "Minimum travel feedrate" msgstr "Minimale snelheid voor bewegingen" -#: src/libslic3r/PrintConfig.cpp:1728 +#: src/libslic3r/PrintConfig.cpp:1727 msgid "Minimum travel feedrate (M205 T)" msgstr "Minimale snelheid voor bewegingen (M205 T)" -#: src/libslic3r/PrintConfig.cpp:1736 +#: src/libslic3r/PrintConfig.cpp:1735 msgid "Maximum acceleration when extruding" msgstr "Maximale acceleratie tijdens extruderen" -#: src/libslic3r/PrintConfig.cpp:1738 +#: src/libslic3r/PrintConfig.cpp:1737 msgid "" "Maximum acceleration when extruding (M204 P)\n" "\n" @@ -12690,31 +12754,31 @@ msgstr "" "Marlin (legacy) firmware gebruikt deze ook voor bewegingsacceleratie (M204 " "T)." -#: src/libslic3r/PrintConfig.cpp:1749 +#: src/libslic3r/PrintConfig.cpp:1748 msgid "Maximum acceleration when retracting" msgstr "Maximale acceleratie tijdens retracten" -#: src/libslic3r/PrintConfig.cpp:1751 +#: src/libslic3r/PrintConfig.cpp:1750 msgid "Maximum acceleration when retracting (M204 R)" msgstr "Maximale acceleratie tijdens retracten (M204 R)" -#: src/libslic3r/PrintConfig.cpp:1759 +#: src/libslic3r/PrintConfig.cpp:1758 msgid "Maximum acceleration for travel moves" msgstr "Maximale acceleratie voor bewegingen" -#: src/libslic3r/PrintConfig.cpp:1761 +#: src/libslic3r/PrintConfig.cpp:1760 msgid "Maximum acceleration for travel moves (M204 T)" msgstr "Maximale acceleratie voor bewegingen (M204 T)" -#: src/libslic3r/PrintConfig.cpp:1768 src/libslic3r/PrintConfig.cpp:1777 +#: src/libslic3r/PrintConfig.cpp:1767 src/libslic3r/PrintConfig.cpp:1776 msgid "Max" msgstr "Max" -#: src/libslic3r/PrintConfig.cpp:1769 +#: src/libslic3r/PrintConfig.cpp:1768 msgid "This setting represents the maximum speed of your fan." msgstr "Deze instelling gaat over de maximale snelheid van uw ventilator." -#: src/libslic3r/PrintConfig.cpp:1778 +#: src/libslic3r/PrintConfig.cpp:1777 msgid "" "This is the highest printable layer height for this extruder, used to cap " "the variable layer height and support layer height. Maximum recommended " @@ -12726,11 +12790,11 @@ msgstr "" "75% van de extrusiebreedte voor een goede interfacehechting. Als dit op 0 " "staat, wordt de hoogte gelimiteerd tot 75% van de nozzlediameter." -#: src/libslic3r/PrintConfig.cpp:1788 +#: src/libslic3r/PrintConfig.cpp:1787 msgid "Max print speed" msgstr "Maximale printsnelheid" -#: src/libslic3r/PrintConfig.cpp:1789 +#: src/libslic3r/PrintConfig.cpp:1788 msgid "" "When setting other speed settings to 0 Slic3r will autocalculate the optimal " "speed in order to keep constant extruder pressure. This experimental setting " @@ -12741,7 +12805,7 @@ msgstr "" "experimentele instelling wordt gebruikt voor de hoogste printsnelheid die u " "toestaat." -#: src/libslic3r/PrintConfig.cpp:1799 +#: src/libslic3r/PrintConfig.cpp:1798 msgid "" "This experimental setting is used to set the maximum volumetric speed your " "extruder supports." @@ -12749,11 +12813,11 @@ msgstr "" "Deze experimentele instelling wordt gebruikt voor de maximale volumetrische " "snelheid van de extruder." -#: src/libslic3r/PrintConfig.cpp:1807 +#: src/libslic3r/PrintConfig.cpp:1806 msgid "Max volumetric slope positive" msgstr "Maximale volumetrische stijging" -#: src/libslic3r/PrintConfig.cpp:1808 +#: src/libslic3r/PrintConfig.cpp:1807 msgid "" "This experimental setting is used to limit the speed of change in extrusion " "rate for a transition from lower speed to higher speed. A value of 1.8 mm³/" @@ -12761,16 +12825,21 @@ msgid "" "extrusion width, 0.2 mm extrusion height, feedrate 20 mm/s) to 5.4 mm³/s " "(feedrate 60 mm/s) will take at least 2 seconds." msgstr "" +"Deze experimentele instelling wordt gebruikt om de extrusiesnelheidsratio te " +"limiteren voor een transitie van lage tot hoge snelheid. Een waarde van 1,8 " +"mm³/s² verzekert dat een wijziging in extrusieratio van 1,8 mm³/s (0,45 mm " +"extrusiebreedte, 0,2 mm laagdikte, voedingssnelheid 20 mm/s) naar 5,4 mm³/s " +"(voedingssnelheid 60 mm/s) tenminste 2 seconden duurt." -#: src/libslic3r/PrintConfig.cpp:1813 src/libslic3r/PrintConfig.cpp:1825 +#: src/libslic3r/PrintConfig.cpp:1812 src/libslic3r/PrintConfig.cpp:1824 msgid "mm³/s²" msgstr "mm³/s²" -#: src/libslic3r/PrintConfig.cpp:1819 +#: src/libslic3r/PrintConfig.cpp:1818 msgid "Max volumetric slope negative" msgstr "Maximale volumetrische daling" -#: src/libslic3r/PrintConfig.cpp:1820 +#: src/libslic3r/PrintConfig.cpp:1819 msgid "" "This experimental setting is used to limit the speed of change in extrusion " "rate for a transition from higher speed to lower speed. A value of 1.8 mm³/" @@ -12778,18 +12847,23 @@ msgid "" "extrusion width, 0.2 mm extrusion height, feedrate 60 mm/s) to 1.8 mm³/s " "(feedrate 20 mm/s) will take at least 2 seconds." msgstr "" +"Deze experimentele instelling wordt gebruikt om de extrusiesnelheidsratio te " +"limiteren voor een transitie van hoge tot lage snelheid. Een waarde van 1,8 " +"mm³/s² verzekert dat een wijziging in extrusieratio van 5,4 mm³/s (0,45 mm " +"extrusiebreedte, 0,2 mm laagdikte, voedingssnelheid 60 mm/s) naar 1,8 mm³/s " +"(voedingssnelheid 20 mm/s) tenminste 2 seconden duurt." -#: src/libslic3r/PrintConfig.cpp:1831 src/libslic3r/PrintConfig.cpp:1840 +#: src/libslic3r/PrintConfig.cpp:1830 src/libslic3r/PrintConfig.cpp:1839 msgid "Min" msgstr "Min" -#: src/libslic3r/PrintConfig.cpp:1832 +#: src/libslic3r/PrintConfig.cpp:1831 msgid "This setting represents the minimum PWM your fan needs to work." msgstr "" "Deze instelling geeft de minimale snelheid van uw ventilator aan waarbij de " "ventilator draait." -#: src/libslic3r/PrintConfig.cpp:1841 +#: src/libslic3r/PrintConfig.cpp:1840 msgid "" "This is the lowest printable layer height for this extruder and limits the " "resolution for variable layer height. Typical values are between 0.05 mm and " @@ -12799,20 +12873,20 @@ msgstr "" "resolutie voor variabele laagdikte. Typische waarden zijn tussen 0,05 en 0,1 " "mm." -#: src/libslic3r/PrintConfig.cpp:1849 +#: src/libslic3r/PrintConfig.cpp:1848 msgid "Min print speed" msgstr "Minimale printsnelheid" -#: src/libslic3r/PrintConfig.cpp:1850 +#: src/libslic3r/PrintConfig.cpp:1849 msgid "Slic3r will not scale speed down below this speed." msgstr "" "PrusaSlicer zal de printsnelheid niet verlagen tot onder deze snelheid." -#: src/libslic3r/PrintConfig.cpp:1857 +#: src/libslic3r/PrintConfig.cpp:1856 msgid "Minimal filament extrusion length" msgstr "Minimale extrusielengte" -#: src/libslic3r/PrintConfig.cpp:1858 +#: src/libslic3r/PrintConfig.cpp:1857 msgid "" "Generate no less than the number of skirt loops required to consume the " "specified amount of filament on the bottom layer. For multi-extruder " @@ -12822,11 +12896,11 @@ msgstr "" "hoeveelheid filament op de eerste laag te verbruiken. Voor multi-" "extruderprinters is dit het minimum voor elke extruder." -#: src/libslic3r/PrintConfig.cpp:1867 +#: src/libslic3r/PrintConfig.cpp:1866 msgid "Configuration notes" msgstr "Configuratie-opmerkingen" -#: src/libslic3r/PrintConfig.cpp:1868 +#: src/libslic3r/PrintConfig.cpp:1867 msgid "" "You can put here your personal notes. This text will be added to the G-code " "header comments." @@ -12834,28 +12908,28 @@ msgstr "" "Hier kunt u eigen opmerkingen plaatsen. Deze tekst wordt bovenin de G-code " "toegevoegd." -#: src/libslic3r/PrintConfig.cpp:1878 +#: src/libslic3r/PrintConfig.cpp:1877 msgid "" "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "Dit is de diameter van uw extruder-nozzle (bijvoorbeeld 0.4)" -#: src/libslic3r/PrintConfig.cpp:1883 +#: src/libslic3r/PrintConfig.cpp:1882 msgid "Host Type" msgstr "Hosttype" -#: src/libslic3r/PrintConfig.cpp:1884 +#: src/libslic3r/PrintConfig.cpp:1883 msgid "" "Slic3r can upload G-code files to a printer host. This field must contain " "the kind of the host." msgstr "" -"PrusaSlicer kan gcode-bestanden uploaden naar een printerhost. Dit veld moet " -"het type host bevatten." +"PrusaSlicer kan .gcode-bestanden uploaden naar een printerhost. Dit veld " +"moet het type host bevatten." -#: src/libslic3r/PrintConfig.cpp:1906 +#: src/libslic3r/PrintConfig.cpp:1905 msgid "Only retract when crossing perimeters" msgstr "Alleen retracten bij kruisende perimeters" -#: src/libslic3r/PrintConfig.cpp:1907 +#: src/libslic3r/PrintConfig.cpp:1906 msgid "" "Disables retraction when the travel path does not exceed the upper layer's " "perimeters (and thus any ooze will be probably invisible)." @@ -12863,7 +12937,7 @@ msgstr "" "Schakelt retracten uit als de bewegingspaden de perimeters van de bovenste " "laag niet overschrijdt (en maakt eventueel druipen dus onzichtbaar)." -#: src/libslic3r/PrintConfig.cpp:1914 +#: src/libslic3r/PrintConfig.cpp:1913 msgid "" "This option will drop the temperature of the inactive extruders to prevent " "oozing. It will enable a tall skirt automatically and move extruders outside " @@ -12873,11 +12947,11 @@ msgstr "" "voorkomen. Het staat een smalle skirt automatisch toe en beweegt extruders " "buiten zo'n skirt als de temperatuur verandert." -#: src/libslic3r/PrintConfig.cpp:1921 +#: src/libslic3r/PrintConfig.cpp:1920 msgid "Output filename format" msgstr "Formaat van bestandsnaam" -#: src/libslic3r/PrintConfig.cpp:1922 +#: src/libslic3r/PrintConfig.cpp:1921 msgid "" "You can use all configuration options as variables inside this template. For " "example: [layer_height], [fill_density] etc. You can also use [timestamp], " @@ -12889,11 +12963,11 @@ msgstr "" "'year', 'month', 'day', 'hour', 'minute', 'second', 'version', " "'input_filename', 'input_filename_base', etc." -#: src/libslic3r/PrintConfig.cpp:1931 +#: src/libslic3r/PrintConfig.cpp:1930 msgid "Detect bridging perimeters" msgstr "Detecteer brugperimeters" -#: src/libslic3r/PrintConfig.cpp:1933 +#: src/libslic3r/PrintConfig.cpp:1932 msgid "" "Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan." @@ -12901,11 +12975,11 @@ msgstr "" "Experimentele optie om het debiet voor overhanging aan te passen. Het debiet " "voor bruggen wordt aangehouden, evenals de printsnelheid en de koeling." -#: src/libslic3r/PrintConfig.cpp:1939 +#: src/libslic3r/PrintConfig.cpp:1938 msgid "Filament parking position" msgstr "Filament parkeerpositie" -#: src/libslic3r/PrintConfig.cpp:1940 +#: src/libslic3r/PrintConfig.cpp:1939 msgid "" "Distance of the extruder tip from the position where the filament is parked " "when unloaded. This should match the value in printer firmware." @@ -12914,11 +12988,11 @@ msgstr "" "wanneer dat niet geladen is. Deze moet overeenkomen met de waarde in de " "firmware." -#: src/libslic3r/PrintConfig.cpp:1948 +#: src/libslic3r/PrintConfig.cpp:1947 msgid "Extra loading distance" msgstr "Extra laadafstand" -#: src/libslic3r/PrintConfig.cpp:1949 +#: src/libslic3r/PrintConfig.cpp:1948 msgid "" "When set to zero, the distance the filament is moved from parking position " "during load is exactly the same as it was moved back during unload. When " @@ -12930,12 +13004,12 @@ msgstr "" "teruggetrokken wordt. Als de waarde positief is, zal het verder geladen " "worden. Als het negatief is, is de laadafstand dus korter." -#: src/libslic3r/PrintConfig.cpp:1957 src/libslic3r/PrintConfig.cpp:1974 -#: src/libslic3r/PrintConfig.cpp:1988 src/libslic3r/PrintConfig.cpp:1998 +#: src/libslic3r/PrintConfig.cpp:1956 src/libslic3r/PrintConfig.cpp:1973 +#: src/libslic3r/PrintConfig.cpp:1987 src/libslic3r/PrintConfig.cpp:1997 msgid "Perimeters" msgstr "Perimeters" -#: src/libslic3r/PrintConfig.cpp:1958 +#: src/libslic3r/PrintConfig.cpp:1957 msgid "" "This is the acceleration your printer will use for perimeters. Set zero to " "disable acceleration control for perimeters." @@ -12943,17 +13017,17 @@ msgstr "" "Deze acceleratie zal uw printer gebruiken voor de perimeters. Als dit is " "ingesteld op 0, wordt de acceleratiecontrole uitgeschakeld." -#: src/libslic3r/PrintConfig.cpp:1965 +#: src/libslic3r/PrintConfig.cpp:1964 msgid "Perimeter extruder" msgstr "Perimeterextruder" -#: src/libslic3r/PrintConfig.cpp:1967 +#: src/libslic3r/PrintConfig.cpp:1966 msgid "" "The extruder to use when printing perimeters and brim. First extruder is 1." msgstr "" "De extruder die gebruikt wordt voor het printen van perimeters en de brim." -#: src/libslic3r/PrintConfig.cpp:1976 +#: src/libslic3r/PrintConfig.cpp:1975 msgid "" "Set this to a non-zero value to set a manual extrusion width for perimeters. " "You may want to use thinner extrudates to get more accurate surfaces. If " @@ -12967,14 +13041,14 @@ msgstr "" "nozzlediameter. Als dit is uitgedrukt als percentage, wordt dit berekend " "over de laagdikte." -#: src/libslic3r/PrintConfig.cpp:1990 +#: src/libslic3r/PrintConfig.cpp:1989 msgid "" "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." msgstr "" "Printsnelheid voor de perimeters (contouren, ook wel bekend als verticale " "shells). Als dit ingesteld is op 0, wordt een automatische snelheid genomen." -#: src/libslic3r/PrintConfig.cpp:2000 +#: src/libslic3r/PrintConfig.cpp:1999 msgid "" "This option sets the number of perimeters to generate for each layer. Note " "that Slic3r may increase this number automatically when it detects sloping " @@ -12986,11 +13060,11 @@ msgstr "" "een hoger aantal perimeters als de optie voor extra perimeters is " "ingeschakeld." -#: src/libslic3r/PrintConfig.cpp:2004 +#: src/libslic3r/PrintConfig.cpp:2003 msgid "(minimum)" msgstr "(minimum)" -#: src/libslic3r/PrintConfig.cpp:2012 +#: src/libslic3r/PrintConfig.cpp:2011 msgid "" "If you want to process the output G-code through custom scripts, just list " "their absolute paths here. Separate multiple scripts with a semicolon. " @@ -13000,38 +13074,38 @@ msgid "" msgstr "" "Als u de output-G-code via custom scripts wilt verwerken, hoeft u alleen de " "paden hier te plaatsen. Scheid meerdere scripts met een puntkomma. Scripts " -"krijgen als eerste argument het pad naar het gcode-bestand. Ze hebben ook " +"krijgen als eerste argument het pad naar het .gcode-bestand. Ze hebben ook " "toegang tot de configuratie-instellingen door het lezen van variabelen." -#: src/libslic3r/PrintConfig.cpp:2024 +#: src/libslic3r/PrintConfig.cpp:2023 msgid "Printer type" msgstr "Printertype" -#: src/libslic3r/PrintConfig.cpp:2025 +#: src/libslic3r/PrintConfig.cpp:2024 msgid "Type of the printer." msgstr "Type van de printer." -#: src/libslic3r/PrintConfig.cpp:2030 +#: src/libslic3r/PrintConfig.cpp:2029 msgid "Printer notes" msgstr "Printeropmerkingen" -#: src/libslic3r/PrintConfig.cpp:2031 +#: src/libslic3r/PrintConfig.cpp:2030 msgid "You can put your notes regarding the printer here." msgstr "Hier kunnen opmerkingen over de printer geplaatst worden." -#: src/libslic3r/PrintConfig.cpp:2039 +#: src/libslic3r/PrintConfig.cpp:2038 msgid "Printer vendor" msgstr "Printerleverancier" -#: src/libslic3r/PrintConfig.cpp:2040 +#: src/libslic3r/PrintConfig.cpp:2039 msgid "Name of the printer vendor." msgstr "Naam van de printerleverancier." -#: src/libslic3r/PrintConfig.cpp:2045 +#: src/libslic3r/PrintConfig.cpp:2044 msgid "Printer variant" msgstr "Printervariant" -#: src/libslic3r/PrintConfig.cpp:2046 +#: src/libslic3r/PrintConfig.cpp:2045 msgid "" "Name of the printer variant. For example, the printer variants may be " "differentiated by a nozzle diameter." @@ -13039,38 +13113,38 @@ msgstr "" "Naam van de printervariant. De nozzlediameter kan bijvoorbeeld afwijken voor " "verschillende varianten." -#: src/libslic3r/PrintConfig.cpp:2063 +#: src/libslic3r/PrintConfig.cpp:2062 msgid "Raft contact Z distance" msgstr "Z-afstand voor raft" -#: src/libslic3r/PrintConfig.cpp:2065 +#: src/libslic3r/PrintConfig.cpp:2064 msgid "" "The vertical distance between object and raft. Ignored for soluble interface." msgstr "" "De verticale afstand tussen object en raft. Wordt genegeerd bij oplosbare " "interface." -#: src/libslic3r/PrintConfig.cpp:2072 +#: src/libslic3r/PrintConfig.cpp:2071 msgid "Raft expansion" msgstr "Raftuitbreiding" -#: src/libslic3r/PrintConfig.cpp:2074 +#: src/libslic3r/PrintConfig.cpp:2073 msgid "Expansion of the raft in XY plane for better stability." msgstr "Uitbreiding van de raft in het XY-vlak voor betere stabiliteit." -#: src/libslic3r/PrintConfig.cpp:2081 +#: src/libslic3r/PrintConfig.cpp:2080 msgid "First layer density" msgstr "Dichtheid eerste laag" -#: src/libslic3r/PrintConfig.cpp:2083 +#: src/libslic3r/PrintConfig.cpp:2082 msgid "Density of the first raft or support layer." msgstr "Dichtheid van de eerste raft- of supportlaag." -#: src/libslic3r/PrintConfig.cpp:2091 +#: src/libslic3r/PrintConfig.cpp:2090 msgid "First layer expansion" msgstr "Uitbreiding van eerste laag" -#: src/libslic3r/PrintConfig.cpp:2093 +#: src/libslic3r/PrintConfig.cpp:2092 msgid "" "Expansion of the first raft or support layer to improve adhesion to print " "bed." @@ -13078,11 +13152,11 @@ msgstr "" "Uitbreiding van de eerste raft- of supportlaag voor verbetering van de " "bedhechting." -#: src/libslic3r/PrintConfig.cpp:2100 +#: src/libslic3r/PrintConfig.cpp:2099 msgid "Raft layers" msgstr "Raftlagen" -#: src/libslic3r/PrintConfig.cpp:2102 +#: src/libslic3r/PrintConfig.cpp:2101 msgid "" "The object will be raised by this number of layers, and support material " "will be generated under it." @@ -13090,11 +13164,11 @@ msgstr "" "Het object wordt verhoogd met dit aantal lagen. Support wordt onder het " "object gegenereerd." -#: src/libslic3r/PrintConfig.cpp:2110 +#: src/libslic3r/PrintConfig.cpp:2109 msgid "Slice resolution" msgstr "Slice-resolutie" -#: src/libslic3r/PrintConfig.cpp:2111 +#: src/libslic3r/PrintConfig.cpp:2110 msgid "" "Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often " @@ -13106,11 +13180,11 @@ msgstr "" "een hoge resolutie vragen meer van een printer dan mogelijk. Als dit " "ingesteld is op 0, wordt simplificatie uitgeschakeld." -#: src/libslic3r/PrintConfig.cpp:2121 +#: src/libslic3r/PrintConfig.cpp:2120 msgid "G-code resolution" msgstr "G-code-resolutie" -#: src/libslic3r/PrintConfig.cpp:2122 +#: src/libslic3r/PrintConfig.cpp:2121 msgid "" "Maximum deviation of exported G-code paths from their full resolution " "counterparts. Very high resolution G-code requires huge amount of RAM to " @@ -13128,22 +13202,22 @@ msgstr "" "reductie is toegepast per laag, kan dit zorgen voor oneffenheden tijdens het " "printen." -#: src/libslic3r/PrintConfig.cpp:2133 +#: src/libslic3r/PrintConfig.cpp:2132 msgid "Minimum travel after retraction" msgstr "Minimale beweging na retracten" -#: src/libslic3r/PrintConfig.cpp:2134 +#: src/libslic3r/PrintConfig.cpp:2133 msgid "" "Retraction is not triggered when travel moves are shorter than this length." msgstr "" "Retracten is niet geactiveerd als bewegingen korter zijn dan de hier " "ingevoerde lengte." -#: src/libslic3r/PrintConfig.cpp:2140 +#: src/libslic3r/PrintConfig.cpp:2139 msgid "Retract amount before wipe" msgstr "Retracthoeveelheid voor het afvegen" -#: src/libslic3r/PrintConfig.cpp:2141 +#: src/libslic3r/PrintConfig.cpp:2140 msgid "" "With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement." @@ -13151,23 +13225,23 @@ msgstr "" "Met bowden-extruders is het verstandig om een aantal maal snel te retracten " "voor het afvegen." -#: src/libslic3r/PrintConfig.cpp:2148 +#: src/libslic3r/PrintConfig.cpp:2147 msgid "Retract on layer change" msgstr "Retracten bij laagwisselingen" -#: src/libslic3r/PrintConfig.cpp:2149 +#: src/libslic3r/PrintConfig.cpp:2148 msgid "This flag enforces a retraction whenever a Z move is done." msgstr "Dit vinkje geeft aan of wordt teruggetrokken bij een Z-beweging." -#: src/libslic3r/PrintConfig.cpp:2154 src/libslic3r/PrintConfig.cpp:2162 +#: src/libslic3r/PrintConfig.cpp:2153 src/libslic3r/PrintConfig.cpp:2161 msgid "Length" msgstr "Lengte" -#: src/libslic3r/PrintConfig.cpp:2155 +#: src/libslic3r/PrintConfig.cpp:2154 msgid "Retraction Length" msgstr "Retractielengte" -#: src/libslic3r/PrintConfig.cpp:2156 +#: src/libslic3r/PrintConfig.cpp:2155 msgid "" "When retraction is triggered, filament is pulled back by the specified " "amount (the length is measured on raw filament, before it enters the " @@ -13176,11 +13250,11 @@ msgstr "" "Als retracten is geactiveerd, wordt filament teruggetrokken op de ingestelde " "waarde (filamentlengte voor het de extruder in gaat)." -#: src/libslic3r/PrintConfig.cpp:2163 +#: src/libslic3r/PrintConfig.cpp:2162 msgid "Retraction Length (Toolchange)" msgstr "Retractielengte (toolwissel)" -#: src/libslic3r/PrintConfig.cpp:2164 +#: src/libslic3r/PrintConfig.cpp:2163 msgid "" "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it " @@ -13190,11 +13264,11 @@ msgstr "" "teruggetrokken op de ingestelde waarde (filamentlengte voor het de extruder " "in gaat)." -#: src/libslic3r/PrintConfig.cpp:2172 +#: src/libslic3r/PrintConfig.cpp:2171 msgid "Lift Z" msgstr "Beweeg Z omhoog" -#: src/libslic3r/PrintConfig.cpp:2173 +#: src/libslic3r/PrintConfig.cpp:2172 msgid "" "If you set this to a positive value, Z is quickly raised every time a " "retraction is triggered. When using multiple extruders, only the setting for " @@ -13204,15 +13278,15 @@ msgstr "" "enigszins omhoog bij het retracten. Als meerdere extruders worden gebruikt, " "wordt alleen de instelling van de eerste extruder aangehouden." -#: src/libslic3r/PrintConfig.cpp:2180 +#: src/libslic3r/PrintConfig.cpp:2179 msgid "Above Z" msgstr "Boven Z" -#: src/libslic3r/PrintConfig.cpp:2181 +#: src/libslic3r/PrintConfig.cpp:2180 msgid "Only lift Z above" msgstr "Beweeg Z alleen omhoog boven" -#: src/libslic3r/PrintConfig.cpp:2182 +#: src/libslic3r/PrintConfig.cpp:2181 msgid "" "If you set this to a positive value, Z lift will only take place above the " "specified absolute Z. You can tune this setting for skipping lift on the " @@ -13222,15 +13296,15 @@ msgstr "" "ingestelde waarde omhoog bewegen voor het retracten. Deze kan aangepast " "worden om warping te voorkomen bij de eerste lagen." -#: src/libslic3r/PrintConfig.cpp:2189 +#: src/libslic3r/PrintConfig.cpp:2188 msgid "Below Z" msgstr "Onder Z" -#: src/libslic3r/PrintConfig.cpp:2190 +#: src/libslic3r/PrintConfig.cpp:2189 msgid "Only lift Z below" msgstr "Beweeg Z alleen omhoog onder" -#: src/libslic3r/PrintConfig.cpp:2191 +#: src/libslic3r/PrintConfig.cpp:2190 msgid "" "If you set this to a positive value, Z lift will only take place below the " "specified absolute Z. You can tune this setting for limiting lift to the " @@ -13239,11 +13313,11 @@ msgstr "" "Als dit ingesteld is op een positieve waarde, zal de nozzle alleen onder de " "ingestelde waarde omhoog bewegen bij het retracten." -#: src/libslic3r/PrintConfig.cpp:2199 src/libslic3r/PrintConfig.cpp:2207 +#: src/libslic3r/PrintConfig.cpp:2198 src/libslic3r/PrintConfig.cpp:2206 msgid "Extra length on restart" msgstr "Extra lengte bij herstart" -#: src/libslic3r/PrintConfig.cpp:2200 +#: src/libslic3r/PrintConfig.cpp:2199 msgid "" "When the retraction is compensated after the travel move, the extruder will " "push this additional amount of filament. This setting is rarely needed." @@ -13251,7 +13325,7 @@ msgstr "" "Als retracten wordt gecompenseerd na een beweging, wordt deze extra " "hoeveelheid filament geëxtrudeerd. Deze instelling is zelden van toepassing." -#: src/libslic3r/PrintConfig.cpp:2208 +#: src/libslic3r/PrintConfig.cpp:2207 msgid "" "When the retraction is compensated after changing tool, the extruder will " "push this additional amount of filament." @@ -13259,19 +13333,19 @@ msgstr "" "Als retracten wordt gecompenseerd na een toolwisseling, wordt deze extra " "hoeveelheid filament geëxtrudeerd." -#: src/libslic3r/PrintConfig.cpp:2215 src/libslic3r/PrintConfig.cpp:2216 +#: src/libslic3r/PrintConfig.cpp:2214 src/libslic3r/PrintConfig.cpp:2215 msgid "Retraction Speed" msgstr "Retractiesnelheid" -#: src/libslic3r/PrintConfig.cpp:2217 +#: src/libslic3r/PrintConfig.cpp:2216 msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "De snelheid voor retracties (geldt alleen voor de extrudermotor)." -#: src/libslic3r/PrintConfig.cpp:2223 src/libslic3r/PrintConfig.cpp:2224 +#: src/libslic3r/PrintConfig.cpp:2222 src/libslic3r/PrintConfig.cpp:2223 msgid "Deretraction Speed" msgstr "Deretractiesnelheid" -#: src/libslic3r/PrintConfig.cpp:2225 +#: src/libslic3r/PrintConfig.cpp:2224 msgid "" "The speed for loading of a filament into extruder after retraction (it only " "applies to the extruder motor). If left to zero, the retraction speed is " @@ -13281,74 +13355,74 @@ msgstr "" "voor de extrudermotor). Als dit ingesteld is op 0, wordt de " "retractiesnelheid gebruikt." -#: src/libslic3r/PrintConfig.cpp:2232 +#: src/libslic3r/PrintConfig.cpp:2231 msgid "Seam position" msgstr "Naadpositie" -#: src/libslic3r/PrintConfig.cpp:2234 +#: src/libslic3r/PrintConfig.cpp:2233 msgid "Position of perimeters starting points." msgstr "Startpuntpositie van perimeters." -#: src/libslic3r/PrintConfig.cpp:2240 +#: src/libslic3r/PrintConfig.cpp:2239 msgid "Random" msgstr "Willekeurig" -#: src/libslic3r/PrintConfig.cpp:2241 +#: src/libslic3r/PrintConfig.cpp:2240 msgid "Nearest" msgstr "Dichtstbijzijnd" -#: src/libslic3r/PrintConfig.cpp:2242 +#: src/libslic3r/PrintConfig.cpp:2241 msgid "Aligned" msgstr "Uitgelijnd" -#: src/libslic3r/PrintConfig.cpp:2250 +#: src/libslic3r/PrintConfig.cpp:2249 msgid "Direction" msgstr "Richting" -#: src/libslic3r/PrintConfig.cpp:2252 +#: src/libslic3r/PrintConfig.cpp:2251 msgid "Preferred direction of the seam" msgstr "Richtingsvoorkeur voor de naad" -#: src/libslic3r/PrintConfig.cpp:2253 +#: src/libslic3r/PrintConfig.cpp:2252 msgid "Seam preferred direction" msgstr "Richtingsvoorkeur voor de naad" -#: src/libslic3r/PrintConfig.cpp:2260 +#: src/libslic3r/PrintConfig.cpp:2259 msgid "Jitter" msgstr "Jitter" -#: src/libslic3r/PrintConfig.cpp:2262 +#: src/libslic3r/PrintConfig.cpp:2261 msgid "Seam preferred direction jitter" msgstr "Voorkeursrichting voor de naad - jitter" -#: src/libslic3r/PrintConfig.cpp:2263 +#: src/libslic3r/PrintConfig.cpp:2262 msgid "Preferred direction of the seam - jitter" msgstr "Voorkeursrichting voor de naad - jitter" -#: src/libslic3r/PrintConfig.cpp:2270 +#: src/libslic3r/PrintConfig.cpp:2269 msgid "Distance from brim/object" msgstr "Afstand van brim en object" -#: src/libslic3r/PrintConfig.cpp:2271 +#: src/libslic3r/PrintConfig.cpp:2270 msgid "" "Distance between skirt and brim (when draft shield is not used) or objects." msgstr "" "Afstand tussen de skirt en de brim of objecten (wanneer tochtscherm niet " "wordt gebruikt)." -#: src/libslic3r/PrintConfig.cpp:2277 +#: src/libslic3r/PrintConfig.cpp:2276 msgid "Skirt height" msgstr "Skirthoogte" -#: src/libslic3r/PrintConfig.cpp:2278 +#: src/libslic3r/PrintConfig.cpp:2277 msgid "Height of skirt expressed in layers." msgstr "Hoogte van de skirt uitgedrukt in het aantal lagen." -#: src/libslic3r/PrintConfig.cpp:2284 +#: src/libslic3r/PrintConfig.cpp:2283 msgid "Draft shield" msgstr "Tochtscherm" -#: src/libslic3r/PrintConfig.cpp:2285 +#: src/libslic3r/PrintConfig.cpp:2284 msgid "" "With draft shield active, the skirt will be printed skirt_distance from the " "object, possibly intersecting brim.\n" @@ -13364,27 +13438,27 @@ msgstr "" "Dit is handig om een ABS of ASA print te beschermen tegen opkrullen en " "loslaten van het printbed door tocht." -#: src/libslic3r/PrintConfig.cpp:2293 +#: src/libslic3r/PrintConfig.cpp:2292 msgid "Disabled" msgstr "Uit" -#: src/libslic3r/PrintConfig.cpp:2294 +#: src/libslic3r/PrintConfig.cpp:2293 msgid "Limited" msgstr "Gelimiteerd" -#: src/libslic3r/PrintConfig.cpp:2295 +#: src/libslic3r/PrintConfig.cpp:2294 msgid "Enabled" msgstr "Aan" -#: src/libslic3r/PrintConfig.cpp:2300 +#: src/libslic3r/PrintConfig.cpp:2299 msgid "Loops (minimum)" msgstr "Rondgangen (minimaal)" -#: src/libslic3r/PrintConfig.cpp:2301 +#: src/libslic3r/PrintConfig.cpp:2300 msgid "Skirt Loops" msgstr "Rondgangen voor de skirt" -#: src/libslic3r/PrintConfig.cpp:2302 +#: src/libslic3r/PrintConfig.cpp:2301 msgid "" "Number of loops for the skirt. If the Minimum Extrusion Length option is " "set, the number of loops might be greater than the one configured here. Set " @@ -13394,11 +13468,11 @@ msgstr "" "ingesteld kan dit aantal rondgangen groter zijn dan hier is ingesteld. Als " "dit ingesteld is op 0, wordt de skirt uitgeschakeld." -#: src/libslic3r/PrintConfig.cpp:2310 +#: src/libslic3r/PrintConfig.cpp:2309 msgid "Slow down if layer print time is below" msgstr "Vertraag bij een kortere laagprinttijd dan" -#: src/libslic3r/PrintConfig.cpp:2311 +#: src/libslic3r/PrintConfig.cpp:2310 msgid "" "If layer print time is estimated below this number of seconds, print moves " "speed will be scaled down to extend duration to this value." @@ -13406,11 +13480,11 @@ msgstr "" "Als de laagprinttijd wordt berekend onder dit aantal seconden, wordt de " "printsnelheid verlaagd om de laagprinttijd te verlengen." -#: src/libslic3r/PrintConfig.cpp:2320 +#: src/libslic3r/PrintConfig.cpp:2319 msgid "Small perimeters" msgstr "Smalle perimeters" -#: src/libslic3r/PrintConfig.cpp:2322 +#: src/libslic3r/PrintConfig.cpp:2321 msgid "" "This separate setting will affect the speed of perimeters having radius <= " "6.5mm (usually holes). If expressed as percentage (for example: 80%) it will " @@ -13421,11 +13495,11 @@ msgstr "" "wordt deze genomen over de snelheid van de perimeters. Als dit ingesteld is " "op 0, wordt een automatische snelheid genomen." -#: src/libslic3r/PrintConfig.cpp:2332 +#: src/libslic3r/PrintConfig.cpp:2331 msgid "Solid infill threshold area" msgstr "Dichte vulling bij oppervlak" -#: src/libslic3r/PrintConfig.cpp:2334 +#: src/libslic3r/PrintConfig.cpp:2333 msgid "" "Force solid infill for regions having a smaller area than the specified " "threshold." @@ -13433,23 +13507,23 @@ msgstr "" "Forceer dichte vulling voor delen met een kleiner doorsnee-oppervlak dan de " "hier ingestelde waarde." -#: src/libslic3r/PrintConfig.cpp:2335 +#: src/libslic3r/PrintConfig.cpp:2334 msgid "mm²" msgstr "mm²" -#: src/libslic3r/PrintConfig.cpp:2341 +#: src/libslic3r/PrintConfig.cpp:2340 msgid "Solid infill extruder" msgstr "Extruder voor dichte vulling" -#: src/libslic3r/PrintConfig.cpp:2343 +#: src/libslic3r/PrintConfig.cpp:2342 msgid "The extruder to use when printing solid infill." msgstr "De extruder die gebruikt wordt voor het printen van dichte vullingen." -#: src/libslic3r/PrintConfig.cpp:2349 +#: src/libslic3r/PrintConfig.cpp:2348 msgid "Solid infill every" msgstr "Dichte vulling elke" -#: src/libslic3r/PrintConfig.cpp:2351 +#: src/libslic3r/PrintConfig.cpp:2350 msgid "" "This feature allows to force a solid layer every given number of layers. " "Zero to disable. You can set this to any value (for example 9999); Slic3r " @@ -13461,7 +13535,7 @@ msgstr "" "waarde; PrusaSlicer zal dan automatisch het maximaal aantal lagen kiezen om " "te combineren op basis van de nozzlediameter en de laagdikte." -#: src/libslic3r/PrintConfig.cpp:2363 +#: src/libslic3r/PrintConfig.cpp:2362 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "solid surfaces. If left zero, default extrusion width will be used if set, " @@ -13473,7 +13547,7 @@ msgstr "" "breedte instellen op 1,125x de nozzlediameter. Als dit is uitgedrukt als " "percentage, wordt dit berekend over de laagdikte." -#: src/libslic3r/PrintConfig.cpp:2375 +#: src/libslic3r/PrintConfig.cpp:2374 msgid "" "Speed for printing solid regions (top/bottom/internal horizontal shells). " "This can be expressed as a percentage (for example: 80%) over the default " @@ -13483,19 +13557,19 @@ msgstr "" "dit berekend over de standaard vullingssnelheid. Als dit ingesteld is op 0, " "worden automatische waarden genomen." -#: src/libslic3r/PrintConfig.cpp:2387 +#: src/libslic3r/PrintConfig.cpp:2386 msgid "Number of solid layers to generate on top and bottom surfaces." msgstr "Aantal te genereren dichte lagen voor boven- en ondervlakken." -#: src/libslic3r/PrintConfig.cpp:2393 src/libslic3r/PrintConfig.cpp:2394 +#: src/libslic3r/PrintConfig.cpp:2392 src/libslic3r/PrintConfig.cpp:2393 msgid "Minimum thickness of a top / bottom shell" msgstr "Minimale dikte van top-/bodemshell" -#: src/libslic3r/PrintConfig.cpp:2400 +#: src/libslic3r/PrintConfig.cpp:2399 msgid "Spiral vase" msgstr "Spiraalmodus" -#: src/libslic3r/PrintConfig.cpp:2401 +#: src/libslic3r/PrintConfig.cpp:2400 msgid "" "This feature will raise Z gradually while printing a single-walled object in " "order to remove any visible seam. This option requires a single perimeter, " @@ -13510,11 +13584,11 @@ msgstr "" "skirt- en brimrondgangen. Het werkt niet bij het printen van meer dan één " "object." -#: src/libslic3r/PrintConfig.cpp:2409 +#: src/libslic3r/PrintConfig.cpp:2408 msgid "Temperature variation" msgstr "Temperatuurverschil" -#: src/libslic3r/PrintConfig.cpp:2410 +#: src/libslic3r/PrintConfig.cpp:2409 msgid "" "Temperature difference to be applied when an extruder is not active. Enables " "a full-height \"sacrificial\" skirt on which the nozzles are periodically " @@ -13523,7 +13597,7 @@ msgstr "" "Temperatuurverschil dat wordt toegepast als een extruder niet actief is. Dit " "genereert een afveegblok waarop de nozzle wordt schoongeveegd." -#: src/libslic3r/PrintConfig.cpp:2420 +#: src/libslic3r/PrintConfig.cpp:2419 msgid "" "This start procedure is inserted at the beginning, after bed has reached the " "target temperature and extruder just started heating, and before extruder " @@ -13541,7 +13615,7 @@ msgstr "" "andere aangepaste acties aan te passen. Merk op dat u voor alle PrusaSlicer-" "instellingen variabelen kunt gebruiken." -#: src/libslic3r/PrintConfig.cpp:2435 +#: src/libslic3r/PrintConfig.cpp:2434 msgid "" "This start procedure is inserted at the beginning, after any printer start " "gcode (and after any toolchange to this filament in case of multi-material " @@ -13564,47 +13638,47 @@ msgstr "" "meerdere extruders hebt, wordt de G-code in de volgorde van de extruders " "verwerkt." -#: src/libslic3r/PrintConfig.cpp:2451 +#: src/libslic3r/PrintConfig.cpp:2450 msgid "Color change G-code" msgstr "Kleurwissel-G-code" -#: src/libslic3r/PrintConfig.cpp:2452 +#: src/libslic3r/PrintConfig.cpp:2451 msgid "This G-code will be used as a code for the color change" msgstr "Deze G-code wordt gebruikt voor een kleurwisseling" -#: src/libslic3r/PrintConfig.cpp:2461 +#: src/libslic3r/PrintConfig.cpp:2460 msgid "This G-code will be used as a code for the pause print" msgstr "Deze G-code wordt gebruikt bij het pauzeren van de print" -#: src/libslic3r/PrintConfig.cpp:2470 +#: src/libslic3r/PrintConfig.cpp:2469 msgid "This G-code will be used as a custom code" msgstr "Deze G-code wordt gebruikt als custom G-code" -#: src/libslic3r/PrintConfig.cpp:2478 +#: src/libslic3r/PrintConfig.cpp:2477 msgid "Single Extruder Multi Material" msgstr "Multi-material met één extruder" -#: src/libslic3r/PrintConfig.cpp:2479 +#: src/libslic3r/PrintConfig.cpp:2478 msgid "The printer multiplexes filaments into a single hot end." msgstr "De printer mengt filament in een enkele extruder." -#: src/libslic3r/PrintConfig.cpp:2484 +#: src/libslic3r/PrintConfig.cpp:2483 msgid "Prime all printing extruders" msgstr "Veeg alle printextruders af" -#: src/libslic3r/PrintConfig.cpp:2485 +#: src/libslic3r/PrintConfig.cpp:2484 msgid "" "If enabled, all printing extruders will be primed at the front edge of the " "print bed at the start of the print." msgstr "" "Alle extruders worden afgeveegd aan de voorzijde van het printbed aan het " -"begin van de print als dit aanstaat." +"begin van de print als dit is ingeschakeld." -#: src/libslic3r/PrintConfig.cpp:2490 +#: src/libslic3r/PrintConfig.cpp:2489 msgid "No sparse layers (EXPERIMENTAL)" msgstr "Geen smalle lagen (experimenteel)" -#: src/libslic3r/PrintConfig.cpp:2491 +#: src/libslic3r/PrintConfig.cpp:2490 msgid "" "If enabled, the wipe tower will not be printed on layers with no " "toolchanges. On layers with a toolchange, extruder will travel downward to " @@ -13616,11 +13690,11 @@ msgstr "" "bewegen naar het afveegblok. De gebruiker is verantwoordelijk voor eventuele " "botsingen met de print." -#: src/libslic3r/PrintConfig.cpp:2498 +#: src/libslic3r/PrintConfig.cpp:2497 msgid "Slice gap closing radius" msgstr "Gatvulradius" -#: src/libslic3r/PrintConfig.cpp:2500 +#: src/libslic3r/PrintConfig.cpp:2499 msgid "" "Cracks smaller than 2x gap closing radius are being filled during the " "triangle mesh slicing. The gap closing operation may reduce the final print " @@ -13630,11 +13704,11 @@ msgstr "" "het slicen. Het vullen kan zorgen dat de printresolutie minder wordt. Daarom " "wordt geadviseerd de waarde laag te houden." -#: src/libslic3r/PrintConfig.cpp:2508 +#: src/libslic3r/PrintConfig.cpp:2507 msgid "Slicing Mode" msgstr "Slicemodus" -#: src/libslic3r/PrintConfig.cpp:2510 +#: src/libslic3r/PrintConfig.cpp:2509 msgid "" "Use \"Even-odd\" for 3DLabPrint airplane models. Use \"Close holes\" to " "close all holes in the model." @@ -13642,31 +13716,31 @@ msgstr "" "Gebruik \"even-oneven\" voor 3DLabPrint vliegtuigmodellen. Gebruik \"Sluit " "gaten\" om alle gaten in het model te vullen." -#: src/libslic3r/PrintConfig.cpp:2515 +#: src/libslic3r/PrintConfig.cpp:2514 msgid "Regular" msgstr "Normaal" -#: src/libslic3r/PrintConfig.cpp:2516 +#: src/libslic3r/PrintConfig.cpp:2515 msgid "Even-odd" msgstr "Even-oneven" -#: src/libslic3r/PrintConfig.cpp:2517 +#: src/libslic3r/PrintConfig.cpp:2516 msgid "Close holes" msgstr "Sluit gaten" -#: src/libslic3r/PrintConfig.cpp:2522 +#: src/libslic3r/PrintConfig.cpp:2521 msgid "Generate support material" msgstr "Genereer support" -#: src/libslic3r/PrintConfig.cpp:2524 +#: src/libslic3r/PrintConfig.cpp:2523 msgid "Enable support material generation." msgstr "Sta de generatie van support toe." -#: src/libslic3r/PrintConfig.cpp:2528 +#: src/libslic3r/PrintConfig.cpp:2527 msgid "Auto generated supports" msgstr "Automatisch gegenereerd support" -#: src/libslic3r/PrintConfig.cpp:2530 +#: src/libslic3r/PrintConfig.cpp:2529 msgid "" "If checked, supports will be generated automatically based on the overhang " "threshold value. If unchecked, supports will be generated inside the " @@ -13675,11 +13749,11 @@ msgstr "" "Support wordt automatisch gegenereerd als dit aan staat. Als dit niet " "aanstaat zal support alleen bij supportforceringen gegenereerd worden." -#: src/libslic3r/PrintConfig.cpp:2536 +#: src/libslic3r/PrintConfig.cpp:2535 msgid "XY separation between an object and its support" msgstr "Horizontale ruimte tussen het object en het support" -#: src/libslic3r/PrintConfig.cpp:2538 +#: src/libslic3r/PrintConfig.cpp:2537 msgid "" "XY separation between an object and its support. If expressed as percentage " "(for example 50%), it will be calculated over external perimeter width." @@ -13687,17 +13761,17 @@ msgstr "" "Horizontale ruimte tussen object en support. Als dit is uitgedrukt als " "percentage, wordt deze berekend over de breedte van de buitenste perimeter." -#: src/libslic3r/PrintConfig.cpp:2549 +#: src/libslic3r/PrintConfig.cpp:2548 msgid "Pattern angle" msgstr "Patroonhoek" -#: src/libslic3r/PrintConfig.cpp:2551 +#: src/libslic3r/PrintConfig.cpp:2550 msgid "" "Use this setting to rotate the support material pattern on the horizontal " "plane." msgstr "Gebruik deze instelling om het patroon van het support te draaien." -#: src/libslic3r/PrintConfig.cpp:2561 src/libslic3r/PrintConfig.cpp:3616 +#: src/libslic3r/PrintConfig.cpp:2560 src/libslic3r/PrintConfig.cpp:3615 msgid "" "Only create support if it lies on a build plate. Don't create support on a " "print." @@ -13705,11 +13779,11 @@ msgstr "" "Genereer alleen support als dit op het bed geplaatst wordt, dus niet op de " "print zelf." -#: src/libslic3r/PrintConfig.cpp:2567 +#: src/libslic3r/PrintConfig.cpp:2566 msgid "Top contact Z distance" msgstr "Z-afstand aan de bovenkant" -#: src/libslic3r/PrintConfig.cpp:2569 +#: src/libslic3r/PrintConfig.cpp:2568 msgid "" "The vertical distance between object and support material interface. Setting " "this to 0 will also prevent Slic3r from using bridge flow and speed for the " @@ -13719,23 +13793,23 @@ msgstr "" "PrusaSlicer bruginstellingen gebruikt voor de eerste laag boven de " "supportinterface." -#: src/libslic3r/PrintConfig.cpp:2577 +#: src/libslic3r/PrintConfig.cpp:2576 msgid "0 (soluble)" msgstr "0 (oplosbaar)" -#: src/libslic3r/PrintConfig.cpp:2578 +#: src/libslic3r/PrintConfig.cpp:2577 msgid "0.1 (detachable)" msgstr "0,1 (losbreekbaar)" -#: src/libslic3r/PrintConfig.cpp:2579 +#: src/libslic3r/PrintConfig.cpp:2578 msgid "0.2 (detachable)" msgstr "0,2 (losbreekbaar)" -#: src/libslic3r/PrintConfig.cpp:2585 +#: src/libslic3r/PrintConfig.cpp:2584 msgid "Bottom contact Z distance" msgstr "Z-afstand aan de onderkant" -#: src/libslic3r/PrintConfig.cpp:2587 +#: src/libslic3r/PrintConfig.cpp:2586 msgid "" "The vertical distance between the object top surface and the support " "material interface. If set to zero, support_material_contact_distance will " @@ -13747,15 +13821,15 @@ msgstr "" #. TRN To be shown in Print Settings "Bottom contact Z distance". Have to be as short as possible #. TRN To be shown in Print Settings "Bottom interface layers". Have to be as short as possible -#: src/libslic3r/PrintConfig.cpp:2595 src/libslic3r/PrintConfig.cpp:2680 +#: src/libslic3r/PrintConfig.cpp:2594 src/libslic3r/PrintConfig.cpp:2679 msgid "Same as top" msgstr "Zelfde als bovenkant" -#: src/libslic3r/PrintConfig.cpp:2602 +#: src/libslic3r/PrintConfig.cpp:2601 msgid "Enforce support for the first" msgstr "Forceer support voor de eerste" -#: src/libslic3r/PrintConfig.cpp:2604 +#: src/libslic3r/PrintConfig.cpp:2603 msgid "" "Generate support material for the specified number of layers counting from " "bottom, regardless of whether normal support material is enabled or not and " @@ -13767,15 +13841,15 @@ msgstr "" "waarbij de ingesteld hoek wordt aangehouden. Dit is handig om meer hechting " "op het bed te verkrijgen bij objecten met een klein contactoppervlak." -#: src/libslic3r/PrintConfig.cpp:2609 +#: src/libslic3r/PrintConfig.cpp:2608 msgid "Enforce support for the first n layers" msgstr "Forceer support voor de eerste n lagen" -#: src/libslic3r/PrintConfig.cpp:2615 +#: src/libslic3r/PrintConfig.cpp:2614 msgid "Support material/raft/skirt extruder" msgstr "Extruder voor support/raft/skirt" -#: src/libslic3r/PrintConfig.cpp:2617 +#: src/libslic3r/PrintConfig.cpp:2616 msgid "" "The extruder to use when printing support material, raft and skirt (1+, 0 to " "use the current extruder to minimize tool changes)." @@ -13783,7 +13857,7 @@ msgstr "" "De extruder die gebruikt wordt voor support, raft en skirt (stel in op 1 of " "op 0 om de huidige extruder te gebruiken)." -#: src/libslic3r/PrintConfig.cpp:2626 +#: src/libslic3r/PrintConfig.cpp:2625 msgid "" "Set this to a non-zero value to set a manual extrusion width for support " "material. If left zero, default extrusion width will be used if set, " @@ -13795,22 +13869,22 @@ msgstr "" "zelf bepalen op basis van de nozzlediameter. Als dit is uitgedrukt als " "percentage, wordt dit berekend over de laagdikte." -#: src/libslic3r/PrintConfig.cpp:2636 +#: src/libslic3r/PrintConfig.cpp:2635 msgid "Interface loops" msgstr "Interface rondgangen" -#: src/libslic3r/PrintConfig.cpp:2638 +#: src/libslic3r/PrintConfig.cpp:2637 msgid "" "Cover the top contact layer of the supports with loops. Disabled by default." msgstr "" "Bedek de bovenste interfacelagen van het support met rondgangen. Dit staat " "standaard uit." -#: src/libslic3r/PrintConfig.cpp:2643 +#: src/libslic3r/PrintConfig.cpp:2642 msgid "Support material/raft interface extruder" msgstr "Extruder voor supportinterfacce en de bovenlaag van de raft" -#: src/libslic3r/PrintConfig.cpp:2645 +#: src/libslic3r/PrintConfig.cpp:2644 msgid "" "The extruder to use when printing support material interface (1+, 0 to use " "the current extruder to minimize tool changes). This affects raft too." @@ -13819,37 +13893,37 @@ msgstr "" "dan 1 of op 0 om de huidige extruder te gebruiken voor minder " "toolwisselingen). Dit heeft ook effect op de raft." -#: src/libslic3r/PrintConfig.cpp:2653 +#: src/libslic3r/PrintConfig.cpp:2652 msgid "Top interface layers" msgstr "Interfacelagen bovenkant" -#: src/libslic3r/PrintConfig.cpp:2655 +#: src/libslic3r/PrintConfig.cpp:2654 msgid "" "Number of interface layers to insert between the object(s) and support " "material." msgstr "Aantal interfacelagen tussen het support en het object." -#: src/libslic3r/PrintConfig.cpp:2662 +#: src/libslic3r/PrintConfig.cpp:2661 msgid "0 (off)" msgstr "0 (uit)" -#: src/libslic3r/PrintConfig.cpp:2663 +#: src/libslic3r/PrintConfig.cpp:2662 msgid "1 (light)" msgstr "1 (licht)" -#: src/libslic3r/PrintConfig.cpp:2664 +#: src/libslic3r/PrintConfig.cpp:2663 msgid "2 (default)" msgstr "2 (standaard)" -#: src/libslic3r/PrintConfig.cpp:2665 +#: src/libslic3r/PrintConfig.cpp:2664 msgid "3 (heavy)" msgstr "3 (zwaar)" -#: src/libslic3r/PrintConfig.cpp:2671 +#: src/libslic3r/PrintConfig.cpp:2670 msgid "Bottom interface layers" msgstr "Interfacelagen onderkant" -#: src/libslic3r/PrintConfig.cpp:2673 +#: src/libslic3r/PrintConfig.cpp:2672 msgid "" "Number of interface layers to insert between the object(s) and support " "material. Set to -1 to use support_material_interface_layers" @@ -13857,11 +13931,11 @@ msgstr "" "Het aantal interfacelagen tussen de objecten en het supportmateriaal. Stel " "in op -1 om evenveel lagen als op bovenkant te gebruiken" -#: src/libslic3r/PrintConfig.cpp:2686 +#: src/libslic3r/PrintConfig.cpp:2685 msgid "Closing radius" msgstr "Sluitradius" -#: src/libslic3r/PrintConfig.cpp:2688 +#: src/libslic3r/PrintConfig.cpp:2687 msgid "" "For snug supports, the support regions will be merged using morphological " "closing operation. Gaps smaller than the closing radius will be filled in." @@ -13869,17 +13943,17 @@ msgstr "" "Voor handvaste supports worden supportdelen samengevoegd met een " "morfologische sluitmethode. Gaten kleiner dan de sluitradius worden gevuld." -#: src/libslic3r/PrintConfig.cpp:2696 +#: src/libslic3r/PrintConfig.cpp:2695 msgid "Interface pattern spacing" msgstr "Tussenafstand voor interface" -#: src/libslic3r/PrintConfig.cpp:2698 +#: src/libslic3r/PrintConfig.cpp:2697 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "" "Ruimte tussen lijnen van supportinterface. Als dit ingesteld is op 0, wordt " "een dichte supportinterface gegenereerd." -#: src/libslic3r/PrintConfig.cpp:2707 +#: src/libslic3r/PrintConfig.cpp:2706 msgid "" "Speed for printing support material interface layers. If expressed as " "percentage (for example 50%) it will be calculated over support material " @@ -13888,23 +13962,23 @@ msgstr "" "Printsnelheid van supportinterfacelagen. Als dit is uitgedrukt als " "percentage, wordt dit berekend over de snelheid van het support." -#: src/libslic3r/PrintConfig.cpp:2716 +#: src/libslic3r/PrintConfig.cpp:2715 msgid "Pattern" msgstr "Patroon" -#: src/libslic3r/PrintConfig.cpp:2718 +#: src/libslic3r/PrintConfig.cpp:2717 msgid "Pattern used to generate support material." msgstr "Patroon dat gebruikt wordt voor het support." -#: src/libslic3r/PrintConfig.cpp:2724 +#: src/libslic3r/PrintConfig.cpp:2723 msgid "Rectilinear grid" msgstr "Rechtlijnig raster" -#: src/libslic3r/PrintConfig.cpp:2730 +#: src/libslic3r/PrintConfig.cpp:2729 msgid "Interface pattern" msgstr "Interfacepatroon" -#: src/libslic3r/PrintConfig.cpp:2732 +#: src/libslic3r/PrintConfig.cpp:2731 msgid "" "Pattern used to generate support material interface. Default pattern for non-" "soluble support interface is Rectilinear, while default pattern for soluble " @@ -13914,23 +13988,23 @@ msgstr "" "niet-oplosbaar support is rechtlijnig, terwijl het patroon voor oplosbaar " "support concentrisch is." -#: src/libslic3r/PrintConfig.cpp:2746 +#: src/libslic3r/PrintConfig.cpp:2745 msgid "Pattern spacing" msgstr "Tussenafstand van het patroon" -#: src/libslic3r/PrintConfig.cpp:2748 +#: src/libslic3r/PrintConfig.cpp:2747 msgid "Spacing between support material lines." msgstr "Afstand tussen supportlijnen." -#: src/libslic3r/PrintConfig.cpp:2757 +#: src/libslic3r/PrintConfig.cpp:2756 msgid "Speed for printing support material." msgstr "Printsnelheid voor support." -#: src/libslic3r/PrintConfig.cpp:2764 +#: src/libslic3r/PrintConfig.cpp:2763 msgid "Style" msgstr "Type" -#: src/libslic3r/PrintConfig.cpp:2766 +#: src/libslic3r/PrintConfig.cpp:2765 msgid "" "Style and shape of the support towers. Projecting the supports into a " "regular grid will create more stable supports, while snug support towers " @@ -13940,15 +14014,15 @@ msgstr "" "regelmatig raster creëert stabielere supports, terwijl handvaste supports " "materiaal besparen en een lelijk oppervlak reduceert." -#: src/libslic3r/PrintConfig.cpp:2773 +#: src/libslic3r/PrintConfig.cpp:2772 msgid "Snug" msgstr "Handvast" -#: src/libslic3r/PrintConfig.cpp:2778 +#: src/libslic3r/PrintConfig.cpp:2777 msgid "Synchronize with object layers" msgstr "Synchroniseer met objectlagen" -#: src/libslic3r/PrintConfig.cpp:2780 +#: src/libslic3r/PrintConfig.cpp:2779 msgid "" "Synchronize support layers with the object print layers. This is useful with " "multi-material printers, where the extruder switch is expensive." @@ -13956,11 +14030,11 @@ msgstr "" "Synchroniseer de supportlagen met de objectlagen. Dit is handig voor multi-" "materialprinters waar een toolwissel duur is." -#: src/libslic3r/PrintConfig.cpp:2786 +#: src/libslic3r/PrintConfig.cpp:2785 msgid "Overhang threshold" msgstr "Maximale overhanghoek" -#: src/libslic3r/PrintConfig.cpp:2788 +#: src/libslic3r/PrintConfig.cpp:2787 msgid "" "Support material will not be generated for overhangs whose slope angle (90° " "= vertical) is above the given threshold. In other words, this value " @@ -13973,11 +14047,11 @@ msgstr "" "geprint moet worden met support. Als dit ingesteld is op 0, wordt dit " "automatisch gedetecteerd (aanbevolen)." -#: src/libslic3r/PrintConfig.cpp:2800 +#: src/libslic3r/PrintConfig.cpp:2799 msgid "With sheath around the support" msgstr "Met schild rond het support" -#: src/libslic3r/PrintConfig.cpp:2802 +#: src/libslic3r/PrintConfig.cpp:2801 msgid "" "Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove." @@ -13985,7 +14059,7 @@ msgstr "" "Voeg een schild (één perimeterlijn) rondom het support toe. Dit maakt het " "support betrouwbaarder maar ook moeilijker te verwijderen." -#: src/libslic3r/PrintConfig.cpp:2809 +#: src/libslic3r/PrintConfig.cpp:2808 msgid "" "Nozzle temperature for layers after the first one. Set this to zero to " "disable temperature control commands in the output G-code." @@ -13993,29 +14067,29 @@ msgstr "" "Nozzletemperatuur voor lagen na de eerste laag. Stel in op 0 om " "temperatuurregeling uit te zetten in de G-code." -#: src/libslic3r/PrintConfig.cpp:2812 +#: src/libslic3r/PrintConfig.cpp:2811 msgid "Nozzle temperature" msgstr "Nozzletemperatuur" -#: src/libslic3r/PrintConfig.cpp:2818 +#: src/libslic3r/PrintConfig.cpp:2817 msgid "Thick bridges" msgstr "Dikke bruggen" -#: src/libslic3r/PrintConfig.cpp:2820 +#: src/libslic3r/PrintConfig.cpp:2819 msgid "" "If enabled, bridges are more reliable, can bridge longer distances, but may " "look worse. If disabled, bridges look better but are reliable just for " "shorter bridged distances." msgstr "" -"Als dit aanstaat worden bruggen betrouwbaarder, kunnen langere bruggen " -"printen, maar er minder mooi uitzien. Als dit uitstaat zien bruggen er beter " -"uit, maar zijn alleen betrouwbaar over korte afstanden." +"Als dit is ingeschakeld worden bruggen betrouwbaarder, kunnen langere " +"bruggen printen, maar er minder mooi uitzien. Als dit uitstaat zien bruggen " +"er beter uit, maar zijn alleen betrouwbaar over korte afstanden." -#: src/libslic3r/PrintConfig.cpp:2826 +#: src/libslic3r/PrintConfig.cpp:2825 msgid "Detect thin walls" msgstr "Detecteer dunne wanden" -#: src/libslic3r/PrintConfig.cpp:2828 +#: src/libslic3r/PrintConfig.cpp:2827 msgid "" "Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)." @@ -14023,11 +14097,11 @@ msgstr "" "Detecteer éénlijnige wanden (delen waar 2 extrusielijnen niet passen en dit " "geprint moet worden met 1 lijn)." -#: src/libslic3r/PrintConfig.cpp:2834 +#: src/libslic3r/PrintConfig.cpp:2833 msgid "Threads" -msgstr "Meerdere processen" +msgstr "Processen" -#: src/libslic3r/PrintConfig.cpp:2835 +#: src/libslic3r/PrintConfig.cpp:2834 msgid "" "Threads are used to parallelize long-running tasks. Optimal threads number " "is slightly above the number of available cores/processors." @@ -14036,7 +14110,7 @@ msgstr "" "draaien. Het optimaal aantal processen is vlak boven het aanwezige aantal " "kernen/processoren." -#: src/libslic3r/PrintConfig.cpp:2847 +#: src/libslic3r/PrintConfig.cpp:2846 msgid "" "This custom code is inserted before every toolchange. Placeholder variables " "for all PrusaSlicer settings as well as {toolchange_z}, {previous_extruder} " @@ -14052,7 +14126,7 @@ msgstr "" "(zoals T{next_extruder}), zal PrusaSlicer deze verder negeren. Het is daarom " "mogelijk om een custom script toe te passen voor en na de toolwisseling." -#: src/libslic3r/PrintConfig.cpp:2860 +#: src/libslic3r/PrintConfig.cpp:2859 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "top surfaces. You may want to use thinner extrudates to fill all narrow " @@ -14065,7 +14139,7 @@ msgstr "" "extrudaat in smalle gebieden voor een gladdere afwerking. Als dit is " "uitgedrukt als percentage, wordt dit berekend over de laagdikte." -#: src/libslic3r/PrintConfig.cpp:2873 +#: src/libslic3r/PrintConfig.cpp:2872 msgid "" "Speed for printing top solid layers (it only applies to the uppermost " "external layers and not to their internal solid layers). You may want to " @@ -14079,15 +14153,15 @@ msgstr "" "vullingssnelheid. Als dit ingesteld is op 0, wordt een automatische snelheid " "genomen." -#: src/libslic3r/PrintConfig.cpp:2888 +#: src/libslic3r/PrintConfig.cpp:2887 msgid "Number of solid layers to generate on top surfaces." msgstr "Aantal te genereren dichte lagen voor bovenvlakken." -#: src/libslic3r/PrintConfig.cpp:2889 +#: src/libslic3r/PrintConfig.cpp:2888 msgid "Top solid layers" msgstr "Bovenste dichte vulling" -#: src/libslic3r/PrintConfig.cpp:2897 +#: src/libslic3r/PrintConfig.cpp:2896 msgid "" "The number of top solid layers is increased above top_solid_layers if " "necessary to satisfy minimum thickness of top shell. This is useful to " @@ -14097,19 +14171,19 @@ msgstr "" "de minimale shelldikte te garanderen. Dit is handig om kussenvorming te " "voorkomen bij het printen met variabele laagdikte." -#: src/libslic3r/PrintConfig.cpp:2900 +#: src/libslic3r/PrintConfig.cpp:2899 msgid "Minimum top shell thickness" msgstr "Minimale shelldikte aan de bovenzijde" -#: src/libslic3r/PrintConfig.cpp:2907 +#: src/libslic3r/PrintConfig.cpp:2906 msgid "Speed for travel moves (jumps between distant extrusion points)." msgstr "Bewegingssnelheid als niet geëxtrudeerd wordt." -#: src/libslic3r/PrintConfig.cpp:2915 +#: src/libslic3r/PrintConfig.cpp:2914 msgid "Z travel" msgstr "Z-beweging" -#: src/libslic3r/PrintConfig.cpp:2916 +#: src/libslic3r/PrintConfig.cpp:2915 msgid "" "Speed for movements along the Z axis.\n" "When set to zero, the value is ignored and regular travel speed is used " @@ -14119,11 +14193,11 @@ msgstr "" "Als dit ingesteld is op 0, zal de waarde worden genegeerd en " "standaardwaarden worden gebruikt." -#: src/libslic3r/PrintConfig.cpp:2924 +#: src/libslic3r/PrintConfig.cpp:2923 msgid "Use firmware retraction" msgstr "Gebruik de firmware-retractie" -#: src/libslic3r/PrintConfig.cpp:2925 +#: src/libslic3r/PrintConfig.cpp:2924 msgid "" "This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin." @@ -14132,11 +14206,11 @@ msgstr "" "retracten in de firmware. Dit wordt alleen ondersteunt bij de recente Marlin-" "variant." -#: src/libslic3r/PrintConfig.cpp:2931 +#: src/libslic3r/PrintConfig.cpp:2930 msgid "Use relative E distances" msgstr "Gebruik relatieve E-waarden" -#: src/libslic3r/PrintConfig.cpp:2932 +#: src/libslic3r/PrintConfig.cpp:2931 msgid "" "If your firmware requires relative E values, check this, otherwise leave it " "unchecked. Most firmwares use absolute values." @@ -14144,11 +14218,11 @@ msgstr "" "Als uw firmware relatieve extrusiewaarden nodig heeft, vink dit dan aan. " "Laat het ander uit staan. De meeste firmware gebruiken absolute waarden." -#: src/libslic3r/PrintConfig.cpp:2938 +#: src/libslic3r/PrintConfig.cpp:2937 msgid "Use volumetric E" msgstr "Gebruik volumetrische E-waarden" -#: src/libslic3r/PrintConfig.cpp:2939 +#: src/libslic3r/PrintConfig.cpp:2938 msgid "" "This experimental setting uses outputs the E values in cubic millimeters " "instead of linear millimeters. If your firmware doesn't already know " @@ -14165,11 +14239,11 @@ msgstr "" "filamentinstellingen. Dit wordt alleen ondersteund in de recente Marlin-" "variant." -#: src/libslic3r/PrintConfig.cpp:2949 +#: src/libslic3r/PrintConfig.cpp:2948 msgid "Enable variable layer height feature" msgstr "Variabele laagdikte toestaan" -#: src/libslic3r/PrintConfig.cpp:2950 +#: src/libslic3r/PrintConfig.cpp:2949 msgid "" "Some printers or printer setups may have difficulties printing with a " "variable layer height. Enabled by default." @@ -14177,11 +14251,11 @@ msgstr "" "Sommige printers of printersetups kunnen niet printen met een variabele " "laagdikte. Staat standaard aan." -#: src/libslic3r/PrintConfig.cpp:2956 +#: src/libslic3r/PrintConfig.cpp:2955 msgid "Wipe while retracting" msgstr "Veeg af bij het retracten" -#: src/libslic3r/PrintConfig.cpp:2957 +#: src/libslic3r/PrintConfig.cpp:2956 msgid "" "This flag will move the nozzle while retracting to minimize the possible " "blob on leaky extruders." @@ -14189,7 +14263,7 @@ msgstr "" "Als u dit aanvinkt beweegt de nozzle tijdens het retracten om een blob of " "lekkende extruders tegen te gaan." -#: src/libslic3r/PrintConfig.cpp:2964 +#: src/libslic3r/PrintConfig.cpp:2963 msgid "" "Multi material printers may need to prime or purge extruders on tool " "changes. Extrude the excess material into the wipe tower." @@ -14197,11 +14271,11 @@ msgstr "" "Multi-materialprinters moeten afvegen bij toolwisselingen. Extrudeer het " "overtollige materiaal op het afveegblok." -#: src/libslic3r/PrintConfig.cpp:2970 +#: src/libslic3r/PrintConfig.cpp:2969 msgid "Purging volumes - load/unload volumes" msgstr "Afveegvolume - laad/ontlaad volumes" -#: src/libslic3r/PrintConfig.cpp:2971 +#: src/libslic3r/PrintConfig.cpp:2970 msgid "" "This vector saves required volumes to change from/to each tool used on the " "wipe tower. These values are used to simplify creation of the full purging " @@ -14212,11 +14286,11 @@ msgstr "" "het creëren van de onderstaande volledige reinigingsvolumes te " "vereenvoudigen." -#: src/libslic3r/PrintConfig.cpp:2977 +#: src/libslic3r/PrintConfig.cpp:2976 msgid "Purging volumes - matrix" msgstr "Afveegvolume - matrix" -#: src/libslic3r/PrintConfig.cpp:2978 +#: src/libslic3r/PrintConfig.cpp:2977 msgid "" "This matrix describes volumes (in cubic milimetres) required to purge the " "new filament on the wipe tower for any given pair of tools." @@ -14224,43 +14298,43 @@ msgstr "" "Deze matrix beschrijft volume (in mm³) dat is vereist om nieuw filament af " "te vegen aan het afveegblok voor elk paar van extruders." -#: src/libslic3r/PrintConfig.cpp:2987 +#: src/libslic3r/PrintConfig.cpp:2986 msgid "Position X" msgstr "X-positie" -#: src/libslic3r/PrintConfig.cpp:2988 +#: src/libslic3r/PrintConfig.cpp:2987 msgid "X coordinate of the left front corner of a wipe tower" msgstr "X-coördinaat van de linkervoorhoek van het afveegblok" -#: src/libslic3r/PrintConfig.cpp:2994 +#: src/libslic3r/PrintConfig.cpp:2993 msgid "Position Y" msgstr "Y-positie" -#: src/libslic3r/PrintConfig.cpp:2995 +#: src/libslic3r/PrintConfig.cpp:2994 msgid "Y coordinate of the left front corner of a wipe tower" msgstr "Y-coördinaat van de linkervoorhoek van het afveegblok" -#: src/libslic3r/PrintConfig.cpp:3002 +#: src/libslic3r/PrintConfig.cpp:3001 msgid "Width of a wipe tower" msgstr "Breedte van het afveegblok" -#: src/libslic3r/PrintConfig.cpp:3008 +#: src/libslic3r/PrintConfig.cpp:3007 msgid "Wipe tower rotation angle" msgstr "Rotatie van het afveegblok" -#: src/libslic3r/PrintConfig.cpp:3009 +#: src/libslic3r/PrintConfig.cpp:3008 msgid "Wipe tower rotation angle with respect to x-axis." msgstr "Rotatie van het afveegblok ten opzichte van de X-as." -#: src/libslic3r/PrintConfig.cpp:3015 src/libslic3r/PrintConfig.cpp:3016 +#: src/libslic3r/PrintConfig.cpp:3014 src/libslic3r/PrintConfig.cpp:3015 msgid "Wipe tower brim width" msgstr "Brimbreedte van het afveegblok" -#: src/libslic3r/PrintConfig.cpp:3024 +#: src/libslic3r/PrintConfig.cpp:3023 msgid "Wipe into this object's infill" msgstr "Afvegen in de vulling van het object" -#: src/libslic3r/PrintConfig.cpp:3025 +#: src/libslic3r/PrintConfig.cpp:3024 msgid "" "Purging after toolchange will be done inside this object's infills. This " "lowers the amount of waste but may result in longer print time due to " @@ -14270,11 +14344,11 @@ msgstr "" "reduceert de hoeveelheid afval, maar kan resulteren in langere printtijden " "door meer bewegingen." -#: src/libslic3r/PrintConfig.cpp:3032 +#: src/libslic3r/PrintConfig.cpp:3031 msgid "Wipe into this object" msgstr "Afvegen in dit object" -#: src/libslic3r/PrintConfig.cpp:3033 +#: src/libslic3r/PrintConfig.cpp:3032 msgid "" "Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " @@ -14284,19 +14358,19 @@ msgstr "" "materiaal dat anders in het afveegblok gebruikt wordt te besparen. Kleuren " "kunnen dan gemengd worden." -#: src/libslic3r/PrintConfig.cpp:3039 +#: src/libslic3r/PrintConfig.cpp:3038 msgid "Maximal bridging distance" msgstr "Maximale brugafstand" -#: src/libslic3r/PrintConfig.cpp:3040 +#: src/libslic3r/PrintConfig.cpp:3039 msgid "Maximal distance between supports on sparse infill sections." msgstr "Maximale afstand tussen support op dunne vullingsdelen." -#: src/libslic3r/PrintConfig.cpp:3046 +#: src/libslic3r/PrintConfig.cpp:3045 msgid "XY Size Compensation" msgstr "Compensatie voor X- en Y-grootte" -#: src/libslic3r/PrintConfig.cpp:3048 +#: src/libslic3r/PrintConfig.cpp:3047 msgid "" "The object will be grown/shrunk in the XY plane by the configured value " "(negative = inwards, positive = outwards). This might be useful for fine-" @@ -14306,11 +14380,11 @@ msgstr "" "waarde (negatief = naar binnen, positief = naar buiten). Dit kan handig zijn " "voor het verfijnen van gaten." -#: src/libslic3r/PrintConfig.cpp:3056 +#: src/libslic3r/PrintConfig.cpp:3055 msgid "Z offset" msgstr "Z-hoogte" -#: src/libslic3r/PrintConfig.cpp:3057 +#: src/libslic3r/PrintConfig.cpp:3056 msgid "" "This value will be added (or subtracted) from all the Z coordinates in the " "output G-code. It is used to compensate for bad Z endstop position: for " @@ -14322,43 +14396,51 @@ msgstr "" "eindstop bijvoorbeeld een waarde gebruikt die 0.3mm van het printbed is, kan " "dit ingesteld worden op -0.3mm." -#: src/libslic3r/PrintConfig.cpp:3066 +#: src/libslic3r/PrintConfig.cpp:3065 msgid "Perimeter generator" -msgstr "" +msgstr "Perimetergeneratie" -#: src/libslic3r/PrintConfig.cpp:3068 +#: src/libslic3r/PrintConfig.cpp:3067 msgid "" "Classic perimeter generator produces perimeters with constant extrusion " "width and for very thin areas is used gap-fill. Arachne engine produces " "perimeters with variable extrusion width. This setting also affects the " "Concentric infill." msgstr "" +"Klassieke perimetergeneratie produceert perimeters met constante " +"extrusiebreedte en gebruikt gatenvulling voor dunne stukken. Arachne " +"produceert perimeters met variabele extrusiebreedte. Deze instelling heeft " +"ook effect op concentrische vulling." + +#: src/libslic3r/PrintConfig.cpp:3074 +msgid "Classic" +msgstr "Klassiek" #: src/libslic3r/PrintConfig.cpp:3075 -msgid "Classic" -msgstr "" - -#: src/libslic3r/PrintConfig.cpp:3076 msgid "Arachne" -msgstr "" +msgstr "Arachne" -#: src/libslic3r/PrintConfig.cpp:3081 +#: src/libslic3r/PrintConfig.cpp:3080 msgid "Perimeter transition length" -msgstr "" +msgstr "Transitielengte voor perimeters" -#: src/libslic3r/PrintConfig.cpp:3083 +#: src/libslic3r/PrintConfig.cpp:3082 msgid "" "When transitioning between different numbers of perimeters as the part " "becomes thinner, a certain amount of space is allotted to split or join the " "perimeter segments. If expressed as a percentage (for example 100%), it will " "be computed based on the nozzle diameter." msgstr "" +"Bij de overgang naar een verschillend aantal perimeters bij dunnere diktes " +"wordt een bepaalde ruimte gebruikt om te splitten of samenvoegen van " +"perimetersegmenten. Als dit wordt uitgedrukt als percentage (bijvoorbeeld " +"100%), wordt deze berekend op basis van de nozzlediameter." -#: src/libslic3r/PrintConfig.cpp:3092 +#: src/libslic3r/PrintConfig.cpp:3091 msgid "Perimeter transitioning filter margin" -msgstr "" +msgstr "Marge voor transitielengte voor perimeters" -#: src/libslic3r/PrintConfig.cpp:3094 +#: src/libslic3r/PrintConfig.cpp:3093 msgid "" "Prevent transitioning back and forth between one extra perimeter and one " "less. This margin extends the range of extrusion widths which follow to " @@ -14369,12 +14451,19 @@ msgid "" "as a percentage (for example 25%), it will be computed based on the nozzle " "diameter." msgstr "" +"Voorkom transities tussen één en meerdere perimeters. Deze marge breidt het " +"bereik uit van de extrusiebreedte die volgt op [minimale perimeterbreedte - " +"marge, 2x minimale perimeterbreedte + marge]. Verhogen van deze marge " +"reduceert het aantal transities, wat het aantal terugtrekkingen verlaagt. " +"Hoewel, een hogere variatie van de extrusiebreedte kan leiden tot onder- of " +"overextrusie problemen. Als dit wordt uitgedrukt als percentage " +"(bijvoorbeeld 25%) wordt dit berekend op basis van de nozzlediameter." -#: src/libslic3r/PrintConfig.cpp:3107 +#: src/libslic3r/PrintConfig.cpp:3106 msgid "Perimeter transitioning threshold angle" -msgstr "" +msgstr "Transitiehoek voor perimeters" -#: src/libslic3r/PrintConfig.cpp:3109 +#: src/libslic3r/PrintConfig.cpp:3108 msgid "" "When to create transitions between even and odd numbers of perimeters. A " "wedge shape with an angle greater than this setting will not have " @@ -14382,23 +14471,31 @@ msgid "" "remaining space. Reducing this setting reduces the number and length of " "these center perimeters, but may leave gaps or overextrude." msgstr "" +"When to create transitions between even and odd numbers of perimeters. A " +"wedge shape with an angle greater than this setting will not have " +"transitions and no perimeters will be printed in the center to fill the " +"remaining space. Reducing this setting reduces the number and length of " +"these center perimeters, but may leave gaps or overextrude." -#: src/libslic3r/PrintConfig.cpp:3120 +#: src/libslic3r/PrintConfig.cpp:3119 msgid "Perimeter distribution count" -msgstr "" +msgstr "Perimeterdistributieaantal" -#: src/libslic3r/PrintConfig.cpp:3122 +#: src/libslic3r/PrintConfig.cpp:3121 msgid "" "The number of perimeters, counted from the center, over which the variation " "needs to be spread. Lower values mean that the outer perimeters don't change " "in width." msgstr "" +"Het aantal perimeters, geteld vanaf het midden waarover de variatie gespreid " +"wordt. Lagere waarde betekenen dat de buitenste perimeter niet in breedte " +"verandert." -#: src/libslic3r/PrintConfig.cpp:3129 +#: src/libslic3r/PrintConfig.cpp:3128 msgid "Minimum feature size" -msgstr "" +msgstr "Minimale objectgrootte" -#: src/libslic3r/PrintConfig.cpp:3131 +#: src/libslic3r/PrintConfig.cpp:3130 msgid "" "Minimum thickness of thin features. Model features that are thinner than " "this value will not be printed, while features thicker than the Minimum " @@ -14406,12 +14503,17 @@ msgid "" "a percentage (for example 25%), it will be computed based on the nozzle " "diameter." msgstr "" +"Minimale dikte van dunne delen. Delen van het model die dunner zijn dan deze " +"waarde worden niet geprint, terwijl delen van het model dikker dan de " +"minimale dikte van dunnen delen worden verbreed tot de minimale " +"perimeterbreedte. Als dit is uitgedrukt als percentage (bijvoorbeeld 25%), " +"wordt dit berekend op basis van de nozzlediameter." -#: src/libslic3r/PrintConfig.cpp:3141 +#: src/libslic3r/PrintConfig.cpp:3140 msgid "Minimum perimeter width" -msgstr "" +msgstr "Minimale perimeterbreedte" -#: src/libslic3r/PrintConfig.cpp:3143 +#: src/libslic3r/PrintConfig.cpp:3142 msgid "" "Width of the perimeter that will replace thin features (according to the " "Minimum feature size) of the model. If the Minimum perimeter width is " @@ -14419,64 +14521,69 @@ msgid "" "thick as the feature itself. If expressed as a percentage (for example 85%), " "it will be computed based on the nozzle diameter." msgstr "" +"Perimeterbreedte die dunne delen vervangt (volgens de minimale dikte van " +"dunne delen) van het model. Als die minimale perimeterbreedte dunner is dan " +"de dikte van de dunne delen, wordt de perimeter net zo dik als het de dunne " +"delen. Als dit is uitgedrukt als percentage (bijvoorbeeld 85%), dan wordt " +"dit berekend op basis van de nozzlediameter." -#: src/libslic3r/PrintConfig.cpp:3211 +#: src/libslic3r/PrintConfig.cpp:3210 msgid "Display width" msgstr "Schermbreedte" -#: src/libslic3r/PrintConfig.cpp:3212 +#: src/libslic3r/PrintConfig.cpp:3211 msgid "Width of the display" msgstr "Breedte van het scherm" -#: src/libslic3r/PrintConfig.cpp:3217 +#: src/libslic3r/PrintConfig.cpp:3216 msgid "Display height" msgstr "Schermhoogte" -#: src/libslic3r/PrintConfig.cpp:3218 +#: src/libslic3r/PrintConfig.cpp:3217 msgid "Height of the display" msgstr "Hoogte van het scherm" -#: src/libslic3r/PrintConfig.cpp:3223 +#: src/libslic3r/PrintConfig.cpp:3222 msgid "Number of pixels in" msgstr "Aantal pixels" -#: src/libslic3r/PrintConfig.cpp:3225 +#: src/libslic3r/PrintConfig.cpp:3224 msgid "Number of pixels in X" msgstr "Aantal pixels in de breedte" -#: src/libslic3r/PrintConfig.cpp:3231 +#: src/libslic3r/PrintConfig.cpp:3230 msgid "Number of pixels in Y" msgstr "Aantal pixels in de hoogte" -#: src/libslic3r/PrintConfig.cpp:3236 +#: src/libslic3r/PrintConfig.cpp:3235 msgid "Display horizontal mirroring" msgstr "Scherm horizontaal spiegelen" -#: src/libslic3r/PrintConfig.cpp:3237 +#: src/libslic3r/PrintConfig.cpp:3236 msgid "Mirror horizontally" msgstr "Spiegel horizontaal" -#: src/libslic3r/PrintConfig.cpp:3238 +#: src/libslic3r/PrintConfig.cpp:3237 msgid "Enable horizontal mirroring of output images" msgstr "Horizontaal spiegelen" -#: src/libslic3r/PrintConfig.cpp:3243 +#: src/libslic3r/PrintConfig.cpp:3242 msgid "Display vertical mirroring" msgstr "Scherm verticaal spiegelen" -#: src/libslic3r/PrintConfig.cpp:3244 +#: src/libslic3r/PrintConfig.cpp:3243 msgid "Mirror vertically" msgstr "Verticaal spiegelen" -#: src/libslic3r/PrintConfig.cpp:3245 +#: src/libslic3r/PrintConfig.cpp:3244 msgid "Enable vertical mirroring of output images" msgstr "Verticaal spiegelen" -#: src/libslic3r/PrintConfig.cpp:3250 +#: src/libslic3r/PrintConfig.cpp:3249 msgid "Display orientation" msgstr "Schermoriëntatie" -#: src/libslic3r/PrintConfig.cpp:3251 +#: src/libslic3r/PrintConfig.cpp:3250 msgid "" "Set the actual LCD display orientation inside the SLA printer. Portrait mode " "will flip the meaning of display width and height parameters and the output " @@ -14486,55 +14593,55 @@ msgstr "" "Staande modus zal de breedte- en hoogteparameters omwisselen en de output " "wordt 90 graden gedraaid." -#: src/libslic3r/PrintConfig.cpp:3257 +#: src/libslic3r/PrintConfig.cpp:3256 msgid "Landscape" msgstr "Liggend" -#: src/libslic3r/PrintConfig.cpp:3258 +#: src/libslic3r/PrintConfig.cpp:3257 msgid "Portrait" msgstr "Staand" -#: src/libslic3r/PrintConfig.cpp:3263 src/libslic3r/PrintConfig.cpp:3898 +#: src/libslic3r/PrintConfig.cpp:3262 src/libslic3r/PrintConfig.cpp:3897 msgid "Fast" msgstr "Snel" -#: src/libslic3r/PrintConfig.cpp:3264 +#: src/libslic3r/PrintConfig.cpp:3263 msgid "Fast tilt" msgstr "Snelle draaiing" -#: src/libslic3r/PrintConfig.cpp:3265 +#: src/libslic3r/PrintConfig.cpp:3264 msgid "Time of the fast tilt" msgstr "Tijd van de snelle draaiing" -#: src/libslic3r/PrintConfig.cpp:3272 src/libslic3r/PrintConfig.cpp:3897 +#: src/libslic3r/PrintConfig.cpp:3271 src/libslic3r/PrintConfig.cpp:3896 msgid "Slow" msgstr "Langzaam" -#: src/libslic3r/PrintConfig.cpp:3273 +#: src/libslic3r/PrintConfig.cpp:3272 msgid "Slow tilt" msgstr "Langzaam draaien" -#: src/libslic3r/PrintConfig.cpp:3274 +#: src/libslic3r/PrintConfig.cpp:3273 msgid "Time of the slow tilt" msgstr "Tijd van de langzame draaiing" -#: src/libslic3r/PrintConfig.cpp:3281 src/libslic3r/PrintConfig.cpp:3899 +#: src/libslic3r/PrintConfig.cpp:3280 src/libslic3r/PrintConfig.cpp:3898 msgid "High viscosity" -msgstr "" +msgstr "Hoge viscositeit" + +#: src/libslic3r/PrintConfig.cpp:3281 +msgid "Tilt for high viscosity resin" +msgstr "Draaiing voor hoogvisceuze resin" #: src/libslic3r/PrintConfig.cpp:3282 -msgid "Tilt for high viscosity resin" -msgstr "" - -#: src/libslic3r/PrintConfig.cpp:3283 msgid "Time of the super slow tilt" -msgstr "" +msgstr "Tijd van de zeer langzame draaiing" -#: src/libslic3r/PrintConfig.cpp:3290 +#: src/libslic3r/PrintConfig.cpp:3289 msgid "Area fill" msgstr "Vulgebied" -#: src/libslic3r/PrintConfig.cpp:3291 +#: src/libslic3r/PrintConfig.cpp:3290 msgid "" "The percentage of the bed area. \n" "If the print area exceeds the specified value, \n" @@ -14544,40 +14651,40 @@ msgstr "" "Als het printgebied buiten een specifieke waarde valt \n" "wordt een korte draaiing gebruikt, anders een snelle" -#: src/libslic3r/PrintConfig.cpp:3298 src/libslic3r/PrintConfig.cpp:3299 -#: src/libslic3r/PrintConfig.cpp:3300 +#: src/libslic3r/PrintConfig.cpp:3297 src/libslic3r/PrintConfig.cpp:3298 +#: src/libslic3r/PrintConfig.cpp:3299 msgid "Printer scaling correction" msgstr "Verschalingscorrectie voor printer" -#: src/libslic3r/PrintConfig.cpp:3306 src/libslic3r/PrintConfig.cpp:3308 +#: src/libslic3r/PrintConfig.cpp:3305 src/libslic3r/PrintConfig.cpp:3307 msgid "Printer scaling correction in X axis" msgstr "Verschalingscorrectie over de X-as" -#: src/libslic3r/PrintConfig.cpp:3307 +#: src/libslic3r/PrintConfig.cpp:3306 msgid "Printer scaling X axis correction" msgstr "Verschalingscorrectie in X-richting" -#: src/libslic3r/PrintConfig.cpp:3314 src/libslic3r/PrintConfig.cpp:3316 +#: src/libslic3r/PrintConfig.cpp:3313 src/libslic3r/PrintConfig.cpp:3315 msgid "Printer scaling correction in Y axis" msgstr "Verschalingscorrectie over de Y-as" -#: src/libslic3r/PrintConfig.cpp:3315 +#: src/libslic3r/PrintConfig.cpp:3314 msgid "Printer scaling Y axis correction" msgstr "Verschalingscorrectie in Y-riching" -#: src/libslic3r/PrintConfig.cpp:3322 src/libslic3r/PrintConfig.cpp:3324 +#: src/libslic3r/PrintConfig.cpp:3321 src/libslic3r/PrintConfig.cpp:3323 msgid "Printer scaling correction in Z axis" msgstr "Verschalingscorrectie over de Z-as" -#: src/libslic3r/PrintConfig.cpp:3323 +#: src/libslic3r/PrintConfig.cpp:3322 msgid "Printer scaling Z axis correction" msgstr "Verschalingscorrectie in Z-riching" -#: src/libslic3r/PrintConfig.cpp:3330 src/libslic3r/PrintConfig.cpp:3331 +#: src/libslic3r/PrintConfig.cpp:3329 src/libslic3r/PrintConfig.cpp:3330 msgid "Printer absolute correction" msgstr "Absolute correctie voor printer" -#: src/libslic3r/PrintConfig.cpp:3332 +#: src/libslic3r/PrintConfig.cpp:3331 msgid "" "Will inflate or deflate the sliced 2D polygons according to the sign of the " "correction." @@ -14585,20 +14692,20 @@ msgstr "" "Zal de geslicede veelhoeken uitrekken of laten krimpen, afhankelijk van de " "correctiewaarde." -#: src/libslic3r/PrintConfig.cpp:3338 +#: src/libslic3r/PrintConfig.cpp:3337 msgid "Elephant foot minimum width" msgstr "Squish-compensatiebreedte" -#: src/libslic3r/PrintConfig.cpp:3340 +#: src/libslic3r/PrintConfig.cpp:3339 msgid "" "Minimum width of features to maintain when doing elephant foot compensation." msgstr "Minimale breedte van delen waarop squish-compensatie wordt toegepast." -#: src/libslic3r/PrintConfig.cpp:3347 src/libslic3r/PrintConfig.cpp:3348 +#: src/libslic3r/PrintConfig.cpp:3346 src/libslic3r/PrintConfig.cpp:3347 msgid "Printer gamma correction" msgstr "Gammacorrectie voor printer" -#: src/libslic3r/PrintConfig.cpp:3349 +#: src/libslic3r/PrintConfig.cpp:3348 msgid "" "This will apply a gamma correction to the rasterized 2D polygons. A gamma " "value of zero means thresholding with the threshold in the middle. This " @@ -14608,43 +14715,43 @@ msgstr "" "betekent een waarde die in het midden ligt. Dit gedrag elimineert anti-" "aliasing zonder dat gaten in de veelhoeken verloren gaan." -#: src/libslic3r/PrintConfig.cpp:3368 src/libslic3r/PrintConfig.cpp:3369 +#: src/libslic3r/PrintConfig.cpp:3367 src/libslic3r/PrintConfig.cpp:3368 msgid "SLA material type" msgstr "SLA-materiaaltype" -#: src/libslic3r/PrintConfig.cpp:3380 src/libslic3r/PrintConfig.cpp:3381 +#: src/libslic3r/PrintConfig.cpp:3379 src/libslic3r/PrintConfig.cpp:3380 msgid "Initial layer height" msgstr "Laagdikte eerste laag" -#: src/libslic3r/PrintConfig.cpp:3387 src/libslic3r/PrintConfig.cpp:3388 +#: src/libslic3r/PrintConfig.cpp:3386 src/libslic3r/PrintConfig.cpp:3387 msgid "Bottle volume" msgstr "Flesinhoud (volume)" -#: src/libslic3r/PrintConfig.cpp:3389 +#: src/libslic3r/PrintConfig.cpp:3388 msgid "ml" msgstr "ml" -#: src/libslic3r/PrintConfig.cpp:3394 src/libslic3r/PrintConfig.cpp:3395 +#: src/libslic3r/PrintConfig.cpp:3393 src/libslic3r/PrintConfig.cpp:3394 msgid "Bottle weight" msgstr "Flesinhoud (gewicht)" -#: src/libslic3r/PrintConfig.cpp:3396 +#: src/libslic3r/PrintConfig.cpp:3395 msgid "kg" msgstr "kg" -#: src/libslic3r/PrintConfig.cpp:3403 +#: src/libslic3r/PrintConfig.cpp:3402 msgid "g/ml" msgstr "g/ml" -#: src/libslic3r/PrintConfig.cpp:3410 +#: src/libslic3r/PrintConfig.cpp:3409 msgid "money/bottle" msgstr "€/fles" -#: src/libslic3r/PrintConfig.cpp:3415 +#: src/libslic3r/PrintConfig.cpp:3414 msgid "Faded layers" msgstr "Transitielagen" -#: src/libslic3r/PrintConfig.cpp:3416 +#: src/libslic3r/PrintConfig.cpp:3415 msgid "" "Number of the layers needed for the exposure time fade from initial exposure " "time to the exposure time" @@ -14652,103 +14759,103 @@ msgstr "" "Aantal lagen waarin de initiële belichtingstijd stapsgewijs wordt " "teruggebracht naar de standaard belichtingstijd" -#: src/libslic3r/PrintConfig.cpp:3423 src/libslic3r/PrintConfig.cpp:3424 +#: src/libslic3r/PrintConfig.cpp:3422 src/libslic3r/PrintConfig.cpp:3423 msgid "Minimum exposure time" msgstr "Minimale belichtingstijd" -#: src/libslic3r/PrintConfig.cpp:3431 src/libslic3r/PrintConfig.cpp:3432 +#: src/libslic3r/PrintConfig.cpp:3430 src/libslic3r/PrintConfig.cpp:3431 msgid "Maximum exposure time" msgstr "Maximale belichtingstijd" -#: src/libslic3r/PrintConfig.cpp:3439 src/libslic3r/PrintConfig.cpp:3440 +#: src/libslic3r/PrintConfig.cpp:3438 src/libslic3r/PrintConfig.cpp:3439 msgid "Exposure time" msgstr "Belichtingstijd" -#: src/libslic3r/PrintConfig.cpp:3446 src/libslic3r/PrintConfig.cpp:3447 +#: src/libslic3r/PrintConfig.cpp:3445 src/libslic3r/PrintConfig.cpp:3446 msgid "Minimum initial exposure time" msgstr "Minimale initiële belichtingstijd" -#: src/libslic3r/PrintConfig.cpp:3454 src/libslic3r/PrintConfig.cpp:3455 +#: src/libslic3r/PrintConfig.cpp:3453 src/libslic3r/PrintConfig.cpp:3454 msgid "Maximum initial exposure time" msgstr "Maximale initiële belichtingstijd" -#: src/libslic3r/PrintConfig.cpp:3462 src/libslic3r/PrintConfig.cpp:3463 +#: src/libslic3r/PrintConfig.cpp:3461 src/libslic3r/PrintConfig.cpp:3462 msgid "Initial exposure time" msgstr "Initiële belichtingstijd" -#: src/libslic3r/PrintConfig.cpp:3469 src/libslic3r/PrintConfig.cpp:3470 +#: src/libslic3r/PrintConfig.cpp:3468 src/libslic3r/PrintConfig.cpp:3469 msgid "Correction for expansion" msgstr "Vergrotingscorrectie" -#: src/libslic3r/PrintConfig.cpp:3476 src/libslic3r/PrintConfig.cpp:3477 +#: src/libslic3r/PrintConfig.cpp:3475 src/libslic3r/PrintConfig.cpp:3476 msgid "Correction for expansion in X axis" msgstr "Uitzettingscorrectie over de X-as" -#: src/libslic3r/PrintConfig.cpp:3483 src/libslic3r/PrintConfig.cpp:3484 +#: src/libslic3r/PrintConfig.cpp:3482 src/libslic3r/PrintConfig.cpp:3483 msgid "Correction for expansion in Y axis" msgstr "Uitzettingscorrectie over de Y-as" -#: src/libslic3r/PrintConfig.cpp:3490 src/libslic3r/PrintConfig.cpp:3491 +#: src/libslic3r/PrintConfig.cpp:3489 src/libslic3r/PrintConfig.cpp:3490 msgid "Correction for expansion in Z axis" msgstr "Uitzettingscorrectie over de Z-as" -#: src/libslic3r/PrintConfig.cpp:3497 +#: src/libslic3r/PrintConfig.cpp:3496 msgid "SLA print material notes" msgstr "SLA-printmateriaal opmerkingen" -#: src/libslic3r/PrintConfig.cpp:3498 +#: src/libslic3r/PrintConfig.cpp:3497 msgid "You can put your notes regarding the SLA print material here." msgstr "U kunt hier opmerkingen plaatsen wat betreft het SLA-materiaal." -#: src/libslic3r/PrintConfig.cpp:3510 src/libslic3r/PrintConfig.cpp:3521 +#: src/libslic3r/PrintConfig.cpp:3509 src/libslic3r/PrintConfig.cpp:3520 msgid "Default SLA material profile" msgstr "Standaard SLA-materiaalprofiel" -#: src/libslic3r/PrintConfig.cpp:3532 +#: src/libslic3r/PrintConfig.cpp:3531 msgid "Generate supports" msgstr "Genereer support" -#: src/libslic3r/PrintConfig.cpp:3534 +#: src/libslic3r/PrintConfig.cpp:3533 msgid "Generate supports for the models" msgstr "Genereer support voor de modellen" -#: src/libslic3r/PrintConfig.cpp:3539 +#: src/libslic3r/PrintConfig.cpp:3538 msgid "Pinhead front diameter" msgstr "Diameter voorzijde pinkop" -#: src/libslic3r/PrintConfig.cpp:3541 +#: src/libslic3r/PrintConfig.cpp:3540 msgid "Diameter of the pointing side of the head" msgstr "Diameter van de puntige zijde van de kop" -#: src/libslic3r/PrintConfig.cpp:3548 +#: src/libslic3r/PrintConfig.cpp:3547 msgid "Head penetration" msgstr "Koppenetratie" -#: src/libslic3r/PrintConfig.cpp:3550 +#: src/libslic3r/PrintConfig.cpp:3549 msgid "How much the pinhead has to penetrate the model surface" msgstr "Hoe ver de supportkop in het model moet steken" -#: src/libslic3r/PrintConfig.cpp:3557 +#: src/libslic3r/PrintConfig.cpp:3556 msgid "Pinhead width" msgstr "Pinkopbreedte" -#: src/libslic3r/PrintConfig.cpp:3559 +#: src/libslic3r/PrintConfig.cpp:3558 msgid "Width from the back sphere center to the front sphere center" msgstr "Centerafstand van de achterste tot de voorste bol" -#: src/libslic3r/PrintConfig.cpp:3567 +#: src/libslic3r/PrintConfig.cpp:3566 msgid "Pillar diameter" msgstr "Pijlerdiameter" -#: src/libslic3r/PrintConfig.cpp:3569 +#: src/libslic3r/PrintConfig.cpp:3568 msgid "Diameter in mm of the support pillars" msgstr "Diameter van de supportpijlers (in mm)" -#: src/libslic3r/PrintConfig.cpp:3577 +#: src/libslic3r/PrintConfig.cpp:3576 msgid "Small pillar diameter percent" msgstr "Percentage van smalle pijlerdiameter" -#: src/libslic3r/PrintConfig.cpp:3579 +#: src/libslic3r/PrintConfig.cpp:3578 msgid "" "The percentage of smaller pillars compared to the normal pillar diameter " "which are used in problematic areas where a normal pilla cannot fit." @@ -14756,11 +14863,11 @@ msgstr "" "Het percentage van smallere pijlers vergeleken met normale pijlerdiameters " "die worden gebruikt in moeilijk te bereiken plekken." -#: src/libslic3r/PrintConfig.cpp:3588 +#: src/libslic3r/PrintConfig.cpp:3587 msgid "Max bridges on a pillar" msgstr "Maximaal aantal bruggen op een pijler" -#: src/libslic3r/PrintConfig.cpp:3590 +#: src/libslic3r/PrintConfig.cpp:3589 msgid "" "Maximum number of bridges that can be placed on a pillar. Bridges hold " "support point pinheads and connect to pillars as small branches." @@ -14768,11 +14875,11 @@ msgstr "" "Maximaal aantal bruggen dat op een pijler geplaatst kan worden. Bruggen " "houden supportpuntkop bij elkaar en verbinden pijlers as smalle takken." -#: src/libslic3r/PrintConfig.cpp:3598 +#: src/libslic3r/PrintConfig.cpp:3597 msgid "Pillar connection mode" msgstr "Pijlerverbindingsmodus" -#: src/libslic3r/PrintConfig.cpp:3599 +#: src/libslic3r/PrintConfig.cpp:3598 msgid "" "Controls the bridge type between two neighboring pillars. Can be zig-zag, " "cross (double zig-zag) or dynamic which will automatically switch between " @@ -14782,23 +14889,23 @@ msgstr "" "kruisend (dubbele zigzag) of dynamisch zijn. Dynamisch houdt in dat wordt " "geschakeld tussen de eerste twee, afhankelijk van de pijlerafstand." -#: src/libslic3r/PrintConfig.cpp:3607 +#: src/libslic3r/PrintConfig.cpp:3606 msgid "Zig-Zag" msgstr "Zigzag" -#: src/libslic3r/PrintConfig.cpp:3608 +#: src/libslic3r/PrintConfig.cpp:3607 msgid "Cross" msgstr "Kruisend" -#: src/libslic3r/PrintConfig.cpp:3609 +#: src/libslic3r/PrintConfig.cpp:3608 msgid "Dynamic" msgstr "Dynamisch" -#: src/libslic3r/PrintConfig.cpp:3621 +#: src/libslic3r/PrintConfig.cpp:3620 msgid "Pillar widening factor" msgstr "Pijlervergrotingsfactor" -#: src/libslic3r/PrintConfig.cpp:3623 +#: src/libslic3r/PrintConfig.cpp:3622 msgid "" "Merging bridges or pillars into another pillars can increase the radius. " "Zero means no increase, one means full increase." @@ -14806,27 +14913,27 @@ msgstr "" "Bruggen of pijlers samenvoegen met andere pijlers kan de radius vergroten. 0 " "betekent geen vergroting, 1 betekent volle vergroting." -#: src/libslic3r/PrintConfig.cpp:3632 +#: src/libslic3r/PrintConfig.cpp:3631 msgid "Support base diameter" msgstr "Supportbasis - diameter" -#: src/libslic3r/PrintConfig.cpp:3634 +#: src/libslic3r/PrintConfig.cpp:3633 msgid "Diameter in mm of the pillar base" msgstr "Diameter van de pijlerbasis (in mm)" -#: src/libslic3r/PrintConfig.cpp:3642 +#: src/libslic3r/PrintConfig.cpp:3641 msgid "Support base height" msgstr "Supportbasis - hoogte" -#: src/libslic3r/PrintConfig.cpp:3644 +#: src/libslic3r/PrintConfig.cpp:3643 msgid "The height of the pillar base cone" msgstr "Hoogte van de pijlerbasiskegel" -#: src/libslic3r/PrintConfig.cpp:3651 +#: src/libslic3r/PrintConfig.cpp:3650 msgid "Support base safety distance" msgstr "Supportbasis - veilige afstand" -#: src/libslic3r/PrintConfig.cpp:3654 +#: src/libslic3r/PrintConfig.cpp:3653 msgid "" "The minimum distance of the pillar base from the model in mm. Makes sense in " "zero elevation mode where a gap according to this parameter is inserted " @@ -14836,27 +14943,27 @@ msgstr "" "modus zonder verhoging waar een gat volgens deze parameter wordt ingevoegd " "tussen het model en de basisplaat." -#: src/libslic3r/PrintConfig.cpp:3664 +#: src/libslic3r/PrintConfig.cpp:3663 msgid "Critical angle" msgstr "Kritische hoek" -#: src/libslic3r/PrintConfig.cpp:3666 +#: src/libslic3r/PrintConfig.cpp:3665 msgid "The default angle for connecting support sticks and junctions." msgstr "De standaardhoek voor de verbinding van supporttakken en kruisingen." -#: src/libslic3r/PrintConfig.cpp:3674 +#: src/libslic3r/PrintConfig.cpp:3673 msgid "Max bridge length" msgstr "Maximale bruglengte" -#: src/libslic3r/PrintConfig.cpp:3676 +#: src/libslic3r/PrintConfig.cpp:3675 msgid "The max length of a bridge" msgstr "Maximale bruglengte" -#: src/libslic3r/PrintConfig.cpp:3683 +#: src/libslic3r/PrintConfig.cpp:3682 msgid "Max pillar linking distance" msgstr "Maximale pijler-verbindafstand" -#: src/libslic3r/PrintConfig.cpp:3685 +#: src/libslic3r/PrintConfig.cpp:3684 msgid "" "The max distance of two pillars to get linked with each other. A zero value " "will prohibit pillar cascading." @@ -14864,7 +14971,7 @@ msgstr "" "Maximale verbindingsafstand van twee pijlers. Een waarde van 0 schakelt aan " "elkaar verbonden pijlers uit." -#: src/libslic3r/PrintConfig.cpp:3695 +#: src/libslic3r/PrintConfig.cpp:3694 msgid "" "How much the supports should lift up the supported object. If \"Pad around " "object\" is enabled, this value is ignored." @@ -14872,39 +14979,39 @@ msgstr "" "Hoe veel het support omhoog moet bewegen op het ondersteunde object. Als " "'Basisplaat rondom object' is ingeschakeld wordt deze waarde genegeerd." -#: src/libslic3r/PrintConfig.cpp:3706 +#: src/libslic3r/PrintConfig.cpp:3705 msgid "This is a relative measure of support points density." msgstr "Relatieve waarde van de dichtheid van supportpunten." -#: src/libslic3r/PrintConfig.cpp:3712 +#: src/libslic3r/PrintConfig.cpp:3711 msgid "Minimal distance of the support points" msgstr "Minimale supportpuntafstand" -#: src/libslic3r/PrintConfig.cpp:3714 +#: src/libslic3r/PrintConfig.cpp:3713 msgid "No support points will be placed closer than this threshold." msgstr "Minimale afstand tussen supportpunten." -#: src/libslic3r/PrintConfig.cpp:3720 +#: src/libslic3r/PrintConfig.cpp:3719 msgid "Use pad" msgstr "Gebruik basisplaat" -#: src/libslic3r/PrintConfig.cpp:3722 +#: src/libslic3r/PrintConfig.cpp:3721 msgid "Add a pad underneath the supported model" msgstr "Voeg een basisplaat toe onder het model met support" -#: src/libslic3r/PrintConfig.cpp:3727 +#: src/libslic3r/PrintConfig.cpp:3726 msgid "Pad wall thickness" msgstr "Basisplaat - wanddikte" -#: src/libslic3r/PrintConfig.cpp:3729 +#: src/libslic3r/PrintConfig.cpp:3728 msgid "The thickness of the pad and its optional cavity walls." msgstr "Dikte van de basisplaat en optionele wanden." -#: src/libslic3r/PrintConfig.cpp:3737 +#: src/libslic3r/PrintConfig.cpp:3736 msgid "Pad wall height" msgstr "Basisplaat - wandhoogte" -#: src/libslic3r/PrintConfig.cpp:3738 +#: src/libslic3r/PrintConfig.cpp:3737 msgid "" "Defines the pad cavity depth. Set to zero to disable the cavity. Be careful " "when enabling this feature, as some resins may produce an extreme suction " @@ -14916,19 +15023,19 @@ msgstr "" "sommige resins een sterk zuigeffect in de holte produceren, wat het afpellen " "van de print van het folie lastig kan maken." -#: src/libslic3r/PrintConfig.cpp:3751 +#: src/libslic3r/PrintConfig.cpp:3750 msgid "Pad brim size" msgstr "Basisplaat - expansie" -#: src/libslic3r/PrintConfig.cpp:3752 +#: src/libslic3r/PrintConfig.cpp:3751 msgid "How far should the pad extend around the contained geometry" msgstr "Hoe ver de basisplaat moet uitsteken buiten de geometrie" -#: src/libslic3r/PrintConfig.cpp:3762 +#: src/libslic3r/PrintConfig.cpp:3761 msgid "Max merge distance" msgstr "Maximale combineerafstand" -#: src/libslic3r/PrintConfig.cpp:3764 +#: src/libslic3r/PrintConfig.cpp:3763 msgid "" "Some objects can get along with a few smaller pads instead of a single big " "one. This parameter defines how far the center of two smaller pads should " @@ -14938,11 +15045,11 @@ msgstr "" "van één grote. Deze parameter bepaalt hoe ver de tussenafstand van de " "kleinere basisplaten mogen zijn." -#: src/libslic3r/PrintConfig.cpp:3784 +#: src/libslic3r/PrintConfig.cpp:3783 msgid "Pad wall slope" msgstr "Basisplaat - zijhoek" -#: src/libslic3r/PrintConfig.cpp:3786 +#: src/libslic3r/PrintConfig.cpp:3785 msgid "" "The slope of the pad wall relative to the bed plane. 90 degrees means " "straight walls." @@ -14950,23 +15057,23 @@ msgstr "" "Hoek van de basisplaatzijde ten opzichte van het bed. 90 graden betekent een " "rechte zijkant." -#: src/libslic3r/PrintConfig.cpp:3797 +#: src/libslic3r/PrintConfig.cpp:3796 msgid "Create pad around object and ignore the support elevation" msgstr "Genereer basisplaat rondom object en schakel objectverhoging uit" -#: src/libslic3r/PrintConfig.cpp:3802 +#: src/libslic3r/PrintConfig.cpp:3801 msgid "Pad around object everywhere" msgstr "Overal basisplaat rondom object" -#: src/libslic3r/PrintConfig.cpp:3804 +#: src/libslic3r/PrintConfig.cpp:3803 msgid "Force pad around object everywhere" msgstr "Forceer basisplaat overal rondom het object" -#: src/libslic3r/PrintConfig.cpp:3809 +#: src/libslic3r/PrintConfig.cpp:3808 msgid "Pad object gap" msgstr "Basisplaat - gat" -#: src/libslic3r/PrintConfig.cpp:3811 +#: src/libslic3r/PrintConfig.cpp:3810 msgid "" "The gap between the object bottom and the generated pad in zero elevation " "mode." @@ -14974,11 +15081,11 @@ msgstr "" "Het gat tussen de onderkant van het object en de gegenereerde basisplaat in " "de modus zonder verhoging." -#: src/libslic3r/PrintConfig.cpp:3820 +#: src/libslic3r/PrintConfig.cpp:3819 msgid "Pad object connector stride" msgstr "Basisplaat - verbindingstakafstand" -#: src/libslic3r/PrintConfig.cpp:3822 +#: src/libslic3r/PrintConfig.cpp:3821 msgid "" "Distance between two connector sticks which connect the object and the " "generated pad." @@ -14986,46 +15093,46 @@ msgstr "" "Afstand tussen twee verbindingstakken die het object verbinden aan de " "basisplaat." -#: src/libslic3r/PrintConfig.cpp:3829 +#: src/libslic3r/PrintConfig.cpp:3828 msgid "Pad object connector width" msgstr "Basisplaat - verbindingstakbreedte" -#: src/libslic3r/PrintConfig.cpp:3831 +#: src/libslic3r/PrintConfig.cpp:3830 msgid "" "Width of the connector sticks which connect the object and the generated pad." msgstr "" "Breedte van de verbindingstakken die het object en de basisplaat met elkaar " "verbinden." -#: src/libslic3r/PrintConfig.cpp:3838 +#: src/libslic3r/PrintConfig.cpp:3837 msgid "Pad object connector penetration" msgstr "Basisplaat - Verbindingstakinsteek" -#: src/libslic3r/PrintConfig.cpp:3841 +#: src/libslic3r/PrintConfig.cpp:3840 msgid "How much should the tiny connectors penetrate into the model body." msgstr "Hoe ver de verbindingstakken in het model steken." -#: src/libslic3r/PrintConfig.cpp:3848 +#: src/libslic3r/PrintConfig.cpp:3847 msgid "Enable hollowing" msgstr "Uithollen toestaan" -#: src/libslic3r/PrintConfig.cpp:3850 +#: src/libslic3r/PrintConfig.cpp:3849 msgid "Hollow out a model to have an empty interior" msgstr "Hol een model uit voor een leeg binnenste" -#: src/libslic3r/PrintConfig.cpp:3855 +#: src/libslic3r/PrintConfig.cpp:3854 msgid "Wall thickness" msgstr "Wanddikte" -#: src/libslic3r/PrintConfig.cpp:3857 +#: src/libslic3r/PrintConfig.cpp:3856 msgid "Minimum wall thickness of a hollowed model." msgstr "Minimale wanddikte van een uitgehold model." -#: src/libslic3r/PrintConfig.cpp:3865 +#: src/libslic3r/PrintConfig.cpp:3864 msgid "Accuracy" msgstr "Nauwkeurigheid" -#: src/libslic3r/PrintConfig.cpp:3867 +#: src/libslic3r/PrintConfig.cpp:3866 msgid "" "Performance vs accuracy of calculation. Lower values may produce unwanted " "artifacts." @@ -15033,7 +15140,7 @@ msgstr "" "Prestatie tegenover nauwkeurigheid van berekenen. Lagere waarde kunnen " "ongewenste artefacten produceren." -#: src/libslic3r/PrintConfig.cpp:3877 +#: src/libslic3r/PrintConfig.cpp:3876 msgid "" "Hollowing is done in two steps: first, an imaginary interior is calculated " "deeper (offset plus the closing distance) in the object and then it's " @@ -15047,11 +15154,11 @@ msgstr "" "binnenste ronder. Bij een waarde van 0 is het binnenste vrijwel gelijk aan " "de buitenzijde." -#: src/libslic3r/PrintConfig.cpp:3889 +#: src/libslic3r/PrintConfig.cpp:3888 msgid "Print speed" msgstr "Printsnelheid" -#: src/libslic3r/PrintConfig.cpp:3891 +#: src/libslic3r/PrintConfig.cpp:3890 msgid "" "A slower printing profile might be necessary when using materials with " "higher viscosity or with some hollowed parts. It slows down the tilt " @@ -15061,63 +15168,63 @@ msgstr "" "vloeibaarheid of met holle delen. Het vertraagt de kantelbeweging en voegt " "een vertraging toe na de belichting." -#: src/libslic3r/PrintConfig.cpp:4359 +#: src/libslic3r/PrintConfig.cpp:4358 msgid "Export OBJ" msgstr "Exporteer OBJ" -#: src/libslic3r/PrintConfig.cpp:4360 +#: src/libslic3r/PrintConfig.cpp:4359 msgid "Export the model(s) as OBJ." -msgstr "Exporteer de modellen als OBJ-bestand." +msgstr "Exporteer de modellen als .OBJ-bestand." -#: src/libslic3r/PrintConfig.cpp:4371 +#: src/libslic3r/PrintConfig.cpp:4370 msgid "Export SLA" msgstr "Exporteer SLA" -#: src/libslic3r/PrintConfig.cpp:4372 +#: src/libslic3r/PrintConfig.cpp:4371 msgid "Slice the model and export SLA printing layers as PNG." -msgstr "Slice het model en exporteer SLA-printlagen als PNG-bestanden." +msgstr "Slice het model en exporteer SLA-printlagen als .PNG-bestanden." -#: src/libslic3r/PrintConfig.cpp:4377 +#: src/libslic3r/PrintConfig.cpp:4376 msgid "Export 3MF" msgstr "Exporteer 3MF" -#: src/libslic3r/PrintConfig.cpp:4378 +#: src/libslic3r/PrintConfig.cpp:4377 msgid "Export the model(s) as 3MF." -msgstr "Exporteer de modellen als 3MF-bestanden." +msgstr "Exporteer de modellen als .3MF-bestanden." -#: src/libslic3r/PrintConfig.cpp:4382 +#: src/libslic3r/PrintConfig.cpp:4381 msgid "Export AMF" msgstr "Exporteer AMF" -#: src/libslic3r/PrintConfig.cpp:4383 +#: src/libslic3r/PrintConfig.cpp:4382 msgid "Export the model(s) as AMF." -msgstr "Exporteer de modellen als AMF-bestanden." +msgstr "Exporteer de modellen als .AMF-bestanden." -#: src/libslic3r/PrintConfig.cpp:4387 +#: src/libslic3r/PrintConfig.cpp:4386 msgid "Export STL" msgstr "Exporteer STL" -#: src/libslic3r/PrintConfig.cpp:4388 +#: src/libslic3r/PrintConfig.cpp:4387 msgid "Export the model(s) as STL." -msgstr "Exporteer de modellen als STL-bestand." +msgstr "Exporteer de modellen als .STL-bestand." -#: src/libslic3r/PrintConfig.cpp:4393 +#: src/libslic3r/PrintConfig.cpp:4392 msgid "Slice the model and export toolpaths as G-code." -msgstr "Slice het model en exporteer de paden als G-code-bestand." +msgstr "Slice het model en exporteer de paden als .gcode-bestand." -#: src/libslic3r/PrintConfig.cpp:4398 +#: src/libslic3r/PrintConfig.cpp:4397 msgid "G-code viewer" msgstr "G-code weergave" -#: src/libslic3r/PrintConfig.cpp:4399 +#: src/libslic3r/PrintConfig.cpp:4398 msgid "Visualize an already sliced and saved G-code" msgstr "Visualiseer een reeds opgeslagen G-code" -#: src/libslic3r/PrintConfig.cpp:4404 +#: src/libslic3r/PrintConfig.cpp:4403 msgid "Slice" msgstr "Slice" -#: src/libslic3r/PrintConfig.cpp:4405 +#: src/libslic3r/PrintConfig.cpp:4404 msgid "" "Slice the model as FFF or SLA based on the printer_technology configuration " "value." @@ -15125,71 +15232,71 @@ msgstr "" "Slice het model als FFF of SLA, gebaseerd op de 'printer_technology'-" "configuratiewaarde." -#: src/libslic3r/PrintConfig.cpp:4410 +#: src/libslic3r/PrintConfig.cpp:4409 msgid "Help" msgstr "Help" -#: src/libslic3r/PrintConfig.cpp:4411 +#: src/libslic3r/PrintConfig.cpp:4410 msgid "Show this help." msgstr "Toon deze hulp zien." -#: src/libslic3r/PrintConfig.cpp:4416 +#: src/libslic3r/PrintConfig.cpp:4415 msgid "Help (FFF options)" msgstr "Help (FFF-opties)" -#: src/libslic3r/PrintConfig.cpp:4417 +#: src/libslic3r/PrintConfig.cpp:4416 msgid "Show the full list of print/G-code configuration options." msgstr "Toon de volledige lijst van print- of G-code-configuratie-opties." -#: src/libslic3r/PrintConfig.cpp:4421 +#: src/libslic3r/PrintConfig.cpp:4420 msgid "Help (SLA options)" msgstr "Help (SLA opties)" -#: src/libslic3r/PrintConfig.cpp:4422 +#: src/libslic3r/PrintConfig.cpp:4421 msgid "Show the full list of SLA print configuration options." msgstr "Toon de volledige lijst van SLA-printconfiguratie-opties." -#: src/libslic3r/PrintConfig.cpp:4426 +#: src/libslic3r/PrintConfig.cpp:4425 msgid "Output Model Info" msgstr "Output model-info" -#: src/libslic3r/PrintConfig.cpp:4427 +#: src/libslic3r/PrintConfig.cpp:4426 msgid "Write information about the model to the console." msgstr "Schrijf informatie over het model naar de console." -#: src/libslic3r/PrintConfig.cpp:4431 +#: src/libslic3r/PrintConfig.cpp:4430 msgid "Save config file" msgstr "Sla configuratiebestand op" -#: src/libslic3r/PrintConfig.cpp:4432 +#: src/libslic3r/PrintConfig.cpp:4431 msgid "Save configuration to the specified file." msgstr "Sla configuratie op in aangegeven bestand." -#: src/libslic3r/PrintConfig.cpp:4442 +#: src/libslic3r/PrintConfig.cpp:4441 msgid "Align XY" msgstr "XY uitlijnen" -#: src/libslic3r/PrintConfig.cpp:4443 +#: src/libslic3r/PrintConfig.cpp:4442 msgid "Align the model to the given point." msgstr "Lijn de modellen uit op het gegeven punt." -#: src/libslic3r/PrintConfig.cpp:4448 +#: src/libslic3r/PrintConfig.cpp:4447 msgid "Cut model at the given Z." msgstr "Snijdt model op de ingestelde hoogte." -#: src/libslic3r/PrintConfig.cpp:4469 +#: src/libslic3r/PrintConfig.cpp:4468 msgid "Center" msgstr "Centreer" -#: src/libslic3r/PrintConfig.cpp:4470 +#: src/libslic3r/PrintConfig.cpp:4469 msgid "Center the print around the given center." msgstr "Centreer de print op het middelpunt." -#: src/libslic3r/PrintConfig.cpp:4474 +#: src/libslic3r/PrintConfig.cpp:4473 msgid "Don't arrange" msgstr "Niet schikken" -#: src/libslic3r/PrintConfig.cpp:4475 +#: src/libslic3r/PrintConfig.cpp:4474 msgid "" "Do not rearrange the given models before merging and keep their original XY " "coordinates." @@ -15197,11 +15304,11 @@ msgstr "" "Herschik de modellen niet voor het samenvoegen en behoudt de originele X- en " "Y-coördinaten." -#: src/libslic3r/PrintConfig.cpp:4478 +#: src/libslic3r/PrintConfig.cpp:4477 msgid "Ensure on bed" msgstr "Plaats op bed" -#: src/libslic3r/PrintConfig.cpp:4479 +#: src/libslic3r/PrintConfig.cpp:4478 msgid "" "Lift the object above the bed when it is partially below. Enabled by " "default, use --no-ensure-on-bed to disable." @@ -15209,23 +15316,23 @@ msgstr "" "Til het object boven het bed als deze er gedeeltelijk onder valt. Staat " "standaard aan. Gebruik \"no_ensure_on_bed\" om uit te zetten." -#: src/libslic3r/PrintConfig.cpp:4483 +#: src/libslic3r/PrintConfig.cpp:4482 msgid "Duplicate" msgstr "Dupliceer" -#: src/libslic3r/PrintConfig.cpp:4484 +#: src/libslic3r/PrintConfig.cpp:4483 msgid "Multiply copies by this factor." msgstr "Meerdere kopieën van dit aantal." -#: src/libslic3r/PrintConfig.cpp:4488 +#: src/libslic3r/PrintConfig.cpp:4487 msgid "Duplicate by grid" msgstr "Dupliceer in raster" -#: src/libslic3r/PrintConfig.cpp:4489 +#: src/libslic3r/PrintConfig.cpp:4488 msgid "Multiply copies by creating a grid." msgstr "Meerdere kopieën in raster." -#: src/libslic3r/PrintConfig.cpp:4493 +#: src/libslic3r/PrintConfig.cpp:4492 msgid "" "Arrange the supplied models in a plate and merge them in a single model in " "order to perform actions once." @@ -15233,7 +15340,7 @@ msgstr "" "Schik de toegevoegde modellen en combineer ze tot één model om eenmalig " "acties uit te voeren." -#: src/libslic3r/PrintConfig.cpp:4498 +#: src/libslic3r/PrintConfig.cpp:4497 msgid "" "Try to repair any non-manifold meshes (this option is implicitly added " "whenever we need to slice the model to perform the requested action)." @@ -15241,31 +15348,31 @@ msgstr "" "Probeer alle niet-gesloten meshes te repareren (deze optie is impliciet " "toegevoegd om, wanneer dat nodig is, onmogelijke modellen toch te slicen)." -#: src/libslic3r/PrintConfig.cpp:4502 +#: src/libslic3r/PrintConfig.cpp:4501 msgid "Rotation angle around the Z axis in degrees." msgstr "Rotatiehoek rond de Z-as in graden." -#: src/libslic3r/PrintConfig.cpp:4506 +#: src/libslic3r/PrintConfig.cpp:4505 msgid "Rotate around X" msgstr "Draai over de X-as" -#: src/libslic3r/PrintConfig.cpp:4507 +#: src/libslic3r/PrintConfig.cpp:4506 msgid "Rotation angle around the X axis in degrees." msgstr "Rotatiehoek rond de X-as in graden." -#: src/libslic3r/PrintConfig.cpp:4511 +#: src/libslic3r/PrintConfig.cpp:4510 msgid "Rotate around Y" msgstr "Draai over de Y-as" -#: src/libslic3r/PrintConfig.cpp:4512 +#: src/libslic3r/PrintConfig.cpp:4511 msgid "Rotation angle around the Y axis in degrees." msgstr "Rotatiehoek rond de Y-as in graden." -#: src/libslic3r/PrintConfig.cpp:4517 +#: src/libslic3r/PrintConfig.cpp:4516 msgid "Scaling factor or percentage." msgstr "Schalingsfactor of percentage." -#: src/libslic3r/PrintConfig.cpp:4522 +#: src/libslic3r/PrintConfig.cpp:4521 msgid "" "Detect unconnected parts in the given model(s) and split them into separate " "objects." @@ -15273,23 +15380,23 @@ msgstr "" "Detecteer niet-verbonden onderdelen in het model en deel ze op in " "verschillende objecten." -#: src/libslic3r/PrintConfig.cpp:4525 +#: src/libslic3r/PrintConfig.cpp:4524 msgid "Scale to Fit" msgstr "Verschaal naar passing" -#: src/libslic3r/PrintConfig.cpp:4526 +#: src/libslic3r/PrintConfig.cpp:4525 msgid "Scale to fit the given volume." msgstr "Verschaal naar passing in het gegeven volume." -#: src/libslic3r/PrintConfig.cpp:4535 +#: src/libslic3r/PrintConfig.cpp:4534 msgid "Ignore non-existent config files" msgstr "Negeer niet-bestaande configuratiebestanden" -#: src/libslic3r/PrintConfig.cpp:4536 +#: src/libslic3r/PrintConfig.cpp:4535 msgid "Do not fail if a file supplied to --load does not exist." msgstr "Geef geen fout als een bestand om te laden niet bestaat." -#: src/libslic3r/PrintConfig.cpp:4539 +#: src/libslic3r/PrintConfig.cpp:4538 msgid "" "Forward-compatibility rule when loading configurations from config files and " "project files (3MF, AMF)." @@ -15297,7 +15404,7 @@ msgstr "" "Doorgang-compatibiliteitsregel bij het laden van configuraties van " "configuratie- en projectbestanden (3MF, AMF)." -#: src/libslic3r/PrintConfig.cpp:4540 +#: src/libslic3r/PrintConfig.cpp:4539 msgid "" "This version of PrusaSlicer may not understand configurations produced by " "the newest PrusaSlicer versions. For example, newer PrusaSlicer may extend " @@ -15310,11 +15417,11 @@ msgstr "" "onbekende waarde zonder melding of woordelijk te vervangen door een " "standaardwaarde." -#: src/libslic3r/PrintConfig.cpp:4547 +#: src/libslic3r/PrintConfig.cpp:4546 msgid "Bail out on unknown configuration values" msgstr "Sla onbekende configuratiewaarden over" -#: src/libslic3r/PrintConfig.cpp:4548 +#: src/libslic3r/PrintConfig.cpp:4547 msgid "" "Enable reading unknown configuration values by verbosely substituting them " "with defaults." @@ -15322,7 +15429,7 @@ msgstr "" "Sta to om onbekende configuratiewaarden te lezen door woordelijk te " "substitueren met standaardwaarden." -#: src/libslic3r/PrintConfig.cpp:4549 +#: src/libslic3r/PrintConfig.cpp:4548 msgid "" "Enable reading unknown configuration values by silently substituting them " "with defaults." @@ -15330,11 +15437,11 @@ msgstr "" "Sta to om onbekende configuratiewaarden te lezen door zonder melding te " "substitueren met standaardwaarden." -#: src/libslic3r/PrintConfig.cpp:4553 +#: src/libslic3r/PrintConfig.cpp:4552 msgid "Load config file" msgstr "Laad configuratiebestand" -#: src/libslic3r/PrintConfig.cpp:4554 +#: src/libslic3r/PrintConfig.cpp:4553 msgid "" "Load configuration from the specified file. It can be used more than once to " "load options from multiple files." @@ -15342,11 +15449,11 @@ msgstr "" "Laad configuratie uit een specifiek bestand. Dit kan meerdere keren gebruikt " "worden om instellingen uit meerdere bestanden te laden." -#: src/libslic3r/PrintConfig.cpp:4557 +#: src/libslic3r/PrintConfig.cpp:4556 msgid "Output File" msgstr "Outputbestand" -#: src/libslic3r/PrintConfig.cpp:4558 +#: src/libslic3r/PrintConfig.cpp:4557 msgid "" "The file where the output will be written (if not specified, it will be " "based on the input file)." @@ -15354,11 +15461,11 @@ msgstr "" "Het bestand waaroverheen wordt geschreven (als dit niet aangegeven is, wort " "dit gebaseerd op het inputbestand)." -#: src/libslic3r/PrintConfig.cpp:4562 +#: src/libslic3r/PrintConfig.cpp:4561 msgid "Single instance mode" msgstr "Enkele instantiemodus" -#: src/libslic3r/PrintConfig.cpp:4563 +#: src/libslic3r/PrintConfig.cpp:4562 msgid "" "If enabled, the command line arguments are sent to an existing instance of " "GUI PrusaSlicer, or an existing PrusaSlicer window is activated. Overrides " @@ -15369,11 +15476,11 @@ msgstr "" "gestuurd. Dit overschrijft de \"enkele instantie\"-configuratiewaarde van de " "programmavoorkeuren." -#: src/libslic3r/PrintConfig.cpp:4574 +#: src/libslic3r/PrintConfig.cpp:4573 msgid "Data directory" msgstr "Bestandslocatie voor de data" -#: src/libslic3r/PrintConfig.cpp:4575 +#: src/libslic3r/PrintConfig.cpp:4574 msgid "" "Load and store settings at the given directory. This is useful for " "maintaining different profiles or including configurations from a network " @@ -15383,11 +15490,11 @@ msgstr "" "verschillende profielen of het opnemen van configuraties van een " "netwerkopslag." -#: src/libslic3r/PrintConfig.cpp:4578 +#: src/libslic3r/PrintConfig.cpp:4577 msgid "Logging level" msgstr "Logboekniveau" -#: src/libslic3r/PrintConfig.cpp:4579 +#: src/libslic3r/PrintConfig.cpp:4578 msgid "" "Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:" "trace\n" @@ -15397,11 +15504,11 @@ msgstr "" "debug, 5: traceer\n" "Voorbeeld: loglevel = 2 geeft fataal-, fout- en waarschuwingslevelberichten." -#: src/libslic3r/PrintConfig.cpp:4585 +#: src/libslic3r/PrintConfig.cpp:4584 msgid "Render with a software renderer" msgstr "Render met software-renderer" -#: src/libslic3r/PrintConfig.cpp:4586 +#: src/libslic3r/PrintConfig.cpp:4585 msgid "" "Render with a software renderer. The bundled MESA software renderer is " "loaded instead of the default OpenGL driver." @@ -15432,6 +15539,10 @@ msgid "" "your models using theFuzzy skinfeature? You can also use modifiers to " "apply fuzzy-skin only to a portion of your model." msgstr "" +"Oneffen oppervlak\n" +"Wist u dat u een vezelachtige textuur op de zijden van uw object kan " +"aanbrengen met de Oneffen oppervlak optie? U kunt ook modificators " +"toepassen op een oneffen oppervlak voor een gedeelte van het model." #: resources/data/hints.ini: [hint:Shapes gallery] msgid "" @@ -15440,6 +15551,11 @@ msgid "" "models as modifiers, negative volumes or as printable objects. Right-click " "the platter and selectAdd Shape - Gallery." msgstr "" +"Vormengalerij\n" +"Wist u dat PrusaSlicer een vormengalerij heeft? U kunt de vormen daaruit " +"gebruiken als modificators, negatieve volumes, of als printbare objecten. " +"Rechtermuisklik in de modelweergave en selecteer Voeg vorm toe - " +"galerij." #: resources/data/hints.ini: [hint:Arrange settings] msgid "" @@ -15447,6 +15563,9 @@ msgid "" "Did you know that you can right-click theArrange iconto adjust the " "size of the gap between objects and to allow automatic rotations?" msgstr "" +"Schikken-instellingen\n" +"Wist u dat u met de rechtermuisklik op het Schikken icoon de afstand " +"tussen de objecten en automatisch roteren kunt instellen?" #: resources/data/hints.ini: [hint:Negative volume] msgid "" @@ -15456,6 +15575,11 @@ msgid "" "holes directly in PrusaSlicer. Read more in the documentation. (Requires " "Advanced or Expert mode.)" msgstr "" +"Negatief volume\n" +"Wist u dat u volumes van elkaar kunt aftrekken met een negatief volume als " +"modificator? Daarmee kunt u bijvoorbeeld makkelijk verschaalbare gaten " +"aanbrengen, direct in PrusaSlicer. Lees meer in de documentatie (vereist " +"geavanceerde- of expertmodus)." #: resources/data/hints.ini: [hint:Simplify mesh] msgid "" @@ -15464,6 +15588,10 @@ msgid "" "Simplify mesh feature? Right-click the model and select Simplify model. Read " "more in the documentation." msgstr "" +"Mesh vereenvoudigen\n" +"Wist u dat het u het aantal facetten van een mesh kunt reduceren met de Mesh " +"vereenvoudigen optie? Rechtermuisklik op het model en selecteer Mesh " +"vereenvoudigen. Lees meer in de documentatie." #: resources/data/hints.ini: [hint:Reload from disk] msgid "" @@ -15472,6 +15600,10 @@ msgid "" "simply reload it in PrusaSlicer? Right-click the model in the 3D view and " "choose Reload from disk. Read more in the documentation." msgstr "" +"Herlaad van schijf\n" +"Wist u dat als u een nieuwere versie van het model heeft gemaakt, u deze " +"eenvoudig kunt herladen in PrusaSlicer? Rechtermuisklik op het model in de " +"modelweergave en kies Herlaad van schijf. Lees meer in de documentatie." #: resources/data/hints.ini: [hint:Hiding sidebar] msgid "" @@ -15479,6 +15611,9 @@ msgid "" "Did you know that you can hide the right sidebar using the shortcut Shift" "+Tab? You can also enable the icon for this from thePreferences." msgstr "" +"Verberg zijbalk\n" +"Wist u dat u de rechter zijbalk kan verbergen met de sneltoets Shift+Tab? U kunt ook het icoontje aanzetten via de Voorkeuren." #: resources/data/hints.ini: [hint:Perspective camera] msgid "" @@ -15486,6 +15621,9 @@ msgid "" "Did you know that you can use the K key to quickly switch between an " "orthographic and perspective camera?" msgstr "" +"Perspectiefweergave\n" +"Wist u dat u de K-toets kunt gebruiken om snel te schakelen tussen " +"orthografische en perspectiefweergave?" #: resources/data/hints.ini: [hint:Camera Views] msgid "" @@ -15493,6 +15631,9 @@ msgid "" "Did you know that you can use the number keys 0-6 to quickly switch " "between predefined camera angles?" msgstr "" +"Weergave\n" +"Wist us dat u de getallen 0-6 kunt gebruiken voor het snel schakelen " +"tussen vooraf ingestelde camerahoeken?" #: resources/data/hints.ini: [hint:Place on face] msgid "" @@ -15501,6 +15642,10 @@ msgid "" "sits on the print bed? Select thePlace on facefunction or press the " "F key." msgstr "" +"Plaats op vlak\n" +"Wist u dat u snel een model zo kan oriënteren dat het met een vlak van het " +"model op het bed geplaatst wordt? Selecteer Plaats op vlak-functie of " +"druk op de F-toets." #: resources/data/hints.ini: [hint:Set number of instances] msgid "" @@ -15508,6 +15653,9 @@ msgid "" "Did you know that you can right-click a model and set an exact number of " "instances instead of copy-pasting it several times?" msgstr "" +"Stel aantal instanties in\n" +"Wist u dat u met de rechtermuisklik op een model een exact aantal instanties " +"kan instellen zonder meerdere keren te kopiëren en plakken?" #: resources/data/hints.ini: [hint:Combine infill] msgid "" @@ -15516,6 +15664,10 @@ msgid "" "compared to perimeters to save print time using the settingCombine infill " "every." msgstr "" +"Combineer vulling\n" +"Wist u dat u tijd kunt besparen door de laagdikte van de vulling te " +"vermenigvuldigen ten opzichte van die van de perimeters met de instelling " +"Combineer vulling elke." #: resources/data/hints.ini: [hint:Variable layer height] msgid "" @@ -15524,6 +15676,10 @@ msgid "" "different layer height and smooth the transitions between them? Try " "theVariable layer height tool. (Not available for SLA printers.)" msgstr "" +"Variabele laagdikte\n" +"Wist u dat u verschillende delen van uw model kunt printen met een " +"verschillende laagdikte om zo transities tussen delen vloeiender te maken? " +"Probeer de Variabele laagdikte. (Niet beschikbaar voor SLA printers.)" #: resources/data/hints.ini: [hint:Undo/redo history] msgid "" @@ -15531,6 +15687,10 @@ msgid "" "Did you know that you can right-click theundo/redo arrowsto see the " "history of changes and to undo or redo several actions at once?" msgstr "" +"Ongedaan maken / Opnieuw doen lijst\n" +"Wist u dat u met de rechtermuisklik op Ongedaan maken / opnieuw doen " +"pijlen kunt klikken voor een lijst van aanpassingen en meerdere acties " +"tegelijk kunt doen?" #: resources/data/hints.ini: [hint:Different layer height for each model] msgid "" @@ -15540,6 +15700,10 @@ msgid "" "Perimeters and adjust the values in the right panel. Read more in the " "documentation." msgstr "" +"Verschillende laagdikte voor elk model\n" +"Wist u dat u elk model op het bed met een andere laagdikte kunt printen? " +"Rechtermuisklik op het model in de modelweergave, kies Lagen en Perimeters " +"en pas de waarde aan in het rechter menu. Lees meer in de documentatie." #: resources/data/hints.ini: [hint:Solid infill threshold area] msgid "" @@ -15548,6 +15712,10 @@ msgid "" "section be filled with solid infill automatically? Set theSolid infill " "threshold area. (Expert mode only.)" msgstr "" +"Dichte vulling bij oppervlak\n" +"Wist u dat u delen van uw model met een klein doorsnee-oppervlak automatisch " +"met dichte vulling kan vullen? Stel de optie Dichte vulling bij " +"oppervlak in. (alleen in expertmodus)." #: resources/data/hints.ini: [hint:Search functionality] msgid "" @@ -15555,6 +15723,10 @@ msgid "" "Did you know that you use theSearchtool to quickly find a specific " "PrusaSlicer setting? Or use the familiar shortcut Ctrl+F." msgstr "" +"Zoekfuncties\n" +"Wist u dat u de Zoekfunctie kunt gebruiken om snel een specifieke " +"instelling in PrusaSlicer te vinden? Of gebruik de bekende sneltoets Ctrl" +"+F." #: resources/data/hints.ini: [hint:Box selection] msgid "" @@ -15562,6 +15734,10 @@ msgid "" "Did you know that you can do a box selection with Shift+Mouse drag? You can " "also box-deselect objects with Alt+Mouse drag." msgstr "" +"Boxselectie\n" +"Wist u dat u een boxselectie kunt doen door Shift in te drukken en dan met " +"de muis te slepen? U kunt ook deselecteren op deze manier met Alt+muis " +"bewegen." #: resources/data/hints.ini: [hint:Zoom on selected objects or all if none #: selected] @@ -15571,6 +15747,9 @@ msgid "" "b> key? If none are selected, the camera will zoom on all objects in the " "scene." msgstr "" +"Zoom op geselecteerde objecten of op alle objecten als geen geselecteerd is\n" +"Wist u dat u in kunt zoomen op geselecteerde objecten door te drukken op de " +"Z-toets? Als niets geselecteerd is, wordt ingezoomd op alle objecten." #: resources/data/hints.ini: [hint:Printable toggle] msgid "" @@ -15579,6 +15758,10 @@ msgid "" "model without having to move or delete it? Toggle the Printable property of " "a model from the Right-click context menu." msgstr "" +"Als printbaar instellen\n" +"Wist u dat u G-code-generatie kunt uitzetten voor geselecteerde modellen " +"zonder deze te verplaatsen of verwijderen? Zet de Printbaar-optie aan of uit " +"van een model met de rechtermuisklik op een object." #: resources/data/hints.ini: [hint:Mirror] msgid "" @@ -15586,6 +15769,10 @@ msgid "" "Did you know that you can mirror the selected model to create a reversed " "version of it? Right-click the model, select Mirror and pick the mirror axis." msgstr "" +"Spiegelen\n" +"Wist u dat u de geselecteerde modellen kunt spiegelen om een omgekeerd " +"object te verkrijgen? Rechtermuisklik op het model, kies voor Spiegelen en " +"kies over welke as gespiegeld moet worden." #: resources/data/hints.ini: [hint:PageUp / PageDown quick rotation by 45 #: degrees] @@ -15595,6 +15782,10 @@ msgid "" "around the Z-axis clockwise or counter-clockwise by pressing Page Up " "or Page Down respectively?" msgstr "" +"PageUp / PageDown snel roteren bij 45 graden\n" +"Wist u dat u snel modellen kunt roteren over 45 graden rond de Z-as (met de " +"klok mee of er tegenin) door te klikken op de PageUp- of PageDown-toetsen?" #: resources/data/hints.ini: [hint:Load config from G-code] msgid "" @@ -15604,6 +15795,11 @@ msgid "" "can use File-Import-Import SL1 / SL1S archive, which also lets you " "reconstruct 3D models from the voxel data." msgstr "" +"Laad configuratie van G-code\n" +"Wist u dat u een configuratie voor printen, filament, en printerprofielen " +"kunt laden door te klikken op Bestand - Importeer - Importeer configuratie? " +"Dit geldt ook voor SL1 / SL1S archieven die u een 3D-model laten " +"reconstrueren van de voxel-data." #: resources/data/hints.ini: [hint:Ironing] msgid "" @@ -15613,6 +15809,11 @@ msgid "" "holes and flatten any lifted plastic. Read more in the documentation. " "(Requires Advanced or Expert mode.)" msgstr "" +"Strijken\n" +"Wist u dat u een glad bovenoppervlak kunt maken door deze te laten strijken? " +"De nozzle gaat dan nogmaals over het oppervlak om kleine gaatjes te vullen " +"en oneffenheden plat te drukken (vereist geavanceerde of expertmodus). Lees " +"meer in de documentatie." #: resources/data/hints.ini: [hint:Paint-on supports] msgid "" @@ -15621,6 +15822,10 @@ msgid "" "where supports should be enforced or blocked? Try thePaint-on supportsfeature. (Requires Advanced or Expert mode.)" msgstr "" +"Support schilderen\n" +"Wist u dat u direct op het model regios kunt schilderen waar support moet of " +"niet mag worden geplaatst? Probeer de support schilderen-functie " +"(vereist geavanceerde of expertmodus)." #: resources/data/hints.ini: [hint:Paint-on seam] msgid "" @@ -15629,6 +15834,10 @@ msgid "" "place the start/endpoint of each perimeter loop? Try theSeam paintingfeature. (Requires Advanced or Expert mode.)" msgstr "" +"Naad schilderen\n" +"Wist u dat u direct op het model regios kunt schilderen waar de naad zich " +"moet bevinden? Probeer de Naad schilderen-functie (vereist " +"geavanceerde of expertmodus)." #: resources/data/hints.ini: [hint:Insert Pause] msgid "" @@ -15638,6 +15847,11 @@ msgid "" "(M601). This can be used to insert magnets, weights or nuts into your " "prints. Read more in the documentation." msgstr "" +"Pauze invoegen\n" +"Wist u dat u een pauze kunt inplannen op een specifieke laag? " +"Rechtermuisklik op de slider in de sliceweergave en selecteer Voeg pauze toe " +"(M601). Dit kan handig zijn voor het plaatsen van magneetjes, gewichtjes, " +"moertjes, etc. in het model. Lees meer in de documentatie." #: resources/data/hints.ini: [hint:Insert Custom G-code] msgid "" @@ -15647,6 +15861,11 @@ msgid "" "custom G-code. With this function you can, for example, create a temperature " "tower. Read more in the documentation." msgstr "" +"Custom G-code invoegen\n" +"Wist u dat u een custom G-code kunt invoegen op een specifieke laag? " +"Linkermuisklik op de laag in de voorbeeldweergave. Dan rechtermuisklik het " +"plus-icoon en selecteer Voeg custom G-code toe. Met deze functie kunt u " +"bijvoorbeeld een temperatuurtoren printen. Lees meer in de documentatie." #: resources/data/hints.ini: [hint:Configuration snapshots] msgid "" @@ -15655,6 +15874,10 @@ msgid "" "user profiles? You can view and move back and forth between snapshots using " "the Configuration - Configuration snapshots menu." msgstr "" +"Configuratiesnapshots\n" +"Wist u dat u terug kunt gaan naar een complete backup van alle systeem- en " +"gebruikersprofielen? U kunt heen en weer bewegen tussen snapshots door " +"middel van het Configuratiesnapshots-menu." #: resources/data/hints.ini: [hint:Minimum shell thickness] msgid "" @@ -15663,6 +15886,10 @@ msgid "" "define theMinimum shell thicknessin millimeters? This feature is " "especially useful when using the variable layer height function." msgstr "" +"Minimale shelldikte\n" +"Wist u dat u in plaats van het aantal boven- en onderlagen de Minimale " +"shelldikte kunt aangeven? Deze optie is handig wanneer een variabele " +"laagdikte wordt gebruikt." #: resources/data/hints.ini: [hint:Settings in non-modal window] msgid "" @@ -15671,6 +15898,11 @@ msgid "" "means you can have settings open on one screen and the G-code Preview on the " "other. Go to thePreferencesand select Settings in non-modal window." msgstr "" +"Instellingen in niet-modaal venster\n" +"Wist u dat u de instellingen kunt openen in een niet-modaal venster? Dit " +"houdt in dat u de instellingen open kunt hebben staan op één scherm en de " +"sliceweergave op een ander scherm. Ga naar Voorkeuren en selecteer " +"Instellingen in niet-modaal venster." #: resources/data/hints.ini: [hint:Adaptive infills] msgid "" @@ -15679,6 +15911,10 @@ msgid "" "to decrease the print time and lower the filament consumption? Read more in " "the documentation." msgstr "" +"Adaptieve vulling\n" +"Wist u dat u adaptief kubische en ondersteunend kubische vulling kunt " +"gebruiken om de printtijd te verkorten en de hoeveelheid filament te " +"reduceren? Lees meer in de documentatie." #: resources/data/hints.ini: [hint:Lightning infill] msgid "" @@ -15687,6 +15923,10 @@ msgid "" "surfaces, save a lot of the filament, and decrease the print time? Read more " "in the documentation." msgstr "" +"Bliksemvulling\n" +"Wist u dat u bliksemvulling kunt toepassen om alleen topvlakken te " +"ondersteunen? Dit bespaart veel filament en reduceert de printtijd. Lees " +"meer in de documentatie." #: resources/data/hints.ini: [hint:Fullscreen mode] msgid "" @@ -15694,6 +15934,9 @@ msgid "" "Did you know that you can switch PrusaSlicer to fullscreen mode? Use the " "F11 hotkey." msgstr "" +"Volledig scherm\n" +"Wist u dat u in PrusaSlicer kunt wisselen naar volledig scherm? Gebruik " +"daarvoor de F11-toets." #: ../src/common/debugrpt.cpp:586 msgid "" From 9da14ba3218fdb0e903099a456e7e569ef8f8e7e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Oct 2022 16:28:40 +0200 Subject: [PATCH 95/97] Remove redundant header from Astar --- src/libslic3r/AStar.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index b35b6a4af..630257a81 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -4,11 +4,14 @@ #include // std::isinf() is here #include -#include "libslic3r/Point.hpp" #include "libslic3r/MutablePriorityQueue.hpp" namespace Slic3r { namespace astar { +// Borrowed from C++20 +template +using remove_cvref_t = std::remove_cv_t>; + // Input interface for the Astar algorithm. Specialize this struct for a // particular type and implement all the 4 methods and specify the Node type // to register the new type for the astar implementation. From ba22eb600e62ddaee0a83f2037c4bc0c3ccd3cdd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 26 Oct 2022 19:19:28 +0200 Subject: [PATCH 96/97] MSVC specific: Enabled /permissive- to enforce C++ standards. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70b7ed567..418a5d9c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,8 @@ if (MSVC) # C4244: 'conversion' conversion from 'type1' to 'type2', possible loss of data. An integer type is converted to a smaller integer type. # C4267: The compiler detected a conversion from size_t to a smaller type. add_compile_options(/wd4244 /wd4267) + # Enforce strict C++ conformance, so our code that compiles on MSVC also compiles on GCC and clang. + add_compile_options(/permissive-) endif () if (MINGW) From dd8234512bc60f131a365effed7dded53bf41fdc Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 26 Oct 2022 17:36:57 +0200 Subject: [PATCH 97/97] Changes in wxWidgets.cmake to support of the updated wxWidgets v3.2.0-patched + ObjectList: Deleted code, which is no needed after update of wxWidgets --- deps/wxWidgets/wxWidgets.cmake | 4 ++-- src/slic3r/GUI/GUI_ObjectList.cpp | 12 +----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 29374974b..8056a01d3 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() prusaslicer_add_cmake_project(wxWidgets - URL https://github.com/prusa3d/wxWidgets/archive/2a0b365df947138c513a888d707d46248d78a341.zip - URL_HASH SHA256=9ab05cd5179196fad4ae702c78eaae9418e73a402cfd390f7438e469b13eb735 + URL https://github.com/prusa3d/wxWidgets/archive/34b524f8d5134a40a90d93a16360d533af2676ae.zip + URL_HASH SHA256=e76ca0dd998905c4dbb86f41f264e6e0468504dc2398f7e7e3bba8dc37de2f45 DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS -DwxBUILD_PRECOMP=ON diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8c7b1db75..164679645 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2522,17 +2522,7 @@ bool ObjectList::can_merge_to_single_object() const wxPoint ObjectList::get_mouse_position_in_control() const { - wxPoint pt = wxGetMousePosition() - this->GetScreenPosition(); - -#ifdef __APPLE__ - // Workaround for OSX. From wxWidgets 3.1.6 Hittest doesn't respect to the header of wxDataViewCtrl - if (wxDataViewItem top_item = this->GetTopItem(); top_item.IsOk()) { - auto rect = this->GetItemRect(top_item, this->GetColumn(0)); - pt.y -= rect.y; - } -#endif // __APPLE__ - - return pt; + return wxGetMousePosition() - this->GetScreenPosition(); } // NO_PARAMETERS function call means that changed object index will be determine from Selection()