From a9a0414cc310ecd0974a08d55b323e854579913e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 16 Jun 2023 18:06:08 +0200 Subject: [PATCH 001/120] WIP: Cut with Tongue and Groove - Implemented rendering for the cut plane --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 107 +++++++++++++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 12 ++- 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 885f9d97a2..655acd5573 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -183,9 +183,10 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_style (int(CutConnectorStyle::Prism)) , m_connector_shape_id (int(CutConnectorShape::Circle)) { -// m_modes = { _u8L("Planar"), _u8L("Grid") + m_mode = size_t(CutMode::cutTongueAndGroove); + m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") -// }; + }; m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; @@ -466,6 +467,18 @@ void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/) update_clipper(); } +bool GLGizmoCut3D::render_cut_mode_combo() +{ + ImGui::AlignTextToFramePadding(); + int selection_idx = int(m_mode); + const bool is_changed = m_imgui->combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width); + + if (is_changed) + m_mode = size_t(selection_idx); + + return is_changed; +} + bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, int& selection_idx) { ImGui::AlignTextToFramePadding(); @@ -606,6 +619,71 @@ bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::s return revert; } +static double get_grabber_mean_size(const BoundingBoxf3& bb) +{ + return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.; +} + +void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shader) +{ + const Camera & camera = wxGetApp().plater()->get_camera(); + const Transform3d cp_matrix = translation_transform(m_plane_center) * m_rotation_m; + ColorRGBA cp_clr = m_plane.model.get_color(); + // input values + const double groove_depth = 2; + const double groove_width = 6; + + // values for calculaton + const double groove_half_depth = 0.5 * groove_depth; + const double groove_half_width = 0.5 * groove_width; + + const float cp_radius = m_cut_plane_radius_koef * (float)m_radius; + const float cp_length = 1.5f * cp_radius; + const float cp_width = 0.02f * (float)get_grabber_mean_size(m_bounding_box); + const double h_shift = 0.25 * (double)cp_radius + groove_half_width; + + GLModel model; + model.init_from(its_make_prism(0.5f * cp_radius, cp_length, cp_width)); + model.set_color(cp_clr); + + // upper halfs of cut_plane + + Transform3d view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * Vec3d(-h_shift, 0, groove_half_depth)) * cp_matrix; + shader->set_uniform("view_model_matrix", view_model_matrix_); + model.render(); + + view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * Vec3d(h_shift, 0, groove_half_depth)) * cp_matrix; + shader->set_uniform("view_model_matrix", view_model_matrix_); + model.render(); + + // lower part of cut_plane + + model.reset(); + model.init_from(its_make_prism(groove_width + 2*groove_depth, cp_length, cp_width)); + cp_clr.a(cp_clr.a() + 0.1f); + model.set_color(cp_clr); + + view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cp_matrix; + shader->set_uniform("view_model_matrix", view_model_matrix_); + model.render(); + + // side parts of cut_plane + + model.reset(); + model.init_from(its_make_prism(sqrt(2* groove_depth* groove_depth), cp_length, cp_width)); + + const double h_side_shift = groove_half_width + groove_half_depth; + const double angle = 0.25 * PI; + + view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(-angle * Vec3d::UnitY()); + shader->set_uniform("view_model_matrix", view_model_matrix_); + model.render(); + + view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(angle * Vec3d::UnitY()); + shader->set_uniform("view_model_matrix", view_model_matrix_); + model.render(); +} + void GLGizmoCut3D::render_cut_plane() { if (cut_line_processing()) @@ -623,9 +701,7 @@ void GLGizmoCut3D::render_cut_plane() shader->start_using(); 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->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (can_perform_cut() && has_valid_contour()) { @@ -636,7 +712,14 @@ void GLGizmoCut3D::render_cut_plane() } else m_plane.model.set_color({ 1.0f, 0.8f, 0.8f, 0.5f }); - m_plane.model.render(); + + if (m_mode == size_t(CutMode::cutPlanar)) { + 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); + m_plane.model.render(); + } + else if (m_mode == size_t(CutMode::cutTongueAndGroove)) + render_cut_plate_for_tongue_and_groove(shader); glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); @@ -644,11 +727,6 @@ void GLGizmoCut3D::render_cut_plane() shader->stop_using(); } -static double get_grabber_mean_size(const BoundingBoxf3& bb) -{ - return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.; -} - static double get_half_size(double size) { return std::max(size * 0.35, 0.05); @@ -1951,9 +2029,11 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) { // WIP : cut plane mode - // render_combo(_u8L("Mode"), m_modes, m_mode); + render_cut_mode_combo(); - if (m_mode == size_t(CutMode::cutPlanar)) { +// if (m_mode == size_t(CutMode::cutPlanar)) { + CutMode mode = CutMode(m_mode); + if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) { ImGui::AlignTextToFramePadding(); ImGuiWrapper::text(wxString(ImGui::InfoMarkerSmall)); ImGui::SameLine(); @@ -1968,7 +2048,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::SameLine(); render_move_center_input(Z); ImGui::SameLine(); - const bool has_connectors = !connectors.empty(); const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); @@ -1982,6 +2061,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) // render_flip_plane_button(); + if (mode == CutMode::cutPlanar) { add_vertical_scaled_interval(0.75f); m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); @@ -1999,6 +2079,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) reset_connectors(); } m_imgui->disabled_end(); + } ImGui::Separator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 51174a51af..73bd42fcb0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -13,6 +13,7 @@ namespace Slic3r { enum class CutConnectorType : int; class ModelVolume; +class GLShaderProgram; struct CutConnectorAttributes; namespace GUI { @@ -180,7 +181,8 @@ class GLGizmoCut3D : public GLGizmoBase enum class CutMode { cutPlanar - , cutGrig + , cutTongueAndGroove + //, cutGrig //,cutRadial //,cutModular }; @@ -190,7 +192,7 @@ class GLGizmoCut3D : public GLGizmoBase , Manual }; -// std::vector m_modes; + std::vector m_modes; size_t m_mode{ size_t(CutMode::cutPlanar) }; std::vector m_connector_modes; @@ -301,8 +303,9 @@ protected: Transform3d get_cut_matrix(const Selection& selection); private: - void set_center(const Vec3d& center, bool update_tbb = false); - bool render_combo(const std::string& label, const std::vector& lines, int& selection_idx); + void set_center(const Vec3d¢er, bool update_tbb = false); + bool render_cut_mode_combo(); + bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx); bool render_double_input(const std::string& label, double& value_in); bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in); void render_move_center_input(int axis); @@ -319,6 +322,7 @@ private: bool cut_line_processing() const; void discard_cut_line_processing(); + void render_cut_plate_for_tongue_and_groove(GLShaderProgram* shader); void render_cut_plane(); static 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); From 519ee6af92a4822631db79042f9ee9eed912ea99 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 21 Jun 2023 15:39:07 +0200 Subject: [PATCH 002/120] WIP: Cut with Tongue and Groove - Implemented parameters input for the cut plane --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 128 ++++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 11 +++ 2 files changed, 105 insertions(+), 34 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 655acd5573..8c1ae9baeb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -629,21 +629,24 @@ void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shade const Camera & camera = wxGetApp().plater()->get_camera(); const Transform3d cp_matrix = translation_transform(m_plane_center) * m_rotation_m; ColorRGBA cp_clr = m_plane.model.get_color(); - // input values - const double groove_depth = 2; - const double groove_width = 6; // values for calculaton - const double groove_half_depth = 0.5 * groove_depth; - const double groove_half_width = 0.5 * groove_width; + const double groove_half_depth = 0.5 * double(m_groove_depth); + const double groove_half_width = 0.5 * double(m_groove_width); - const float cp_radius = m_cut_plane_radius_koef * (float)m_radius; - const float cp_length = 1.5f * cp_radius; - const float cp_width = 0.02f * (float)get_grabber_mean_size(m_bounding_box); - const double h_shift = 0.25 * (double)cp_radius + groove_half_width; + const double cp_radius = 1.5 * m_radius; + + const double cp_half_width = 0.5 * cp_radius; + const float cp_length = 1.5f * float(cp_radius); + const float cp_height = 0.02f * (float)get_grabber_mean_size(m_bounding_box); + + const double h_shift = 0.5 * cp_half_width + groove_half_width; + + const float side_width = is_approx(m_groove_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_angle)); + const float flaps_width = 2.f * side_width * cos(m_groove_angle); GLModel model; - model.init_from(its_make_prism(0.5f * cp_radius, cp_length, cp_width)); + model.init_from(its_make_prism(float(cp_half_width), cp_length, cp_height)); model.set_color(cp_clr); // upper halfs of cut_plane @@ -659,7 +662,7 @@ void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shade // lower part of cut_plane model.reset(); - model.init_from(its_make_prism(groove_width + 2*groove_depth, cp_length, cp_width)); + model.init_from(its_make_prism(m_groove_width + flaps_width, cp_length, cp_height)); cp_clr.a(cp_clr.a() + 0.1f); model.set_color(cp_clr); @@ -670,16 +673,16 @@ void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shade // side parts of cut_plane model.reset(); - model.init_from(its_make_prism(sqrt(2* groove_depth* groove_depth), cp_length, cp_width)); - const double h_side_shift = groove_half_width + groove_half_depth; - const double angle = 0.25 * PI; + model.init_from(its_make_prism(float(side_width), cp_length, cp_height)); - view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(-angle * Vec3d::UnitY()); + const double h_side_shift = groove_half_width + 0.25 * double(flaps_width); + + view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); shader->set_uniform("view_model_matrix", view_model_matrix_); model.render(); - view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(angle * Vec3d::UnitY()); + view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); shader->set_uniform("view_model_matrix", view_model_matrix_); model.render(); } @@ -1383,6 +1386,11 @@ void GLGizmoCut3D::update_bb() m_snap_fine_in_radius = m_grabber_connection_len * 0.85; m_snap_fine_out_radius = m_grabber_connection_len * 1.15; + // input params for cut with tongue and groove + m_groove_depth = m_groove_depth_init = 0.5f * float(get_grabber_mean_size(m_bounding_box)); + m_groove_width = m_groove_width_init = 4.0f * m_groove_depth; + m_groove_angle = m_groove_angle_init = 0.25f * float(PI); + m_plane.reset(); m_cone.reset(); m_sphere.reset(); @@ -2026,11 +2034,47 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) ImGuiWrapper::text(" "); } +void GLGizmoCut3D::render_groove_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance, bool is_angle /*=false*/) +{ + bool is_changed{false}; + + if (is_angle) { + ImGuiWrapper::text(label); + + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.7f); + + float val = rad2deg(in_val); + const float old_val = val; + + const std::string format = "%.0f " + _u8L("°"); + m_imgui->slider_float(("##groove_" + label).c_str(), &val, 30.f, 120.f, format.c_str(), 1.f, true, from_u8(label)); + + if (!is_approx(old_val, val)) { + in_val = deg2rad(val); + is_changed = true; + } + } + else if (render_slider_double_input(label, in_val, in_tolerance)) + is_changed = true; + + ImGui::SameLine(); + + m_imgui->disabled_begin(is_approx(in_val, init_val)); + const std::string act_name = _u8L("Reset"); + if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { + in_val = init_val; + in_tolerance = 0.1f; + is_changed = true; + } + m_imgui->disabled_end(); + + //if (is_changed) + // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); +}; + void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) { - // WIP : cut plane mode - render_cut_mode_combo(); - // if (m_mode == size_t(CutMode::cutPlanar)) { CutMode mode = CutMode(m_mode); if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) { @@ -2041,6 +2085,9 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) get_wraped_wxString(_L("Hold SHIFT key to draw a cut line"), 40)); ImGui::Separator(); + // WIP : cut plane mode + render_cut_mode_combo(); + render_build_size(); ImGui::AlignTextToFramePadding(); @@ -2062,23 +2109,31 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) // render_flip_plane_button(); if (mode == CutMode::cutPlanar) { - add_vertical_scaled_interval(0.75f); + add_vertical_scaled_interval(0.75f); - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); - if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) - set_connectors_editing(true); - m_imgui->disabled_end(); + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); + if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) + set_connectors_editing(true); + m_imgui->disabled_end(); - ImGui::SameLine(1.5f * m_control_width); + ImGui::SameLine(1.5f * m_control_width); - m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); - act_name = _L("Reset cut"); - if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); - reset_cut_plane(); - reset_connectors(); - } - m_imgui->disabled_end(); + m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); + act_name = _L("Reset cut"); + if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); + reset_cut_plane(); + reset_connectors(); + } + m_imgui->disabled_end(); + } + else if (mode == CutMode::cutTongueAndGroove) { + ImGui::Separator(); + ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Groove") + ": "); + + render_groove_input(_u8L("Depth"), m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance); + render_groove_input(_u8L("Width"), m_groove_width, m_groove_width_init, m_groove_width_tolerance); + render_groove_input(_u8L("Angle"), m_groove_angle, m_groove_angle_init, m_groove_width_tolerance, true); } ImGui::Separator(); @@ -2496,6 +2551,11 @@ bool GLGizmoCut3D::can_perform_cut() const if (m_part_selection.valid()) return ! m_part_selection.is_one_object(); + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + const float flaps_width = -2.f * m_groove_depth / tan(m_groove_angle); + return flaps_width < m_groove_width; + } + return true;// has_valid_contour(); } @@ -2835,7 +2895,7 @@ void GLGizmoCut3D::init_connector_shapes() continue; 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); + 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))); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 73bd42fcb0..8e758ec773 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -112,6 +112,16 @@ class GLGizmoCut3D : public GLGizmoBase bool m_rotate_upper{ false }; bool m_rotate_lower{ false }; + // Input params for cut with tongue and groove + float m_groove_depth; + float m_groove_width; + float m_groove_angle; + float m_groove_depth_init; + float m_groove_width_init; + float m_groove_angle_init; + float m_groove_depth_tolerance{ 0.1f }; + float m_groove_width_tolerance{ 0.1f }; + bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; bool m_cut_plane_as_circle{ false }; @@ -277,6 +287,7 @@ protected: void add_horizontal_scaled_interval(float interval); void add_horizontal_shift(float shift); void render_color_marker(float size, const ImU32& color); + void render_groove_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance, bool is_angle = false); void render_cut_plane_input_window(CutConnectors &connectors); void init_input_window_data(CutConnectors &connectors); void render_input_window_warning() const; From ceb9582cb1fe337696ccdbfa406f83fb0cee16e2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 22 Jun 2023 15:34:28 +0200 Subject: [PATCH 003/120] WIP: Cut with Tongue and Groove * Implemented cut with groove + Cut by contour extracted to separate function perform_cut_by_contour --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 371 +++++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 + 2 files changed, 261 insertions(+), 112 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8c1ae9baeb..969e6f57d6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2633,6 +2633,261 @@ void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id) obj->cut_id.copy(cut_id); } +ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count) +{ + const Selection& selection = m_parent.get_selection(); + const int instance_idx = selection.get_instance_idx(); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + if (m_keep_upper) cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + if (m_keep_lower) cut_mo->clone_for_cut(&lower); + + const Transform3d cut_matrix = get_cut_matrix(selection); + + auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) { + if (upper && !upper->volumes.empty()) { + ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); + cut_objects.push_back(upper); + } + if (lower && !lower->volumes.empty()) { + ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower); + cut_objects.push_back(lower); + } + }; + + const size_t cut_parts_cnt = m_part_selection.parts().size(); + bool has_modifiers = false; + + // Distribute SolidParts to the Upper/Lower object + for (size_t id = 0; id < cut_parts_cnt; ++id) { + if (m_part_selection.parts()[id].is_modifier) + has_modifiers = true; // modifiers will be added later to the related parts + else if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower)) + obj->add_volume(*(cut_mo->volumes[id])); + } + + if (has_modifiers) { + // Distribute Modifiers to the Upper/Lower object + auto upper_bb = upper ? upper->instance_bounding_box(instance_idx) : BoundingBoxf3(); + auto lower_bb = lower ? lower->instance_bounding_box(instance_idx) : BoundingBoxf3(); + const Transform3d inst_matrix = cut_mo->instances[instance_idx]->get_transformation().get_matrix(); + + for (size_t id = 0; id < cut_parts_cnt; ++id) + if (m_part_selection.parts()[id].is_modifier) { + ModelVolume* vol = cut_mo->volumes[id]; + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + // Don't add modifiers which are not intersecting with solid parts + if (upper_bb.intersects(bb)) + upper->add_volume(*vol); + if (lower_bb.intersects(bb)) + lower->add_volume(*vol); + } + } + + ModelObjectPtrs cut_object_ptrs; + + ModelVolumePtrs& volumes = cut_mo->volumes; + if (volumes.size() == cut_parts_cnt) { + // Means that object is cut without connectors + + // Just add Upper and Lower objects to cut_object_ptrs + add_cut_objects(cut_object_ptrs, upper, lower); + } + else if (volumes.size() > cut_parts_cnt) { + // Means that object is cut with connectors + + // All volumes are distributed to Upper / Lower object, + // So we don’t need them anymore + for (size_t id = 0; id < cut_parts_cnt; id++) + delete *(volumes.begin() + id); + volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); + + // Perform cut just to get connectors + const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); + assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); + + // Connectors from upper object + for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) + upper->add_volume(*volume, volume->type()); + + // Connectors from lower object + for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) + lower->add_volume(*volume, volume->type()); + + // Add Upper and Lower objects to cut_object_ptrs + add_cut_objects(cut_object_ptrs, upper, lower); + + // Add Dowel-connectors as separate objects to cut_object_ptrs + if (cut_connectors_obj.size() >= 3) + for (size_t id = 2; id < cut_connectors_obj.size(); id++) + cut_object_ptrs.push_back(cut_connectors_obj[id]); + } + + // Now merge all model parts together: + { + for (ModelObject* mo : cut_object_ptrs) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume* mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) + mo->delete_volume(i); + } + } + } + } + + return cut_object_ptrs; +} + +ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes) +{ + const Selection& selection = m_parent.get_selection(); + const int instance_idx = selection.get_instance_idx(); + + const ModelObjectCutAttributes groove_cut_attributes = ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts ; + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + cut_mo->clone_for_cut(&lower); + + const double groove_half_depth = 0.5 * double(m_groove_depth); + + const Transform3d cut_matrix = get_cut_matrix(selection); + + Model tmp_model; + ModelObjectPtrs cut_part_ptrs; + + ModelObject* tmp_object{ nullptr }; + cut_mo->clone_for_cut(&tmp_object); + + auto cut = [&tmp_model, instance_idx](ModelObject* object, const Transform3d& cut_matrix, bool leave_upper, ModelObjectPtrs& cut_part_ptrs) { + tmp_model = Model(); + tmp_model.add_object(*object); + cut_part_ptrs = tmp_model.objects.back()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts); + auto& volumes = cut_part_ptrs.front()->volumes; + + assert(cut_part_ptrs.size() == 1); + assert(volumes.size() == 2); + + object->clear_volumes(); + object->add_volume(*(leave_upper ? volumes.front() : volumes.back())); + ModelObject::reset_instance_transformation(object, instance_idx); + }; + + // cut by upper plane + + const Transform3d cut_matrix_upper = translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cut_matrix; + { + cut(cut_mo, cut_matrix_upper, false, cut_part_ptrs); + upper->add_volume(*cut_part_ptrs.front()->volumes.front()); + } + + // cut by lower plane + + const Transform3d cut_matrix_lower = translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cut_matrix; + { + cut(cut_mo, cut_matrix_lower, true, cut_part_ptrs); + lower->add_volume(*cut_part_ptrs.front()->volumes.back()); + } + + // cut middle part with 2 angles and add parts to related upper/lower objects + + const double h_side_shift = 0.5 * double(m_groove_width + m_groove_depth / tan(m_groove_angle)); + + // cut by angle1 plane + { + const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); + + cut(cut_mo, cut_matrix_angle1, false, cut_part_ptrs); + lower->add_volume(*cut_part_ptrs.front()->volumes.front()); + } + + // cut by angle2 plane + { + const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); + + cut(cut_mo, cut_matrix_angle2, false, cut_part_ptrs); + lower->add_volume(*cut_part_ptrs.front()->volumes.front()); + } + + // apply tolerance to the middle part + { + const double h_groove_shift_tolerance = groove_half_depth - (double)m_groove_depth_tolerance; + + const Transform3d cut_matrix_lower_tolerance = translation_transform(m_rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * cut_matrix; + cut(cut_mo, cut_matrix_lower_tolerance, true, cut_part_ptrs); + + const double h_side_shift_tolerance = h_side_shift - 0.5 * double(m_groove_width_tolerance); + + const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); + cut(cut_mo, cut_matrix_angle1_tolerance, false, cut_part_ptrs); + + const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); + cut(cut_mo, cut_matrix_angle2_tolerance, true, cut_part_ptrs); + + // this part can be added to the upper object now + upper->add_volume(*cut_part_ptrs.front()->volumes.back()); + } + + auto add_cut_objects = [this, &instance_idx](ModelObjectPtrs& cut_objects, ModelObject* object, const Transform3d& cut_matrix) { + if (object && !object->volumes.empty()) { + ModelObject::reset_instance_transformation(object, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); + cut_objects.push_back(object); + } + }; + + ModelObjectPtrs cut_object_ptrs; + + add_cut_objects(cut_object_ptrs, upper, cut_matrix_upper); + add_cut_objects(cut_object_ptrs, lower, cut_matrix_lower); + + // Now merge all model parts together: + { + for (ModelObject* mo : cut_object_ptrs) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume* mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) + mo->delete_volume(i); + } + } + } + } + return cut_object_ptrs; +} + void GLGizmoCut3D::perform_cut(const Selection& selection) { if (!can_perform_cut()) @@ -2658,6 +2913,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); }); const bool cut_by_contour = m_part_selection.valid(); + const bool cut_with_groove = CutMode(m_mode) == CutMode::cutTongueAndGroove; ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr; if (cut_mo) cut_mo->cut_connectors = mo->cut_connectors; @@ -2686,118 +2942,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); - ModelObjectPtrs cut_object_ptrs; - if (cut_by_contour) { - // Clone the object to duplicate instances, materials etc. - ModelObject* upper{ nullptr }; - if (m_keep_upper) cut_mo->clone_for_cut(&upper); - ModelObject* lower{ nullptr }; - if (m_keep_lower) cut_mo->clone_for_cut(&lower); - - auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) { - if (upper && !upper->volumes.empty()) { - ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); - cut_objects.push_back(upper); - } - if (lower && !lower->volumes.empty()) { - ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower); - cut_objects.push_back(lower); - } - }; - - const size_t cut_parts_cnt = m_part_selection.parts().size(); - bool has_modifiers = false; - - // Distribute SolidParts to the Upper/Lower object - for (size_t id = 0; id < cut_parts_cnt; ++id) { - if (m_part_selection.parts()[id].is_modifier) - has_modifiers = true; // modifiers will be added later to the related parts - else if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower)) - obj->add_volume(*(cut_mo->volumes[id])); - } - - if (has_modifiers) { - // Distribute Modifiers to the Upper/Lower object - auto upper_bb = upper ? upper->instance_bounding_box(instance_idx) : BoundingBoxf3(); - auto lower_bb = lower ? lower->instance_bounding_box(instance_idx) : BoundingBoxf3(); - const Transform3d inst_matrix = cut_mo->instances[instance_idx]->get_transformation().get_matrix(); - - for (size_t id = 0; id < cut_parts_cnt; ++id) - if (m_part_selection.parts()[id].is_modifier) { - ModelVolume* vol = cut_mo->volumes[id]; - auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); - // Don't add modifiers which are not intersecting with solid parts - if (upper_bb.intersects(bb)) - upper->add_volume(*vol); - if (lower_bb.intersects(bb)) - lower->add_volume(*vol); - } - } - - ModelVolumePtrs& volumes = cut_mo->volumes; - if (volumes.size() == cut_parts_cnt) { - // Means that object is cut without connectors - - // Just add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); - } - else if (volumes.size() > cut_parts_cnt) { - // Means that object is cut with connectors - - // All volumes are distributed to Upper / Lower object, - // So we don’t need them anymore - for (size_t id = 0; id < cut_parts_cnt; id++) - delete *(volumes.begin() + id); - volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); - - // Perform cut just to get connectors - const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); - assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); - - // Connectors from upper object - for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) - upper->add_volume(*volume, volume->type()); - - // Connectors from lower object - for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) - lower->add_volume(*volume, volume->type()); - - // Add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); - - // Add Dowel-connectors as separate objects to cut_object_ptrs - if (cut_connectors_obj.size() >= 3) - for (size_t id = 2; id < cut_connectors_obj.size(); id++) - cut_object_ptrs.push_back(cut_connectors_obj[id]); - } - - // Now merge all model parts together: - { - for (ModelObject* mo : cut_object_ptrs) { - TriangleMesh mesh; - // Merge all SolidPart but not Connectors - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part() && !mv->is_cut_connector()) { - TriangleMesh m = mv->mesh(); - m.transform(mv->get_matrix()); - mesh.merge(m); - } - } - if (! mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(mesh); - new_volume->name = mo->name; - // Delete all merged SolidPart but not Connectors - for (int i=int(mo->volumes.size())-2; i>=0; --i) { - const ModelVolume* mv = mo->volumes[i]; - if (mv->is_model_part() && !mv->is_cut_connector()) - mo->delete_volume(i); - } - } - } - } - } - else - cut_object_ptrs = cut_mo->cut(instance_idx, cut_matrix, attributes); + ModelObjectPtrs cut_object_ptrs = cut_by_contour ? perform_cut_by_contour(cut_mo, attributes, dowels_count) : + cut_with_groove ? perform_cut_with_groove(cut_mo, attributes) : + cut_mo->cut(instance_idx, cut_matrix, attributes); // save cut_id to post update synchronization const CutObjectBase cut_id = cut_mo->cut_id; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 8e758ec773..352584231b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -341,6 +341,8 @@ private: void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); void render_cut_plane_grabbers(); void render_cut_line(); + ModelObjectPtrs perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count); + ModelObjectPtrs perform_cut_with_groove(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes); void perform_cut(const Selection&selection); void set_center_pos(const Vec3d¢er_pos, bool update_tbb = false); void update_bb(); From 1aa8d8ea99a56a622d234f71be8d312e1ebe4735 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 23 Jun 2023 16:53:29 +0200 Subject: [PATCH 004/120] WIP: Cut with Tongue and Groove * Implemented preview rendering of groove --- src/libslic3r/Model.cpp | 1 + src/libslic3r/Model.hpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 195 +++++++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 4 +- 4 files changed, 135 insertions(+), 73 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 16cc75225a..e8ae239c3a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1552,6 +1552,7 @@ void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + upper->volumes.back()->cut_info.is_from_upper = false; return; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index bedd26436f..2ee48f9917 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -239,6 +239,7 @@ enum class CutConnectorShape : int { , Square , Hexagon , Circle + , Rivet , Undef //,D-shape }; @@ -480,7 +481,7 @@ private: void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); public: - static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, + static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix = Transform3d::Identity(), bool place_on_cut = false, bool flip = false); ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes); @@ -777,6 +778,7 @@ public: // It contains information about connetors struct CutInfo { + bool is_from_upper{ true }; bool is_connector{ false }; bool is_processed{ true }; CutConnectorType connector_type{ CutConnectorType::Plug }; @@ -794,6 +796,7 @@ public: void set_processed() { is_processed = true; } void invalidate() { is_connector = false; } + void reset_from_upper() { is_from_upper = true; } template inline void serialize(Archive& ar) { ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); @@ -801,6 +804,9 @@ public: }; CutInfo cut_info; + bool is_from_upper() const { return cut_info.is_from_upper; } + void reset_from_upper() { cut_info.reset_from_upper(); } + bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } void invalidate_cut_info() { cut_info.invalidate(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 969e6f57d6..828dc95155 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -184,6 +184,8 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_shape_id (int(CutConnectorShape::Circle)) { m_mode = size_t(CutMode::cutTongueAndGroove); + m_contour_width = .0f; + m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") }; @@ -1299,7 +1301,8 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=fa bool can_set_center_pos = false; { - if (tbb.max.z() > -.5 && tbb.min.z() < .5) + double limit_val = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove_depth) : 0.5; + if (tbb.max.z() > -limit_val && tbb.min.z() < limit_val) can_set_center_pos = true; else { const double old_dist = (m_bb_center - m_plane_center).norm(); @@ -1551,6 +1554,28 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor m_valid = true; } +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* object, int instance_idx_in) +{ + m_instance_idx = instance_idx_in; + + m_model = Model(); + // add upper object + m_model.add_object(*object); + + m_parts.clear(); + + for (const ModelVolume* volume : object->volumes) { + assert(volume != nullptr); + m_parts.emplace_back(Part{ GLModel(), MeshRaycaster(volume->mesh()), true, !volume->is_model_part() }); + m_parts.back().glmodel.init_from(volume->mesh()); + + // Now check whether this part is below or above the plane. + m_parts.back().selected = volume->is_from_upper(); + } + + m_valid = true; +} + void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_model) { if (! valid()) @@ -1701,6 +1726,17 @@ void GLGizmoCut3D::on_render() m_c->selection_info()->set_use_shift(true); } + // init part_selection for groove mode + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + { + if (!m_part_selection.valid()) + process_contours(); + + m_contour_width = 0.f; + //check_and_update_connectors_state(); + } + else + m_contour_width = .4f; update_clipper(); @@ -1988,7 +2024,10 @@ void GLGizmoCut3D::process_contours() const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); - m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + m_part_selection = PartSelection(perform_cut_with_groove(model_objects[object_idx], true).front(), instance_idx); + else + m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); m_parent.toggle_model_objects_visibility(false); } @@ -2069,8 +2108,10 @@ void GLGizmoCut3D::render_groove_input(const std::string& label, float& in_val, } m_imgui->disabled_end(); - //if (is_changed) - // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + if (is_changed) { + reset_cut_by_contours(); + // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + } }; void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) @@ -2228,7 +2269,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::Separator(); - m_imgui->disabled_begin(!m_is_contour_changed && !can_perform_cut()); + m_imgui->disabled_begin(!can_perform_cut()); if(m_imgui->button(_L("Perform cut"))) perform_cut(m_parent.get_selection()); m_imgui->disabled_end(); @@ -2324,8 +2365,6 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { - if (m_is_contour_changed) - return; if (! m_invalid_connectors_idxs.empty()) { wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":"; if (m_info_stats.outside_cut_contour > size_t(0)) @@ -2472,7 +2511,7 @@ void GLGizmoCut3D::render_connectors() { ::glEnable(GL_DEPTH_TEST); - if (m_is_contour_changed || cut_line_processing() || + if (cut_line_processing() || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) return; @@ -2548,15 +2587,16 @@ bool GLGizmoCut3D::can_perform_cut() const { if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; - if (m_part_selection.valid()) - return ! m_part_selection.is_one_object(); if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { const float flaps_width = -2.f * m_groove_depth / tan(m_groove_angle); return flaps_width < m_groove_width; } - return true;// has_valid_contour(); + if (m_part_selection.valid()) + return ! m_part_selection.is_one_object(); + + return true; } bool GLGizmoCut3D::has_valid_contour() const @@ -2753,15 +2793,11 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const return cut_object_ptrs; } -ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes) +ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts/* = false*/) { const Selection& selection = m_parent.get_selection(); const int instance_idx = selection.get_instance_idx(); - const ModelObjectCutAttributes groove_cut_attributes = ModelObjectCutAttribute::KeepUpper | - ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts ; - // Clone the object to duplicate instances, materials etc. ModelObject* upper{ nullptr }; cut_mo->clone_for_cut(&upper); @@ -2772,25 +2808,31 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, const const Transform3d cut_matrix = get_cut_matrix(selection); - Model tmp_model; ModelObjectPtrs cut_part_ptrs; + Model tmp_model = Model(); + tmp_model.add_object(*cut_mo); + ModelObject* tmp_object = tmp_model.objects.front(); - ModelObject* tmp_object{ nullptr }; - cut_mo->clone_for_cut(&tmp_object); + auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const ModelObjectPtrs& cut_part_ptrs) { + const auto& volumes = cut_part_ptrs.front()->volumes; + for (const ModelVolume* volume : volumes) + if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || + (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper()) ) { + ModelVolume* new_vol = object->add_volume(*volume); + new_vol->reset_from_upper(); + } + }; - auto cut = [&tmp_model, instance_idx](ModelObject* object, const Transform3d& cut_matrix, bool leave_upper, ModelObjectPtrs& cut_part_ptrs) { - tmp_model = Model(); - tmp_model.add_object(*object); - cut_part_ptrs = tmp_model.objects.back()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | - ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts); - auto& volumes = cut_part_ptrs.front()->volumes; + auto cut = [instance_idx, add_volumes_from_cut] + (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute attribute, ModelObjectPtrs& cut_part_ptrs) { + Model model = Model(); + model.add_object(*object); + cut_part_ptrs = model.objects.front()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); assert(cut_part_ptrs.size() == 1); - assert(volumes.size() == 2); object->clear_volumes(); - object->add_volume(*(leave_upper ? volumes.front() : volumes.back())); + add_volumes_from_cut(object, attribute, cut_part_ptrs); ModelObject::reset_instance_transformation(object, instance_idx); }; @@ -2798,16 +2840,16 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, const const Transform3d cut_matrix_upper = translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - cut(cut_mo, cut_matrix_upper, false, cut_part_ptrs); - upper->add_volume(*cut_part_ptrs.front()->volumes.front()); + cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); } // cut by lower plane const Transform3d cut_matrix_lower = translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - cut(cut_mo, cut_matrix_lower, true, cut_part_ptrs); - lower->add_volume(*cut_part_ptrs.front()->volumes.back()); + cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); } // cut middle part with 2 angles and add parts to related upper/lower objects @@ -2818,16 +2860,16 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, const { const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); - cut(cut_mo, cut_matrix_angle1, false, cut_part_ptrs); - lower->add_volume(*cut_part_ptrs.front()->volumes.front()); + cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); } // cut by angle2 plane { const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); - cut(cut_mo, cut_matrix_angle2, false, cut_part_ptrs); - lower->add_volume(*cut_part_ptrs.front()->volumes.front()); + cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); } // apply tolerance to the middle part @@ -2835,56 +2877,69 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, const const double h_groove_shift_tolerance = groove_half_depth - (double)m_groove_depth_tolerance; const Transform3d cut_matrix_lower_tolerance = translation_transform(m_rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * cut_matrix; - cut(cut_mo, cut_matrix_lower_tolerance, true, cut_part_ptrs); + cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); const double h_side_shift_tolerance = h_side_shift - 0.5 * double(m_groove_width_tolerance); const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); - cut(cut_mo, cut_matrix_angle1_tolerance, false, cut_part_ptrs); + cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); - cut(cut_mo, cut_matrix_angle2_tolerance, true, cut_part_ptrs); - - // this part can be added to the upper object now - upper->add_volume(*cut_part_ptrs.front()->volumes.back()); + cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); } - auto add_cut_objects = [this, &instance_idx](ModelObjectPtrs& cut_objects, ModelObject* object, const Transform3d& cut_matrix) { - if (object && !object->volumes.empty()) { - ModelObject::reset_instance_transformation(object, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); - cut_objects.push_back(object); - } - }; + // this part can be added to the upper object now + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); ModelObjectPtrs cut_object_ptrs; - add_cut_objects(cut_object_ptrs, upper, cut_matrix_upper); - add_cut_objects(cut_object_ptrs, lower, cut_matrix_lower); + if (keep_as_parts) { + // add volumes from lower object to the upper, but mark them as a lower + const auto& volumes = lower->volumes; + for (const ModelVolume* volume : volumes) { + ModelVolume* new_vol = upper->add_volume(*volume); + new_vol->cut_info.is_from_upper = false; + } + cut_object_ptrs.push_back(upper); + } + else { - // Now merge all model parts together: - { - for (ModelObject* mo : cut_object_ptrs) { - TriangleMesh mesh; - // Merge all SolidPart but not Connectors - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part() && !mv->is_cut_connector()) { - TriangleMesh m = mv->mesh(); - m.transform(mv->get_matrix()); - mesh.merge(m); - } + auto add_cut_objects = [this, &instance_idx](ModelObjectPtrs& cut_objects, ModelObject* object, const Transform3d& cut_matrix) { + if (object && !object->volumes.empty()) { + ModelObject::reset_instance_transformation(object, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); + cut_objects.push_back(object); } - if (!mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(mesh); - new_volume->name = mo->name; - // Delete all merged SolidPart but not Connectors - for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { - const ModelVolume* mv = mo->volumes[i]; - if (mv->is_model_part() && !mv->is_cut_connector()) - mo->delete_volume(i); + }; + + add_cut_objects(cut_object_ptrs, upper, cut_matrix_upper); + add_cut_objects(cut_object_ptrs, lower, cut_matrix_lower); + + // Now merge all model parts together: + { + for (ModelObject* mo : cut_object_ptrs) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume* mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) + mo->delete_volume(i); + } } } } } + return cut_object_ptrs; } @@ -2943,7 +2998,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); ModelObjectPtrs cut_object_ptrs = cut_by_contour ? perform_cut_by_contour(cut_mo, attributes, dowels_count) : - cut_with_groove ? perform_cut_with_groove(cut_mo, attributes) : + cut_with_groove ? perform_cut_with_groove(cut_mo) : cut_mo->cut(instance_idx, cut_matrix, attributes); // save cut_id to post update synchronization diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 352584231b..e418ce3121 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -138,7 +138,6 @@ class GLGizmoCut3D : public GLGizmoBase float m_contour_width{ 0.4f }; float m_cut_plane_radius_koef{ 1.5f }; - bool m_is_contour_changed{ false }; float m_shortcut_label_width{ -1.f }; mutable std::vector m_selected; // which pins are currently selected @@ -154,6 +153,7 @@ class GLGizmoCut3D : public GLGizmoBase public: PartSelection() = default; PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc); + PartSelection(const ModelObject* mo, int instance_idx_in); ~PartSelection() { m_model.clear_objects(); } struct Part { @@ -342,7 +342,7 @@ private: void render_cut_plane_grabbers(); void render_cut_line(); ModelObjectPtrs perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count); - ModelObjectPtrs perform_cut_with_groove(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes); + ModelObjectPtrs perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts = false); void perform_cut(const Selection&selection); void set_center_pos(const Vec3d¢er_pos, bool update_tbb = false); void update_bb(); From 159d92cf6a9e418cf1d7a2a963e5c1c0f0b0c753 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 26 Jun 2023 13:49:48 +0200 Subject: [PATCH 005/120] WIP: Cut with Tongue and Groove * Implemented a check for invalid groove detection --- src/libslic3r/Model.cpp | 2 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 81 +++++++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 + 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index e8ae239c3a..0d4a2877c1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1645,6 +1645,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, // Post-process cut parts ModelObjectPtrs res; + if (upper->volumes.empty()) + return res; if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) { reset_instance_transformation(upper, instance, cut_matrix); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 828dc95155..e14184fd65 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -356,7 +356,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return true; } else if (mouse_event.RightDown()) { - if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE) { + if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE && + CutMode(m_mode) == CutMode::cutPlanar) { // Check the internal part raycasters. if (! m_part_selection.valid()) process_contours(); @@ -2015,17 +2016,29 @@ void GLGizmoCut3D::reset_cut_by_contours() void GLGizmoCut3D::process_contours() { - reset_cut_by_contours(); - const Selection& selection = m_parent.get_selection(); const ModelObjectPtrs& model_objects = selection.get_model()->objects; - wxBusyCursor wait; const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); + ModelObjectPtrs cut_objects; + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + cut_objects = perform_cut_with_groove(model_objects[object_idx], true); + if (cut_objects.empty() || m_invalid_groove) { + m_parent.set_color_clip_plane_colors({ CONNECTOR_DEF_COLOR , CONNECTOR_DEF_COLOR }); + return; + } + else + m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR }); + } + + reset_cut_by_contours(); + + wxBusyCursor wait; + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) - m_part_selection = PartSelection(perform_cut_with_groove(model_objects[object_idx], true).front(), instance_idx); + m_part_selection = PartSelection(cut_objects.front(), instance_idx); else m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); m_parent.toggle_model_objects_visibility(false); @@ -2381,6 +2394,8 @@ void GLGizmoCut3D::render_input_window_warning() const m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Select at least one object to keep after cutting.")); if (!has_valid_contour()) m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane is placed out of object")); + if (m_invalid_groove) + m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane with groove is invalid")); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) @@ -2590,7 +2605,7 @@ bool GLGizmoCut3D::can_perform_cut() const if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { const float flaps_width = -2.f * m_groove_depth / tan(m_groove_angle); - return flaps_width < m_groove_width; + return flaps_width < m_groove_width && !m_invalid_groove; } if (m_part_selection.valid()) @@ -2795,6 +2810,8 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts/* = false*/) { + m_invalid_groove = false; + const Selection& selection = m_parent.get_selection(); const int instance_idx = selection.get_instance_idx(); @@ -2813,43 +2830,54 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool tmp_model.add_object(*cut_mo); ModelObject* tmp_object = tmp_model.objects.front(); + bool invalid_added_volumes = false; + auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const ModelObjectPtrs& cut_part_ptrs) { const auto& volumes = cut_part_ptrs.front()->volumes; + bool has_volume = false; for (const ModelVolume* volume : volumes) if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper()) ) { ModelVolume* new_vol = object->add_volume(*volume); new_vol->reset_from_upper(); + has_volume = true; } + return has_volume; }; - auto cut = [instance_idx, add_volumes_from_cut] + auto cut = [instance_idx, add_volumes_from_cut, &invalid_added_volumes] (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute attribute, ModelObjectPtrs& cut_part_ptrs) { Model model = Model(); model.add_object(*object); cut_part_ptrs = model.objects.front()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); - assert(cut_part_ptrs.size() == 1); + if (cut_part_ptrs.empty()) + return false; object->clear_volumes(); - add_volumes_from_cut(object, attribute, cut_part_ptrs); + invalid_added_volumes |= !add_volumes_from_cut(object, attribute, cut_part_ptrs); ModelObject::reset_instance_transformation(object, instance_idx); + return true; }; + ModelObjectPtrs cut_object_ptrs; + // cut by upper plane const Transform3d cut_matrix_upper = translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); - add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + if (!cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + return cut_object_ptrs; + invalid_added_volumes |= !add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); } // cut by lower plane const Transform3d cut_matrix_lower = translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); + if (!cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs)) + return cut_object_ptrs; + invalid_added_volumes |= !add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); } // cut middle part with 2 angles and add parts to related upper/lower objects @@ -2860,16 +2888,18 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool { const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); - cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + if (!cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + return cut_object_ptrs; + invalid_added_volumes |= !add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); } // cut by angle2 plane { const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); - cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + if (!cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + return cut_object_ptrs; + invalid_added_volumes |= !add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); } // apply tolerance to the middle part @@ -2877,23 +2907,27 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool const double h_groove_shift_tolerance = groove_half_depth - (double)m_groove_depth_tolerance; const Transform3d cut_matrix_lower_tolerance = translation_transform(m_rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * cut_matrix; - cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + if (!cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs)) + return cut_object_ptrs; const double h_side_shift_tolerance = h_side_shift - 0.5 * double(m_groove_width_tolerance); const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); - cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); + if (!cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + return cut_object_ptrs; const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); - cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + if (!cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs)) + return cut_object_ptrs; } // this part can be added to the upper object now - add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); - - ModelObjectPtrs cut_object_ptrs; + invalid_added_volumes |= !add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); if (keep_as_parts) { + if (!m_invalid_groove) + m_invalid_groove = upper->volumes.empty() || lower->volumes.empty() || invalid_added_volumes; + // add volumes from lower object to the upper, but mark them as a lower const auto& volumes = lower->volumes; for (const ModelVolume* volume : volumes) { @@ -2903,7 +2937,6 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool cut_object_ptrs.push_back(upper); } else { - auto add_cut_objects = [this, &instance_idx](ModelObjectPtrs& cut_objects, ModelObject* object, const Transform3d& cut_matrix) { if (object && !object->volumes.empty()) { ModelObject::reset_instance_transformation(object, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index e418ce3121..dc0700f555 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -104,6 +104,8 @@ class GLGizmoCut3D : public GLGizmoBase } } m_info_stats; + bool m_invalid_groove{ false }; + bool m_keep_upper{ true }; bool m_keep_lower{ true }; bool m_keep_as_parts{ false }; From 0777139d1904ae206d1a51683f25115817483956 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 27 Jun 2023 17:06:38 +0200 Subject: [PATCH 006/120] WIP: Cut with Tongue and Groove * Implemented Z rotation for TAG plane --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 81 +++++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 +- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index e14184fd65..7d2d896f1e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -257,8 +257,8 @@ std::string GLGizmoCut3D::get_tooltip() const "Drag to move the cut plane\n" "Right-click a part to assign it to the other side"); - if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { - std::string axis = m_hover_id == X ? "X" : "Y"; + if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation)) { + std::string axis = m_hover_id == X ? "X" : m_hover_id == Y ? "Y" : "Z"; return axis + ": " + format(float(rad2deg(m_angle)), 1) + _u8L("°"); } @@ -787,8 +787,10 @@ void GLGizmoCut3D::render_rotation_snapping(GrabberID axis, const ColorRGBA& col if (axis == X) view_model_matrix = view_model_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ()); - else + else if (axis == Y) view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * rotation_transform(-0.5 * PI * Vec3d::UnitY()); + else + view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()); line_shader->start_using(); line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); @@ -808,9 +810,9 @@ void GLGizmoCut3D::render_rotation_snapping(GrabberID axis, const ColorRGBA& col line_shader->stop_using(); } -void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix) +void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef/* = 1.0*/) { - const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, m_grabber_connection_len)); + const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, line_len_koef * m_grabber_connection_len)); render_line(m_grabber_connection, color, line_view_matrix, 0.2f); }; @@ -826,9 +828,9 @@ void GLGizmoCut3D::render_cut_plane_grabbers() const double mean_size = get_grabber_mean_size(m_bounding_box); double size; - const bool dragging_by_cut_plane = m_dragging && m_hover_id == CutPlane; + const bool no_xy_dragging = m_dragging && m_hover_id == CutPlane; - if (!dragging_by_cut_plane) { + if (!no_xy_dragging && m_hover_id != CutPlaneZRotation) { render_grabber_connection(GRABBER_COLOR, view_matrix); // render sphere grabber @@ -839,11 +841,11 @@ void GLGizmoCut3D::render_cut_plane_grabbers() render_model(m_sphere.model, color, view_matrix * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size)); } - const bool no_one_grabber_hovered = !m_dragging && (m_hover_id < 0 || m_hover_id == CutPlane); + const bool no_xy_grabber_hovered = !m_dragging && (m_hover_id < 0 || m_hover_id == CutPlane); // render X grabber - if (no_one_grabber_hovered || m_hover_id == X) + if (no_xy_grabber_hovered || m_hover_id == X) { size = m_dragging && m_hover_id == X ? get_dragging_half_size(mean_size) : get_half_size(mean_size); const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); @@ -862,7 +864,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers() // render Y grabber - if (no_one_grabber_hovered || m_hover_id == Y) + if (no_xy_grabber_hovered || m_hover_id == Y) { size = m_dragging && m_hover_id == Y ? get_dragging_half_size(mean_size) : get_half_size(mean_size); const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); @@ -876,7 +878,33 @@ void GLGizmoCut3D::render_cut_plane_grabbers() Vec3d offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); - render_model(m_cone.model, color, view_matrix * translation_transform(offset)* rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + + // render CutPlaneZRotation grabber + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove && (no_xy_grabber_hovered || m_hover_id == CutPlaneZRotation)) + { + size = 0.75 * (m_dragging && m_hover_id == CutPlaneZRotation ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + + color = ColorRGBA::BLUE(); + ColorRGBA cp_color = m_hover_id == CutPlaneZRotation ? color : m_plane.model.get_color();; + + const double grabber_shift = -1.75 * m_grabber_connection_len; + + render_model(m_sphere.model, cp_color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + + if (m_hover_id == CutPlaneZRotation) { + const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + + render_rotation_snapping(CutPlaneZRotation, color); + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitX()), 1.75); + + Vec3d offset = Vec3d(1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + offset = Vec3d(-1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } } } @@ -996,6 +1024,12 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() 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, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity())); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_sphere.mesh_raycaster, Transform3d::Identity())); + } } update_raycasters_for_picking_transform(); @@ -1087,10 +1121,21 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - offset = 1.25 * size * Vec3d::UnitZ(); m_raycasters[id++]->set_transform(trafo * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size)); m_raycasters[id++]->set_transform(trafo); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + + const double grabber_shift = -1.75 * m_grabber_connection_len; + + offset = Vec3d(1.25 * size, grabber_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + offset = Vec3d(-1.25 * size, grabber_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + + m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + } } } @@ -1142,8 +1187,8 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3& m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); break; } - default: case Z: + default: { // no rotation applied break; @@ -1217,14 +1262,14 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) if (is_approx(theta, two_pi)) theta = 0.0; - if (m_hover_id == X) + if (m_hover_id != Y) theta += 0.5 * PI; if (!is_approx(theta, 0.0)) reset_cut_by_contours(); Vec3d rotation = Vec3d::Zero(); - rotation[m_hover_id] = theta; + rotation[m_hover_id == CutPlaneZRotation ? Z : m_hover_id] = theta; const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation); const bool update_tbb = !m_rotation_m.rotation().isApprox(rotation_tmp.rotation()); @@ -1259,7 +1304,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) return; if (m_hover_id == Z || m_hover_id == CutPlane) dragging_grabber_z(data); - else if (m_hover_id == X || m_hover_id == Y) + else if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) dragging_grabber_xy(data); else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) dragging_connector(data); @@ -1272,13 +1317,13 @@ void GLGizmoCut3D::on_start_dragging() 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) + if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) m_start_dragging_m = m_rotation_m; } void GLGizmoCut3D::on_stop_dragging() { - if (m_hover_id == X || m_hover_id == Y) { + if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) { m_angle_arc.reset(); m_angle = 0.0; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index dc0700f555..ff5f45fb8e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -30,6 +30,7 @@ class GLGizmoCut3D : public GLGizmoBase Y, Z, CutPlane, + CutPlaneZRotation, Count, }; @@ -340,7 +341,7 @@ private: static 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(GrabberID axis, const ColorRGBA& color); - void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); + void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef = 1.0); void render_cut_plane_grabbers(); void render_cut_line(); ModelObjectPtrs perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count); From 034bb31341006885c5d8556b94386ef79c4dc3c2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 28 Jun 2023 11:12:47 +0200 Subject: [PATCH 007/120] WIP: Cut with Tongue and Groove * Improved cut plane rendering, when moving the plane * Added update of the scene after switching between cut modes --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 46 +++++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 7d2d896f1e..d9201a25ec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -35,6 +35,9 @@ 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); static const ColorRGBA HOVERED_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 1.0f); +static const ColorRGBA CUT_PLANE_DEF_COLOR = ColorRGBA(0.9f, 0.9f, 0.9f, 0.5f); +static const ColorRGBA CUT_PLANE_ERR_COLOR = ColorRGBA(1.0f, 0.8f, 0.8f, 0.5f); + const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount; @@ -476,9 +479,18 @@ bool GLGizmoCut3D::render_cut_mode_combo() int selection_idx = int(m_mode); const bool is_changed = m_imgui->combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width); - if (is_changed) + if (is_changed) { m_mode = size_t(selection_idx); + apply_color_clip_plane_colors(); + if (auto oc = m_c->object_clipper()) { + m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; + oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); + } + if (m_part_selection.valid()) + reset_cut_by_contours(); + } + return is_changed; } @@ -710,14 +722,7 @@ void GLGizmoCut3D::render_cut_plane() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - if (can_perform_cut() && has_valid_contour()) { - if (m_hover_id == CutPlane) - m_plane.model.set_color({ 0.9f, 0.9f, 0.9f, 0.5f }); - else - m_plane.model.set_color({ 0.8f, 0.8f, 0.8f, 0.5f }); - } - else - m_plane.model.set_color({ 1.0f, 0.8f, 0.8f, 0.5f }); + m_plane.model.set_color(can_perform_cut() && has_valid_contour() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR); if (m_mode == size_t(CutMode::cutPlanar)) { const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; @@ -964,11 +969,19 @@ std::string GLGizmoCut3D::on_get_name() const return _u8L("Cut"); } +void GLGizmoCut3D::apply_color_clip_plane_colors() +{ + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + m_parent.set_color_clip_plane_colors({ CUT_PLANE_DEF_COLOR , CUT_PLANE_DEF_COLOR }); + else + m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR }); +} + void GLGizmoCut3D::on_set_state() { if (m_state == On) { m_parent.set_use_color_clip_plane(true); - m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR }); + apply_color_clip_plane_colors(); update_bb(); m_connectors_editing = !m_selected.empty(); @@ -1777,12 +1790,8 @@ void GLGizmoCut3D::on_render() { if (!m_part_selection.valid()) process_contours(); - - m_contour_width = 0.f; //check_and_update_connectors_state(); } - else - m_contour_width = .4f; update_clipper(); @@ -2053,6 +2062,8 @@ void GLGizmoCut3D::flip_cut_plane() void GLGizmoCut3D::reset_cut_by_contours() { m_part_selection = PartSelection(); + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return; const Selection& selection = m_parent.get_selection(); const ModelObjectPtrs& model_objects = selection.get_model()->objects; @@ -2071,11 +2082,9 @@ void GLGizmoCut3D::process_contours() if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { cut_objects = perform_cut_with_groove(model_objects[object_idx], true); if (cut_objects.empty() || m_invalid_groove) { - m_parent.set_color_clip_plane_colors({ CONNECTOR_DEF_COLOR , CONNECTOR_DEF_COLOR }); + m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); return; } - else - m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR }); } reset_cut_by_contours(); @@ -2185,7 +2194,8 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::Separator(); // WIP : cut plane mode - render_cut_mode_combo(); + if (render_cut_mode_combo()) + mode = CutMode(m_mode); render_build_size(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ff5f45fb8e..292294d880 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -336,6 +336,7 @@ private: bool cut_line_processing() const; void discard_cut_line_processing(); + void apply_color_clip_plane_colors(); void render_cut_plate_for_tongue_and_groove(GLShaderProgram* shader); void render_cut_plane(); static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix); From 92c7a31f42e4b67ee68887cb877ac08b2962d46c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 29 Jun 2023 13:03:14 +0200 Subject: [PATCH 008/120] WIP: Cut with Tongue and Groove * Added move possibility during X axis for cut plane * Added possibility to optimize rendering (recalculate new cut only on stop dragging) * Next fix for flickering (code refactoring for toggle_model_objects_visibility()) --- src/libslic3r/Model.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 169 ++++++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 8 +- 3 files changed, 118 insertions(+), 61 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0d4a2877c1..5c001e3538 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1645,7 +1645,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, // Post-process cut parts ModelObjectPtrs res; - if (upper->volumes.empty()) + if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) return res; if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index d9201a25ec..27585aefd4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -317,8 +317,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) flip_cut_plane(); } - if (m_part_selection.valid()) - m_parent.toggle_model_objects_visibility(false); + toggle_model_objects_visibility(); return true; } @@ -487,8 +486,7 @@ bool GLGizmoCut3D::render_cut_mode_combo() m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); } - if (m_part_selection.valid()) - reset_cut_by_contours(); + reset_cut_by_contours(); } return is_changed; @@ -886,28 +884,49 @@ void GLGizmoCut3D::render_cut_plane_grabbers() render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); } - // render CutPlaneZRotation grabber + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - if (CutMode(m_mode) == CutMode::cutTongueAndGroove && (no_xy_grabber_hovered || m_hover_id == CutPlaneZRotation)) - { - size = 0.75 * (m_dragging && m_hover_id == CutPlaneZRotation ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + // render CutPlaneZRotation grabber - color = ColorRGBA::BLUE(); - ColorRGBA cp_color = m_hover_id == CutPlaneZRotation ? color : m_plane.model.get_color();; + if (no_xy_grabber_hovered || m_hover_id == CutPlaneZRotation) + { + size = 0.75 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = ColorRGBA::BLUE(); + const ColorRGBA cp_color = m_hover_id == CutPlaneZRotation ? color : m_plane.model.get_color(); - const double grabber_shift = -1.75 * m_grabber_connection_len; + const double grabber_shift = -1.75 * m_grabber_connection_len; - render_model(m_sphere.model, cp_color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + render_model(m_sphere.model, cp_color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + + if (m_hover_id == CutPlaneZRotation) { + const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + + render_rotation_snapping(CutPlaneZRotation, color); + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitX()), 1.75); + + Vec3d offset = Vec3d(1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + offset = Vec3d(-1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + } + + // render CutPlaneXMove grabber + + if (no_xy_grabber_hovered || m_hover_id == CutPlaneXMove) + { + size = 0.5 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = m_hover_id == CutPlaneXMove ? ColorRGBA::RED() : m_plane.model.get_color(); + + const double grabber_shift = 0.2 * m_grabber_connection_len; + + render_model(m_sphere.model, color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitX()) * scale_transform(size)); - if (m_hover_id == CutPlaneZRotation) { const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); - render_rotation_snapping(CutPlaneZRotation, color); - render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitX()), 1.75); - - Vec3d offset = Vec3d(1.25 * size, grabber_shift, 0.0); + Vec3d offset = Vec3d(1.25 * size + grabber_shift, 0.0, 0.0); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); - offset = Vec3d(-1.25 * size, grabber_shift, 0.0); + offset = Vec3d(-1.25 * size + grabber_shift, 0.0, 0.0); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); } } @@ -1039,9 +1058,13 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity())); if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_sphere.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_sphere.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); } } @@ -1140,14 +1163,23 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - const double grabber_shift = -1.75 * m_grabber_connection_len; + double grabber_shift = -1.75 * m_grabber_connection_len; + + m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); offset = Vec3d(1.25 * size, grabber_shift, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); offset = Vec3d(-1.25 * size, grabber_shift, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + grabber_shift = 0.2 * m_grabber_connection_len; + + m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_shift * Vec3d::UnitX()) * scale_transform(size)); + + offset = Vec3d(1.25 * size + grabber_shift, 0.0, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + offset = Vec3d(-1.25 * size + grabber_shift, 0.0, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); } } } @@ -1214,13 +1246,14 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3& return transform(mouse_ray, m).intersect_plane(0.0); } -void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data) +void GLGizmoCut3D::dragging_grabber_move(const GLGizmoBase::UpdateData &data) { - const Vec3d grabber_init_pos = (m_hover_id == CutPlane ? 0. : m_grabber_connection_len) * Vec3d::UnitZ(); + const Vec3d grabber_init_pos = m_hover_id == CutPlaneXMove ? 0.2 * m_grabber_connection_len * Vec3d::UnitX(): + (m_hover_id == CutPlane ? 0. : m_grabber_connection_len) * Vec3d::UnitZ(); const Vec3d starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * grabber_init_pos; double projection = 0.0; - Vec3d starting_vec = m_rotation_m * Vec3d::UnitZ(); + Vec3d starting_vec = m_rotation_m * (m_hover_id == CutPlaneXMove ? Vec3d::UnitX() : Vec3d::UnitZ()); if (starting_vec.norm() != 0.0) { const 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 @@ -1248,7 +1281,7 @@ void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data) m_was_cut_plane_dragged = true; } -void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) +void GLGizmoCut3D::dragging_grabber_rotation(const GLGizmoBase::UpdateData &data) { const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((GrabberID)m_hover_id, data.mouse_ray)); @@ -1315,13 +1348,16 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) { if (m_hover_id < 0) return; - if (m_hover_id == Z || m_hover_id == CutPlane) - dragging_grabber_z(data); + if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove) + dragging_grabber_move(data); else if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) - dragging_grabber_xy(data); + dragging_grabber_rotation(data); else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) dragging_connector(data); check_and_update_connectors_state(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); } void GLGizmoCut3D::on_start_dragging() @@ -1342,11 +1378,14 @@ void GLGizmoCut3D::on_stop_dragging() Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); m_start_dragging_m = m_rotation_m; } - else if (m_hover_id == Z || m_hover_id == CutPlane) { + else if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove) { if (m_was_cut_plane_dragged) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_plane_center = m_plane_center; } + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove && m_optimaze_groove_rendering) + reset_cut_by_contours(); //check_and_update_connectors_state(); } @@ -1468,6 +1507,8 @@ void GLGizmoCut3D::update_bb() if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info(); selection && selection->model_object()) m_selected.resize(selection->model_object()->cut_connectors.size(), false); + +// reset_cut_by_contours(); } } @@ -1785,13 +1826,8 @@ void GLGizmoCut3D::on_render() m_c->selection_info()->set_use_shift(true); } - // init part_selection for groove mode - if (CutMode(m_mode) == CutMode::cutTongueAndGroove) - { - if (!m_part_selection.valid()) - process_contours(); - //check_and_update_connectors_state(); - } + // check objects visibility + toggle_model_objects_visibility(); update_clipper(); @@ -2062,12 +2098,14 @@ void GLGizmoCut3D::flip_cut_plane() void GLGizmoCut3D::reset_cut_by_contours() { m_part_selection = PartSelection(); - if (CutMode(m_mode) == CutMode::cutTongueAndGroove) - return; - const Selection& selection = m_parent.get_selection(); - const ModelObjectPtrs& model_objects = selection.get_model()->objects; - m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (m_optimaze_groove_rendering && m_dragging) + return; + process_contours(); + } + else + toggle_model_objects_visibility(); } void GLGizmoCut3D::process_contours() @@ -2078,24 +2116,19 @@ void GLGizmoCut3D::process_contours() const int instance_idx = selection.get_instance_idx(); const int object_idx = selection.get_object_idx(); - ModelObjectPtrs cut_objects; - if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - cut_objects = perform_cut_with_groove(model_objects[object_idx], true); - if (cut_objects.empty() || m_invalid_groove) { - m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); - return; - } - } - - reset_cut_by_contours(); - wxBusyCursor wait; - if (CutMode(m_mode) == CutMode::cutTongueAndGroove) - m_part_selection = PartSelection(cut_objects.front(), instance_idx); - else + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + ModelObjectPtrs cut_objects = perform_cut_with_groove(model_objects[object_idx], true); + if (!cut_objects.empty() && !m_invalid_groove) + m_part_selection = PartSelection(cut_objects.front(), instance_idx); + } + else { + reset_cut_by_contours(); m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); - m_parent.toggle_model_objects_visibility(false); + } + + toggle_model_objects_visibility(); } void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/) @@ -2239,10 +2272,11 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) else if (mode == CutMode::cutTongueAndGroove) { ImGui::Separator(); ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Groove") + ": "); - render_groove_input(_u8L("Depth"), m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance); render_groove_input(_u8L("Width"), m_groove_width, m_groove_width_init, m_groove_width_tolerance); render_groove_input(_u8L("Angle"), m_groove_angle, m_groove_angle_init, m_groove_width_tolerance, true); + + m_imgui->checkbox(_L("Optimize rendering"), m_optimaze_groove_rendering); } ImGui::Separator(); @@ -2577,6 +2611,25 @@ void GLGizmoCut3D::check_and_update_connectors_state() } } +void GLGizmoCut3D::toggle_model_objects_visibility() +{ + bool has_active_volume = false; + std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + for (const auto raycaster : *raycasters) + if (raycaster->is_active()) { + has_active_volume = true; + break; + } + + if (m_part_selection.valid() && has_active_volume) + m_parent.toggle_model_objects_visibility(false); + else if (!m_part_selection.valid() && !has_active_volume) { + const Selection& selection = m_parent.get_selection(); + const ModelObjectPtrs& model_objects = selection.get_model()->objects; + m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); + } +} + void GLGizmoCut3D::render_connectors() { ::glEnable(GL_DEPTH_TEST); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 292294d880..433356cad7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -31,6 +31,7 @@ class GLGizmoCut3D : public GLGizmoBase Z, CutPlane, CutPlaneZRotation, + CutPlaneXMove, Count, }; @@ -124,6 +125,7 @@ class GLGizmoCut3D : public GLGizmoBase float m_groove_angle_init; float m_groove_depth_tolerance{ 0.1f }; float m_groove_width_tolerance{ 0.1f }; + bool m_optimaze_groove_rendering{ false }; bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; @@ -264,8 +266,8 @@ protected: bool on_is_activable() const override; bool on_is_selectable() const override; Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const; - void dragging_grabber_z(const GLGizmoBase::UpdateData &data); - void dragging_grabber_xy(const GLGizmoBase::UpdateData &data); + void dragging_grabber_move(const GLGizmoBase::UpdateData &data); + void dragging_grabber_rotation(const GLGizmoBase::UpdateData &data); void dragging_connector(const GLGizmoBase::UpdateData &data); void on_dragging(const UpdateData&data) override; void on_start_dragging() override; @@ -360,6 +362,8 @@ private: void validate_connector_settings(); bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); void check_and_update_connectors_state(); + + void toggle_model_objects_visibility(); }; } // namespace GUI From 6a2afc915392b1681ff6391d19a057d328ef3b55 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 29 Jun 2023 10:02:18 +0200 Subject: [PATCH 009/120] WIP: Add new connector - "Rivet" * Improves for UI * Implemented Rivet mesh --- resources/icons/rivet.svg | 13 ++ src/imgui/imconfig.h | 1 + src/libslic3r/Model.cpp | 4 +- src/libslic3r/Model.hpp | 1 + src/libslic3r/TriangleMesh.cpp | 175 +++++++++++++++++++++++++++ src/libslic3r/TriangleMesh.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 35 ++++-- src/slic3r/GUI/ImGuiWrapper.cpp | 1 + 8 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 resources/icons/rivet.svg diff --git a/resources/icons/rivet.svg b/resources/icons/rivet.svg new file mode 100644 index 0000000000..db4760cb65 --- /dev/null +++ b/resources/icons/rivet.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 5aed978426..a152252bac 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -152,6 +152,7 @@ namespace ImGui // const wchar_t MmuSegmentationMarker = 0x1F; const wchar_t PlugMarker = 0x1C; const wchar_t DowelMarker = 0x1D; + const wchar_t RivetMarker = 0x1E; // Do not forget use following letters only in wstring const wchar_t DocumentationButton = 0x2600; const wchar_t DocumentationHoverButton = 0x2601; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5c001e3538..7bf8608dde 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1302,7 +1302,9 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn break; } - if (connector_attributes.style == CutConnectorStyle::Prism) + if (connector_attributes.type == CutConnectorType::Rivet) + connector_mesh = its_make_rivet(1.0, 1.0, (2 * PI / /*sectorCount*/10)); + else if (connector_attributes.style == CutConnectorStyle::Prism) connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); else if (connector_attributes.type == CutConnectorType::Plug) connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount)); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2ee48f9917..d0409588ff 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -224,6 +224,7 @@ private: enum class CutConnectorType : int { Plug , Dowel + , Rivet , Undef }; diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 4bf9fd486a..374387273f 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1262,6 +1262,181 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC return mesh; } +indexed_triangle_set its_make_rivet(double r, double h, double fa) +{ + const float radius = (float)r; + const float height = (float)h; + const size_t sectors_cnt = 10;//(float)fa; + const float halfPI = 0.5f * (float)PI; + + const float space_len = 0.2f * radius; + + const float b_len = 0.75f * radius; + const float m_len = radius; + const float t_len = 0.5f * radius; + + const float b_height = 0.f; + const float m_height = 0.5f * height; + const float t_height = height; + + const float b_angle = acos(space_len/b_len); + const float m_angle = acos(space_len/m_len); + const float t_angle = acos(space_len/t_len); + + const float b_angle_step = b_angle / (float)sectors_cnt; + const float m_angle_step = m_angle / (float)sectors_cnt; + const float t_angle_step = t_angle / (float)sectors_cnt; + + const Vec2f b_vec = Eigen::Vector2f(0, b_len); + const Vec2f m_vec = Eigen::Vector2f(0, m_len); + const Vec2f t_vec = Eigen::Vector2f(0, t_len); + + const Vec2f space_vec = Eigen::Vector2f(0, space_len); + Vec2f b_pt = Eigen::Rotation2Df(halfPI) * space_vec; + + + indexed_triangle_set mesh; + + auto& vertices = mesh.vertices; + auto& facets = mesh.indices; + + vertices.reserve(2 * 3 * sectors_cnt + 2); + facets.reserve(2 * 6 * sectors_cnt); + + + float b_angle_start = halfPI - b_angle; + float m_angle_start = halfPI - m_angle; + float t_angle_start = halfPI - t_angle; + + float b_angle_stop = halfPI + b_angle; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.emplace_back(Vec3f(-space_len, 0.f, b_height)); + vertices.emplace_back(Vec3f(-space_len, 0.f, t_height)); + + int frst_id = 0; + int scnd_id = 1; + + auto add_side_vertices = [b_vec, m_vec, t_vec, b_height, m_height, t_height](std::vector& vertices, float b_angle, float m_angle, float t_angle) { + Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec; + Vec2f m_pt = Eigen::Rotation2Df(m_angle) * m_vec; + Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec; + + vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); + vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); + vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); + }; + + // add first vertices and facets + { + add_side_vertices(vertices, b_angle_start, m_angle_start, t_angle_start); + + int id = (int)vertices.size() - 1; + + facets.emplace_back(id - 4, id - 2, id - 1); + facets.emplace_back(id - 4, id - 1, id); + facets.emplace_back(id - 4, id, id - 3); + } + + while (b_angle_start < b_angle_stop) { + b_angle_start += b_angle_step; + m_angle_start += m_angle_step; + t_angle_start += t_angle_step; + + add_side_vertices(vertices, b_angle_start, m_angle_start, t_angle_start); + + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, id - 2, id - 5); + + facets.emplace_back(id - 2, id - 1, id - 5); + facets.emplace_back(id - 1, id - 4, id - 5); + facets.emplace_back(id - 4, id - 1, id); + facets.emplace_back(id, id - 3, id - 4); + + facets.emplace_back(id, scnd_id, id - 3); + } + + // add facets to close the mesh + { + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, scnd_id, id); + facets.emplace_back(frst_id, id, id - 1); + facets.emplace_back(frst_id, id - 1, id - 2); + } + + + + + b_angle_start = 3 * halfPI - b_angle; + m_angle_start = 3 * halfPI - m_angle; + t_angle_start = 3 * halfPI - t_angle; + + b_angle_stop = 3 * halfPI + b_angle; + + frst_id = (int)vertices.size(); + scnd_id = frst_id+1; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.emplace_back(Vec3f(space_len, 0.f, b_height)); + vertices.emplace_back(Vec3f(space_len, 0.f, t_height)); + + // add first vertices and facets + { + Vec2f b_pt = Eigen::Rotation2Df(b_angle_start) * b_vec; + Vec2f m_pt = Eigen::Rotation2Df(m_angle_start) * m_vec; + Vec2f t_pt = Eigen::Rotation2Df(t_angle_start) * t_vec; + + vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); + vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); + vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); + + int id = (int)vertices.size() - 1; + + facets.emplace_back(id - 4, id - 2, id - 1); + facets.emplace_back(id - 4, id - 1, id); + facets.emplace_back(id - 4, id , id - 3); + } + + while (b_angle_start < b_angle_stop) { + b_angle_start += b_angle_step; + m_angle_start += m_angle_step; + t_angle_start += t_angle_step; + + Vec2f b_pt = Eigen::Rotation2Df(b_angle_start) * b_vec; + Vec2f m_pt = Eigen::Rotation2Df(m_angle_start) * m_vec; + Vec2f t_pt = Eigen::Rotation2Df(t_angle_start) * t_vec; + + vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); + vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); + vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); + + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, id - 2, id - 5); + + facets.emplace_back(id - 2, id - 1, id - 5); + facets.emplace_back(id - 1, id - 4, id - 5); + facets.emplace_back(id - 4, id - 1, id); + facets.emplace_back(id, id - 3, id - 4); + + facets.emplace_back(id, scnd_id, id - 3); + } + + // add facets to close the mesh + { + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, scnd_id, id); + facets.emplace_back(frst_id, id, id - 1); + facets.emplace_back(frst_id, id - 1, id - 2); + } + + + 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 0f43f9d58d..f6a18f59ce 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -321,6 +321,7 @@ indexed_triangle_set its_make_frustum(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); +indexed_triangle_set its_make_rivet(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_convex_hull(const std::vector &pts); inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 27585aefd4..9a217a461d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -186,8 +186,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_style (int(CutConnectorStyle::Prism)) , m_connector_shape_id (int(CutConnectorShape::Circle)) { - m_mode = size_t(CutMode::cutTongueAndGroove); - m_contour_width = .0f; + m_connector_type = CutConnectorType::Rivet; m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") @@ -197,7 +196,8 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, std::map connetor_types = { {ImGui::PlugMarker , _u8L("Plug") }, - {ImGui::DowelMarker, _u8L("Dowel") }, + {ImGui::DowelMarker, _u8L("Dowel") }, + {ImGui::RivetMarker, _u8L("Rivet") }, }; for (auto connector : connetor_types) { std::string type_label = " " + connector.second + " "; @@ -1986,20 +1986,27 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) m_imgui->text(m_labels_map["Type"]); bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); + type_changed |= render_connect_type_radio_button(CutConnectorType::Rivet); 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 = int(CutConnectorStyle::Prism); - apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); - } - if (render_combo(m_labels_map["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_begin(m_connector_type != CutConnectorType::Plug); + if (type_changed && m_connector_type == CutConnectorType::Dowel) { + m_connector_style = int(CutConnectorStyle::Prism); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + } + if (render_combo(m_labels_map["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(m_labels_map["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); }); + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Rivet); + if (type_changed && m_connector_type == CutConnectorType::Rivet) { + m_connector_shape_id = int(CutConnectorShape::Circle); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + } + if (render_combo(m_labels_map["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); }); + m_imgui->disabled_end(); if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) apply_selected_connectors([this, &connectors](size_t idx) { @@ -3232,11 +3239,13 @@ void GLGizmoCut3D::reset_connectors() void GLGizmoCut3D::init_connector_shapes() { - for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug}) + for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug, CutConnectorType::Rivet}) for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prism}) { if (type == CutConnectorType::Dowel && style == CutConnectorStyle::Frustum) continue; for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { + if (type == CutConnectorType::Rivet && shape != CutConnectorShape::Circle) + continue; const CutConnectorAttributes attribs = { type, style, shape }; indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); m_shapes[attribs].model.init_from(its); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 1a800eee42..ec1d039888 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -67,6 +67,7 @@ static const std::map font_icons = { {ImGui::InfoMarkerSmall , "notification_info" }, {ImGui::PlugMarker , "plug" }, {ImGui::DowelMarker , "dowel" }, + {ImGui::RivetMarker , "rivet" }, }; static const std::map font_icons_large = { From 9fd3de5e46ba93282fcab80bc8db7369ebc77109 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Jul 2023 15:04:29 +0200 Subject: [PATCH 010/120] WIP: Add new connector - "Rivet" * Code refactoring for its_make_rivet() to change a shape of rivet + Implemented perform the cut with rivets --- src/libslic3r/Model.cpp | 20 +++- src/libslic3r/TriangleMesh.cpp | 198 ++++++++++++--------------------- src/libslic3r/TriangleMesh.hpp | 2 +- 3 files changed, 90 insertions(+), 130 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7bf8608dde..e57d576ff2 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1303,7 +1303,7 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn } if (connector_attributes.type == CutConnectorType::Rivet) - connector_mesh = its_make_rivet(1.0, 1.0, (2 * PI / /*sectorCount*/10)); + connector_mesh = its_make_rivet(1.0, 1.0); else if (connector_attributes.style == CutConnectorStyle::Prism) connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); else if (connector_attributes.type == CutConnectorType::Plug) @@ -1415,7 +1415,8 @@ void ModelVolume::apply_tolerance() rot_norm.normalize(); double z_offset = 0.5 * static_cast(cut_info.height_tolerance); - if (cut_info.connector_type == CutConnectorType::Plug) + if (cut_info.connector_type == CutConnectorType::Plug || + cut_info.connector_type == CutConnectorType::Rivet) z_offset -= 0.05; // add small Z offset to better preview set_offset(get_offset() + rot_norm * z_offset); @@ -1452,7 +1453,20 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& // This transformation is already there if (volume->cut_info.connector_type != CutConnectorType::Dowel) { if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - ModelVolume* vol = upper->add_volume(*volume); + ModelVolume* vol = nullptr; + if (volume->cut_info.connector_type == CutConnectorType::Rivet) { + TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); + + vol = upper->add_volume(std::move(mesh)); + vol->set_transformation(volume->get_transformation()); + vol->set_type(ModelVolumeType::NEGATIVE_VOLUME); + + vol->cut_info = volume->cut_info; + vol->name = volume->name; + } + else + vol = upper->add_volume(*volume); + vol->set_transformation(volume_matrix); vol->apply_tolerance(); } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 374387273f..4d1d163851 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1262,17 +1262,17 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC return mesh; } -indexed_triangle_set its_make_rivet(double r, double h, double fa) +indexed_triangle_set its_make_rivet(double r, double h, float space_proportion) { const float radius = (float)r; const float height = (float)h; - const size_t sectors_cnt = 10;//(float)fa; + const size_t sectors_cnt = 10; //(float)fa; const float halfPI = 0.5f * (float)PI; - const float space_len = 0.2f * radius; + const float space_len = space_proportion * radius; - const float b_len = 0.75f * radius; - const float m_len = radius; + const float b_len = radius; + const float m_len = (1 + 0.5f*space_proportion) * radius; const float t_len = 0.5f * radius; const float b_height = 0.f; @@ -1280,46 +1280,18 @@ indexed_triangle_set its_make_rivet(double r, double h, double fa) const float t_height = height; const float b_angle = acos(space_len/b_len); - const float m_angle = acos(space_len/m_len); const float t_angle = acos(space_len/t_len); const float b_angle_step = b_angle / (float)sectors_cnt; - const float m_angle_step = m_angle / (float)sectors_cnt; const float t_angle_step = t_angle / (float)sectors_cnt; const Vec2f b_vec = Eigen::Vector2f(0, b_len); - const Vec2f m_vec = Eigen::Vector2f(0, m_len); const Vec2f t_vec = Eigen::Vector2f(0, t_len); - const Vec2f space_vec = Eigen::Vector2f(0, space_len); - Vec2f b_pt = Eigen::Rotation2Df(halfPI) * space_vec; - - indexed_triangle_set mesh; - - auto& vertices = mesh.vertices; - auto& facets = mesh.indices; - - vertices.reserve(2 * 3 * sectors_cnt + 2); - facets.reserve(2 * 6 * sectors_cnt); - - - float b_angle_start = halfPI - b_angle; - float m_angle_start = halfPI - m_angle; - float t_angle_start = halfPI - t_angle; - - float b_angle_stop = halfPI + b_angle; - - // 2 special vertices, top and bottom center, rest are relative to this - vertices.emplace_back(Vec3f(-space_len, 0.f, b_height)); - vertices.emplace_back(Vec3f(-space_len, 0.f, t_height)); - - int frst_id = 0; - int scnd_id = 1; - - auto add_side_vertices = [b_vec, m_vec, t_vec, b_height, m_height, t_height](std::vector& vertices, float b_angle, float m_angle, float t_angle) { + auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector& vertices, float b_angle, float t_angle, const Vec2f& m_vec) { Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec; - Vec2f m_pt = Eigen::Rotation2Df(m_angle) * m_vec; + Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec; Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec; vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); @@ -1327,25 +1299,8 @@ indexed_triangle_set its_make_rivet(double r, double h, double fa) vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); }; - // add first vertices and facets - { - add_side_vertices(vertices, b_angle_start, m_angle_start, t_angle_start); - - int id = (int)vertices.size() - 1; - - facets.emplace_back(id - 4, id - 2, id - 1); - facets.emplace_back(id - 4, id - 1, id); - facets.emplace_back(id - 4, id, id - 3); - } - - while (b_angle_start < b_angle_stop) { - b_angle_start += b_angle_step; - m_angle_start += m_angle_step; - t_angle_start += t_angle_step; - - add_side_vertices(vertices, b_angle_start, m_angle_start, t_angle_start); - - int id = (int)vertices.size() - 1; + auto add_side_facets = [](std::vector& facets, int vertices_cnt, int frst_id, int scnd_id) { + int id = vertices_cnt - 1; facets.emplace_back(frst_id, id - 2, id - 5); @@ -1355,84 +1310,75 @@ indexed_triangle_set its_make_rivet(double r, double h, double fa) facets.emplace_back(id, id - 3, id - 4); facets.emplace_back(id, scnd_id, id - 3); - } + }; - // add facets to close the mesh - { - int id = (int)vertices.size() - 1; + const float f = (b_len - m_len) / m_len; // Flattening - facets.emplace_back(frst_id, scnd_id, id); - facets.emplace_back(frst_id, id, id - 1); - facets.emplace_back(frst_id, id - 1, id - 2); - } + auto get_m_len = [b_len, f](float angle) { + const float rad_sqr = b_len * b_len; + const float sin_sqr = sin(angle) * sin(angle); + const float f_sqr = (1-f)*(1-f); + return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr)); + }; + + auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len, + b_height, t_height, b_angle, t_angle, b_angle_step, t_angle_step] + (indexed_triangle_set& mesh, float center_x, float angle_rotation, int frst_vertex_id) { + auto& vertices = mesh.vertices; + auto& facets = mesh.indices; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.emplace_back(Vec3f(center_x, 0.f, b_height)); + vertices.emplace_back(Vec3f(center_x, 0.f, t_height)); + + float b_angle_start = angle_rotation - b_angle; + float t_angle_start = angle_rotation - t_angle; + const float b_angle_stop = angle_rotation + b_angle; + + const int frst_id = frst_vertex_id; + const int scnd_id = frst_id + 1; + + // add first side vertices and internal facets + { + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, id - 2, id - 1); + facets.emplace_back(frst_id, id - 1, id); + facets.emplace_back(frst_id, id, scnd_id); + } + + // add d side vertices and facets + while (b_angle_start < b_angle_stop) { + b_angle_start += b_angle_step; + t_angle_start += t_angle_step; + + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + add_side_facets(facets, (int)vertices.size(), frst_id, scnd_id); + } + + // add last internal facets to close the mesh + { + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, scnd_id, id); + facets.emplace_back(frst_id, id, id - 1); + facets.emplace_back(frst_id, id - 1, id - 2); + } + }; + indexed_triangle_set mesh; + mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2)); + mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6)); - b_angle_start = 3 * halfPI - b_angle; - m_angle_start = 3 * halfPI - m_angle; - t_angle_start = 3 * halfPI - t_angle; - - b_angle_stop = 3 * halfPI + b_angle; - - frst_id = (int)vertices.size(); - scnd_id = frst_id+1; - - // 2 special vertices, top and bottom center, rest are relative to this - vertices.emplace_back(Vec3f(space_len, 0.f, b_height)); - vertices.emplace_back(Vec3f(space_len, 0.f, t_height)); - - // add first vertices and facets - { - Vec2f b_pt = Eigen::Rotation2Df(b_angle_start) * b_vec; - Vec2f m_pt = Eigen::Rotation2Df(m_angle_start) * m_vec; - Vec2f t_pt = Eigen::Rotation2Df(t_angle_start) * t_vec; - - vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); - vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); - vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); - - int id = (int)vertices.size() - 1; - - facets.emplace_back(id - 4, id - 2, id - 1); - facets.emplace_back(id - 4, id - 1, id); - facets.emplace_back(id - 4, id , id - 3); - } - - while (b_angle_start < b_angle_stop) { - b_angle_start += b_angle_step; - m_angle_start += m_angle_step; - t_angle_start += t_angle_step; - - Vec2f b_pt = Eigen::Rotation2Df(b_angle_start) * b_vec; - Vec2f m_pt = Eigen::Rotation2Df(m_angle_start) * m_vec; - Vec2f t_pt = Eigen::Rotation2Df(t_angle_start) * t_vec; - - vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); - vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); - vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); - - int id = (int)vertices.size() - 1; - - facets.emplace_back(frst_id, id - 2, id - 5); - - facets.emplace_back(id - 2, id - 1, id - 5); - facets.emplace_back(id - 1, id - 4, id - 5); - facets.emplace_back(id - 4, id - 1, id); - facets.emplace_back(id, id - 3, id - 4); - - facets.emplace_back(id, scnd_id, id - 3); - } - - // add facets to close the mesh - { - int id = (int)vertices.size() - 1; - - facets.emplace_back(frst_id, scnd_id, id); - facets.emplace_back(frst_id, id, id - 1); - facets.emplace_back(frst_id, id - 1, id - 2); - } - + add_sub_mesh(mesh, -space_len, halfPI , 0); + add_sub_mesh(mesh, space_len, 3 * halfPI, (int)mesh.vertices.size()); return mesh; } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index f6a18f59ce..d56f868637 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -321,7 +321,7 @@ indexed_triangle_set its_make_frustum(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); -indexed_triangle_set its_make_rivet(double r, double h, double fa=(2*PI/360)); +indexed_triangle_set its_make_rivet(double r, double h, float space_proportion = 0.25f); indexed_triangle_set its_convex_hull(const std::vector &pts); inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); } From 83fa61ee41677fe8b43d19321949bbf9e6e8df07 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Jul 2023 12:49:37 +0200 Subject: [PATCH 011/120] Cut: Extend WIP: Add new connector - "Rivet" to set max value and max tolerance --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 14 +++++++++----- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9a217a461d..22370f0a84 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -523,7 +523,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i return !is_approx(old_val, value); } -bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in) +bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float max_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) { constexpr float UndefMinVal = -0.1f; const float f_mm_to_in = static_cast(ObjectManipulation::mm_to_in); @@ -547,18 +547,22 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v const BoundingBoxf3 bbox = m_bounding_box; const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f); + const float max_v = max_val > 0.f ? /*std::min(max_val, mean_size)*/max_val : mean_size; ImGuiWrapper::text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width * 0.7f); - const bool is_value_changed = render_slider("##" + label, value_in, 1.f, mean_size, _L("Value")); +// const bool is_value_changed = render_slider("##" + label, value_in, 1.f, mean_size, _L("Value")); + const bool is_value_changed = render_slider("##" + label, value_in, 1.f, max_v, _L("Value")); ImGui::SameLine(); ImGui::PushItemWidth(m_control_width * 0.45f); - const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, 0.5f * mean_size, _L("Tolerance")); +// const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, 0.5f * mean_size, _L("Tolerance")); + const float max_tolerance_v = max_tolerance > 0.f ? std::min(max_tolerance, 0.5f * mean_size) : 0.5f * mean_size; + const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, max_tolerance_v, _L("Tolerance")); return is_value_changed || is_tolerance_changed; } @@ -1490,7 +1494,7 @@ void GLGizmoCut3D::update_bb() // input params for cut with tongue and groove m_groove_depth = m_groove_depth_init = 0.5f * float(get_grabber_mean_size(m_bounding_box)); m_groove_width = m_groove_width_init = 4.0f * m_groove_depth; - m_groove_angle = m_groove_angle_init = 0.25f * float(PI); + m_groove_angle = m_groove_angle_init = float(PI) / 3.f;// 0.25f * float(PI); m_plane.reset(); m_cone.reset(); @@ -2201,7 +2205,7 @@ void GLGizmoCut3D::render_groove_input(const std::string& label, float& in_val, is_changed = true; } } - else if (render_slider_double_input(label, in_val, in_tolerance)) + else if (render_slider_double_input(label, in_val, in_tolerance, -0.1f, 2.0)) is_changed = true; ImGui::SameLine(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 433356cad7..f2842ffbca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -323,7 +323,7 @@ private: bool render_cut_mode_combo(); bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx); bool render_double_input(const std::string& label, double& value_in); - bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in); + bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float max_val = -0.1f, float max_tolerance = -0.1f); 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; From 2e6d1ff08fc8f2dfb7c9108c48e7441d12f21271 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Jul 2023 13:44:17 +0200 Subject: [PATCH 012/120] WIP: Cut with Tongue and Groove * Changed limit value for tolerances * Set optimize rendering value to true by default --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 3 ++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 22370f0a84..be7ad12a2c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -480,6 +480,7 @@ bool GLGizmoCut3D::render_cut_mode_combo() if (is_changed) { m_mode = size_t(selection_idx); + update_raycasters_for_picking(); apply_color_clip_plane_colors(); if (auto oc = m_c->object_clipper()) { @@ -2205,7 +2206,7 @@ void GLGizmoCut3D::render_groove_input(const std::string& label, float& in_val, is_changed = true; } } - else if (render_slider_double_input(label, in_val, in_tolerance, -0.1f, 2.0)) + else if (render_slider_double_input(label, in_val, in_tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) is_changed = true; ImGui::SameLine(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index f2842ffbca..9d9c8bcf89 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -125,7 +125,7 @@ class GLGizmoCut3D : public GLGizmoBase float m_groove_angle_init; float m_groove_depth_tolerance{ 0.1f }; float m_groove_width_tolerance{ 0.1f }; - bool m_optimaze_groove_rendering{ false }; + bool m_optimaze_groove_rendering{ true }; bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; From 7cd99d98f537198b78158feced2aa8977bfcbe08 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Jul 2023 18:06:51 +0200 Subject: [PATCH 013/120] WIP: Cut with Rivets * Code refactoring: get_connector_mesh() and apply_cut_connectors() moved from ModelObject to CutGizmo. * Allow to change values of space and bulges for snaps --- src/libslic3r/Model.cpp | 60 ------------ src/libslic3r/Model.hpp | 2 - src/libslic3r/TriangleMesh.cpp | 6 +- src/libslic3r/TriangleMesh.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 141 ++++++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 9 +- 6 files changed, 139 insertions(+), 81 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index e57d576ff2..dd0553100e 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1280,66 +1280,6 @@ bool ModelObject::has_connectors() const return false; } -indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) -{ - indexed_triangle_set connector_mesh; - - int sectorCount {1}; - 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; - default: - break; - } - - if (connector_attributes.type == CutConnectorType::Rivet) - connector_mesh = its_make_rivet(1.0, 1.0); - else if (connector_attributes.style == CutConnectorStyle::Prism) - connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); - else if (connector_attributes.type == CutConnectorType::Plug) - connector_mesh = its_make_frustum(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& 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)); - // 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(translation_transform(connector.pos) * connector.rotation_m * - scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); - - 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()); - - // delete all connectors - cut_connectors.clear(); -} - void ModelObject::invalidate_cut() { this->cut_id.invalidate(); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index d0409588ff..7f9b354408 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -463,8 +463,6 @@ public: size_t materials_count() const; size_t facets_count() const; 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 object and its connectors/volumes void invalidate_cut(); // delete volumes which are marked as connector for this object diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 4d1d163851..236fa22c51 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1262,7 +1262,7 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC return mesh; } -indexed_triangle_set its_make_rivet(double r, double h, float space_proportion) +indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion) { const float radius = (float)r; const float height = (float)h; @@ -1272,7 +1272,7 @@ indexed_triangle_set its_make_rivet(double r, double h, float space_proportion) const float space_len = space_proportion * radius; const float b_len = radius; - const float m_len = (1 + 0.5f*space_proportion) * radius; + const float m_len = (1 + bulge_proportion) * radius; const float t_len = 0.5f * radius; const float b_height = 0.f; @@ -1351,7 +1351,7 @@ indexed_triangle_set its_make_rivet(double r, double h, float space_proportion) } // add d side vertices and facets - while (b_angle_start < b_angle_stop) { + while (!is_approx(b_angle_start, b_angle_stop)) { b_angle_start += b_angle_step; t_angle_start += t_angle_step; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index d56f868637..4b524402c0 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -321,7 +321,7 @@ indexed_triangle_set its_make_frustum(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); -indexed_triangle_set its_make_rivet(double r, double h, float space_proportion = 0.25f); +indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f); indexed_triangle_set its_convex_hull(const std::vector &pts); inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index be7ad12a2c..8b00172ab9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -230,7 +230,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, {"Size" , _u8L("Size")}, }; - update_connector_shape(); +// update_connector_shape(); } std::string GLGizmoCut3D::get_tooltip() const @@ -498,8 +498,8 @@ bool GLGizmoCut3D::render_combo(const std::string& label, const std::vectorcombo(label, lines, selection_idx, 0, m_label_width, m_control_width); - if (is_changed) - update_connector_shape(); + //if (is_changed) + // update_connector_shape(); return is_changed; } @@ -524,7 +524,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i return !is_approx(old_val, value); } -bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float max_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) +bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) { constexpr float UndefMinVal = -0.1f; const float f_mm_to_in = static_cast(ObjectManipulation::mm_to_in); @@ -548,7 +548,7 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v const BoundingBoxf3 bbox = m_bounding_box; const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f); - const float max_v = max_val > 0.f ? /*std::min(max_val, mean_size)*/max_val : mean_size; + const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/min_val : 1.f; ImGuiWrapper::text(label); @@ -556,7 +556,7 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v ImGui::PushItemWidth(m_control_width * 0.7f); // const bool is_value_changed = render_slider("##" + label, value_in, 1.f, mean_size, _L("Value")); - const bool is_value_changed = render_slider("##" + label, value_in, 1.f, max_v, _L("Value")); + const bool is_value_changed = render_slider("##" + label, value_in, min_v, mean_size, _L("Value")); ImGui::SameLine(); ImGui::PushItemWidth(m_control_width * 0.45f); @@ -599,7 +599,7 @@ bool GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type) ImGui::PushItemWidth(m_control_width); if (ImGui::RadioButton(m_connector_types[size_t(type)].c_str(), m_connector_type == type)) { m_connector_type = type; - update_connector_shape(); +// update_connector_shape(); return true; } return false; @@ -2013,7 +2013,8 @@ 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); }); m_imgui->disabled_end(); - if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) + const float depth_min_value = m_connector_type == CutConnectorType::Rivet ? m_connector_size : -0.1f; + if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_depth_ratio > 0) connectors[idx].height = m_connector_depth_ratio; @@ -2029,6 +2030,45 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) connectors[idx].radius_tolerance = 0.5f * m_connector_size_tolerance; }); + if (m_connector_type == CutConnectorType::Rivet) { + + const std::string format = "%.0f %%"; + + bool is_changed = false; + { + const std::string label = _u8L("Bulge"); + ImGuiWrapper::text(label); + + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.7f); + + float val = m_snap_bulge_proportion *100.f; + if (m_imgui->slider_float(("##snap_" + label).c_str(), &val, 5.f, 100.f * m_snap_space_proportion, format.c_str(), 1.f, true, _u8L("Bulge proportion related to radius"))) { + m_snap_bulge_proportion = val * 0.01f; + is_changed = true; + } + } + + { + const std::string label = _u8L("Space"); + ImGuiWrapper::text(label); + + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.7f); + + float val = m_snap_space_proportion *100.f; + if (m_imgui->slider_float(("##snap_" + label).c_str(), &val, 10.f, 50.f, format.c_str(), 1.f, true, _u8L("Space proportion related to radius"))) { + m_snap_space_proportion = val * 0.01f; + is_changed = true; + } + } + + if (is_changed) { + update_connector_shape(); + update_raycasters_for_picking(); + } + } + ImGui::Separator(); if (m_imgui->button(_L("Confirm connectors"))) { @@ -2224,7 +2264,7 @@ void GLGizmoCut3D::render_groove_input(const std::string& label, float& in_val, reset_cut_by_contours(); // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); } -}; +} void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) { @@ -2758,7 +2798,7 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count) connector.pos += m_cut_normal * 0.5 * double(connector.height); } } - mo->apply_cut_connectors(_u8L("Connector")); + apply_cut_connectors(mo, _u8L("Connector")); } } @@ -3252,7 +3292,7 @@ void GLGizmoCut3D::init_connector_shapes() if (type == CutConnectorType::Rivet && shape != CutConnectorShape::Circle) continue; const CutConnectorAttributes attribs = { type, style, shape }; - indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); + indexed_triangle_set its = 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))); } @@ -3263,9 +3303,18 @@ void GLGizmoCut3D::update_connector_shape() { CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; - const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); - m_connector_mesh.clear(); - m_connector_mesh = TriangleMesh(its); + if (m_connector_type == CutConnectorType::Rivet) { + indexed_triangle_set its = get_connector_mesh(attribs); + m_shapes[attribs].reset(); + 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 = get_connector_mesh(attribs); + //m_connector_mesh.clear(); + //m_connector_mesh = TriangleMesh(its); + } + + } bool GLGizmoCut3D::cut_line_processing() const @@ -3522,5 +3571,69 @@ void GLGizmoCut3D::data_changed(bool is_serializing) } + + +indexed_triangle_set GLGizmoCut3D::get_connector_mesh(CutConnectorAttributes connector_attributes) +{ + indexed_triangle_set connector_mesh; + + int sectorCount{ 1 }; + 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; + default: + break; + } + + if (connector_attributes.type == CutConnectorType::Rivet) + connector_mesh = its_make_snap(1.0, 1.0, m_snap_space_proportion, m_snap_bulge_proportion); + else if (connector_attributes.style == CutConnectorStyle::Prism) + connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); + else if (connector_attributes.type == CutConnectorType::Plug) + connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount)); + else + connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); + + return connector_mesh; +} + +void GLGizmoCut3D::apply_cut_connectors(ModelObject* mo, const std::string& connector_name) +{ + if (mo->cut_connectors.empty()) + return; + + using namespace Geometry; + + size_t connector_id = mo->cut_id.connectors_cnt(); + for (const CutConnector& connector : mo->cut_connectors) { + TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); + // Mesh will be centered when loading. + ModelVolume* new_volume = mo->add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); + + // Transform the new modifier to be aligned inside the instance + new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m * + scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); + + new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; + new_volume->name = connector_name + "-" + std::to_string(++connector_id); + } + mo->cut_id.increase_connectors_cnt(mo->cut_connectors.size()); + + // delete all connectors + mo->cut_connectors.clear(); +} + + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 9d9c8bcf89..f4140c9c26 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -127,6 +127,10 @@ class GLGizmoCut3D : public GLGizmoBase float m_groove_width_tolerance{ 0.1f }; bool m_optimaze_groove_rendering{ true }; + // Input params for cut with snaps + float m_snap_bulge_proportion{ 0.15f }; + float m_snap_space_proportion{ 0.3f }; + bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; bool m_cut_plane_as_circle{ false }; @@ -323,7 +327,7 @@ private: bool render_cut_mode_combo(); bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx); bool render_double_input(const std::string& label, double& value_in); - bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float max_val = -0.1f, float max_tolerance = -0.1f); + bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f); 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; @@ -364,6 +368,9 @@ private: void check_and_update_connectors_state(); void toggle_model_objects_visibility(); + + indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); + void apply_cut_connectors(ModelObject* mo, const std::string& connector_name); }; } // namespace GUI From e4fde63c47800e744a8a68ab5ac2c6cef2ce9e69 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Jul 2023 12:35:43 +0200 Subject: [PATCH 014/120] WIP: Cut with tongue and groove * Allow to change angle of the groove * Rewrite rendering for the cut plane + Added reset_cut_by_contours() on flip of cutPlane and switch of cutPlane mode --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 345 ++++++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 9 +- 2 files changed, 294 insertions(+), 60 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8b00172ab9..76964983a6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -642,6 +642,225 @@ static double get_grabber_mean_size(const BoundingBoxf3& bb) return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.; } +indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane() +{ + const float ghw = 0.5f * m_groove_width; // groove half width + const float cpr = 1.5f * float(m_radius); // cut plane radius + + const float cpl = 1.5f * cpr; // cut plane length + const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height + + // We need two upward facing triangles + float x = 0.5f * cpr, y = 0.5f * cpl; + + const float proj = y * tan(m_groove_angle); + const float extension_x = ghw + proj; + + // upper cut plane is simple + + if (is_approx(ghw, proj)) { + + return { + { + // roof + {0,1,6}, {6,1,2}, {2,3,5}, {5,3,4}, + // sides + {0,7,8}, {0,8,1}, {1,8,9}, {1,9,2}, {2,9,10}, {2,10,3}, {3,10,11}, {3,11,4}, + {4,11,12}, {4,12,5}, {5, 12,13}, {5,13,6}, {6,13,7}, {6, 7,0}, + // bottom + {7,13,8}, {8,13,9}, {10,9,12}, {10,12,4} + }, + { + // roof vertices + {-x, -y, cph}, {-extension_x, -y, cph}, {0.f, y, cph}, {extension_x, -y, cph}, {x, -y, cph}, {x, y, cph}, {-x, y, cph}, + // bottom vertices + {-x, -y, 0.f}, {-extension_x, -y, 0.f}, {0.f, y, 0.f}, {extension_x, -y, 0.f}, {x, -y, 0.f}, {x, y, 0.f}, {-x, y, 0.f}, + } + }; + } + + if (ghw < proj) { + + const float cross_pt_y = ghw / tan(m_groove_angle); + return { + { + // roof + {0,1,6}, {1,2,6}, {2,5,6}, {2,3,5}, {3,4,5}, + // sides + {0,7,8}, {0,8,1}, {1,8,9}, {1,9,2}, {2,9,10}, {2,10,3}, {3,10,11}, {3,11,4}, + {4,11,12}, {4,12,5}, {5,12,13}, {5,13,6}, {6,13,7}, {6,7,0}, + // bottom + {7,13,8}, {8,13,9}, {9,13,12}, {9,12,10}, {10,12, 11} + }, + { + // roof vertices + {-x, -y, cph}, {-extension_x, -y, cph}, {0.f, cross_pt_y, cph}, {extension_x, -y, cph}, {x, -y, cph}, {x, y, cph}, {-x, y, cph}, + // bottom vertices + {-x, -y, 0.f}, {-extension_x, -y, 0.f}, {0.f, cross_pt_y, 0.f}, {extension_x, -y, 0.f}, {x, -y, 0.f}, {x, y, 0.f}, {-x, y, 0.f} + } + }; + } + + // upper cut plane contains 2 sub planes + + const float narrowing_x = ghw - proj; + return { + { + // roof + {0,1,3}, {3,1,2}, {7,4,6}, {6,4,5}, + // sides + {0,8,9}, {0,9,1}, {1,9,10}, {1,10,2}, {2,10,11}, {2,11,3}, {3,11,8}, {3,8,0}, + {4,12,13}, {4,13,5}, {5,13,14}, {5,14,6}, {6,14,15}, {6,15,7}, {7,15,12}, {7,12,4}, + // bottom + {9,8,11}, {9,11,10}, {12,15,14}, {12,14,13} + }, + { + // roof vertices + {-x, -y, cph}, {-extension_x, -y, cph}, {-narrowing_x, y, cph}, {-x, y, cph}, {extension_x, -y, cph}, {x, -y, cph}, {x, y, cph}, {narrowing_x, y, cph}, + // bottom vertices + {-x, -y, 0.f}, {-extension_x, -y, 0.f}, {-narrowing_x, y, 0.f}, {-x, y, 0.f}, {extension_x, -y, 0.f}, {x, -y, 0.f}, {x, y, 0.f}, {narrowing_x, y, 0.f}, + } + }; +} + +indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width) +{ + const float ghw = 0.5f * (m_groove_width + flaps_width); // groove half width + const float cpr = 1.5f * float(m_radius); // cut plane radius + + const float cpl = 1.5f * cpr; // cut plane length + const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height + + // We need two upward facing triangles + float x = 0.5f * cpr, y = 0.5f * cpl; + + const float proj = y * tan(m_groove_angle); + const float extension_x = ghw + proj; + + // upper cut plane is trapezium + + if (ghw > proj) { + + const float narrowing_x = ghw - proj; + return { + { + // roof + {0,3,1}, {1,3,2}, + // sides + {0,4,7}, {0,7,3}, {3,7,6}, {3,6,2}, {2,6,5}, {2,5,1}, {1,5,4}, {1,4,0}, + // bottom + {4,5,7}, {7,5,6} + }, + { + // roof vertices + {-extension_x, -y, cph}, {-narrowing_x, y, cph}, {narrowing_x, y, cph}, {extension_x, -y, cph}, + // bottom vertices + {-extension_x, -y, 0.f}, {-narrowing_x, y, 0.f}, {narrowing_x, y, 0.f}, {extension_x, -y, 0.f} + } + }; + } + + // upper cut plane is triangle + + const float cross_pt_y = ghw / tan(m_groove_angle); + return { + { + // roof + {0,2,1}, + // sides + {0,3,5}, {0,5,2}, {2,5,4}, {2,4,1}, {1,4,3}, {0,1,3}, + // bottom + {3,4,5} + }, + { + // roof vertices + {-extension_x, -y, cph}, {0.f, cross_pt_y, cph}, {extension_x, -y, cph}, + // bottom vertices + {-extension_x, -y, 0.f}, {0.f, cross_pt_y, 0.f}, {extension_x, -y, 0.f} + } + }; +} + +indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width) +{ + const float ghw_upper = 0.5f * (m_groove_width); // groove half width + const float ghw_lower = 0.5f * (m_groove_width + flaps_width); // groove half width + const float cpr = 1.5f * float(m_radius); // cut plane radius + + const float cpl = 1.5f * cpr; // cut plane length + const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height + + const float ghd = 0.5f * m_groove_depth; // groove half depth + + + // We need two upward facing triangles + float x = 0.5f * cpr, y = 0.5f * cpl; + float z_upper = ghd; + float z_lower = -ghd; + + const float proj = y * tan(m_groove_angle); + + const float extension_upper_x = ghw_upper + proj; + const float extension_lower_x = ghw_lower + proj; + + const float narrowing_upper_x = ghw_upper - proj; + const float narrowing_lower_x = ghw_lower - proj; + + // groove is open + + if (ghw_upper > proj && ghw_lower > proj) { + return { + { + {1,0,2}, {2,0,3}, {5,4,7}, {5,7,6}, + {0,1,2}, {3,0,2}, {4,5,7}, {7,5,6} + }, + { + // left vertices + {-extension_lower_x, -y, z_lower}, {-extension_upper_x, -y, z_upper}, {-narrowing_upper_x, y, z_upper}, {-narrowing_lower_x, y, z_lower}, + // right vertices + {narrowing_lower_x, y, z_lower}, {narrowing_upper_x, y, z_upper}, {extension_upper_x, -y, z_upper}, {extension_lower_x, -y, z_lower} + } + }; + } + + const float cross_pt_upper_y = ghw_upper / tan(m_groove_angle); + + // groove is closed + + if (ghw_upper < proj && ghw_lower < proj) { + const float cross_pt_lower_y = ghw_lower / tan(m_groove_angle); + + return { + { + {1,0,3}, {1,3,4}, {1,4,5}, {1,5,2}, + {0,1,3}, {3,1,4}, {4,1,5}, {5,1,2} + }, + { + // roof vertices + {-extension_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {extension_upper_x, -y, z_upper}, + // bottom vertices + {-extension_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {extension_lower_x, -y, z_lower} + } + }; + + } + + // groove is closed from the roof + + return { + { + {1,0,3}, {1,3,4}, {1,5,6}, {1,6,2}, + {0,1,3}, {3,1,4}, {5,1,6}, {6,1,2} + }, + { + // roof vertices + {-extension_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {extension_upper_x, -y, z_upper}, + // bottom vertices + {-extension_lower_x, -y, z_lower}, {-narrowing_lower_x, y, z_lower}, {narrowing_lower_x, y, z_lower}, {extension_lower_x, -y, z_lower} + } + }; +} + void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shader) { const Camera & camera = wxGetApp().plater()->get_camera(); @@ -650,37 +869,25 @@ void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shade // values for calculaton const double groove_half_depth = 0.5 * double(m_groove_depth); - const double groove_half_width = 0.5 * double(m_groove_width); - const double cp_radius = 1.5 * m_radius; - - const double cp_half_width = 0.5 * cp_radius; - const float cp_length = 1.5f * float(cp_radius); - const float cp_height = 0.02f * (float)get_grabber_mean_size(m_bounding_box); - - const double h_shift = 0.5 * cp_half_width + groove_half_width; - - const float side_width = is_approx(m_groove_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_angle)); - const float flaps_width = 2.f * side_width * cos(m_groove_angle); + const float side_width = is_approx(m_groove_flaps_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_flaps_angle)); + const float flaps_width = 2.f * side_width * cos(m_groove_flaps_angle); GLModel model; - model.init_from(its_make_prism(float(cp_half_width), cp_length, cp_height)); + + // upper cut_plane + + model.init_from(its_make_upper_groove_plane()); model.set_color(cp_clr); - // upper halfs of cut_plane - - Transform3d view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * Vec3d(-h_shift, 0, groove_half_depth)) * cp_matrix; - shader->set_uniform("view_model_matrix", view_model_matrix_); - model.render(); - - view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * Vec3d(h_shift, 0, groove_half_depth)) * cp_matrix; + Transform3d view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cp_matrix; shader->set_uniform("view_model_matrix", view_model_matrix_); model.render(); // lower part of cut_plane model.reset(); - model.init_from(its_make_prism(m_groove_width + flaps_width, cp_length, cp_height)); + model.init_from(its_make_lower_groove_plane(flaps_width)); cp_clr.a(cp_clr.a() + 0.1f); model.set_color(cp_clr); @@ -691,16 +898,9 @@ void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shade // side parts of cut_plane model.reset(); + model.init_from(its_make_sides_groove_plane(flaps_width)); - model.init_from(its_make_prism(float(side_width), cp_length, cp_height)); - - const double h_side_shift = groove_half_width + 0.25 * double(flaps_width); - - view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); - shader->set_uniform("view_model_matrix", view_model_matrix_); - model.render(); - - view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cp_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); + view_model_matrix_ = camera.get_view_matrix() * cp_matrix; shader->set_uniform("view_model_matrix", view_model_matrix_); model.render(); } @@ -1495,8 +1695,8 @@ void GLGizmoCut3D::update_bb() // input params for cut with tongue and groove m_groove_depth = m_groove_depth_init = 0.5f * float(get_grabber_mean_size(m_bounding_box)); m_groove_width = m_groove_width_init = 4.0f * m_groove_depth; - m_groove_angle = m_groove_angle_init = float(PI) / 3.f;// 0.25f * float(PI); - + m_groove_flaps_angle = m_groove_flaps_angle_init = float(PI) / 3.f;// 0.25f * float(PI); + m_groove_angle = m_groove_angle_init = 0.f; m_plane.reset(); m_cone.reset(); m_sphere.reset(); @@ -2145,6 +2345,9 @@ void GLGizmoCut3D::flip_cut_plane() update_clipper(); m_part_selection.turn_over_selection(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); } void GLGizmoCut3D::reset_cut_by_contours() @@ -2225,28 +2428,11 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) ImGuiWrapper::text(" "); } -void GLGizmoCut3D::render_groove_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance, bool is_angle /*=false*/) +void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) { bool is_changed{false}; - if (is_angle) { - ImGuiWrapper::text(label); - - ImGui::SameLine(m_label_width); - ImGui::PushItemWidth(m_control_width * 0.7f); - - float val = rad2deg(in_val); - const float old_val = val; - - const std::string format = "%.0f " + _u8L("°"); - m_imgui->slider_float(("##groove_" + label).c_str(), &val, 30.f, 120.f, format.c_str(), 1.f, true, from_u8(label)); - - if (!is_approx(old_val, val)) { - in_val = deg2rad(val); - is_changed = true; - } - } - else if (render_slider_double_input(label, in_val, in_tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) + if (render_slider_double_input(label, in_val, in_tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) is_changed = true; ImGui::SameLine(); @@ -2266,6 +2452,43 @@ void GLGizmoCut3D::render_groove_input(const std::string& label, float& in_val, } } +void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val) +{ + bool is_changed{ false }; + + ImGuiWrapper::text(label); + + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.7f); + + float val = rad2deg(in_val); + const float old_val = val; + + const std::string format = "%.0f " + _u8L("°"); + m_imgui->slider_float(("##groove_" + label).c_str(), &val, min_val, max_val, format.c_str(), 1.f, true, from_u8(label)); + + if (!is_approx(old_val, val)) { + in_val = deg2rad(val); + is_changed = true; + } + + ImGui::SameLine(); + + m_imgui->disabled_begin(is_approx(in_val, init_val)); + const std::string act_name = _u8L("Reset"); + if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { + in_val = init_val; + is_changed = true; + } + m_imgui->disabled_end(); + + if (is_changed) { + reset_cut_by_contours(); + // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + } +} + + void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) { // if (m_mode == size_t(CutMode::cutPlanar)) { @@ -2324,9 +2547,10 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) else if (mode == CutMode::cutTongueAndGroove) { ImGui::Separator(); ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Groove") + ": "); - render_groove_input(_u8L("Depth"), m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance); - render_groove_input(_u8L("Width"), m_groove_width, m_groove_width_init, m_groove_width_tolerance); - render_groove_input(_u8L("Angle"), m_groove_angle, m_groove_angle_init, m_groove_width_tolerance, true); + render_groove_float_input(_u8L("Depth"), m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance); + render_groove_float_input(_u8L("Width"), m_groove_width, m_groove_width_init, m_groove_width_tolerance); + render_groove_angle_input(_u8L("Flaps Angle"), m_groove_flaps_angle, m_groove_flaps_angle_init, 30.f, 120.f); + render_groove_angle_input(_u8L("Groove Angle"), m_groove_angle, m_groove_angle_init, 0.f, 15.f); m_imgui->checkbox(_L("Optimize rendering"), m_optimaze_groove_rendering); } @@ -2764,7 +2988,7 @@ bool GLGizmoCut3D::can_perform_cut() const return false; if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - const float flaps_width = -2.f * m_groove_depth / tan(m_groove_angle); + const float flaps_width = -2.f * m_groove_depth / tan(m_groove_flaps_angle); return flaps_width < m_groove_width && !m_invalid_groove; } @@ -3042,11 +3266,11 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool // cut middle part with 2 angles and add parts to related upper/lower objects - const double h_side_shift = 0.5 * double(m_groove_width + m_groove_depth / tan(m_groove_angle)); + const double h_side_shift = 0.5 * double(m_groove_width + m_groove_depth / tan(m_groove_flaps_angle)); // cut by angle1 plane { - const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); + const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); if (!cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) return cut_object_ptrs; @@ -3055,7 +3279,7 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool // cut by angle2 plane { - const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); + const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); if (!cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) return cut_object_ptrs; @@ -3072,11 +3296,11 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool const double h_side_shift_tolerance = h_side_shift - 0.5 * double(m_groove_width_tolerance); - const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(-m_groove_angle * Vec3d::UnitY()); + const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); if (!cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) return cut_object_ptrs; - const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(m_groove_angle * Vec3d::UnitY()); + const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); if (!cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs)) return cut_object_ptrs; } @@ -3376,6 +3600,9 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse m_angle_arc.reset(); discard_cut_line_processing(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); } else if (action == SLAGizmoEventType::Moving) this->set_dirty(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index f4140c9c26..ff52ddc1ba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -119,9 +119,11 @@ class GLGizmoCut3D : public GLGizmoBase // Input params for cut with tongue and groove float m_groove_depth; float m_groove_width; + float m_groove_flaps_angle; float m_groove_angle; float m_groove_depth_init; float m_groove_width_init; + float m_groove_flaps_angle_init; float m_groove_angle_init; float m_groove_depth_tolerance{ 0.1f }; float m_groove_width_tolerance{ 0.1f }; @@ -296,7 +298,8 @@ protected: void add_horizontal_scaled_interval(float interval); void add_horizontal_shift(float shift); void render_color_marker(float size, const ImU32& color); - void render_groove_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance, bool is_angle = false); + void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance); + void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val); void render_cut_plane_input_window(CutConnectors &connectors); void init_input_window_data(CutConnectors &connectors); void render_input_window_warning() const; @@ -369,6 +372,10 @@ private: void toggle_model_objects_visibility(); + indexed_triangle_set its_make_upper_groove_plane(); + indexed_triangle_set its_make_lower_groove_plane(float flaps_width); + indexed_triangle_set its_make_sides_groove_plane(float flaps_width); + indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(ModelObject* mo, const std::string& connector_name); }; From e8bdd9e78d0879dfb1b0ef328bb78b136652fd00 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 14 Jul 2023 12:51:39 +0200 Subject: [PATCH 015/120] Code refactoring: Rivet is changed to Snap --- resources/icons/{rivet.svg => snap.svg} | 6 +++--- src/imgui/imconfig.h | 2 +- src/libslic3r/Model.cpp | 4 ++-- src/libslic3r/Model.hpp | 3 +-- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 22 +++++++++++----------- src/slic3r/GUI/ImGuiWrapper.cpp | 2 +- 6 files changed, 19 insertions(+), 20 deletions(-) rename resources/icons/{rivet.svg => snap.svg} (77%) diff --git a/resources/icons/rivet.svg b/resources/icons/snap.svg similarity index 77% rename from resources/icons/rivet.svg rename to resources/icons/snap.svg index db4760cb65..0242c8e3bc 100644 --- a/resources/icons/rivet.svg +++ b/resources/icons/snap.svg @@ -3,11 +3,11 @@ - - + - + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index a152252bac..99d933ea87 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -152,7 +152,7 @@ namespace ImGui // const wchar_t MmuSegmentationMarker = 0x1F; const wchar_t PlugMarker = 0x1C; const wchar_t DowelMarker = 0x1D; - const wchar_t RivetMarker = 0x1E; + const wchar_t SnapMarker = 0x1E; // Do not forget use following letters only in wstring const wchar_t DocumentationButton = 0x2600; const wchar_t DocumentationHoverButton = 0x2601; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index dd0553100e..92bb15df85 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1356,7 +1356,7 @@ void ModelVolume::apply_tolerance() double z_offset = 0.5 * static_cast(cut_info.height_tolerance); if (cut_info.connector_type == CutConnectorType::Plug || - cut_info.connector_type == CutConnectorType::Rivet) + cut_info.connector_type == CutConnectorType::Snap) z_offset -= 0.05; // add small Z offset to better preview set_offset(get_offset() + rot_norm * z_offset); @@ -1394,7 +1394,7 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& if (volume->cut_info.connector_type != CutConnectorType::Dowel) { if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { ModelVolume* vol = nullptr; - if (volume->cut_info.connector_type == CutConnectorType::Rivet) { + if (volume->cut_info.connector_type == CutConnectorType::Snap) { TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); vol = upper->add_volume(std::move(mesh)); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7f9b354408..a14bf37705 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -224,7 +224,7 @@ private: enum class CutConnectorType : int { Plug , Dowel - , Rivet + , Snap , Undef }; @@ -240,7 +240,6 @@ enum class CutConnectorShape : int { , Square , Hexagon , Circle - , Rivet , Undef //,D-shape }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 76964983a6..2f05270b1f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -186,7 +186,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_style (int(CutConnectorStyle::Prism)) , m_connector_shape_id (int(CutConnectorShape::Circle)) { - m_connector_type = CutConnectorType::Rivet; + m_connector_type = CutConnectorType::Snap; m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") @@ -197,7 +197,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, std::map connetor_types = { {ImGui::PlugMarker , _u8L("Plug") }, {ImGui::DowelMarker, _u8L("Dowel") }, - {ImGui::RivetMarker, _u8L("Rivet") }, + {ImGui::SnapMarker, _u8L("Snap") }, }; for (auto connector : connetor_types) { std::string type_label = " " + connector.second + " "; @@ -2191,7 +2191,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) m_imgui->text(m_labels_map["Type"]); bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); - type_changed |= render_connect_type_radio_button(CutConnectorType::Rivet); + type_changed |= render_connect_type_radio_button(CutConnectorType::Snap); if (type_changed) apply_selected_connectors([this, &connectors] (size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); }); @@ -2204,8 +2204,8 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); m_imgui->disabled_end(); - m_imgui->disabled_begin(m_connector_type == CutConnectorType::Rivet); - if (type_changed && m_connector_type == CutConnectorType::Rivet) { + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Snap); + if (type_changed && m_connector_type == CutConnectorType::Snap) { m_connector_shape_id = int(CutConnectorShape::Circle); apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); } @@ -2213,7 +2213,7 @@ 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); }); m_imgui->disabled_end(); - const float depth_min_value = m_connector_type == CutConnectorType::Rivet ? m_connector_size : -0.1f; + const float depth_min_value = m_connector_type == CutConnectorType::Snap ? m_connector_size : -0.1f; if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_depth_ratio > 0) @@ -2230,7 +2230,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) connectors[idx].radius_tolerance = 0.5f * m_connector_size_tolerance; }); - if (m_connector_type == CutConnectorType::Rivet) { + if (m_connector_type == CutConnectorType::Snap) { const std::string format = "%.0f %%"; @@ -3508,12 +3508,12 @@ void GLGizmoCut3D::reset_connectors() void GLGizmoCut3D::init_connector_shapes() { - for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug, CutConnectorType::Rivet}) + for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug, CutConnectorType::Snap}) for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prism}) { if (type == CutConnectorType::Dowel && style == CutConnectorStyle::Frustum) continue; for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { - if (type == CutConnectorType::Rivet && shape != CutConnectorShape::Circle) + if (type == CutConnectorType::Snap && shape != CutConnectorShape::Circle) continue; const CutConnectorAttributes attribs = { type, style, shape }; indexed_triangle_set its = get_connector_mesh(attribs); @@ -3527,7 +3527,7 @@ void GLGizmoCut3D::update_connector_shape() { CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; - if (m_connector_type == CutConnectorType::Rivet) { + if (m_connector_type == CutConnectorType::Snap) { indexed_triangle_set its = get_connector_mesh(attribs); m_shapes[attribs].reset(); m_shapes[attribs].model.init_from(its); @@ -3822,7 +3822,7 @@ indexed_triangle_set GLGizmoCut3D::get_connector_mesh(CutConnectorAttributes con break; } - if (connector_attributes.type == CutConnectorType::Rivet) + if (connector_attributes.type == CutConnectorType::Snap) connector_mesh = its_make_snap(1.0, 1.0, m_snap_space_proportion, m_snap_bulge_proportion); else if (connector_attributes.style == CutConnectorStyle::Prism) connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index ec1d039888..4673b338f2 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -67,7 +67,7 @@ static const std::map font_icons = { {ImGui::InfoMarkerSmall , "notification_info" }, {ImGui::PlugMarker , "plug" }, {ImGui::DowelMarker , "dowel" }, - {ImGui::RivetMarker , "rivet" }, + {ImGui::SnapMarker , "snap" }, }; static const std::map font_icons_large = { From 9067f601d84f1e23544c4031facdf731ef8e4991 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 14 Jul 2023 16:45:48 +0200 Subject: [PATCH 016/120] WIP: Cut with tongue and groove * Suppress to apply connectors to the model, when cut with TAG * Add grabbers to move cutPlane during Y axes, when groove angle is non-zero --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 72 +++++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 2f05270b1f..91fdcf3b4b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1116,6 +1116,8 @@ void GLGizmoCut3D::render_cut_plane_grabbers() } } + const double grabber_x_shift = 0.2 * m_grabber_connection_len; + // render CutPlaneXMove grabber if (no_xy_grabber_hovered || m_hover_id == CutPlaneXMove) @@ -1123,17 +1125,32 @@ void GLGizmoCut3D::render_cut_plane_grabbers() size = 0.5 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); color = m_hover_id == CutPlaneXMove ? ColorRGBA::RED() : m_plane.model.get_color(); - const double grabber_shift = 0.2 * m_grabber_connection_len; - - render_model(m_sphere.model, color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitX()) * scale_transform(size)); + render_model(m_sphere.model, color, view_matrix * translation_transform(grabber_x_shift * Vec3d::UnitX()) * scale_transform(size)); const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); - Vec3d offset = Vec3d(1.25 * size + grabber_shift, 0.0, 0.0); + Vec3d offset = Vec3d(1.25 * size + grabber_x_shift, 0.0, 0.0); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); - offset = Vec3d(-1.25 * size + grabber_shift, 0.0, 0.0); + offset = Vec3d(-1.25 * size + grabber_x_shift, 0.0, 0.0); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); } + + // render CutPlaneYMove grabber + + if (m_groove_angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove)) + { + size = 0.5 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = m_hover_id == CutPlaneYMove ? ColorRGBA::GREEN() : m_plane.model.get_color(); + + render_model(m_sphere.model, color, view_matrix * translation_transform(grabber_x_shift * Vec3d::UnitX()) * scale_transform(size)); + + const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + + Vec3d offset = Vec3d( grabber_x_shift, 1.25 * size, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + offset = Vec3d(grabber_x_shift, -1.25 * size, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + } } } @@ -1267,9 +1284,11 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_sphere.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cone.mesh_raycaster, Transform3d::Identity())); } } @@ -1368,23 +1387,39 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - double grabber_shift = -1.75 * m_grabber_connection_len; + double grabber_x_shift = -1.75 * m_grabber_connection_len; - m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_x_shift * Vec3d::UnitY()) * scale_transform(size)); - offset = Vec3d(1.25 * size, grabber_shift, 0.0); + offset = Vec3d(1.25 * size, grabber_x_shift, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - offset = Vec3d(-1.25 * size, grabber_shift, 0.0); + offset = Vec3d(-1.25 * size, grabber_x_shift, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - grabber_shift = 0.2 * m_grabber_connection_len; + // set grabbers to un-hovered size - m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_shift * Vec3d::UnitX()) * scale_transform(size)); + const double def_size = size * 0.5; + scale = Vec3d(0.75 * def_size, 0.75 * def_size, 1.8 * def_size); - offset = Vec3d(1.25 * size + grabber_shift, 0.0, 0.0); + const double offset_shift = def_size * 0.25; + grabber_x_shift = 0.2 * m_grabber_connection_len; + + offset = Vec3d(offset_shift + grabber_x_shift, 0.0, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - offset = Vec3d(-1.25 * size + grabber_shift, 0.0, 0.0); + offset = Vec3d(-offset_shift + grabber_x_shift, 0.0, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + + if (m_groove_angle > 0.0f) { + offset = Vec3d(grabber_x_shift, offset_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(scale)); + offset = Vec3d(grabber_x_shift, -offset_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitX()) * scale_transform(scale)); + } + else { + // discard transformation for CutPlaneYMove grabbers + m_raycasters[id++]->set_transform(Transform3d::Identity()); + m_raycasters[id++]->set_transform(Transform3d::Identity()); + } } } } @@ -1454,11 +1489,12 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3& void GLGizmoCut3D::dragging_grabber_move(const GLGizmoBase::UpdateData &data) { const Vec3d grabber_init_pos = m_hover_id == CutPlaneXMove ? 0.2 * m_grabber_connection_len * Vec3d::UnitX(): + m_hover_id == CutPlaneYMove ? 0.0 * Vec3d::UnitY() : (m_hover_id == CutPlane ? 0. : m_grabber_connection_len) * Vec3d::UnitZ(); const Vec3d starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * grabber_init_pos; double projection = 0.0; - Vec3d starting_vec = m_rotation_m * (m_hover_id == CutPlaneXMove ? Vec3d::UnitX() : Vec3d::UnitZ()); + Vec3d starting_vec = m_rotation_m * (m_hover_id == CutPlaneXMove ? Vec3d::UnitX() : m_hover_id == CutPlaneYMove ? Vec3d::UnitY() : Vec3d::UnitZ()); if (starting_vec.norm() != 0.0) { const 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 @@ -1553,7 +1589,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) { if (m_hover_id < 0) return; - if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove) + if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove || m_hover_id == CutPlaneYMove) dragging_grabber_move(data); else if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) dragging_grabber_rotation(data); @@ -1583,7 +1619,7 @@ void GLGizmoCut3D::on_stop_dragging() Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); m_start_dragging_m = m_rotation_m; } - else if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove) { + else if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove|| m_hover_id == CutPlaneYMove) { if (m_was_cut_plane_dragged) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_plane_center = m_plane_center; @@ -3006,6 +3042,8 @@ bool GLGizmoCut3D::has_valid_contour() const void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return; if (m_connector_mode == CutConnectorMode::Manual) { clear_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index ff52ddc1ba..30663a99c7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -32,6 +32,7 @@ class GLGizmoCut3D : public GLGizmoBase CutPlane, CutPlaneZRotation, CutPlaneXMove, + CutPlaneYMove, Count, }; From 34af44b4bf649b1ddc17c82a0082221fae704561 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 14 Jul 2023 17:32:02 +0200 Subject: [PATCH 017/120] WIP: Cut with TAG Added experimental rendering for the cutPlane mesh --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 306 ++++++++++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 6 + 2 files changed, 308 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 91fdcf3b4b..6b2968bb0e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -187,6 +187,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_shape_id (int(CutConnectorShape::Circle)) { m_connector_type = CutConnectorType::Snap; + m_mode = size_t(CutMode::cutTongueAndGroove); m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") @@ -487,6 +488,10 @@ bool GLGizmoCut3D::render_cut_mode_combo() m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); } + if (m_use_TAG_mesh) { + m_plane.reset(); + on_unregister_raycasters_for_picking(); + } reset_cut_by_contours(); } @@ -861,6 +866,263 @@ indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width }; } + + +indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() +{ + // values for calculation + + const float side_width = is_approx(m_groove_flaps_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_flaps_angle)); + const float flaps_width = 2.f * side_width * cos(m_groove_flaps_angle); + + const float groove_half_width_upper = 0.5f * (m_groove_width); + const float groove_half_width_lower = 0.5f * (m_groove_width + flaps_width); + + const float cut_plane_radius = 1.5f * float(m_radius); + const float cut_plane_length = 1.5f * cut_plane_radius; + + const float groove_half_depth = 0.5f * m_groove_depth; + + const float x = 0.5f * cut_plane_radius; + const float y = 0.5f * cut_plane_length; + float z_upper = groove_half_depth; + float z_lower = -groove_half_depth; + + const float proj = y * tan(m_groove_angle); + + float ext_upper_x = groove_half_width_upper + proj; // upper_x extension + float ext_lower_x = groove_half_width_lower + proj; // lower_x extension + + float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing + float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing + + const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes + + + // Different cases of groove plane: + + // groove is open + + if (groove_half_width_upper > proj && groove_half_width_lower > proj) { + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) { + return std::vector({ + // upper left part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {-nar_upper_x, y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}, + // upper right part vertices + {ext_upper_x, -y, z_upper}, {nar_upper_x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper} + }); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + if (m_use_TAG_mesh_full) { + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle); + + nar_upper_x += under_x_shift; + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {5,4,7}, {5,7,6}, // lower part + {3,4,5}, {3,5,2}, // left side + {9,6,8}, {8,6,7}, // right side + {1,0,2}, {2,0,3}, // upper left part + {9,8,10}, {10,8,11}, // upper right part + // under view + {20,21,22}, {20,22,23}, // upper right part + {12,13,14}, {12,14,15}, // upper left part + {18,21,20}, {18,20,19}, // right side + {16,15,14}, {16,14,17}, // left side + {16,17,18}, {16,18,19}, // lower part + // left edge + {1,13,0}, {0,13,12}, + // front edge + {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, + // right edge + {11,23,10}, {10,23,22}, + // back edge + {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} + }; + } + else { + std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {5,4,7}, {5,7,6}, // lower part + {3,4,2}, {2,4,5}, // left side + {9,6,8}, {8,6,7}, // right side + {1,0,2}, {2,0,3}, // upper left part + {9,8,10}, {10,8,11}, // upper right part + // under view + {16,15,14}, {16,14,17}, // left side + {18,21,20}, {18,20,19}, // right side + {16,17,18}, {16,18,19} // lower part + }; + } + return mesh; + } + + float cross_pt_upper_y = groove_half_width_upper / tan(m_groove_angle); + + // groove is closed + + if (groove_half_width_upper < proj && groove_half_width_lower < proj) { + float cross_pt_lower_y = groove_half_width_lower / tan(m_groove_angle); + + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) { + return std::vector({ + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {ext_lower_x, -y, z_lower} + }); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + if (m_use_TAG_mesh_full) { + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle); + + cross_pt_upper_y += cut_plane_thiknes; + cross_pt_lower_y += cut_plane_thiknes; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {8,7,9}, // lower part + {5,8,6}, {6,8,7}, // left side + {4,9,8}, {4,8,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part + {18,15,14}, {14,18,19}, // right side + {17,16,15}, {17,15,18}, // left side + {17,18,19}, // lower part + // left edge + {1,11,0}, {0,11,10}, + // front edge + {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, + // right edge + {3,13,12}, {3,12,2}, + // back edge + {2,12,11}, {2,11,1} + }; + } + else { + std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {8,7,9}, // lower part + {5,8,6}, {6,8,7}, // left side + {4,9,8}, {4,8,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {18,15,14}, {14,18,19}, // right side + {17,16,15}, {17,15,18}, // left side + {17,18,19}, // lower part + }; + } + return mesh; + + } + + // groove is closed from the roof + + indexed_triangle_set mesh; + mesh.vertices = { + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} + }; + + mesh.vertices.reserve(2 * mesh.vertices.size() + 1); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle); + + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = { + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {under_x_shift, cross_pt_upper_y, z_upper}, {-under_x_shift, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} + }; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + if (m_use_TAG_mesh_full) { + mesh.indices = { + // above view + {8,7,10}, {8,10,9}, // lower part + {5,8,7}, {5,7,6}, // left side + {4,10,9}, {4,9,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part + {21,16,15}, {21,15,22}, // right side + {19,18,17}, {19,17,20}, // left side + {19,20,21}, {19,21,22}, // lower part + // left edge + {1,12,11}, {1,11,0}, + // front edge + {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, + // right edge + {3,14,13}, {3,14,2}, + // back edge + {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} + }; + } + else { + mesh.indices = { + // above view + {8,7,10}, {8,10,9}, // lower part + {5,8,7}, {5,7,6}, // left side + {4,10,9}, {4,9,5}, // right side + {1,0,6}, {1,6,5}, {1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {21,16,15}, {21,15,22}, // right side + {19,18,17}, {19,17,20}, // left side + {19,20,21}, {19,21,22}, // lower part + }; + } + + return mesh; +} + void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shader) { const Camera & camera = wxGetApp().plater()->get_camera(); @@ -925,6 +1187,17 @@ void GLGizmoCut3D::render_cut_plane() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + if (m_use_TAG_mesh) { + ColorRGBA cp_clr = can_perform_cut() && has_valid_contour() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR; + if (m_mode == size_t(CutMode::cutTongueAndGroove)) + cp_clr.a(cp_clr.a() - 0.1f); + m_plane.model.set_color(cp_clr); + + 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); + m_plane.model.render(); + } + else { m_plane.model.set_color(can_perform_cut() && has_valid_contour() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR); if (m_mode == size_t(CutMode::cutPlanar)) { @@ -934,7 +1207,7 @@ void GLGizmoCut3D::render_cut_plane() } else if (m_mode == size_t(CutMode::cutTongueAndGroove)) render_cut_plate_for_tongue_and_groove(shader); - + } glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); @@ -1731,7 +2004,7 @@ void GLGizmoCut3D::update_bb() // input params for cut with tongue and groove m_groove_depth = m_groove_depth_init = 0.5f * float(get_grabber_mean_size(m_bounding_box)); m_groove_width = m_groove_width_init = 4.0f * m_groove_depth; - m_groove_flaps_angle = m_groove_flaps_angle_init = float(PI) / 3.f;// 0.25f * float(PI); + m_groove_flaps_angle = m_groove_flaps_angle_init = float(PI) / 3.f; m_groove_angle = m_groove_angle_init = 0.f; m_plane.reset(); m_cone.reset(); @@ -1768,7 +2041,13 @@ void GLGizmoCut3D::init_picking_models() if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box); - indexed_triangle_set its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); + indexed_triangle_set its; + if (m_use_TAG_mesh) + its = m_mode == size_t(CutMode::cutTongueAndGroove) ? its_make_groove_plane() : + its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); + else + its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); + m_plane.model.init_from(its); m_plane.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } @@ -2097,12 +2376,23 @@ void GLGizmoCut3D::on_render() void GLGizmoCut3D::render_debug_input_window(float x) { - return; m_imgui->begin(wxString("DEBUG")); ImVec2 pos = ImGui::GetWindowPos(); pos.x = x; ImGui::SetWindowPos(pos, ImGuiCond_Always); + + bool is_changed = m_imgui->checkbox(("Render Cut plane as a one mesh"), m_use_TAG_mesh); + is_changed |= m_imgui->checkbox(("Render Cut plane as a full mesh"), m_use_TAG_mesh_full); + + if (is_changed) + { + m_plane.reset(); + on_unregister_raycasters_for_picking(); + } + + m_imgui->end(); + return; /* static bool hide_clipped = false; static bool fill_cut = false; @@ -2483,6 +2773,10 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in m_imgui->disabled_end(); if (is_changed) { + if (m_use_TAG_mesh) { + m_plane.reset(); + on_unregister_raycasters_for_picking(); + } reset_cut_by_contours(); // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); } @@ -2519,6 +2813,10 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in m_imgui->disabled_end(); if (is_changed) { + if (m_use_TAG_mesh) { + m_plane.reset(); + on_unregister_raycasters_for_picking(); + } reset_cut_by_contours(); // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 30663a99c7..d611dca63c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -235,6 +235,11 @@ class GLGizmoCut3D : public GLGizmoBase std::map m_labels_map; + // Debug values + bool m_use_TAG_mesh {true}; + bool m_use_TAG_mesh_full {true}; + // + public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -376,6 +381,7 @@ private: indexed_triangle_set its_make_upper_groove_plane(); indexed_triangle_set its_make_lower_groove_plane(float flaps_width); indexed_triangle_set its_make_sides_groove_plane(float flaps_width); + indexed_triangle_set its_make_groove_plane(); indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); void apply_cut_connectors(ModelObject* mo, const std::string& connector_name); From 9c254b6782f9207a78913693f467b7d45d674bc6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 Jul 2023 17:23:01 +0200 Subject: [PATCH 018/120] CutGizmo: Fixed a moving of the cut plane on dragging. There no unexpected jumping any more --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 37 +++++++++++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 ++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 6b2968bb0e..0264531d5a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -290,6 +290,14 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) else if (mouse_event.Moving()) return false; + if (m_hover_id >= CutPlane && mouse_event.LeftDown() && !m_connectors_editing) { + // before processing of a use_grabbers(), detect start move position as a projection of mouse position to the cut plane + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false)) + m_cut_plane_start_move_pos = pos_world; + } + if (use_grabbers(mouse_event)) { if (m_hover_id >= m_connectors_group_id) { if (mouse_event.LeftDown() && !mouse_event.CmdDown() && !mouse_event.AltDown()) @@ -304,7 +312,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) // disable / enable current contour Vec3d pos; Vec3d pos_world; - m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world, false); + m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world); if (m_was_contour_selected) { // Following would inform the clipper about the mouse click, so it can // toggle the respective contour as disabled. @@ -318,6 +326,14 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) flip_cut_plane(); } + if (m_hover_id >= CutPlane && mouse_event.Dragging() && !m_connectors_editing) { + // if we continue to dragging a cut plane, than update a start move position as a projection of mouse position to the cut plane after processing of a use_grabbers() + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false)) + m_cut_plane_start_move_pos = pos_world; + } + toggle_model_objects_visibility(); return true; } @@ -1761,17 +1777,19 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3& void GLGizmoCut3D::dragging_grabber_move(const GLGizmoBase::UpdateData &data) { - const Vec3d grabber_init_pos = m_hover_id == CutPlaneXMove ? 0.2 * m_grabber_connection_len * Vec3d::UnitX(): - m_hover_id == CutPlaneYMove ? 0.0 * Vec3d::UnitY() : - (m_hover_id == CutPlane ? 0. : m_grabber_connection_len) * Vec3d::UnitZ(); - const Vec3d starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * grabber_init_pos; - double projection = 0.0; + Vec3d starting_drag_position; + if (m_hover_id == Z) + starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * (m_grabber_connection_len * Vec3d::UnitZ()); + else + starting_drag_position = m_cut_plane_start_move_pos; + + double projection = 0.0; Vec3d starting_vec = m_rotation_m * (m_hover_id == CutPlaneXMove ? Vec3d::UnitX() : m_hover_id == CutPlaneYMove ? Vec3d::UnitY() : Vec3d::UnitZ()); if (starting_vec.norm() != 0.0) { const 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 + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing through the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebraic 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 = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir; @@ -3766,7 +3784,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, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour/* = true*/) +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_contours/* = true*/) { const float sla_shift = m_c->selection_info()->get_sla_shift(); @@ -3803,6 +3821,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po } }*/ + if (respect_contours) { // Do not react to clicks outside a contour (or inside a contour that is ignored) int cont_id = m_c->object_clipper()->is_projection_inside_cut(hit); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index d611dca63c..d461948735 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -58,6 +58,7 @@ class GLGizmoCut3D : public GLGizmoBase double m_radius{ 0.0 }; double m_grabber_radius{ 0.0 }; double m_grabber_connection_len{ 0.0 }; + Vec3d m_cut_plane_start_move_pos {Vec3d::Zero()}; double m_snap_coarse_in_radius{ 0.0 }; double m_snap_coarse_out_radius{ 0.0 }; @@ -244,7 +245,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, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true); + bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_contours = true); 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; } From c239c54dc691cbf482050d74c63996908a5f8adb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 1 Aug 2023 10:58:31 +0200 Subject: [PATCH 019/120] CutGizmo: Bug fixing and improvements: * Get correct labels width form all label * Set Plane mode as default for cut * Fixed tooltip for CutPlane in TaG mode * Fixed Crash when "Instances" item is selected with open CutGizmo * Fixed move limits for CutPlane * TaG mode: Suppressed perform a cut when CutPlane is out of object * Update warning info lines when switch between modes * Don't allow to switch to TaG mode, when cut has connectors --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 55 ++++++++++++++++++---------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 0264531d5a..9f060c778c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -186,9 +186,6 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_style (int(CutConnectorStyle::Prism)) , m_connector_shape_id (int(CutConnectorShape::Circle)) { - m_connector_type = CutConnectorType::Snap; - m_mode = size_t(CutMode::cutTongueAndGroove); - m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") }; @@ -229,6 +226,10 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, {"Shape" , _u8L("Shape")}, {"Depth" , _u8L("Depth")}, {"Size" , _u8L("Size")}, + {"Groove" , _u8L("Groove")}, + {"Width" , _u8L("Width")}, + {"Flaps Angle" , _u8L("Flaps Angle")}, + {"Groove Angle" , _u8L("Groove Angle")}, }; // update_connector_shape(); @@ -256,10 +257,14 @@ std::string GLGizmoCut3D::get_tooltip() const return tooltip; } - if (!m_dragging && m_hover_id == CutPlane) + if (!m_dragging && m_hover_id == CutPlane) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return _u8L("Click to flip the cut plane\n" + "Drag to move the cut plane"); return _u8L("Click to flip the cut plane\n" "Drag to move the cut plane\n" "Right-click a part to assign it to the other side"); + } if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation)) { std::string axis = m_hover_id == X ? "X" : m_hover_id == Y ? "Y" : "Z"; @@ -509,6 +514,8 @@ bool GLGizmoCut3D::render_cut_mode_combo() on_unregister_raycasters_for_picking(); } reset_cut_by_contours(); + update_clipper(); + check_and_update_connectors_state(); } return is_changed; @@ -1931,7 +1938,7 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=fa bool can_set_center_pos = false; { - double limit_val = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove_depth) : 0.5; + double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove_depth) : */0.5; if (tbb.max.z() > -limit_val && tbb.min.z() < limit_val) can_set_center_pos = true; else { @@ -2020,7 +2027,7 @@ void GLGizmoCut3D::update_bb() m_snap_fine_out_radius = m_grabber_connection_len * 1.15; // input params for cut with tongue and groove - m_groove_depth = m_groove_depth_init = 0.5f * float(get_grabber_mean_size(m_bounding_box)); + m_groove_depth = m_groove_depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box))); m_groove_width = m_groove_width_init = 4.0f * m_groove_depth; m_groove_flaps_angle = m_groove_flaps_angle_init = float(PI) / 3.f; m_groove_angle = m_groove_angle_init = 0.f; @@ -2703,8 +2710,10 @@ void GLGizmoCut3D::reset_cut_by_contours() return; process_contours(); } - else + else { + m_invalid_groove = false; toggle_model_objects_visibility(); + } } void GLGizmoCut3D::process_contours() @@ -2713,6 +2722,8 @@ void GLGizmoCut3D::process_contours() const ModelObjectPtrs& model_objects = selection.get_model()->objects; const int instance_idx = selection.get_instance_idx(); + if (instance_idx < 0) + return; const int object_idx = selection.get_object_idx(); wxBusyCursor wait; @@ -2721,6 +2732,8 @@ void GLGizmoCut3D::process_contours() ModelObjectPtrs cut_objects = perform_cut_with_groove(model_objects[object_idx], true); if (!cut_objects.empty() && !m_invalid_groove) m_part_selection = PartSelection(cut_objects.front(), instance_idx); + else + m_invalid_groove = true; } else { reset_cut_by_contours(); @@ -2853,9 +2866,12 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) get_wraped_wxString(_L("Hold SHIFT key to draw a cut line"), 40)); ImGui::Separator(); - // WIP : cut plane mode + const bool has_connectors = !connectors.empty(); + + m_imgui->disabled_begin(has_connectors); if (render_cut_mode_combo()) mode = CutMode(m_mode); + m_imgui->disabled_end(); render_build_size(); @@ -2864,7 +2880,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::SameLine(); render_move_center_input(Z); ImGui::SameLine(); - const bool has_connectors = !connectors.empty(); const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); m_imgui->disabled_begin(is_cut_plane_init); @@ -2898,13 +2913,12 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) } else if (mode == CutMode::cutTongueAndGroove) { ImGui::Separator(); - ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Groove") + ": "); - render_groove_float_input(_u8L("Depth"), m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance); - render_groove_float_input(_u8L("Width"), m_groove_width, m_groove_width_init, m_groove_width_tolerance); - render_groove_angle_input(_u8L("Flaps Angle"), m_groove_flaps_angle, m_groove_flaps_angle_init, 30.f, 120.f); - render_groove_angle_input(_u8L("Groove Angle"), m_groove_angle, m_groove_angle_init, 0.f, 15.f); - - m_imgui->checkbox(_L("Optimize rendering"), m_optimaze_groove_rendering); + ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": "); + render_groove_float_input(m_labels_map["Depth"], m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance); + render_groove_float_input(m_labels_map["Width"], m_groove_width, m_groove_width_init, m_groove_width_tolerance); + render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove_flaps_angle, m_groove_flaps_angle_init, 30.f, 120.f); + render_groove_angle_input(m_labels_map["Groove Angle"], m_groove_angle, m_groove_angle_init, 0.f, 15.f); +// m_imgui->checkbox(_L("Optimize rendering"), m_optimaze_groove_rendering); } ImGui::Separator(); @@ -2974,7 +2988,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) add_vertical_scaled_interval(0.75f); - m_imgui->disabled_begin(has_connectors || m_part_selection.valid()); + m_imgui->disabled_begin(has_connectors || m_part_selection.valid() || mode == CutMode::cutTongueAndGroove); ImGuiWrapper::text(_L("Cut into") + ":"); if (m_part_selection.valid()) @@ -3222,6 +3236,8 @@ void GLGizmoCut3D::check_and_update_connectors_state() { m_info_stats.invalidate(); m_invalid_connectors_idxs.clear(); + if (CutMode(m_mode) != CutMode::cutPlanar) + return; const ModelObject* mo = m_c->selection_info()->model_object(); auto inst_id = m_c->selection_info()->get_active_instance(); if (inst_id < 0) @@ -3262,7 +3278,8 @@ void GLGizmoCut3D::render_connectors() { ::glEnable(GL_DEPTH_TEST); - if (cut_line_processing() || + if (cut_line_processing() || + CutMode(m_mode) != CutMode::cutPlanar || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) return; @@ -3341,7 +3358,7 @@ bool GLGizmoCut3D::can_perform_cut() const if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { const float flaps_width = -2.f * m_groove_depth / tan(m_groove_flaps_angle); - return flaps_width < m_groove_width && !m_invalid_groove; + return flaps_width < m_groove_width && !m_invalid_groove && has_valid_contour(); } if (m_part_selection.valid()) From 0c686f1ff96c7157f39f891411432673277128da Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 1 Aug 2023 16:15:51 +0200 Subject: [PATCH 020/120] CutGizmo improvements: Reworked CutPlane grabber for TaG mode --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 73 +++++++++++++++------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9f060c778c..717f42d6e7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1334,7 +1334,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers() const bool no_xy_dragging = m_dragging && m_hover_id == CutPlane; - if (!no_xy_dragging && m_hover_id != CutPlaneZRotation) { + if (!no_xy_dragging && m_hover_id != CutPlaneZRotation && m_hover_id != CutPlaneXMove && m_hover_id != CutPlaneYMove) { render_grabber_connection(GRABBER_COLOR, view_matrix); // render sphere grabber @@ -1412,40 +1412,42 @@ void GLGizmoCut3D::render_cut_plane_grabbers() } } - const double grabber_x_shift = 0.2 * m_grabber_connection_len; + const double xy_connection_len = 0.75 * m_grabber_connection_len; // render CutPlaneXMove grabber if (no_xy_grabber_hovered || m_hover_id == CutPlaneXMove) { - size = 0.5 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); color = m_hover_id == CutPlaneXMove ? ColorRGBA::RED() : m_plane.model.get_color(); - render_model(m_sphere.model, color, view_matrix * translation_transform(grabber_x_shift * Vec3d::UnitX()) * scale_transform(size)); + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()), 0.75); - const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + Vec3d offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones(); + render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size)); - Vec3d offset = Vec3d(1.25 * size + grabber_x_shift, 0.0, 0.0); + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = (size + xy_connection_len) * Vec3d::UnitX(); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); - offset = Vec3d(-1.25 * size + grabber_x_shift, 0.0, 0.0); - render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); } // render CutPlaneYMove grabber if (m_groove_angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove)) { - size = 0.5 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); color = m_hover_id == CutPlaneYMove ? ColorRGBA::GREEN() : m_plane.model.get_color(); - render_model(m_sphere.model, color, view_matrix * translation_transform(grabber_x_shift * Vec3d::UnitX()) * scale_transform(size)); + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitX()), 0.75); - const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + Vec3d offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones(); + render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size)); - Vec3d offset = Vec3d( grabber_x_shift, 1.25 * size, 0.0); + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = (size + xy_connection_len) * Vec3d::UnitY(); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); - offset = Vec3d(grabber_x_shift, -1.25 * size, 0.0); - render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); } } } @@ -1580,10 +1582,10 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cube.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cube.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cone.mesh_raycaster, Transform3d::Identity())); } } @@ -1683,33 +1685,28 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - double grabber_x_shift = -1.75 * m_grabber_connection_len; + double grabber_y_shift = -1.75 * m_grabber_connection_len; - m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_x_shift * Vec3d::UnitY()) * scale_transform(size)); + m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_y_shift * Vec3d::UnitY()) * scale_transform(size)); - offset = Vec3d(1.25 * size, grabber_x_shift, 0.0); + offset = Vec3d(1.25 * size, grabber_y_shift, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - offset = Vec3d(-1.25 * size, grabber_x_shift, 0.0); + offset = Vec3d(-1.25 * size, grabber_y_shift, 0.0); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - // set grabbers to un-hovered size + const double xy_connection_len = 0.75 * m_grabber_connection_len; + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); - const double def_size = size * 0.5; - scale = Vec3d(0.75 * def_size, 0.75 * def_size, 1.8 * def_size); - - const double offset_shift = def_size * 0.25; - grabber_x_shift = 0.2 * m_grabber_connection_len; - - offset = Vec3d(offset_shift + grabber_x_shift, 0.0, 0.0); - m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - offset = Vec3d(-offset_shift + grabber_x_shift, 0.0, 0.0); - m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size)); + offset = (size + xy_connection_len) * Vec3d::UnitX(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); if (m_groove_angle > 0.0f) { - offset = Vec3d(grabber_x_shift, offset_shift, 0.0); - m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(scale)); - offset = Vec3d(grabber_x_shift, -offset_shift, 0.0); - m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitX()) * scale_transform(scale)); + offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size)); + offset = (size + xy_connection_len) * Vec3d::UnitY(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); } else { // discard transformation for CutPlaneYMove grabbers @@ -2034,6 +2031,7 @@ void GLGizmoCut3D::update_bb() m_plane.reset(); m_cone.reset(); m_sphere.reset(); + m_cube.reset(); m_grabber_connection.reset(); m_circle.reset(); m_scale.reset(); @@ -2063,6 +2061,11 @@ 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_cube.model.is_initialized()) { + indexed_triangle_set its = its_make_cube(1., 1., 1.); + m_cube.model.init_from(its); + m_cube.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index d461948735..db62cacdd1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -83,6 +83,7 @@ class GLGizmoCut3D : public GLGizmoBase PickingModel m_plane; PickingModel m_sphere; PickingModel m_cone; + PickingModel m_cube; std::map m_shapes; std::vector> m_raycasters; From 75eccfd6507e4cb446569e2d4a53bb64158c5521 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 1 Aug 2023 16:23:18 +0200 Subject: [PATCH 021/120] Added new type of SceneRaycaster (FallbackGizmo) to add possibility to split the Gizmo grabbers to two priority of "raycastering". meshes with Gizmo type has highest priority then FallbackGizmo. + CutGizmo: Use SceneRaycaster type for CutPlane grabber --- src/slic3r/GUI/GLCanvas3D.cpp | 21 +++++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 3 ++- src/slic3r/GUI/SceneRaycaster.cpp | 22 ++++++++++++++++++++++ src/slic3r/GUI/SceneRaycaster.hpp | 9 +++++++-- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index debdc2054d..687317c390 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2684,6 +2684,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (curr_gizmo != nullptr) curr_gizmo->unregister_raycasters_for_picking(); m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo); + if (curr_gizmo != nullptr && !m_selection.is_empty()) + curr_gizmo->register_raycasters_for_picking(); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo); if (curr_gizmo != nullptr && !m_selection.is_empty()) curr_gizmo->register_raycasters_for_picking(); @@ -5744,6 +5747,7 @@ void GLCanvas3D::_picking_pass() break; } case SceneRaycaster::EType::Gizmo: + case SceneRaycaster::EType::FallbackGizmo: { const Size& cnv_size = get_canvas_size(); const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() && @@ -5776,6 +5780,7 @@ void GLCanvas3D::_picking_pass() { case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; } case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; } + case SceneRaycaster::EType::FallbackGizmo: { object_type = "Gizmo2 element"; break; } case SceneRaycaster::EType::Volume: { if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower) @@ -5830,6 +5835,8 @@ void GLCanvas3D::_picking_pass() add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count()); add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.fallback_gizmos_count(), (int)m_scene_raycaster.active_fallback_gizmos_count()); + add_strings_row_to_table("Gizmo2 elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); ImGui::EndTable(); } @@ -5847,6 +5854,20 @@ void GLCanvas3D::_picking_pass() } } + std::vector>* gizmo2_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::FallbackGizmo); + if (gizmo2_raycasters != nullptr && !gizmo2_raycasters->empty()) { + ImGui::Separator(); + imgui.text("Gizmo2 raycasters IDs:"); + if (ImGui::BeginTable("Gizmo2Raycasters", 3)) { + for (size_t i = 0; i < gizmo2_raycasters->size(); ++i) { + add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT, + std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::FallbackGizmo, (*gizmo2_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text), + to_string(Geometry::Transformation((*gizmo2_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + } + ImGui::EndTable(); + } + } + imgui.end(); #endif // ENABLE_RAYCAST_PICKING_DEBUG } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 717f42d6e7..8c27a96f0c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1575,7 +1575,7 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() 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, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::FallbackGizmo, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity())); if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_sphere.mesh_raycaster, Transform3d::Identity())); @@ -1596,6 +1596,7 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() void GLGizmoCut3D::on_unregister_raycasters_for_picking() { m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::FallbackGizmo); m_raycasters.clear(); // 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); diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp index 64493d86b4..08c3407632 100644 --- a/src/slic3r/GUI/SceneRaycaster.cpp +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -40,6 +40,7 @@ std::shared_ptr SceneRaycaster::add_raycaster(EType type, in case EType::Bed: { return m_bed.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } case EType::Volume: { return m_volumes.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::FallbackGizmo: { return m_fallback_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } default: { assert(false); return nullptr; } }; } @@ -62,6 +63,7 @@ void SceneRaycaster::remove_raycasters(EType type) case EType::Bed: { m_bed.clear(); break; } case EType::Volume: { m_volumes.clear(); break; } case EType::Gizmo: { m_gizmos.clear(); break; } + case EType::FallbackGizmo: { m_fallback_gizmos.clear(); break; } default: { break; } }; } @@ -86,6 +88,12 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr item) return; } } + for (auto it = m_fallback_gizmos.begin(); it != m_fallback_gizmos.end(); ++it) { + if (*it == item) { + m_fallback_gizmos.erase(it); + return; + } + } } SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const @@ -174,6 +182,9 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came if (!m_gizmos.empty()) test_raycasters(EType::Gizmo, mouse_pos, camera, ret); + if (!m_fallback_gizmos.empty() && !ret.is_valid()) + test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret); + if (!m_gizmos_on_top || !ret.is_valid()) { if (camera.is_looking_downward() && !m_bed.empty()) test_raycasters(EType::Bed, mouse_pos, camera, ret); @@ -241,6 +252,14 @@ size_t SceneRaycaster::active_gizmos_count() const { } return count; } +size_t SceneRaycaster::active_fallback_gizmos_count() const { + size_t count = 0; + for (const auto& g : m_fallback_gizmos) { + if (g->is_active()) + ++count; + } + return count; +} #endif // ENABLE_RAYCAST_PICKING_DEBUG std::vector>* SceneRaycaster::get_raycasters(EType type) @@ -251,6 +270,7 @@ std::vector>* SceneRaycaster::get_raycasters case EType::Bed: { ret = &m_bed; break; } case EType::Volume: { ret = &m_volumes; break; } case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } default: { break; } } assert(ret != nullptr); @@ -265,6 +285,7 @@ const std::vector>* SceneRaycaster::get_rayc case EType::Bed: { ret = &m_bed; break; } case EType::Volume: { ret = &m_volumes; break; } case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } default: { break; } } assert(ret != nullptr); @@ -278,6 +299,7 @@ int SceneRaycaster::base_id(EType type) case EType::Bed: { return int(EIdBase::Bed); } case EType::Volume: { return int(EIdBase::Volume); } case EType::Gizmo: { return int(EIdBase::Gizmo); } + case EType::FallbackGizmo: { return int(EIdBase::FallbackGizmo); } default: { break; } }; diff --git a/src/slic3r/GUI/SceneRaycaster.hpp b/src/slic3r/GUI/SceneRaycaster.hpp index df44b1701c..8102e20de4 100644 --- a/src/slic3r/GUI/SceneRaycaster.hpp +++ b/src/slic3r/GUI/SceneRaycaster.hpp @@ -42,14 +42,16 @@ public: None, Bed, Volume, - Gizmo + Gizmo, + FallbackGizmo // Is used for gizmo grabbers which will be hit after all grabbers of Gizmo type }; enum class EIdBase { Bed = 0, Volume = 1000, - Gizmo = 1000000 + Gizmo = 1000000, + FallbackGizmo = 2000000 }; struct HitResult @@ -66,6 +68,7 @@ private: std::vector> m_bed; std::vector> m_volumes; std::vector> m_gizmos; + std::vector> m_fallback_gizmos; // When set to true, if checking gizmos returns a valid hit, // the search is not performed on other types @@ -99,9 +102,11 @@ public: size_t beds_count() const { return m_bed.size(); } size_t volumes_count() const { return m_volumes.size(); } size_t gizmos_count() const { return m_gizmos.size(); } + size_t fallback_gizmos_count() const { return m_fallback_gizmos.size(); } size_t active_beds_count() const; size_t active_volumes_count() const; size_t active_gizmos_count() const; + size_t active_fallback_gizmos_count() const; #endif // ENABLE_RAYCAST_PICKING_DEBUG static int decode_id(EType type, int id); From ba25da571a87c855061ed2a3085887fd1f5f22d5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 31 Jul 2023 16:04:41 +0200 Subject: [PATCH 022/120] MeshRaycaster: added a function to check for mesh-line intersections --- src/slic3r/GUI/MeshUtils.cpp | 9 ++++++--- src/slic3r/GUI/MeshUtils.hpp | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 60ea6c856d..88a4f29728 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -465,14 +465,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 +bool MeshRaycaster::intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const { - point = trafo.inverse() * point; + Transform3d trafo_inv = trafo.inverse(); + Vec3d to = trafo_inv * (point + direction); + point = trafo_inv * point; + direction = (to-point).normalized(); 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(); + return !hits.empty() || !neg_hits.empty(); } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 3645ecc026..a7dd3d7f75 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -187,7 +187,9 @@ public: const AABBMesh &get_aabb_mesh() const { return m_emesh; } - bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const; + // Given a point and direction in world coords, returns whether the respective line + // intersects the mesh if it is transformed into world by trafo. + bool intersects_line(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 From 4e74e4ecc06daa71869ddd1c48914ffb29e87761 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 Aug 2023 10:01:23 +0200 Subject: [PATCH 023/120] CutGizmo: Implemented validation of groove size/position --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 103 +++++++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 8 ++- 2 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8c27a96f0c..7effe33107 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -509,10 +509,8 @@ bool GLGizmoCut3D::render_cut_mode_combo() m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); } - if (m_use_TAG_mesh) { - m_plane.reset(); - on_unregister_raycasters_for_picking(); - } + if (m_use_TAG_mesh) + update_plane_model(); reset_cut_by_contours(); update_clipper(); check_and_update_connectors_state(); @@ -921,6 +919,23 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes + // Vertices of the groove used to detection if groove is valid + // They are written as: + // {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper, + // right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper } + { + m_groove_vertices.clear(); + m_groove_vertices.reserve(8); + + m_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f( ext_lower_x, -y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f( nar_lower_x, y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f( ext_upper_x, -y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f( nar_upper_x, y, z_upper).cast()); + } // Different cases of groove plane: @@ -1211,7 +1226,7 @@ void GLGizmoCut3D::render_cut_plane() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (m_use_TAG_mesh) { - ColorRGBA cp_clr = can_perform_cut() && has_valid_contour() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR; + ColorRGBA cp_clr = can_perform_cut() && has_valid_groove() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR; if (m_mode == size_t(CutMode::cutTongueAndGroove)) cp_clr.a(cp_clr.a() - 0.1f); m_plane.model.set_color(cp_clr); @@ -1718,6 +1733,14 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() } } +void GLGizmoCut3D::update_plane_model() +{ + m_plane.reset(); + on_unregister_raycasters_for_picking(); + + init_picking_models(); +} + void GLGizmoCut3D::on_set_hover_id() { } @@ -2415,10 +2438,7 @@ void GLGizmoCut3D::render_debug_input_window(float x) is_changed |= m_imgui->checkbox(("Render Cut plane as a full mesh"), m_use_TAG_mesh_full); if (is_changed) - { - m_plane.reset(); - on_unregister_raycasters_for_picking(); - } + update_plane_model(); m_imgui->end(); return; @@ -2710,14 +2730,12 @@ void GLGizmoCut3D::reset_cut_by_contours() m_part_selection = PartSelection(); if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - if (m_optimaze_groove_rendering && m_dragging) + if (m_optimaze_groove_rendering && m_dragging || !has_valid_groove()) return; process_contours(); } - else { - m_invalid_groove = false; + else toggle_model_objects_visibility(); - } } void GLGizmoCut3D::process_contours() @@ -2734,10 +2752,8 @@ void GLGizmoCut3D::process_contours() if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { ModelObjectPtrs cut_objects = perform_cut_with_groove(model_objects[object_idx], true); - if (!cut_objects.empty() && !m_invalid_groove) + if (!cut_objects.empty()) m_part_selection = PartSelection(cut_objects.front(), instance_idx); - else - m_invalid_groove = true; } else { reset_cut_by_contours(); @@ -2808,10 +2824,8 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in m_imgui->disabled_end(); if (is_changed) { - if (m_use_TAG_mesh) { - m_plane.reset(); - on_unregister_raycasters_for_picking(); - } + if (m_use_TAG_mesh) + update_plane_model(); reset_cut_by_contours(); // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); } @@ -2848,10 +2862,8 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in m_imgui->disabled_end(); if (is_changed) { - if (m_use_TAG_mesh) { - m_plane.reset(); - on_unregister_raycasters_for_picking(); - } + if (m_use_TAG_mesh) + update_plane_model(); reset_cut_by_contours(); // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); } @@ -3129,7 +3141,7 @@ void GLGizmoCut3D::render_input_window_warning() const m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Select at least one object to keep after cutting.")); if (!has_valid_contour()) m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane is placed out of object")); - if (m_invalid_groove) + else if (!has_valid_groove()) m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane with groove is invalid")); } @@ -3362,7 +3374,7 @@ bool GLGizmoCut3D::can_perform_cut() const if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { const float flaps_width = -2.f * m_groove_depth / tan(m_groove_flaps_angle); - return flaps_width < m_groove_width && !m_invalid_groove && has_valid_contour(); + return flaps_width < m_groove_width && has_valid_groove(); } if (m_part_selection.valid()) @@ -3371,6 +3383,39 @@ bool GLGizmoCut3D::can_perform_cut() const return true; } +bool GLGizmoCut3D::has_valid_groove() const +{ + if (CutMode(m_mode) != CutMode::cutTongueAndGroove) + return true; + + const Selection& selection = m_parent.get_selection(); + const auto&list = selection.get_volume_idxs(); + // is more volumes selected? + if (list.empty()) + return false; + + const Transform3d cp_matrix = translation_transform(m_plane_center) * m_rotation_m; + + for (size_t id = 0; id < m_groove_vertices.size(); id += 2) { + const Vec3d beg = cp_matrix * m_groove_vertices[id]; + const Vec3d end = cp_matrix * m_groove_vertices[id + 1]; + + bool intersection = false; + for (const unsigned int volume_idx : list) { + const GLVolume* glvol = selection.get_volume(volume_idx); + if (!glvol->is_modifier && + glvol->mesh_raycaster->intersects_line(beg, end - beg, glvol->world_matrix())) { + intersection = true; + break; + } + } + if (!intersection) + return false; + } + + return true; +} + bool GLGizmoCut3D::has_valid_contour() const { const auto clipper = m_c->object_clipper(); @@ -3569,7 +3614,8 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts/* = false*/) { - m_invalid_groove = false; + if (!has_valid_groove()) + return {}; const Selection& selection = m_parent.get_selection(); const int instance_idx = selection.get_instance_idx(); @@ -3684,9 +3730,6 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool invalid_added_volumes |= !add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); if (keep_as_parts) { - if (!m_invalid_groove) - m_invalid_groove = upper->volumes.empty() || lower->volumes.empty() || invalid_added_volumes; - // add volumes from lower object to the upper, but mark them as a lower const auto& volumes = lower->volumes; for (const ModelVolume* volume : volumes) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index db62cacdd1..c8bf50c29c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -109,8 +109,6 @@ class GLGizmoCut3D : public GLGizmoBase } } m_info_stats; - bool m_invalid_groove{ false }; - bool m_keep_upper{ true }; bool m_keep_lower{ true }; bool m_keep_as_parts{ false }; @@ -163,6 +161,9 @@ class GLGizmoCut3D : public GLGizmoBase bool m_was_cut_plane_dragged { false }; bool m_was_contour_selected { false }; + // Vertices of the groove used to detection if groove is valid + std::vector m_groove_vertices; + class PartSelection { public: PartSelection() = default; @@ -323,6 +324,8 @@ protected: void set_volumes_picking_state(bool state); void update_raycasters_for_picking_transform(); + void update_plane_model(); + void on_render_input_window(float x, float y, float bottom_limit) override; bool wants_enter_leave_snapshots() const override { return true; } @@ -348,6 +351,7 @@ private: void render_connectors(); bool can_perform_cut() const; + bool has_valid_groove() const; bool has_valid_contour() const; void apply_connectors_in_model(ModelObject* mo, int &dowels_count); bool cut_line_processing() const; From 492e356a21734b3503caae115fbb280da5fbaa22 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 Aug 2023 16:09:28 +0200 Subject: [PATCH 024/120] CutGizmo: Fixed and improved Undo/Redo. --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 90 ++++++++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + src/slic3r/GUI/ImGuiWrapper.cpp | 2 + src/slic3r/GUI/ImGuiWrapper.hpp | 6 ++ 4 files changed, 80 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 7effe33107..072c000320 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -494,6 +494,22 @@ void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/) update_clipper(); } +void GLGizmoCut3D::switch_to_mode(size_t new_mode) +{ + m_mode = new_mode; + update_raycasters_for_picking(); + + apply_color_clip_plane_colors(); + if (auto oc = m_c->object_clipper()) { + m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; + oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); + } + if (m_use_TAG_mesh) + update_plane_model(); + reset_cut_by_contours(); + update_clipper(); +} + bool GLGizmoCut3D::render_cut_mode_combo() { ImGui::AlignTextToFramePadding(); @@ -501,18 +517,8 @@ bool GLGizmoCut3D::render_cut_mode_combo() const bool is_changed = m_imgui->combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width); if (is_changed) { - m_mode = size_t(selection_idx); - update_raycasters_for_picking(); - - apply_color_clip_plane_colors(); - if (auto oc = m_c->object_clipper()) { - m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; - oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); - } - if (m_use_TAG_mesh) - update_plane_model(); - reset_cut_by_contours(); - update_clipper(); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change cut mode"), UndoRedo::SnapshotType::GizmoAction); + switch_to_mode(size_t(selection_idx)); check_and_update_connectors_state(); } @@ -1503,19 +1509,50 @@ 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_connectors_editing, - m_ar_plane_center, m_rotation_m); + size_t mode; + float groove_depth; + float groove_width; + float groove_flaps_angle; + float groove_angle; + float groove_depth_tolerance; + float groove_width_tolerance; + + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, mode, m_connectors_editing, + m_ar_plane_center, m_rotation_m, + groove_depth, groove_width, groove_flaps_angle, groove_angle, groove_depth_tolerance, groove_width_tolerance); m_transformed_bounding_box = transformed_bounding_box(m_ar_plane_center, m_rotation_m); set_center_pos(m_ar_plane_center); + if (m_mode != mode) + switch_to_mode(mode); + else if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (!is_approx(m_groove_depth , groove_depth) || + !is_approx(m_groove_width , groove_width) || + !is_approx(m_groove_flaps_angle , groove_flaps_angle) || + !is_approx(m_groove_angle , groove_angle) || + !is_approx(m_groove_depth_tolerance, groove_depth_tolerance) || + !is_approx(m_groove_width_tolerance, groove_width_tolerance) ) + { + m_groove_depth = groove_depth; + m_groove_width = groove_width; + m_groove_flaps_angle = groove_flaps_angle; + m_groove_angle = groove_angle; + m_groove_depth_tolerance= groove_depth_tolerance; + m_groove_width_tolerance= groove_width_tolerance; + update_plane_model(); + } + reset_cut_by_contours(); + } + 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_connectors_editing, - m_ar_plane_center, m_start_dragging_m); + m_ar_plane_center, m_start_dragging_m, + m_groove_depth, m_groove_width, m_groove_flaps_angle, m_groove_angle, m_groove_depth_tolerance, m_groove_width_tolerance); } std::string GLGizmoCut3D::on_get_name() const @@ -1568,7 +1605,9 @@ void GLGizmoCut3D::on_set_state() void GLGizmoCut3D::on_register_raycasters_for_picking() { - assert(m_raycasters.empty()); + // assert(m_raycasters.empty()); + if (!m_raycasters.empty()) + on_unregister_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); @@ -2809,14 +2848,24 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in { bool is_changed{false}; - if (render_slider_double_input(label, in_val, in_tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) + float val = in_val; + float tolerance = in_tolerance; + if (render_slider_double_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + } + in_val = val; + in_tolerance = tolerance; is_changed = true; + } ImGui::SameLine(); m_imgui->disabled_begin(is_approx(in_val, init_val)); const std::string act_name = _u8L("Reset"); if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); in_val = init_val; in_tolerance = 0.1f; is_changed = true; @@ -2827,7 +2876,6 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in if (m_use_TAG_mesh) update_plane_model(); reset_cut_by_contours(); - // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); } } @@ -2847,6 +2895,10 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in m_imgui->slider_float(("##groove_" + label).c_str(), &val, min_val, max_val, format.c_str(), 1.f, true, from_u8(label)); if (!is_approx(old_val, val)) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + } in_val = deg2rad(val); is_changed = true; } @@ -2856,6 +2908,7 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in m_imgui->disabled_begin(is_approx(in_val, init_val)); const std::string act_name = _u8L("Reset"); if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); in_val = init_val; is_changed = true; } @@ -2865,7 +2918,6 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in if (m_use_TAG_mesh) update_plane_model(); reset_cut_by_contours(); - // Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index c8bf50c29c..11ac1ee7a8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -338,6 +338,7 @@ protected: private: void set_center(const Vec3d¢er, bool update_tbb = false); + void switch_to_mode(size_t new_mode); bool render_cut_mode_combo(); bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx); bool render_double_input(const std::string& label, double& value_in); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 4673b338f2..0023a4f63b 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -640,6 +640,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float m_last_slider_status.edited = ImGui::IsItemEdited(); m_last_slider_status.clicked = ImGui::IsItemClicked(); m_last_slider_status.deactivated_after_edit = ImGui::IsItemDeactivatedAfterEdit(); + if (!m_last_slider_status.can_take_snapshot) + m_last_slider_status.can_take_snapshot = ImGui::IsItemClicked(); if (!tooltip.empty() && ImGui::IsItemHovered()) this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 26e58d4ad1..92e761a434 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -49,6 +49,11 @@ public: bool edited { false }; bool clicked { false }; bool deactivated_after_edit { false }; + // flag to indicate possibility to take snapshot from the slider value + // It's used from Gizmos to take snapshots just from the very beginning of the editing + bool can_take_snapshot { false }; + // When Undo/Redo snapshot is taken, then call this function + void invalidate_snapshot() { can_take_snapshot = false; } }; ImGuiWrapper(); @@ -80,6 +85,7 @@ public: ImVec2 get_item_spacing() const; float get_slider_float_height() const; const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; } + LastSliderStatus& get_last_slider_status() { return m_last_slider_status; } void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); From 6659d108d5905b0f1fa05ccea6699fbe5e4ca1cf Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Aug 2023 10:10:23 +0200 Subject: [PATCH 025/120] CutGizmo: Fixed memory leaks. All temporary ModelObjectPtrs have to belong to some Model to correct memory de-allocation --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 69 +++++++++++++++------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 072c000320..91bf6d6c86 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2186,13 +2186,14 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor { m_model = Model(); m_model.add_object(*mo); - ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix, + + Model tmp_model = Model(); + tmp_model.objects = m_model.objects.front()->cut(instance_idx_in, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); - assert(cut_part_ptrs.size() == 1); - m_model = Model(); - m_model.add_object(*cut_part_ptrs.front()); + assert(tmp_model.objects.size() == 1); + m_model = tmp_model; m_instance_idx = instance_idx_in; @@ -2790,9 +2791,11 @@ void GLGizmoCut3D::process_contours() wxBusyCursor wait; if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - ModelObjectPtrs cut_objects = perform_cut_with_groove(model_objects[object_idx], true); - if (!cut_objects.empty()) - m_part_selection = PartSelection(cut_objects.front(), instance_idx); + Model tmp_model = Model(); + tmp_model.objects = perform_cut_with_groove(model_objects[object_idx], true); + if (!tmp_model.objects.empty()) + m_part_selection = PartSelection(tmp_model.objects.front(), instance_idx); + tmp_model = Model(); } else { reset_cut_by_contours(); @@ -3682,15 +3685,14 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool const Transform3d cut_matrix = get_cut_matrix(selection); - ModelObjectPtrs cut_part_ptrs; + Model tmp_model_for_cut = Model(); + Model tmp_model = Model(); tmp_model.add_object(*cut_mo); ModelObject* tmp_object = tmp_model.objects.front(); - bool invalid_added_volumes = false; - - auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const ModelObjectPtrs& cut_part_ptrs) { - const auto& volumes = cut_part_ptrs.front()->volumes; + auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { + const auto& volumes = tmp_model_for_cut.objects.front()->volumes; bool has_volume = false; for (const ModelVolume* volume : volumes) if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || @@ -3702,17 +3704,18 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool return has_volume; }; - auto cut = [instance_idx, add_volumes_from_cut, &invalid_added_volumes] - (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute attribute, ModelObjectPtrs& cut_part_ptrs) { + auto cut = [instance_idx, add_volumes_from_cut] + (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute attribute, Model& tmp_model_for_cut) { Model model = Model(); model.add_object(*object); - cut_part_ptrs = model.objects.front()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); - if (cut_part_ptrs.empty()) + tmp_model_for_cut = Model(); + tmp_model_for_cut.objects = model.objects.front()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); + if (tmp_model_for_cut.objects.empty()) return false; object->clear_volumes(); - invalid_added_volumes |= !add_volumes_from_cut(object, attribute, cut_part_ptrs); + add_volumes_from_cut(object, attribute, tmp_model_for_cut); ModelObject::reset_instance_transformation(object, instance_idx); return true; }; @@ -3723,18 +3726,18 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool const Transform3d cut_matrix_upper = translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - if (!cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + if (!cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) return cut_object_ptrs; - invalid_added_volumes |= !add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } // cut by lower plane const Transform3d cut_matrix_lower = translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - if (!cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs)) + if (!cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut)) return cut_object_ptrs; - invalid_added_volumes |= !add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); } // cut middle part with 2 angles and add parts to related upper/lower objects @@ -3745,18 +3748,18 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool { const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + if (!cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) return cut_object_ptrs; - invalid_added_volumes |= !add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } // cut by angle2 plane { const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + if (!cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) return cut_object_ptrs; - invalid_added_volumes |= !add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } // apply tolerance to the middle part @@ -3764,22 +3767,22 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool const double h_groove_shift_tolerance = groove_half_depth - (double)m_groove_depth_tolerance; const Transform3d cut_matrix_lower_tolerance = translation_transform(m_rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * cut_matrix; - if (!cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs)) + if (!cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut)) return cut_object_ptrs; const double h_side_shift_tolerance = h_side_shift - 0.5 * double(m_groove_width_tolerance); const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, cut_part_ptrs)) + if (!cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) return cut_object_ptrs; const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, cut_part_ptrs)) + if (!cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut)) return cut_object_ptrs; } // this part can be added to the upper object now - invalid_added_volumes |= !add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, cut_part_ptrs); + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); if (keep_as_parts) { // add volumes from lower object to the upper, but mark them as a lower @@ -3789,6 +3792,9 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool new_vol->cut_info.is_from_upper = false; } cut_object_ptrs.push_back(upper); + + // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks + cut_object_ptrs.push_back(lower); } else { auto add_cut_objects = [this, &instance_idx](ModelObjectPtrs& cut_objects, ModelObject* object, const Transform3d& cut_matrix) { @@ -3884,7 +3890,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); - ModelObjectPtrs cut_object_ptrs = cut_by_contour ? perform_cut_by_contour(cut_mo, attributes, dowels_count) : + Model tmp_model = Model(); + tmp_model.objects = cut_by_contour ? perform_cut_by_contour(cut_mo, attributes, dowels_count) : cut_with_groove ? perform_cut_with_groove(cut_mo) : cut_mo->cut(instance_idx, cut_matrix, attributes); @@ -3892,7 +3899,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) const CutObjectBase cut_id = cut_mo->cut_id; // update cut results on plater and in the model - plater->cut(object_idx, cut_object_ptrs); + plater->cut(object_idx, tmp_model.objects); synchronize_model_after_cut(plater->model(), cut_id); } From 0c2913f20c1e8eb73be7e9c7f9266f726b6ba384 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Aug 2023 15:36:30 +0200 Subject: [PATCH 026/120] CutGizmo: Code refactoring to correct a modifiers processing during a cut. Now it's work in same way for all types of cut. After cut we leave just only modifiers which are intersecting with solid parts bounding box. + Clean code in perform_cut_with_groove. Deleted non-used anymore code. + Extract same code (for perform_cut_by_contour and perform_cut_with_groove) to separate functions + Save CutInfo for objects which was cut with Tongue and Groove mode --- src/libslic3r/Model.cpp | 17 +++ src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 180 ++++++++++++--------------- 2 files changed, 100 insertions(+), 97 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 92bb15df85..cfe76f8622 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1609,7 +1609,23 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, res.push_back(upper); } else { + // Delete all modifiers which are not intersecting with solid parts bounding box + auto delete_extra_modifiers = [instance](ModelObject* mo) { + if (!mo) return; + const BoundingBoxf3 obj_bb = mo->instance_bounding_box(instance); + const Transform3d inst_matrix = mo->instances[instance]->get_transformation().get_matrix(); + + for (int i = int(mo->volumes.size()) - 1; i >= 0; --i) + if (const ModelVolume* vol = mo->volumes[i]; + !vol->is_model_part() && !vol->is_cut_connector()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + if (!obj_bb.intersects(bb)) + mo->delete_volume(i); + } + }; + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) { + delete_extra_modifiers(upper); reset_instance_transformation(upper, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), attributes.has(ModelObjectCutAttribute::FlipUpper)); @@ -1617,6 +1633,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, } if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) { + delete_extra_modifiers(lower); reset_instance_transformation(lower, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 91bf6d6c86..9d4aeb24ea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -3547,6 +3547,48 @@ void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id) obj->cut_id.copy(cut_id); } +void distribute_modifiers_from_object(ModelObject *from_obj, const int instance_idx, ModelObject *to_obj1, ModelObject *to_obj2) +{ + auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3(); + auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3(); + const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix(); + + for (ModelVolume*vol : from_obj->volumes) + if (!vol->is_model_part()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + // Don't add modifiers which are not intersecting with solid parts + if (obj1_bb.intersects(bb)) + to_obj1->add_volume(*vol); + if (obj2_bb.intersects(bb)) + to_obj2->add_volume(*vol); + } +} + +void merge_solid_parts_inside_object(ModelObjectPtrs& objects) +{ + for (ModelObject* mo : objects) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume* mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) + mo->delete_volume(i); + } + } + } +} + ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count) { const Selection& selection = m_parent.get_selection(); @@ -3584,20 +3626,7 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const if (has_modifiers) { // Distribute Modifiers to the Upper/Lower object - auto upper_bb = upper ? upper->instance_bounding_box(instance_idx) : BoundingBoxf3(); - auto lower_bb = lower ? lower->instance_bounding_box(instance_idx) : BoundingBoxf3(); - const Transform3d inst_matrix = cut_mo->instances[instance_idx]->get_transformation().get_matrix(); - - for (size_t id = 0; id < cut_parts_cnt; ++id) - if (m_part_selection.parts()[id].is_modifier) { - ModelVolume* vol = cut_mo->volumes[id]; - auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); - // Don't add modifiers which are not intersecting with solid parts - if (upper_bb.intersects(bb)) - upper->add_volume(*vol); - if (lower_bb.intersects(bb)) - lower->add_volume(*vol); - } + distribute_modifiers_from_object(cut_mo, instance_idx, upper, lower); } ModelObjectPtrs cut_object_ptrs; @@ -3640,29 +3669,7 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const } // Now merge all model parts together: - { - for (ModelObject* mo : cut_object_ptrs) { - TriangleMesh mesh; - // Merge all SolidPart but not Connectors - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part() && !mv->is_cut_connector()) { - TriangleMesh m = mv->mesh(); - m.transform(mv->get_matrix()); - mesh.merge(m); - } - } - if (!mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(mesh); - new_volume->name = mo->name; - // Delete all merged SolidPart but not Connectors - for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { - const ModelVolume* mv = mo->volumes[i]; - if (mv->is_model_part() && !mv->is_cut_connector()) - mo->delete_volume(i); - } - } - } - } + merge_solid_parts_inside_object(cut_object_ptrs); return cut_object_ptrs; } @@ -3693,41 +3700,35 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { const auto& volumes = tmp_model_for_cut.objects.front()->volumes; - bool has_volume = false; for (const ModelVolume* volume : volumes) - if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || - (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper()) ) { - ModelVolume* new_vol = object->add_volume(*volume); - new_vol->reset_from_upper(); - has_volume = true; + if (volume->is_model_part()) { + if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || + (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper()) ) { + ModelVolume* new_vol = object->add_volume(*volume); + new_vol->reset_from_upper(); + } } - return has_volume; }; auto cut = [instance_idx, add_volumes_from_cut] - (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute attribute, Model& tmp_model_for_cut) { + (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) { Model model = Model(); model.add_object(*object); tmp_model_for_cut = Model(); tmp_model_for_cut.objects = model.objects.front()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); - if (tmp_model_for_cut.objects.empty()) - return false; + assert(!tmp_model_for_cut.objects.empty()); object->clear_volumes(); - add_volumes_from_cut(object, attribute, tmp_model_for_cut); + add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); ModelObject::reset_instance_transformation(object, instance_idx); - return true; }; - ModelObjectPtrs cut_object_ptrs; - // cut by upper plane const Transform3d cut_matrix_upper = translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - if (!cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) - return cut_object_ptrs; + cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } @@ -3735,8 +3736,7 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool const Transform3d cut_matrix_lower = translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cut_matrix; { - if (!cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut)) - return cut_object_ptrs; + cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); } @@ -3748,8 +3748,7 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool { const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) - return cut_object_ptrs; + cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } @@ -3757,8 +3756,7 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool { const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) - return cut_object_ptrs; + cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } @@ -3767,23 +3765,22 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool const double h_groove_shift_tolerance = groove_half_depth - (double)m_groove_depth_tolerance; const Transform3d cut_matrix_lower_tolerance = translation_transform(m_rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * cut_matrix; - if (!cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut)) - return cut_object_ptrs; + cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); const double h_side_shift_tolerance = h_side_shift - 0.5 * double(m_groove_width_tolerance); const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut)) - return cut_object_ptrs; + cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); - if (!cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut)) - return cut_object_ptrs; + cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } // this part can be added to the upper object now add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + ModelObjectPtrs cut_object_ptrs; + if (keep_as_parts) { // add volumes from lower object to the upper, but mark them as a lower const auto& volumes = lower->volumes; @@ -3791,46 +3788,34 @@ ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool ModelVolume* new_vol = upper->add_volume(*volume); new_vol->cut_info.is_from_upper = false; } + + // add modifiers + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) + upper->add_volume(*volume); + cut_object_ptrs.push_back(upper); // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks cut_object_ptrs.push_back(lower); } else { - auto add_cut_objects = [this, &instance_idx](ModelObjectPtrs& cut_objects, ModelObject* object, const Transform3d& cut_matrix) { - if (object && !object->volumes.empty()) { - ModelObject::reset_instance_transformation(object, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); - cut_objects.push_back(object); + // add modifiers if object has any + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) { + distribute_modifiers_from_object(cut_mo, instance_idx, upper, lower); + break; } - }; - add_cut_objects(cut_object_ptrs, upper, cut_matrix_upper); - add_cut_objects(cut_object_ptrs, lower, cut_matrix_lower); + assert(!upper->volumes.empty() && !lower->volumes.empty()); + + ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix_upper, m_place_on_cut_upper, m_rotate_upper); + cut_object_ptrs.push_back(upper); + ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix_lower, m_place_on_cut_upper, m_rotate_upper); + cut_object_ptrs.push_back(lower); // Now merge all model parts together: - { - for (ModelObject* mo : cut_object_ptrs) { - TriangleMesh mesh; - // Merge all SolidPart but not Connectors - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part() && !mv->is_cut_connector()) { - TriangleMesh m = mv->mesh(); - m.transform(mv->get_matrix()); - mesh.merge(m); - } - } - if (!mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(mesh); - new_volume->name = mo->name; - // Delete all merged SolidPart but not Connectors - for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { - const ModelVolume* mv = mo->volumes[i]; - if (mv->is_model_part() && !mv->is_cut_connector()) - mo->delete_volume(i); - } - } - } - } + merge_solid_parts_inside_object(cut_object_ptrs); } return cut_object_ptrs; @@ -3860,8 +3845,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // This shall delete the part selection class and deallocate the memory. ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); }); - const bool cut_by_contour = m_part_selection.valid(); const bool cut_with_groove = CutMode(m_mode) == CutMode::cutTongueAndGroove; + const bool cut_by_contour = !cut_with_groove && m_part_selection.valid(); + ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr; if (cut_mo) cut_mo->cut_connectors = mo->cut_connectors; @@ -3885,7 +3871,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) | - only_if(!has_connectors && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo); + only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo); // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); From 89d66737b5274f7a638ce032ac50d1dc779f56c0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 7 Aug 2023 11:03:56 +0200 Subject: [PATCH 027/120] CutGizmo: Put the check of groove flaps validity to the has_valid_groove() function to avoid perform a cut with non valid groove --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9d4aeb24ea..5a4637b126 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -3427,10 +3427,8 @@ bool GLGizmoCut3D::can_perform_cut() const if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; - if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - const float flaps_width = -2.f * m_groove_depth / tan(m_groove_flaps_angle); - return flaps_width < m_groove_width && has_valid_groove(); - } + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return has_valid_groove(); if (m_part_selection.valid()) return ! m_part_selection.is_one_object(); @@ -3443,6 +3441,10 @@ bool GLGizmoCut3D::has_valid_groove() const if (CutMode(m_mode) != CutMode::cutTongueAndGroove) return true; + const float flaps_width = -2.f * m_groove_depth / tan(m_groove_flaps_angle); + if (flaps_width > m_groove_width) + return false; + const Selection& selection = m_parent.get_selection(); const auto&list = selection.get_volume_idxs(); // is more volumes selected? From 190a4cde48df9f167468f37d1a95f36bb7f24fa8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Aug 2023 10:25:26 +0200 Subject: [PATCH 028/120] CutGizmo: Next bug fixing * Fixed a crash on UndoRedo action when switching between cut modes * Fixed update of the CutGizmo parameters when selected object is changed --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5a4637b126..55d57f7178 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -507,7 +507,6 @@ void GLGizmoCut3D::switch_to_mode(size_t new_mode) if (m_use_TAG_mesh) update_plane_model(); reset_cut_by_contours(); - update_clipper(); } bool GLGizmoCut3D::render_cut_mode_combo() @@ -1572,7 +1571,6 @@ void GLGizmoCut3D::on_set_state() { if (m_state == On) { m_parent.set_use_color_clip_plane(true); - apply_color_clip_plane_colors(); update_bb(); m_connectors_editing = !m_selected.empty(); @@ -2065,8 +2063,14 @@ void GLGizmoCut3D::update_bb() m_bounding_box = box; + // check, if mode is set to Planar, when object has a connectors + if (const int object_idx = m_parent.get_selection().get_object_idx(); + object_idx >= 0 && !wxGetApp().plater()->model().objects[object_idx]->cut_connectors.empty()) + m_mode = size_t(CutMode::cutPlanar); + invalidate_cut_plane(); reset_cut_by_contours(); + apply_color_clip_plane_colors(); m_max_pos = box.max; m_min_pos = box.min; @@ -2077,6 +2081,8 @@ void GLGizmoCut3D::update_bb() else set_center_pos(m_bb_center); + m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; + m_radius = box.radius(); m_grabber_connection_len = 0.5 * m_radius;// std::min(0.75 * m_radius, 35.0); m_grabber_radius = m_grabber_connection_len * 0.85; @@ -2107,8 +2113,6 @@ void GLGizmoCut3D::update_bb() if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info(); selection && selection->model_object()) m_selected.resize(selection->model_object()->cut_connectors.size(), false); - -// reset_cut_by_contours(); } } From 1b451cdf9f8859aff58df54fa89d689aa47518d7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 9 Aug 2023 13:22:11 +0200 Subject: [PATCH 029/120] CutGizmo: Big code refactoring. All manipulations related to cut are extracted to CutUtils now --- src/PrusaSlicer.cpp | 6 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/CutUtils.cpp | 627 +++++++++++++++++++++++++++ src/libslic3r/CutUtils.hpp | 65 +++ src/libslic3r/Model.cpp | 321 -------------- src/libslic3r/Model.hpp | 20 - src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 424 ++++-------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 16 +- src/slic3r/GUI/Plater.cpp | 15 +- src/slic3r/GUI/Plater.hpp | 3 +- 10 files changed, 781 insertions(+), 718 deletions(-) create mode 100644 src/libslic3r/CutUtils.cpp create mode 100644 src/libslic3r/CutUtils.hpp diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index e600f343ca..480f42811e 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -40,6 +40,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/CutUtils.hpp" #include "libslic3r/ModelArrange.hpp" #include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" @@ -437,8 +438,11 @@ int CLI::run(int argc, char **argv) } #else // model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); - model.objects.front()->cut(0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()), + Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); + auto cut_objects = cut.perform_with_plane(); + for (ModelObject* obj : cut_objects) + model.add_object(*obj); #endif model.delete_object(size_t(0)); } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 0de0b4e517..a31e4cc7c8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -201,6 +201,8 @@ set(SLIC3R_SOURCES BlacklistedLibraryCheck.hpp LocalesUtils.cpp LocalesUtils.hpp + CutUtils.cpp + CutUtils.hpp Model.cpp Model.hpp ModelArrange.hpp diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp new file mode 100644 index 0000000000..7d107b1d05 --- /dev/null +++ b/src/libslic3r/CutUtils.cpp @@ -0,0 +1,627 @@ + +#include "CutUtils.hpp" +#include "libslic3r.h" +#include "Model.hpp" +#include "TriangleMeshSlicer.hpp" +#include "TriangleSelector.hpp" + + +namespace Slic3r { + +using namespace Geometry; + +static void apply_tolerance(ModelVolume* vol) +{ + ModelVolume::CutInfo& cut_info = vol->cut_info; + + assert(cut_info.is_connector); + if (!cut_info.is_processed) + return; + + Vec3d sf = vol->get_scaling_factor(); + + // make a "hole" wider + sf[X] += double(cut_info.radius_tolerance); + sf[Y] += double(cut_info.radius_tolerance); + + // make a "hole" dipper + sf[Z] += double(cut_info.height_tolerance); + + vol->set_scaling_factor(sf); + + // correct offset in respect to the new depth + Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ(); + if (rot_norm.norm() != 0.0) + rot_norm.normalize(); + + double z_offset = 0.5 * static_cast(cut_info.height_tolerance); + if (cut_info.connector_type == CutConnectorType::Plug || + cut_info.connector_type == CutConnectorType::Snap) + z_offset -= 0.05; // add small Z offset to better preview + + vol->set_offset(vol->get_offset() + rot_norm * z_offset); +} + +static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART) +{ + if (mesh.empty()) + return; + + mesh.transform(cut_matrix); + ModelVolume* vol = object->add_volume(mesh); + vol->set_type(type); + + vol->name = src_volume->name + suffix; + // 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()); + vol->cut_info = src_volume->cut_info; +} + +static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) +{ + const auto volume_matrix = volume->get_matrix(); + + const Transformation cut_transformation = Transformation(cut_matrix); + const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_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); + + 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); +} + +static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + std::vector& dowels) +{ + assert(volume->cut_info.is_connector); + volume->cut_info.set_processed(); + + const auto volume_matrix = volume->get_matrix(); + + // ! Don't apply instance transformation for the conntectors. + // This transformation is already there + if (volume->cut_info.connector_type != CutConnectorType::Dowel) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + ModelVolume* vol = nullptr; + if (volume->cut_info.connector_type == CutConnectorType::Snap) { + TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); + + vol = upper->add_volume(std::move(mesh)); + vol->set_transformation(volume->get_transformation()); + vol->set_type(ModelVolumeType::NEGATIVE_VOLUME); + + vol->cut_info = volume->cut_info; + vol->name = volume->name; + } + else + 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); + // 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); + } + } + else { + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { + ModelObject* dowel{ nullptr }; + // Clone the object to duplicate instances, materials etc. + volume->get_object()->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); + + dowels.push_back(dowel); + } + + // Cut the dowel + apply_tolerance(volume); + + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); + + // add small Z offset to better preview + upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); + lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); + + // Add cut parts to the related objects + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); + add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); + } +} + +static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +{ + 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(Transformation(volume_matrix)); + + if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { + upper->add_volume(*volume); + return; + } + + // 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 process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +{ + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); + + // Add required cut parts to the objects + + if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + upper->volumes.back()->cut_info.is_from_upper = false; + return; + } + + 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); +} + +static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, + const Transform3d& cut_matrix = Transform3d::Identity(), + bool place_on_cut = false, bool flip = false) +{ + // 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 double rot_z = obj_instance->get_rotation().z(); + + obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor())); + + 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(PI * Vec3d::UnitX()); + + 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); + } +} + + +Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/) + : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes) +{ + m_model = Model(); + if (object) + m_model.add_object(*object); +} + +const ModelObjectPtrs& Cut::perform_with_plane() +{ + if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) { + m_model.clear_objects(); + return m_model.objects; + } + + ModelObject* mo = m_model.objects.front(); + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) + mo->clone_for_cut(&upper); + + ModelObject* lower{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::KeepAsParts)) + mo->clone_for_cut(&lower); + + std::vector dowels; + + // 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 = mo->instances[m_instance]->get_transformation().get_matrix_no_offset(); + const Transformation cut_transformation = Transformation(m_cut_matrix); + const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); + + for (ModelVolume* volume : mo->volumes) { + volume->reset_extra_facets(); + + if (!volume->is_model_part()) { + if (volume->cut_info.is_processed) + process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower); + else + process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); + } + else if (!volume->mesh().empty()) + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); + } + + // Post-process cut parts + + if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) { + m_model = Model(); + m_model.objects.push_back(upper); + return m_model.objects; + } + + ModelObjectPtrs cut_object_ptrs; + + if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) { + reset_instance_transformation(upper, m_instance, m_cut_matrix); + cut_object_ptrs.push_back(upper); + } + else { + // Delete all modifiers which are not intersecting with solid parts bounding box + auto delete_extra_modifiers = [this](ModelObject* mo) { + if (!mo) return; + const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance); + const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix(); + + for (int i = int(mo->volumes.size()) - 1; i >= 0; --i) + if (const ModelVolume* vol = mo->volumes[i]; + !vol->is_model_part() && !vol->is_cut_connector()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + if (!obj_bb.intersects(bb)) + mo->delete_volume(i); + } + }; + + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) { + delete_extra_modifiers(upper); + reset_instance_transformation(upper, m_instance, m_cut_matrix, + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), + m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + cut_object_ptrs.push_back(upper); + } + + if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) { + delete_extra_modifiers(lower); + reset_instance_transformation(lower, m_instance, m_cut_matrix, + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); + cut_object_ptrs.push_back(lower); + } + + if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { + for (auto dowel : dowels) { + reset_instance_transformation(dowel, m_instance, Transform3d::Identity()); + dowel->name += "-Dowel-" + dowel->volumes[0]->name; + cut_object_ptrs.push_back(dowel); + } + } + } + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + + m_model.clear_objects(); + m_model.objects = cut_object_ptrs; + + return m_model.objects; +} + +static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2) +{ + auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3(); + auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3(); + const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix(); + + for (ModelVolume* vol : from_obj->volumes) + if (!vol->is_model_part()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + // Don't add modifiers which are not intersecting with solid parts + if (obj1_bb.intersects(bb)) + to_obj1->add_volume(*vol); + if (obj2_bb.intersects(bb)) + to_obj2->add_volume(*vol); + } +} + +static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) +{ + for (ModelObject* mo : objects) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume* mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) + mo->delete_volume(i); + } + } + } +} + + +const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowels_count) +{ + ModelObject* cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower); + + auto add_cut_objects = [this](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) { + if (upper && !upper->volumes.empty()) { + reset_instance_transformation(upper, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + cut_objects.push_back(upper); + } + if (lower && !lower->volumes.empty()) { + reset_instance_transformation(lower, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); + cut_objects.push_back(lower); + } + }; + + const size_t cut_parts_cnt = parts.size(); + bool has_modifiers = false; + + // Distribute SolidParts to the Upper/Lower object + for (size_t id = 0; id < cut_parts_cnt; ++id) { + if (parts[id].is_modifier) + has_modifiers = true; // modifiers will be added later to the related parts + else if (ModelObject* obj = (parts[id].selected ? upper : lower)) + obj->add_volume(*(cut_mo->volumes[id])); + } + + if (has_modifiers) { + // Distribute Modifiers to the Upper/Lower object + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + } + + ModelObjectPtrs cut_object_ptrs; + + ModelVolumePtrs& volumes = cut_mo->volumes; + if (volumes.size() == cut_parts_cnt) { + // Means that object is cut without connectors + + // Just add Upper and Lower objects to cut_object_ptrs + add_cut_objects(cut_object_ptrs, upper, lower); + } + else if (volumes.size() > cut_parts_cnt) { + // Means that object is cut with connectors + + // All volumes are distributed to Upper / Lower object, + // So we don’t need them anymore + for (size_t id = 0; id < cut_parts_cnt; id++) + delete* (volumes.begin() + id); + volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); + + // Perform cut just to get connectors + Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes); + const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane(); + assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); + + // Connectors from upper object + for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) + upper->add_volume(*volume, volume->type()); + + // Connectors from lower object + for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) + lower->add_volume(*volume, volume->type()); + + // Add Upper and Lower objects to cut_object_ptrs + add_cut_objects(cut_object_ptrs, upper, lower); + + // Add Dowel-connectors as separate objects to cut_object_ptrs + if (cut_connectors_obj.size() >= 3) + for (size_t id = 2; id < cut_connectors_obj.size(); id++) + cut_object_ptrs.push_back(cut_connectors_obj[id]); + } + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + + m_model.clear_objects(); + m_model.objects = cut_object_ptrs; + + return m_model.objects; +} + + +const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/) +{ + ModelObject* cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + cut_mo->clone_for_cut(&lower); + + const double groove_half_depth = 0.5 * double(groove.depth); + + Model tmp_model_for_cut = Model(); + + Model tmp_model = Model(); + tmp_model.add_object(*cut_mo); + ModelObject* tmp_object = tmp_model.objects.front(); + + auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { + const auto& volumes = tmp_model_for_cut.objects.front()->volumes; + for (const ModelVolume* volume : volumes) + if (volume->is_model_part()) { + if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || + (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) { + ModelVolume* new_vol = object->add_volume(*volume); + new_vol->reset_from_upper(); + } + } + }; + + auto cut = [this, add_volumes_from_cut] + (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) { + Cut cut(object, m_instance, cut_matrix); + + tmp_model_for_cut = Model(); + tmp_model_for_cut.add_object(*cut.perform_with_plane().front()); + assert(!tmp_model_for_cut.objects.empty()); + + object->clear_volumes(); + add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); + reset_instance_transformation(object, m_instance); + }; + + // cut by upper plane + + const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by lower plane + + const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + } + + // cut middle part with 2 angles and add parts to related upper/lower objects + + const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + + // cut by angle1 plane + { + const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + + cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by angle2 plane + { + const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + + cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // apply tolerance to the middle part + { + const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance; + + const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix; + cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + + const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); + + const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // this part can be added to the upper object now + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + ModelObjectPtrs cut_object_ptrs; + + if (keep_as_parts) { + // add volumes from lower object to the upper, but mark them as a lower + const auto& volumes = lower->volumes; + for (const ModelVolume* volume : volumes) { + ModelVolume* new_vol = upper->add_volume(*volume); + new_vol->cut_info.is_from_upper = false; + } + + // add modifiers + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) + upper->add_volume(*volume); + + cut_object_ptrs.push_back(upper); + + // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks + cut_object_ptrs.push_back(lower); + } + else { + // add modifiers if object has any + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) { + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + break; + } + + assert(!upper->volumes.empty() && !lower->volumes.empty()); + + reset_instance_transformation(upper, m_instance, cut_matrix_upper, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + cut_object_ptrs.push_back(upper); + reset_instance_transformation(lower, m_instance, cut_matrix_lower, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + cut_object_ptrs.push_back(lower); + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + } + + m_model.clear_objects(); + m_model.objects = cut_object_ptrs; + + return m_model.objects; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp new file mode 100644 index 0000000000..a31404e898 --- /dev/null +++ b/src/libslic3r/CutUtils.hpp @@ -0,0 +1,65 @@ +#ifndef slic3r_CutUtils_hpp_ +#define slic3r_CutUtils_hpp_ + +#include "libslic3r.h" +#include "enum_bitmask.hpp" +#include "Geometry.hpp" +#include "ObjectID.hpp" +#include "Point.hpp" +#include "Model.hpp" + +#include + +namespace Slic3r { + +using ModelObjectPtrs = std::vector; + +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo }; +using ModelObjectCutAttributes = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); + + +class Cut { + + Model m_model; + int m_instance; + const Transform3d& m_cut_matrix; + ModelObjectCutAttributes m_attributes; + +public: + + Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts ); + ~Cut() { m_model.clear_objects(); } + + struct Groove + { + float depth{ 0.f }; + float width{ 0.f }; + float flaps_angle{ 0.f }; + float angle{ 0.f }; + float depth_init{ 0.f }; + float width_init{ 0.f }; + float flaps_angle_init{ 0.f }; + float angle_init{ 0.f }; + float depth_tolerance{ 0.1f }; + float width_tolerance{ 0.1f }; + }; + + struct Part + { + bool selected; + bool is_modifier; + }; + + const ModelObjectPtrs& perform_with_plane(); + const ModelObjectPtrs& perform_by_contour(std::vector parts, int dowels_count); + const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false); + +}; // namespace Cut + +} // namespace Slic3r + +#endif /* slic3r_CutUtils_hpp_ */ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index cfe76f8622..2a8c78729b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1332,327 +1332,6 @@ void ModelVolume::reset_extra_facets() this->mmu_segmentation_facets.reset(); } -void ModelVolume::apply_tolerance() -{ - assert(cut_info.is_connector); - if (!cut_info.is_processed) - return; - - Vec3d sf = get_scaling_factor(); - - // make a "hole" wider - sf[X] += double(cut_info.radius_tolerance); - sf[Y] += double(cut_info.radius_tolerance); - - // make a "hole" dipper - sf[Z] += double(cut_info.height_tolerance); - - set_scaling_factor(sf); - - // correct offset in respect to the new depth - Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ(); - if (rot_norm.norm() != 0.0) - rot_norm.normalize(); - - double z_offset = 0.5 * static_cast(cut_info.height_tolerance); - if (cut_info.connector_type == CutConnectorType::Plug || - cut_info.connector_type == CutConnectorType::Snap) - z_offset -= 0.05; // add small Z offset to better preview - - set_offset(get_offset() + rot_norm * z_offset); -} - -static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART) -{ - if (mesh.empty()) - return; - - mesh.transform(cut_matrix); - ModelVolume* vol = object->add_volume(mesh); - vol->set_type(type); - - vol->name = src_volume->name + suffix; - // 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()); - vol->cut_info = src_volume->cut_info; -} - -void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - std::vector& dowels) -{ - assert(volume->cut_info.is_connector); - volume->cut_info.set_processed(); - - const auto volume_matrix = volume->get_matrix(); - - // ! Don't apply instance transformation for the conntectors. - // This transformation is already there - if (volume->cut_info.connector_type != CutConnectorType::Dowel) { - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - ModelVolume* vol = nullptr; - if (volume->cut_info.connector_type == CutConnectorType::Snap) { - TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); - - vol = upper->add_volume(std::move(mesh)); - vol->set_transformation(volume->get_transformation()); - vol->set_type(ModelVolumeType::NEGATIVE_VOLUME); - - vol->cut_info = volume->cut_info; - vol->name = volume->name; - } - else - 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); - // 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); - } - } - else { - if (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); - - dowels.push_back(dowel); - } - - // Cut the dowel - volume->apply_tolerance(); - - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); - - // add small Z offset to better preview - upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); - lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); - - // Add cut parts to the related objects - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); - add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); - } -} - -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 = 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(volume_matrix)); - - if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - upper->add_volume(*volume); - return; - } - - // 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); -} - -void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) -{ - 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() * translation_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); - - 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); -} -void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) -{ - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); - - // Add required cut parts to the objects - - if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); - upper->volumes.back()->cut_info.is_from_upper = false; - return; - } - - 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); -} - -void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, - bool place_on_cut/* = false*/, bool flip/* = false*/) -{ - using namespace Geometry; - - // 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 double rot_z = obj_instance->get_rotation().z(); - - obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor())); - - 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(PI * Vec3d::UnitX()); - - 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"; - - // Clone the object to duplicate instances, materials etc. - ModelObject* upper{ nullptr }; - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - clone_for_cut(&upper); - - ModelObject* lower{ nullptr }; - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::KeepAsParts)) - clone_for_cut(&lower); - - std::vector dowels; - - 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 = instances[instance]->get_transformation().get_matrix_no_offset(); - const Transformation cut_transformation = Transformation(cut_matrix); - const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); - - for (ModelVolume* volume : volumes) { - volume->reset_extra_facets(); - - if (!volume->is_model_part()) { - if (volume->cut_info.is_processed) - process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); - else - process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels); - } - else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower); - } - - // Post-process cut parts - - ModelObjectPtrs res; - if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) - return res; - - if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) { - reset_instance_transformation(upper, instance, cut_matrix); - res.push_back(upper); - } - else { - // Delete all modifiers which are not intersecting with solid parts bounding box - auto delete_extra_modifiers = [instance](ModelObject* mo) { - if (!mo) return; - const BoundingBoxf3 obj_bb = mo->instance_bounding_box(instance); - const Transform3d inst_matrix = mo->instances[instance]->get_transformation().get_matrix(); - - for (int i = int(mo->volumes.size()) - 1; i >= 0; --i) - if (const ModelVolume* vol = mo->volumes[i]; - !vol->is_model_part() && !vol->is_cut_connector()) { - auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); - if (!obj_bb.intersects(bb)) - mo->delete_volume(i); - } - }; - - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) { - delete_extra_modifiers(upper); - reset_instance_transformation(upper, instance, cut_matrix, - attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), - attributes.has(ModelObjectCutAttribute::FlipUpper)); - res.push_back(upper); - } - - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) { - delete_extra_modifiers(lower); - reset_instance_transformation(lower, instance, cut_matrix, - attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), - attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower)); - res.push_back(lower); - } - - if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { - for (auto dowel : dowels) { - reset_instance_transformation(dowel, instance, Transform3d::Identity()); - dowel->name += "-Dowel-" + dowel->volumes[0]->name; - res.push_back(dowel); - } - } - } - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; - - return res; -} /// /// Compare TriangleMeshes by Bounding boxes (mainly for sort) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a14bf37705..221033c521 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -317,10 +317,6 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER, }; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo }; -using ModelObjectCutAttributes = enum_bitmask; -ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); - // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, @@ -468,21 +464,6 @@ public: void delete_connectors(); void clone_for_cut(ModelObject **obj); -private: - void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - std::vector& dowels); - void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); - void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh); - void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); -public: - static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix = Transform3d::Identity(), - bool place_on_cut = false, bool flip = false); - - 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, @@ -850,7 +831,6 @@ public: bool is_the_only_one_part() const; // behave like an object 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; 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 55d57f7178..3dd4fe6edd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -675,7 +675,7 @@ static double get_grabber_mean_size(const BoundingBoxf3& bb) indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane() { - const float ghw = 0.5f * m_groove_width; // groove half width + const float ghw = 0.5f * m_groove.width; // groove half width const float cpr = 1.5f * float(m_radius); // cut plane radius const float cpl = 1.5f * cpr; // cut plane length @@ -684,7 +684,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane() // We need two upward facing triangles float x = 0.5f * cpr, y = 0.5f * cpl; - const float proj = y * tan(m_groove_angle); + const float proj = y * tan(m_groove.angle); const float extension_x = ghw + proj; // upper cut plane is simple @@ -712,7 +712,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane() if (ghw < proj) { - const float cross_pt_y = ghw / tan(m_groove_angle); + const float cross_pt_y = ghw / tan(m_groove.angle); return { { // roof @@ -756,7 +756,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane() indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width) { - const float ghw = 0.5f * (m_groove_width + flaps_width); // groove half width + const float ghw = 0.5f * (m_groove.width + flaps_width); // groove half width const float cpr = 1.5f * float(m_radius); // cut plane radius const float cpl = 1.5f * cpr; // cut plane length @@ -765,7 +765,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width // We need two upward facing triangles float x = 0.5f * cpr, y = 0.5f * cpl; - const float proj = y * tan(m_groove_angle); + const float proj = y * tan(m_groove.angle); const float extension_x = ghw + proj; // upper cut plane is trapezium @@ -793,7 +793,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width // upper cut plane is triangle - const float cross_pt_y = ghw / tan(m_groove_angle); + const float cross_pt_y = ghw / tan(m_groove.angle); return { { // roof @@ -814,14 +814,14 @@ indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width) { - const float ghw_upper = 0.5f * (m_groove_width); // groove half width - const float ghw_lower = 0.5f * (m_groove_width + flaps_width); // groove half width + const float ghw_upper = 0.5f * (m_groove.width); // groove half width + const float ghw_lower = 0.5f * (m_groove.width + flaps_width); // groove half width const float cpr = 1.5f * float(m_radius); // cut plane radius const float cpl = 1.5f * cpr; // cut plane length const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height - const float ghd = 0.5f * m_groove_depth; // groove half depth + const float ghd = 0.5f * m_groove.depth; // groove half depth // We need two upward facing triangles @@ -829,7 +829,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width float z_upper = ghd; float z_lower = -ghd; - const float proj = y * tan(m_groove_angle); + const float proj = y * tan(m_groove.angle); const float extension_upper_x = ghw_upper + proj; const float extension_lower_x = ghw_lower + proj; @@ -854,12 +854,12 @@ indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width }; } - const float cross_pt_upper_y = ghw_upper / tan(m_groove_angle); + const float cross_pt_upper_y = ghw_upper / tan(m_groove.angle); // groove is closed if (ghw_upper < proj && ghw_lower < proj) { - const float cross_pt_lower_y = ghw_lower / tan(m_groove_angle); + const float cross_pt_lower_y = ghw_lower / tan(m_groove.angle); return { { @@ -898,23 +898,23 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() { // values for calculation - const float side_width = is_approx(m_groove_flaps_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_flaps_angle)); - const float flaps_width = 2.f * side_width * cos(m_groove_flaps_angle); + const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); + const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle); - const float groove_half_width_upper = 0.5f * (m_groove_width); - const float groove_half_width_lower = 0.5f * (m_groove_width + flaps_width); + const float groove_half_width_upper = 0.5f * (m_groove.width); + const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width); const float cut_plane_radius = 1.5f * float(m_radius); const float cut_plane_length = 1.5f * cut_plane_radius; - const float groove_half_depth = 0.5f * m_groove_depth; + const float groove_half_depth = 0.5f * m_groove.depth; const float x = 0.5f * cut_plane_radius; const float y = 0.5f * cut_plane_length; float z_upper = groove_half_depth; float z_lower = -groove_half_depth; - const float proj = y * tan(m_groove_angle); + const float proj = y * tan(m_groove.angle); float ext_upper_x = groove_half_width_upper + proj; // upper_x extension float ext_lower_x = groove_half_width_lower + proj; // lower_x extension @@ -966,7 +966,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() z_upper -= cut_plane_thiknes; z_lower -= cut_plane_thiknes; if (m_use_TAG_mesh_full) { - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle); + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); nar_upper_x += under_x_shift; nar_lower_x += under_x_shift; @@ -1019,12 +1019,12 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() return mesh; } - float cross_pt_upper_y = groove_half_width_upper / tan(m_groove_angle); + float cross_pt_upper_y = groove_half_width_upper / tan(m_groove.angle); // groove is closed if (groove_half_width_upper < proj && groove_half_width_lower < proj) { - float cross_pt_lower_y = groove_half_width_lower / tan(m_groove_angle); + float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle); indexed_triangle_set mesh; @@ -1045,7 +1045,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() z_lower -= cut_plane_thiknes; if (m_use_TAG_mesh_full) { - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle); + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); cross_pt_upper_y += cut_plane_thiknes; cross_pt_lower_y += cut_plane_thiknes; @@ -1112,7 +1112,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() z_upper -= cut_plane_thiknes; z_lower -= cut_plane_thiknes; - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle); + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); nar_lower_x += under_x_shift; ext_upper_x += under_x_shift; @@ -1173,10 +1173,10 @@ void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shade ColorRGBA cp_clr = m_plane.model.get_color(); // values for calculaton - const double groove_half_depth = 0.5 * double(m_groove_depth); + const double groove_half_depth = 0.5 * double(m_groove.depth); - const float side_width = is_approx(m_groove_flaps_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_flaps_angle)); - const float flaps_width = 2.f * side_width * cos(m_groove_flaps_angle); + const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); + const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle); GLModel model; @@ -1454,7 +1454,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers() // render CutPlaneYMove grabber - if (m_groove_angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove)) + if (m_groove.angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove)) { size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); color = m_hover_id == CutPlaneYMove ? ColorRGBA::GREEN() : m_plane.model.get_color(); @@ -1526,19 +1526,19 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) if (m_mode != mode) switch_to_mode(mode); else if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - if (!is_approx(m_groove_depth , groove_depth) || - !is_approx(m_groove_width , groove_width) || - !is_approx(m_groove_flaps_angle , groove_flaps_angle) || - !is_approx(m_groove_angle , groove_angle) || - !is_approx(m_groove_depth_tolerance, groove_depth_tolerance) || - !is_approx(m_groove_width_tolerance, groove_width_tolerance) ) + if (!is_approx(m_groove.depth , groove_depth) || + !is_approx(m_groove.width , groove_width) || + !is_approx(m_groove.flaps_angle , groove_flaps_angle) || + !is_approx(m_groove.angle , groove_angle) || + !is_approx(m_groove.depth_tolerance, groove_depth_tolerance) || + !is_approx(m_groove.width_tolerance, groove_width_tolerance) ) { - m_groove_depth = groove_depth; - m_groove_width = groove_width; - m_groove_flaps_angle = groove_flaps_angle; - m_groove_angle = groove_angle; - m_groove_depth_tolerance= groove_depth_tolerance; - m_groove_width_tolerance= groove_width_tolerance; + m_groove.depth = groove_depth; + m_groove.width = groove_width; + m_groove.flaps_angle = groove_flaps_angle; + m_groove.angle = groove_angle; + m_groove.depth_tolerance= groove_depth_tolerance; + m_groove.width_tolerance= groove_width_tolerance; update_plane_model(); } reset_cut_by_contours(); @@ -1551,7 +1551,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_ar_plane_center, m_start_dragging_m, - m_groove_depth, m_groove_width, m_groove_flaps_angle, m_groove_angle, m_groove_depth_tolerance, m_groove_width_tolerance); + m_groove.depth, m_groove.width, m_groove.flaps_angle, m_groove.angle, m_groove.depth_tolerance, m_groove.width_tolerance); } std::string GLGizmoCut3D::on_get_name() const @@ -1755,7 +1755,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() offset = (size + xy_connection_len) * Vec3d::UnitX(); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); - if (m_groove_angle > 0.0f) { + if (m_groove.angle > 0.0f) { offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones(); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size)); offset = (size + xy_connection_len) * Vec3d::UnitY(); @@ -1996,7 +1996,7 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=fa bool can_set_center_pos = false; { - double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove_depth) : */0.5; + double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove.depth) : */0.5; if (tbb.max.z() > -limit_val && tbb.min.z() < limit_val) can_set_center_pos = true; else { @@ -2093,10 +2093,10 @@ void GLGizmoCut3D::update_bb() m_snap_fine_out_radius = m_grabber_connection_len * 1.15; // input params for cut with tongue and groove - m_groove_depth = m_groove_depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box))); - m_groove_width = m_groove_width_init = 4.0f * m_groove_depth; - m_groove_flaps_angle = m_groove_flaps_angle_init = float(PI) / 3.f; - m_groove_angle = m_groove_angle_init = 0.f; + m_groove.depth = m_groove.depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box))); + m_groove.width = m_groove.width_init = 4.0f * m_groove.depth; + m_groove.flaps_angle = m_groove.flaps_angle_init = float(PI) / 3.f; + m_groove.angle = m_groove.angle_init = 0.f; m_plane.reset(); m_cone.reset(); m_sphere.reset(); @@ -2188,16 +2188,9 @@ void GLGizmoCut3D::render_clipper_cut() GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) { + Cut cut(mo, instance_idx_in, cut_matrix); m_model = Model(); - m_model.add_object(*mo); - - Model tmp_model = Model(); - tmp_model.objects = m_model.objects.front()->cut(instance_idx_in, cut_matrix, - ModelObjectCutAttribute::KeepUpper | - ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts); - assert(tmp_model.objects.size() == 1); - m_model = tmp_model; + m_model.add_object(*cut.perform_with_plane().front()); m_instance_idx = instance_idx_in; @@ -2270,6 +2263,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor m_valid = true; } +// In CutMode::cutTongueAndGroove we use PartSelection just for rendering GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* object, int instance_idx_in) { m_instance_idx = instance_idx_in; @@ -2388,6 +2382,16 @@ bool GLGizmoCut3D::PartSelection::is_one_object() const }); } +std::vector GLGizmoCut3D::PartSelection::get_cut_parts() +{ + std::vector parts; + + for (const auto& part : m_parts) + parts.push_back({part.selected, part.is_modifier}); + + return parts; +} + void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) { @@ -2795,11 +2799,12 @@ void GLGizmoCut3D::process_contours() wxBusyCursor wait; if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - Model tmp_model = Model(); - tmp_model.objects = perform_cut_with_groove(model_objects[object_idx], true); - if (!tmp_model.objects.empty()) - m_part_selection = PartSelection(tmp_model.objects.front(), instance_idx); - tmp_model = Model(); + if (has_valid_groove()) { + Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection)); + const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true); + if (!new_objects.empty()) + m_part_selection = PartSelection(new_objects.front(), instance_idx); + } } else { reset_cut_by_contours(); @@ -2989,10 +2994,10 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) else if (mode == CutMode::cutTongueAndGroove) { ImGui::Separator(); ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": "); - render_groove_float_input(m_labels_map["Depth"], m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance); - render_groove_float_input(m_labels_map["Width"], m_groove_width, m_groove_width_init, m_groove_width_tolerance); - render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove_flaps_angle, m_groove_flaps_angle_init, 30.f, 120.f); - render_groove_angle_input(m_labels_map["Groove Angle"], m_groove_angle, m_groove_angle_init, 0.f, 15.f); + render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); + render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); + render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f); + render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f); // m_imgui->checkbox(_L("Optimize rendering"), m_optimaze_groove_rendering); } @@ -3445,8 +3450,8 @@ bool GLGizmoCut3D::has_valid_groove() const if (CutMode(m_mode) != CutMode::cutTongueAndGroove) return true; - const float flaps_width = -2.f * m_groove_depth / tan(m_groove_flaps_angle); - if (flaps_width > m_groove_width) + const float flaps_width = -2.f * m_groove.depth / tan(m_groove.flaps_angle); + if (flaps_width > m_groove.width) return false; const Selection& selection = m_parent.get_selection(); @@ -3553,280 +3558,6 @@ void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id) obj->cut_id.copy(cut_id); } -void distribute_modifiers_from_object(ModelObject *from_obj, const int instance_idx, ModelObject *to_obj1, ModelObject *to_obj2) -{ - auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3(); - auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3(); - const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix(); - - for (ModelVolume*vol : from_obj->volumes) - if (!vol->is_model_part()) { - auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); - // Don't add modifiers which are not intersecting with solid parts - if (obj1_bb.intersects(bb)) - to_obj1->add_volume(*vol); - if (obj2_bb.intersects(bb)) - to_obj2->add_volume(*vol); - } -} - -void merge_solid_parts_inside_object(ModelObjectPtrs& objects) -{ - for (ModelObject* mo : objects) { - TriangleMesh mesh; - // Merge all SolidPart but not Connectors - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part() && !mv->is_cut_connector()) { - TriangleMesh m = mv->mesh(); - m.transform(mv->get_matrix()); - mesh.merge(m); - } - } - if (!mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(mesh); - new_volume->name = mo->name; - // Delete all merged SolidPart but not Connectors - for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { - const ModelVolume* mv = mo->volumes[i]; - if (mv->is_model_part() && !mv->is_cut_connector()) - mo->delete_volume(i); - } - } - } -} - -ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count) -{ - const Selection& selection = m_parent.get_selection(); - const int instance_idx = selection.get_instance_idx(); - - // Clone the object to duplicate instances, materials etc. - ModelObject* upper{ nullptr }; - if (m_keep_upper) cut_mo->clone_for_cut(&upper); - ModelObject* lower{ nullptr }; - if (m_keep_lower) cut_mo->clone_for_cut(&lower); - - const Transform3d cut_matrix = get_cut_matrix(selection); - - auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) { - if (upper && !upper->volumes.empty()) { - ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); - cut_objects.push_back(upper); - } - if (lower && !lower->volumes.empty()) { - ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower); - cut_objects.push_back(lower); - } - }; - - const size_t cut_parts_cnt = m_part_selection.parts().size(); - bool has_modifiers = false; - - // Distribute SolidParts to the Upper/Lower object - for (size_t id = 0; id < cut_parts_cnt; ++id) { - if (m_part_selection.parts()[id].is_modifier) - has_modifiers = true; // modifiers will be added later to the related parts - else if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower)) - obj->add_volume(*(cut_mo->volumes[id])); - } - - if (has_modifiers) { - // Distribute Modifiers to the Upper/Lower object - distribute_modifiers_from_object(cut_mo, instance_idx, upper, lower); - } - - ModelObjectPtrs cut_object_ptrs; - - ModelVolumePtrs& volumes = cut_mo->volumes; - if (volumes.size() == cut_parts_cnt) { - // Means that object is cut without connectors - - // Just add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); - } - else if (volumes.size() > cut_parts_cnt) { - // Means that object is cut with connectors - - // All volumes are distributed to Upper / Lower object, - // So we don’t need them anymore - for (size_t id = 0; id < cut_parts_cnt; id++) - delete *(volumes.begin() + id); - volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); - - // Perform cut just to get connectors - const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); - assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); - - // Connectors from upper object - for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) - upper->add_volume(*volume, volume->type()); - - // Connectors from lower object - for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) - lower->add_volume(*volume, volume->type()); - - // Add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); - - // Add Dowel-connectors as separate objects to cut_object_ptrs - if (cut_connectors_obj.size() >= 3) - for (size_t id = 2; id < cut_connectors_obj.size(); id++) - cut_object_ptrs.push_back(cut_connectors_obj[id]); - } - - // Now merge all model parts together: - merge_solid_parts_inside_object(cut_object_ptrs); - - return cut_object_ptrs; -} - -ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts/* = false*/) -{ - if (!has_valid_groove()) - return {}; - - const Selection& selection = m_parent.get_selection(); - const int instance_idx = selection.get_instance_idx(); - - // Clone the object to duplicate instances, materials etc. - ModelObject* upper{ nullptr }; - cut_mo->clone_for_cut(&upper); - ModelObject* lower{ nullptr }; - cut_mo->clone_for_cut(&lower); - - const double groove_half_depth = 0.5 * double(m_groove_depth); - - const Transform3d cut_matrix = get_cut_matrix(selection); - - Model tmp_model_for_cut = Model(); - - Model tmp_model = Model(); - tmp_model.add_object(*cut_mo); - ModelObject* tmp_object = tmp_model.objects.front(); - - auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { - const auto& volumes = tmp_model_for_cut.objects.front()->volumes; - for (const ModelVolume* volume : volumes) - if (volume->is_model_part()) { - if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || - (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper()) ) { - ModelVolume* new_vol = object->add_volume(*volume); - new_vol->reset_from_upper(); - } - } - }; - - auto cut = [instance_idx, add_volumes_from_cut] - (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) { - Model model = Model(); - model.add_object(*object); - - tmp_model_for_cut = Model(); - tmp_model_for_cut.objects = model.objects.front()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts); - assert(!tmp_model_for_cut.objects.empty()); - - object->clear_volumes(); - add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); - ModelObject::reset_instance_transformation(object, instance_idx); - }; - - // cut by upper plane - - const Transform3d cut_matrix_upper = translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cut_matrix; - { - cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // cut by lower plane - - const Transform3d cut_matrix_lower = translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cut_matrix; - { - cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - } - - // cut middle part with 2 angles and add parts to related upper/lower objects - - const double h_side_shift = 0.5 * double(m_groove_width + m_groove_depth / tan(m_groove_flaps_angle)); - - // cut by angle1 plane - { - const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); - - cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // cut by angle2 plane - { - const Transform3d cut_matrix_angle2 = translation_transform(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); - - cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // apply tolerance to the middle part - { - const double h_groove_shift_tolerance = groove_half_depth - (double)m_groove_depth_tolerance; - - const Transform3d cut_matrix_lower_tolerance = translation_transform(m_rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * cut_matrix; - cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - - const double h_side_shift_tolerance = h_side_shift - 0.5 * double(m_groove_width_tolerance); - - const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle)); - cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - - const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_groove_angle)); - cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // this part can be added to the upper object now - add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - - ModelObjectPtrs cut_object_ptrs; - - if (keep_as_parts) { - // add volumes from lower object to the upper, but mark them as a lower - const auto& volumes = lower->volumes; - for (const ModelVolume* volume : volumes) { - ModelVolume* new_vol = upper->add_volume(*volume); - new_vol->cut_info.is_from_upper = false; - } - - // add modifiers - for (const ModelVolume* volume : cut_mo->volumes) - if (!volume->is_model_part()) - upper->add_volume(*volume); - - cut_object_ptrs.push_back(upper); - - // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks - cut_object_ptrs.push_back(lower); - } - else { - // add modifiers if object has any - for (const ModelVolume* volume : cut_mo->volumes) - if (!volume->is_model_part()) { - distribute_modifiers_from_object(cut_mo, instance_idx, upper, lower); - break; - } - - assert(!upper->volumes.empty() && !lower->volumes.empty()); - - ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix_upper, m_place_on_cut_upper, m_rotate_upper); - cut_object_ptrs.push_back(upper); - ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix_lower, m_place_on_cut_upper, m_rotate_upper); - cut_object_ptrs.push_back(lower); - - // Now merge all model parts together: - merge_solid_parts_inside_object(cut_object_ptrs); - } - - return cut_object_ptrs; -} - void GLGizmoCut3D::perform_cut(const Selection& selection) { if (!can_perform_cut()) @@ -3867,8 +3598,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) wxBusyCursor wait; - const Transform3d cut_matrix = get_cut_matrix(selection); - ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) | @@ -3882,16 +3611,15 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); - Model tmp_model = Model(); - tmp_model.objects = cut_by_contour ? perform_cut_by_contour(cut_mo, attributes, dowels_count) : - cut_with_groove ? perform_cut_with_groove(cut_mo) : - cut_mo->cut(instance_idx, cut_matrix, attributes); - + Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes); + const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection.get_cut_parts(), dowels_count): + cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) : + cut.perform_with_plane(); // save cut_id to post update synchronization const CutObjectBase cut_id = cut_mo->cut_id; // update cut results on plater and in the model - plater->cut(object_idx, tmp_model.objects); + plater->apply_cut_object_to_model(object_idx, new_objects); synchronize_model_after_cut(plater->model(), cut_id); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 11ac1ee7a8..34ba6e9ab1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -7,6 +7,7 @@ #include "slic3r/GUI/I18N.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/CutUtils.hpp" #include "imgui/imgui.h" namespace Slic3r { @@ -118,16 +119,7 @@ class GLGizmoCut3D : public GLGizmoBase bool m_rotate_lower{ false }; // Input params for cut with tongue and groove - float m_groove_depth; - float m_groove_width; - float m_groove_flaps_angle; - float m_groove_angle; - float m_groove_depth_init; - float m_groove_width_init; - float m_groove_flaps_angle_init; - float m_groove_angle_init; - float m_groove_depth_tolerance{ 0.1f }; - float m_groove_width_tolerance{ 0.1f }; + Cut::Groove m_groove; bool m_optimaze_groove_rendering{ true }; // Input params for cut with snaps @@ -187,6 +179,8 @@ class GLGizmoCut3D : public GLGizmoBase const std::vector& parts() const { return m_parts; } const std::vector* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); } + std::vector get_cut_parts(); + private: Model m_model; int m_instance_idx; @@ -367,8 +361,6 @@ private: void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef = 1.0); void render_cut_plane_grabbers(); void render_cut_line(); - ModelObjectPtrs perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count); - ModelObjectPtrs perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts = false); void perform_cut(const Selection&selection); void set_center_pos(const Vec3d¢er_pos, bool update_tbb = false); void update_bb(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fcd97cdc15..fb8cfb99e7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6318,20 +6318,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, 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]; - - wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - - wxBusyCursor wait; - - const auto new_objects = object->cut(instance_idx, cut_matrix, attributes); - cut(obj_idx, new_objects); -} - -void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects) +void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects) { 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 8eeabae428..bed65d7d13 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -264,8 +264,7 @@ public: void convert_unit(ConversionType conv_type); void toggle_layers_editing(bool enable); - void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes); - void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); + void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); void export_gcode(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); From 91fae9a154a9bf37cde44f95dff0bc818a19f061 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 9 Aug 2023 13:48:38 +0200 Subject: [PATCH 030/120] CutGizmo: Code cleaning --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 529 +++++---------------------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 10 - 2 files changed, 92 insertions(+), 447 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3dd4fe6edd..885b8582ad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -504,8 +504,8 @@ void GLGizmoCut3D::switch_to_mode(size_t new_mode) m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); } - if (m_use_TAG_mesh) - update_plane_model(); + + update_plane_model(); reset_cut_by_contours(); } @@ -673,227 +673,6 @@ static double get_grabber_mean_size(const BoundingBoxf3& bb) return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.; } -indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane() -{ - const float ghw = 0.5f * m_groove.width; // groove half width - const float cpr = 1.5f * float(m_radius); // cut plane radius - - const float cpl = 1.5f * cpr; // cut plane length - const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height - - // We need two upward facing triangles - float x = 0.5f * cpr, y = 0.5f * cpl; - - const float proj = y * tan(m_groove.angle); - const float extension_x = ghw + proj; - - // upper cut plane is simple - - if (is_approx(ghw, proj)) { - - return { - { - // roof - {0,1,6}, {6,1,2}, {2,3,5}, {5,3,4}, - // sides - {0,7,8}, {0,8,1}, {1,8,9}, {1,9,2}, {2,9,10}, {2,10,3}, {3,10,11}, {3,11,4}, - {4,11,12}, {4,12,5}, {5, 12,13}, {5,13,6}, {6,13,7}, {6, 7,0}, - // bottom - {7,13,8}, {8,13,9}, {10,9,12}, {10,12,4} - }, - { - // roof vertices - {-x, -y, cph}, {-extension_x, -y, cph}, {0.f, y, cph}, {extension_x, -y, cph}, {x, -y, cph}, {x, y, cph}, {-x, y, cph}, - // bottom vertices - {-x, -y, 0.f}, {-extension_x, -y, 0.f}, {0.f, y, 0.f}, {extension_x, -y, 0.f}, {x, -y, 0.f}, {x, y, 0.f}, {-x, y, 0.f}, - } - }; - } - - if (ghw < proj) { - - const float cross_pt_y = ghw / tan(m_groove.angle); - return { - { - // roof - {0,1,6}, {1,2,6}, {2,5,6}, {2,3,5}, {3,4,5}, - // sides - {0,7,8}, {0,8,1}, {1,8,9}, {1,9,2}, {2,9,10}, {2,10,3}, {3,10,11}, {3,11,4}, - {4,11,12}, {4,12,5}, {5,12,13}, {5,13,6}, {6,13,7}, {6,7,0}, - // bottom - {7,13,8}, {8,13,9}, {9,13,12}, {9,12,10}, {10,12, 11} - }, - { - // roof vertices - {-x, -y, cph}, {-extension_x, -y, cph}, {0.f, cross_pt_y, cph}, {extension_x, -y, cph}, {x, -y, cph}, {x, y, cph}, {-x, y, cph}, - // bottom vertices - {-x, -y, 0.f}, {-extension_x, -y, 0.f}, {0.f, cross_pt_y, 0.f}, {extension_x, -y, 0.f}, {x, -y, 0.f}, {x, y, 0.f}, {-x, y, 0.f} - } - }; - } - - // upper cut plane contains 2 sub planes - - const float narrowing_x = ghw - proj; - return { - { - // roof - {0,1,3}, {3,1,2}, {7,4,6}, {6,4,5}, - // sides - {0,8,9}, {0,9,1}, {1,9,10}, {1,10,2}, {2,10,11}, {2,11,3}, {3,11,8}, {3,8,0}, - {4,12,13}, {4,13,5}, {5,13,14}, {5,14,6}, {6,14,15}, {6,15,7}, {7,15,12}, {7,12,4}, - // bottom - {9,8,11}, {9,11,10}, {12,15,14}, {12,14,13} - }, - { - // roof vertices - {-x, -y, cph}, {-extension_x, -y, cph}, {-narrowing_x, y, cph}, {-x, y, cph}, {extension_x, -y, cph}, {x, -y, cph}, {x, y, cph}, {narrowing_x, y, cph}, - // bottom vertices - {-x, -y, 0.f}, {-extension_x, -y, 0.f}, {-narrowing_x, y, 0.f}, {-x, y, 0.f}, {extension_x, -y, 0.f}, {x, -y, 0.f}, {x, y, 0.f}, {narrowing_x, y, 0.f}, - } - }; -} - -indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width) -{ - const float ghw = 0.5f * (m_groove.width + flaps_width); // groove half width - const float cpr = 1.5f * float(m_radius); // cut plane radius - - const float cpl = 1.5f * cpr; // cut plane length - const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height - - // We need two upward facing triangles - float x = 0.5f * cpr, y = 0.5f * cpl; - - const float proj = y * tan(m_groove.angle); - const float extension_x = ghw + proj; - - // upper cut plane is trapezium - - if (ghw > proj) { - - const float narrowing_x = ghw - proj; - return { - { - // roof - {0,3,1}, {1,3,2}, - // sides - {0,4,7}, {0,7,3}, {3,7,6}, {3,6,2}, {2,6,5}, {2,5,1}, {1,5,4}, {1,4,0}, - // bottom - {4,5,7}, {7,5,6} - }, - { - // roof vertices - {-extension_x, -y, cph}, {-narrowing_x, y, cph}, {narrowing_x, y, cph}, {extension_x, -y, cph}, - // bottom vertices - {-extension_x, -y, 0.f}, {-narrowing_x, y, 0.f}, {narrowing_x, y, 0.f}, {extension_x, -y, 0.f} - } - }; - } - - // upper cut plane is triangle - - const float cross_pt_y = ghw / tan(m_groove.angle); - return { - { - // roof - {0,2,1}, - // sides - {0,3,5}, {0,5,2}, {2,5,4}, {2,4,1}, {1,4,3}, {0,1,3}, - // bottom - {3,4,5} - }, - { - // roof vertices - {-extension_x, -y, cph}, {0.f, cross_pt_y, cph}, {extension_x, -y, cph}, - // bottom vertices - {-extension_x, -y, 0.f}, {0.f, cross_pt_y, 0.f}, {extension_x, -y, 0.f} - } - }; -} - -indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width) -{ - const float ghw_upper = 0.5f * (m_groove.width); // groove half width - const float ghw_lower = 0.5f * (m_groove.width + flaps_width); // groove half width - const float cpr = 1.5f * float(m_radius); // cut plane radius - - const float cpl = 1.5f * cpr; // cut plane length - const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height - - const float ghd = 0.5f * m_groove.depth; // groove half depth - - - // We need two upward facing triangles - float x = 0.5f * cpr, y = 0.5f * cpl; - float z_upper = ghd; - float z_lower = -ghd; - - const float proj = y * tan(m_groove.angle); - - const float extension_upper_x = ghw_upper + proj; - const float extension_lower_x = ghw_lower + proj; - - const float narrowing_upper_x = ghw_upper - proj; - const float narrowing_lower_x = ghw_lower - proj; - - // groove is open - - if (ghw_upper > proj && ghw_lower > proj) { - return { - { - {1,0,2}, {2,0,3}, {5,4,7}, {5,7,6}, - {0,1,2}, {3,0,2}, {4,5,7}, {7,5,6} - }, - { - // left vertices - {-extension_lower_x, -y, z_lower}, {-extension_upper_x, -y, z_upper}, {-narrowing_upper_x, y, z_upper}, {-narrowing_lower_x, y, z_lower}, - // right vertices - {narrowing_lower_x, y, z_lower}, {narrowing_upper_x, y, z_upper}, {extension_upper_x, -y, z_upper}, {extension_lower_x, -y, z_lower} - } - }; - } - - const float cross_pt_upper_y = ghw_upper / tan(m_groove.angle); - - // groove is closed - - if (ghw_upper < proj && ghw_lower < proj) { - const float cross_pt_lower_y = ghw_lower / tan(m_groove.angle); - - return { - { - {1,0,3}, {1,3,4}, {1,4,5}, {1,5,2}, - {0,1,3}, {3,1,4}, {4,1,5}, {5,1,2} - }, - { - // roof vertices - {-extension_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {extension_upper_x, -y, z_upper}, - // bottom vertices - {-extension_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {extension_lower_x, -y, z_lower} - } - }; - - } - - // groove is closed from the roof - - return { - { - {1,0,3}, {1,3,4}, {1,5,6}, {1,6,2}, - {0,1,3}, {3,1,4}, {5,1,6}, {6,1,2} - }, - { - // roof vertices - {-extension_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {extension_upper_x, -y, z_upper}, - // bottom vertices - {-extension_lower_x, -y, z_lower}, {-narrowing_lower_x, y, z_lower}, {narrowing_lower_x, y, z_lower}, {extension_lower_x, -y, z_lower} - } - }; -} - - - indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() { // values for calculation @@ -965,57 +744,39 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() z_upper -= cut_plane_thiknes; z_lower -= cut_plane_thiknes; - if (m_use_TAG_mesh_full) { - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); - nar_upper_x += under_x_shift; - nar_lower_x += under_x_shift; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); - std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + nar_upper_x += under_x_shift; + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; - mesh.indices = { - // above view - {5,4,7}, {5,7,6}, // lower part - {3,4,5}, {3,5,2}, // left side - {9,6,8}, {8,6,7}, // right side - {1,0,2}, {2,0,3}, // upper left part - {9,8,10}, {10,8,11}, // upper right part - // under view - {20,21,22}, {20,22,23}, // upper right part - {12,13,14}, {12,14,15}, // upper left part - {18,21,20}, {18,20,19}, // right side - {16,15,14}, {16,14,17}, // left side - {16,17,18}, {16,18,19}, // lower part - // left edge - {1,13,0}, {0,13,12}, - // front edge - {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, - // right edge - {11,23,10}, {10,23,22}, - // back edge - {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} - }; - } - else { - std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); - mesh.indices = { - // above view - {5,4,7}, {5,7,6}, // lower part - {3,4,2}, {2,4,5}, // left side - {9,6,8}, {8,6,7}, // right side - {1,0,2}, {2,0,3}, // upper left part - {9,8,10}, {10,8,11}, // upper right part - // under view - {16,15,14}, {16,14,17}, // left side - {18,21,20}, {18,20,19}, // right side - {16,17,18}, {16,18,19} // lower part - }; - } + mesh.indices = { + // above view + {5,4,7}, {5,7,6}, // lower part + {3,4,5}, {3,5,2}, // left side + {9,6,8}, {8,6,7}, // right side + {1,0,2}, {2,0,3}, // upper left part + {9,8,10}, {10,8,11}, // upper right part + // under view + {20,21,22}, {20,22,23}, // upper right part + {12,13,14}, {12,14,15}, // upper left part + {18,21,20}, {18,20,19}, // right side + {16,15,14}, {16,14,17}, // left side + {16,17,18}, {16,18,19}, // lower part + // left edge + {1,13,0}, {0,13,12}, + // front edge + {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, + // right edge + {11,23,10}, {10,23,22}, + // back edge + {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} + }; return mesh; } @@ -1044,56 +805,38 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() z_upper -= cut_plane_thiknes; z_lower -= cut_plane_thiknes; - if (m_use_TAG_mesh_full) { - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); - cross_pt_upper_y += cut_plane_thiknes; - cross_pt_lower_y += cut_plane_thiknes; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; + cross_pt_upper_y += cut_plane_thiknes; + cross_pt_lower_y += cut_plane_thiknes; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; - std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); - mesh.indices = { - // above view - {8,7,9}, // lower part - {5,8,6}, {6,8,7}, // left side - {4,9,8}, {4,8,5}, // right side - {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part - {18,15,14}, {14,18,19}, // right side - {17,16,15}, {17,15,18}, // left side - {17,18,19}, // lower part - // left edge - {1,11,0}, {0,11,10}, - // front edge - {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, - // right edge - {3,13,12}, {3,12,2}, - // back edge - {2,12,11}, {2,11,1} - }; - } - else { - std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + mesh.indices = { + // above view + {8,7,9}, // lower part + {5,8,6}, {6,8,7}, // left side + {4,9,8}, {4,8,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part + {18,15,14}, {14,18,19}, // right side + {17,16,15}, {17,15,18}, // left side + {17,18,19}, // lower part + // left edge + {1,11,0}, {0,11,10}, + // front edge + {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, + // right edge + {3,13,12}, {3,12,2}, + // back edge + {2,12,11}, {2,11,1} + }; - mesh.indices = { - // above view - {8,7,9}, // lower part - {5,8,6}, {6,8,7}, // left side - {4,9,8}, {4,8,5}, // right side - {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {18,15,14}, {14,18,19}, // right side - {17,16,15}, {17,15,18}, // left side - {17,18,19}, // lower part - }; - } return mesh; - } // groove is closed from the roof @@ -1127,89 +870,30 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() }; mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); - if (m_use_TAG_mesh_full) { - mesh.indices = { - // above view - {8,7,10}, {8,10,9}, // lower part - {5,8,7}, {5,7,6}, // left side - {4,10,9}, {4,9,5}, // right side - {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part - {21,16,15}, {21,15,22}, // right side - {19,18,17}, {19,17,20}, // left side - {19,20,21}, {19,21,22}, // lower part - // left edge - {1,12,11}, {1,11,0}, - // front edge - {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, - // right edge - {3,14,13}, {3,14,2}, - // back edge - {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} - }; - } - else { - mesh.indices = { - // above view - {8,7,10}, {8,10,9}, // lower part - {5,8,7}, {5,7,6}, // left side - {4,10,9}, {4,9,5}, // right side - {1,0,6}, {1,6,5}, {1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {21,16,15}, {21,15,22}, // right side - {19,18,17}, {19,17,20}, // left side - {19,20,21}, {19,21,22}, // lower part - }; - } + mesh.indices = { + // above view + {8,7,10}, {8,10,9}, // lower part + {5,8,7}, {5,7,6}, // left side + {4,10,9}, {4,9,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part + {21,16,15}, {21,15,22}, // right side + {19,18,17}, {19,17,20}, // left side + {19,20,21}, {19,21,22}, // lower part + // left edge + {1,12,11}, {1,11,0}, + // front edge + {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, + // right edge + {3,14,13}, {3,14,2}, + // back edge + {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} + }; return mesh; } -void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shader) -{ - const Camera & camera = wxGetApp().plater()->get_camera(); - const Transform3d cp_matrix = translation_transform(m_plane_center) * m_rotation_m; - ColorRGBA cp_clr = m_plane.model.get_color(); - - // values for calculaton - const double groove_half_depth = 0.5 * double(m_groove.depth); - - const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); - const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle); - - GLModel model; - - // upper cut_plane - - model.init_from(its_make_upper_groove_plane()); - model.set_color(cp_clr); - - Transform3d view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * cp_matrix; - shader->set_uniform("view_model_matrix", view_model_matrix_); - model.render(); - - // lower part of cut_plane - - model.reset(); - model.init_from(its_make_lower_groove_plane(flaps_width)); - cp_clr.a(cp_clr.a() + 0.1f); - model.set_color(cp_clr); - - view_model_matrix_ = camera.get_view_matrix() * translation_transform(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * cp_matrix; - shader->set_uniform("view_model_matrix", view_model_matrix_); - model.render(); - - // side parts of cut_plane - - model.reset(); - model.init_from(its_make_sides_groove_plane(flaps_width)); - - view_model_matrix_ = camera.get_view_matrix() * cp_matrix; - shader->set_uniform("view_model_matrix", view_model_matrix_); - model.render(); -} - void GLGizmoCut3D::render_cut_plane() { if (cut_line_processing()) @@ -1230,27 +914,15 @@ void GLGizmoCut3D::render_cut_plane() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - if (m_use_TAG_mesh) { - ColorRGBA cp_clr = can_perform_cut() && has_valid_groove() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR; - if (m_mode == size_t(CutMode::cutTongueAndGroove)) - cp_clr.a(cp_clr.a() - 0.1f); - m_plane.model.set_color(cp_clr); + ColorRGBA cp_clr = can_perform_cut() && has_valid_groove() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR; + if (m_mode == size_t(CutMode::cutTongueAndGroove)) + cp_clr.a(cp_clr.a() - 0.1f); + m_plane.model.set_color(cp_clr); - 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); - m_plane.model.render(); - } - else { - m_plane.model.set_color(can_perform_cut() && has_valid_contour() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR); + 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); + m_plane.model.render(); - if (m_mode == size_t(CutMode::cutPlanar)) { - 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); - m_plane.model.render(); - } - else if (m_mode == size_t(CutMode::cutTongueAndGroove)) - render_cut_plate_for_tongue_and_groove(shader); - } glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); @@ -1981,7 +1653,7 @@ void GLGizmoCut3D::on_stop_dragging() m_ar_plane_center = m_plane_center; } - if (CutMode(m_mode) == CutMode::cutTongueAndGroove && m_optimaze_groove_rendering) + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) reset_cut_by_contours(); //check_and_update_connectors_state(); } @@ -2136,12 +1808,8 @@ void GLGizmoCut3D::init_picking_models() if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box); - indexed_triangle_set its; - if (m_use_TAG_mesh) - its = m_mode == size_t(CutMode::cutTongueAndGroove) ? its_make_groove_plane() : - its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); - else - its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); + indexed_triangle_set its = m_mode == size_t(CutMode::cutTongueAndGroove) ? its_make_groove_plane() : + its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); m_plane.model.init_from(its); m_plane.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); @@ -2476,20 +2144,10 @@ void GLGizmoCut3D::on_render() void GLGizmoCut3D::render_debug_input_window(float x) { + return; m_imgui->begin(wxString("DEBUG")); - ImVec2 pos = ImGui::GetWindowPos(); - pos.x = x; - ImGui::SetWindowPos(pos, ImGuiCond_Always); - - bool is_changed = m_imgui->checkbox(("Render Cut plane as a one mesh"), m_use_TAG_mesh); - is_changed |= m_imgui->checkbox(("Render Cut plane as a full mesh"), m_use_TAG_mesh_full); - - if (is_changed) - update_plane_model(); - m_imgui->end(); - return; /* static bool hide_clipped = false; static bool fill_cut = false; @@ -2778,7 +2436,7 @@ void GLGizmoCut3D::reset_cut_by_contours() m_part_selection = PartSelection(); if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - if (m_optimaze_groove_rendering && m_dragging || !has_valid_groove()) + if (m_dragging || !has_valid_groove()) return; process_contours(); } @@ -2885,8 +2543,7 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in m_imgui->disabled_end(); if (is_changed) { - if (m_use_TAG_mesh) - update_plane_model(); + update_plane_model(); reset_cut_by_contours(); } } @@ -2927,8 +2584,7 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in m_imgui->disabled_end(); if (is_changed) { - if (m_use_TAG_mesh) - update_plane_model(); + update_plane_model(); reset_cut_by_contours(); } } @@ -2998,7 +2654,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f); render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f); -// m_imgui->checkbox(_L("Optimize rendering"), m_optimaze_groove_rendering); } ImGui::Separator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 34ba6e9ab1..060c9f1d0b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -120,7 +120,6 @@ class GLGizmoCut3D : public GLGizmoBase // Input params for cut with tongue and groove Cut::Groove m_groove; - bool m_optimaze_groove_rendering{ true }; // Input params for cut with snaps float m_snap_bulge_proportion{ 0.15f }; @@ -232,11 +231,6 @@ class GLGizmoCut3D : public GLGizmoBase std::map m_labels_map; - // Debug values - bool m_use_TAG_mesh {true}; - bool m_use_TAG_mesh_full {true}; - // - public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -353,7 +347,6 @@ private: void discard_cut_line_processing(); void apply_color_clip_plane_colors(); - void render_cut_plate_for_tongue_and_groove(GLShaderProgram* shader); void render_cut_plane(); static 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); @@ -377,9 +370,6 @@ private: void toggle_model_objects_visibility(); - indexed_triangle_set its_make_upper_groove_plane(); - indexed_triangle_set its_make_lower_groove_plane(float flaps_width); - indexed_triangle_set its_make_sides_groove_plane(float flaps_width); indexed_triangle_set its_make_groove_plane(); indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); From 6b6f7bc309c80a4c30da6ec1c4d29c5c43919f49 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 9 Aug 2023 15:16:35 +0200 Subject: [PATCH 031/120] CutGizmo: Improvements for TaG mode. Don't recalculate cut during editing a groove parameters. --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 18 +++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 885b8582ad..a0864094f4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -574,6 +574,8 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v m_imgui->slider_float(label.c_str(), &value, min_val, max_val, format.c_str(), 1.f, true, tooltip); val = value * (m_imperial_units ? static_cast(ObjectManipulation::in_to_mm) : 1.f); + m_is_slider_editing_done |= m_imgui->get_last_slider_status().deactivated_after_edit; + return !is_approx(old_val, value); }; @@ -2436,7 +2438,7 @@ void GLGizmoCut3D::reset_cut_by_contours() m_part_selection = PartSelection(); if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { - if (m_dragging || !has_valid_groove()) + if (m_dragging || m_groove_editing || !has_valid_groove()) return; process_contours(); } @@ -2524,6 +2526,7 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in if (m_imgui->get_last_slider_status().can_take_snapshot) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); m_imgui->get_last_slider_status().invalidate_snapshot(); + m_groove_editing = true; } in_val = val; in_tolerance = tolerance; @@ -2546,6 +2549,11 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in update_plane_model(); reset_cut_by_contours(); } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } } void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val) @@ -2563,10 +2571,12 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in const std::string format = "%.0f " + _u8L("°"); m_imgui->slider_float(("##groove_" + label).c_str(), &val, min_val, max_val, format.c_str(), 1.f, true, from_u8(label)); + m_is_slider_editing_done |= m_imgui->get_last_slider_status().deactivated_after_edit; if (!is_approx(old_val, val)) { if (m_imgui->get_last_slider_status().can_take_snapshot) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); m_imgui->get_last_slider_status().invalidate_snapshot(); + m_groove_editing = true; } in_val = deg2rad(val); is_changed = true; @@ -2587,6 +2597,11 @@ void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in update_plane_model(); reset_cut_by_contours(); } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } } @@ -2648,6 +2663,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) m_imgui->disabled_end(); } else if (mode == CutMode::cutTongueAndGroove) { + m_is_slider_editing_done = false; ImGui::Separator(); ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": "); render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 060c9f1d0b..4409c1dbbd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -120,6 +120,9 @@ class GLGizmoCut3D : public GLGizmoBase // Input params for cut with tongue and groove Cut::Groove m_groove; + bool m_groove_editing { false }; + + bool m_is_slider_editing_done { false }; // Input params for cut with snaps float m_snap_bulge_proportion{ 0.15f }; From 5852c60b80a657820a3d9418a383050f760336e0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 9 Aug 2023 17:26:27 +0200 Subject: [PATCH 032/120] CutGizmo: Fixed a rendering of the sliced object in SLA mode --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 26 +++++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 ++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index a0864094f4..49917d09f8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1855,14 +1855,25 @@ void GLGizmoCut3D::render_clipper_cut() ::glEnable(GL_DEPTH_TEST); } +void GLGizmoCut3D::PartSelection::add_object(const ModelObject* object) +{ + m_model = Model(); + m_model.add_object(*object); + + const double sla_shift_z = wxGetApp().plater()->canvas3D()->get_selection().get_first_volume()->get_sla_shift_z(); + if (!is_approx(sla_shift_z, 0.)) { + Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset(); + inst_offset[Z] += sla_shift_z; + model_object()->instances[m_instance_idx]->set_offset(inst_offset); + } +} + GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) + : m_instance_idx(instance_idx_in) { Cut cut(mo, instance_idx_in, cut_matrix); - m_model = Model(); - m_model.add_object(*cut.perform_with_plane().front()); - - m_instance_idx = instance_idx_in; + add_object(cut.perform_with_plane().front()); const ModelVolumePtrs& volumes = model_object()->volumes; @@ -1935,12 +1946,9 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor // In CutMode::cutTongueAndGroove we use PartSelection just for rendering GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* object, int instance_idx_in) + : m_instance_idx (instance_idx_in) { - m_instance_idx = instance_idx_in; - - m_model = Model(); - // add upper object - m_model.add_object(*object); + add_object(object); m_parts.clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 4409c1dbbd..aa8ed04dd9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -193,6 +193,8 @@ class GLGizmoCut3D : public GLGizmoBase std::vector m_contour_points; // Debugging std::vector> m_debug_pts; // Debugging + + void add_object(const ModelObject* object); }; PartSelection m_part_selection; From 88dbb2db6c6a48a449bb13b5425431f28eee8e0c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Aug 2023 10:43:45 +0200 Subject: [PATCH 033/120] Fix for SPE-1835 : Scaling one part of an object after cutting with connectors, when open Scale gizmo with "S"-shortcut. --- src/slic3r/GUI/GLCanvas3D.cpp | 5 ----- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 6 ++++++ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ad42dd2738..4ae60fb1c0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3592,11 +3592,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) update_sequential_clearance(true); } } - 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(); - } return; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1a7251ddf7..6cc852c6a6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -99,6 +99,12 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable) m_grabbers[i].enabled = enable; } +void GLGizmoScale3D::on_set_state() +{ + if (m_state == On) + wxGetApp().obj_list()->selection_changed(); +} + void GLGizmoScale3D::data_changed(bool is_serializing) { set_scale(Vec3d::Ones()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 6a7b4a331e..6e304061ab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -74,6 +74,8 @@ protected: virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; + void on_set_state() override; + private: void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color); From 6215393ae5094df1ce46b2ada5119d12bfbe7c12 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Aug 2023 11:02:26 +0200 Subject: [PATCH 034/120] Fix for SPE-1836 : CUT_rotate circle isn't updated after undo --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 885f9d97a2..36576e9494 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -835,6 +835,8 @@ 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_ar_plane_center, m_rotation_m); + m_start_dragging_m = m_rotation_m; + m_transformed_bounding_box = transformed_bounding_box(m_ar_plane_center, m_rotation_m); set_center_pos(m_ar_plane_center); From 6cd6ee2f189ff36e0e2a524e369688fbe75e41c6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Aug 2023 12:06:42 +0200 Subject: [PATCH 035/120] Cut: TaG mode: Fixed post-processing for the cut objects --- src/libslic3r/CutUtils.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 7d107b1d05..c5e186823f 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -608,10 +608,21 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran assert(!upper->volumes.empty() && !lower->volumes.empty()); - reset_instance_transformation(upper, m_instance, cut_matrix_upper, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); - cut_object_ptrs.push_back(upper); - reset_instance_transformation(lower, m_instance, cut_matrix_lower, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); - cut_object_ptrs.push_back(lower); + // check which parts have to stay/be deleted + + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) { + reset_instance_transformation(upper, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + cut_object_ptrs.push_back(upper); + } + else + m_model.objects.push_back(upper); // will be deleted in m_model.clear_objects(); + + if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) { + reset_instance_transformation(lower, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); + cut_object_ptrs.push_back(lower); + } + else + m_model.objects.push_back(lower); // will be deleted in m_model.clear_objects(); // Now merge all model parts together: merge_solid_parts_inside_object(cut_object_ptrs); From 1d69dd82f8c590fa307dcd3f8b77e0d3f8611815 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Aug 2023 13:02:19 +0200 Subject: [PATCH 036/120] CutGizmo: Code refactoring to extract post process procedures over the Upper/Lower objects into separate function --- src/libslic3r/CutUtils.cpp | 96 +++++++++++++++++++------------------- src/libslic3r/CutUtils.hpp | 4 ++ 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index c5e186823f..ba2c3452af 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -243,6 +243,42 @@ Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, m_model.add_object(*object); } +void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip) +{ + if (!object) return; + + if (keep && !object->volumes.empty()) { + reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip); + cut_object_ptrs.push_back(object); + } + else + m_model.objects.push_back(object); // will be deleted in m_model.clear_objects(); +} + +void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs) +{ + post_process(upper, cut_object_ptrs, + m_attributes.has(ModelObjectCutAttribute::KeepUpper), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), + m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + + post_process(lower, cut_object_ptrs, + m_attributes.has(ModelObjectCutAttribute::KeepLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); +} + + +void Cut::finalize(const ModelObjectPtrs& objects) +{ + //clear model from temporarry objects + m_model.clear_objects(); + + // add to model result objects + m_model.objects = objects; +} + + const ModelObjectPtrs& Cut::perform_with_plane() { if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) { @@ -317,25 +353,13 @@ const ModelObjectPtrs& Cut::perform_with_plane() } }; - if (m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) { - delete_extra_modifiers(upper); - reset_instance_transformation(upper, m_instance, m_cut_matrix, - m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), - m_attributes.has(ModelObjectCutAttribute::FlipUpper)); - cut_object_ptrs.push_back(upper); - } - - if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) { - delete_extra_modifiers(lower); - reset_instance_transformation(lower, m_instance, m_cut_matrix, - m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), - m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); - cut_object_ptrs.push_back(lower); - } + post_process(upper, lower, cut_object_ptrs); + delete_extra_modifiers(upper); + delete_extra_modifiers(lower); if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { for (auto dowel : dowels) { - reset_instance_transformation(dowel, m_instance, Transform3d::Identity()); + reset_instance_transformation(dowel, m_instance); dowel->name += "-Dowel-" + dowel->volumes[0]->name; cut_object_ptrs.push_back(dowel); } @@ -344,8 +368,7 @@ const ModelObjectPtrs& Cut::perform_with_plane() BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; - m_model.clear_objects(); - m_model.objects = cut_object_ptrs; + finalize(cut_object_ptrs); return m_model.objects; } @@ -403,17 +426,6 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe ModelObject* lower{ nullptr }; if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower); - auto add_cut_objects = [this](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) { - if (upper && !upper->volumes.empty()) { - reset_instance_transformation(upper, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); - cut_objects.push_back(upper); - } - if (lower && !lower->volumes.empty()) { - reset_instance_transformation(lower, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); - cut_objects.push_back(lower); - } - }; - const size_t cut_parts_cnt = parts.size(); bool has_modifiers = false; @@ -437,7 +449,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe // Means that object is cut without connectors // Just add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); + post_process(upper, lower, cut_object_ptrs); } else if (volumes.size() > cut_parts_cnt) { // Means that object is cut with connectors @@ -462,7 +474,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe lower->add_volume(*volume, volume->type()); // Add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); + post_process(upper, lower, cut_object_ptrs); // Add Dowel-connectors as separate objects to cut_object_ptrs if (cut_connectors_obj.size() >= 3) @@ -473,8 +485,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe // Now merge all model parts together: merge_solid_parts_inside_object(cut_object_ptrs); - m_model.clear_objects(); - m_model.objects = cut_object_ptrs; + finalize(cut_object_ptrs); return m_model.objects; } @@ -608,28 +619,15 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran assert(!upper->volumes.empty() && !lower->volumes.empty()); - // check which parts have to stay/be deleted + // Add Upper and Lower parts to cut_object_ptrs - if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) { - reset_instance_transformation(upper, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper)); - cut_object_ptrs.push_back(upper); - } - else - m_model.objects.push_back(upper); // will be deleted in m_model.clear_objects(); - - if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) { - reset_instance_transformation(lower, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); - cut_object_ptrs.push_back(lower); - } - else - m_model.objects.push_back(lower); // will be deleted in m_model.clear_objects(); + post_process(upper, lower, cut_object_ptrs); // Now merge all model parts together: merge_solid_parts_inside_object(cut_object_ptrs); } - m_model.clear_objects(); - m_model.objects = cut_object_ptrs; + finalize(cut_object_ptrs); return m_model.objects; } diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index a31404e898..7aa445160b 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -26,6 +26,10 @@ class Cut { const Transform3d& m_cut_matrix; ModelObjectCutAttributes m_attributes; + void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip); + void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects); + void finalize(const ModelObjectPtrs& objects); + public: Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, From e241e905ec6582cf45e3543173a007f7734b1cb1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Aug 2023 14:52:20 +0200 Subject: [PATCH 037/120] CutGizmo: Manually cherry-picked https://github.com/Prusa-Development/PrusaSlicerPrivate/commit/6dbbbfea7aa55c088c28fd55c58c02ab533bde48. This changed fixed cut for mirrored object --- src/libslic3r/CutUtils.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index ba2c3452af..09f4142dac 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -208,7 +208,12 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan auto& obj_instance = object->instances[i]; const double rot_z = obj_instance->get_rotation().z(); - obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor())); + Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()); + // add respect to mirroring + if (obj_instance->is_left_handed()) + inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1))); + + obj_instance->set_transformation(inst_trafo); Vec3d rotation = Vec3d::Zero(); if (!flip && !place_on_cut) { From 8a1a5d22596deb2fe1af06ed525ac93f28ed8fae Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Aug 2023 15:06:36 +0200 Subject: [PATCH 038/120] GalleryDialog: Fixed a memory leak --- src/slic3r/GUI/GalleryDialog.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index a08673e235..5ac51d54f7 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -134,7 +134,12 @@ GalleryDialog::GalleryDialog(wxWindow* parent) : } GalleryDialog::~GalleryDialog() -{ +{ + // From wxWidgets docs: + // The method void wxListCtrl::SetImageList(wxImageList* imageList, int which) + // does not take ownership of the image list, you have to delete it yourself. + if (m_image_list) + delete m_image_list; } int GalleryDialog::show(bool show_from_menu) From b213ff01d4f3dde7f9379ed2a2a6818665b25498 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Aug 2023 16:40:20 +0200 Subject: [PATCH 039/120] CutGizmo: Next bug-fixing: * Performance during drawing of the cut line. Recalculate cut only when Mouse button is up. * Reset button for Depth / Width modification + tolerance --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 49917d09f8..1749c9d2dc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2543,7 +2543,7 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in ImGui::SameLine(); - m_imgui->disabled_begin(is_approx(in_val, init_val)); + m_imgui->disabled_begin(is_approx(in_val, init_val) && is_approx(in_tolerance, 0.1f)); const std::string act_name = _u8L("Reset"); if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); @@ -3446,6 +3446,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse } if (cut_line_processing()) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + m_groove_editing = true; reset_cut_by_contours(); m_line_end = pt; @@ -3478,8 +3480,10 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse m_angle_arc.reset(); discard_cut_line_processing(); - if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + m_groove_editing = false; reset_cut_by_contours(); + } } else if (action == SLAGizmoEventType::Moving) this->set_dirty(); From 49ed94ff7799eb6722d8045b4858e015637e6b23 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 11 Aug 2023 10:09:31 +0200 Subject: [PATCH 040/120] CutGizmo: TaG mode: Fix of volume relation to upper/lower object when whole volume is over a cut plane --- src/libslic3r/CutUtils.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 09f4142dac..11c2d62d8c 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -186,8 +186,10 @@ static void process_solid_part_cut(ModelVolume* volume, const Transform3d& insta if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); - upper->volumes.back()->cut_info.is_from_upper = false; + if (!lower_mesh.empty()) { + add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + upper->volumes.back()->cut_info.is_from_upper = false; + } return; } From 840f1b0ea930dd0690039f334cd48dcc9d448a87 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 11 Aug 2023 13:05:50 +0200 Subject: [PATCH 041/120] Cut: Experiment: Fixed a variable declaration of m_cut_matrix, which caused an ASAN-crash --- src/libslic3r/CutUtils.cpp | 2 ++ src/libslic3r/CutUtils.hpp | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 11c2d62d8c..1622af87e1 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -1,9 +1,11 @@ #include "CutUtils.hpp" +#include "Geometry.hpp" #include "libslic3r.h" #include "Model.hpp" #include "TriangleMeshSlicer.hpp" #include "TriangleSelector.hpp" +#include "ObjectID.hpp" namespace Slic3r { diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index 7aa445160b..2c477a3e2b 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -1,10 +1,7 @@ #ifndef slic3r_CutUtils_hpp_ #define slic3r_CutUtils_hpp_ -#include "libslic3r.h" #include "enum_bitmask.hpp" -#include "Geometry.hpp" -#include "ObjectID.hpp" #include "Point.hpp" #include "Model.hpp" @@ -23,7 +20,7 @@ class Cut { Model m_model; int m_instance; - const Transform3d& m_cut_matrix; + const Transform3d m_cut_matrix; ModelObjectCutAttributes m_attributes; void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip); From 7f647c49f55cf4c5c873b9c1dab9bacbea3c3428 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 14 Aug 2023 08:14:07 +0200 Subject: [PATCH 042/120] Special case of mirror when emboss gizmo is opend --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 40 +++++++++++- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 80 ++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 9 +++ src/slic3r/GUI/TextLines.cpp | 16 +++-- 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 9ea6198f7a..24da753278 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -15,11 +15,44 @@ #include "MainFrame.hpp" #include "MsgDialog.hpp" +#include // Fix for fatal error C1189: #error: gl.h included before glew.h (compiling source file C:\git\slicer2\src\slic3r\GUI\GUI_ObjectManipulation.cpp) #include #include #include "slic3r/Utils/FixModelByWin10.hpp" +// For special mirroring in manipulation gizmo +#include "Gizmos/GLGizmosManager.hpp" +#include "Gizmos/GLGizmoEmboss.hpp" +namespace { +using namespace Slic3r::GUI; +bool is_emboss_mirror(size_t axis_idx) +{ + Plater* plater = wxGetApp().plater(); + if (!plater) + return false; + + GLCanvas3D *canvas = plater->canvas3D(); + if (!canvas) + return false; + + GLGizmosManager &manager = canvas->get_gizmos_manager(); + // is embossing + if (manager.get_current_type() != GLGizmosManager::Emboss) + return false; + + GLGizmoBase *gizmo = manager.get_current(); + if (!gizmo) + return false; + + GLGizmoEmboss *emboss = dynamic_cast(gizmo); + if (!emboss) + return false; + + return emboss->do_mirror(axis_idx); +} +} // namespace + namespace Slic3r { namespace GUI @@ -253,10 +286,12 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : sizer->AddStretchSpacer(2); sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL); - + btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) { + if (::is_emboss_mirror(axis_idx)) + return; + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); - Selection& selection = canvas->get_selection(); TransformationType transformation_type; if (is_local_coordinates()) transformation_type.set_local(); @@ -265,6 +300,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : transformation_type.set_relative(); + Selection& selection = canvas->get_selection(); selection.setup_cache(); selection.mirror((Axis)axis_idx, transformation_type); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 137b05b54e..6576cb273b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -360,6 +360,67 @@ void GLGizmoEmboss::on_shortcut_key() { } } +namespace{ +ModelVolumePtrs prepare_volumes_to_slice(const ModelVolume &mv) +{ + const ModelVolumePtrs &volumes = mv.get_object()->volumes; + ModelVolumePtrs result; + result.reserve(volumes.size()); + for (ModelVolume *volume : volumes) { + // only part could be surface for volumes + if (!volume->is_model_part()) + continue; + + // is selected volume + if (mv.id() == volume->id()) + continue; + + result.push_back(volume); + } + return result; +} +} + +bool GLGizmoEmboss::do_mirror(size_t axis) +{ + // is valid input + assert(axis < 3); + if (axis >= 3) + return false; + + // is gizmo opened and initialized? + assert(m_parent.get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss); + if (m_parent.get_gizmos_manager().get_current_type() != GLGizmosManager::Emboss) + return false; + + const TextConfiguration &tc= *m_volume->text_configuration; + if(tc.style.prop.per_glyph){ + // init textlines before mirroring on mirrored text volume transformation + Transform3d tr = m_volume->get_matrix(); + const std::optional &fix_tr = tc.fix_3mf_tr; + if (fix_tr.has_value()) + tr = tr * (fix_tr->inverse()); + + // mirror + Vec3d scale = Vec3d::Ones(); + scale[axis] = -1.; + tr = tr * Eigen::Scaling(scale); + + // collect volumes in object + ModelVolumePtrs volumes = prepare_volumes_to_slice(*m_volume); + m_text_lines.init(tr, volumes, m_style_manager, m_text_lines.get_lines().size()); + } + + // mirror + Transform3d tr = m_volume->get_matrix(); + Vec3d scale = Vec3d::Ones(); + scale[axis] = -1.; + tr = tr * Eigen::Scaling(scale); + m_volume->set_transformation(tr); + // NOTE: Staff around volume transformation change is done in job finish + return process(); +} + namespace{ // verify correct volume type for creation of text bool check(ModelVolumeType volume_type) { @@ -1101,11 +1162,6 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* return; const GLVolume &gl_volume = *gl_volume_ptr; const ModelObjectPtrs &objects = selection.get_model()->objects; - const ModelObject *mo_ptr = get_model_object(gl_volume, objects); - if (mo_ptr == nullptr) - return; - const ModelObject &mo = *mo_ptr; - const ModelVolume *mv_ptr = get_model_volume(gl_volume, objects); if (mv_ptr == nullptr) return; @@ -1126,19 +1182,7 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* } // prepare volumes to slice - ModelVolumePtrs volumes; - volumes.reserve(mo.volumes.size()); - for (ModelVolume *volume : mo.volumes) { - // only part could be surface for volumes - if (!volume->is_model_part()) - continue; - - // is selected volume - if (mv.id() == volume->id()) - continue; - - volumes.push_back(volume); - } + ModelVolumePtrs volumes = prepare_volumes_to_slice(mv); // For interactivity during drag over surface it must be from gl_volume not volume. Transform3d mv_trafo = gl_volume.get_volume_transformation().get_matrix(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index b86749c55e..63a344da5c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -52,6 +52,15 @@ public: /// Handle pressing of shortcut /// void on_shortcut_key(); + + /// + /// Mirroring from object manipulation panel + /// !! Emboss gizmo must be active + /// + /// Axis for mirroring must be one of {0,1,2} + /// True on success start job otherwise False + bool do_mirror(size_t axis); + protected: bool on_init() override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 018cb3537d..667354a417 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -218,7 +218,7 @@ indexed_triangle_set create_its(const TextLines &lines, float radius) return its; } -GLModel::Geometry create_geometry(const TextLines &lines, float radius) +GLModel::Geometry create_geometry(const TextLines &lines, float radius, bool is_mirrored) { indexed_triangle_set its = create_its(lines, radius); @@ -232,8 +232,15 @@ GLModel::Geometry create_geometry(const TextLines &lines, float radius) geometry.add_vertex(vertex); geometry.reserve_indices(its.indices.size() * 3); - for (Vec3i t : its.indices) - geometry.add_triangle(t[0], t[1], t[2]); + + if (is_mirrored) { + // change order of indices + for (Vec3i t : its.indices) + geometry.add_triangle(t[0], t[2], t[1]); + } else { + for (Vec3i t : its.indices) + geometry.add_triangle(t[0], t[1], t[2]); + } return geometry; } @@ -333,9 +340,10 @@ void TextLinesModel::init(const Transform3d &text_tr, for (size_t i = 0; i < count_lines; ++i) m_lines[i].y = line_centers[i]; + bool is_mirrored = has_reflection(text_tr); float radius = static_cast(line_height_mm / 20.); //* - GLModel::Geometry geometry = create_geometry(m_lines, radius); + GLModel::Geometry geometry = create_geometry(m_lines, radius, is_mirrored); if (geometry.vertices_count() == 0 || geometry.indices_count() == 0) return; m_model.init_from(std::move(geometry)); From f79f2557d6fe3208b765939172f0d8cc7524b5b9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 14 Aug 2023 12:19:14 +0200 Subject: [PATCH 043/120] Revert of 88dbb2db6c6a48a449bb13b5425431f28eee8e0c + Fix for SPE-1835 : Scaling one part of an object after cutting with connectors, when open Scale gizmo with "S"-shortcut. --- src/slic3r/GUI/GLCanvas3D.cpp | 16 +++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 6 ------ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 2 -- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e4923e8fbd..8bdc3bfa3a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2878,8 +2878,15 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) return; - if (m_gizmos.on_char(evt)) + if (m_gizmos.on_char(evt)) { + if (m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && + m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { + // Update selection from object list to check selection of the cut objects + // It's not allowed to scale separate ct parts + wxGetApp().obj_list()->selection_changed(); + } return; + } if ((evt.GetModifiers() & ctrlMask) != 0) { // CTRL is pressed @@ -3595,6 +3602,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) update_sequential_clearance(true); } } + else if (evt.LeftUp() && + m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && + m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { + // Update selection from object list to check selection of the cut objects + // It's not allowed to scale separate ct parts + wxGetApp().obj_list()->selection_changed(); + } return; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 6cc852c6a6..1a7251ddf7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -99,12 +99,6 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable) m_grabbers[i].enabled = enable; } -void GLGizmoScale3D::on_set_state() -{ - if (m_state == On) - wxGetApp().obj_list()->selection_changed(); -} - void GLGizmoScale3D::data_changed(bool is_serializing) { set_scale(Vec3d::Ones()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 6e304061ab..6a7b4a331e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -74,8 +74,6 @@ protected: virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; - void on_set_state() override; - private: void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color); From 0cda6ddc1716663c485eb990e54a621f6546b2e1 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 14 Aug 2023 13:51:00 +0200 Subject: [PATCH 044/120] =?UTF-8?q?Fix:=20../src/slic3r/GUI/TextLines.cpp:?= =?UTF-8?q?27:24:=20warning:=20=E2=80=98const=20Slic3r::Polygon*=20{anonym?= =?UTF-8?q?ous}::largest(const=20Polygons&)=E2=80=99=20defined=20but=20not?= =?UTF-8?q?=20used=20[-Wunused-function]=20../src/slic3r/GUI/TextLines.cpp?= =?UTF-8?q?:74:10:=20warning:=20variable=20=E2=80=98calc=5Fnorm=E2=80=99?= =?UTF-8?q?=20set=20but=20not=20used=20[-Wunused-but-set-variable]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/slic3r/GUI/TextLines.cpp | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 667354a417..8916ce5ae4 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -24,27 +24,6 @@ using namespace Slic3r::Emboss; using namespace Slic3r::GUI; namespace { -const Slic3r::Polygon *largest(const Slic3r::Polygons &polygons) -{ - if (polygons.empty()) - return nullptr; - if (polygons.size() == 1) - return &polygons.front(); - - // compare polygon to find largest - size_t biggest_size = 0; - const Slic3r::Polygon *result = nullptr; - for (const Slic3r::Polygon &polygon : polygons) { - Point s = polygon.bounding_box().size(); - size_t size = s.x() * s.y(); - if (size <= biggest_size) - continue; - biggest_size = size; - result = &polygon; - } - return result; -} - // Be careful it is not water tide and contain self intersections // It is only for visualization purposes indexed_triangle_set its_create_torus(const Slic3r::Polygon &polygon, float radius, size_t steps = 20) @@ -69,12 +48,6 @@ indexed_triangle_set its_create_torus(const Slic3r::Polygon &polygon, float radi for (size_t i = 0; i < count - 1; ++i) line_norm[i] = calc_line_norm(points_d[i], points_d[i + 1]); line_norm.back() = calc_line_norm(points_d.back(), points_d.front()); - - // calculate normals for each point - auto calc_norm = [](const Vec2f &prev, const Vec2f &next) -> Vec2f { - Vec2f dir = prev + next; - return Vec2f(-dir.x(), dir.y()); - }; // precalculate sinus and cosinus double angle_step = 2 * M_PI / steps; From 667141302300229d5ea9fc5a1cd75381b8259459 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 15 Aug 2023 10:39:51 +0200 Subject: [PATCH 045/120] Fix for SPE-1843 - PresetComboBoxes on Settings Tabs doesn't revert the selection, when "Cancel" button is clicked in UnsaveChangesDilalog This bug was caused by mistakenly commented line of code in https://github.com/Prusa-Development/PrusaSlicerPrivate/commit/9e4859ebbf9006a454e905c0aef352be1cabf251 --- src/slic3r/GUI/Tab.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ad93afa879..1a479e566b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3652,7 +3652,8 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, } } - // update_tab_ui(); //! ysFIXME delete after testing + // ! update preset combobox, to revert previously selection + update_tab_ui(); // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. From a386d092b70c35c0f07d31e1190ad2855e9b072b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 15 Aug 2023 14:37:00 +0200 Subject: [PATCH 046/120] CutGizmo: Set "emission_factor" for shader, when rendering GLModels to avoid flickering of the connectors on change camera view --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9d23a51808..b8e64fafa6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -948,6 +948,7 @@ void GLGizmoCut3D::render_model(GLModel& model, const ColorRGBA& color, Transfor shader->start_using(); shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("emission_factor", 0.2f); shader->set_uniform("projection_matrix", wxGetApp().plater()->get_camera().get_projection_matrix()); model.set_color(color); From 0b31ef6e1e14b0a98306496a84b32f407d86fe2b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Jul 2023 14:57:47 +0200 Subject: [PATCH 047/120] Beginning of arrange gen2 --- sandboxes/CMakeLists.txt | 5 +- sandboxes/print_arrange_polys/CMakeLists.txt | 7 + sandboxes/print_arrange_polys/main.cpp | 103 + src/PrusaSlicer.cpp | 12 +- src/libnest2d/CMakeLists.txt | 9 +- .../backends/libslic3r/geometries.hpp | 12 + src/libslic3r/AnyPtr.hpp | 136 +- src/libslic3r/Arrange.cpp | 3 +- src/libslic3r/Arrange/Arrange.hpp | 236 + src/libslic3r/Arrange/ArrangeImpl.hpp | 450 ++ .../Arrange/ArrangeSettingsDb_AppCfg.cpp | 234 + .../Arrange/ArrangeSettingsDb_AppCfg.hpp | 78 + src/libslic3r/Arrange/ArrangeSettingsView.hpp | 118 + src/libslic3r/Arrange/Core/ArrangeBase.hpp | 258 + .../Arrange/Core/ArrangeFirstFit.hpp | 161 + .../Arrange/Core/ArrangeItemTraits.hpp | 108 + src/libslic3r/Arrange/Core/Beds.cpp | 129 + src/libslic3r/Arrange/Core/Beds.hpp | 191 + .../Arrange/Core/DataStoreTraits.hpp | 78 + .../Arrange/Core/NFP/CircularEdgeIterator.hpp | 110 + src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp | 92 + src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp | 70 + .../Core/NFP/Kernels/CompactifyKernel.hpp | 61 + .../Core/NFP/Kernels/GravityKernel.hpp | 58 + .../Arrange/Core/NFP/Kernels/KernelTraits.hpp | 57 + .../Arrange/Core/NFP/Kernels/KernelUtils.hpp | 75 + .../Kernels/RectangleOverfitKernelWrapper.hpp | 94 + .../Kernels/SVGDebugOutputKernelWrapper.hpp | 96 + .../Core/NFP/Kernels/TMArrangeKernel.hpp | 268 + src/libslic3r/Arrange/Core/NFP/NFP.cpp | 412 ++ src/libslic3r/Arrange/Core/NFP/NFP.hpp | 50 + .../Arrange/Core/NFP/NFPArrangeItemTraits.hpp | 176 + .../Arrange/Core/NFP/NFPConcave_CGAL.cpp | 111 + .../Arrange/Core/NFP/NFPConcave_CGAL.hpp | 14 + .../Arrange/Core/NFP/NFPConcave_Tesselate.cpp | 70 + .../Arrange/Core/NFP/NFPConcave_Tesselate.hpp | 15 + .../Arrange/Core/NFP/PackStrategyNFP.hpp | 279 + .../NFP/RectangleOverfitPackingStrategy.hpp | 122 + src/libslic3r/Arrange/Core/PackingContext.hpp | 124 + .../Arrange/Items/ArbitraryDataStore.hpp | 91 + src/libslic3r/Arrange/Items/ArrangeItem.cpp | 184 + src/libslic3r/Arrange/Items/ArrangeItem.hpp | 461 ++ .../Arrange/Items/SimpleArrangeItem.cpp | 15 + .../Arrange/Items/SimpleArrangeItem.hpp | 178 + .../Arrange/Items/TrafoOnlyArrangeItem.hpp | 79 + .../Arrange/Items/WritableItemTraits.hpp | 112 + src/libslic3r/Arrange/Scene.cpp | 64 + src/libslic3r/Arrange/Scene.hpp | 308 + src/libslic3r/Arrange/SceneBuilder.cpp | 859 +++ src/libslic3r/Arrange/SceneBuilder.hpp | 645 ++ .../Arrange/SegmentedRectangleBed.hpp | 105 + src/libslic3r/Arrange/Tasks/ArrangeTask.hpp | 81 + .../Arrange/Tasks/ArrangeTaskImpl.hpp | 109 + src/libslic3r/Arrange/Tasks/FillBedTask.hpp | 53 + .../Arrange/Tasks/FillBedTaskImpl.hpp | 173 + .../Arrange/Tasks/MultiplySelectionTask.hpp | 108 + .../Tasks/MultiplySelectionTaskImpl.hpp | 120 + src/libslic3r/BoostAdapter.hpp | 139 +- src/libslic3r/BoundingBox.hpp | 35 +- src/libslic3r/CMakeLists.txt | 55 +- src/libslic3r/ClipperUtils.cpp | 8 + src/libslic3r/ClipperUtils.hpp | 4 + src/libslic3r/Geometry/ConvexHull.hpp | 7 +- src/libslic3r/Line.hpp | 28 +- src/libslic3r/MinAreaBoundingBox.cpp | 13 + src/libslic3r/MinAreaBoundingBox.hpp | 4 +- src/libslic3r/Model.cpp | 35 +- src/libslic3r/Model.hpp | 7 +- src/libslic3r/ModelArrange.cpp | 117 +- src/libslic3r/ModelArrange.hpp | 66 +- src/libslic3r/MultiPoint.hpp | 6 + src/libslic3r/Optimize/NLoptOptimizer.hpp | 25 +- src/libslic3r/Optimize/Optimizer.hpp | 9 +- src/libslic3r/Polygon.hpp | 6 + src/libslic3r/Polyline.hpp | 3 + src/libslic3r/PrintConfig.cpp | 15 +- src/libslic3r/PrintConfig.hpp | 3 - src/libslic3r/libslic3r.h | 83 +- src/slic3r/CMakeLists.txt | 8 +- src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp | 134 + src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp | 53 + src/slic3r/GUI/GLCanvas3D.cpp | 278 +- src/slic3r/GUI/GLCanvas3D.hpp | 45 +- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 16 +- src/slic3r/GUI/Jobs/ArrangeJob2.cpp | 207 + src/slic3r/GUI/Jobs/ArrangeJob2.hpp | 138 + src/slic3r/GUI/Plater.cpp | 121 +- src/slic3r/GUI/Plater.hpp | 1 + tests/CMakeLists.txt | 2 +- tests/arrange/CMakeLists.txt | 17 + tests/arrange/arrange_tests_main.cpp | 1 + tests/arrange/test_arrange.cpp | 1122 ++++ tests/arrange/test_arrange_integration.cpp | 1036 +++ tests/data/default_fff.ini | 313 + tests/data/prusaparts.cpp | 5981 +++++++++++++++++ tests/data/prusaparts.hpp | 14 + tests/fff_print/test_data.cpp | 2 +- tests/fff_print/test_model.cpp | 2 +- tests/libnest2d/CMakeLists.txt | 11 - tests/libnest2d/libnest2d_tests_main.cpp | 1233 ---- tests/libnest2d/printer_parts.cpp | 3175 --------- tests/libnest2d/printer_parts.hpp | 14 - tests/libslic3r/CMakeLists.txt | 8 +- tests/libslic3r/test_anyptr.cpp | 198 + tests/libslic3r/test_geometry.cpp | 16 +- tests/libslic3r/test_marchingsquares.cpp | 2 - tests/slic3rutils/CMakeLists.txt | 1 + tests/slic3rutils/slic3r_arrangejob_tests.cpp | 351 + tests/test_utils.hpp | 19 + xs/xsp/Model.xsp | 4 +- 110 files changed, 18609 insertions(+), 5009 deletions(-) create mode 100644 sandboxes/print_arrange_polys/CMakeLists.txt create mode 100644 sandboxes/print_arrange_polys/main.cpp create mode 100644 src/libslic3r/Arrange/Arrange.hpp create mode 100644 src/libslic3r/Arrange/ArrangeImpl.hpp create mode 100644 src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp create mode 100644 src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp create mode 100644 src/libslic3r/Arrange/ArrangeSettingsView.hpp create mode 100644 src/libslic3r/Arrange/Core/ArrangeBase.hpp create mode 100644 src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp create mode 100644 src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp create mode 100644 src/libslic3r/Arrange/Core/Beds.cpp create mode 100644 src/libslic3r/Arrange/Core/Beds.hpp create mode 100644 src/libslic3r/Arrange/Core/DataStoreTraits.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp create mode 100644 src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/NFP.cpp create mode 100644 src/libslic3r/Arrange/Core/NFP/NFP.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp create mode 100644 src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp create mode 100644 src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp create mode 100644 src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp create mode 100644 src/libslic3r/Arrange/Core/PackingContext.hpp create mode 100644 src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp create mode 100644 src/libslic3r/Arrange/Items/ArrangeItem.cpp create mode 100644 src/libslic3r/Arrange/Items/ArrangeItem.hpp create mode 100644 src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp create mode 100644 src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp create mode 100644 src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp create mode 100644 src/libslic3r/Arrange/Items/WritableItemTraits.hpp create mode 100644 src/libslic3r/Arrange/Scene.cpp create mode 100644 src/libslic3r/Arrange/Scene.hpp create mode 100644 src/libslic3r/Arrange/SceneBuilder.cpp create mode 100644 src/libslic3r/Arrange/SceneBuilder.hpp create mode 100644 src/libslic3r/Arrange/SegmentedRectangleBed.hpp create mode 100644 src/libslic3r/Arrange/Tasks/ArrangeTask.hpp create mode 100644 src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp create mode 100644 src/libslic3r/Arrange/Tasks/FillBedTask.hpp create mode 100644 src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp create mode 100644 src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp create mode 100644 src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp create mode 100644 src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp create mode 100644 src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp create mode 100644 src/slic3r/GUI/Jobs/ArrangeJob2.cpp create mode 100644 src/slic3r/GUI/Jobs/ArrangeJob2.hpp create mode 100644 tests/arrange/CMakeLists.txt create mode 100644 tests/arrange/arrange_tests_main.cpp create mode 100644 tests/arrange/test_arrange.cpp create mode 100644 tests/arrange/test_arrange_integration.cpp create mode 100644 tests/data/default_fff.ini create mode 100644 tests/data/prusaparts.cpp create mode 100644 tests/data/prusaparts.hpp delete mode 100644 tests/libnest2d/CMakeLists.txt delete mode 100644 tests/libnest2d/libnest2d_tests_main.cpp delete mode 100644 tests/libnest2d/printer_parts.cpp delete mode 100644 tests/libnest2d/printer_parts.hpp create mode 100644 tests/libslic3r/test_anyptr.cpp create mode 100644 tests/slic3rutils/slic3r_arrangejob_tests.cpp diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt index f6a4e4a84a..ae760e4487 100644 --- a/sandboxes/CMakeLists.txt +++ b/sandboxes/CMakeLists.txt @@ -1,7 +1,8 @@ #add_subdirectory(slasupporttree) #add_subdirectory(openvdb) # add_subdirectory(meshboolean) -add_subdirectory(its_neighbor_index) +#add_subdirectory(its_neighbor_index) # add_subdirectory(opencsg) #add_subdirectory(aabb-evaluation) -add_subdirectory(wx_gl_test) \ No newline at end of file +#add_subdirectory(wx_gl_test) +add_subdirectory(print_arrange_polys) diff --git a/sandboxes/print_arrange_polys/CMakeLists.txt b/sandboxes/print_arrange_polys/CMakeLists.txt new file mode 100644 index 0000000000..c9f3ed0b17 --- /dev/null +++ b/sandboxes/print_arrange_polys/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(print_arrange_polys main.cpp) + +target_link_libraries(print_arrange_polys libslic3r admesh) + +if (WIN32) + prusaslicer_copy_dlls(print_arrange_polys) +endif() diff --git a/sandboxes/print_arrange_polys/main.cpp b/sandboxes/print_arrange_polys/main.cpp new file mode 100644 index 0000000000..c076a9462d --- /dev/null +++ b/sandboxes/print_arrange_polys/main.cpp @@ -0,0 +1,103 @@ +#include +#include + +#include + +#include + +void print_arrange_polygons(const std::string &dirpath, std::ostream &out) +{ + using namespace Slic3r; + + boost::filesystem::path p = dirpath; //"/home/quarky/Workspace/printing/Original-Prusa-i3-MK3/Printed-Parts/stl/"; + + if (!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p)) + return; + + for (const auto& entry : boost::filesystem::directory_iterator(p)) { + if (!boost::filesystem::is_regular_file(entry)) { + continue; + } + + TriangleMesh mesh; + mesh.ReadSTLFile(entry.path().c_str()); + ExPolygons outline = mesh.horizontal_projection(); + + out << "// " << entry.path().filename() << ": " << std::endl; + for (const ExPolygon &expoly : outline) { + out << "MyPoly{\n"; // Start of polygon + + out << "\t{\n"; // Start of contour + for (const auto& point : expoly.contour.points) { + out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates + } + out << " },\n"; // End of contour + + out << " {\n"; // start of holes + for (const auto& hole : expoly.holes) { + out << " {\n"; // Start of hole + for (const auto& point : hole.points) { + out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates + } + out << " },\n"; // End of hole Polygon + } + out << " }\n"; // end of holes Polygons + out << "},\n"; // End of ExPolygon + } + } +} + +void print_arrange_items(const std::string &dirpath, std::ostream &out) +{ + using namespace Slic3r; + + boost::filesystem::path p = dirpath; + + if (!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p)) + return; + + for (const auto& entry : boost::filesystem::directory_iterator(p)) { + if (!boost::filesystem::is_regular_file(entry)) { + continue; + } + + TriangleMesh mesh; + mesh.ReadSTLFile(entry.path().c_str()); + ExPolygons outline = mesh.horizontal_projection(); + + out << "ExPolygons{ " << "// " << entry.path().filename() << ":\n"; + for (const ExPolygon &expoly : outline) { + out << " MyPoly{\n"; // Start of polygon + + out << " {\n"; // Start of contour + for (const auto& point : expoly.contour.points) { + out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates + } + out << " },\n"; // End of contour + + out << " {\n"; // start of holes + for (const auto& hole : expoly.holes) { + out << " {\n"; // Start of hole + for (const auto& point : hole.points) { + out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates + } + out << " },\n"; // End of hole Polygon + } + out << " }\n"; // end of holes Polygons + out << " },\n"; // End of ExPolygon + } + out << "},\n"; + } +} + +int main(int argc, const char *argv[]) +{ + if (argc <= 1) + return -1; + + std::string dirpath = argv[1]; + + print_arrange_items(dirpath, std::cout); + + return 0; +} diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 480f42811e..0d627aa837 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -314,10 +314,10 @@ int CLI::run(int argc, char **argv) // Loop through transform options. bool user_center_specified = false; - Points bed = get_bed_shape(m_print_config); - ArrangeParams arrange_cfg; - arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); - + arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config)); + arr2::ArrangeSettings arrange_cfg; + arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config)); + for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { Model m; @@ -330,7 +330,7 @@ int CLI::run(int argc, char **argv) if (this->has_print_action()) arrange_objects(m, bed, arrange_cfg); else - arrange_objects(m, InfiniteBed{}, arrange_cfg); + arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg); } m_models.clear(); m_models.emplace_back(std::move(m)); @@ -576,7 +576,7 @@ int CLI::run(int argc, char **argv) if (! m_config.opt_bool("dont_arrange")) { if (user_center_specified) { Vec2d c = m_config.option("center")->value; - arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg); + arrange_objects(model, arr2::InfiniteBed{scaled(c)}, arrange_cfg); } else arrange_objects(model, bed, arrange_cfg); } diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 154c965e5b..ccfdfb5512 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -18,11 +18,10 @@ set(LIBNEST2D_SRCFILES include/libnest2d/optimizers/nlopt/simplex.hpp include/libnest2d/optimizers/nlopt/subplex.hpp include/libnest2d/optimizers/nlopt/genetic.hpp - src/libnest2d.cpp ) -add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES}) +add_library(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) -target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) -target_link_libraries(libnest2d PUBLIC NLopt::nlopt TBB::tbb TBB::tbbmalloc Boost::boost libslic3r) -target_compile_definitions(libnest2d PUBLIC LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r) +target_include_directories(libnest2d INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_link_libraries(libnest2d INTERFACE NLopt::nlopt TBB::tbb TBB::tbbmalloc Boost::boost libslic3r) +target_compile_definitions(libnest2d INTERFACE LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r) diff --git a/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp index 14b075b19d..48b54aa1c6 100644 --- a/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/libslic3r/geometries.hpp @@ -243,6 +243,12 @@ inline void translate(Slic3r::ExPolygon& sh, const Slic3r::Point& offs) sh.translate(offs); } +template<> +inline void translate(Slic3r::Polygon& sh, const Slic3r::Point& offs) +{ + sh.translate(offs); +} + #define DISABLE_BOOST_ROTATE template<> inline void rotate(Slic3r::ExPolygon& sh, const Radians& rads) @@ -250,6 +256,12 @@ inline void rotate(Slic3r::ExPolygon& sh, const Radians& rads) sh.rotate(rads); } +template<> +inline void rotate(Slic3r::Polygon& sh, const Radians& rads) +{ + sh.rotate(rads); +} + } // namespace shapelike namespace nfp { diff --git a/src/libslic3r/AnyPtr.hpp b/src/libslic3r/AnyPtr.hpp index 823fac0808..b5bbdd73db 100644 --- a/src/libslic3r/AnyPtr.hpp +++ b/src/libslic3r/AnyPtr.hpp @@ -15,12 +15,19 @@ namespace Slic3r { // The stored pointer is not checked for being null when dereferenced. // // This is a movable only object due to the fact that it can possibly hold -// a unique_ptr which a non-copy. +// a unique_ptr which can only be moved. +// +// Drawbacks: +// No custom deleters are supported when storing a unique_ptr, but overloading +// std::default_delete for a particular type could be a workaround +// +// raw array types are problematic, since std::default_delete also does not +// support them well. template class AnyPtr { - enum { RawPtr, UPtr, ShPtr, WkPtr }; + enum { RawPtr, UPtr, ShPtr }; - boost::variant, std::shared_ptr, std::weak_ptr> ptr; + boost::variant, std::shared_ptr> ptr; template static T *get_ptr(Self &&s) { @@ -28,91 +35,119 @@ class AnyPtr { case RawPtr: return boost::get(s.ptr); case UPtr: return boost::get>(s.ptr).get(); case ShPtr: return boost::get>(s.ptr).get(); - case WkPtr: { - auto shptr = boost::get>(s.ptr).lock(); - return shptr.get(); - } } return nullptr; } -public: - template>> - AnyPtr(TT *p = nullptr) : ptr{p} - {} - template>> - AnyPtr(std::unique_ptr p) : ptr{std::unique_ptr(std::move(p))} - {} - template>> - AnyPtr(std::shared_ptr p) : ptr{std::shared_ptr(std::move(p))} - {} - template>> - AnyPtr(std::weak_ptr p) : ptr{std::weak_ptr(std::move(p))} - {} + template friend class AnyPtr; - ~AnyPtr() = default; + template + using SimilarPtrOnly = std::enable_if_t>; + +public: + + AnyPtr() noexcept = default; + + AnyPtr(T *p) noexcept: ptr{p} {} + + AnyPtr(std::nullptr_t) noexcept {}; + + template> + AnyPtr(TT *p) noexcept : ptr{p} + {} + template> + AnyPtr(std::unique_ptr p) noexcept : ptr{std::unique_ptr(std::move(p))} + {} + template> + AnyPtr(std::shared_ptr p) noexcept : ptr{std::shared_ptr(std::move(p))} + {} AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {} + + template> + AnyPtr(AnyPtr &&other) noexcept + { + this->operator=(std::move(other)); + } + AnyPtr(const AnyPtr &other) = delete; - AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; } + AnyPtr &operator=(AnyPtr &&other) noexcept + { + ptr = std::move(other.ptr); + return *this; + } + AnyPtr &operator=(const AnyPtr &other) = delete; - template>> - AnyPtr &operator=(TT *p) { ptr = p; return *this; } + template> + AnyPtr& operator=(AnyPtr &&other) noexcept + { + switch (other.ptr.which()) { + case RawPtr: *this = boost::get(other.ptr); break; + case UPtr: *this = std::move(boost::get>(other.ptr)); break; + case ShPtr: *this = std::move(boost::get>(other.ptr)); break; + } - template>> - AnyPtr &operator=(std::unique_ptr p) { ptr = std::move(p); return *this; } + return *this; + } - template>> - AnyPtr &operator=(std::shared_ptr p) { ptr = p; return *this; } + template> + AnyPtr &operator=(TT *p) noexcept + { + ptr = static_cast(p); + return *this; + } - template>> - AnyPtr &operator=(std::weak_ptr p) { ptr = std::move(p); return *this; } + template> + AnyPtr &operator=(std::unique_ptr p) noexcept + { + ptr = std::unique_ptr(std::move(p)); + return *this; + } - const T &operator*() const { return *get_ptr(*this); } - T &operator*() { return *get_ptr(*this); } + template> + AnyPtr &operator=(std::shared_ptr p) noexcept + { + ptr = std::shared_ptr(std::move(p)); + return *this; + } - T *operator->() { return get_ptr(*this); } - const T *operator->() const { return get_ptr(*this); } + const T &operator*() const noexcept { return *get_ptr(*this); } + T &operator*() noexcept { return *get_ptr(*this); } - T *get() { return get_ptr(*this); } - const T *get() const { return get_ptr(*this); } + T *operator->() noexcept { return get_ptr(*this); } + const T *operator->() const noexcept { return get_ptr(*this); } - operator bool() const + T *get() noexcept { return get_ptr(*this); } + const T *get() const noexcept { return get_ptr(*this); } + + operator bool() const noexcept { switch (ptr.which()) { case RawPtr: return bool(boost::get(ptr)); case UPtr: return bool(boost::get>(ptr)); case ShPtr: return bool(boost::get>(ptr)); - case WkPtr: { - auto shptr = boost::get>(ptr).lock(); - return bool(shptr); - } } return false; } - // If the stored pointer is a shared or weak pointer, returns a reference + // If the stored pointer is a shared pointer, returns a reference // counted copy. Empty shared pointer is returned otherwise. - std::shared_ptr get_shared_cpy() const + std::shared_ptr get_shared_cpy() const noexcept { std::shared_ptr ret; - switch (ptr.which()) { - case ShPtr: ret = boost::get>(ptr); break; - case WkPtr: ret = boost::get>(ptr).lock(); break; - default: - ; - } + if (ptr.which() == ShPtr) + ret = boost::get>(ptr); return ret; } // If the underlying pointer is unique, convert to shared pointer - void convert_unique_to_shared() + void convert_unique_to_shared() noexcept { if (ptr.which() == UPtr) ptr = std::shared_ptr{std::move(boost::get>(ptr))}; @@ -125,6 +160,7 @@ public: } }; + } // namespace Slic3r #endif // ANYPTR_HPP diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index ea71a1a55f..db4c343275 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -12,6 +12,7 @@ #include #include +#include #if defined(_MSC_VER) && defined(__clang__) #define BOOST_NO_CXX17_HDR_STRING_VIEW @@ -258,7 +259,7 @@ protected: auto& index = isBig(item.area()) ? spatindex : smalls_spatindex; // Query the spatial index for the neighbors - std::vector result; + boost::container::small_vector result; result.reserve(index.size()); index.query(query, std::back_inserter(result)); diff --git a/src/libslic3r/Arrange/Arrange.hpp b/src/libslic3r/Arrange/Arrange.hpp new file mode 100644 index 0000000000..1578fed1c6 --- /dev/null +++ b/src/libslic3r/Arrange/Arrange.hpp @@ -0,0 +1,236 @@ +#ifndef ARRANGE2_HPP +#define ARRANGE2_HPP + +#include "Scene.hpp" +#include "Items/WritableItemTraits.hpp" +#include "Core/NFP/NFPArrangeItemTraits.hpp" + +#include "libslic3r/MinAreaBoundingBox.hpp" + +namespace Slic3r { namespace arr2 { + +template class Arranger +{ +public: + class Ctl : public ArrangeTaskCtl { + public: + virtual void on_packed(ArrItem &item) {}; + }; + + virtual ~Arranger() = default; + + virtual void arrange(std::vector &items, + const std::vector &fixed, + const ExtendedBed &bed, + Ctl &ctl) = 0; + + void arrange(std::vector &items, + const std::vector &fixed, + const ExtendedBed &bed, + ArrangeTaskCtl &ctl); + + void arrange(std::vector &items, + const std::vector &fixed, + const ExtendedBed &bed, + Ctl &&ctl) + { + arrange(items, fixed, bed, ctl); + } + + void arrange(std::vector &items, + const std::vector &fixed, + const ExtendedBed &bed, + ArrangeTaskCtl &&ctl) + { + arrange(items, fixed, bed, ctl); + } + + static std::unique_ptr create(const ArrangeSettingsView &settings); +}; + +template using ArrangerCtl = typename Arranger::Ctl; + +template +class DefaultArrangerCtl : public Arranger::Ctl { + ArrangeTaskCtl *taskctl = nullptr; + +public: + DefaultArrangerCtl() = default; + + explicit DefaultArrangerCtl(ArrangeTaskBase::Ctl &ctl) : taskctl{&ctl} {} + + void update_status(int st) override + { + if (taskctl) + taskctl->update_status(st); + } + + bool was_canceled() const override + { + if (taskctl) + return taskctl->was_canceled(); + + return false; + } +}; + +template +void Arranger::arrange(std::vector &items, + const std::vector &fixed, + const ExtendedBed &bed, + ArrangeTaskCtl &ctl) +{ + arrange(items, fixed, bed, DefaultArrangerCtl{ctl}); +} + +template class ArrangeableToItemConverter +{ +public: + virtual ~ArrangeableToItemConverter() = default; + + virtual ArrItem convert(const Arrangeable &arrbl, coord_t offs = 0) const = 0; + + static std::unique_ptr create( + ArrangeSettingsView::GeometryHandling geometry_handling, + coord_t safety_d); + + static std::unique_ptr create( + const Scene &sc) + { + return create(sc.settings().get_geometry_handling(), + scaled(sc.settings().get_distance_from_objects())); + } +}; + +template> +class AnyWritableDataStore: public AnyWritable +{ + DStore &dstore; + +public: + AnyWritableDataStore(DStore &store): dstore{store} {} + + void write(std::string_view key, std::any d) override + { + set_data(dstore, std::string{key}, std::move(d)); + } +}; + +template +class BasicItemConverter : public ArrangeableToItemConverter +{ + coord_t m_safety_d; + +public: + BasicItemConverter(coord_t safety_d = 0) : m_safety_d{safety_d} {} + + coord_t safety_dist() const noexcept { return m_safety_d; } +}; + +template +class ConvexItemConverter : public BasicItemConverter +{ +public: + using BasicItemConverter::BasicItemConverter; + + ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override; +}; + +template +class AdvancedItemConverter : public BasicItemConverter +{ +protected: + virtual ArrItem get_arritem(const Arrangeable &arrbl, coord_t eps) const; + +public: + using BasicItemConverter::BasicItemConverter; + + ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override; +}; + +template +class BalancedItemConverter : public AdvancedItemConverter +{ +protected: + ArrItem get_arritem(const Arrangeable &arrbl, coord_t offs) const override; + +public: + using AdvancedItemConverter::AdvancedItemConverter; +}; + +template struct ImbueableItemTraits_ +{ + static constexpr const char *Key = "object_id"; + + static void imbue_id(ArrItem &itm, const ObjectID &id) + { + set_arbitrary_data(itm, Key, id); + } + + static std::optional retrieve_id(const ArrItem &itm) + { + std::optional ret; + auto idptr = get_data(itm, Key); + if (idptr) + ret = *idptr; + + return ret; + } +}; + +template +using ImbueableItemTraits = ImbueableItemTraits_>; + +template +void imbue_id(ArrItem &itm, const ObjectID &id) +{ + ImbueableItemTraits::imbue_id(itm, id); +} + +template +std::optional retrieve_id(const ArrItem &itm) +{ + return ImbueableItemTraits::retrieve_id(itm); +} + +template +bool apply_arrangeitem(const ArrItem &itm, ArrangeableModel &mdl) +{ + bool ret = false; + + if (auto id = retrieve_id(itm)) { + mdl.visit_arrangeable(*id, [&itm, &ret](Arrangeable &arrbl) { + if ((ret = arrbl.assign_bed(get_bed_index(itm)))) + arrbl.transform(unscaled(get_translation(itm)), get_rotation(itm)); + }); + } + + return ret; +} + +template +double get_min_area_bounding_box_rotation(const ArrItem &itm) +{ + return MinAreaBoundigBox{envelope_convex_hull(itm), + MinAreaBoundigBox::pcConvex} + .angle_to_X(); +} + +template +double get_fit_into_bed_rotation(const ArrItem &itm, const RectangleBed &bed) +{ + double ret = 0.; + + auto bbsz = envelope_bounding_box(itm).size(); + auto binbb = bounding_box(bed); + auto binbbsz = binbb.size(); + + if (bbsz.x() >= binbbsz.x() || bbsz.y() >= binbbsz.y()) + ret = fit_into_box_rotation(envelope_convex_hull(itm), binbb); + + return ret; +} + +}} // namespace Slic3r::arr2 + +#endif // ARRANGE2_HPP diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp new file mode 100644 index 0000000000..0b0949de28 --- /dev/null +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -0,0 +1,450 @@ +#ifndef ARRANGEIMPL_HPP +#define ARRANGEIMPL_HPP + +#include + +#include "Arrange.hpp" + +#include "Core/ArrangeBase.hpp" +#include "Core/ArrangeFirstFit.hpp" +#include "Core/NFP/PackStrategyNFP.hpp" +#include "Core/NFP/Kernels/TMArrangeKernel.hpp" +#include "Core/NFP/Kernels/GravityKernel.hpp" +#include "Core/NFP/RectangleOverfitPackingStrategy.hpp" +#include "Core/Beds.hpp" + +#include "Items/WritableItemTraits.hpp" + +#include "SegmentedRectangleBed.hpp" + +#include "libslic3r/Execution/ExecutionTBB.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" + +#ifndef NDEBUG +#include "Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp" +#endif + +namespace Slic3r { namespace arr2 { + +// arrange overload for SegmentedRectangleBed which is exactly what is used +// by XL printers. +template +void arrange(SelectionStrategy &&selstrategy, + PackStrategy &&packingstrategy, + const Range &items, + const Range &fixed, + const SegmentedRectangleBed &bed) +{ + // Dispatch: + arrange(std::forward(selstrategy), + std::forward(packingstrategy), items, fixed, + RectangleBed{bed.bb}, SelStrategyTag{}); + + size_t beds = get_bed_count(crange(items)); + size_t fixed_beds = std::max(beds, get_bed_count(fixed)); + std::vector fixed_is_empty(fixed_beds, true); + + std::vector pilebb(beds); + + for (auto &itm : items) { + auto bedidx = get_bed_index(itm); + if (bedidx >= 0) { + pilebb[bedidx].merge(fixed_bounding_box(itm)); + if (is_wipe_tower(itm)) + fixed_is_empty[bedidx] = false; + } + } + + for (auto &fxitm : fixed) { + auto bedidx = get_bed_index(fxitm); + if (bedidx >= 0 || is_wipe_tower(fxitm)) + fixed_is_empty[bedidx] = false; + } + + auto bedbb = bounding_box(bed); + auto piecesz = unscaled(bedbb).size(); + piecesz.x() /= bed.segments_x(); + piecesz.y() /= bed.segments_y(); + + using Pivots = RectPivots; + + Pivots pivot = bed.alignment(); + + for (size_t bedidx = 0; bedidx < beds; ++bedidx) { + if (! fixed_is_empty[bedidx]) + continue; + + BoundingBox bb; + auto pilesz = unscaled(pilebb[bedidx]).size(); + bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x()); + bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y()); + + switch (pivot) { + case Pivots::BottomLeft: + bb.translate(bedbb.min - bb.min); + break; + case Pivots::TopRight: + bb.translate(bedbb.max - bb.max); + break; + case Pivots::BottomRight: { + Point bedref{bedbb.max.x(), bedbb.min.y()}; + Point bbref {bb.max.x(), bb.min.y()}; + bb.translate(bedref - bbref); + break; + } + case Pivots::TopLeft: { + Point bedref{bedbb.min.x(), bedbb.max.y()}; + Point bbref {bb.min.x(), bb.max.y()}; + bb.translate(bedref - bbref); + break; + } + case Pivots::Center: { + bb.translate(bedbb.center() - bb.center()); + break; + } + default: + ; + } + + Vec2crd d = bb.center() - pilebb[bedidx].center(); + + auto pilebbx = pilebb[bedidx]; + pilebbx.translate(d); + + Point corr{0, 0}; + corr.x() = -std::min(0, pilebbx.min.x() - bedbb.min.x()) + -std::max(0, pilebbx.max.x() - bedbb.max.x()); + corr.y() = -std::min(0, pilebbx.min.y() - bedbb.min.y()) + -std::max(0, pilebbx.max.y() - bedbb.max.y()); + + d += corr; + + for (auto &itm : items) + if (itm.bed_idx() == static_cast(bedidx) && !is_wipe_tower(itm)) + translate(itm, d); + } +} + + +using VariantKernel = + boost::variant; + +template<> struct KernelTraits_ { + template + static double placement_fitness(const VariantKernel &kernel, + const ArrItem &itm, + const Vec2crd &transl) + { + double ret = NaNd; + boost::apply_visitor( + [&](auto &k) { ret = k.placement_fitness(itm, transl); }, kernel); + + return ret; + } + + template + static bool on_start_packing(VariantKernel &kernel, + ArrItem &itm, + const Bed &bed, + const Ctx &packing_context, + const Range &remaining_items) + { + bool ret = false; + + boost::apply_visitor([&](auto &k) { + ret = k.on_start_packing(itm, bed, packing_context, remaining_items); + }, kernel); + + return ret; + } + + template + static bool on_item_packed(VariantKernel &kernel, ArrItem &itm) + { + bool ret = false; + boost::apply_visitor([&](auto &k) { ret = k.on_item_packed(itm); }, + kernel); + + return ret; + } +}; + +template +struct firstfit::ItemArrangedVisitor> { + template + static void on_arranged(ArrItem &itm, + const Bed &bed, + const Range &packed, + const Range &remaining) + { + using OnArrangeCb = std::function &)>; + + auto cb = get_data(itm, "on_arranged"); + + if (cb) { + (*cb)(itm); + } + } +}; + +inline RectPivots xlpivots_to_rect_pivots(ArrangeSettingsView::XLPivots xlpivot) +{ + if (xlpivot == arr2::ArrangeSettingsView::xlpRandom) { + // means it should be random + std::random_device rd{}; + std::mt19937 rng(rd()); + std::uniform_int_distribution + dist(0, arr2::ArrangeSettingsView::xlpRandom - 1); + xlpivot = static_cast(dist(rng)); + } + + RectPivots rectpivot = RectPivots::Center; + + switch(xlpivot) { + case arr2::ArrangeSettingsView::xlpCenter: rectpivot = RectPivots::Center; break; + case arr2::ArrangeSettingsView::xlpFrontLeft: rectpivot = RectPivots::BottomLeft; break; + case arr2::ArrangeSettingsView::xlpFrontRight: rectpivot = RectPivots::BottomRight; break; + case arr2::ArrangeSettingsView::xlpRearLeft: rectpivot = RectPivots::TopLeft; break; + case arr2::ArrangeSettingsView::xlpRearRight: rectpivot = RectPivots::TopRight; break; + default: + ; + } + + return rectpivot; +} + +// An arranger put together to fulfill all the requirements of PrusaSlicer based +// on the supplied ArrangeSettings +template +class DefaultArranger: public Arranger { + ArrangeSettings m_settings; + + static constexpr auto Accuracy = 1.; + + template + void arrange_( + const Range &items, + const Range &fixed, + const Bed &bed, + ArrangerCtl &ctl) + { + auto cmpfn = [](const auto &itm1, const auto &itm2) { + int pa = get_priority(itm1); + int pb = get_priority(itm2); + + return pa == pb ? envelope_area(itm1) > envelope_area(itm2) : + pa > pb; + }; + + auto on_arranged = [&ctl](auto &itm, auto &bed, auto &ctx, auto &rem) { + ctl.update_status(rem.size()); + + ctl.on_packed(itm); + + firstfit::DefaultOnArrangedFn{}(itm, bed, ctx, rem); + }; + + auto stop_cond = [&ctl] { return ctl.was_canceled(); }; + + firstfit::SelectionStrategy sel{cmpfn, on_arranged, stop_cond}; + + constexpr auto ep = ex_tbb; + + VariantKernel basekernel; + switch (m_settings.get_arrange_strategy()) { + default: + [[fallthrough]]; + case ArrangeSettingsView::asAuto: + basekernel = TMArrangeKernel{items.size(), area(bed)}; + break; + case ArrangeSettingsView::asPullToCenter: + basekernel = GravityKernel{}; + break; + } + +#ifndef NDEBUG + SVGDebugOutputKernelWrapper kernel{bounding_box(bed), basekernel}; +#else + auto & kernel = basekernel; +#endif + + // Use the minimum bounding box rotation as a starting point. + if (m_settings.is_rotation_enabled()) { + for (auto &itm : items) { + double fit_bed_rot = 0.; + + if constexpr (std::is_convertible_v) + fit_bed_rot = get_fit_into_bed_rotation(itm, bed); + + auto minbbr = get_min_area_bounding_box_rotation(itm); + std::vector rotations = + {minbbr, fit_bed_rot, + minbbr + PI / 4., minbbr + PI / 2., + minbbr + PI, minbbr + 3 * PI / 4.}; + + set_allowed_rotations(itm, rotations); + } + } + + bool with_wipe_tower = std::any_of(items.begin(), items.end(), + [](auto &itm) { + return is_wipe_tower(itm); + }); + + // With rectange bed, and no fixed items, let's use an infinite bed + // with RectangleOverfitKernelWrapper. It produces better results than + // a pure RectangleBed with inner-fit polygon calculation. + if (!with_wipe_tower && + m_settings.get_arrange_strategy() == ArrangeSettingsView::asAuto && + std::is_convertible_v) { + PackStrategyNFP base_strategy{std::move(kernel), ep, Accuracy, stop_cond}; + + RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)}; + + arr2::arrange(sel, final_strategy, items, fixed, bed); + } else { + PackStrategyNFP ps{std::move(kernel), ep, Accuracy, stop_cond}; + + arr2::arrange(sel, ps, items, fixed, bed); + } + } + +public: + explicit DefaultArranger(const ArrangeSettingsView &settings) + { + m_settings.set_from(settings); + } + + void arrange( + std::vector &items, + const std::vector &fixed, + const ExtendedBed &bed, + ArrangerCtl &ctl) override + { + visit_bed([this, &items, &fixed, &ctl](auto rawbed) { + + if constexpr (IsSegmentedBed) + rawbed.pivot = xlpivots_to_rect_pivots( + m_settings.get_xl_alignment()); + + arrange_(range(items), crange(fixed), rawbed, ctl); + }, bed); + } +}; + +template +std::unique_ptr> Arranger::create( + const ArrangeSettingsView &settings) +{ + // Currently all that is needed is handled by DefaultArranger + return std::make_unique>(settings); +} + +template +ArrItem ConvexItemConverter::convert(const Arrangeable &arrbl, + coord_t offs) const +{ + auto bed_index = arrbl.get_bed_index(); + Polygon outline = arrbl.convex_outline(); + Polygon envelope = arrbl.convex_envelope(); + + coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); + + if (infl != 0) { + outline = Geometry::convex_hull(offset(outline, infl)); + if (! envelope.empty()) + envelope = Geometry::convex_hull(offset(envelope, infl)); + } + + ArrItem ret; + set_convex_shape(ret, outline); + if (! envelope.empty()) + set_convex_envelope(ret, envelope); + + set_bed_index(ret, bed_index); + set_priority(ret, arrbl.priority()); + + imbue_id(ret, arrbl.id()); + arrbl.imbue_data(AnyWritableDataStore{ret}); + + return ret; +} + +template +ArrItem AdvancedItemConverter::convert(const Arrangeable &arrbl, + coord_t offs) const +{ + auto bed_index = arrbl.get_bed_index(); + ArrItem ret = get_arritem(arrbl, offs); + + set_bed_index(ret, bed_index); + set_priority(ret, arrbl.priority()); + imbue_id(ret, arrbl.id()); + arrbl.imbue_data(AnyWritableDataStore{ret}); + + return ret; +} + +template +ArrItem AdvancedItemConverter::get_arritem(const Arrangeable &arrbl, + coord_t offs) const +{ + coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); + + auto outline = arrbl.full_outline(); + auto envelope = arrbl.full_envelope(); + if (infl != 0) { + outline = offset_ex(outline, infl); + if (! envelope.empty()) + envelope = offset_ex(outline, infl); + } + + ArrItem ret; + set_shape(ret, outline); + if (! envelope.empty()) + set_envelope(ret, envelope); + + return ret; +} + +template +ArrItem BalancedItemConverter::get_arritem(const Arrangeable &arrbl, + coord_t offs) const +{ + ArrItem ret = AdvancedItemConverter::get_arritem(arrbl, offs); + set_convex_envelope(ret, envelope_convex_hull(ret)); + + return ret; +} + +template +std::unique_ptr> +ArrangeableToItemConverter::create( + ArrangeSettingsView::GeometryHandling gh, + coord_t safety_d) +{ + std::unique_ptr> ret; + + switch(gh) { + case arr2::ArrangeSettingsView::ghConvex: + ret = std::make_unique>(safety_d); + break; + case arr2::ArrangeSettingsView::ghBalanced: + ret = std::make_unique>(safety_d); + break; + case arr2::ArrangeSettingsView::ghAdvanced: + ret = std::make_unique>(safety_d); + break; + default: + ; + } + + return ret; +} + +}} // namespace Slic3r::arr2 + +#endif // ARRANGEIMPL_HPP diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp new file mode 100644 index 0000000000..9353d4aac2 --- /dev/null +++ b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp @@ -0,0 +1,234 @@ +#include "ArrangeSettingsDb_AppCfg.hpp" + +namespace Slic3r { + +PrinterTechnology ArrangeSettingsDb_AppCfg::current_printer_technology() const +{ + PrinterTechnology pt = ptFFF; + + if (m_printtech_getter) + pt = m_printtech_getter(); + + return pt; +} + +const DynamicPrintConfig *ArrangeSettingsDb_AppCfg::config() const +{ + const DynamicPrintConfig *ret = nullptr; + + if (m_config_getter) + ret = m_config_getter(); + + return ret; +} + +ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg( + AppConfig *appcfg, + std::function cfgfn, + std::function printtech_fn) + : m_appcfg{appcfg}, m_config_getter{cfgfn}, m_printtech_getter{printtech_fn} +{ + m_settings_fff.postfix = "_fff"; + m_settings_fff_seq.postfix = "_fff_seq_print"; + m_settings_sla.postfix = "_sla"; + + std::string dist_fff_str = + m_appcfg->get("arrange", "min_object_distance_fff"); + + std::string dist_bed_fff_str = + m_appcfg->get("arrange", "min_bed_distance_fff"); + + std::string dist_fff_seq_print_str = + m_appcfg->get("arrange", "min_object_distance_fff_seq_print"); + + std::string dist_bed_fff_seq_print_str = + m_appcfg->get("arrange", "min_bed_distance_fff_seq_print"); + + std::string dist_sla_str = + m_appcfg->get("arrange", "min_object_distance_sla"); + + std::string dist_bed_sla_str = + m_appcfg->get("arrange", "min_bed_distance_sla"); + + std::string en_rot_fff_str = + m_appcfg->get("arrange", "enable_rotation_fff"); + + std::string en_rot_fff_seqp_str = + m_appcfg->get("arrange", "enable_rotation_fff_seq_print"); + + std::string en_rot_sla_str = + m_appcfg->get("arrange", "enable_rotation_sla"); + + // std::string alignment_fff_str = + // m_appcfg->get("arrange", "alignment_fff"); + + // std::string alignment_fff_seqp_str = + // m_appcfg->get("arrange", "alignment_fff_seq_pring"); + + // std::string alignment_sla_str = + // m_appcfg->get("arrange", "alignment_sla"); + + // Override default alignment and save save/load it to a temporary slot "alignment_xl" + std::string alignment_xl_str = + m_appcfg->get("arrange", "alignment_xl"); + + std::string geom_handling_str = + m_appcfg->get("arrange", "geometry_handling"); + + std::string strategy_str = + m_appcfg->get("arrange", "arrange_strategy"); + + if (!dist_fff_str.empty()) + m_settings_fff.vals.d_obj = string_to_float_decimal_point(dist_fff_str); + + if (!dist_bed_fff_str.empty()) + m_settings_fff.vals.d_bed = string_to_float_decimal_point(dist_bed_fff_str); + + if (!dist_fff_seq_print_str.empty()) + m_settings_fff_seq.vals.d_obj = string_to_float_decimal_point(dist_fff_seq_print_str); + + if (!dist_bed_fff_seq_print_str.empty()) + m_settings_fff_seq.vals.d_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str); + + if (!dist_sla_str.empty()) + m_settings_sla.vals.d_obj = string_to_float_decimal_point(dist_sla_str); + + if (!dist_bed_sla_str.empty()) + m_settings_sla.vals.d_bed = string_to_float_decimal_point(dist_bed_sla_str); + + if (!en_rot_fff_str.empty()) + m_settings_fff.vals.rotations = (en_rot_fff_str == "1" || en_rot_fff_str == "yes"); + + if (!en_rot_fff_seqp_str.empty()) + m_settings_fff_seq.vals.rotations = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes"); + + if (!en_rot_sla_str.empty()) + m_settings_sla.vals.rotations = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); + + // if (!alignment_sla_str.empty()) + // m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str); + + // if (!alignment_fff_str.empty()) + // m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str); + + // if (!alignment_fff_seqp_str.empty()) + // m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str); + + // Override default alignment and save save/load it to a temporary slot "alignment_xl" + ArrangeSettingsView::XLPivots arr_alignment = ArrangeSettingsView::xlpFrontLeft; + if (!alignment_xl_str.empty()) { + int align_val = std::stoi(alignment_xl_str); + + if (align_val >= 0 && align_val < ArrangeSettingsView::xlpCount) + arr_alignment = + static_cast(align_val); + } + + m_settings_sla.vals.xl_align = arr_alignment ; + m_settings_fff.vals.xl_align = arr_alignment ; + m_settings_fff_seq.vals.xl_align = arr_alignment ; + + ArrangeSettingsView::GeometryHandling geom_handl = arr2::ArrangeSettingsView::ghConvex; + if (!geom_handling_str.empty()) { + int gh = std::stoi(geom_handling_str); + if(gh >= 0 && gh < ArrangeSettingsView::GeometryHandling::ghCount) + geom_handl = static_cast(gh); + } + + m_settings_sla.vals.geom_handling = geom_handl; + m_settings_fff.vals.geom_handling = geom_handl; + m_settings_fff_seq.vals.geom_handling = geom_handl; + + ArrangeSettingsView::ArrangeStrategy arr_strategy = arr2::ArrangeSettingsView::asAuto; + if (!strategy_str.empty()) { + int strateg = std::stoi(strategy_str); + if(strateg >= 0 && strateg < ArrangeSettingsView::ArrangeStrategy::asCount) + arr_strategy = static_cast(strateg); + } + + m_settings_sla.vals.arr_strategy = arr_strategy; + m_settings_fff.vals.arr_strategy = arr_strategy; + m_settings_fff_seq.vals.arr_strategy = arr_strategy; + + if (config()) { + // Set default obj distance for fff sequential print mode + m_settings_fff_seq.defaults.d_obj = + std::max(m_settings_fff_seq.defaults.d_obj, + float(min_object_distance(*config()))); + } +} + +void ArrangeSettingsDb_AppCfg::distance_from_obj_range(float &min, + float &max) const +{ + min = 0.f; + if (config() && current_printer_technology() == ptFFF) { + auto co_opt = config()->option("complete_objects"); + if (co_opt && co_opt->value) { + min = float(min_object_distance(*config())); + } + } + max = 100.f; +} + +void ArrangeSettingsDb_AppCfg::distance_from_bed_range(float &min, + float &max) const +{ + min = 0.f; + max = 100.f; +} + +arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_distance_from_objects(float v) +{ + Slot &slot = get_slot(this); + slot.vals.d_obj = v; + m_appcfg->set("arrange", "min_object_distance" + slot.postfix, + float_to_string_decimal_point(v)); + + return *this; +} + +arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_distance_from_bed(float v) +{ + Slot &slot = get_slot(this); + slot.vals.d_bed = v; + m_appcfg->set("arrange", "min_bed_distance" + slot.postfix, + float_to_string_decimal_point(v)); + + return *this; +} + +arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_rotation_enabled(bool v) +{ + Slot &slot = get_slot(this); + slot.vals.rotations = v; + m_appcfg->set("arrange", "enable_rotation" + slot.postfix, v ? "1" : "0"); + + return *this; +} + +arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_xl_alignment(XLPivots v) +{ + m_settings_fff.vals.xl_align = v; + m_appcfg->set("arrange", "alignment_xl", std::to_string(v)); + + return *this; +} + +arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_geometry_handling(GeometryHandling v) +{ + m_settings_fff.vals.geom_handling = v; + m_appcfg->set("arrange", "geometry_handling", std::to_string(v)); + + return *this; +} + +arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_arrange_strategy(ArrangeStrategy v) +{ + m_settings_fff.vals.arr_strategy = v; + m_appcfg->set("arrange", "arrange_strategy", std::to_string(v)); + + return *this; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp new file mode 100644 index 0000000000..908deb73bc --- /dev/null +++ b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp @@ -0,0 +1,78 @@ +#ifndef ARRANGESETTINGSDB_APPCFG_HPP +#define ARRANGESETTINGSDB_APPCFG_HPP + +#include "ArrangeSettingsView.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/PrintConfig.hpp" + +namespace Slic3r { + +class ArrangeSettingsDb_AppCfg: public arr2::ArrangeSettingsDb +{ + AppConfig *m_appcfg; + std::function m_config_getter; + std::function m_printtech_getter; + + struct Slot { Values vals; Values defaults; std::string postfix; }; + + // Settings and their defaults are stored separately for fff, + // sla and fff sequential mode + Slot m_settings_fff, m_settings_fff_seq, m_settings_sla; + + PrinterTechnology current_printer_technology() const; + const DynamicPrintConfig *config() const; + + template + static auto & get_slot(Self *self) { + PrinterTechnology ptech = self->current_printer_technology(); + + auto *ptr = &self->m_settings_fff; + + if (ptech == ptSLA) { + ptr = &self->m_settings_sla; + } else if (ptech == ptFFF && self->config()) { + auto co_opt = self->config()->template option( + "complete_objects"); + if (co_opt && co_opt->value) + ptr = &self->m_settings_fff_seq; + else + ptr = &self->m_settings_fff; + } + + return *ptr; + } + + template + static auto& get_ref(Self *self) { return get_slot(self).vals; } + +public: + explicit ArrangeSettingsDb_AppCfg( + AppConfig *appcfg, + std::function cfgfn, + std::function printtech_getter); + + float get_distance_from_objects() const override { return get_ref(this).d_obj; } + float get_distance_from_bed() const override { return get_ref(this).d_bed; } + bool is_rotation_enabled() const override { return get_ref(this).rotations; } + + XLPivots get_xl_alignment() const override { return m_settings_fff.vals.xl_align; } + GeometryHandling get_geometry_handling() const override { return m_settings_fff.vals.geom_handling; } + ArrangeStrategy get_arrange_strategy() const override { return m_settings_fff.vals.arr_strategy; } + + void distance_from_obj_range(float &min, float &max) const override; + void distance_from_bed_range(float &min, float &max) const override; + + ArrangeSettingsDb& set_distance_from_objects(float v) override; + ArrangeSettingsDb& set_distance_from_bed(float v) override; + ArrangeSettingsDb& set_rotation_enabled(bool v) override; + + ArrangeSettingsDb& set_xl_alignment(XLPivots v) override; + ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) override; + ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) override; + + Values get_defaults() const override { return get_slot(this).defaults; } +}; + +} // namespace Slic3r + +#endif // ARRANGESETTINGSDB_APPCFG_HPP diff --git a/src/libslic3r/Arrange/ArrangeSettingsView.hpp b/src/libslic3r/Arrange/ArrangeSettingsView.hpp new file mode 100644 index 0000000000..5bd63aeea0 --- /dev/null +++ b/src/libslic3r/Arrange/ArrangeSettingsView.hpp @@ -0,0 +1,118 @@ +#ifndef ARRANGESETTINGSVIEW_HPP +#define ARRANGESETTINGSVIEW_HPP + +namespace Slic3r { namespace arr2 { + +class ArrangeSettingsView +{ +public: + enum GeometryHandling { ghConvex, ghBalanced, ghAdvanced, ghCount }; + enum ArrangeStrategy { asAuto, asPullToCenter, asCount }; + enum XLPivots { + xlpCenter, + xlpRearLeft, + xlpFrontLeft, + xlpFrontRight, + xlpRearRight, + xlpRandom, + xlpCount + }; + + virtual ~ArrangeSettingsView() = default; + + virtual float get_distance_from_objects() const = 0; + virtual float get_distance_from_bed() const = 0; + virtual bool is_rotation_enabled() const = 0; + + virtual XLPivots get_xl_alignment() const = 0; + virtual GeometryHandling get_geometry_handling() const = 0; + virtual ArrangeStrategy get_arrange_strategy() const = 0; +}; + +class ArrangeSettingsDb: public ArrangeSettingsView +{ +public: + + virtual void distance_from_obj_range(float &min, float &max) const = 0; + virtual void distance_from_bed_range(float &min, float &max) const = 0; + + virtual ArrangeSettingsDb& set_distance_from_objects(float v) = 0; + virtual ArrangeSettingsDb& set_distance_from_bed(float v) = 0; + virtual ArrangeSettingsDb& set_rotation_enabled(bool v) = 0; + + virtual ArrangeSettingsDb& set_xl_alignment(XLPivots v) = 0; + virtual ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) = 0; + virtual ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) = 0; + + struct Values { + float d_obj = 6.f, d_bed = 0.f; + bool rotations = false; + XLPivots xl_align = XLPivots::xlpFrontLeft; + GeometryHandling geom_handling = GeometryHandling::ghConvex; + ArrangeStrategy arr_strategy = ArrangeStrategy::asAuto; + + Values() = default; + Values(const ArrangeSettingsView &sv) + { + d_bed = sv.get_distance_from_bed(); + d_obj = sv.get_distance_from_objects(); + arr_strategy = sv.get_arrange_strategy(); + geom_handling = sv.get_geometry_handling(); + rotations = sv.is_rotation_enabled(); + xl_align = sv.get_xl_alignment(); + } + }; + + virtual Values get_defaults() const { return {}; } + + ArrangeSettingsDb& set_from(const ArrangeSettingsView &sv) + { + set_distance_from_bed(sv.get_distance_from_bed()); + set_distance_from_objects(sv.get_distance_from_objects()); + set_arrange_strategy(sv.get_arrange_strategy()); + set_geometry_handling(sv.get_geometry_handling()); + set_rotation_enabled(sv.is_rotation_enabled()); + set_xl_alignment(sv.get_xl_alignment()); + + return *this; + } +}; + +class ArrangeSettings: public Slic3r::arr2::ArrangeSettingsDb +{ + ArrangeSettingsDb::Values m_v = {}; + +public: + explicit ArrangeSettings( + const ArrangeSettingsDb::Values &v = {}) + : m_v{v} + {} + + explicit ArrangeSettings(const ArrangeSettingsView &v) + : m_v{v} + {} + + float get_distance_from_objects() const override { return m_v.d_obj; } + float get_distance_from_bed() const override { return m_v.d_bed; } + bool is_rotation_enabled() const override { return m_v.rotations; } + XLPivots get_xl_alignment() const override { return m_v.xl_align; } + GeometryHandling get_geometry_handling() const override { return m_v.geom_handling; } + ArrangeStrategy get_arrange_strategy() const override { return m_v.arr_strategy; } + + void distance_from_obj_range(float &min, float &max) const override { min = 0.f; max = 100.f; } + void distance_from_bed_range(float &min, float &max) const override { min = 0.f; max = 100.f; } + + ArrangeSettingsDb& set_distance_from_objects(float v) override { m_v.d_obj = v; return *this; } + ArrangeSettingsDb& set_distance_from_bed(float v) override { m_v.d_bed = v; return *this; } + ArrangeSettingsDb& set_rotation_enabled(bool v) override { m_v.rotations = v; return *this; } + ArrangeSettingsDb& set_xl_alignment(XLPivots v) override { m_v.xl_align = v; return *this; } + ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) override { m_v.geom_handling = v; return *this; } + ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) override { m_v.arr_strategy = v; return *this; } + + auto & values() const { return m_v; } + auto & values() { return m_v; } +}; + +}} // namespace Slic3r::arr2 + +#endif // ARRANGESETTINGSVIEW_HPP diff --git a/src/libslic3r/Arrange/Core/ArrangeBase.hpp b/src/libslic3r/Arrange/Core/ArrangeBase.hpp new file mode 100644 index 0000000000..97177d4b3c --- /dev/null +++ b/src/libslic3r/Arrange/Core/ArrangeBase.hpp @@ -0,0 +1,258 @@ +#ifndef ARRANGEBASE_HPP +#define ARRANGEBASE_HPP + +#include +#include + +#include "ArrangeItemTraits.hpp" +#include "PackingContext.hpp" + +#include "libslic3r/Point.hpp" + +namespace Slic3r { namespace arr2 { + +namespace detail_is_const_it { + +template +struct IsConstIt_ { static constexpr bool value = false; }; + +template +using iterator_category_t = typename std::iterator_traits::iterator_category; + +template +using iterator_reference_t = typename std::iterator_traits::reference; + +template +struct IsConstIt_ >> > +{ + static constexpr bool value = + std::is_const_v>>; +}; + +} // namespace detail_is_const_it + +template +static constexpr bool IsConstIterator = detail_is_const_it::IsConstIt_::value; + +template +constexpr bool is_const_iterator(const It &it) noexcept { return IsConstIterator; } + +// The pack() function will use tag dispatching, based on the given strategy +// object that is used as its first argument. + +// This tag is derived for a packing strategy as default, and will be used +// to cast a compile error. +struct UnimplementedPacking {}; + +// PackStrategyTag_ needs to be specialized for any valid packing strategy class +template struct PackStrategyTag_ { + using Tag = UnimplementedPacking; +}; + +// Helper metafunc to derive packing strategy tag from a strategy object. +template +using PackStrategyTag = + typename PackStrategyTag_>::Tag; + + +template struct PackStrategyTraits_ { + template using Context = DefaultPackingContext; + + template + static Context create_context(PackStrategy &ps, + const Bed &bed, + int bed_index) + { + return {}; + } +}; + +template using PackStrategyTraits = PackStrategyTraits_>; + +template +using PackStrategyContext = + typename PackStrategyTraits::template Context>; + +template +PackStrategyContext create_context(PackStrategy &&ps, + const Bed &bed, + int bed_index) +{ + return PackStrategyTraits::template create_context< + StripCVRef>(ps, bed, bed_index); +} + +// Function to pack one item into a bed. +// strategy parameter holds clue to what packing strategy to use. This function +// needs to be overloaded for the strategy tag belonging to the given +// strategy. +// 'bed' parameter is the type of bed into which the new item should be packed. +// See beds.hpp for valid bed classes. +// 'item' parameter is the item to be packed. After succesful arrangement +// (see return value) the item will have it's translation and rotation +// set correctly. If the function returns false, the translation and +// rotation of the input item might be changed to arbitrary values. +// 'fixed_items' paramter holds a range of ArrItem type objects that are already +// on the bed and need to be avoided by the newly packed item. +// 'remaining_items' is a range of ArrItem type objects that are intended to be +// packed in the future. This information can be leveradged by +// the packing strategy to make more intelligent placement +// decisions for the input item. +template +bool pack(Strategy &&strategy, + const Bed &bed, + ArrItem &item, + const PackStrategyContext &context, + const Range &remaining_items) +{ + static_assert(IsConstIterator, "Remaining item iterator is not const!"); + + // Dispatch: + return pack(std::forward(strategy), bed, item, context, + remaining_items, PackStrategyTag{}); +} + +// Overload without fixed items: +template +bool pack(Strategy &&strategy, const Bed &bed, ArrItem &item) +{ + std::vector dummy; + auto context = create_context(strategy, bed, PhysicalBedId); + return pack(std::forward(strategy), bed, item, context, + crange(dummy)); +} + +// Overload when strategy is unkown, yields compile error: +template +bool pack(Strategy &&strategy, + const Bed &bed, + ArrItem &item, + const PackStrategyContext &context, + const Range &remaining_items, + const UnimplementedPacking &) +{ + static_assert(always_false::value, + "Packing unimplemented for this placement strategy"); + + return false; +} + +// Helper function to remove unpackable items from the input container. +template +void remove_unpackable_items(PackStrategy &&ps, + Container &c, + const Bed &bed, + const StopCond &stopcond) +{ + // Safety test: try to pack each item into an empty bed. If it fails + // then it should be removed from the list + auto it = c.begin(); + while (it != c.end() && !stopcond()) { + StripCVRef &itm = *it; + auto cpy{itm}; + + if (!pack(ps, bed, cpy)) { + set_bed_index(itm, Unarranged); + it = c.erase(it); + } else + it++; + } +} + +// arrange() function will use tag dispatching based on the selection strategy +// given as its first argument. + +// This tag is derived for a selection strategy as default, and will be used +// to cast a compile error. +struct UnimplementedSelection {}; + +// SelStrategyTag_ needs to be specialized for any valid selection strategy class +template struct SelStrategyTag_ { + using Tag = UnimplementedSelection; +}; + +// Helper metafunc to derive the selection strategy tag from a strategy object. +template +using SelStrategyTag = typename SelStrategyTag_>::Tag; + +// Main function to start the arrangement. Takes a selection and a packing +// strategy object as the first two parameters. An implementation +// (function overload) must exist for this function that takes the coresponding +// selection strategy tag belonging to the given selstrategy argument. +// +// items parameter is a range of arrange items to arrange. +// fixed parameter is a range of arrange items that have fixed position and will +// not move during the arrangement but need to be avoided by the +// moving items. +// bed parameter is the type of bed into which the items need to fit. +template +void arrange(SelectionStrategy &&selstrategy, + PackStrategy &&packingstrategy, + const Range &items, + const Range &fixed, + const TBed &bed) +{ + static_assert(IsConstIterator, "Fixed item iterator is not const!"); + + // Dispatch: + arrange(std::forward(selstrategy), + std::forward(packingstrategy), items, fixed, bed, + SelStrategyTag{}); +} + +template +void arrange(SelectionStrategy &&selstrategy, + PackStrategy &&packingstrategy, + const Range &items, + const TBed &bed) +{ + std::vector::value_type> dummy; + arrange(std::forward(selstrategy), + std::forward(packingstrategy), items, crange(dummy), + bed); +} + +// Overload for unimplemented selection strategy, yields compile error: +template +void arrange(SelectionStrategy &&selstrategy, + PackStrategy &&packingstrategy, + const Range &items, + const Range &fixed, + const TBed &bed, + const UnimplementedSelection &) +{ + static_assert(always_false::value, + "Arrange unimplemented for this selection strategy"); +} + +template +size_t get_bed_count(const Range &items) +{ + auto it = std::max_element(items.begin(), + items.end(), + [](auto &i1, auto &i2) { + return get_bed_index(i1) < get_bed_index(i2); + }); + + size_t beds = 0; + if (it != items.end()) + beds = get_bed_index(*it) + 1; + + return beds; +} + +struct DefaultStopCondition { + constexpr bool operator()() const noexcept { return false; } +}; + +}} // namespace Slic3r::arr2 + +#endif // ARRANGEBASE_HPP diff --git a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp new file mode 100644 index 0000000000..ee6940b770 --- /dev/null +++ b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp @@ -0,0 +1,161 @@ +#ifndef ARRANGEFIRSTFIT_HPP +#define ARRANGEFIRSTFIT_HPP + +#include + +#include + +namespace Slic3r { namespace arr2 { namespace firstfit { + +struct SelectionTag {}; + +// Can be specialized by Items +template +struct ItemArrangedVisitor { + template + static void on_arranged(ArrItem &itm, + const Bed &bed, + const Range &packed_items, + const Range &remaining_items) + {} +}; + +// Use the the visitor baked into the ArrItem type by default +struct DefaultOnArrangedFn { + template + void operator()(ArrItem &itm, + const Bed &bed, + const Range &packed, + const Range &remaining) + { + ItemArrangedVisitor>::on_arranged(itm, bed, packed, + remaining); + } +}; + +struct DefaultItemCompareFn { + template + bool operator() (const ArrItem &ia, const ArrItem &ib) + { + return get_priority(ia) > get_priority(ib); + } +}; + +template +struct SelectionStrategy +{ + CompareFn cmpfn; + OnArrangedFn on_arranged_fn; + StopCondition cancel_fn; + + SelectionStrategy(CompareFn cmp = {}, + OnArrangedFn on_arranged = {}, + StopCondition stopcond = {}) + : cmpfn{cmp}, + on_arranged_fn{std::move(on_arranged)}, + cancel_fn{std::move(stopcond)} + {} +}; + +} // namespace firstfit + +template struct SelStrategyTag_> { + using Tag = firstfit::SelectionTag; +}; + +template +void arrange( + SelStrategy &&sel, + PackStrategy &&ps, + const Range &items, + const Range &fixed, + const TBed &bed, + const firstfit::SelectionTag &) +{ + using ArrItem = typename std::iterator_traits::value_type; + using ArrItemRef = std::reference_wrapper; + + auto sorted_items = reserve_vector(items.size()); + + for (auto &itm : items) { + set_bed_index(itm, Unarranged); + sorted_items.emplace_back(itm); + } + + int max_bed_idx = get_bed_count(fixed); + + using Context = PackStrategyContext; + + auto bed_contexts = reserve_vector(max_bed_idx + 1); + + for (auto &itm : fixed) { + if (get_bed_index(itm) >= 0) { + auto bedidx = static_cast(get_bed_index(itm)); + + while (bed_contexts.size() <= bedidx) + bed_contexts.emplace_back( + create_context(ps, bed, bedidx)); + + add_fixed_item(bed_contexts[bedidx], itm); + } + } + + if constexpr (!std::is_null_pointer_v) { + std::stable_sort(sorted_items.begin(), sorted_items.end(), sel.cmpfn); + } + + auto is_cancelled = [&sel]() { + return sel.cancel_fn(); + }; + + remove_unpackable_items(ps, sorted_items, bed, [&is_cancelled]() { + return is_cancelled(); + }); + + auto it = sorted_items.begin(); + + using SConstIt = typename std::vector::const_iterator; + + while (it != sorted_items.end() && !is_cancelled()) { + bool was_packed = false; + size_t j = 0; + while (!was_packed && !is_cancelled()) { + for (; j < bed_contexts.size() && !was_packed && !is_cancelled(); j++) { + set_bed_index(*it, int(j)); + + auto remaining = Range{std::next(static_cast(it)), + sorted_items.cend()}; + + was_packed = pack(ps, bed, *it, bed_contexts[j], remaining); + + if(was_packed) { + add_packed_item(bed_contexts[j], *it); + + auto packed_range = Range{sorted_items.cbegin(), + static_cast(it)}; + + sel.on_arranged_fn(*it, bed, packed_range, remaining); + } else { + set_bed_index(*it, Unarranged); + } + } + + if (!was_packed) { + bed_contexts.emplace_back( + create_context(ps, bed, bed_contexts.size())); + j = bed_contexts.size() - 1; + } + } + ++it; + } +} + +}} // namespace Slic3r::arr2 + +#endif // ARRANGEFIRSTFIT_HPP diff --git a/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp b/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp new file mode 100644 index 0000000000..5806627eb1 --- /dev/null +++ b/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp @@ -0,0 +1,108 @@ +#ifndef ARRANGE_ITEM_TRAITS_HPP +#define ARRANGE_ITEM_TRAITS_HPP + +#include + +namespace Slic3r { namespace arr2 { + +// A logical bed representing an object not being arranged. Either the arrange +// has not yet successfully run on this ArrangePolygon or it could not fit the +// object due to overly large size or invalid geometry. +const constexpr int Unarranged = -1; + +const constexpr int PhysicalBedId = 0; + +// Basic interface of an arrange item. This struct can be specialized for any +// type that is arrangeable. +template struct ArrangeItemTraits_ { + static Vec2crd get_translation(const ArrItem &ap) + { + return ap.get_translation(); + } + + static double get_rotation(const ArrItem &ap) + { + return ap.get_rotation(); + } + + static int get_bed_index(const ArrItem &ap) { return ap.get_bed_index(); } + + static int get_priority(const ArrItem &ap) { return ap.get_priority(); } + + // Setters: + + static void set_translation(ArrItem &ap, const Vec2crd &v) + { + ap.set_translation(v); + } + + static void set_rotation(ArrItem &ap, double v) { ap.set_rotation(v); } + + static void set_bed_index(ArrItem &ap, int v) { ap.set_bed_index(v); } +}; + +template using ArrangeItemTraits = ArrangeItemTraits_>; + +// Getters: + +template Vec2crd get_translation(const T &itm) +{ + return ArrangeItemTraits::get_translation(itm); +} + +template double get_rotation(const T &itm) +{ + return ArrangeItemTraits::get_rotation(itm); +} + +template int get_bed_index(const T &itm) +{ + return ArrangeItemTraits::get_bed_index(itm); +} + +template int get_priority(const T &itm) +{ + return ArrangeItemTraits::get_priority(itm); +} + +// Setters: + +template void set_translation(T &itm, const Vec2crd &v) +{ + ArrangeItemTraits::set_translation(itm, v); +} + +template void set_rotation(T &itm, double v) +{ + ArrangeItemTraits::set_rotation(itm, v); +} + +template void set_bed_index(T &itm, int v) +{ + ArrangeItemTraits::set_bed_index(itm, v); +} + +// Helper functions for arrange items +template bool is_arranged(const ArrItem &ap) +{ + return get_bed_index(ap) > Unarranged; +} + +template bool is_fixed(const ArrItem &ap) +{ + return get_bed_index(ap) >= PhysicalBedId; +} + +template void translate(ArrItem &ap, const Vec2crd &t) +{ + set_translation(ap, get_translation(ap) + t); +} + +template void rotate(ArrItem &ap, double rads) +{ + set_rotation(ap, get_rotation(ap) + rads); +} + +}} // namespace Slic3r::arr2 + +#endif // ARRANGE_ITEM_HPP diff --git a/src/libslic3r/Arrange/Core/Beds.cpp b/src/libslic3r/Arrange/Core/Beds.cpp new file mode 100644 index 0000000000..90d5f4f77d --- /dev/null +++ b/src/libslic3r/Arrange/Core/Beds.cpp @@ -0,0 +1,129 @@ +#include "Beds.hpp" + +namespace Slic3r { namespace arr2 { + +BoundingBox bounding_box(const InfiniteBed &bed) +{ + BoundingBox ret; + using C = coord_t; + + // It is important for Mx and My to be strictly less than half of the + // range of type C. width(), height() and area() will not overflow this way. + C Mx = C((std::numeric_limits::lowest() + 2 * bed.center.x()) / 4.01); + C My = C((std::numeric_limits::lowest() + 2 * bed.center.y()) / 4.01); + + ret.max = bed.center - Point{Mx, My}; + ret.min = bed.center + Point{Mx, My}; + + return ret; +} + +Polygon to_rectangle(const BoundingBox &bb) +{ + Polygon ret; + ret.points = { + bb.min, + Point{bb.max.x(), bb.min.y()}, + bb.max, + Point{bb.min.x(), bb.max.y()} + }; + + return ret; +} + +Polygon approximate_circle_with_polygon(const arr2::CircleBed &bed, int nedges) +{ + Polygon ret; + + double angle_incr = (2 * M_PI) / nedges; // Angle increment for each edge + double angle = 0; // Starting angle + + // Loop to generate vertices for each edge + for (int i = 0; i < nedges; i++) { + // Calculate coordinates of the vertices using trigonometry + auto x = bed.center().x() + static_cast(bed.radius() * std::cos(angle)); + auto y = bed.center().y() + static_cast(bed.radius() * std::sin(angle)); + + // Add vertex to the vector + ret.points.emplace_back(x, y); + + // Update the angle for the next iteration + angle += angle_incr; + } + + return ret; +} + +inline coord_t width(const BoundingBox &box) +{ + return box.max.x() - box.min.x(); +} +inline coord_t height(const BoundingBox &box) +{ + return box.max.y() - box.min.y(); +} +inline double poly_area(const Points &pts) +{ + return std::abs(Polygon::area(pts)); +} +inline double distance_to(const Point &p1, const Point &p2) +{ + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + return std::sqrt(dx * dx + dy * dy); +} + +static CircleBed to_circle(const Point ¢er, const Points &points) +{ + std::vector vertex_distances; + double avg_dist = 0; + + for (const Point &pt : points) { + double distance = distance_to(center, pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + CircleBed ret(center, avg_dist); + for (auto el : vertex_distances) { + if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { + ret = {}; + break; + } + } + + return ret; +} + +template auto call_with_bed(const Points &bed, Fn &&fn) +{ + if (bed.empty()) + return fn(InfiniteBed{}); + else if (bed.size() == 1) + return fn(InfiniteBed{bed.front()}); + else { + auto bb = BoundingBox(bed); + CircleBed circ = to_circle(bb.center(), bed); + auto parea = poly_area(bed); + + if ((1.0 - parea / area(bb)) < 1e-3) { + return fn(RectangleBed{bb}); + } else if (!std::isnan(circ.radius())) + return fn(circ); + else + return fn(IrregularBed{{ExPolygon(bed)}}); + } +} + +ArrangeBed to_arrange_bed(const Points &bedpts) +{ + ArrangeBed ret; + + call_with_bed(bedpts, [&](const auto &bed) { ret = bed; }); + + return ret; +} + +}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Core/Beds.hpp b/src/libslic3r/Arrange/Core/Beds.hpp new file mode 100644 index 0000000000..e49cb9c7ca --- /dev/null +++ b/src/libslic3r/Arrange/Core/Beds.hpp @@ -0,0 +1,191 @@ +#ifndef BEDS_HPP +#define BEDS_HPP + +#include + +#include +#include +#include +#include + +#include + +namespace Slic3r { namespace arr2 { + +// Bed types to be used with arrangement. Most generic bed is a simple polygon +// with holes, but other special bed types are also valid, like a bed without +// boundaries, or a special case of a rectangular or circular bed which leaves +// a lot of room for optimizations. + +// Representing an unbounded bed. +struct InfiniteBed { + Point center; + explicit InfiniteBed(const Point &p = {0, 0}): center{p} {} +}; + +BoundingBox bounding_box(const InfiniteBed &bed); + +inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; } + +struct RectangleBed { + BoundingBox bb; + + explicit RectangleBed(const BoundingBox &bedbb) : bb{bedbb} {} + explicit RectangleBed(coord_t w, coord_t h, Point c = {0, 0}): + bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}} + {} + + coord_t width() const { return bb.size().x(); } + coord_t height() const { return bb.size().y(); } +}; + +inline BoundingBox bounding_box(const RectangleBed &bed) { return bed.bb; } +inline RectangleBed offset(RectangleBed bed, coord_t v) +{ + bed.bb.offset(v); + return bed; +} + +Polygon to_rectangle(const BoundingBox &bb); + +inline Polygon to_rectangle(const RectangleBed &bed) +{ + return to_rectangle(bed.bb); +} + +class CircleBed { + Point m_center; + double m_radius; + +public: + CircleBed(): m_center(0, 0), m_radius(NaNd) {} + explicit CircleBed(const Point& c, double r) + : m_center(c) + , m_radius(r) + {} + + double radius() const { return m_radius; } + const Point& center() const { return m_center; } +}; + +// Function to approximate a circle with a convex polygon +Polygon approximate_circle_with_polygon(const CircleBed &bed, int nedges = 24); + +inline BoundingBox bounding_box(const CircleBed &bed) +{ + auto r = static_cast(std::round(bed.radius())); + Point R{r, r}; + + return {bed.center() - R, bed.center() + R}; +} +inline CircleBed offset(const CircleBed &bed, coord_t v) +{ + return CircleBed{bed.center(), bed.radius() + v}; +} + +struct IrregularBed { ExPolygons poly; }; +inline BoundingBox bounding_box(const IrregularBed &bed) +{ + return get_extents(bed.poly); +} + +inline IrregularBed offset(IrregularBed bed, coord_t v) +{ + bed.poly = offset_ex(bed.poly, v); + return bed; +} + +using ArrangeBed = + boost::variant; + +inline BoundingBox bounding_box(const ArrangeBed &bed) +{ + BoundingBox ret; + auto visitor = [&ret](const auto &b) { ret = bounding_box(b); }; + boost::apply_visitor(visitor, bed); + + return ret; +} + +inline ArrangeBed offset(ArrangeBed bed, coord_t v) +{ + auto visitor = [v](auto &b) { b = offset(b, v); }; + boost::apply_visitor(visitor, bed); + + return bed; +} + +inline double area(const BoundingBox &bb) +{ + auto bbsz = bb.size(); + return double(bbsz.x()) * bbsz.y(); +} + +inline double area(const RectangleBed &bed) +{ + auto bbsz = bed.bb.size(); + return double(bbsz.x()) * bbsz.y(); +} + +inline double area(const InfiniteBed &bed) +{ + return std::numeric_limits::infinity(); +} + +inline double area(const IrregularBed &bed) +{ + return std::accumulate(bed.poly.begin(), bed.poly.end(), 0., + [](double s, auto &p) { return s + p.area(); }); +} + +inline double area(const CircleBed &bed) +{ + return bed.radius() * bed.radius() * PI; +} + +inline double area(const ArrangeBed &bed) +{ + double ret = 0.; + auto visitor = [&ret](auto &b) { ret = area(b); }; + boost::apply_visitor(visitor, bed); + + return ret; +} + +inline ExPolygons to_expolygons(const InfiniteBed &bed) +{ + return {ExPolygon{to_rectangle(RectangleBed{scaled(1000.), scaled(1000.)})}}; +} + +inline ExPolygons to_expolygons(const RectangleBed &bed) +{ + return {ExPolygon{to_rectangle(bed)}}; +} + +inline ExPolygons to_expolygons(const CircleBed &bed) +{ + return {ExPolygon{approximate_circle_with_polygon(bed)}}; +} + +inline ExPolygons to_expolygons(const IrregularBed &bed) { return bed.poly; } + +inline ExPolygons to_expolygons(const ArrangeBed &bed) +{ + ExPolygons ret; + auto visitor = [&ret](const auto &b) { ret = to_expolygons(b); }; + boost::apply_visitor(visitor, bed); + + return ret; +} + +ArrangeBed to_arrange_bed(const Points &bedpts); + +} // namespace arr2 + +inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; } +inline const BoundingBox &bounding_box(const BoundingBox &bb) { return bb; } +inline BoundingBox bounding_box(const Polygon &p) { return get_extents(p); } + +} // namespace Slic3r + +#endif // BEDS_HPP diff --git a/src/libslic3r/Arrange/Core/DataStoreTraits.hpp b/src/libslic3r/Arrange/Core/DataStoreTraits.hpp new file mode 100644 index 0000000000..4aca48639d --- /dev/null +++ b/src/libslic3r/Arrange/Core/DataStoreTraits.hpp @@ -0,0 +1,78 @@ +#ifndef DATASTORETRAITS_HPP +#define DATASTORETRAITS_HPP + +#include + +#include "libslic3r/libslic3r.h" + +namespace Slic3r { namespace arr2 { + +// Some items can be containers of arbitrary data stored under string keys. +template struct DataStoreTraits_ +{ + static constexpr bool Implemented = false; + + template static const T *get(const ArrItem &, const std::string &key) + { + return nullptr; + } + + // Same as above just not const. + template static T *get(ArrItem &, const std::string &key) + { + return nullptr; + } + + static bool has_key(const ArrItem &itm, const std::string &key) + { + return false; + } +}; + +template struct WritableDataStoreTraits_ +{ + static constexpr bool Implemented = false; + + template static void set(ArrItem &, const std::string &key, T &&data) + { + } +}; + +template using DataStoreTraits = DataStoreTraits_>; +template constexpr bool IsDataStore = DataStoreTraits>::Implemented; +template using DataStoreOnly = std::enable_if_t, TT>; + +template +const T *get_data(const ArrItem &itm, const std::string &key) +{ + return DataStoreTraits::template get(itm, key); +} + +template +bool has_key(const ArrItem &itm, const std::string &key) +{ + return DataStoreTraits::has_key(itm, key); +} + +template +T *get_data(ArrItem &itm, const std::string &key) +{ + return DataStoreTraits::template get(itm, key); +} + +template using WritableDataStoreTraits = WritableDataStoreTraits_>; +template constexpr bool IsWritableDataStore = WritableDataStoreTraits>::Implemented; +template using WritableDataStoreOnly = std::enable_if_t, TT>; + +template +void set_data(ArrItem &itm, const std::string &key, T &&data) +{ + WritableDataStoreTraits::template set(itm, key, std::forward(data)); +} + +template constexpr bool IsReadWritableDataStore = IsDataStore && IsWritableDataStore; +template using ReadWritableDataStoreOnly = std::enable_if_t, TT>; + +}} // namespace Slic3r::arr2 + +#endif // DATASTORETRAITS_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp b/src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp new file mode 100644 index 0000000000..3370530756 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp @@ -0,0 +1,110 @@ +#ifndef CIRCULAR_EDGEITERATOR_HPP +#define CIRCULAR_EDGEITERATOR_HPP + +#include +#include + +namespace Slic3r { + +// Circular iterator over a polygon yielding individual edges as Line objects +// if flip_lines is true, the orientation of each line is flipped (not the +// direction of traversal) +template +class CircularEdgeIterator_ { + const Polygon *m_poly = nullptr; + size_t m_i = 0; + size_t m_c = 0; // counting how many times the iterator has circled over + +public: + + // i: vertex position of first line's starting vertex + // poly: target polygon + CircularEdgeIterator_(size_t i, const Polygon &poly) + : m_poly{&poly} + , m_i{!poly.empty() ? i % poly.size() : 0} + , m_c{!poly.empty() ? i / poly.size() : 0} + {} + + explicit CircularEdgeIterator_ (const Polygon &poly) + : CircularEdgeIterator_(0, poly) {} + + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Line; + using pointer = Line*; + using reference = Line&; + + CircularEdgeIterator_ & operator++() + { + assert (m_poly); + ++m_i; + if (m_i == m_poly->size()) { // faster than modulo (?) + m_i = 0; + ++m_c; + } + + return *this; + } + + CircularEdgeIterator_ operator++(int) + { + auto cpy = *this; ++(*this); return cpy; + } + + Line operator*() const + { + size_t nx = m_i == m_poly->size() - 1 ? 0 : m_i + 1; + Line ret; + if constexpr (flip_lines) + ret = Line((*m_poly)[nx], (*m_poly)[m_i]); + else + ret = Line((*m_poly)[m_i], (*m_poly)[nx]); + + return ret; + } + + Line operator->() const { return *(*this); } + + bool operator==(const CircularEdgeIterator_& other) const + { + return m_i == other.m_i && m_c == other.m_c; + } + + bool operator!=(const CircularEdgeIterator_& other) const + { + return !(*this == other); + } + + CircularEdgeIterator_& operator +=(size_t dist) + { + m_i = (m_i + dist) % m_poly->size(); + m_c = (m_i + (m_c * m_poly->size()) + dist) / m_poly->size(); + + return *this; + } + + CircularEdgeIterator_ operator +(size_t dist) + { + auto cpy = *this; + cpy += dist; + + return cpy; + } +}; + +using CircularEdgeIterator = CircularEdgeIterator_<>; +using CircularReverseEdgeIterator = CircularEdgeIterator_; + +inline Range line_range(const Polygon &poly) +{ + return Range{CircularEdgeIterator{0, poly}, CircularEdgeIterator{poly.size(), poly}}; +} + +inline Range line_range_flp(const Polygon &poly) +{ + return Range{CircularReverseEdgeIterator{0, poly}, CircularReverseEdgeIterator{poly.size(), poly}}; +} + +} // namespace Slic3r + +#endif // CIRCULAR_EDGEITERATOR_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp b/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp new file mode 100644 index 0000000000..03e52fe0ab --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp @@ -0,0 +1,92 @@ +#include "EdgeCache.hpp" +#include "CircularEdgeIterator.hpp" + +namespace Slic3r { namespace arr2 { + +void EdgeCache::create_cache(const ExPolygon &sh) +{ + m_contour.distances.reserve(sh.contour.size()); + m_holes.reserve(sh.holes.size()); + + m_contour.poly = &sh.contour; + + fill_distances(sh.contour, m_contour.distances); + + for (const Polygon &hole : sh.holes) { + auto &hc = m_holes.emplace_back(); + hc.poly = &hole; + fill_distances(hole, hc.distances); + } +} + +Vec2crd EdgeCache::coords(const ContourCache &cache, double distance) const +{ + assert(cache.poly); + return arr2::coords(*cache.poly, cache.distances, distance); +} + +void EdgeCache::sample_contour(double accuracy, std::vector &samples) +{ + const auto N = m_contour.distances.size(); + const auto S = stride(N, accuracy); + + samples.reserve(N / S + 1); + for(size_t i = 0; i < N; i += S) { + samples.emplace_back( + ContourLocation{0, m_contour.distances[i] / m_contour.distances.back()}); + } + + for (size_t hidx = 1; hidx <= m_holes.size(); ++hidx) { + auto& hc = m_holes[hidx - 1]; + + const auto NH = hc.distances.size(); + const auto SH = stride(NH, accuracy); + samples.reserve(samples.size() + NH / SH + 1); + for (size_t i = 0; i < NH; i += SH) { + samples.emplace_back( + ContourLocation{hidx, hc.distances[i] / hc.distances.back()}); + } + } +} + +Vec2crd coords(const Polygon &poly, const std::vector &distances, double distance) +{ + assert(poly.size() > 1 && distance >= .0 && distance <= 1.0); + + // distance is from 0.0 to 1.0, we scale it up to the full length of + // the circumference + double d = distance * distances.back(); + + // Magic: we find the right edge in log time + auto it = std::lower_bound(distances.begin(), distances.end(), d); + + assert(it != distances.end()); + + auto idx = it - distances.begin(); // get the index of the edge + auto &pts = poly.points; + auto edge = idx == long(pts.size() - 1) ? Line(pts.back(), pts.front()) : + Line(pts[idx], pts[idx + 1]); + + // Get the remaining distance on the target edge + auto ed = d - (idx > 0 ? *std::prev(it) : 0 ); + + double t = ed / edge.length(); + Vec2d n {double(edge.b.x()) - edge.a.x(), double(edge.b.y()) - edge.a.y()}; + Vec2crd ret = (edge.a.cast() + t * n).cast(); + + return ret; +} + +void fill_distances(const Polygon &poly, std::vector &distances) +{ + distances.reserve(poly.size()); + + double dist = 0.; + auto lrange = line_range(poly); + for (const Line &l : lrange) { + dist += l.length(); + distances.emplace_back(dist); + } +} + +}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp b/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp new file mode 100644 index 0000000000..8146df1f8f --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp @@ -0,0 +1,70 @@ +#ifndef EDGECACHE_HPP +#define EDGECACHE_HPP + +#include + +#include + +namespace Slic3r { namespace arr2 { + +// Position on the circumference of an ExPolygon. +// countour_id: 0th is contour, 1..N are holes +// dist: position given as a floating point number within <0., 1.> +struct ContourLocation { size_t contour_id; double dist; }; + +void fill_distances(const Polygon &poly, std::vector &distances); + +Vec2crd coords(const Polygon &poly, const std::vector& distances, double distance); + +// A class for getting a point on the circumference of the polygon (in log time) +// +// This is a transformation of the provided polygon to be able to pinpoint +// locations on the circumference. The optimizer will pass a floating point +// value e.g. within <0,1> and we have to transform this value quickly into a +// coordinate on the circumference. By definition 0 should yield the first +// vertex and 1.0 would be the last (which should coincide with first). +// +// We also have to make this work for the holes of the captured polygon. +class EdgeCache { + struct ContourCache { + const Polygon *poly; + std::vector distances; + } m_contour; + + std::vector m_holes; + + void create_cache(const ExPolygon& sh); + + Vec2crd coords(const ContourCache& cache, double distance) const; + +public: + + explicit EdgeCache(const ExPolygon *sh) + { + create_cache(*sh); + } + + // Given coeff for accuracy <0., 1.>, return the number of vertices to skip + // when fetching corners. + static inline size_t stride(const size_t N, double accuracy) + { + return static_cast( + std::round(N / std::pow(N, std::pow(accuracy, 1./3.))) + ); + } + + void sample_contour(double accuracy, std::vector &samples); + + Vec2crd coords(const ContourLocation &loc) const + { + assert(loc.contour_id <= m_holes.size()); + + return loc.contour_id > 0 ? + coords(m_holes[loc.contour_id - 1], loc.dist) : + coords(m_contour, loc.dist); + } +}; + +}} // namespace Slic3r::arr2 + +#endif // EDGECACHE_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp new file mode 100644 index 0000000000..c476774d12 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp @@ -0,0 +1,61 @@ +#ifndef COMPACTIFYKERNEL_HPP +#define COMPACTIFYKERNEL_HPP + +#include + +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" + +#include +#include + +#include "KernelUtils.hpp" + +namespace Slic3r { namespace arr2 { + +struct CompactifyKernel { + ExPolygons merged_pile; + + template + double placement_fitness(const ArrItem &itm, const Vec2crd &transl) const + { + auto pile = merged_pile; + + ExPolygons itm_tr = to_expolygons(envelope_outline(itm)); + for (auto &p : itm_tr) + p.translate(transl); + + append(pile, std::move(itm_tr)); + + pile = union_ex(pile); + + Polygon chull = Geometry::convex_hull(pile); + + return -(chull.area()); + } + + template + bool on_start_packing(ArrItem &itm, + const Bed &bed, + const Context &packing_context, + const Range & /*remaining_items*/) + { + bool ret = find_initial_position(itm, bounding_box(bed).center(), bed, + packing_context); + + merged_pile.clear(); + for (const auto &gitm : all_items_range(packing_context)) { + append(merged_pile, to_expolygons(fixed_outline(gitm))); + } + merged_pile = union_ex(merged_pile); + + return ret; + } + + template + bool on_item_packed(ArrItem &itm) { return true; } +}; + +}} // namespace Slic3r::arr2 + +#endif // COMPACTIFYKERNEL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp new file mode 100644 index 0000000000..ba4f503c76 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp @@ -0,0 +1,58 @@ +#ifndef GRAVITYKERNEL_HPP +#define GRAVITYKERNEL_HPP + +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" + +#include "KernelUtils.hpp" + +namespace Slic3r { namespace arr2 { + +struct GravityKernel { + std::optional sink; + std::optional item_sink; + Vec2d active_sink; + + GravityKernel(Vec2crd gravity_center) : sink{gravity_center} {} + GravityKernel() = default; + + template + double placement_fitness(const ArrItem &itm, const Vec2crd &transl) const + { + Vec2d center = unscaled(envelope_bounding_box(itm).center()); + + center += unscaled(transl); + + return - (center - active_sink).squaredNorm(); + } + + template + bool on_start_packing(ArrItem &itm, + const Bed &bed, + const Ctx &packing_context, + const Range & /*remaining_items*/) + { + bool ret = false; + + item_sink = get_gravity_sink(itm); + + if (!sink) { + sink = bounding_box(bed).center(); + } + + if (item_sink) + active_sink = unscaled(*item_sink); + else + active_sink = unscaled(*sink); + + ret = find_initial_position(itm, scaled(active_sink), bed, packing_context); + + return ret; + } + + template bool on_item_packed(ArrItem &itm) { return true; } +}; + +}} // namespace Slic3r::arr2 + +#endif // GRAVITYKERNEL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp new file mode 100644 index 0000000000..04dec4e2bd --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp @@ -0,0 +1,57 @@ +#ifndef KERNELTRAITS_HPP +#define KERNELTRAITS_HPP + +#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" + +namespace Slic3r { namespace arr2 { + +// An arrangement kernel that specifies the object function to the arrangement +// optimizer and additional callback functions to be able to track the state +// of the arranged pile during arrangement. +template struct KernelTraits_ +{ + // Has to return a score value marking the quality of the arrangement. The + // higher this value is, the better a particular placement of the item is. + // parameter transl is the translation needed for the item to be moved to + // the candidate position. + // To discard the item, return NaN as score for every translation. + template + static double placement_fitness(const Kernel &k, + const ArrItem &itm, + const Vec2crd &transl) + { + return k.placement_fitness(itm, transl); + } + + // Called whenever a new item is about to be processed by the optimizer. + // The current state of the arrangement can be saved by the kernel: the + // already placed items and the remaining items that need to fit into a + // particular bed. + // Returns true if the item is can be packed immediately, false if it + // should be processed further. This way, a kernel have the power to + // choose an initial position for the item that is not on the NFP. + template + static bool on_start_packing(Kernel &k, + ArrItem &itm, + const Bed &bed, + const Ctx &packing_context, + const Range &remaining_items) + { + return k.on_start_packing(itm, bed, packing_context, remaining_items); + } + + // Called when an item has been succesfully packed. itm should have the + // final translation and rotation already set. + // Can return false to discard the item after the optimization. + template + static bool on_item_packed(Kernel &k, ArrItem &itm) + { + return k.on_item_packed(itm); + } +}; + +template using KernelTraits = KernelTraits_>; + +}} // namespace Slic3r::arr2 + +#endif // KERNELTRAITS_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp new file mode 100644 index 0000000000..fd37a0fb37 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp @@ -0,0 +1,75 @@ +#ifndef ARRANGEKERNELUTILS_HPP +#define ARRANGEKERNELUTILS_HPP + +#include + +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" +#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" + +namespace Slic3r { namespace arr2 { + +template +bool find_initial_position(Itm &itm, + const Vec2crd &sink, + const Bed &bed, + const Context &packing_context) +{ + bool ret = false; + + if constexpr (std::is_convertible_v || + std::is_convertible_v || + std::is_convertible_v) + { + if (all_items_range(packing_context).empty()) { + auto rotations = allowed_rotations(itm); + auto chull = envelope_convex_hull(itm); + + for (double rot : rotations) { + auto chullcpy = chull; + chullcpy.rotate(rot); + auto bbitm = bounding_box(chullcpy); + + Vec2crd cb = sink; + Vec2crd ci = bbitm.center(); + + Vec2crd d = cb - ci; + bbitm.translate(d); + + if (bounding_box(bed).contains(bbitm)) { + rotate(itm, rot); + translate(itm, d); + ret = true; + break; + } + } + } + } + + return ret; +} + +template std::optional get_gravity_sink(const ArrItem &itm) +{ + constexpr const char * SinkKey = "sink"; + + std::optional ret; + + auto ptr = get_data(itm, SinkKey); + + if (ptr) + ret = *ptr; + + return ret; +} + +template bool is_wipe_tower(const ArrItem &itm) +{ + constexpr const char * Key = "is_wipe_tower"; + + return has_key(itm, Key); +} + +}} // namespace Slic3r::arr2 + +#endif // ARRANGEKERNELUTILS_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp new file mode 100644 index 0000000000..ca9cc0721a --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp @@ -0,0 +1,94 @@ +#ifndef RECTANGLEOVERFITKERNELWRAPPER_HPP +#define RECTANGLEOVERFITKERNELWRAPPER_HPP + +#include "KernelTraits.hpp" + +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" + +namespace Slic3r { namespace arr2 { + +// This is a kernel wrapper that will apply a penality to the object function +// if the result cannot fit into the given rectangular bounds. This can be used +// to arrange into rectangular boundaries without calculating the IFP of the +// rectangle bed. Note that after the arrangement, what is garanteed is that +// the resulting pile will fit into the rectangular boundaries, but it will not +// be within the given rectangle. The items need to be moved afterwards manually. +// Use RectangeOverfitPackingStrategy to automate this post process step. +template +struct RectangleOverfitKernelWrapper { + Kernel &k; + BoundingBox binbb; + BoundingBox pilebb; + + RectangleOverfitKernelWrapper(Kernel &kern, const BoundingBox &limits) + : k{kern} + , binbb{limits} + {} + + double overfit(const BoundingBox &itmbb) const + { + auto fullbb = pilebb; + fullbb.merge(itmbb); + auto fullbbsz = fullbb.size(); + auto binbbsz = binbb.size(); + + auto wdiff = fullbbsz.x() - binbbsz.x() - SCALED_EPSILON; + auto hdiff = fullbbsz.y() - binbbsz.y() - SCALED_EPSILON; + double miss = .0; + if (wdiff > 0) + miss += double(wdiff); + if (hdiff > 0) + miss += double(hdiff); + + miss = miss > 0? miss : 0; + + return miss; + } + + template + double placement_fitness(const ArrItem &item, const Vec2crd &transl) const + { + double score = KernelTraits::placement_fitness(k, item, transl); + + auto itmbb = envelope_bounding_box(item); + itmbb.translate(transl); + double miss = overfit(itmbb); + score -= miss * miss; + + return score; + } + + template + bool on_start_packing(ArrItem &itm, + const Bed &bed, + const Ctx &packing_context, + const Range &remaining_items) + { + pilebb = BoundingBox{}; + + for (auto &fitm : all_items_range(packing_context)) + pilebb.merge(fixed_bounding_box(fitm)); + + return KernelTraits::on_start_packing(k, itm, RectangleBed{binbb}, + packing_context, + remaining_items); + } + + template + bool on_item_packed(ArrItem &itm) + { + bool ret = KernelTraits::on_item_packed(k, itm); + + double miss = overfit(envelope_bounding_box(itm)); + + if (miss > 0.) + ret = false; + + return ret; + } +}; + +}} // namespace Slic3r::arr2 + +#endif // RECTANGLEOVERFITKERNELWRAPPER_H diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp new file mode 100644 index 0000000000..d52a46c226 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp @@ -0,0 +1,96 @@ +#ifndef SVGDEBUGOUTPUTKERNELWRAPPER_HPP +#define SVGDEBUGOUTPUTKERNELWRAPPER_HPP + +#include + +#include "KernelTraits.hpp" + +#include "libslic3r/Arrange/Core/PackingContext.hpp" +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" + +#include + +namespace Slic3r { namespace arr2 { + +template +struct SVGDebugOutputKernelWrapper { + Kernel &k; + std::unique_ptr svg; + BoundingBox drawbounds; + + template + SVGDebugOutputKernelWrapper(const BoundingBox &bounds, Kernel &kern) + : k{kern}, drawbounds{bounds} + {} + + template + bool on_start_packing(ArrItem &itm, + const Bed &bed, + const Context &packing_context, + const Range &rem) + { + using namespace Slic3r; + + bool ret = KernelTraits::on_start_packing(k, itm, bed, + packing_context, + rem); + + if (arr2::get_bed_index(itm) < 0) + return ret; + + svg.reset(); + auto bounds = drawbounds; + auto fixed = all_items_range(packing_context); + svg = std::make_unique(std::string("arrange_bed") + + std::to_string( + arr2::get_bed_index(itm)) + + "_" + std::to_string(fixed.size()) + + ".svg", + bounds, 0, false); + + svg->draw(ExPolygon{arr2::to_rectangle(drawbounds)}, "blue", .2f); + + auto nfp = calculate_nfp(itm, packing_context, bed); + svg->draw_outline(nfp); + svg->draw(nfp, "green", 0.2f); + + for (const auto &fixeditm : fixed) { + ExPolygons fixeditm_outline = to_expolygons(fixed_outline(fixeditm)); + svg->draw_outline(fixeditm_outline); + svg->draw(fixeditm_outline, "yellow", 0.5f); + } + + return ret; + } + + template + double placement_fitness(const ArrItem &item, const Vec2crd &transl) const + { + return KernelTraits::placement_fitness(k, item, transl); + } + + template + bool on_item_packed(ArrItem &itm) + { + using namespace Slic3r; + using namespace Slic3r::arr2; + + bool ret = KernelTraits::on_item_packed(k, itm); + + if (svg) { + ExPolygons itm_outline = to_expolygons(fixed_outline(itm)); + + svg->draw_outline(itm_outline); + svg->draw(itm_outline, "grey"); + + svg->Close(); + } + + return ret; + } +}; + +}} // namespace Slic3r::arr2 + +#endif // SVGDEBUGOUTPUTKERNELWRAPPER_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp new file mode 100644 index 0000000000..f5dba769d5 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp @@ -0,0 +1,268 @@ +#ifndef TMARRANGEKERNEL_HPP +#define TMARRANGEKERNEL_HPP + +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" + +#include "KernelUtils.hpp" + +#include +#include + +namespace Slic3r { namespace arr2 { + +// Summon the spatial indexing facilities from boost +namespace bgi = boost::geometry::index; +using SpatElement = std::pair; +using SpatIndex = bgi::rtree >; + +class TMArrangeKernel { + SpatIndex m_rtree; // spatial index for the normal (bigger) objects + SpatIndex m_smallsrtree; // spatial index for only the smaller items + BoundingBox m_pilebb; + double m_bin_area = NaNd; + double m_norm; + size_t m_rem_cnt = 0; + size_t m_item_cnt = 0; + + + struct ItemStats { double area = 0.; BoundingBox bb; }; + std::vector m_itemstats; + + // A coefficient used in separating bigger items and smaller items. + static constexpr double BigItemTreshold = 0.02; + + template ArithmeticOnly norm(T val) const + { + return double(val) / m_norm; + } + + // Treat big items (compared to the print bed) differently + bool is_big(double a) const { return a / m_bin_area > BigItemTreshold; } + +protected: + std::optional sink; + std::optional item_sink; + Point active_sink; + + const BoundingBox & pilebb() const { return m_pilebb; } + +public: + TMArrangeKernel() = default; + TMArrangeKernel(Vec2crd gravity_center, size_t itm_cnt, double bedarea = NaNd) + : sink{gravity_center} + , m_bin_area(bedarea) + , m_item_cnt{itm_cnt} + {} + + TMArrangeKernel(size_t itm_cnt, double bedarea = NaNd) + : m_bin_area(bedarea), m_item_cnt{itm_cnt} + {} + + template + double placement_fitness(const ArrItem &item, const Vec2crd &transl) const + { + // Candidate item bounding box + auto ibb = envelope_bounding_box(item); + ibb.translate(transl); + + // Calculate the full bounding box of the pile with the candidate item + auto fullbb = m_pilebb; + fullbb.merge(ibb); + + // The bounding box of the big items (they will accumulate in the center + // of the pile + BoundingBox bigbb; + if(m_rtree.empty()) { + bigbb = fullbb; + } + else { + auto boostbb = m_rtree.bounds(); + boost::geometry::convert(boostbb, bigbb); + } + + // Will hold the resulting score + double score = 0; + + // Density is the pack density: how big is the arranged pile + double density = 0; + + // Distinction of cases for the arrangement scene + enum e_cases { + // This branch is for big items in a mixed (big and small) scene + // OR for all items in a small-only scene. + BIG_ITEM, + + // This branch is for the last big item in a mixed scene + LAST_BIG_ITEM, + + // For small items in a mixed scene. + SMALL_ITEM, + + WIPE_TOWER, + } compute_case; + + bool is_wt = is_wipe_tower(item); + bool bigitems = is_big(envelope_area(item)) || m_rtree.empty(); + if (is_wt) + compute_case = WIPE_TOWER; + else if (bigitems && m_rem_cnt > 0) + compute_case = BIG_ITEM; + else if (bigitems && m_rem_cnt == 0) + compute_case = LAST_BIG_ITEM; + else + compute_case = SMALL_ITEM; + + switch (compute_case) { + case WIPE_TOWER: { + score = (unscaled(ibb.center()) - unscaled(active_sink)).squaredNorm(); + break; + } + case BIG_ITEM: { + const Point& minc = ibb.min; // bottom left corner + const Point& maxc = ibb.max; // top right corner + + // top left and bottom right corners + Point top_left{minc.x(), maxc.y()}; + Point bottom_right{maxc.x(), minc.y()}; + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + dists[0] = (minc - cc).cast().norm(); + dists[1] = (maxc - cc).cast().norm(); + dists[2] = (ibb.center() - cc).template cast().norm(); + dists[3] = (top_left - cc).cast().norm(); + dists[4] = (bottom_right - cc).cast().norm(); + + // The smalles distance from the arranged pile center: + double dist = norm(*(std::min_element(dists.begin(), dists.end()))); + double bindist = norm((ibb.center() - active_sink).template cast().norm()); + dist = 0.8 * dist + 0.2 * bindist; + + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item + // aligned with its neighbors. We will check the alignment + // with all neighbors and return the score for the best + // alignment. So it is enough for the candidate to be + // aligned with only one item. + auto alignment_score = 1.0; + + auto query = bgi::intersects(ibb); + auto& index = is_big(envelope_area(item)) ? m_rtree : m_smallsrtree; + + // Query the spatial index for the neighbors + std::vector result; + result.reserve(index.size()); + + index.query(query, std::back_inserter(result)); + + // now get the score for the best alignment + for(auto& e : result) { + auto idx = e.second; + const ItemStats& p = m_itemstats[idx]; + auto parea = p.area; + if(std::abs(1.0 - parea / fixed_area(item)) < 1e-6) { + auto bb = p.bb; + bb.merge(ibb); + auto bbarea = area(bb); + auto ascore = 1.0 - (fixed_area(item) + parea) / bbarea; + + if(ascore < alignment_score) + alignment_score = ascore; + } + } + + auto fullbbsz = fullbb.size(); + density = std::sqrt(norm(fullbbsz.x()) * norm(fullbbsz.y())); + double R = double(m_rem_cnt) / (m_item_cnt); + + // The final mix of the score is the balance between the + // distance from the full pile center, the pack density and + // the alignment with the neighbors + if (result.empty()) + score = 0.50 * dist + 0.50 * density; + else + // Let the density matter more when fewer objects remain + score = 0.50 * dist + (1.0 - R) * 0.20 * density + + 0.30 * alignment_score; + + break; + } + case LAST_BIG_ITEM: { + score = norm((ibb.center() - m_pilebb.center()).template cast().norm()); + break; + } + case SMALL_ITEM: { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = norm((ibb.center() - bigbb.center()).template cast().norm()); + break; + } + } + + return -score; + } + + template + bool on_start_packing(ArrItem &itm, + const Bed &bed, + const Context &packing_context, + const Range &remaining_items) + { + item_sink = get_gravity_sink(itm); + + if (!sink) { + sink = bounding_box(bed).center(); + } + + if (item_sink) + active_sink = *item_sink; + else + active_sink = *sink; + + auto fixed = all_items_range(packing_context); + + bool ret = find_initial_position(itm, active_sink, bed, packing_context); + + m_rem_cnt = remaining_items.size(); + + if (m_item_cnt == 0) + m_item_cnt = m_rem_cnt + fixed.size() + 1; + + if (std::isnan(m_bin_area)) + m_bin_area = area(bed); + + m_norm = std::sqrt(m_bin_area); + + m_itemstats.clear(); + m_itemstats.reserve(fixed.size()); + m_rtree.clear(); + m_smallsrtree.clear(); + m_pilebb = {}; + unsigned idx = 0; + for (auto &fixitem : fixed) { + auto fixitmbb = fixed_bounding_box(fixitem); + m_itemstats.emplace_back(ItemStats{fixed_area(fixitem), fixitmbb}); + m_pilebb.merge(fixitmbb); + + if(is_big(fixed_area(fixitem))) + m_rtree.insert({fixitmbb, idx}); + + m_smallsrtree.insert({fixitmbb, idx}); + idx++; + } + + return ret; + } + + template + bool on_item_packed(ArrItem &itm) { return true; } +}; + +}} // namespace Slic3r::arr2 + +#endif // TMARRANGEKERNEL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.cpp b/src/libslic3r/Arrange/Core/NFP/NFP.cpp new file mode 100644 index 0000000000..5dfe559f73 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/NFP.cpp @@ -0,0 +1,412 @@ +#ifndef NFP_CPP +#define NFP_CPP + +#include "NFP.hpp" +#include "CircularEdgeIterator.hpp" + +#include "NFPConcave_Tesselate.hpp" + +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +namespace Slic3r { using LargeInt = __int128; } +#else +#include +namespace Slic3r { using LargeInt = boost::multiprecision::int128_t; } +#endif + +#include + +namespace Slic3r { + +static bool line_cmp(const Line& e1, const Line& e2) +{ + using Ratio = boost::rational; + + const Vec<2, int64_t> ax(1, 0); // Unit vector for the X axis + + Vec<2, int64_t> p1 = (e1.b - e1.a).cast(); + Vec<2, int64_t> p2 = (e2.b - e2.a).cast(); + + // Quadrant mapping array. The quadrant of a vector can be determined + // from the dot product of the vector and its perpendicular pair + // with the unit vector X axis. The products will carry the values + // lcos = dot(p, ax) = l * cos(phi) and + // lsin = -dotperp(p, ax) = l * sin(phi) where + // l is the length of vector p. From the signs of these values we can + // construct an index which has the sign of lcos as MSB and the + // sign of lsin as LSB. This index can be used to retrieve the actual + // quadrant where vector p resides using the following map: + // (+ is 0, - is 1) + // cos | sin | decimal | quadrant + // + | + | 0 | 0 + // + | - | 1 | 3 + // - | + | 2 | 1 + // - | - | 3 | 2 + std::array quadrants {0, 3, 1, 2 }; + + std::array q {0, 0}; // Quadrant indices for p1 and p2 + + using TDots = std::array; + TDots lcos { p1.dot(ax), p2.dot(ax) }; + TDots lsin { -dotperp(p1, ax), -dotperp(p2, ax) }; + + // Construct the quadrant indices for p1 and p2 + for(size_t i = 0; i < 2; ++i) { + if (lcos[i] == 0) + q[i] = lsin[i] > 0 ? 1 : 3; + else if (lsin[i] == 0) + q[i] = lcos[i] > 0 ? 0 : 2; + else + q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)]; + } + + if (q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant + auto lsq1 = p1.squaredNorm(); // squared magnitudes, avoid sqrt + auto lsq2 = p2.squaredNorm(); // squared magnitudes, avoid sqrt + + // We will actually compare l^2 * cos^2(phi) which saturates the + // cos function. But with the quadrant info we can get the sign back + int sign = q[0] == 1 || q[0] == 2 ? -1 : 1; + + // If Ratio is an actual rational type, there is no precision loss + auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; + auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; + + return q[0] < 2 ? pcos1 > pcos2 : pcos1 < pcos2; + } + + // If in different quadrants, compare the quadrant indices only. + return q[0] < q[1]; +} + +static inline bool vsort(const Vec2crd& v1, const Vec2crd& v2) +{ + return v1.y() == v2.y() ? v1.x() < v2.x() : v1.y() < v2.y(); +} + +ExPolygons ifp_convex(const arr2::RectangleBed &obed, const Polygon &convexpoly) +{ + ExPolygon ret; + + auto sbox = bounding_box(convexpoly); + auto sboxsize = sbox.size(); + coord_t sheight = sboxsize.y(); + coord_t swidth = sboxsize.x(); + Point sliding_top = reference_vertex(convexpoly); + auto leftOffset = sliding_top.x() - sbox.min.x(); + auto rightOffset = sliding_top.x() - sbox.max.x(); + coord_t topOffset = 0; + auto bottomOffset = sheight; + + auto bedbb = obed.bb; +// bedbb.offset(1); + auto bedsz = bedbb.size(); + auto boxWidth = bedsz.x(); + auto boxHeight = bedsz.y(); + + auto bedMinx = bedbb.min.x(); + auto bedMiny = bedbb.min.y(); + auto bedMaxx = bedbb.max.x(); + auto bedMaxy = bedbb.max.y(); + + Polygon innerNfp{ Point{bedMinx + leftOffset, bedMaxy + topOffset}, + Point{bedMaxx + rightOffset, bedMaxy + topOffset}, + Point{bedMaxx + rightOffset, bedMiny + bottomOffset}, + Point{bedMinx + leftOffset, bedMiny + bottomOffset}, + Point{bedMinx + leftOffset, bedMaxy + topOffset} }; + + if (sheight <= boxHeight && swidth <= boxWidth) + ret.contour = std::move(innerNfp); + + return {ret}; +} + +Polygon ifp_convex_convex(const Polygon &fixed, const Polygon &movable) +{ + auto subnfps = reserve_polygons(fixed.size()); + + // For each edge of the bed polygon, determine the nfp of convexpoly and + // the zero area polygon formed by the edge. The union of all these sub-nfps + // will contain a hole that is the actual ifp. + auto lrange = line_range(fixed); + for (const Line &l : lrange) { // Older mac compilers generate warnging if line_range is called in-place + Polygon fixed = {l.a, l.b}; + subnfps.emplace_back(nfp_convex_convex_legacy(fixed, movable)); + } + + // Do the union and then keep only the holes (should be only one or zero, if + // the convexpoly cannot fit into the bed) + Polygons ifp = union_(subnfps); + Polygon ret; + + // find the first hole + auto it = std::find_if(ifp.begin(), ifp.end(), [](const Polygon &subifp){ + return subifp.is_clockwise(); + }); + + if (it != ifp.end()) { + ret = std::move(*it); + std::reverse(ret.begin(), ret.end()); + } + + return ret; +} + +ExPolygons ifp_convex(const arr2::CircleBed &bed, const Polygon &convexpoly) +{ + Polygon circle = approximate_circle_with_polygon(bed); + + return {ExPolygon{ifp_convex_convex(circle, convexpoly)}}; +} + +ExPolygons ifp_convex(const arr2::IrregularBed &bed, const Polygon &convexpoly) +{ + auto bb = get_extents(bed.poly); + bb.offset(scaled(1.)); + + Polygon rect = arr2::to_rectangle(bb); + + ExPolygons blueprint = diff_ex(rect, bed.poly); + Polygons ifp; + for (const ExPolygon &part : blueprint) { + Polygons triangles = Slic3r::convex_decomposition_tess(part); + for (const Polygon &tr : triangles) { + Polygon subifp = nfp_convex_convex_legacy(tr, convexpoly); + ifp.emplace_back(std::move(subifp)); + } + } + + ifp = union_(ifp); + + Polygons ret; + + std::copy_if(ifp.begin(), ifp.end(), std::back_inserter(ret), + [](const Polygon &p) { return p.is_clockwise(); }); + + for (Polygon &p : ret) + std::reverse(p.begin(), p.end()); + + return to_expolygons(ret); +} + +Vec2crd reference_vertex(const Polygon &poly) +{ + Vec2crd ret{std::numeric_limits::min(), + std::numeric_limits::min()}; + + auto it = std::max_element(poly.points.begin(), poly.points.end(), vsort); + if (it != poly.points.end()) + ret = std::max(ret, static_cast(*it), vsort); + + return ret; +} + +Vec2crd reference_vertex(const ExPolygon &expoly) +{ + return reference_vertex(expoly.contour); +} + +Vec2crd reference_vertex(const Polygons &outline) +{ + Vec2crd ret{std::numeric_limits::min(), + std::numeric_limits::min()}; + + for (const Polygon &poly : outline) + ret = std::max(ret, reference_vertex(poly), vsort); + + return ret; +} + +Vec2crd reference_vertex(const ExPolygons &outline) +{ + Vec2crd ret{std::numeric_limits::min(), + std::numeric_limits::min()}; + + for (const ExPolygon &expoly : outline) + ret = std::max(ret, reference_vertex(expoly), vsort); + + return ret; +} + +Vec2crd min_vertex(const Polygon &poly) +{ + Vec2crd ret{std::numeric_limits::max(), + std::numeric_limits::max()}; + + auto it = std::min_element(poly.points.begin(), poly.points.end(), vsort); + if (it != poly.points.end()) + ret = std::min(ret, static_cast(*it), vsort); + + return ret; +} + +// Find the vertex corresponding to the edge with minimum angle to X axis. +// Only usable with CircularEdgeIterator<> template. +template It find_min_anglex_edge(It from) +{ + bool found = false; + auto it = from; + while (!found ) { + found = !line_cmp(*it, *std::next(it)); + ++it; + } + + return it; +} + +// Only usable if both fixed and movable polygon is convex. In that case, +// their edges are already sorted by angle to X axis, only the starting +// (lowest X axis) edge needs to be found first. +void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &poly) +{ + if (fixed.empty() || movable.empty()) + return; + + // Clear poly and adjust its capacity. Nothing happens if poly is + // already sufficiently large and and empty. + poly.clear(); + poly.points.reserve(fixed.size() + movable.size()); + + // Find starting positions on the fixed and moving polygons + auto it_fx = find_min_anglex_edge(CircularEdgeIterator{fixed}); + auto it_mv = find_min_anglex_edge(CircularReverseEdgeIterator{movable}); + + // End positions are at the same vertex after completing one full circle + auto end_fx = it_fx + fixed.size(); + auto end_mv = it_mv + movable.size(); + + // Pos zero is just fine as starting point: + poly.points.emplace_back(0, 0); + + // Output iterator adapter for std::merge + struct OutItAdaptor { + Polygon *outpoly; + OutItAdaptor(Polygon &out) : outpoly{&out} {} + + OutItAdaptor &operator *() { return *this; } + void operator=(const Line &l) + { + // Yielding l.b, offsetted so that l.a touches the last vertex in + // in outpoly + outpoly->points.emplace_back(l.b + outpoly->back() - l.a); + } + + OutItAdaptor& operator++() { return *this; }; + }; + + // Use std algo to merge the edges from the two polygons + std::merge(it_fx, end_fx, it_mv, end_mv, OutItAdaptor{poly}, line_cmp); +} + +Polygon nfp_convex_convex(const Polygon &fixed, const Polygon &movable) +{ + Polygon ret; + nfp_convex_convex(fixed, movable, ret); + + return ret; +} + +static void buildPolygon(const std::vector& edgelist, + Polygon& rpoly, + Point& top_nfp) +{ + auto& rsh = rpoly.points; + + rsh.reserve(2 * edgelist.size()); + + // Add the two vertices from the first edge into the final polygon. + rsh.emplace_back(edgelist.front().a); + rsh.emplace_back(edgelist.front().b); + + // Sorting function for the nfp reference vertex search + + // the reference (rightmost top) vertex so far + top_nfp = *std::max_element(std::cbegin(rsh), std::cend(rsh), vsort); + + auto tmp = std::next(std::begin(rsh)); + + // Construct final nfp by placing each edge to the end of the previous + for(auto eit = std::next(edgelist.begin()); eit != edgelist.end(); ++eit) { + auto d = *tmp - eit->a; + Vec2crd p = eit->b + d; + + rsh.emplace_back(p); + + // Set the new reference vertex + if (vsort(top_nfp, p)) + top_nfp = p; + + tmp = std::next(tmp); + } +} + +Polygon nfp_convex_convex_legacy(const Polygon &fixed, const Polygon &movable) +{ + assert (!fixed.empty()); + assert (!movable.empty()); + + Polygon rsh; // Final nfp placeholder + Point max_nfp; + std::vector edgelist; + + auto cap = fixed.points.size() + movable.points.size(); + + // Reserve the needed memory + edgelist.reserve(cap); + rsh.points.reserve(cap); + + auto add_edge = [&edgelist](const Point &v1, const Point &v2) { + Line e{v1, v2}; + if ((e.b - e.a).cast().squaredNorm() > 0) + edgelist.emplace_back(e); + }; + + Point max_fixed = fixed.points.front(); + { // place all edges from fixed into edgelist + auto first = std::cbegin(fixed); + auto next = std::next(first); + + while(next != std::cend(fixed)) { + add_edge(*(first), *(next)); + max_fixed = std::max(max_fixed, *first, vsort); + + ++first; ++next; + } + + add_edge(*std::crbegin(fixed), *std::cbegin(fixed)); + max_fixed = std::max(max_fixed, *std::crbegin(fixed), vsort); + } + + Point max_movable = movable.points.front(); + Point min_movable = movable.points.front(); + { // place all edges from movable into edgelist + auto first = std::cbegin(movable); + auto next = std::next(first); + + while(next != std::cend(movable)) { + add_edge(*(next), *(first)); + min_movable = std::min(min_movable, *first, vsort); + max_movable = std::max(max_movable, *first, vsort); + + ++first; ++next; + } + + add_edge(*std::cbegin(movable), *std::crbegin(movable)); + min_movable = std::min(min_movable, *std::crbegin(movable), vsort); + max_movable = std::max(max_movable, *std::crbegin(movable), vsort); + } + + std::sort(edgelist.begin(), edgelist.end(), line_cmp); + + buildPolygon(edgelist, rsh, max_nfp); + + auto dtouch = max_fixed - min_movable; + auto top_other = max_movable + dtouch; + auto dnfp = top_other - max_nfp; + rsh.translate(dnfp); + + return rsh; +} + +} // namespace Slic3r + +#endif // NFP_CPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.hpp b/src/libslic3r/Arrange/Core/NFP/NFP.hpp new file mode 100644 index 0000000000..3f23ea133b --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/NFP.hpp @@ -0,0 +1,50 @@ +#ifndef NFP_HPP +#define NFP_HPP + +#include +#include + +namespace Slic3r { + +template +Unit dotperp(const Vec<2, T> &a, const Vec<2, T> &b) +{ + return Unit(a.x()) * Unit(b.y()) - Unit(a.y()) * Unit(b.x()); +} + +// Convex-Convex nfp in linear time (fixed.size() + movable.size()), +// no memory allocations (if out param is used). +// FIXME: Currently broken for very sharp triangles. +Polygon nfp_convex_convex(const Polygon &fixed, const Polygon &movable); +void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &out); +Polygon nfp_convex_convex_legacy(const Polygon &fixed, const Polygon &movable); + +Polygon ifp_convex_convex(const Polygon &fixed, const Polygon &movable); + +ExPolygons ifp_convex(const arr2::RectangleBed &bed, const Polygon &convexpoly); +ExPolygons ifp_convex(const arr2::CircleBed &bed, const Polygon &convexpoly); +ExPolygons ifp_convex(const arr2::IrregularBed &bed, const Polygon &convexpoly); +inline ExPolygons ifp_convex(const arr2::InfiniteBed &bed, const Polygon &convexpoly) +{ + return {}; +} + +inline ExPolygons ifp_convex(const arr2::ArrangeBed &bed, const Polygon &convexpoly) +{ + ExPolygons ret; + auto visitor = [&ret, &convexpoly](const auto &b) { ret = ifp_convex(b, convexpoly); }; + boost::apply_visitor(visitor, bed); + + return ret; +} + +Vec2crd reference_vertex(const Polygon &outline); +Vec2crd reference_vertex(const ExPolygon &outline); +Vec2crd reference_vertex(const Polygons &outline); +Vec2crd reference_vertex(const ExPolygons &outline); + +Vec2crd min_vertex(const Polygon &outline); + +} // namespace Slic3r + +#endif // NFP_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp b/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp new file mode 100644 index 0000000000..009162661c --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp @@ -0,0 +1,176 @@ +#ifndef NFPARRANGEITEMTRAITS_HPP +#define NFPARRANGEITEMTRAITS_HPP + +#include + +#include "libslic3r/Arrange/Core/ArrangeBase.hpp" + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/BoundingBox.hpp" + +namespace Slic3r { namespace arr2 { + +// Additional methods that an ArrangeItem object has to implement in order +// to be usable with PackStrategyNFP. +template struct NFPArrangeItemTraits_ +{ + template + static ExPolygons calculate_nfp(const ArrItem &item, + const Context &packing_context, + const Bed &bed, + StopCond stop_condition = {}) + { + static_assert(always_false::value, + "NFP unimplemented for this item type."); + return {}; + } + + static Vec2crd reference_vertex(const ArrItem &item) + { + return item.reference_vertex(); + } + + static BoundingBox envelope_bounding_box(const ArrItem &itm) + { + return itm.envelope_bounding_box(); + } + + static BoundingBox fixed_bounding_box(const ArrItem &itm) + { + return itm.fixed_bounding_box(); + } + + static const Polygons & envelope_outline(const ArrItem &itm) + { + return itm.envelope_outline(); + } + + static const Polygons & fixed_outline(const ArrItem &itm) + { + return itm.fixed_outline(); + } + + static const Polygon & envelope_convex_hull(const ArrItem &itm) + { + return itm.envelope_convex_hull(); + } + + static const Polygon & fixed_convex_hull(const ArrItem &itm) + { + return itm.fixed_convex_hull(); + } + + static double envelope_area(const ArrItem &itm) + { + return itm.envelope_area(); + } + + static double fixed_area(const ArrItem &itm) + { + return itm.fixed_area(); + } + + static auto allowed_rotations(const ArrItem &) + { + return std::array{0.}; + } +}; + +template +using NFPArrangeItemTraits = NFPArrangeItemTraits_>; + +template +ExPolygons calculate_nfp(const ArrItem &itm, + const Context &context, + const Bed &bed, + StopCond stopcond = {}) +{ + return NFPArrangeItemTraits::calculate_nfp(itm, context, bed, + std::move(stopcond)); +} + +template Vec2crd reference_vertex(const ArrItem &itm) +{ + return NFPArrangeItemTraits::reference_vertex(itm); +} + +template BoundingBox envelope_bounding_box(const ArrItem &itm) +{ + return NFPArrangeItemTraits::envelope_bounding_box(itm); +} + +template BoundingBox fixed_bounding_box(const ArrItem &itm) +{ + return NFPArrangeItemTraits::fixed_bounding_box(itm); +} + +template decltype(auto) envelope_convex_hull(const ArrItem &itm) +{ + return NFPArrangeItemTraits::envelope_convex_hull(itm); +} + +template decltype(auto) fixed_convex_hull(const ArrItem &itm) +{ + return NFPArrangeItemTraits::fixed_convex_hull(itm); +} + +template decltype(auto) envelope_outline(const ArrItem &itm) +{ + return NFPArrangeItemTraits::envelope_outline(itm); +} + +template decltype(auto) fixed_outline(const ArrItem &itm) +{ + return NFPArrangeItemTraits::fixed_outline(itm); +} + +template double envelope_area(const ArrItem &itm) +{ + return NFPArrangeItemTraits::envelope_area(itm); +} + +template double fixed_area(const ArrItem &itm) +{ + return NFPArrangeItemTraits::fixed_area(itm); +} + +template +auto allowed_rotations(const ArrItem &itm) +{ + return NFPArrangeItemTraits::allowed_rotations(itm); +} + +template +BoundingBox bounding_box(const Range &itms) noexcept +{ + auto pilebb = + std::accumulate(itms.begin(), itms.end(), BoundingBox{}, + [](BoundingBox bb, const auto &itm) { + bb.merge(fixed_bounding_box(itm)); + return bb; + }); + + return pilebb; +} + +template +BoundingBox bounding_box_on_bedidx(const Range &itms, int bed_index) noexcept +{ + auto pilebb = + std::accumulate(itms.begin(), itms.end(), BoundingBox{}, + [bed_index](BoundingBox bb, const auto &itm) { + if (bed_index == get_bed_index(itm)) + bb.merge(fixed_bounding_box(itm)); + + return bb; + }); + + return pilebb; +} + +}} // namespace Slic3r::arr2 + +#endif // ARRANGEITEMTRAITSNFP_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp new file mode 100644 index 0000000000..96cec85b8b --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp @@ -0,0 +1,111 @@ +#include "NFP.hpp" +#include "NFPConcave_CGAL.hpp" + +#include +#include +#include +#include +#include + +#include "libslic3r/ClipperUtils.hpp" + +namespace Slic3r { + +using K = CGAL::Exact_predicates_inexact_constructions_kernel; +using Partition_traits_2 = CGAL::Partition_traits_2::type >; +using Point_2 = Partition_traits_2::Point_2; +using Polygon_2 = Partition_traits_2::Polygon_2; // a polygon of indices + +ExPolygons nfp_concave_concave_cgal(const ExPolygon &fixed, const ExPolygon &movable) +{ + Polygons fixed_decomp = convex_decomposition_cgal(fixed); + Polygons movable_decomp = convex_decomposition_cgal(movable); + + auto refs_mv = reserve_vector(movable_decomp.size()); + + for (const Polygon &p : movable_decomp) + refs_mv.emplace_back(reference_vertex(p)); + + auto nfps = reserve_polygons(fixed_decomp.size() *movable_decomp.size()); + + Vec2crd ref_whole = reference_vertex(movable); + for (const Polygon &fixed_part : fixed_decomp) { + size_t mvi = 0; + for (const Polygon &movable_part : movable_decomp) { + Polygon subnfp = nfp_convex_convex(fixed_part, movable_part); + const Vec2crd &ref_mp = refs_mv[mvi]; + auto d = ref_whole - ref_mp; + subnfp.translate(d); + nfps.emplace_back(subnfp); + mvi++; + } + } + + return union_ex(nfps); +} + +// TODO: holes +Polygons convex_decomposition_cgal(const ExPolygon &expoly) +{ + CGAL::Polygon_vertical_decomposition_2 decomp; + + CGAL::Polygon_2 contour; + for (auto &p : expoly.contour.points) + contour.push_back({unscaled(p.x()), unscaled(p.y())}); + + CGAL::Polygon_with_holes_2 cgalpoly{contour}; + for (const Polygon &h : expoly.holes) { + CGAL::Polygon_2 hole; + for (auto &p : h.points) + hole.push_back({unscaled(p.x()), unscaled(p.y())}); + + cgalpoly.add_hole(hole); + } + + std::vector> out; + decomp(cgalpoly, std::back_inserter(out)); + + Polygons ret; + for (auto &pwh : out) { + Polygon poly; + for (auto &p : pwh) + poly.points.emplace_back(scaled(p.x()), scaled(p.y())); + ret.emplace_back(std::move(poly)); + } + + return ret; //convex_decomposition_cgal(expoly.contour); +} + +Polygons convex_decomposition_cgal(const Polygon &poly) +{ + auto pts = reserve_vector(poly.size()); + + for (const Point &p : poly.points) + pts.emplace_back(unscaled(p.x()), unscaled(p.y())); + + Partition_traits_2 traits(CGAL::make_property_map(pts)); + + Polygon_2 polyidx; + for (size_t i = 0; i < pts.size(); ++i) + polyidx.push_back(i); + + std::vector outp; + + CGAL::optimal_convex_partition_2(polyidx.vertices_begin(), + polyidx.vertices_end(), + std::back_inserter(outp), + traits); + + Polygons ret; + for (const Polygon_2& poly : outp){ + Polygon r; + for(Point_2 p : poly.container()) + r.points.emplace_back(scaled(pts[p].x()), scaled(pts[p].y())); + + ret.emplace_back(std::move(r)); + } + + return ret; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp new file mode 100644 index 0000000000..89e73ea4d6 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp @@ -0,0 +1,14 @@ +#ifndef NFPCONCAVE_CGAL_HPP +#define NFPCONCAVE_CGAL_HPP + +#include + +namespace Slic3r { + +Polygons convex_decomposition_cgal(const Polygon &expoly); +Polygons convex_decomposition_cgal(const ExPolygon &expoly); +ExPolygons nfp_concave_concave_cgal(const ExPolygon &fixed, const ExPolygon &movable); + +} // namespace Slic3r + +#endif // NFPCONCAVE_CGAL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp new file mode 100644 index 0000000000..4b3660a9cc --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp @@ -0,0 +1,70 @@ +#include "NFPConcave_Tesselate.hpp" + +#include +#include + +#include "NFP.hpp" + +namespace Slic3r { + +Polygons convex_decomposition_tess(const Polygon &expoly) +{ + return convex_decomposition_tess(ExPolygon{expoly}); +} + +Polygons convex_decomposition_tess(const ExPolygon &expoly) +{ + std::vector tr = Slic3r::triangulate_expolygon_2d(expoly); + + auto ret = Slic3r::reserve_polygons(tr.size() / 3); + for (size_t i = 0; i < tr.size(); i += 3) { + ret.emplace_back( + Polygon{scaled(tr[i]), scaled(tr[i + 1]), scaled(tr[i + 2])}); + } + + return ret; +} + +Polygons convex_decomposition_tess(const ExPolygons &expolys) +{ + constexpr size_t AvgTriangleCountGuess = 50; + + auto ret = reserve_polygons(AvgTriangleCountGuess * expolys.size()); + for (const ExPolygon &expoly : expolys) { + Polygons convparts = convex_decomposition_tess(expoly); + std::move(convparts.begin(), convparts.end(), std::back_inserter(ret)); + } + + return ret; +} + +ExPolygons nfp_concave_concave_tess(const ExPolygon &fixed, + const ExPolygon &movable) +{ + Polygons fixed_decomp = convex_decomposition_tess(fixed); + Polygons movable_decomp = convex_decomposition_tess(movable); + + auto refs_mv = reserve_vector(movable_decomp.size()); + + for (const Polygon &p : movable_decomp) + refs_mv.emplace_back(reference_vertex(p)); + + auto nfps = reserve_polygons(fixed_decomp.size() * movable_decomp.size()); + + Vec2crd ref_whole = reference_vertex(movable); + for (const Polygon &fixed_part : fixed_decomp) { + size_t mvi = 0; + for (const Polygon &movable_part : movable_decomp) { + Polygon subnfp = nfp_convex_convex(fixed_part, movable_part); + const Vec2crd &ref_mp = refs_mv[mvi]; + auto d = ref_whole - ref_mp; + subnfp.translate(d); + nfps.emplace_back(subnfp); + mvi++; + } + } + + return union_ex(nfps); +} + +} // namespace Slic3r diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp new file mode 100644 index 0000000000..05e7a48a50 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp @@ -0,0 +1,15 @@ +#ifndef NFPCONCAVE_TESSELATE_HPP +#define NFPCONCAVE_TESSELATE_HPP + +#include + +namespace Slic3r { + +Polygons convex_decomposition_tess(const Polygon &expoly); +Polygons convex_decomposition_tess(const ExPolygon &expoly); +Polygons convex_decomposition_tess(const ExPolygons &expolys); +ExPolygons nfp_concave_concave_tess(const ExPolygon &fixed, const ExPolygon &movable); + +} // namespace Slic3r + +#endif // NFPCONCAVE_TESSELATE_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp new file mode 100644 index 0000000000..c042829dfb --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp @@ -0,0 +1,279 @@ +#ifndef PACKSTRATEGYNFP_HPP +#define PACKSTRATEGYNFP_HPP + +#include "libslic3r/Arrange/Core/ArrangeBase.hpp" + +#include "EdgeCache.hpp" +#include "Kernels/KernelTraits.hpp" + +#include "NFPArrangeItemTraits.hpp" + +#include "libslic3r/Optimize/NLoptOptimizer.hpp" +#include "libslic3r/Execution/ExecutionSeq.hpp" + +namespace Slic3r { namespace arr2 { + +struct NFPPackingTag{}; + +struct DummyArrangeKernel +{ + template + double placement_fitness(const ArrItem &itm, const Vec2crd &dest_pos) const + { + return NaNd; + } + + template + bool on_start_packing(ArrItem &itm, + const Bed &bed, + const Context &packing_context, + const Range &remaining_items) + { + return true; + } + + template bool on_item_packed(ArrItem &itm) { return true; } +}; + +template using OptAlg = typename Strategy::OptAlg; + +template +struct PackStrategyNFP { + using OptAlg = OptMethod; + + ArrangeKernel kernel; + ExecPolicy ep; + double accuracy = 1.; + opt::Optimizer solver; + StopCond stop_condition; + + PackStrategyNFP(opt::Optimizer slv, + ArrangeKernel k = {}, + ExecPolicy execpolicy = {}, + double accur = 1., + StopCond stop_cond = {}) + : kernel{std::move(k)}, + ep{std::move(execpolicy)}, + accuracy{accur}, + solver{std::move(slv)}, + stop_condition{std::move(stop_cond)} + {} + + PackStrategyNFP(ArrangeKernel k = {}, + ExecPolicy execpolicy = {}, + double accur = 1., + StopCond stop_cond = {}) + : PackStrategyNFP{opt::Optimizer{}, std::move(k), + std::move(execpolicy), accur, std::move(stop_cond)} + { + // Defaults for AlgNLoptSubplex + auto iters = static_cast(std::floor(1000 * accuracy)); + auto optparams = + opt::StopCriteria{}.max_iterations(iters).rel_score_diff( + 1e-20) /*.abs_score_diff(1e-20)*/; + + solver.set_criteria(optparams); + } +}; + +template +struct PackStrategyTag_> +{ + using Tag = NFPPackingTag; +}; + + +template +double pick_best_spot_on_nfp_verts_only(ArrItem &item, + const ExPolygons &nfp, + const Bed &bed, + const PStrategy &strategy) +{ + using KernelT = KernelTraits; + + auto score = -std::numeric_limits::infinity(); + Vec2crd orig_tr = get_translation(item); + Vec2crd translation{0, 0}; + + auto eval_fitness = [&score, &strategy, &item, &translation, + &orig_tr](const Vec2crd &p) { + set_translation(item, orig_tr); + Vec2crd ref_v = reference_vertex(item); + Vec2crd tr = p - ref_v; + double fitness = KernelT::placement_fitness(strategy.kernel, item, tr); + if (fitness > score) { + score = fitness; + translation = tr; + } + }; + + for (const ExPolygon &expoly : nfp) { + for (const Point &p : expoly.contour) { + eval_fitness(p); + } + + for (const Polygon &h : expoly.holes) + for (const Point &p : h.points) + eval_fitness(p); + } + + set_translation(item, orig_tr + translation); + + return score; +} + +struct CornerResult +{ + size_t contour_id; + opt::Result<1> oresult; +}; + +template +double pick_best_spot_on_nfp(ArrItem &item, + const ExPolygons &nfp, + const Bed &bed, + const PackStrategyNFP &strategy) +{ + auto &ex_policy = strategy.ep; + using KernelT = KernelTraits; + + auto score = -std::numeric_limits::infinity(); + Vec2crd orig_tr = get_translation(item); + Vec2crd translation{0, 0}; + Vec2crd ref_v = reference_vertex(item); + + auto edge_caches = reserve_vector(nfp.size()); + auto sample_sets = reserve_vector>( + nfp.size()); + + for (const ExPolygon &expoly : nfp) { + edge_caches.emplace_back(EdgeCache{&expoly}); + edge_caches.back().sample_contour(strategy.accuracy, + sample_sets.emplace_back()); + } + + auto nthreads = execution::max_concurrency(ex_policy); + + std::vector gresults(edge_caches.size()); + + auto resultcmp = [](auto &a, auto &b) { + return a.oresult.score < b.oresult.score; + }; + + execution::for_each( + ex_policy, size_t(0), edge_caches.size(), + [&](size_t edge_cache_idx) { + auto &ec_contour = edge_caches[edge_cache_idx]; + auto &corners = sample_sets[edge_cache_idx]; + std::vector results(corners.size()); + + auto cornerfn = [&](size_t i) { + ContourLocation cr = corners[i]; + auto objfn = [&](opt::Input<1> &in) { + Vec2crd p = ec_contour.coords(ContourLocation{cr.contour_id, in[0]}); + Vec2crd tr = p - ref_v; + + return KernelT::placement_fitness(strategy.kernel, item, tr); + }; + + // Assuming that solver is a lightweight object + auto solver = strategy.solver; + solver.to_max(); + auto oresult = solver.optimize(objfn, + opt::initvals({cr.dist}), + opt::bounds({{0., 1.}})); + + results[i] = CornerResult{cr.contour_id, oresult}; + }; + + execution::for_each(ex_policy, size_t(0), results.size(), + cornerfn, nthreads); + + auto it = std::max_element(results.begin(), results.end(), + resultcmp); + + if (it != results.end()) + gresults[edge_cache_idx] = *it; + }, + nthreads); + + auto it = std::max_element(gresults.begin(), gresults.end(), resultcmp); + if (it != gresults.end()) { + score = it->oresult.score; + size_t path_id = std::distance(gresults.begin(), it); + size_t contour_id = it->contour_id; + double dist = it->oresult.optimum[0]; + + Vec2crd pos = edge_caches[path_id].coords(ContourLocation{contour_id, dist}); + Vec2crd tr = pos - ref_v; + + set_translation(item, orig_tr + tr); + } + + return score; +} + +template +bool pack(Strategy &strategy, + const Bed &bed, + ArrItem &item, + const PackStrategyContext &packing_context, + const Range &remaining_items, + const NFPPackingTag &) +{ + using KernelT = KernelTraits; + + bool packed = KernelT::on_start_packing(strategy.kernel, item, bed, + packing_context, remaining_items); + + double orig_rot = get_rotation(item); + double final_rot = 0.; + double final_score = -std::numeric_limits::infinity(); + Vec2crd orig_tr = get_translation(item); + Vec2crd final_tr = orig_tr; + + bool cancelled = false; + const auto & rotations = allowed_rotations(item); + + for (auto rot_it = rotations.begin(); + !cancelled && !packed && rot_it != rotations.end(); ++rot_it) { + + double rot = *rot_it; + + set_rotation(item, orig_rot + rot); + set_translation(item, orig_tr); + + auto nfp = calculate_nfp(item, packing_context, bed, + strategy.stop_condition); + double score = NaNd; + if (!nfp.empty()) { + score = pick_best_spot_on_nfp(item, nfp, bed, strategy); + + cancelled = std::isnan(score) || strategy.stop_condition(); + if (score > final_score) { + final_score = score; + final_rot = rot; + final_tr = get_translation(item); + } + } else { + cancelled = true; + } + } + + packed = !cancelled; + + if (packed) { + set_translation(item, final_tr); + set_rotation(item, orig_rot + final_rot); + packed = KernelT::on_item_packed(strategy.kernel, item); + } + + return packed; +} + +}} // namespace Slic3r::arr2 + +#endif // PACKSTRATEGYNFP_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp b/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp new file mode 100644 index 0000000000..eb41094df6 --- /dev/null +++ b/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp @@ -0,0 +1,122 @@ +#ifndef RECTANGLEOVERFITPACKINGSTRATEGY_HPP +#define RECTANGLEOVERFITPACKINGSTRATEGY_HPP + +#include "Kernels/RectangleOverfitKernelWrapper.hpp" + +#include "libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" + +namespace Slic3r { namespace arr2 { + +using PostAlignmentFn = std::function; + +struct CenterAlignmentFn { + Vec2crd operator() (const BoundingBox &bedbb, + const BoundingBox &pilebb) + { + return bedbb.center() - pilebb.center(); + } +}; + +// With rectange bed, and no fixed items, an infinite bed with +// RectangleOverfitKernelWrapper can produce better results than a pure +// RectangleBed with inner-fit polygon calculation. +template +struct RectangleOverfitPackingStrategy { + PackStrategyNFP base_strategy; + + PostAlignmentFn post_alignment_fn = CenterAlignmentFn{}; + + template + struct Context: public DefaultPackingContext { + BoundingBox limits; + int bed_index; + PostAlignmentFn post_alignment_fn; + + explicit Context(const BoundingBox limits, + int bedidx, + PostAlignmentFn alignfn = CenterAlignmentFn{}) + : limits{limits}, bed_index{bedidx}, post_alignment_fn{alignfn} + {} + + ~Context() + { + // Here, the post alignment can be safely done. No throwing + // functions are called! + if (fixed_items_range(*this).empty()) { + auto itms = packed_items_range(*this); + auto pilebb = bounding_box(itms); + + for (auto &itm : itms) { + translate(itm, post_alignment_fn(limits, pilebb)); + } + } + } + }; + + RectangleOverfitPackingStrategy(PackStrategyNFP s, + PostAlignmentFn post_align_fn) + : base_strategy{std::move(s)}, post_alignment_fn{post_align_fn} + {} + + RectangleOverfitPackingStrategy(PackStrategyNFP s) + : base_strategy{std::move(s)} + {} +}; + +struct RectangleOverfitPackingStrategyTag {}; + +template +struct PackStrategyTag_> { + using Tag = RectangleOverfitPackingStrategyTag; +}; + +template +struct PackStrategyTraits_> { + template + using Context = typename RectangleOverfitPackingStrategy< + Args...>::template Context>; + + template + static Context create_context( + RectangleOverfitPackingStrategy &ps, + const Bed &bed, + int bed_index) + { + return Context{bounding_box(bed), bed_index, + ps.post_alignment_fn}; + } +}; + +template +bool pack(Strategy &strategy, + const Bed &bed, + ArrItem &item, + const PackStrategyContext &packing_context, + const Range &remaining_items, + const RectangleOverfitPackingStrategyTag &) +{ + bool ret = false; + + if (fixed_items_range(packing_context).empty()) { + auto &base = strategy.base_strategy; + PackStrategyNFP modded_strategy{ + base.solver, + RectangleOverfitKernelWrapper{base.kernel, packing_context.limits}, + base.ep, base.accuracy}; + + ret = pack(modded_strategy, + InfiniteBed{packing_context.limits.center()}, item, + packing_context, remaining_items, NFPPackingTag{}); + } else { + ret = pack(strategy.base_strategy, bed, item, packing_context, + remaining_items, NFPPackingTag{}); + } + + return ret; +} + +}} // namespace Slic3r::arr2 + +#endif // RECTANGLEOVERFITPACKINGSTRATEGY_HPP diff --git a/src/libslic3r/Arrange/Core/PackingContext.hpp b/src/libslic3r/Arrange/Core/PackingContext.hpp new file mode 100644 index 0000000000..77aa87e5c6 --- /dev/null +++ b/src/libslic3r/Arrange/Core/PackingContext.hpp @@ -0,0 +1,124 @@ +#ifndef PACKINGCONTEXT_HPP +#define PACKINGCONTEXT_HPP + +#include "ArrangeItemTraits.hpp" + +namespace Slic3r { namespace arr2 { + +template +struct PackingContextTraits_ { + template + static void add_fixed_item(Ctx &ctx, const ArrItem &itm) + { + ctx.add_fixed_item(itm); + } + + template + static void add_packed_item(Ctx &ctx, ArrItem &itm) + { + ctx.add_packed_item(itm); + } + + // returns a range of all packed items in the context ctx + static auto all_items_range(const Ctx &ctx) + { + return ctx.all_items_range(); + } + + static auto fixed_items_range(const Ctx &ctx) + { + return ctx.fixed_items_range(); + } + + static auto packed_items_range(const Ctx &ctx) + { + return ctx.packed_items_range(); + } + + static auto packed_items_range(Ctx &ctx) + { + return ctx.packed_items_range(); + } +}; + +template +void add_fixed_item(Ctx &ctx, const ArrItem &itm) +{ + PackingContextTraits_>::add_fixed_item(ctx, itm); +} + +template +void add_packed_item(Ctx &ctx, ArrItem &itm) +{ + PackingContextTraits_>::add_packed_item(ctx, itm); +} + +template +auto all_items_range(const Ctx &ctx) +{ + return PackingContextTraits_>::all_items_range(ctx); +} + +template +auto fixed_items_range(const Ctx &ctx) +{ + return PackingContextTraits_>::fixed_items_range(ctx); +} + +template +auto packed_items_range(Ctx &&ctx) +{ + return PackingContextTraits_>::packed_items_range(ctx); +} + +template +class DefaultPackingContext { + using ArrItemRaw = StripCVRef; + std::vector> m_fixed; + std::vector> m_packed; + std::vector> m_items; + +public: + DefaultPackingContext() = default; + + template + explicit DefaultPackingContext(const Range &fixed_items) + { + std::copy(fixed_items.begin(), fixed_items.end(), std::back_inserter(m_fixed)); + std::copy(fixed_items.begin(), fixed_items.end(), std::back_inserter(m_items)); + } + + auto all_items_range() const noexcept { return crange(m_items); } + auto fixed_items_range() const noexcept { return crange(m_fixed); } + auto packed_items_range() const noexcept { return crange(m_packed); } + auto packed_items_range() noexcept { return range(m_packed); } + + void add_fixed_item(const ArrItem &itm) + { + m_fixed.emplace_back(itm); + m_items.emplace_back(itm); + } + + void add_packed_item(ArrItem &itm) + { + m_packed.emplace_back(itm); + m_items.emplace_back(itm); + } +}; + +template +auto default_context(const Range &items) +{ + using ArrItem = StripCVRef::value_type>; + return DefaultPackingContext{items}; +} + +template +auto default_context(const Cont &container) +{ + return DefaultPackingContext{crange(container)}; +} + +}} // namespace Slic3r::arr2 + +#endif // PACKINGCONTEXT_HPP diff --git a/src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp b/src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp new file mode 100644 index 0000000000..69cfe8bb58 --- /dev/null +++ b/src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp @@ -0,0 +1,91 @@ +#ifndef ARBITRARYDATASTORE_HPP +#define ARBITRARYDATASTORE_HPP + +#include +#include +#include + +#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" + +namespace Slic3r { namespace arr2 { + +// An associative container able to store and retrieve any data type. +// Based on std::any +class ArbitraryDataStore { + std::map m_data; + +public: + template void add(const std::string &key, T &&data) + { + m_data[key] = std::any{std::forward(data)}; + } + + void add(const std::string &key, std::any &&data) + { + m_data[key] = std::move(data); + } + + // Return nullptr if the key does not exist or the stored data has a + // type other then T. Otherwise returns a pointer to the stored data. + template const T *get(const std::string &key) const + { + auto it = m_data.find(key); + return it != m_data.end() ? std::any_cast(&(it->second)) : + nullptr; + } + + // Same as above just not const. + template T *get(const std::string &key) + { + auto it = m_data.find(key); + return it != m_data.end() ? std::any_cast(&(it->second)) : nullptr; + } + + bool has_key(const std::string &key) const + { + auto it = m_data.find(key); + return it != m_data.end(); + } +}; + +// Some items can be containers of arbitrary data stored under string keys. +template<> struct DataStoreTraits_ +{ + static constexpr bool Implemented = true; + + template + static const T *get(const ArbitraryDataStore &s, const std::string &key) + { + return s.get(key); + } + + // Same as above just not const. + template + static T *get(ArbitraryDataStore &s, const std::string &key) + { + return s.get(key); + } + + template + static bool has_key(ArbitraryDataStore &s, const std::string &key) + { + return s.has_key(key); + } +}; + +template<> struct WritableDataStoreTraits_ +{ + static constexpr bool Implemented = true; + + template + static void set(ArbitraryDataStore &store, + const std::string &key, + T &&data) + { + store.add(key, std::forward(data)); + } +}; + +}} // namespace Slic3r::arr2 + +#endif // ARBITRARYDATASTORE_HPP diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.cpp b/src/libslic3r/Arrange/Items/ArrangeItem.cpp new file mode 100644 index 0000000000..ff8c761147 --- /dev/null +++ b/src/libslic3r/Arrange/Items/ArrangeItem.cpp @@ -0,0 +1,184 @@ +#include "ArrangeItem.hpp" + +#include "libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp" + +#include "libslic3r/Arrange/ArrangeImpl.hpp" +#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" +#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" +#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" + +#include "libslic3r/Geometry/ConvexHull.hpp" + +namespace Slic3r { namespace arr2 { + +const Polygons &DecomposedShape::transformed_outline() const +{ + if (!m_transformed_outline_valid) { + m_transformed_outline = contours(); + for (Polygon &poly : m_transformed_outline) { + poly.rotate(rotation()); + poly.translate(translation()); + } + + auto sc = scaled(1.) * scaled(1.); + m_area = std::accumulate(m_transformed_outline.begin(), + m_transformed_outline.end(), 0., + [sc](double s, const auto &p) { + return s + p.area() / sc; + }); + + m_convex_hull = Geometry::convex_hull(m_transformed_outline); + m_bounding_box = get_extents(m_convex_hull); + + m_transformed_outline_valid = true; + } + + return m_transformed_outline; +} + +const Polygon &DecomposedShape::convex_hull() const +{ + if (!m_transformed_outline_valid) + transformed_outline(); + + return m_convex_hull; +} + +const BoundingBox &DecomposedShape::bounding_box() const +{ + if (!m_transformed_outline_valid) + transformed_outline(); + + return m_bounding_box; +} + +const Vec2crd &DecomposedShape::reference_vertex() const +{ + if (!m_reference_vertex_valid) { + m_reference_vertex = Slic3r::reference_vertex(transformed_outline()); + m_refs.clear(); + m_mins.clear(); + m_refs.reserve(m_transformed_outline.size()); + m_mins.reserve(m_transformed_outline.size()); + for (auto &poly : m_transformed_outline) { + m_refs.emplace_back(Slic3r::reference_vertex(poly)); + m_mins.emplace_back(Slic3r::min_vertex(poly)); + } + m_reference_vertex_valid = true; + } + + return m_reference_vertex; +} + +const Vec2crd &DecomposedShape::reference_vertex(size_t i) const +{ + if (!m_reference_vertex_valid) { + reference_vertex(); + } + + return m_refs[i]; +} + +const Vec2crd &DecomposedShape::min_vertex(size_t idx) const +{ + if (!m_reference_vertex_valid) { + reference_vertex(); + } + + return m_mins[idx]; +} + +DecomposedShape decompose(const ExPolygons &shape) +{ + ExPolygons shape_s = expolygons_simplify(shape, scaled(.2)); + return DecomposedShape{convex_decomposition_tess(shape_s)}; +} + +DecomposedShape decompose(const Polygon &shape) +{ + Polygons convex_shapes; + + bool is_convex = polygon_is_convex(shape); + if (is_convex) { + convex_shapes.emplace_back(shape); + } else { + Polygon shape_s = shape; + shape_s.simplify(scaled(.2)); + convex_shapes = convex_decomposition_tess(shape_s); + } + + return DecomposedShape{std::move(convex_shapes)}; +} + +ArrangeItem::ArrangeItem(const ExPolygons &shape) + : m_shape{decompose(shape)}, m_envelope{&m_shape} +{} + +ArrangeItem::ArrangeItem(Polygon shape) + : m_shape{decompose(shape)}, m_envelope{&m_shape} +{} + +ArrangeItem::ArrangeItem(const ArrangeItem &other) +{ + this->operator= (other); +} + +ArrangeItem::ArrangeItem(ArrangeItem &&other) noexcept +{ + this->operator=(std::move(other)); +} + +ArrangeItem &ArrangeItem::operator=(const ArrangeItem &other) +{ + m_shape = other.m_shape; + m_datastore = other.m_datastore; + m_bed_idx = other.m_bed_idx; + m_priority = other.m_priority; + + if (other.m_envelope.get() == &other.m_shape) + m_envelope = &m_shape; + else + m_envelope = std::make_unique(other.envelope()); + + return *this; +} + +void ArrangeItem::set_shape(DecomposedShape shape) +{ + m_shape = std::move(shape); + m_envelope = &m_shape; +} + +void ArrangeItem::set_envelope(DecomposedShape envelope) +{ + m_envelope = std::make_unique(std::move(envelope)); + + // Initial synch of transformations of envelope and shape. + // They need to be in synch all the time + m_envelope->translation(m_shape.translation()); + m_envelope->rotation(m_shape.rotation()); +} + +ArrangeItem &ArrangeItem::operator=(ArrangeItem &&other) noexcept +{ + m_shape = std::move(other.m_shape); + m_datastore = std::move(other.m_datastore); + m_bed_idx = other.m_bed_idx; + m_priority = other.m_priority; + + if (other.m_envelope.get() == &other.m_shape) + m_envelope = &m_shape; + else + m_envelope = std::move(other.m_envelope); + + return *this; +} + +template struct ImbueableItemTraits_; +template class ArrangeableToItemConverter; +template struct ArrangeTask; +template struct FillBedTask; +template struct MultiplySelectionTask; +template class Arranger; + +}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.hpp b/src/libslic3r/Arrange/Items/ArrangeItem.hpp new file mode 100644 index 0000000000..1fb276a6be --- /dev/null +++ b/src/libslic3r/Arrange/Items/ArrangeItem.hpp @@ -0,0 +1,461 @@ +#ifndef ARRANGEITEM_HPP +#define ARRANGEITEM_HPP + +#include +#include + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/AnyPtr.hpp" + +#include "libslic3r/Arrange/Core/PackingContext.hpp" +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/NFP/NFP.hpp" + +#include "libslic3r/Arrange/Items/WritableItemTraits.hpp" + +#include "libslic3r/Arrange/Arrange.hpp" +#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" +#include "libslic3r/Arrange/Tasks/FillBedTask.hpp" +#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp" + +#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp" + +#include + +namespace Slic3r { namespace arr2 { + +inline bool check_polygons_are_convex(const Polygons &pp) { + return std::all_of(pp.begin(), pp.end(), [](const Polygon &p) { + return polygon_is_convex(p); + }); +} + +// A class that stores a set of polygons that are garanteed to be all convex. +// They collectively represent a decomposition of a more complex shape into +// its convex part. Note that this class only stores the result of the decomp, +// does not do the job itself. In debug mode, an explicit check is done for +// each component to be convex. +// +// Additionally class stores a translation vector and a rotation angle for the +// stored polygon, plus additional privitives that are all cached cached after +// appying a the transformations. The caching is not thread safe! +class DecomposedShape +{ + Polygons m_shape; + + Vec2crd m_translation{0, 0}; // The translation of the poly + double m_rotation{0.0}; // The rotation of the poly in radians + + mutable Polygons m_transformed_outline; + mutable bool m_transformed_outline_valid = false; + + mutable Point m_reference_vertex; + mutable std::vector m_refs; + mutable std::vector m_mins; + mutable bool m_reference_vertex_valid = false; + + mutable Polygon m_convex_hull; + mutable BoundingBox m_bounding_box; + mutable double m_area = 0; + +public: + DecomposedShape() = default; + + explicit DecomposedShape(Polygon sh) + { + m_shape.emplace_back(std::move(sh)); + assert(check_polygons_are_convex(m_shape)); + } + + explicit DecomposedShape(std::initializer_list pts) + : DecomposedShape(Polygon{pts}) + {} + + explicit DecomposedShape(Polygons sh) : m_shape{std::move(sh)} + { + assert(check_polygons_are_convex(m_shape)); + } + + const Polygons &contours() const { return m_shape; } + + const Vec2crd &translation() const { return m_translation; } + double rotation() const { return m_rotation; } + + void translation(const Vec2crd &v) + { + m_translation = v; + m_transformed_outline_valid = false; + m_reference_vertex_valid = false; + } + + void rotation(double v) + { + m_rotation = v; + m_transformed_outline_valid = false; + m_reference_vertex_valid = false; + } + + const Polygons &transformed_outline() const; + const Polygon &convex_hull() const; + const BoundingBox &bounding_box() const; + + // The cached reference vertex in the context of NFP creation. Always + // refers to the leftmost upper vertex. + const Vec2crd &reference_vertex() const; + const Vec2crd &reference_vertex(size_t idx) const; + + // Also for NFP calculations, the rightmost lowest vertex of the shape. + const Vec2crd &min_vertex(size_t idx) const; + + double area_unscaled() const + { + // update cache + transformed_outline(); + + return m_area; + } +}; + +DecomposedShape decompose(const ExPolygons &polys); +DecomposedShape decompose(const Polygon &p); + +class ArrangeItem +{ +private: + DecomposedShape m_shape; // Shape of item when it's not moving + AnyPtr m_envelope; // Possibly different shape when packed + + ArbitraryDataStore m_datastore; + + int m_bed_idx{Unarranged}; // To which logical bed does this item belong + int m_priority{0}; // For sorting + +public: + ArrangeItem() = default; + + explicit ArrangeItem(DecomposedShape shape) + : m_shape(std::move(shape)), m_envelope{&m_shape} + {} + + explicit ArrangeItem(DecomposedShape shape, DecomposedShape envelope) + : m_shape(std::move(shape)) + , m_envelope{std::make_unique(std::move(envelope))} + {} + + explicit ArrangeItem(const ExPolygons &shape); + explicit ArrangeItem(Polygon shape); + explicit ArrangeItem(std::initializer_list pts) + : ArrangeItem(Polygon{pts}) + {} + + ArrangeItem(const ArrangeItem &); + ArrangeItem(ArrangeItem &&) noexcept; + ArrangeItem & operator=(const ArrangeItem &); + ArrangeItem & operator=(ArrangeItem &&) noexcept; + + int bed_idx() const { return m_bed_idx; } + int priority() const { return m_priority; } + + void bed_idx(int v) { m_bed_idx = v; } + void priority(int v) { m_priority = v; } + + const ArbitraryDataStore &datastore() const { return m_datastore; } + ArbitraryDataStore &datastore() { return m_datastore; } + + const DecomposedShape & shape() const { return m_shape; } + void set_shape(DecomposedShape shape); + + const DecomposedShape & envelope() const { return *m_envelope; } + void set_envelope(DecomposedShape envelope); + + const Vec2crd &translation() const { return m_shape.translation(); } + double rotation() const { return m_shape.rotation(); } + + void translation(const Vec2crd &v) + { + m_shape.translation(v); + m_envelope->translation(v); + } + + void rotation(double v) + { + m_shape.rotation(v); + m_envelope->rotation(v); + } + + void update_caches() const + { + m_shape.reference_vertex(); + m_envelope->reference_vertex(); + } +}; + +template<> struct ArrangeItemTraits_ +{ + static const Vec2crd &get_translation(const ArrangeItem &itm) + { + return itm.translation(); + } + + static double get_rotation(const ArrangeItem &itm) + { + return itm.rotation(); + } + + static int get_bed_index(const ArrangeItem &itm) + { + return itm.bed_idx(); + } + + static int get_priority(const ArrangeItem &itm) + { + return itm.priority(); + } + + // Setters: + + static void set_translation(ArrangeItem &itm, const Vec2crd &v) + { + itm.translation(v); + } + + static void set_rotation(ArrangeItem &itm, double v) + { + itm.rotation(v); + } + + static void set_bed_index(ArrangeItem &itm, int v) + { + itm.bed_idx(v); + } +}; + +// Some items can be containers of arbitrary data stored under string keys. +template<> struct DataStoreTraits_ +{ + static constexpr bool Implemented = true; + + template + static const T *get(const ArrangeItem &itm, const std::string &key) + { + return itm.datastore().get(key); + } + + // Same as above just not const. + template + static T *get(ArrangeItem &itm, const std::string &key) + { + return itm.datastore().get(key); + } + + static bool has_key(const ArrangeItem &itm, const std::string &key) + { + return itm.datastore().has_key(key); + } +}; + +template<> struct WritableDataStoreTraits_ +{ + static constexpr bool Implemented = true; + + template + static void set(ArrangeItem &itm, + const std::string &key, + T &&data) + { + itm.datastore().add(key, std::forward(data)); + } +}; + +template +static Polygons calculate_nfp_unnormalized(const ArrangeItem &item, + const Range &fixed_items, + StopCond &&stop_cond = {}) +{ + size_t cap = 0; + + for (const ArrangeItem &fixitem : fixed_items) { + const Polygons &outlines = fixitem.shape().transformed_outline(); + cap += outlines.size(); + } + + const Polygons &item_outlines = item.envelope().transformed_outline(); + + auto nfps = reserve_polygons(cap * item_outlines.size()); + + Vec2crd ref_whole = item.envelope().reference_vertex(); + Polygon subnfp; + + for (const ArrangeItem &fixed : fixed_items) { + // fixed_polys should already be a set of strictly convex polygons, + // as ArrangeItem stores convex-decomposed polygons + const Polygons & fixed_polys = fixed.shape().transformed_outline(); + + for (const Polygon &fixed_poly : fixed_polys) { + Point max_fixed = Slic3r::reference_vertex(fixed_poly); + for (size_t mi = 0; mi < item_outlines.size(); ++mi) { + const Polygon &movable = item_outlines[mi]; + const Vec2crd &mref = item.envelope().reference_vertex(mi); + subnfp = nfp_convex_convex_legacy(fixed_poly, movable); + + Vec2crd min_movable = item.envelope().min_vertex(mi); + + Vec2crd dtouch = max_fixed - min_movable; + Vec2crd top_other = mref + dtouch; + Vec2crd max_nfp = Slic3r::reference_vertex(subnfp); + auto dnfp = top_other - max_nfp; + + auto d = ref_whole - mref + dnfp; + subnfp.translate(d); + nfps.emplace_back(subnfp); + } + + if (stop_cond()) + break; + + nfps = union_(nfps); + } + + if (stop_cond()) { + nfps.clear(); + break; + } + } + + return nfps; +} + +template<> struct NFPArrangeItemTraits_ { + template + static ExPolygons calculate_nfp(const ArrangeItem &item, + const Context &packing_context, + const Bed &bed, + StopCond &&stopcond) + { + auto static_items = all_items_range(packing_context); + Polygons nfps = arr2::calculate_nfp_unnormalized(item, static_items, stopcond); + + ExPolygons nfp_ex; + + if (!stopcond()) { + if constexpr (!std::is_convertible_v) { + ExPolygons ifpbed = ifp_convex(bed, item.envelope().convex_hull()); + nfp_ex = diff_ex(ifpbed, nfps); + } else { + nfp_ex = union_ex(nfps); + } + } + + item.update_caches(); + + return nfp_ex; + } + + static const Vec2crd& reference_vertex(const ArrangeItem &item) + { + return item.envelope().reference_vertex(); + } + + static BoundingBox envelope_bounding_box(const ArrangeItem &itm) + { + return itm.envelope().bounding_box(); + } + + static BoundingBox fixed_bounding_box(const ArrangeItem &itm) + { + return itm.shape().bounding_box(); + } + + static double envelope_area(const ArrangeItem &itm) + { + return itm.envelope().area_unscaled() * scaled(1.) * + scaled(1.); + } + + static double fixed_area(const ArrangeItem &itm) + { + return itm.shape().area_unscaled() * scaled(1.) * + scaled(1.); + } + + static const Polygons & envelope_outline(const ArrangeItem &itm) + { + return itm.envelope().transformed_outline(); + } + + static const Polygons & fixed_outline(const ArrangeItem &itm) + { + return itm.shape().transformed_outline(); + } + + static const Polygon & envelope_convex_hull(const ArrangeItem &itm) + { + return itm.envelope().convex_hull(); + } + + static const Polygon & fixed_convex_hull(const ArrangeItem &itm) + { + return itm.shape().convex_hull(); + } + + static const std::vector& allowed_rotations(const ArrangeItem &itm) + { + static const std::vector ret_zero = {0.}; + + const std::vector * ret_ptr = &ret_zero; + + auto rots = get_data>(itm, "rotations"); + if (rots) { + ret_ptr = rots; + } + + return *ret_ptr; + } +}; + +template<> struct IsWritableItem_: public std::true_type {}; + +template<> +struct WritableItemTraits_ { + + static void set_priority(ArrangeItem &itm, int p) { itm.priority(p); } + static void set_convex_shape(ArrangeItem &itm, const Polygon &shape) + { + itm.set_shape(DecomposedShape{shape}); + } + static void set_shape(ArrangeItem &itm, const ExPolygons &shape) + { + itm.set_shape(decompose(shape)); + } + static void set_convex_envelope(ArrangeItem &itm, const Polygon &envelope) + { + itm.set_envelope(DecomposedShape{envelope}); + } + static void set_envelope(ArrangeItem &itm, const ExPolygons &envelope) + { + itm.set_envelope(decompose(envelope)); + } + + template + static void set_arbitrary_data(ArrangeItem &itm, const std::string &key, T &&data) + { + set_data(itm, key, std::forward(data)); + } + + static void set_allowed_rotations(ArrangeItem &itm, const std::vector &rotations) + { + set_data(itm, "rotations", rotations); + } +}; + +extern template struct ImbueableItemTraits_; +extern template class ArrangeableToItemConverter; +extern template struct ArrangeTask; +extern template struct FillBedTask; +extern template struct MultiplySelectionTask; +extern template class Arranger; + +}} // namespace Slic3r::arr2 + +#endif // ARRANGEITEM_HPP diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp new file mode 100644 index 0000000000..75ab1cf423 --- /dev/null +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp @@ -0,0 +1,15 @@ +#include "SimpleArrangeItem.hpp" +#include "libslic3r/Arrange/ArrangeImpl.hpp" + +namespace Slic3r { namespace arr2 { + +Polygon SimpleArrangeItem::outline() const +{ + Polygon ret = shape(); + ret.rotate(m_rotation); + ret.translate(m_translation); + + return ret; +} + +}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp new file mode 100644 index 0000000000..4f321c93b0 --- /dev/null +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp @@ -0,0 +1,178 @@ +#ifndef SIMPLEARRANGEITEM_HPP +#define SIMPLEARRANGEITEM_HPP + +#include "libslic3r/Arrange/Core/PackingContext.hpp" + +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/NFP/NFP.hpp" + +#include "libslic3r/Arrange/Arrange.hpp" + +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" + +#include "WritableItemTraits.hpp" + +namespace Slic3r { namespace arr2 { + +class SimpleArrangeItem { + Polygon m_shape; + + Vec2crd m_translation = Vec2crd::Zero(); + double m_rotation = 0.; + int m_priority = 0; + int m_bed_idx = Unarranged; + + std::vector m_allowed_rotations = {0.}; + +public: + explicit SimpleArrangeItem(Polygon chull = {}): m_shape{std::move(chull)} {} + + void set_shape(Polygon chull) { m_shape = std::move(chull); } + + const Vec2crd& get_translation() const noexcept { return m_translation; } + double get_rotation() const noexcept { return m_rotation; } + int get_priority() const noexcept { return m_priority; } + int get_bed_index() const noexcept { return m_bed_idx; } + + void set_translation(const Vec2crd &v) { m_translation = v; } + void set_rotation(double v) noexcept { m_rotation = v; } + void set_priority(int v) noexcept { m_priority = v; } + void set_bed_index(int v) noexcept { m_bed_idx = v; } + + const Polygon &shape() const { return m_shape; } + Polygon outline() const; + + const auto &allowed_rotations() const noexcept + { + return m_allowed_rotations; + } + + void set_allowed_rotations(std::vector rots) + { + m_allowed_rotations = std::move(rots); + } +}; + +template<> struct NFPArrangeItemTraits_ +{ + template + static ExPolygons calculate_nfp(const SimpleArrangeItem &item, + const Context &packing_context, + const Bed &bed, + StopCond &&stop_cond) + { + auto fixed_items = all_items_range(packing_context); + auto nfps = reserve_polygons(fixed_items.size()); + for (const SimpleArrangeItem &fixed_part : fixed_items) { + Polygon subnfp = nfp_convex_convex_legacy(fixed_part.outline(), + item.outline()); + nfps.emplace_back(subnfp); + + + if (stop_cond()) { + nfps.clear(); + break; + } + } + + ExPolygons nfp_ex; + if (!stop_cond()) { + if constexpr (!std::is_convertible_v) { + ExPolygons ifpbed = ifp_convex(bed, item.outline()); + nfp_ex = diff_ex(ifpbed, nfps); + } else { + nfp_ex = union_ex(nfps); + } + } + + return nfp_ex; + } + + static Vec2crd reference_vertex(const SimpleArrangeItem &item) + { + return Slic3r::reference_vertex(item.outline()); + } + + static BoundingBox envelope_bounding_box(const SimpleArrangeItem &itm) + { + return get_extents(itm.outline()); + } + + static BoundingBox fixed_bounding_box(const SimpleArrangeItem &itm) + { + return get_extents(itm.outline()); + } + + static Polygons envelope_outline(const SimpleArrangeItem &itm) + { + return {itm.outline()}; + } + + static Polygons fixed_outline(const SimpleArrangeItem &itm) + { + return {itm.outline()}; + } + + static Polygon envelope_convex_hull(const SimpleArrangeItem &itm) + { + return Geometry::convex_hull(itm.outline()); + } + + static Polygon fixed_convex_hull(const SimpleArrangeItem &itm) + { + return Geometry::convex_hull(itm.outline()); + } + + static double envelope_area(const SimpleArrangeItem &itm) + { + return itm.shape().area(); + } + + static double fixed_area(const SimpleArrangeItem &itm) + { + return itm.shape().area(); + } + + static const auto& allowed_rotations(const SimpleArrangeItem &itm) noexcept + { + return itm.allowed_rotations(); + } +}; + +template<> struct IsWritableItem_: public std::true_type {}; + +template<> +struct WritableItemTraits_ { + + static void set_priority(SimpleArrangeItem &itm, int p) { itm.set_priority(p); } + static void set_convex_shape(SimpleArrangeItem &itm, const Polygon &shape) + { + itm.set_shape(shape); + } + static void set_shape(SimpleArrangeItem &itm, const ExPolygons &shape) + { + itm.set_shape(Geometry::convex_hull(shape)); + } + static void set_convex_envelope(SimpleArrangeItem &itm, const Polygon &envelope) + { + itm.set_shape(envelope); + } + static void set_envelope(SimpleArrangeItem &itm, const ExPolygons &envelope) + { + itm.set_shape(Geometry::convex_hull(envelope)); + } + + template + static void set_data(SimpleArrangeItem &itm, const std::string &key, T &&data) + {} + + static void set_allowed_rotations(SimpleArrangeItem &itm, const std::vector &rotations) + { + itm.set_allowed_rotations(rotations); + } +}; + +}} // namespace Slic3r::arr2 + +#endif // SIMPLEARRANGEITEM_HPP diff --git a/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp b/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp new file mode 100644 index 0000000000..90b6651563 --- /dev/null +++ b/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp @@ -0,0 +1,79 @@ +#ifndef TRAFOONLYARRANGEITEM_HPP +#define TRAFOONLYARRANGEITEM_HPP + +#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" + +#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp" +#include "libslic3r/Arrange/Items/WritableItemTraits.hpp" + +namespace Slic3r { namespace arr2 { + +class TrafoOnlyArrangeItem { + int m_bed_idx = Unarranged; + int m_priority = 0; + Vec2crd m_translation = Vec2crd::Zero(); + double m_rotation = 0.; + + ArbitraryDataStore m_datastore; + +public: + TrafoOnlyArrangeItem() = default; + + template + explicit TrafoOnlyArrangeItem(const ArrItm &other) + : m_bed_idx{arr2::get_bed_index(other)}, + m_priority{arr2::get_priority(other)}, + m_translation(arr2::get_translation(other)), + m_rotation{arr2::get_rotation(other)} + {} + + const Vec2crd& get_translation() const noexcept { return m_translation; } + double get_rotation() const noexcept { return m_rotation; } + int get_bed_index() const noexcept { return m_bed_idx; } + int get_priority() const noexcept { return m_priority; } + + const ArbitraryDataStore &datastore() const noexcept { return m_datastore; } + ArbitraryDataStore &datastore() { return m_datastore; } +}; + +template<> struct DataStoreTraits_ +{ + static constexpr bool Implemented = true; + + template + static const T *get(const TrafoOnlyArrangeItem &itm, const std::string &key) + { + return itm.datastore().get(key); + } + + template + static T *get(TrafoOnlyArrangeItem &itm, const std::string &key) + { + return itm.datastore().get(key); + } + + static bool has_key(const TrafoOnlyArrangeItem &itm, const std::string &key) + { + return itm.datastore().has_key(key); + } +}; + +template<> struct IsWritableItem_: public std::true_type {}; + +template<> struct WritableDataStoreTraits_ +{ + static constexpr bool Implemented = true; + + template + static void set(TrafoOnlyArrangeItem &itm, + const std::string &key, + T &&data) + { + set_data(itm.datastore(), key, std::forward(data)); + } +}; + +} // namespace arr2 +} // namespace Slic3r + +#endif // TRAFOONLYARRANGEITEM_HPP diff --git a/src/libslic3r/Arrange/Items/WritableItemTraits.hpp b/src/libslic3r/Arrange/Items/WritableItemTraits.hpp new file mode 100644 index 0000000000..04281faa52 --- /dev/null +++ b/src/libslic3r/Arrange/Items/WritableItemTraits.hpp @@ -0,0 +1,112 @@ +#ifndef WRITABLEITEMTRAITS_HPP +#define WRITABLEITEMTRAITS_HPP + +#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" + +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { namespace arr2 { + +template struct IsWritableItem_ : public std::false_type +{}; + +// Using this interface to set up any arrange item. Provides default +// implementation but it needs to be explicitly switched on with +// IsWritableItem_ or completely reimplement a specialization. +template struct WritableItemTraits_ +{ + static_assert(IsWritableItem_::value, "Not a Writable item type!"); + + static void set_priority(Itm &itm, int p) { itm.set_priority(p); } + + static void set_convex_shape(Itm &itm, const Polygon &shape) + { + itm.set_convex_shape(shape); + } + + static void set_shape(Itm &itm, const ExPolygons &shape) + { + itm.set_shape(shape); + } + + static void set_convex_envelope(Itm &itm, const Polygon &envelope) + { + itm.set_convex_envelope(envelope); + } + + static void set_envelope(Itm &itm, const ExPolygons &envelope) + { + itm.set_envelope(envelope); + } + + template + static void set_arbitrary_data(Itm &itm, const std::string &key, T &&data) + { + if constexpr (IsWritableDataStore) + set_data(itm, key, std::forward(data)); + } + + static void set_allowed_rotations(Itm &itm, + const std::vector &rotations) + { + itm.set_allowed_rotations(rotations); + } +}; + +template +using WritableItemTraits = WritableItemTraits_>; + +template constexpr bool IsWritableItem = IsWritableItem_::value; +template +using WritableItemOnly = std::enable_if_t, TT>; + +template void set_priority(Itm &itm, int p) +{ + WritableItemTraits::set_priority(itm, p); +} + +template void set_convex_shape(Itm &itm, const Polygon &shape) +{ + WritableItemTraits::set_convex_shape(itm, shape); +} + +template void set_shape(Itm &itm, const ExPolygons &shape) +{ + WritableItemTraits::set_shape(itm, shape); +} + +template +void set_convex_envelope(Itm &itm, const Polygon &envelope) +{ + WritableItemTraits::set_convex_envelope(itm, envelope); +} + +template void set_envelope(Itm &itm, const ExPolygons &envelope) +{ + WritableItemTraits::set_envelope(itm, envelope); +} + +template +void set_arbitrary_data(Itm &itm, const std::string &key, T &&data) +{ + WritableItemTraits::set_arbitrary_data(itm, key, std::forward(data)); +} + +template +void set_allowed_rotations(Itm &itm, const std::vector &rotations) +{ + WritableItemTraits::set_arbitrary_data(itm, "rotations", rotations); +} + +template int raise_priority(ArrItem &itm) +{ + int ret = get_priority(itm) + 1; + set_priority(itm, ret); + + return ret; +} + +}} // namespace Slic3r::arr2 + +#endif // WRITABLEITEMTRAITS_HPP diff --git a/src/libslic3r/Arrange/Scene.cpp b/src/libslic3r/Arrange/Scene.cpp new file mode 100644 index 0000000000..a59b3f9aec --- /dev/null +++ b/src/libslic3r/Arrange/Scene.cpp @@ -0,0 +1,64 @@ +#include "Scene.hpp" + +#include "Items/ArrangeItem.hpp" + +#include "Tasks/ArrangeTask.hpp" +#include "Tasks/FillBedTask.hpp" + +namespace Slic3r { namespace arr2 { + +std::vector Scene::selected_ids() const +{ + auto items = reserve_vector(model().arrangeable_count()); + + model().for_each_arrangeable([ &items](auto &arrbl) mutable { + if (arrbl.is_selected()) + items.emplace_back(arrbl.id()); + }); + + return items; +} + +using DefaultArrangeItem = ArrangeItem; + +std::unique_ptr ArrangeTaskBase::create(Tasks task_type, const Scene &sc) +{ + std::unique_ptr ret; + switch(task_type) { + case Tasks::Arrange: + ret = ArrangeTask::create(sc); + break; + case Tasks::FillBed: + ret = FillBedTask::create(sc); + break; + default: + ; + } + + return ret; +} + +std::set selected_geometry_ids(const Scene &sc) +{ + std::set result; + + std::vector selected_ids = sc.selected_ids(); + for (const ObjectID &id : selected_ids) { + sc.model().visit_arrangeable(id, [&result](const Arrangeable &arrbl) { + auto id = arrbl.geometry_id(); + if (id.valid()) + result.insert(arrbl.geometry_id()); + }); + } + + return result; +} + +void arrange(Scene &scene, ArrangeTaskCtl &ctl) +{ + auto task = ArrangeTaskBase::create(Tasks::Arrange, scene); + auto result = task->process(ctl); + result->apply_on(scene.model()); +} + +}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp new file mode 100644 index 0000000000..07069f0bf8 --- /dev/null +++ b/src/libslic3r/Arrange/Scene.hpp @@ -0,0 +1,308 @@ +#ifndef ARR2_SCENE_HPP +#define ARR2_SCENE_HPP + +#include +#include + +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/AnyPtr.hpp" +#include "libslic3r/Arrange/ArrangeSettingsView.hpp" +#include "libslic3r/Arrange/SegmentedRectangleBed.hpp" + +namespace Slic3r { namespace arr2 { + +class AnyWritable +{ +public: + virtual ~AnyWritable() = default; + + virtual void write(std::string_view key, std::any d) = 0; +}; + +class Arrangeable +{ +public: + virtual ~Arrangeable() = default; + + virtual ObjectID id() const = 0; + virtual ObjectID geometry_id() const = 0; + virtual ExPolygons full_outline() const = 0; + virtual Polygon convex_outline() const = 0; + + virtual ExPolygons full_envelope() const { return {}; } + virtual Polygon convex_envelope() const { return {}; } + + virtual void transform(const Vec2d &transl, double rot) = 0; + + virtual bool is_printable() const { return true; } + virtual bool is_selected() const { return true; } + virtual int priority() const { return 0; } + + virtual void imbue_data(AnyWritable &datastore) const {} + void imbue_data(AnyWritable &&datastore) const { imbue_data(datastore); } + + // Returns the bed index on which the given ModelInstance is sitting. + virtual int get_bed_index() const = 0; + + // Assign the ModelInstance to the given bed index. Note that this + // method can return false, indicating that the given bed is not available + // to be occupied (e.g. the handler has a limited amount of logical bed) + virtual bool assign_bed(int bed_idx) = 0; +}; + +class ArrangeableModel +{ +public: + virtual ~ArrangeableModel() = default; + + virtual void for_each_arrangeable(std::function) = 0; + virtual void for_each_arrangeable(std::function) const = 0; + + virtual void visit_arrangeable(const ObjectID &id, std::function) const = 0; + virtual void visit_arrangeable(const ObjectID &id, std::function) = 0; + + virtual ObjectID add_arrangeable(const ObjectID &prototype_id) = 0; + + size_t arrangeable_count() const + { + size_t cnt = 0; + for_each_arrangeable([&cnt](auto &) { ++cnt; }); + + return cnt; + } +}; + +using XLBed = SegmentedRectangleBed, + std::integral_constant>; + +template struct ExtendedBed_ +{ + using Type = + boost::variant; +}; + +template struct ExtendedBed_> +{ + using Type = boost::variant; +}; + +using ExtendedBed = typename ExtendedBed_::Type; + +template void visit_bed(BedFn &&fn, const ExtendedBed &bed) +{ + boost::apply_visitor(fn, bed); +} + +template void visit_bed(BedFn &&fn, ExtendedBed &bed) +{ + boost::apply_visitor(fn, bed); +} + +inline ExtendedBed to_extended_bed(const ArrangeBed &bed) +{ + ExtendedBed ret; + boost::apply_visitor([&ret](auto &rawbed) { ret = rawbed; }, bed); + + return ret; +} + +class Scene; + +// A little CRTP to implement fluent interface returning Subclass references +template +class SceneBuilderBase +{ +protected: + AnyPtr m_arrangeable_model; + + AnyPtr m_settings; + + ExtendedBed m_bed = arr2::InfiniteBed{}; + + coord_t m_brims_offs = 0; + coord_t m_skirt_offs = 0; + +public: + + virtual ~SceneBuilderBase() = default; + + SceneBuilderBase() = default; + SceneBuilderBase(const SceneBuilderBase &) = delete; + SceneBuilderBase& operator=(const SceneBuilderBase &) = delete; + SceneBuilderBase(SceneBuilderBase &&) = default; + SceneBuilderBase& operator=(SceneBuilderBase &&) = default; + + // All setters return an rvalue reference so that at the end, the + // build_scene method can be called fluently + + Subclass &&set_arrange_settings(AnyPtr settings) + { + m_settings = std::move(settings); + return std::move(static_cast(*this)); + } + + Subclass &&set_arrange_settings(const ArrangeSettingsView &settings) + { + m_settings = std::make_unique(settings); + return std::move(static_cast(*this)); + } + + Subclass &&set_bed(const Points &pts) + { + m_bed = arr2::to_arrange_bed(pts); + return std::move(static_cast(*this)); + } + + Subclass && set_bed(const arr2::ArrangeBed &bed) + { + m_bed = bed; + return std::move(static_cast(*this)); + } + + Subclass &&set_bed(const XLBed &bed) + { + m_bed = bed; + return std::move(static_cast(*this)); + } + + Subclass &&set_arrangeable_model(AnyPtr model) + { + m_arrangeable_model = std::move(model); + return std::move(static_cast(*this)); + } + + // Can only be called on an rvalue instance (hence the && at the end), + // the method will potentially move its content into sc + virtual void build_scene(Scene &sc) &&; +}; + +class BasicSceneBuilder: public SceneBuilderBase {}; + +class Scene +{ + template friend class SceneBuilderBase; + + AnyPtr m_amodel; + AnyPtr m_settings; + ExtendedBed m_bed; + +public: + // Can only be built from an rvalue SceneBuilder, as it's content will + // potentially be moved to the constructed ArrangeScene object + template + explicit Scene(SceneBuilderBase &&bld) + { + std::move(bld).build_scene(*this); + } + + const ArrangeableModel &model() const noexcept { return *m_amodel; } + ArrangeableModel &model() noexcept { return *m_amodel; } + + const ArrangeSettingsView &settings() const noexcept { return *m_settings; } + + template void visit_bed(BedFn &&fn) const + { + arr2::visit_bed(fn, m_bed); + } + + const ExtendedBed & bed() const { return m_bed; } + + std::vector selected_ids() const; +}; + +std::set selected_geometry_ids(const Scene &sc); + +class EmptyArrangeableModel: public ArrangeableModel +{ +public: + void for_each_arrangeable(std::function) override {} + void for_each_arrangeable(std::function) const override {} + void visit_arrangeable(const ObjectID &id, std::function) const override {} + void visit_arrangeable(const ObjectID &id, std::function) override {} + ObjectID add_arrangeable(const ObjectID &prototype_id) override { return {}; } +}; + +template +void SceneBuilderBase::build_scene(Scene &sc) && +{ + if (!m_arrangeable_model) + m_arrangeable_model = std::make_unique(); + + if (!m_settings) + m_settings = std::make_unique(); + + coord_t inset = std::max(scaled(m_settings->get_distance_from_bed()), + m_skirt_offs + m_brims_offs); + + coord_t md = scaled(m_settings->get_distance_from_objects()); + md = md / 2 - inset; + + visit_bed([md](auto &rawbed) { rawbed = offset(rawbed, md); }, m_bed); + + sc.m_settings = std::move(m_settings); + sc.m_amodel = std::move(m_arrangeable_model); + sc.m_bed = std::move(m_bed); +} + +class ArrangeResult +{ +public: + virtual ~ArrangeResult() = default; + + virtual bool apply_on(ArrangeableModel &mdlwt) = 0; +}; + +enum class Tasks { Arrange, FillBed }; + +class ArrangeTaskCtl +{ +public: + virtual ~ArrangeTaskCtl() = default; + + virtual void update_status(int st) = 0; + + virtual bool was_canceled() const = 0; +}; + +class DummyCtl : public ArrangeTaskCtl +{ +public: + void update_status(int) override {} + bool was_canceled() const override { return false; } +}; + +class ArrangeTaskBase +{ +public: + using Ctl = ArrangeTaskCtl; + + virtual ~ArrangeTaskBase() = default; + + [[nodiscard]] virtual std::unique_ptr process(Ctl &ctl) = 0; + + [[nodiscard]] virtual int item_count_to_process() const = 0; + + [[nodiscard]] static std::unique_ptr create( + Tasks task_type, const Scene &sc); + + [[nodiscard]] std::unique_ptr process(Ctl &&ctl) + { + return process(ctl); + } + + [[nodiscard]] std::unique_ptr process() + { + return process(DummyCtl{}); + } +}; + +void arrange(Scene &scene, ArrangeTaskCtl &ctl); +inline void arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{}) +{ + arrange(scene, ctl); +} + +} // namespace arr2 +} // namespace Slic3r + +#endif // ARR2_SCENE_HPP diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp new file mode 100644 index 0000000000..ecebb5a8f7 --- /dev/null +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -0,0 +1,859 @@ +#ifndef SCENEBUILDER_CPP +#define SCENEBUILDER_CPP + +#include "SceneBuilder.hpp" + +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" + +#include "Core/ArrangeItemTraits.hpp" +#include "Geometry/ConvexHull.hpp" + +namespace Slic3r { namespace arr2 { + +coord_t get_skirt_inset(const Print &fffprint) +{ + float skirt_inset = 0.f; + + if (fffprint.has_skirt()) { + float skirtflow = fffprint.objects().empty() + ? 0 + : fffprint.skirt_flow().width(); + skirt_inset = fffprint.config().skirts.value * skirtflow + + fffprint.config().skirt_distance.value; + } + + return scaled(skirt_inset); +} + +coord_t brim_offset(const PrintObject &po) +{ + const BrimType brim_type = po.config().brim_type.value; + const float brim_separation = po.config().brim_separation.getFloat(); + const float brim_width = po.config().brim_width.getFloat(); + const bool has_outer_brim = brim_type == BrimType::btOuterOnly || + brim_type == BrimType::btOuterAndInner; + + // How wide is the brim? (in scaled units) + return has_outer_brim ? scaled(brim_width + brim_separation) : 0; +} + +size_t model_instance_count (const Model &m) +{ + return std::accumulate(m.objects.begin(), + m.objects.end(), + size_t(0), + [](size_t s, const Slic3r::ModelObject *mo) { + return s + mo->instances.size(); + }); +} + +void transform_instance(ModelInstance &mi, + const Vec2d &transl_unscaled, + double rot, + const Transform3d &physical_tr) +{ + auto trafo = mi.get_transformation().get_matrix(); + auto tr = Transform3d::Identity(); + tr.translate(to_3d(transl_unscaled, 0.)); + trafo = physical_tr.inverse() * tr * Eigen::AngleAxisd(rot, Vec3d::UnitZ()) * physical_tr * trafo; + + mi.set_transformation(Geometry::Transformation{trafo}); + + mi.invalidate_object_bounding_box(); +} + +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate) +{ + BoundingBoxf3 bb; + const Transform3d inst_matrix + = dont_translate ? mi.get_transformation().get_matrix_no_offset() + : mi.get_transformation().get_matrix(); + + for (ModelVolume *v : mi.get_object()->volumes) { + if (v->is_model_part()) { + bb.merge(v->mesh().transformed_bounding_box(inst_matrix + * v->get_matrix())); + } + } + + return bb; +} + +ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) +{ + ExPolygons outline; + for (const ModelVolume *v : inst.get_object()->volumes) { + Polygons vol_outline; + vol_outline = project_mesh(v->mesh().its, + tr * inst.get_matrix() * v->get_matrix(), + [] {}); + switch (v->type()) { + case ModelVolumeType::MODEL_PART: + outline = union_ex(outline, vol_outline); + break; + case ModelVolumeType::NEGATIVE_VOLUME: + outline = diff_ex(outline, vol_outline); + break; + default:; + } + } + + return outline; +} + +Polygon extract_convex_outline(const ModelInstance &inst, const Transform3d &tr) +{ + return inst.get_object()->convex_hull_2d(tr * inst.get_matrix()); +} + +inline static bool is_infinite_bed(const ExtendedBed &ebed) noexcept +{ + bool ret = false; + visit_bed( + [&ret](auto &rawbed) { + ret = std::is_convertible_v; + }, + ebed); + + return ret; +} + +void SceneBuilder::set_brim_and_skirt() +{ + if (!m_fff_print) + return; + + m_brims_offs = 0; + + for (const PrintObject *po : m_fff_print->objects()) { + if (po) { + m_brims_offs = std::max(m_brims_offs, brim_offset(*po)); + } + } + + m_skirt_offs = get_skirt_inset(*m_fff_print); +} + +void SceneBuilder::build_scene(Scene &sc) && +{ + if (m_sla_print && !m_fff_print) { + m_arrangeable_model = std::make_unique(m_sla_print.get(), *this); + } else { + m_arrangeable_model = std::make_unique(*this); + } + + if (m_fff_print && !m_sla_print) { + if (is_infinite_bed(m_bed)) { + set_bed(*m_fff_print); + } else { + set_brim_and_skirt(); + } + } + + std::move(*this).SceneBuilderBase::build_scene(sc); +} + +void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel) +{ + if (!m_model) + m_model = std::make_unique(); + + if (!m_selection) + m_selection = std::make_unique(*m_model); + + if (!m_vbed_handler) { + m_vbed_handler = VirtualBedHandler::create(m_bed); + } + + if (!m_wipetower_handler) { + m_wipetower_handler = std::make_unique(); + } + + amodel.m_vbed_handler = std::move(m_vbed_handler); + amodel.m_model = std::move(m_model); + amodel.m_selmask = std::move(m_selection); + amodel.m_wth = std::move(m_wipetower_handler); + + amodel.m_wth->set_selection_predicate( + [&amodel] { return amodel.m_selmask->is_wipe_tower(); }); +} + +int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const +{ + int bedidx = 0; + auto stride_s = stride_scaled(); + if (stride_s > 0) { + double bedx = unscaled(m_start); + auto instance_bb = obj.bounding_box(); + auto reference_pos_x = (instance_bb.min.x() - bedx); + auto stride = unscaled(stride_s); + + bedidx = static_cast(std::floor(reference_pos_x / stride)); + } + + return bedidx; +} + +bool XStriderVBedHandler::assign_bed(VBedPlaceable &obj, int bed_index) +{ + bool ret = false; + auto stride_s = stride_scaled(); + if (bed_index == 0 || (bed_index > 0 && stride_s > 0)) { + auto current_bed_index = get_bed_index(obj); + auto stride = unscaled(stride_s); + auto transl = Vec2d{(bed_index - current_bed_index) * stride, 0.}; + obj.displace(transl, 0.); + + ret = true; + } + + return ret; +} + +Transform3d XStriderVBedHandler::get_physical_bed_trafo(int bed_index) const +{ + auto stride_s = stride_scaled(); + auto tr = Transform3d::Identity(); + tr.translate(Vec3d{-bed_index * unscaled(stride_s), 0., 0.}); + + return tr; +} + +int YStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const +{ + int bedidx = 0; + auto stride_s = stride_scaled(); + if (stride_s > 0) { + double ystart = unscaled(m_start); + auto instance_bb = obj.bounding_box(); + auto reference_pos_y = (instance_bb.min.y() - ystart); + auto stride = unscaled(stride_s); + + bedidx = static_cast(std::floor(reference_pos_y / stride)); + } + + return bedidx; +} + +bool YStriderVBedHandler::assign_bed(VBedPlaceable &obj, int bed_index) +{ + bool ret = false; + auto stride_s = stride_scaled(); + if (bed_index == 0 || (bed_index > 0 && stride_s > 0)) { + auto current_bed_index = get_bed_index(obj); + auto stride = unscaled(stride_s); + auto transl = Vec2d{0., (bed_index - current_bed_index) * stride}; + obj.displace(transl, 0.); + + ret = true; + } + + return ret; +} + +Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const +{ + auto stride_s = stride_scaled(); + auto tr = Transform3d::Identity(); + tr.translate(Vec3d{0., -bed_index * unscaled(stride_s), 0.}); + + return tr; +} + +FixedSelection::FixedSelection(const Model &m) : m_wp{true} +{ + m_seldata.resize(m.objects.size()); + for (size_t i = 0; i < m.objects.size(); ++i) { + m_seldata[i].resize(m.objects[i]->instances.size(), true); + } +} + +FixedSelection::FixedSelection(const SelectionMask &other) +{ + auto obj_sel = other.selected_objects(); + m_seldata.reserve(obj_sel.size()); + for (int oidx = 0; oidx < static_cast(obj_sel.size()); ++oidx) + m_seldata.emplace_back(other.selected_instances(oidx)); +} + +std::vector FixedSelection::selected_objects() const +{ + auto ret = Slic3r::reserve_vector(m_seldata.size()); + std::transform(m_seldata.begin(), + m_seldata.end(), + std::back_inserter(ret), + [](auto &a) { + return std::any_of(a.begin(), a.end(), [](bool b) { + return b; + }); + }); + return ret; +} + +static std::vector find_true_indices(const std::vector &v) +{ + auto ret = reserve_vector(v.size()); + + for (size_t i = 0; i < v.size(); ++i) + if (v[i]) + ret.emplace_back(i); + + return ret; +} + +std::vector selected_object_indices(const SelectionMask &sm) +{ + auto sel = sm.selected_objects(); + return find_true_indices(sel); +} + +std::vector selected_instance_indices(int obj_idx, const SelectionMask &sm) +{ + auto sel = sm.selected_instances(obj_idx); + return find_true_indices(sel); +} + +SceneBuilder::SceneBuilder() = default; +SceneBuilder::~SceneBuilder() = default; +SceneBuilder::SceneBuilder(SceneBuilder &&) = default; +SceneBuilder& SceneBuilder::operator=(SceneBuilder&&) = default; + +SceneBuilder &&SceneBuilder::set_model(AnyPtr mdl) +{ + m_model = std::move(mdl); + return std::move(*this); +} + +SceneBuilder &&SceneBuilder::set_model(Model &mdl) +{ + m_model = &mdl; + return std::move(*this); +} + +SceneBuilder &&SceneBuilder::set_fff_print(AnyPtr mdl_print) +{ + m_fff_print = std::move(mdl_print); + return std::move(*this); +} + +SceneBuilder &&SceneBuilder::set_sla_print(AnyPtr mdl_print) +{ + m_sla_print = std::move(mdl_print); + + return std::move(*this); +} + +SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg) +{ + Points bedpts = get_bed_shape(cfg); + + if (is_XL_printer(cfg)) { + m_bed = XLBed{get_extents(bedpts)}; + } else { + m_bed = arr2::to_arrange_bed(bedpts); + } + + return std::move(*this); +} + +SceneBuilder &&SceneBuilder::set_bed(const Print &print) +{ + Points bedpts = get_bed_shape(print.config()); + + if (is_XL_printer(print.config())) { + m_bed = XLBed{get_extents(bedpts)}; + } else { + m_bed = arr2::to_arrange_bed(bedpts); + } + + set_brim_and_skirt(); + + return std::move(*this); +} + +SceneBuilder &&SceneBuilder::set_sla_print(const SLAPrint *slaprint) +{ + m_sla_print = slaprint; + return std::move(*this); +} + +int ArrangeableWipeTowerBase::get_bed_index() const { return PhysicalBedId; } + +bool ArrangeableWipeTowerBase::assign_bed(int bed_idx) +{ + return bed_idx == PhysicalBedId; +} + +bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) +{ + return bed_idx == PhysicalBedId; +} + +ArrangeableSlicerModel::ArrangeableSlicerModel(SceneBuilder &builder) +{ + builder.build_arrangeable_slicer_model(*this); +} + +ArrangeableSlicerModel::~ArrangeableSlicerModel() = default; + +void ArrangeableSlicerModel::for_each_arrangeable( + std::function fn) +{ + for_each_arrangeable_(*this, fn); + + m_wth->visit(fn); +} + +void ArrangeableSlicerModel::for_each_arrangeable( + std::function fn) const +{ + for_each_arrangeable_(*this, fn); + + m_wth->visit(fn); +} + +ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id) +{ + ObjectID ret; + + auto [inst, pos] = find_instance_by_id(*m_model, prototype_id); + if (inst) { + auto new_inst = inst->get_object()->add_instance(*inst); + if (new_inst) { + ret = new_inst->id(); + } + } + + return ret; +} + +template +void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn) +{ + InstPos pos; + for (auto *obj : self.m_model->objects) { + for (auto *inst : obj->instances) { + ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), + self.m_selmask.get(), pos}; + fn(ainst); + + ++pos.inst_idx; + } + pos.inst_idx = 0; + ++pos.obj_idx; + } +} + +template +void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) +{ + if (id == self.m_model->wipe_tower.id()) { + self.m_wth->visit(fn); + + return; + } + + auto [inst, pos] = find_instance_by_id(*self.m_model, id); + + if (inst) { + ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), + self.m_selmask.get(), pos}; + + fn(ainst); + } +} + +void ArrangeableSlicerModel::visit_arrangeable( + const ObjectID &id, std::function fn) const +{ + visit_arrangeable_(*this, id, fn); +} + +void ArrangeableSlicerModel::visit_arrangeable( + const ObjectID &id, std::function fn) +{ + visit_arrangeable_(*this, id, fn); +} + +template +void ArrangeableSLAPrint::for_each_arrangeable_(Self &&self, Fn &&fn) +{ + InstPos pos; + for (auto *obj : self.m_model->objects) { + for (auto *inst : obj->instances) { + ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), + self.m_selmask.get(), pos}; + + auto obj_id = inst->get_object()->id(); + const SLAPrintObject *po = + self.m_slaprint->get_print_object_by_model_object_id(obj_id); + + if (po) { + auto &vbh = self.m_vbed_handler; + auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst})); + ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()}; + fn(ainst_po); + } else { + fn(ainst); + } + + ++pos.inst_idx; + } + pos.inst_idx = 0; + ++pos.obj_idx; + } +} + +void ArrangeableSLAPrint::for_each_arrangeable( + std::function fn) +{ + for_each_arrangeable_(*this, fn); + + m_wth->visit(fn); +} + +void ArrangeableSLAPrint::for_each_arrangeable( + std::function fn) const +{ + for_each_arrangeable_(*this, fn); + + m_wth->visit(fn); +} + +template +void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) +{ + auto [inst, pos] = find_instance_by_id(*self.m_model, id); + + if (inst) { + ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), + self.m_selmask.get(), pos}; + + auto obj_id = inst->get_object()->id(); + const SLAPrintObject *po = + self.m_slaprint->get_print_object_by_model_object_id(obj_id); + + if (po) { + auto &vbh = self.m_vbed_handler; + auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst})); + ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()}; + fn(ainst_po); + } else { + fn(ainst); + } + } +} + +void ArrangeableSLAPrint::visit_arrangeable( + const ObjectID &id, std::function fn) const +{ + visit_arrangeable_(*this, id, fn); +} + +void ArrangeableSLAPrint::visit_arrangeable( + const ObjectID &id, std::function fn) +{ + visit_arrangeable_(*this, id, fn); +} + +template +ExPolygons ArrangeableModelInstance::full_outline() const +{ + int bedidx = m_vbedh->get_bed_index(*this); + auto tr = m_vbedh->get_physical_bed_trafo(bedidx); + + return extract_full_outline(*m_mi, tr); +} + +template +Polygon ArrangeableModelInstance::convex_outline() const +{ + int bedidx = m_vbedh->get_bed_index(*this); + auto tr = m_vbedh->get_physical_bed_trafo(bedidx); + + return extract_convex_outline(*m_mi, tr); +} + +template +bool ArrangeableModelInstance::is_selected() const +{ + bool ret = false; + + if (m_selmask) { + auto sel = m_selmask->selected_instances(m_pos_within_model.obj_idx); + if (m_pos_within_model.inst_idx < sel.size() && + sel[m_pos_within_model.inst_idx]) + ret = true; + } + + return ret; +} + +template +void ArrangeableModelInstance::transform(const Vec2d &transl, double rot) +{ + if constexpr (!std::is_const_v && !std::is_const_v) { + int bedidx = m_vbedh->get_bed_index(*this); + auto physical_trafo = m_vbedh->get_physical_bed_trafo(bedidx); + + transform_instance(*m_mi, transl, rot, physical_trafo); + } +} + +template +bool ArrangeableModelInstance::assign_bed(int bed_idx) +{ + bool ret = false; + + if constexpr (!std::is_const_v && !std::is_const_v) + ret = m_vbedh->assign_bed(*this, bed_idx); + + return ret; +} + +template class ArrangeableModelInstance; +template class ArrangeableModelInstance; + +ExPolygons ArrangeableSLAPrintObject::full_outline() const +{ + ExPolygons ret; + + auto laststep = m_po->last_completed_step(); + if (laststep < slaposCount && laststep > slaposSupportTree) { + Polygons polys; + auto omesh = m_po->get_mesh_to_print(); + auto &smesh = m_po->support_mesh(); + + Transform3d trafo_instance = m_inst_trafo * m_po->trafo().inverse(); + + if (omesh) { + Polygons ptmp = project_mesh(*omesh, trafo_instance, [] {}); + std::move(ptmp.begin(), ptmp.end(), std::back_inserter(polys)); + } + + Polygons ptmp = project_mesh(smesh.its, trafo_instance, [] {}); + std::move(ptmp.begin(), ptmp.end(), std::back_inserter(polys)); + ret = union_ex(polys); + } + + return ret; +} + +ExPolygons ArrangeableSLAPrintObject::full_envelope() const +{ + ExPolygons ret = full_outline(); + + auto laststep = m_po->last_completed_step(); + if (laststep < slaposCount && laststep > slaposSupportTree) { + auto &pmesh = m_po->pad_mesh(); + if (!pmesh.empty()) { + + Transform3d trafo_instance = m_inst_trafo * m_po->trafo().inverse(); + + Polygons ptmp = project_mesh(pmesh.its, trafo_instance, [] {}); + ret = union_ex(ret, ptmp); + } + } else { + // The 1.1 multiplier is a safety gap, as the offset might be bigger + // in sharp edges of a polygon, depending on clipper's offset algorithm + coord_t pad_infl = 0; + { + double infl = m_po->config().pad_enable.getBool() * + (m_po->config().pad_brim_size.getFloat() + + m_po->config().pad_around_object.getBool() * + m_po->config().pad_object_gap.getFloat()); + + pad_infl = scaled(1.1 * infl); + } + + if (pad_infl > 0) { + ret = offset_ex(ret, pad_infl); + } + } + + return ret; +} + +Polygon ArrangeableSLAPrintObject::convex_outline() const +{ + Polygons polys; + + polys.emplace_back(m_arrbl->convex_outline()); + + auto laststep = m_po->last_completed_step(); + if (laststep < slaposCount && laststep > slaposSupportTree) { + auto omesh = m_po->get_mesh_to_print(); + auto &smesh = m_po->support_mesh(); + + Transform3f trafo_instance = m_inst_trafo.cast(); + trafo_instance = trafo_instance * m_po->trafo().cast().inverse(); + + Polygons polys; + polys.reserve(3); + auto zlvl = -m_po->get_elevation(); + + if (omesh) { + polys.emplace_back( + its_convex_hull_2d_above(*omesh, trafo_instance, zlvl)); + } + + polys.emplace_back( + its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl)); + } + + return Geometry::convex_hull(polys); +} + +Polygon ArrangeableSLAPrintObject::convex_envelope() const +{ + Polygons polys; + + polys.emplace_back(convex_outline()); + + auto laststep = m_po->last_completed_step(); + if (laststep < slaposCount && laststep > slaposSupportTree) { + auto &pmesh = m_po->pad_mesh(); + if (!pmesh.empty()) { + + Transform3f trafo_instance = m_inst_trafo.cast(); + trafo_instance = trafo_instance * m_po->trafo().cast().inverse(); + auto zlvl = -m_po->get_elevation(); + + polys.emplace_back( + its_convex_hull_2d_above(pmesh.its, trafo_instance, zlvl)); + } + } else { + // The 1.1 multiplier is a safety gap, as the offset might be bigger + // in sharp edges of a polygon, depending on clipper's offset algorithm + coord_t pad_infl = 0; + { + double infl = m_po->config().pad_enable.getBool() * + (m_po->config().pad_brim_size.getFloat() + + m_po->config().pad_around_object.getBool() * + m_po->config().pad_object_gap.getFloat()); + + pad_infl = scaled(1.1 * infl); + } + + if (pad_infl > 0) { + polys = offset(polys, pad_infl); + } + } + + return Geometry::convex_hull(polys); +} + +DuplicableModel::DuplicableModel(AnyPtr mdl, AnyPtr vbh, const BoundingBox &bedbb) + : m_model{std::move(mdl)}, m_vbh{std::move(vbh)}, m_duplicates(1), m_bedbb{bedbb} +{ +} + +DuplicableModel::~DuplicableModel() = default; + +ObjectID DuplicableModel::add_arrangeable(const ObjectID &prototype_id) +{ + ObjectID ret; + if (prototype_id.valid()) { + size_t idx = prototype_id.id - 1; + if (idx < m_duplicates.size()) { + ModelDuplicate md = m_duplicates[idx]; + md.id = m_duplicates.size(); + ret = md.id.id + 1; + m_duplicates.emplace_back(std::move(md)); + } + } + + return ret; +} + +void DuplicableModel::apply_duplicates() +{ + for (ModelObject *o : m_model->objects) { + // make a copy of the pointers in order to avoid recursion + // when appending their copies + ModelInstancePtrs instances = o->instances; + o->instances.clear(); + for (const ModelInstance *i : instances) { + for (const ModelDuplicate &md : m_duplicates) { + ModelInstance *instance = o->add_instance(*i); + arr2::transform_instance(*instance, md.tr, md.rot); + } + } + for (auto *i : instances) + delete i; + + instances.clear(); + + o->invalidate_bounding_box(); + } +} + +template +ObjectID ArrangeableFullModel::geometry_id() const { return m_mdl->id(); } + +template +ExPolygons ArrangeableFullModel::full_outline() const +{ + auto ret = reserve_vector(arr2::model_instance_count(*m_mdl)); + + auto transl = Transform3d::Identity(); + transl.translate(to_3d(m_dup->tr, 0.)); + Transform3d trafo = transl* Eigen::AngleAxisd(m_dup->rot, Vec3d::UnitZ()); + + for (auto *mo : m_mdl->objects) { + for (auto *mi : mo->instances) { + auto expolys = arr2::extract_full_outline(*mi, trafo); + std::move(expolys.begin(), expolys.end(), std::back_inserter(ret)); + } + } + + return ret; +} + +template +Polygon ArrangeableFullModel::convex_outline() const +{ + auto ret = reserve_polygons(arr2::model_instance_count(*m_mdl)); + + auto transl = Transform3d::Identity(); + transl.translate(to_3d(m_dup->tr, 0.)); + Transform3d trafo = transl* Eigen::AngleAxisd(m_dup->rot, Vec3d::UnitZ()); + + for (auto *mo : m_mdl->objects) { + for (auto *mi : mo->instances) { + ret.emplace_back(arr2::extract_convex_outline(*mi, trafo)); + } + } + + return Geometry::convex_hull(ret); +} + +template class ArrangeableFullModel; +template class ArrangeableFullModel; + +std::unique_ptr VirtualBedHandler::create(const ExtendedBed &bed) +{ + std::unique_ptr ret; + if (is_infinite_bed(bed)) { + ret = std::make_unique(); + } else { + // The gap between logical beds in the x axis expressed in ratio of + // the current bed width. + constexpr double LogicalBedGap = 1. / 10.; + + BoundingBox bedbb; + visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed); + + auto bedwidth = bedbb.size().x(); + coord_t xgap = LogicalBedGap * bedwidth; + ret = std::make_unique(bedbb, xgap); + } + + return ret; +} + +}} // namespace Slic3r::arr2 + +#endif // SCENEBUILDER_CPP diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp new file mode 100644 index 0000000000..7939ba83d3 --- /dev/null +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -0,0 +1,645 @@ +#ifndef SCENEBUILDER_HPP +#define SCENEBUILDER_HPP + +#include "Scene.hpp" +#include "Core/ArrangeItemTraits.hpp" + +namespace Slic3r { + +class Model; +class ModelInstance; +class ModelWipeTower; +class Print; +class SLAPrint; +class SLAPrintObject; +class PrintObject; +class DynamicPrintConfig; + +namespace arr2 { + +using SelectionPredicate = std::function; + +class WipeTowerHandler +{ +public: + virtual ~WipeTowerHandler() = default; + + virtual void visit(std::function) = 0; + virtual void visit(std::function) const = 0; + virtual void set_selection_predicate(SelectionPredicate pred) = 0; +}; + +class VBedPlaceable { +public: + virtual ~VBedPlaceable() = default; + + virtual BoundingBoxf bounding_box() const = 0; + virtual void displace(const Vec2d &transl, double rot) = 0; +}; + +// An interface to handle virtual beds for ModelInstances. A ModelInstance +// may be assigned to a logical bed identified by an integer index value (zero +// is the actual physical bed). The ModelInstance may still be outside of it's +// bed, regardless of being assigned to it. The handler object should provide +// means to read the assigned bed index of a ModelInstance, to assign a +// different bed index and to provide a trafo that maps it to the physical bed +// given a logical bed index. The reason is that the arrangement expects items +// to be in the coordinate system of the physical bed. +class VirtualBedHandler +{ +public: + virtual ~VirtualBedHandler() = default; + + // Returns the bed index on which the given ModelInstance is sitting. + virtual int get_bed_index(const VBedPlaceable &obj) const = 0; + + // The returned trafo can be used to move the outline of the ModelInstance + // to the coordinate system of the physical bed, should that differ from + // the coordinate space of a logical bed. + virtual Transform3d get_physical_bed_trafo(int bed_index) const = 0; + + // Assign the ModelInstance to the given bed index. Note that this + // method can return false, indicating that the given bed is not available + // to be occupied (e.g. the handler has a limited amount of logical bed) + virtual bool assign_bed(VBedPlaceable &obj, int bed_idx) = 0; + + bool assign_bed(VBedPlaceable &&obj, int bed_idx) + { + return assign_bed(obj, bed_idx); + } + + static std::unique_ptr create(const ExtendedBed &bed); +}; + +class SelectionMask +{ +public: + virtual ~SelectionMask() = default; + + virtual std::vector selected_objects() const = 0; + virtual std::vector selected_instances(int obj_id) const = 0; + virtual bool is_wipe_tower() const = 0; +}; + +class FixedSelection : public Slic3r::arr2::SelectionMask +{ + std::vector> m_seldata; + bool m_wp = false; + +public: + FixedSelection() = default; + + explicit FixedSelection(std::initializer_list> seld, + bool wp = false) + : m_seldata{std::move(seld)}, m_wp{wp} + {} + + explicit FixedSelection(const Model &m); + + explicit FixedSelection(const SelectionMask &other); + + std::vector selected_objects() const override; + + std::vector selected_instances(int obj_id) const override + { + return obj_id < int(m_seldata.size()) ? m_seldata[obj_id] : + std::vector{}; + } + + bool is_wipe_tower() const override { return m_wp; } +}; + +struct ArrangeableWipeTowerBase: public Arrangeable +{ + ObjectID oid; + + Polygon poly; + Point pos = Point::Zero(); + double rot = 0.; + SelectionPredicate selection_pred; + + ArrangeableWipeTowerBase( + const ObjectID &objid, + Polygon shape, + const Point &p, + double r, + SelectionPredicate selection_predicate = [] { return false; }) + : oid{objid}, + poly{std::move(shape)}, + pos{p}, + rot{r}, + selection_pred{std::move(selection_predicate)} + {} + + ObjectID id() const override { return oid; } + ObjectID geometry_id() const override { return {}; } + + ExPolygons full_outline() const override + { + auto cpy = poly; + cpy.translate(pos); + return {ExPolygon{cpy}}; + } + + Polygon convex_outline() const override + { + auto cpy = poly; + cpy.translate(pos); + return cpy; + } + + bool is_selected() const override + { + return selection_pred(); + } + + int get_bed_index() const override; + bool assign_bed(int /*bed_idx*/) override; + + int priority() const override { return 1; } + + void transform(const Vec2d &transl, double rot) override {} + + void imbue_data(AnyWritable &datastore) const override + { + datastore.write("is_wipe_tower", {}); + } +}; + +class SceneBuilder; + +class ArrangeableSlicerModel: public ArrangeableModel +{ +protected: + AnyPtr m_model; + AnyPtr m_wth; + AnyPtr m_vbed_handler; + AnyPtr m_selmask; + +private: + friend class SceneBuilder; + + template + static void for_each_arrangeable_(Self &&self, Fn &&fn); + + template + static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn); + +public: + explicit ArrangeableSlicerModel(SceneBuilder &builder); + ~ArrangeableSlicerModel(); + + void for_each_arrangeable(std::function) override; + void for_each_arrangeable(std::function) const override; + + void visit_arrangeable(const ObjectID &id, std::function) const override; + void visit_arrangeable(const ObjectID &id, std::function) override; + + ObjectID add_arrangeable(const ObjectID &prototype_id) override; +}; + +class SceneBuilder: public SceneBuilderBase +{ +protected: + AnyPtr m_model; + AnyPtr m_wipetower_handler; + AnyPtr m_vbed_handler; + AnyPtr m_selection; + + AnyPtr m_sla_print; + AnyPtr m_fff_print; + + void set_brim_and_skirt(); + +public: + SceneBuilder(); + ~SceneBuilder(); + SceneBuilder(SceneBuilder&&); + SceneBuilder& operator=(SceneBuilder&&); + + SceneBuilder && set_model(AnyPtr mdl); + + SceneBuilder && set_model(Model &mdl); + + SceneBuilder && set_fff_print(AnyPtr fffprint); + SceneBuilder && set_sla_print(AnyPtr mdl_print); + + using SceneBuilderBase::set_bed; + + SceneBuilder &&set_bed(const DynamicPrintConfig &cfg); + SceneBuilder &&set_bed(const Print &print); + + SceneBuilder && set_wipe_tower_handler(WipeTowerHandler &wth) + { + m_wipetower_handler = &wth; + return std::move(*this); + } + + SceneBuilder && set_wipe_tower_handler(AnyPtr wth) + { + m_wipetower_handler = std::move(wth); + return std::move(*this); + } + + SceneBuilder && set_virtual_bed_handler(AnyPtr vbedh) + { + m_vbed_handler = std::move(vbedh); + return std::move(*this); + } + + SceneBuilder && set_sla_print(const SLAPrint *slaprint); + + SceneBuilder && set_selection(AnyPtr sel) + { + m_selection = std::move(sel); + return std::move(*this); + } + + // Can only be called on an rvalue instance (hence the && at the end), + // the method will potentially move its content into sc + void build_scene(Scene &sc) && override; + + void build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel); +}; + +struct MissingWipeTowerHandler : public WipeTowerHandler +{ + void visit(std::function) override {} + void visit(std::function) const override {} + void set_selection_predicate(std::function) override {} +}; + +// Only a physical bed, non-zero bed index values are discarded. +class PhysicalOnlyVBedHandler final : public VirtualBedHandler +{ +public: + using VirtualBedHandler::assign_bed; + + int get_bed_index(const VBedPlaceable &obj) const override { return 0; } + + Transform3d get_physical_bed_trafo(int bed_index) const override + { + return Transform3d::Identity(); + } + + bool assign_bed(VBedPlaceable &inst, int bed_idx) override; +}; + +// A virtual bed handler implementation, that defines logical beds to be created +// on the right side of the physical bed along the X axis in a row +class XStriderVBedHandler final : public VirtualBedHandler +{ + coord_t m_stride_scaled; + coord_t m_start; + +public: + explicit XStriderVBedHandler(const BoundingBox &bedbb, coord_t xgap) + : m_stride_scaled{bedbb.size().x() + 2 * std::max(0, xgap)}, + m_start{bedbb.min.x() - std::max(0, xgap)} + { + } + + coord_t stride_scaled() const { return m_stride_scaled; } + + // Can return negative indices when the instance is to the left of the + // physical bed + int get_bed_index(const VBedPlaceable &obj) const override; + + // Only positive beds are accepted + bool assign_bed(VBedPlaceable &inst, int bed_idx) override; + + using VirtualBedHandler::assign_bed; + + Transform3d get_physical_bed_trafo(int bed_index) const override; +}; + +// Same as XStriderVBedHandler only that it lays out vbeds on the Y axis +class YStriderVBedHandler final : public VirtualBedHandler +{ + coord_t m_stride_scaled; + coord_t m_start; + +public: + coord_t stride_scaled() const { return m_stride_scaled; } + + explicit YStriderVBedHandler(const BoundingBox &bedbb, coord_t ygap) + : m_stride_scaled{bedbb.size().y() + 2 * std::max(0, ygap)} + , m_start{bedbb.min.y() - std::max(0, ygap)} + {} + + int get_bed_index(const VBedPlaceable &obj) const override; + bool assign_bed(VBedPlaceable &inst, int bed_idx) override; + + Transform3d get_physical_bed_trafo(int bed_index) const override; +}; + +std::vector selected_object_indices(const SelectionMask &sm); +std::vector selected_instance_indices(int obj_idx, const SelectionMask &sm); + +coord_t get_skirt_inset(const Print &fffprint); + +coord_t brim_offset(const PrintObject &po); + +// unscaled coords are necessary to be able to handle bigger coordinate range +// than what is available with scaled coords. This is useful when working with +// virtual beds. +void transform_instance(ModelInstance &mi, + const Vec2d &transl_unscaled, + double rot, + const Transform3d &physical_tr = Transform3d::Identity()); + +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, + bool dont_translate = false); + + +ExPolygons extract_full_outline(const ModelInstance &inst, + const Transform3d &tr = Transform3d::Identity()); + +Polygon extract_convex_outline(const ModelInstance &inst, + const Transform3d &tr = Transform3d::Identity()); + +size_t model_instance_count (const Model &m); + +class VBedPlaceableMI : public VBedPlaceable +{ + ModelInstance *m_mi; + +public: + explicit VBedPlaceableMI(ModelInstance &mi) : m_mi{&mi} {} + + BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); } + void displace(const Vec2d &transl, double rot) override + { + transform_instance(*m_mi, transl, rot); + } +}; + +struct InstPos { size_t obj_idx = 0, inst_idx = 0; }; + +template +class ArrangeableModelInstance : public Arrangeable, VBedPlaceable +{ + InstPtr *m_mi; + VBedHPtr *m_vbedh; + const SelectionMask *m_selmask; + InstPos m_pos_within_model; + +public: + explicit ArrangeableModelInstance(InstPtr *mi, + VBedHPtr *vbedh, + const SelectionMask *selmask, + const InstPos &pos) + : m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos} + { + assert(m_mi != nullptr && m_vbedh != nullptr); + } + + // Arrangeable: + ObjectID id() const override { return m_mi->id(); } + ObjectID geometry_id() const override { return m_mi->get_object()->id(); } + ExPolygons full_outline() const override; + Polygon convex_outline() const override; + bool is_printable() const override { return m_mi->printable; } + bool is_selected() const override; + void transform(const Vec2d &tr, double rot) override; + + int get_bed_index() const override { return m_vbedh->get_bed_index(*this); } + bool assign_bed(int bed_idx) override; + + // VBedPlaceable: + BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); } + void displace(const Vec2d &transl, double rot) override + { + if constexpr (!std::is_const_v) + transform_instance(*m_mi, transl, rot); + } +}; + +extern template class ArrangeableModelInstance; +extern template class ArrangeableModelInstance; + +class ArrangeableSLAPrintObject : public Arrangeable +{ + const SLAPrintObject *m_po; + Arrangeable *m_arrbl; + Transform3d m_inst_trafo; + +public: + ArrangeableSLAPrintObject(const SLAPrintObject *po, + Arrangeable *arrbl, + const Transform3d &inst_tr = Transform3d::Identity()) + : m_po{po}, m_arrbl{arrbl}, m_inst_trafo{inst_tr} + {} + + ObjectID id() const override { return m_arrbl->id(); } + ObjectID geometry_id() const override { return m_arrbl->geometry_id(); } + + ExPolygons full_outline() const override; + ExPolygons full_envelope() const override; + + Polygon convex_outline() const override; + Polygon convex_envelope() const override; + + void transform(const Vec2d &transl, double rot) override + { + m_arrbl->transform(transl, rot); + } + int get_bed_index() const override { return m_arrbl->get_bed_index(); } + bool assign_bed(int bedidx) override + { + return m_arrbl->assign_bed(bedidx); + } + + bool is_printable() const override { return m_arrbl->is_printable(); } + bool is_selected() const override { return m_arrbl->is_selected(); } + int priority() const override { return m_arrbl->priority(); } +}; + +class ArrangeableSLAPrint : public ArrangeableSlicerModel { + const SLAPrint *m_slaprint; + + friend class SceneBuilder; + + template + static void for_each_arrangeable_(Self &&self, Fn &&fn); + + template + static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn); + +public: + explicit ArrangeableSLAPrint(const SLAPrint *slaprint, + SceneBuilder &builder) + : m_slaprint{slaprint}, ArrangeableSlicerModel{builder} + { + assert(slaprint != nullptr); + } + + void for_each_arrangeable(std::function) override; + + void for_each_arrangeable( + std::function) const override; + + void visit_arrangeable( + const ObjectID &id, + std::function) const override; + + void visit_arrangeable(const ObjectID &id, + std::function) override; +}; + +template +auto find_instance_by_id(Mdl &&model, const ObjectID &id) +{ + std::remove_reference_t< + decltype(std::declval().objects[0]->instances[0])> + ret = nullptr; + + InstPos pos; + + for (auto * obj : model.objects) { + for (auto *inst : obj->instances) { + if (inst->id() == id) { + ret = inst; + break; + } + ++pos.inst_idx; + } + + if (ret) + break; + + ++pos.obj_idx; + pos.inst_idx = 0; + } + + return std::make_pair(ret, pos); +} + +struct ModelDuplicate +{ + ObjectID id; + Vec2d tr = Vec2d::Zero(); + double rot = 0.; + int bed_idx = Unarranged; +}; + +// Implementing the Arrangeable interface with the whole Model being one outline +// with all its objects and instances. +template +class ArrangeableFullModel: public Arrangeable, VBedPlaceable +{ + Mdl *m_mdl; + Dup *m_dup; + VBH *m_vbh; + +public: + explicit ArrangeableFullModel(Mdl *mdl, + Dup *md, + VBH *vbh) + : m_mdl{mdl}, m_dup{md}, m_vbh{vbh} + { + assert(m_mdl != nullptr); + } + + ObjectID id() const override { return m_dup->id.id + 1; } + ObjectID geometry_id() const override; + + ExPolygons full_outline() const override; + + Polygon convex_outline() const override; + + bool is_printable() const override { return true; } + bool is_selected() const override { return m_dup->id == 0; } + + int get_bed_index() const override + { + return m_vbh->get_bed_index(*this); + } + + void transform(const Vec2d &tr, double rot) override + { + if constexpr (!std::is_const_v && !std::is_const_v) { + m_dup->tr += tr; + m_dup->rot += rot; + } + } + + bool assign_bed(int bed_idx) override + { + bool ret = false; + + if constexpr (!std::is_const_v && !std::is_const_v) { + if ((ret = m_vbh->assign_bed(*this, bed_idx))) + m_dup->bed_idx = bed_idx; + } + + return ret; + } + + BoundingBoxf bounding_box() const override { return unscaled(get_extents(convex_outline())); } + void displace(const Vec2d &transl, double rot) override + { + transform(transl, rot); + } +}; + +extern template class ArrangeableFullModel; +extern template class ArrangeableFullModel; + +class DuplicableModel: public ArrangeableModel { + AnyPtr m_model; + AnyPtr m_vbh; + std::vector m_duplicates; + BoundingBox m_bedbb; + + template + static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) + { + if (id.valid()) { + size_t idx = id.id - 1; + if (idx < self.m_duplicates.size()) { + auto &md = self.m_duplicates[idx]; + ArrangeableFullModel arrbl{self.m_model.get(), &md, self.m_vbh.get()}; + fn(arrbl); + } + } + } + +public: + explicit DuplicableModel(AnyPtr mdl, + AnyPtr vbh, + const BoundingBox &bedbb); + ~DuplicableModel(); + + void for_each_arrangeable(std::function fn) override + { + for (ModelDuplicate &md : m_duplicates) { + ArrangeableFullModel arrbl{m_model.get(), &md, m_vbh.get()}; + fn(arrbl); + } + } + void for_each_arrangeable(std::function fn) const override + { + for (const ModelDuplicate &md : m_duplicates) { + ArrangeableFullModel arrbl{m_model.get(), &md, m_vbh.get()}; + fn(arrbl); + } + } + void visit_arrangeable(const ObjectID &id, std::function fn) const override + { + visit_arrangeable_(*this, id, fn); + } + void visit_arrangeable(const ObjectID &id, std::function fn) override + { + visit_arrangeable_(*this, id, fn); + } + + ObjectID add_arrangeable(const ObjectID &prototype_id) override; + + void apply_duplicates(); +}; + +} // namespace arr2 +} // namespace Slic3r + +#endif // SCENEBUILDER_HPP diff --git a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp new file mode 100644 index 0000000000..40d051e504 --- /dev/null +++ b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp @@ -0,0 +1,105 @@ +#ifndef SEGMENTEDRECTANGLEBED_HPP +#define SEGMENTEDRECTANGLEBED_HPP + +#include "libslic3r/Arrange/Core/Beds.hpp" + +namespace Slic3r { namespace arr2 { + +enum class RectPivots { + Center, BottomLeft, BottomRight, TopLeft, TopRight +}; + +template struct IsSegmentedBed_ : public std::false_type {}; +template constexpr bool IsSegmentedBed = IsSegmentedBed_>::value; + +template +struct SegmentedRectangleBed { + Vec<2, size_t> segments = Vec<2, size_t>::Ones(); + BoundingBox bb; + RectPivots pivot = RectPivots::Center; + + SegmentedRectangleBed() = default; + SegmentedRectangleBed(const BoundingBox &bb, + size_t segments_x, + size_t segments_y, + const RectPivots pivot = RectPivots::Center) + : segments{segments_x, segments_y}, bb{bb}, pivot{pivot} + {} + + size_t segments_x() const noexcept { return segments.x(); } + size_t segments_y() const noexcept { return segments.y(); } + + auto alignment() const noexcept { return pivot; } +}; + +template +struct SegmentedRectangleBed, + std::integral_constant> +{ + BoundingBox bb; + RectPivots pivot = RectPivots::Center; + + SegmentedRectangleBed() = default; + + explicit SegmentedRectangleBed(const BoundingBox &b, + const RectPivots pivot = RectPivots::Center) + : bb{b} + {} + + size_t segments_x() const noexcept { return SegX; } + size_t segments_y() const noexcept { return SegY; } + + auto alignment() const noexcept { return pivot; } +}; + +template +struct SegmentedRectangleBed, + std::integral_constant, + std::integral_constant> +{ + BoundingBox bb; + + SegmentedRectangleBed() = default; + + explicit SegmentedRectangleBed(const BoundingBox &b) : bb{b} {} + + size_t segments_x() const noexcept { return SegX; } + size_t segments_y() const noexcept { return SegY; } + + auto alignment() const noexcept { return pivot; } +}; + +template +struct IsSegmentedBed_> + : public std::true_type {}; + +template +auto offset(const SegmentedRectangleBed &bed, coord_t val_scaled) +{ + auto cpy = bed; + cpy.bb.offset(val_scaled); + + return cpy; +} + +template +auto bounding_box(const SegmentedRectangleBed &bed) +{ + return bed.bb; +} + +template +auto area(const SegmentedRectangleBed &bed) +{ + return arr2::area(bed.bb); +} + +template +ExPolygons to_expolygons(const SegmentedRectangleBed &bed) +{ + return to_expolygons(RectangleBed{bed.bb}); +} + +}} // namespace Slic3r::arr2 + +#endif // SEGMENTEDRECTANGLEBED_HPP diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTask.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTask.hpp new file mode 100644 index 0000000000..1f3b9013d2 --- /dev/null +++ b/src/libslic3r/Arrange/Tasks/ArrangeTask.hpp @@ -0,0 +1,81 @@ +#ifndef ARRANGETASK_HPP +#define ARRANGETASK_HPP + +#include "libslic3r/Arrange/Arrange.hpp" +#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp" + +namespace Slic3r { namespace arr2 { + +struct ArrangeTaskResult : public ArrangeResult +{ + std::vector items; + + bool apply_on(ArrangeableModel &mdl) override + { + bool ret = true; + for (auto &itm : items) { + if (is_arranged(itm)) + ret = ret && apply_arrangeitem(itm, mdl); + } + + return ret; + } + + template + void add_item(const ArrItem &itm) + { + items.emplace_back(itm); + if (auto id = retrieve_id(itm)) + imbue_id(items.back(), *id); + } + + template + void add_items(const Range &items_range) + { + for (auto &itm : items_range) + add_item(itm); + } +}; + +template struct ArrangeTask : public ArrangeTaskBase +{ + struct ArrangeSet + { + std::vector selected, unselected; + } printable, unprintable; + + ExtendedBed bed; + ArrangeSettings settings; + + static std::unique_ptr create( + const Scene &sc, + const ArrangeableToItemConverter &converter); + + static std::unique_ptr create(const Scene &sc) + { + auto conv = ArrangeableToItemConverter::create(sc); + return create(sc, *conv); + } + + std::unique_ptr process(Ctl &ctl) override + { + return process_native(ctl); + } + + std::unique_ptr process_native(Ctl &ctl); + std::unique_ptr process_native(Ctl &&ctl) + { + return process_native(ctl); + } + + int item_count_to_process() const override + { + return static_cast(printable.selected.size() + + unprintable.selected.size()); + } +}; + +} // namespace arr2 +} // namespace Slic3r + +#endif // ARRANGETASK_HPP diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp new file mode 100644 index 0000000000..d4ff0244d2 --- /dev/null +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -0,0 +1,109 @@ +#ifndef ARRANGETASK_IMPL_HPP +#define ARRANGETASK_IMPL_HPP + +#include + +#include "ArrangeTask.hpp" + +namespace Slic3r { namespace arr2 { + +// Prepare the selected and unselected items separately. If nothing is +// selected, behaves as if everything would be selected. +template +void extract_selected(ArrangeTask &task, + const ArrangeableModel &mdl, + const ArrangeableToItemConverter &itm_conv) +{ + // Go through the objects and check if inside the selection + mdl.for_each_arrangeable( + [&task, &itm_conv](const Arrangeable &arrbl) { + bool selected = arrbl.is_selected(); + bool printable = arrbl.is_printable(); + + auto itm = itm_conv.convert(arrbl, selected ? 0 : -SCALED_EPSILON); + + auto &container_parent = printable ? task.printable : + task.unprintable; + + auto &container = selected ? + container_parent.selected : + container_parent.unselected; + + container.emplace_back(std::move(itm)); + }); + + // If the selection was empty arrange everything + if (task.printable.selected.empty() && task.unprintable.selected.empty()) { + task.printable.selected.swap(task.printable.unselected); + task.unprintable.selected.swap(task.unprintable.unselected); + } +} + +template +std::unique_ptr> ArrangeTask::create( + const Scene &sc, const ArrangeableToItemConverter &converter) +{ + auto task = std::make_unique>(); + + task->settings.set_from(sc.settings()); + + task->bed = sc.bed(); + + extract_selected(*task, sc.model(), converter); + + return task; +} + +template +std::unique_ptr +ArrangeTask::process_native(Ctl &ctl) +{ + auto result = std::make_unique(); + + auto arranger = Arranger::create(settings); + + class TwoStepArrangeCtl: public Ctl + { + Ctl &parent; + ArrangeTask &self; + public: + TwoStepArrangeCtl(Ctl &p, ArrangeTask &slf) : parent{p}, self{slf} {} + + void update_status(int remaining) override + { + parent.update_status(remaining + self.unprintable.selected.size()); + } + + bool was_canceled() const override { return parent.was_canceled(); } + + } subctl{ctl, *this}; + + arranger->arrange(printable.selected, printable.unselected, bed, subctl); + arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl); + + // Unprintable items should go to the first bed not containing any printable + // items + auto beds = std::max(get_bed_count(crange(printable.selected)), + get_bed_count(crange(printable.unselected))); + + // If there are no printables, leave the physical bed empty + beds = std::max(beds, size_t{1}); + + result->add_items(crange(printable.selected)); + + for (auto &itm : unprintable.selected) { + if (is_arranged(itm)) { + int bedidx = get_bed_index(itm) + beds; + arr2::set_bed_index(itm, bedidx); + } + + result->add_item(itm); + } + + return result; +} + +} // namespace arr2 +} // namespace Slic3r + +#endif //ARRANGETASK_IMPL_HPP diff --git a/src/libslic3r/Arrange/Tasks/FillBedTask.hpp b/src/libslic3r/Arrange/Tasks/FillBedTask.hpp new file mode 100644 index 0000000000..2931d85fc5 --- /dev/null +++ b/src/libslic3r/Arrange/Tasks/FillBedTask.hpp @@ -0,0 +1,53 @@ +#ifndef FILLBEDTASK_HPP +#define FILLBEDTASK_HPP + +#include "MultiplySelectionTask.hpp" + +#include "libslic3r/Arrange/Arrange.hpp" + +namespace Slic3r { namespace arr2 { + +struct FillBedTaskResult: public MultiplySelectionTaskResult {}; + +template +struct FillBedTask: public ArrangeTaskBase +{ + std::optional prototype_item; + + std::vector selected, unselected; + + ArrangeSettings settings; + ExtendedBed bed; + size_t selected_existing_count = 0; + + std::unique_ptr process_native(Ctl &ctl); + std::unique_ptr process_native(Ctl &&ctl) + { + return process_native(ctl); + } + + std::unique_ptr process(Ctl &ctl) override + { + return process_native(ctl); + } + + int item_count_to_process() const override + { + return selected.size(); + } + + static std::unique_ptr create( + const Scene &sc, + const ArrangeableToItemConverter &converter); + + static std::unique_ptr create(const Scene &sc) + { + auto conv = ArrangeableToItemConverter::create(sc); + return create(sc, *conv); + } +}; + +} // namespace arr2 +} // namespace Slic3r + +#endif // FILLBEDTASK_HPP diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp new file mode 100644 index 0000000000..9c76a35c81 --- /dev/null +++ b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp @@ -0,0 +1,173 @@ +#ifndef FILLBEDTASKIMPL_HPP +#define FILLBEDTASKIMPL_HPP + +#include "FillBedTask.hpp" + +#include "Arrange/Core/NFP/NFPArrangeItemTraits.hpp" + +namespace Slic3r { namespace arr2 { + +template +int calculate_items_needed_to_fill_bed(const ExtendedBed &bed, + const ArrItem &prototype_item, + size_t prototype_count, + const std::vector &fixed) +{ + double poly_area = fixed_area(prototype_item); + + auto area_sum_fn = [](double s, const auto &itm) { + return s + (get_bed_index(itm) == 0) * fixed_area(itm); + }; + + double unsel_area = std::accumulate(fixed.begin(), + fixed.end(), + 0., + area_sum_fn); + + double fixed_area = unsel_area + prototype_count * poly_area; + double bed_area = 0.; + + visit_bed([&bed_area] (auto &realbed) { bed_area = area(realbed); }, bed); + + // This is the maximum number of items, + // the real number will always be close but less. + auto needed_items = static_cast( + std::ceil((bed_area - fixed_area) / poly_area)); + + return needed_items; +} + +template +void extract(FillBedTask &task, + const Scene &scene, + const ArrangeableToItemConverter &itm_conv) +{ + task.prototype_item = {}; + + auto selected_ids = scene.selected_ids(); + + if (selected_ids.empty()) + return; + + std::set selected_objects = selected_geometry_ids(scene); + + if (selected_objects.size() != 1) + return; + + ObjectID prototype_geometry_id = *(selected_objects.begin()); + + auto set_prototype_item = [&task, &itm_conv](const Arrangeable &arrbl) { + if (arrbl.is_printable()) + task.prototype_item = itm_conv.convert(arrbl); + }; + + scene.model().visit_arrangeable(selected_ids.front(), set_prototype_item); + + if (!task.prototype_item) + return; + + set_bed_index(*task.prototype_item, Unarranged); + + auto collect_task_items = [&prototype_geometry_id, &task, + &itm_conv](const Arrangeable &arrbl) { + if (arrbl.geometry_id() == prototype_geometry_id) { + if (arrbl.is_printable()) { + auto itm = itm_conv.convert(arrbl); + raise_priority(itm); + task.selected.emplace_back(std::move(itm)); + } + } else { + auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); + task.unselected.emplace_back(std::move(itm)); + } + }; + + scene.model().for_each_arrangeable(collect_task_items); + + int needed_items = calculate_items_needed_to_fill_bed(task.bed, + *task.prototype_item, + task.selected.size(), + task.unselected); + + task.selected_existing_count = task.selected.size(); + task.selected.reserve(task.selected.size() + needed_items); + std::fill_n(std::back_inserter(task.selected), needed_items, + *task.prototype_item); +} + + +template +std::unique_ptr> FillBedTask::create( + const Scene &sc, const ArrangeableToItemConverter &converter) +{ + auto task = std::make_unique>(); + + task->settings.set_from(sc.settings()); + + task->bed = sc.bed(); + + extract(*task, sc, converter); + + return task; +} + +template +std::unique_ptr FillBedTask::process_native( + Ctl &ctl) +{ + auto result = std::make_unique(); + + if (!prototype_item) + return result; + + result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{}); + + class FillBedCtl: public ArrangerCtl + { + ArrangeTaskCtl &parent; + FillBedTask &self; + bool do_stop = false; + + public: + FillBedCtl(ArrangeTaskCtl &p, FillBedTask &slf) : parent{p}, self{slf} {} + + void update_status(int remaining) override + { + parent.update_status(remaining); + } + + bool was_canceled() const override + { + return parent.was_canceled() || do_stop; + } + + void on_packed(ArrItem &itm) override + { + do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) == 0; + } + + } subctl(ctl, *this); + + auto arranger = Arranger::create(settings); + + arranger->arrange(selected, unselected, bed, subctl); + + auto arranged_range = Range{selected.begin(), + selected.begin() + selected_existing_count}; + + result->add_arranged_items(arranged_range); + + auto to_add_range = Range{selected.begin() + selected_existing_count, + selected.end()}; + + for (auto &itm : to_add_range) + if (get_bed_index(itm) == PhysicalBedId) + result->add_new_item(itm); + + return result; +} + +} // namespace arr2 +} // namespace Slic3r + +#endif // FILLBEDTASKIMPL_HPP diff --git a/src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp b/src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp new file mode 100644 index 0000000000..156ff2f57a --- /dev/null +++ b/src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp @@ -0,0 +1,108 @@ +#ifndef MULTIPLYSELECTIONTASK_HPP +#define MULTIPLYSELECTIONTASK_HPP + +#include "libslic3r/Arrange/Arrange.hpp" +#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp" + +namespace Slic3r { namespace arr2 { + +struct MultiplySelectionTaskResult: public ArrangeResult { + ObjectID prototype_id; + + std::vector arranged_items; + std::vector to_add; + + bool apply_on(ArrangeableModel &mdl) override + { + bool ret = prototype_id.valid(); + + if (!ret) + return ret; + + for (auto &itm : to_add) { + auto id = mdl.add_arrangeable(prototype_id); + imbue_id(itm, id); + ret = ret && apply_arrangeitem(itm, mdl); + } + + for (auto &itm : arranged_items) { + if (is_arranged(itm)) + ret = ret && apply_arrangeitem(itm, mdl); + } + + return ret; + } + + template + void add_arranged_item(const ArrItem &itm) + { + arranged_items.emplace_back(itm); + if (auto id = retrieve_id(itm)) + imbue_id(arranged_items.back(), *id); + } + + template + void add_arranged_items(const Range &items_range) + { + arranged_items.reserve(items_range.size()); + for (auto &itm : items_range) + add_arranged_item(itm); + } + + template void add_new_item(const ArrItem &itm) + { + to_add.emplace_back(itm); + } + + template void add_new_items(const Range &items_range) + { + to_add.reserve(items_range.size()); + for (auto &itm : items_range) { + to_add.emplace_back(itm); + } + } +}; + +template +struct MultiplySelectionTask: public ArrangeTaskBase +{ + std::optional prototype_item; + + std::vector selected, unselected; + + ArrangeSettings settings; + ExtendedBed bed; + size_t selected_existing_count = 0; + + std::unique_ptr process_native(Ctl &ctl); + std::unique_ptr process_native(Ctl &&ctl) + { + return process_native(ctl); + } + + std::unique_ptr process(Ctl &ctl) override + { + return process_native(ctl); + } + + int item_count_to_process() const override + { + return selected.size(); + } + + static std::unique_ptr create( + const Scene &sc, + size_t multiply_count, + const ArrangeableToItemConverter &converter); + + static std::unique_ptr create(const Scene &sc, + size_t multiply_count) + { + auto conv = ArrangeableToItemConverter::create(sc); + return create(sc, multiply_count, *conv); + } +}; + +}} // namespace Slic3r::arr2 + +#endif // MULTIPLYSELECTIONTASK_HPP diff --git a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp new file mode 100644 index 0000000000..0abf31376d --- /dev/null +++ b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp @@ -0,0 +1,120 @@ +#ifndef MULTIPLYSELECTIONTASKIMPL_HPP +#define MULTIPLYSELECTIONTASKIMPL_HPP + +#include "MultiplySelectionTask.hpp" + +namespace Slic3r { namespace arr2 { + +template +std::unique_ptr> MultiplySelectionTask::create( + const Scene &scene, size_t count, const ArrangeableToItemConverter &itm_conv) +{ + auto task_ptr = std::make_unique>(); + + auto &task = *task_ptr; + + task.settings.set_from(scene.settings()); + + task.bed = scene.bed(); + + task.prototype_item = {}; + + auto selected_ids = scene.selected_ids(); + + if (selected_ids.empty()) + return task_ptr; + + std::set selected_objects = selected_geometry_ids(scene); + + if (selected_objects.size() != 1) + return task_ptr; + + ObjectID prototype_geometry_id = *(selected_objects.begin()); + + auto set_prototype_item = [&task, &itm_conv](const Arrangeable &arrbl) { + if (arrbl.is_printable()) + task.prototype_item = itm_conv.convert(arrbl); + }; + + scene.model().visit_arrangeable(selected_ids.front(), set_prototype_item); + + if (!task.prototype_item) + return task_ptr; + + set_bed_index(*task.prototype_item, Unarranged); + + auto collect_task_items = [&prototype_geometry_id, &task, + &itm_conv](const Arrangeable &arrbl) { + if (arrbl.geometry_id() == prototype_geometry_id) { + if (arrbl.is_printable()) { + auto itm = itm_conv.convert(arrbl); + raise_priority(itm); + task.selected.emplace_back(std::move(itm)); + } + } else { + auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); + task.unselected.emplace_back(std::move(itm)); + } + }; + + scene.model().for_each_arrangeable(collect_task_items); + + task.selected_existing_count = task.selected.size(); + task.selected.reserve(task.selected.size() + count); + std::fill_n(std::back_inserter(task.selected), count, *task.prototype_item); + + return task_ptr; +} + +template +std::unique_ptr +MultiplySelectionTask::process_native(Ctl &ctl) +{ + auto result = std::make_unique(); + + if (!prototype_item) + return result; + + result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{}); + + class MultiplySelectionCtl: public ArrangerCtl + { + ArrangeTaskCtl &parent; + MultiplySelectionTask &self; + + public: + MultiplySelectionCtl(ArrangeTaskCtl &p, MultiplySelectionTask &slf) + : parent{p}, self{slf} {} + + void update_status(int remaining) override + { + parent.update_status(remaining); + } + + bool was_canceled() const override + { + return parent.was_canceled(); + } + + } subctl(ctl, *this); + + auto arranger = Arranger::create(settings); + + arranger->arrange(selected, unselected, bed, subctl); + + auto arranged_range = Range{selected.begin(), + selected.begin() + selected_existing_count}; + + result->add_arranged_items(arranged_range); + + auto to_add_range = Range{selected.begin() + selected_existing_count, + selected.end()}; + + result->add_new_items(to_add_range); + + return result; +} + +}} // namespace Slic3r::arr2 + +#endif // MULTIPLYSELECTIONTASKIMPL_HPP diff --git a/src/libslic3r/BoostAdapter.hpp b/src/libslic3r/BoostAdapter.hpp index 36256213f9..07ab42eefb 100644 --- a/src/libslic3r/BoostAdapter.hpp +++ b/src/libslic3r/BoostAdapter.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include @@ -126,13 +128,146 @@ struct indexed_access, 1, d> { } }; -} -} + +/* ************************************************************************** */ +/* Segment concept adaptaion ************************************************ */ +/* ************************************************************************** */ + +template<> struct tag { + using type = segment_tag; +}; + +template<> struct point_type { + using type = Slic3r::Point; +}; + +template<> struct indexed_access { + static inline coord_t get(Slic3r::Line const& l) { return l.a.x(); } + static inline void set(Slic3r::Line &l, coord_t c) { l.a.x() = c; } +}; + +template<> struct indexed_access { + static inline coord_t get(Slic3r::Line const& l) { return l.a.y(); } + static inline void set(Slic3r::Line &l, coord_t c) { l.a.y() = c; } +}; + +template<> struct indexed_access { + static inline coord_t get(Slic3r::Line const& l) { return l.b.x(); } + static inline void set(Slic3r::Line &l, coord_t c) { l.b.x() = c; } +}; + +template<> struct indexed_access { + static inline coord_t get(Slic3r::Line const& l) { return l.b.y(); } + static inline void set(Slic3r::Line &l, coord_t c) { l.b.y() = c; } +}; + +/* ************************************************************************** */ +/* Polyline concept adaptation ********************************************** */ +/* ************************************************************************** */ + +template<> struct tag { + using type = linestring_tag; +}; + +/* ************************************************************************** */ +/* Polygon concept adaptation *********************************************** */ +/* ************************************************************************** */ + +// Ring implementation ///////////////////////////////////////////////////////// + +// Boost would refer to ClipperLib::Path (alias Slic3r::ExPolygon) as a ring +template<> struct tag { + using type = ring_tag; +}; + +template<> struct point_order { + static const order_selector value = counterclockwise; +}; + +// All our Paths should be closed for the bin packing application +template<> struct closure { + static const constexpr closure_selector value = closure_selector::open; +}; + +// Polygon implementation ////////////////////////////////////////////////////// + +template<> struct tag { + using type = polygon_tag; +}; + +template<> struct exterior_ring { + static inline Slic3r::Polygon& get(Slic3r::ExPolygon& p) + { + return p.contour; + } + static inline Slic3r::Polygon const& get(Slic3r::ExPolygon const& p) + { + return p.contour; + } +}; + +template<> struct ring_const_type { + using type = const Slic3r::Polygon&; +}; + +template<> struct ring_mutable_type { + using type = Slic3r::Polygon&; +}; + +template<> struct interior_const_type { + using type = const Slic3r::Polygons&; +}; + +template<> struct interior_mutable_type { + using type = Slic3r::Polygons&; +}; + +template<> +struct interior_rings { + + static inline Slic3r::Polygons& get(Slic3r::ExPolygon& p) { return p.holes; } + + static inline const Slic3r::Polygons& get(Slic3r::ExPolygon const& p) + { + return p.holes; + } +}; + +/* ************************************************************************** */ +/* MultiPolygon concept adaptation ****************************************** */ +/* ************************************************************************** */ + +template<> struct tag { + using type = multi_polygon_tag; +}; + +}} // namespace geometry::traits template<> struct range_value> { using type = Slic3r::Vec2d; }; +template<> +struct range_value { + using type = Slic3r::Point; +}; + +// This is an addition to the ring implementation of Polygon concept +template<> +struct range_value { + using type = Slic3r::Point; +}; + +template<> +struct range_value { + using type = Slic3r::Polygon; +}; + +template<> +struct range_value { + using type = Slic3r::ExPolygon; +}; + } // namespace boost #endif // SLABOOSTADAPTER_HPP diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index fc1b500740..9a4c68a147 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -54,8 +54,8 @@ public: return ! (this->max.x() < other.min.x() || this->min.x() > other.max.x() || this->max.y() < other.min.y() || this->min.y() > other.max.y()); } - bool operator==(const BoundingBoxBase &rhs) { return this->min == rhs.min && this->max == rhs.max; } - bool operator!=(const BoundingBoxBase &rhs) { return ! (*this == rhs); } + bool operator==(const BoundingBoxBase &rhs) const noexcept { return this->min == rhs.min && this->max == rhs.max; } + bool operator!=(const BoundingBoxBase &rhs) const noexcept { return ! (*this == rhs); } private: // to access construct() @@ -192,6 +192,7 @@ public: BoundingBox() : BoundingBoxBase() {} BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {} + BoundingBox(const BoundingBoxBase &bb): BoundingBox(bb.min, bb.max) {} BoundingBox(const Points &points) : BoundingBoxBase(points) {} BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; } @@ -215,6 +216,7 @@ public: BoundingBoxf() : BoundingBoxBase() {} BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase(pmin, pmax) {} BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {} + BoundingBoxf(const BoundingBoxBase &bb): BoundingBoxf{bb.min, bb.max} {} }; class BoundingBoxf3 : public BoundingBox3Base @@ -239,17 +241,23 @@ inline bool empty(const BoundingBox3Base &bb) inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } -template -BoundingBoxBase> scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } +template +BoundingBoxBase> scaled(const BoundingBoxBase> &bb) { return {scaled(bb.min), scaled(bb.max)}; } template -BoundingBox3Base> scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; } +BoundingBoxBase> scaled(const BoundingBox &bb) { return {scaled(bb.min), scaled(bb.max)}; } + +template +BoundingBox3Base> scaled(const BoundingBox3Base> &bb) { return {scaled(bb.min), scaled(bb.max)}; } + +template +BoundingBoxBase> unscaled(const BoundingBoxBase> &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } template BoundingBoxBase> unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } -template -BoundingBox3Base> unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } +template +BoundingBox3Base> unscaled(const BoundingBox3Base> &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } template auto cast(const BoundingBoxBase &b) @@ -298,6 +306,19 @@ inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point & coord_t(0)); } +template +BoundingBoxBase> to_2d(const BoundingBox3Base> &bb) +{ + return {to_2d(bb.min), to_2d(bb.max)}; +} + +template +BoundingBoxBase> to_2d(const BoundingBox3Base> &bb) +{ + return {to_2d(bb.min), to_2d(bb.max)}; +} + + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index a31e4cc7c8..75cef23e6d 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -216,8 +216,55 @@ set(SLIC3R_SOURCES MeasureUtils.hpp CustomGCode.cpp CustomGCode.hpp - Arrange.hpp - Arrange.cpp + Arrange/Arrange.hpp + Arrange/ArrangeImpl.hpp + Arrange/Items/ArrangeItem.hpp + Arrange/Items/ArrangeItem.cpp + Arrange/Items/SimpleArrangeItem.hpp + Arrange/Items/SimpleArrangeItem.cpp + Arrange/Items/TrafoOnlyArrangeItem.hpp + Arrange/Items/WritableItemTraits.hpp + Arrange/Items/ArbitraryDataStore.hpp + Arrange/ArrangeSettingsView.hpp + Arrange/ArrangeSettingsDb_AppCfg.hpp + Arrange/ArrangeSettingsDb_AppCfg.cpp + Arrange/Scene.hpp + Arrange/Scene.cpp + Arrange/SceneBuilder.hpp + Arrange/SceneBuilder.cpp + Arrange/Tasks/ArrangeTask.hpp + Arrange/Tasks/ArrangeTaskImpl.hpp + Arrange/Tasks/FillBedTask.hpp + Arrange/Tasks/FillBedTaskImpl.hpp + Arrange/Tasks/MultiplySelectionTask.hpp + Arrange/Tasks/MultiplySelectionTaskImpl.hpp + Arrange/SegmentedRectangleBed.hpp + Arrange/Core/ArrangeItemTraits.hpp + Arrange/Core/DataStoreTraits.hpp + Arrange/Core/ArrangeBase.hpp + Arrange/Core/PackingContext.hpp + Arrange/Core/ArrangeFirstFit.hpp + Arrange/Core/Beds.hpp + Arrange/Core/Beds.cpp + Arrange/Core/NFP/NFP.hpp + Arrange/Core/NFP/NFP.cpp + Arrange/Core/NFP/NFPConcave_CGAL.hpp + Arrange/Core/NFP/NFPConcave_CGAL.cpp + Arrange/Core/NFP/NFPConcave_Tesselate.hpp + Arrange/Core/NFP/NFPConcave_Tesselate.cpp + Arrange/Core/NFP/EdgeCache.hpp + Arrange/Core/NFP/EdgeCache.cpp + Arrange/Core/NFP/CircularEdgeIterator.hpp + Arrange/Core/NFP/NFPArrangeItemTraits.hpp + Arrange/Core/NFP/PackStrategyNFP.hpp + Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp + Arrange/Core/NFP/Kernels/KernelTraits.hpp + Arrange/Core/NFP/Kernels/GravityKernel.hpp + Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp + Arrange/Core/NFP/Kernels/CompactifyKernel.hpp + Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp + Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp + Arrange/Core/NFP/Kernels/KernelUtils.hpp MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp @@ -433,6 +480,10 @@ set(SLIC3R_SOURCES add_library(libslic3r STATIC ${SLIC3R_SOURCES}) +if (WIN32) + target_compile_definitions(libslic3r PUBLIC NOMINMAX) +endif() + foreach(_source IN ITEMS ${SLIC3R_SOURCES}) get_filename_component(_source_path "${_source}" PATH) string(REPLACE "/" "\\" _group_path "${_source_path}") diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 85ef53c888..e942423947 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -773,8 +773,16 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type) { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); } +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, ClipperLib::PolyFillType fill_type) + { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No, fill_type); } Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject) { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &subject2) + { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(subject2), ClipperLib::pftNonZero)); } +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &subject2) + { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(subject2), ClipperLib::pftNonZero)); } +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2) + { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ClipperLib::pftNonZero)); } Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 774e9cb42f..7935034b7b 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -498,7 +498,11 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2); // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero); +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero); Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &subject2); +Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2); +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &subject2); Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); // Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed. diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp index eb0be4fe1f..fc5de5349b 100644 --- a/src/libslic3r/Geometry/ConvexHull.hpp +++ b/src/libslic3r/Geometry/ConvexHull.hpp @@ -3,11 +3,10 @@ #include -#include "../Polygon.hpp" +#include "../ExPolygon.hpp" namespace Slic3r { -class ExPolygon; using ExPolygons = std::vector; namespace Geometry { @@ -16,7 +15,9 @@ Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); Polygon convex_hull(const ExPolygons &expolygons); -Polygon convex_hulll(const Polylines &polylines); +Polygon convex_hull(const Polylines &polylines); +inline Polygon convex_hull(const Polygon &poly) { return convex_hull(poly.points); } +inline Polygon convex_hull(const ExPolygon &poly) { return convex_hull(poly.contour.points); } // Returns true if the intersection of the two convex polygons A and B // is not an empty set. diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index d90757bedd..325828fe20 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -38,6 +38,32 @@ template using Scalar = typename Traits>::Scalar; template auto get_a(L &&l) { return Traits>::get_a(l); } template auto get_b(L &&l) { return Traits>::get_b(l); } +template auto sqlength(L &&l) +{ + return (get_b(l) - get_a(l)).squaredNorm(); +} + +template +auto sqlength(L &&l) +{ + return (get_b(l).template cast() - get_a(l).template cast()).squaredNorm(); +} + +template == 2> > +auto angle_to_x(const L &l) +{ + auto dx = double(get_b(l).x()) - get_a(l).x(); + auto dy = double(get_b(l).y()) - get_a(l).y(); + + double a = std::atan2(dy, dx); + auto s = std::signbit(a); + + if(s) + a += 2. * PI; + + return a; +} + // Distance to the closest point of line. template inline double distance_to_squared(const L &line, const Vec, Scalar> &point, Vec, Scalar> *nearest_point) @@ -162,7 +188,7 @@ public: void translate(double x, double y) { this->translate(Point(x, y)); } void rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); } void reverse() { std::swap(this->a, this->b); } - double length() const { return (b - a).cast().norm(); } + double length() const { return (b.cast() - a.cast()).norm(); } Point midpoint() const { return (this->a + this->b) / 2; } bool intersection_infinite(const Line &other, Point* point) const; bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp index 51fd8a45e7..da27634205 100644 --- a/src/libslic3r/MinAreaBoundingBox.cpp +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -1,6 +1,7 @@ #include "MinAreaBoundingBox.hpp" #include +#include #if defined(_MSC_VER) && defined(__clang__) #define BOOST_NO_CXX17_HDR_STRING_VIEW @@ -103,4 +104,16 @@ void remove_collinear_points(ExPolygon &p) { p = libnest2d::removeCollinearPoints(p, Unit(0)); } + +double fit_into_box_rotation(const Polygon &shape, const BoundingBox &bb) +{ + using namespace libnest2d; + + _Box box{{bb.min.x(), bb.min.y()}, {bb.max.x(), bb.max.y()}}; + + return fitIntoBoxRotation, Rational>(shape, + box, + EPSILON); +} + } // namespace Slic3r diff --git a/src/libslic3r/MinAreaBoundingBox.hpp b/src/libslic3r/MinAreaBoundingBox.hpp index 242fc96111..10c71c5f1c 100644 --- a/src/libslic3r/MinAreaBoundingBox.hpp +++ b/src/libslic3r/MinAreaBoundingBox.hpp @@ -50,6 +50,8 @@ public: const Point& axis() const { return m_axis; } }; -} +double fit_into_box_rotation(const Polygon &shape, const BoundingBox &box); + +} // namespace Slic3r #endif // MINAREABOUNDINGBOX_HPP diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2a8c78729b..bc02e06f67 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1062,7 +1062,8 @@ Polygon ModelObject::convex_hull_2d(const Transform3d& trafo_instance) const tbb::parallel_for(tbb::blocked_range(0, volumes.size()), [&](const tbb::blocked_range& range) { for (size_t i = range.begin(); i < range.end(); ++i) { const ModelVolume* v = volumes[i]; - chs.emplace_back(its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast(), 0.0f)); + if (v->is_model_part()) + chs.emplace_back(its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast(), 0.0f)); } }); @@ -1997,38 +1998,6 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } -arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const -{ -// static const double SIMPLIFY_TOLERANCE_MM = 0.1; - - Polygon p = get_object()->convex_hull_2d(this->get_matrix()); - -// if (!p.points.empty()) { -// Polygons pp{p}; -// pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); -// if (!pp.empty()) p = pp.front(); -// } - - arrangement::ArrangePolygon ret; - ret.poly.contour = std::move(p); - ret.translation = Vec2crd::Zero(); - ret.rotation = 0.; - - return ret; -} - -void ModelInstance::apply_arrange_result(const Vec2d &offs, double rotation) -{ - // write the transformation data into the model instance - auto trafo = get_transformation().get_matrix(); - auto tr = Transform3d::Identity(); - tr.translate(to_3d(unscaled(offs), 0.)); - trafo = tr * Eigen::AngleAxisd(rotation, Vec3d::UnitZ()) * trafo; - m_transformation.set_matrix(trafo); - - this->object->invalidate_bounding_box(); -} - indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const { TriangleSelector selector(mv.mesh()); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 221033c521..2df612f03b 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -11,7 +11,6 @@ #include "SLA/SupportPoint.hpp" #include "SLA/Hollowing.hpp" #include "TriangleMesh.hpp" -#include "Arrange.hpp" #include "CustomGCode.hpp" #include "enum_bitmask.hpp" #include "TextConfiguration.hpp" @@ -1155,11 +1154,7 @@ public: bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } - // Getting the input polygon for arrange - arrangement::ArrangePolygon get_arrange_polygon() const; - - // Apply the arrange result on the ModelInstance - void apply_arrange_result(const Vec2d& offs, double rotation); + void invalidate_object_bounding_box() { object->invalidate_bounding_box(); } protected: friend class Print; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 01a89a8e56..2755102b5a 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,77 +1,17 @@ #include "ModelArrange.hpp" + +#include + #include #include +#include "Arrange/Core/ArrangeItemTraits.hpp" +#include "Arrange/Items/ArrangeItem.hpp" + #include "MTUtils.hpp" namespace Slic3r { -arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances) -{ - size_t count = 0; - for (auto obj : model.objects) count += obj->instances.size(); - - ArrangePolygons input; - input.reserve(count); - instances.clear(); instances.reserve(count); - for (ModelObject *mo : model.objects) - for (ModelInstance *minst : mo->instances) { - input.emplace_back(minst->get_arrange_polygon()); - instances.emplace_back(minst); - } - - return input; -} - -bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, VirtualBedFn vfn) -{ - bool ret = true; - - for(size_t i = 0; i < input.size(); ++i) { - if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); } - if (input[i].bed_idx >= 0) - instances[i]->apply_arrange_result(input[i].translation.cast(), - input[i].rotation); - } - - return ret; -} - -Slic3r::arrangement::ArrangePolygon get_arrange_poly(const Model &model) -{ - ArrangePolygon ap; - Points &apts = ap.poly.contour.points; - for (const ModelObject *mo : model.objects) - for (const ModelInstance *minst : mo->instances) { - ArrangePolygon obj_ap = minst->get_arrange_polygon(); - ap.poly.contour.rotate(obj_ap.rotation); - ap.poly.contour.translate(obj_ap.translation.x(), obj_ap.translation.y()); - const Points &pts = obj_ap.poly.contour.points; - std::copy(pts.begin(), pts.end(), std::back_inserter(apts)); - } - - apts = std::move(Geometry::convex_hull(apts).points); - return ap; -} - -void duplicate(Model &model, Slic3r::arrangement::ArrangePolygons &copies, VirtualBedFn vfn) -{ - for (ModelObject *o : model.objects) { - // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = o->instances; - o->instances.clear(); - for (const ModelInstance *i : instances) { - for (arrangement::ArrangePolygon &ap : copies) { - if (ap.bed_idx != 0) vfn(ap); - ModelInstance *instance = o->add_instance(*i); - Vec2d pos = unscale(ap.translation); - instance->set_offset(instance->get_offset() + to_3d(pos, 0.)); - } - } - o->invalidate_bounding_box(); - } -} - void duplicate_objects(Model &model, size_t copies_num) { for (ModelObject *o : model.objects) { @@ -83,4 +23,49 @@ void duplicate_objects(Model &model, size_t copies_num) } } +bool arrange_objects(Model &model, + const arr2::ArrangeBed &bed, + const arr2::ArrangeSettingsView &settings) +{ + arr2::Scene scene{arr2::SceneBuilder{} + .set_bed(bed) + .set_arrange_settings(settings) + .set_model(model)}; + + auto task = arr2::ArrangeTaskBase::create(arr2::Tasks::Arrange, scene); + auto result = task->process(); + return result->apply_on(scene.model()); +} + +void duplicate_objects(Model &model, + size_t copies_num, + const arr2::ArrangeBed &bed, + const arr2::ArrangeSettingsView &settings) +{ + duplicate_objects(model, copies_num); + arrange_objects(model, bed, settings); +} + +void duplicate(Model &model, + size_t copies_num, + const arr2::ArrangeBed &bed, + const arr2::ArrangeSettingsView &settings) +{ + auto vbh = arr2::VirtualBedHandler::create(arr2::to_extended_bed(bed)); + arr2::DuplicableModel dup_model{&model, std::move(vbh), bounding_box(bed)}; + + arr2::Scene scene{arr2::BasicSceneBuilder{} + .set_arrangeable_model(&dup_model) + .set_arrange_settings(&settings) + .set_bed(bed)}; + + if (copies_num >= 1) + copies_num -= 1; + + auto task = arr2::MultiplySelectionTask::create(scene, copies_num); + auto result = task->process_native(arr2::DummyCtl{}); + if (result->apply_on(scene.model())) + dup_model.apply_duplicates(); +} + } // namespace Slic3r diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 124c5c018f..420f102fd9 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -1,7 +1,7 @@ #ifndef MODELARRANGE_HPP #define MODELARRANGE_HPP -#include +#include namespace Slic3r { @@ -9,63 +9,23 @@ class Model; class ModelInstance; using ModelInstancePtrs = std::vector; -using arrangement::ArrangePolygon; -using arrangement::ArrangePolygons; -using arrangement::ArrangeParams; -using arrangement::InfiniteBed; -using arrangement::CircleBed; - -// Do something with ArrangePolygons in virtual beds -using VirtualBedFn = std::function; - -[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&) -{ - throw Slic3r::RuntimeError("Objects could not fit on the bed"); -} - -ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances); -ArrangePolygon get_arrange_poly(const Model &model); -bool apply_arrange_polys(ArrangePolygons &polys, ModelInstancePtrs &instances, VirtualBedFn); - -void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn); +//void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn); void duplicate_objects(Model &model, size_t copies_num); -template -bool arrange_objects(Model & model, - const TBed & bed, - const ArrangeParams ¶ms, - VirtualBedFn vfn = throw_if_out_of_bed) -{ - ModelInstancePtrs instances; - auto&& input = get_arrange_polys(model, instances); - arrangement::arrange(input, bed, params); - - return apply_arrange_polys(input, instances, vfn); -} +bool arrange_objects(Model &model, + const arr2::ArrangeBed &bed, + const arr2::ArrangeSettingsView &settings); -template -void duplicate(Model & model, - size_t copies_num, - const TBed & bed, - const ArrangeParams ¶ms, - VirtualBedFn vfn = throw_if_out_of_bed) -{ - ArrangePolygons copies(copies_num, get_arrange_poly(model)); - arrangement::arrange(copies, bed, params); - duplicate(model, copies, vfn); -} - -template void duplicate_objects(Model & model, size_t copies_num, - const TBed & bed, - const ArrangeParams ¶ms, - VirtualBedFn vfn = throw_if_out_of_bed) -{ - duplicate_objects(model, copies_num); - arrange_objects(model, bed, params, vfn); -} + const arr2::ArrangeBed &bed, + const arr2::ArrangeSettingsView &settings); -} +void duplicate(Model & model, + size_t copies_num, + const arr2::ArrangeBed &bed, + const arr2::ArrangeSettingsView &settings); + +} // namespace Slic3r #endif // MODELARRANGE_HPP diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 62b53255b4..637b059220 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -90,6 +90,12 @@ public: inline auto end() const { return points.end(); } inline auto cbegin() const { return points.begin(); } inline auto cend() const { return points.end(); } + inline auto rbegin() { return points.rbegin(); } + inline auto rbegin() const { return points.rbegin(); } + inline auto rend() { return points.rend(); } + inline auto rend() const { return points.rend(); } + inline auto crbegin()const { return points.crbegin(); } + inline auto crend() const { return points.crend(); } }; class MultiPoint3 diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 9e423ff919..d6cea718b3 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -13,6 +13,8 @@ #include +#include + #include "Optimizer.hpp" namespace Slic3r { namespace opt { @@ -104,29 +106,6 @@ struct NLoptRAII { // Helper RAII class for nlopt_opt ~NLoptRAII() { nlopt_destroy(ptr); } }; -// Map a generic function to each argument following the mapping function -template -Fn for_each_argument(Fn &&fn, Args&&...args) -{ - // see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/ - (fn(std::forward(args)),...); - - return fn; -} - -// Call fn on each element of the input tuple tup. -template -Fn for_each_in_tuple(Fn fn, Tup &&tup) -{ - auto mpfn = [&fn](auto&...pack) { - for_each_argument(fn, pack...); - }; - - std::apply(mpfn, tup); - - return fn; -} - // Wrap each element of the tuple tup into a wrapper class W and return // a new tuple with each element being of type W where T_i is the type of // i-th element of tup. diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index 6212a5f59d..a82d15d42e 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -1,5 +1,5 @@ -#ifndef OPTIMIZER_HPP -#define OPTIMIZER_HPP +#ifndef PRUSASLICER_OPTIMIZER_HPP +#define PRUSASLICER_OPTIMIZER_HPP #include #include @@ -10,6 +10,11 @@ #include #include +#ifdef WIN32 +#undef min +#undef max +#endif + namespace Slic3r { namespace opt { template diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index e0c3958fd9..f1e72bbe2c 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -268,6 +268,12 @@ bool polygons_match(const Polygon &l, const Polygon &r); Polygon make_circle(double radius, double error); Polygon make_circle_num_segments(double radius, size_t num_segments); +// To replace reserve_vector where it's used for Polygons +template IntegerOnly reserve_polygons(I cap) +{ + return reserve_vector(cap); +} + } // Slic3r // start Boost diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 703e50cfa2..0e6dcff041 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -78,6 +78,9 @@ public: void split_at(const Point &point, Polyline* p1, Polyline* p2) const; bool is_straight() const; bool is_closed() const { return this->points.front() == this->points.back(); } + + using iterator = Points::iterator; + using const_iterator = Points::const_iterator; }; inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3da804066b..f09a4493d6 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4651,9 +4651,11 @@ std::string validate(const FullPrintConfig &cfg) BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_DEFINITION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ int print_config_static_initializer() { \ /* Putting a trace here to avoid the compiler to optimize out this function. */ \ - BOOST_LOG_TRIVIAL(trace) << "Initializing StaticPrintConfigs"; \ + /*BOOST_LOG_TRIVIAL(trace) << "Initializing StaticPrintConfigs";*/ \ + /* Tamas: alternative solution through a static volatile int. Boost log pollutes stdout and prevents tests from generating clean output */ \ + static volatile int ret = 1; \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_INITIALIZATION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ - return 1; \ + return ret; \ } PRINT_CONFIG_CACHE_INITIALIZE(( PrintObjectConfig, PrintRegionConfig, MachineEnvelopeConfig, GCodeConfig, PrintConfig, FullPrintConfig, @@ -4949,15 +4951,6 @@ Points get_bed_shape(const DynamicPrintConfig &config) return to_points(bed_shape_opt->values); } -void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out) -{ - if (is_XL_printer(cfg)) { - out = arrangement::SegmentedRectangleBed{get_extents(get_bed_shape(cfg)), 4, 4}; - } else { - out = arrangement::to_arrange_bed(get_bed_shape(cfg)); - } -} - Points get_bed_shape(const PrintConfig &cfg) { return to_points(cfg.bed_shape.values); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 54a835fe79..78c0ca03b4 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -19,7 +19,6 @@ #include "libslic3r.h" #include "Config.hpp" #include "SLA/SupportTreeStrategies.hpp" -#include "libslic3r/Arrange.hpp" #include #include @@ -1200,8 +1199,6 @@ Points get_bed_shape(const DynamicPrintConfig &cfg); Points get_bed_shape(const PrintConfig &cfg); Points get_bed_shape(const SLAPrinterConfig &cfg); -void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out); - std::string get_sla_suptree_prefix(const DynamicPrintConfig &config); // ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 83089fefee..9377a35d8d 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -321,7 +321,8 @@ template // Arbitrary allocator can be used IntegerOnly> reserve_vector(I capacity) { std::vector ret; - if (capacity > I(0)) ret.reserve(size_t(capacity)); + if (capacity > I(0)) + ret.reserve(size_t(capacity)); return ret; } @@ -330,6 +331,18 @@ IntegerOnly> reserve_vector(I capacity) template using remove_cvref_t = std::remove_cv_t>; +namespace detail_strip_ref_wrappers { +template struct StripCVRef_ { using type = remove_cvref_t; }; +template struct StripCVRef_> +{ + using type = std::remove_cv_t; +}; +} // namespace detail + +// Removes reference wrappers as well +template using StripCVRef = + typename detail_strip_ref_wrappers::StripCVRef_>::type; + // A very simple range concept implementation with iterator-like objects. // This should be replaced by std::ranges::subrange (C++20) template class Range @@ -358,6 +371,48 @@ template auto range(Cont &&cont) return Range{std::begin(cont), std::end(cont)}; } +template auto crange(Cont &&cont) +{ + return Range{std::cbegin(cont), std::cend(cont)}; +} + +template> +class IntIterator { + IntType m_val; +public: + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = IntType; + using pointer = IntType*; // or also value_type* + using reference = IntType&; // or also value_type& + + IntIterator(IntType v): m_val{v} {} + + IntIterator & operator++() { ++m_val; return *this; } + IntIterator operator++(int) { auto cpy = *this; ++m_val; return cpy; } + IntIterator & operator--() { --m_val; return *this; } + IntIterator operator--(int) { auto cpy = *this; --m_val; return cpy; } + + IntType operator*() const { return m_val; } + IntType operator->() const { return m_val; } + + bool operator==(const IntIterator& other) const + { + return m_val == other.m_val; + } + + bool operator!=(const IntIterator& other) const + { + return !(*this == other); + } +}; + +template> +auto range(IntType from, IntType to) +{ + return Range{IntIterator{from}, IntIterator{to}}; +} + template> constexpr T NaN = std::numeric_limits::quiet_NaN(); @@ -385,6 +440,32 @@ inline IntegerOnly fast_round_up(double a) template using SamePair = std::pair; +// Helper to be used in static_assert. +template struct always_false { enum { value = false }; }; + +// Map a generic function to each argument following the mapping function +template +Fn for_each_argument(Fn &&fn, Args&&...args) +{ + // see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/ + (fn(std::forward(args)),...); + + return fn; +} + +// Call fn on each element of the input tuple tup. +template +Fn for_each_in_tuple(Fn fn, Tup &&tup) +{ + auto mpfn = [&fn](auto&...pack) { + for_each_argument(fn, pack...); + }; + + std::apply(mpfn, tup); + + return fn; +} + } // namespace Slic3r #endif // _libslic3r_h_ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5d7b2c0ef7..c5d9cbb075 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -8,6 +8,8 @@ set(SLIC3R_GUI_SOURCES pchheader.hpp GUI/AboutDialog.cpp GUI/AboutDialog.hpp + GUI/ArrangeSettingsDialogImgui.hpp + GUI/ArrangeSettingsDialogImgui.cpp GUI/SysInfoDialog.cpp GUI/SysInfoDialog.hpp GUI/KBShortcutsDialog.cpp @@ -194,8 +196,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/BusyCursorJob.hpp GUI/Jobs/CancellableJob.hpp GUI/Jobs/PlaterWorker.hpp - GUI/Jobs/ArrangeJob.hpp - GUI/Jobs/ArrangeJob.cpp + GUI/Jobs/ArrangeJob2.hpp + GUI/Jobs/ArrangeJob2.cpp GUI/Jobs/CreateFontNameImageJob.cpp GUI/Jobs/CreateFontNameImageJob.hpp GUI/Jobs/CreateFontStyleImagesJob.cpp @@ -204,8 +206,6 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/EmbossJob.hpp GUI/Jobs/RotoptimizeJob.hpp GUI/Jobs/RotoptimizeJob.cpp - GUI/Jobs/FillBedJob.hpp - GUI/Jobs/FillBedJob.cpp GUI/Jobs/SLAImportJob.hpp GUI/Jobs/SLAImportJob.cpp GUI/Jobs/ProgressIndicator.hpp diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp new file mode 100644 index 0000000000..9c75ce1cfa --- /dev/null +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp @@ -0,0 +1,134 @@ +#include "ArrangeSettingsDialogImgui.hpp" +#include "I18N.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/GUI.hpp" + +namespace Slic3r { namespace GUI { + +struct Settings { + float d_obj; + float d_bed; + bool rotations; + int xl_align; + int geom_handling; + int arr_strategy; +}; + +static void read_settings(Settings &s, const arr2::ArrangeSettingsDb *db) +{ + assert(db); + s.d_obj = db->get_distance_from_objects(); + s.d_bed = db->get_distance_from_bed(); + s.rotations = db->is_rotation_enabled(); + s.xl_align = db->get_xl_alignment(); + s.geom_handling = db->get_geometry_handling(); + s.arr_strategy = db->get_arrange_strategy(); +} + +ArrangeSettingsDialogImgui::ArrangeSettingsDialogImgui( + ImGuiWrapper *imgui, AnyPtr db) + : m_imgui{imgui}, m_db{std::move(db)} +{} + +void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) +{ + assert(m_imgui && m_db); + + m_imgui->set_next_window_pos(pos_x, pos_y, ImGuiCond_Always, 0.5f, 0.0f); + + m_imgui->begin(_L("Arrange options"), + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoCollapse); + + Settings settings; + read_settings(settings, m_db.get()); + + m_imgui->text(GUI::format_wxstr( + _L("Press %1%left mouse button to enter the exact value"), + shortkey_ctrl_prefix())); + + float dobj_min, dobj_max; + float dbed_min, dbed_max; + + m_db->distance_from_obj_range(dobj_min, dobj_max); + m_db->distance_from_bed_range(dbed_min, dbed_max); + + if (m_imgui->slider_float(_L("Spacing"), &settings.d_obj, dobj_min, + dobj_max, "%5.2f") || + dobj_min > settings.d_obj) { + settings.d_obj = std::max(dobj_min, settings.d_obj); + m_db->set_distance_from_objects(settings.d_obj); + } + + if (m_imgui->slider_float(_L("Spacing from bed"), &settings.d_bed, + dbed_min, dbed_max, "%5.2f") || + dbed_min > settings.d_bed) { + settings.d_bed = std::max(dbed_min, settings.d_bed); + m_db->set_distance_from_bed(settings.d_bed); + } + + if (m_imgui->checkbox(_L("Enable rotations (slow)"), settings.rotations)) { + m_db->set_rotation_enabled(settings.rotations); + } + +// Points bed = m_config ? get_bed_shape(*m_config) : Points{}; + if (/*arrangement::is_box(bed) */ m_show_xl_combo_predicate() && + settings.xl_align >= 0 && + m_imgui->combo(_L("Alignment"), + {_u8L("Center"), _u8L("Rear left"), _u8L("Front left"), + _u8L("Front right"), _u8L("Rear right"), + _u8L("Random")}, + settings.xl_align)) { + if (settings.xl_align >= 0 && + settings.xl_align < ArrangeSettingsView::xlpCount) + m_db->set_xl_alignment(static_cast( + settings.xl_align)); + } + + if (m_imgui->combo(_L("Geometry handling"), + {_u8L("Fast"), _u8L("Balanced"), _u8L("Full complexity")}, + settings.geom_handling)) { + if (settings.geom_handling >= 0 && + settings.geom_handling < ArrangeSettingsView::ghCount) + m_db->set_geometry_handling( + static_cast( + settings.geom_handling)); + } + + if (m_imgui->combo(_L("Strategy"), + {_u8L("Automatic"), _u8L("Pull to center")}, + settings.arr_strategy)) { + if (settings.arr_strategy >= 0 && + settings.arr_strategy < ArrangeSettingsView::asCount) + m_db->set_arrange_strategy( + static_cast( + settings.arr_strategy)); + } + + ImGui::Separator(); + + if (m_imgui->button(_L("Reset defaults"))) { + arr2::ArrangeSettingsDb::Values df = m_db->get_defaults(); + m_db->set_distance_from_objects(df.d_obj); + m_db->set_distance_from_bed(df.d_bed); + m_db->set_rotation_enabled(df.rotations); + if (m_show_xl_combo_predicate()) + m_db->set_xl_alignment(df.xl_align); + + m_db->set_geometry_handling(df.geom_handling); + m_db->set_arrange_strategy(df.arr_strategy); + + if (m_on_reset_btn) + m_on_reset_btn(); + } + + ImGui::SameLine(); + + if (m_imgui->button(_L("Arrange")) && m_on_arrange_btn) { + m_on_arrange_btn(); + } + + m_imgui->end(); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp new file mode 100644 index 0000000000..2c46a0732b --- /dev/null +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp @@ -0,0 +1,53 @@ +#ifndef ARRANGESETTINGSDIALOGIMGUI_HPP +#define ARRANGESETTINGSDIALOGIMGUI_HPP + +#include "libslic3r/Arrange/ArrangeSettingsView.hpp" +#include "ImGuiWrapper.hpp" +#include "libslic3r/AnyPtr.hpp" + +namespace Slic3r { +namespace GUI { + +class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView { + ImGuiWrapper *m_imgui; + AnyPtr m_db; + + std::function m_on_arrange_btn; + std::function m_on_reset_btn; + + std::function m_show_xl_combo_predicate = [] { return true; }; + +public: + ArrangeSettingsDialogImgui(ImGuiWrapper *imgui, AnyPtr db); + + void render(float pos_x, float pos_y); + + void show_xl_align_combo(std::function pred) + { + m_show_xl_combo_predicate = pred; + } + + void on_arrange_btn(std::function on_arrangefn) + { + m_on_arrange_btn = on_arrangefn; + } + + void on_reset_btn(std::function on_resetfn) + { + m_on_reset_btn = on_resetfn; + } + + // ArrangeSettingsView iface: + + float get_distance_from_objects() const override { return m_db->get_distance_from_objects(); } + float get_distance_from_bed() const override { return m_db->get_distance_from_bed(); } + bool is_rotation_enabled() const override { return m_db->is_rotation_enabled(); } + + XLPivots get_xl_alignment() const override { return m_db->get_xl_alignment(); } + GeometryHandling get_geometry_handling() const override { return m_db->get_geometry_handling(); } + ArrangeStrategy get_arrange_strategy() const override { return m_db->get_arrange_strategy(); } +}; + +}} // namespace Slic3r::GUI + +#endif // ARRANGESETTINGSDIALOGIMGUI_HPP diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8bdc3bfa3a..fb2be672df 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -35,6 +35,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp" #if ENABLE_RETINA_GL #include "slic3r/Utils/RetinaHelper.hpp" @@ -1045,94 +1046,6 @@ wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent); const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; -void GLCanvas3D::load_arrange_settings() -{ - std::string dist_fff_str = - wxGetApp().app_config->get("arrange", "min_object_distance_fff"); - - std::string dist_bed_fff_str = - wxGetApp().app_config->get("arrange", "min_bed_distance_fff"); - - std::string dist_fff_seq_print_str = - wxGetApp().app_config->get("arrange", "min_object_distance_fff_seq_print"); - - std::string dist_bed_fff_seq_print_str = - wxGetApp().app_config->get("arrange", "min_bed_distance_fff_seq_print"); - - std::string dist_sla_str = - wxGetApp().app_config->get("arrange", "min_object_distance_sla"); - - std::string dist_bed_sla_str = - wxGetApp().app_config->get("arrange", "min_bed_distance_sla"); - - std::string en_rot_fff_str = - wxGetApp().app_config->get("arrange", "enable_rotation_fff"); - - std::string en_rot_fff_seqp_str = - wxGetApp().app_config->get("arrange", "enable_rotation_fff_seq_print"); - - std::string en_rot_sla_str = - wxGetApp().app_config->get("arrange", "enable_rotation_sla"); - -// std::string alignment_fff_str = -// wxGetApp().app_config->get("arrange", "alignment_fff"); - -// std::string alignment_fff_seqp_str = -// wxGetApp().app_config->get("arrange", "alignment_fff_seq_pring"); - -// std::string alignment_sla_str = -// wxGetApp().app_config->get("arrange", "alignment_sla"); - - // Override default alignment and save save/load it to a temporary slot "alignment_xl" - std::string alignment_xl_str = - wxGetApp().app_config->get("arrange", "alignment_xl"); - - if (!dist_fff_str.empty()) - m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str); - - if (!dist_bed_fff_str.empty()) - m_arrange_settings_fff.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_str); - - if (!dist_fff_seq_print_str.empty()) - m_arrange_settings_fff_seq_print.distance = string_to_float_decimal_point(dist_fff_seq_print_str); - - if (!dist_bed_fff_seq_print_str.empty()) - m_arrange_settings_fff_seq_print.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str); - - if (!dist_sla_str.empty()) - m_arrange_settings_sla.distance = string_to_float_decimal_point(dist_sla_str); - - if (!dist_bed_sla_str.empty()) - m_arrange_settings_sla.distance_from_bed = string_to_float_decimal_point(dist_bed_sla_str); - - if (!en_rot_fff_str.empty()) - m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes"); - - if (!en_rot_fff_seqp_str.empty()) - m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes"); - - if (!en_rot_sla_str.empty()) - m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); - -// if (!alignment_sla_str.empty()) -// m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str); - -// if (!alignment_fff_str.empty()) -// m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str); - -// if (!alignment_fff_seqp_str.empty()) -// m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str); - - // Override default alignment and save save/load it to a temporary slot "alignment_xl" - int arr_alignment = static_cast(arrangement::Pivots::BottomLeft); - if (!alignment_xl_str.empty()) - arr_alignment = std::stoi(alignment_xl_str); - - m_arrange_settings_sla.alignment = arr_alignment ; - m_arrange_settings_fff.alignment = arr_alignment ; - m_arrange_settings_fff_seq_print.alignment = arr_alignment ; -} - static std::vector processed_objects_idxs(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes) { std::vector ret; @@ -1392,38 +1305,47 @@ bool GLCanvas3D::is_arrange_alignment_enabled() const return m_config ? is_XL_printer(*m_config) : false; } -GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) - : m_canvas(canvas) - , m_context(nullptr) - , m_bed(bed) +GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed) + : m_canvas(canvas), + m_context(nullptr), + m_bed(bed) #if ENABLE_RETINA_GL - , m_retina_helper(nullptr) + , + m_retina_helper(nullptr) #endif - , m_in_render(false) - , m_main_toolbar(GLToolbar::Normal, "Main") - , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") - , m_gizmos(*this) - , m_use_clipping_planes(false) - , m_sidebar_field("") - , m_extra_frame_requested(false) - , m_config(nullptr) - , m_process(nullptr) - , m_model(nullptr) - , m_dirty(true) - , m_initialized(false) - , m_apply_zoom_to_volumes_filter(false) - , m_picking_enabled(false) - , m_moving_enabled(false) - , m_dynamic_background_enabled(false) - , m_multisample_allowed(false) - , m_moving(false) - , m_tab_down(false) - , m_cursor_type(Standard) - , m_reload_delayed(false) - , m_render_sla_auxiliaries(true) - , m_labels(*this) - , m_slope(m_volumes) - , m_sla_view(*this) + , + m_in_render(false), + m_main_toolbar(GLToolbar::Normal, "Main"), + m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo"), + m_gizmos(*this), + m_use_clipping_planes(false), + m_sidebar_field(""), + m_extra_frame_requested(false), + m_config(nullptr), + m_process(nullptr), + m_model(nullptr), + m_dirty(true), + m_initialized(false), + m_apply_zoom_to_volumes_filter(false), + m_picking_enabled(false), + m_moving_enabled(false), + m_dynamic_background_enabled(false), + m_multisample_allowed(false), + m_moving(false), + m_tab_down(false), + m_cursor_type(Standard), + m_reload_delayed(false), + m_render_sla_auxiliaries(true), + m_labels(*this), + m_slope(m_volumes), + m_sla_view(*this), + m_arrange_settings_dialog{wxGetApp().imgui(), + std::make_unique( + wxGetApp().app_config, + [this]() { return m_config; }, + [this] { + return current_printer_technology(); + })} { if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); @@ -1433,9 +1355,13 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) #endif // ENABLE_RETINA_GL } - load_arrange_settings(); - m_selection.set_volumes(&m_volumes.volumes); + m_arrange_settings_dialog.show_xl_align_combo([this](){ + return this->is_arrange_alignment_enabled(); + }); + m_arrange_settings_dialog.on_arrange_btn([]{ + wxGetApp().plater()->arrange(); + }); } GLCanvas3D::~GLCanvas3D() @@ -4831,104 +4757,9 @@ bool GLCanvas3D::_render_search_list(float pos_x) bool GLCanvas3D::_render_arrange_menu(float pos_x) { - ImGuiWrapper *imgui = wxGetApp().imgui(); + m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height()); - imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); - - imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - ArrangeSettings settings = get_arrange_settings(); - ArrangeSettings &settings_out = get_arrange_settings_ref(this); - - auto &appcfg = wxGetApp().app_config; - PrinterTechnology ptech = current_printer_technology(); - - bool settings_changed = false; - float dist_min = 0.f; - float dist_bed_min = 0.f; - std::string dist_key = "min_object_distance"; - std::string dist_bed_key = "min_bed_distance"; - std::string rot_key = "enable_rotation"; - std::string align_key = "alignment"; - std::string postfix; - - if (ptech == ptSLA) { - postfix = "_sla"; - } else if (ptech == ptFFF) { - auto co_opt = m_config->option("complete_objects"); - if (co_opt && co_opt->value) { - dist_min = float(min_object_distance(*m_config)); - postfix = "_fff_seq_print"; - } else { - dist_min = 0.f; - postfix = "_fff"; - } - } - - dist_key += postfix; - dist_bed_key += postfix; - rot_key += postfix; - align_key += postfix; - - imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); - - if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) { - settings.distance = std::max(dist_min, settings.distance); - settings_out.distance = settings.distance; - appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); - settings_changed = true; - } - - if (imgui->slider_float(_L("Spacing from bed"), &settings.distance_from_bed, dist_bed_min, 100.0f, "%5.2f") || dist_bed_min > settings.distance_from_bed) { - settings.distance_from_bed = std::max(dist_bed_min, settings.distance_from_bed); - settings_out.distance_from_bed = settings.distance_from_bed; - appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed)); - settings_changed = true; - } - - if (imgui->checkbox(_L("Enable rotations (slow)"), settings.enable_rotation)) { - settings_out.enable_rotation = settings.enable_rotation; - appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); - settings_changed = true; - } - - Points bed = m_config ? get_bed_shape(*m_config) : Points{}; - - if (arrangement::is_box(bed) && settings.alignment >= 0 && - imgui->combo(_L("Alignment"), {_u8L("Center"), _u8L("Rear left"), _u8L("Front left"), _u8L("Front right"), _u8L("Rear right"), _u8L("Random") }, settings.alignment)) { - settings_out.alignment = settings.alignment; - appcfg->set("arrange", align_key.c_str(), std::to_string(settings_out.alignment)); - settings_changed = true; - } - - ImGui::Separator(); - - if (imgui->button(_L("Reset"))) { - auto alignment = settings_out.alignment; - settings_out = ArrangeSettings{}; - settings_out.distance = std::max(dist_min, settings_out.distance); - - // Default alignment for XL printers set explicitly: - if (is_arrange_alignment_enabled()) - settings_out.alignment = static_cast(arrangement::Pivots::BottomLeft); - else - settings_out.alignment = alignment; - - appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); - appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed)); - appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); - settings_changed = true; - } - - ImGui::SameLine(); - - if (imgui->button(_L("Arrange"))) { - wxGetApp().plater()->arrange(); - } - - imgui->end(); - - return settings_changed; + return true; } #define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 @@ -7773,15 +7604,20 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot) { DynamicPrintConfig cfg; - cfg.opt("wipe_tower_x", true)->value = m_pos(X); - cfg.opt("wipe_tower_y", true)->value = m_pos(Y); - cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; + cfg.opt("wipe_tower_x", true)->value = pos.x(); + cfg.opt("wipe_tower_y", true)->value = pos.y(); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * rot; wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); } +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const +{ + apply_wipe_tower(m_pos, m_rotation); +} + void GLCanvas3D::RenderTimer::Notify() { wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 4d7368ed26..a25fddaa67 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -18,6 +18,7 @@ #include "Camera.hpp" #include "SceneRaycaster.hpp" #include "GUI_Utils.hpp" +#include "ArrangeSettingsDialogImgui.hpp" #include "libslic3r/Slicing.hpp" @@ -466,6 +467,8 @@ public: float accuracy = 0.65f; // Unused currently bool enable_rotation = false; int alignment = 0; + int geometry_handling = 0; + int strategy = 0; }; enum class ESLAViewType @@ -581,44 +584,11 @@ private: SLAView m_sla_view; bool m_sla_view_type_detection_active{ false }; - ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla, - m_arrange_settings_fff_seq_print; - bool is_arrange_alignment_enabled() const; - template - static auto & get_arrange_settings_ref(Self *self) { - PrinterTechnology ptech = self->current_printer_technology(); - - auto *ptr = &self->m_arrange_settings_fff; - - if (ptech == ptSLA) { - ptr = &self->m_arrange_settings_sla; - } else if (ptech == ptFFF) { - auto co_opt = self->m_config->template option("complete_objects"); - if (co_opt && co_opt->value) - ptr = &self->m_arrange_settings_fff_seq_print; - else - ptr = &self->m_arrange_settings_fff; - } - - return *ptr; - } + ArrangeSettingsDialogImgui m_arrange_settings_dialog; public: - ArrangeSettings get_arrange_settings() const { - const ArrangeSettings &settings = get_arrange_settings_ref(this); - ArrangeSettings ret = settings; - if (&settings == &m_arrange_settings_fff_seq_print) { - ret.distance = std::max(ret.distance, - float(min_object_distance(*m_config))); - } - - if (!is_arrange_alignment_enabled()) - ret.alignment = -1; - - return ret; - } struct ContoursList { @@ -631,7 +601,6 @@ public: }; private: - void load_arrange_settings(); class SequentialPrintClearance { @@ -754,10 +723,13 @@ public: void update_instance_printable_state_for_objects(const std::vector& object_idxs); void set_config(const DynamicPrintConfig* config); + const DynamicPrintConfig *config() const { return m_config; } void set_process(BackgroundSlicingProcess* process); void set_model(Model* model); const Model* get_model() const { return m_model; } + const arr2::ArrangeSettingsView * get_arrange_settings_view() const { return &m_arrange_settings_dialog; } + const Selection& get_selection() const { return m_selection; } Selection& get_selection() { return m_selection; } @@ -919,8 +891,11 @@ public: inline const Vec2d& pos() const { return m_pos; } inline double rotation() const { return m_rotation; } inline const Vec2d bb_size() const { return m_bb.size(); } + inline const BoundingBoxf& bounding_box() const { return m_bb; } void apply_wipe_tower() const; + + static void apply_wipe_tower(Vec2d pos, double rot); }; WipeTowerInfo get_wipe_tower_info() const; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2d6333f8f7..7609a949f3 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -403,27 +403,27 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, arrangement::ArrangeParams get_arrange_params(Plater *p) { - const GLCanvas3D::ArrangeSettings &settings = - p->canvas3D()->get_arrange_settings(); + const arr2::ArrangeSettingsView *settings = + p->canvas3D()->get_arrange_settings_view(); arrangement::ArrangeParams params; - params.allow_rotations = settings.enable_rotation; - params.min_obj_distance = scaled(settings.distance); - params.min_bed_distance = scaled(settings.distance_from_bed); + params.allow_rotations = settings->is_rotation_enabled(); + params.min_obj_distance = scaled(settings->get_distance_from_objects()); + params.min_bed_distance = scaled(settings->get_distance_from_bed()); arrangement::Pivots pivot = arrangement::Pivots::Center; int pivot_max = static_cast(arrangement::Pivots::TopRight); - if (settings.alignment < 0) { + if (settings->get_xl_alignment() < 0) { pivot = arrangement::Pivots::Center; - } else if (settings.alignment > pivot_max) { + } else if (settings->get_xl_alignment() == arr2::ArrangeSettingsView::xlpRandom) { // means it should be random std::random_device rd{}; std::mt19937 rng(rd()); std::uniform_int_distribution dist(0, pivot_max); pivot = static_cast(dist(rng)); } else { - pivot = static_cast(settings.alignment); + pivot = static_cast(settings->get_xl_alignment()); } params.alignment = pivot; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob2.cpp b/src/slic3r/GUI/Jobs/ArrangeJob2.cpp new file mode 100644 index 0000000000..91830c3fb3 --- /dev/null +++ b/src/slic3r/GUI/Jobs/ArrangeJob2.cpp @@ -0,0 +1,207 @@ +#include "ArrangeJob2.hpp" + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +namespace Slic3r { namespace GUI { + +class GUISelectionMask: public arr2::SelectionMask { + const Selection *m_sel; + +public: + explicit GUISelectionMask(const Selection *sel) : m_sel{sel} {} + + bool is_wipe_tower() const override + { + return m_sel->is_wipe_tower(); + } + + std::vector selected_objects() const override + { + auto selmap = m_sel->get_object_idxs(); + + std::vector ret(m_sel->get_model()->objects.size(), false); + + for (auto sel : selmap) { + ret[sel] = true; + } + + return ret; + } + + std::vector selected_instances(int obj_id) const override + { + auto objcnt = static_cast(m_sel->get_model()->objects.size()); + auto icnt = obj_id < objcnt ? + m_sel->get_model()->objects[obj_id]->instances.size() : + 0; + + std::vector ret(icnt, false); + + auto selmap = m_sel->get_content(); + auto objit = selmap.find(obj_id); + + if (objit != selmap.end() && obj_id < objcnt) { + ret = std::vector(icnt, false); + for (auto sel : objit->second) { + ret[sel] = true; + } + } + + return ret; + } +}; + +struct WipeTowerGeometry +{ + Polygon poly; + Point pos = Point::Zero(); + double rot = 0.; +}; + +static WipeTowerGeometry get_wtg(const GLCanvas3D::WipeTowerInfo &wti) +{ + WipeTowerGeometry ret; + + auto bb = scaled(wti.bounding_box()); + ret.poly = Polygon({ + {bb.min}, + {bb.max.x(), bb.min.y()}, + {bb.max}, + {bb.min.x(), bb.max.y()} + }); + + ret.pos = scaled(wti.pos()); + ret.rot = wti.rotation(); + + return ret; +} + +// Wipe tower logic based on GLCanvas3D::WipeTowerInfo implements the Arrangeable +// interface with this class: +class ArrangeableWT: public arr2::ArrangeableWipeTowerBase +{ + BoundingBox m_xl_bb; +public: + explicit ArrangeableWT(const ObjectID &oid, + const WipeTowerGeometry &wtg, + std::function sel_pred, + const BoundingBox xl_bb = {}) + : arr2::ArrangeableWipeTowerBase{oid, wtg.poly, wtg.pos, wtg.rot, + std::move(sel_pred)}, m_xl_bb{xl_bb} + {} + + void transform(const Vec2d &transl, double rot) override + { + GLCanvas3D::WipeTowerInfo::apply_wipe_tower(unscaled(pos) + transl, rot); + } + + void imbue_data(arr2::AnyWritable &datastore) const override + { + // For XL printers, there is a requirement that the wipe tower + // needs to be placed right beside the extruders which reside at the + // top edge of the bed. + if (m_xl_bb.defined) { + Vec2crd xl_center = m_xl_bb.center(); + datastore.write("sink", Vec2crd{xl_center.x(), 2 * m_xl_bb.max.y()}); + } + + arr2::ArrangeableWipeTowerBase::imbue_data(datastore); + } +}; + +// Now the wipe tower handler implementation for GLCanvas3D::WipeTowerInfo +// This is what creates the ArrangeableWT when the arrangement requests it. +// An object of this class is installed into the arrangement Scene. +struct WTH : public arr2::WipeTowerHandler +{ + GLCanvas3D::WipeTowerInfo wti; + ObjectID oid; + std::function sel_pred; + BoundingBox xl_bb; + + WTH(const ObjectID &objid, + const GLCanvas3D::WipeTowerInfo &w, + std::function sel_predicate = [] { return false; }) + : wti(w), oid{objid}, sel_pred{std::move(sel_predicate)} + {} + + template + static void visit_(Self &&self, Fn &&fn) + { + auto wtg = get_wtg(self.wti); + ArrangeableWT wta{self.oid, wtg, self.sel_pred, self.xl_bb}; + fn(wta); + } + + void visit(std::function fn) override + { + visit_(*this, fn); + } + + void visit(std::function fn) const override + { + visit_(*this, fn); + } + + void set_selection_predicate(std::function pred) override + { + sel_pred = std::move(pred); + } +}; + +arr2::SceneBuilder build_scene(Plater &plater, ArrangeSelectionMode mode) +{ + arr2::SceneBuilder builder; + + if (mode == ArrangeSelectionMode::SelectionOnly) { + auto sel = std::make_unique(&plater.get_selection()); + builder.set_selection(std::move(sel)); + } + + builder.set_arrange_settings(plater.canvas3D()->get_arrange_settings_view()); + + auto wti = plater.canvas3D()->get_wipe_tower_info(); + + AnyPtr wth; + + if (wti) { + wth = std::make_unique(plater.model().wipe_tower.id(), wti); + } + + if (plater.config()) { + builder.set_bed(*plater.config()); + if (wth && is_XL_printer(*plater.config())) { + wth->xl_bb = bounding_box(get_bed_shape(*plater.config())); + } + } + + builder.set_wipe_tower_handler(std::move(wth)); + builder.set_model(plater.model()); + + if (plater.printer_technology() == ptSLA) + builder.set_sla_print(&plater.sla_print()); + else + builder.set_fff_print(&plater.fff_print()); + + return builder; +} + +FillBedJob2::FillBedJob2(arr2::Scene &&scene, const Callbacks &cbs) : Base(std::move(scene), _u8L("Filling bed"), cbs) {} + +ArrangeJob2::ArrangeJob2(arr2::Scene &&scene, const Callbacks &cbs) : Base(std::move(scene), _u8L("Arranging"), cbs) {} + +}} // namespace Slic3r diff --git a/src/slic3r/GUI/Jobs/ArrangeJob2.hpp b/src/slic3r/GUI/Jobs/ArrangeJob2.hpp new file mode 100644 index 0000000000..31d51e9124 --- /dev/null +++ b/src/slic3r/GUI/Jobs/ArrangeJob2.hpp @@ -0,0 +1,138 @@ +#ifndef ARRANGEJOB2_HPP +#define ARRANGEJOB2_HPP + +#include + +#include "Job.hpp" + +#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" +#include "libslic3r/Arrange/Tasks/FillBedTask.hpp" +#include "libslic3r/Arrange/Items/ArrangeItem.hpp" +#include "libslic3r/Arrange/SceneBuilder.hpp" + +namespace Slic3r { + +class Model; +class DynamicPrintConfig; +class ModelInstance; + +class Print; +class SLAPrint; + +namespace GUI { + +class Plater; + +enum class ArrangeSelectionMode { SelectionOnly, Full }; + +arr2::SceneBuilder build_scene( + Plater &plater, ArrangeSelectionMode mode = ArrangeSelectionMode::Full); + +struct ArrCtl : public arr2::ArrangeTaskBase::Ctl +{ + Job::Ctl &parent_ctl; + int total; + const std::string &msg; + + ArrCtl(Job::Ctl &ctl, int cnt, const std::string &m) + : parent_ctl{ctl}, total{cnt}, msg{m} + {} + + bool was_canceled() const override + { + return parent_ctl.was_canceled(); + } + + void update_status(int remaining) override + { + if (remaining > 0) + parent_ctl.update_status((total - remaining) * 100 / total, msg); + } +}; + +template +class ArrangeJob_ : public Job +{ +public: + using ResultType = + typename decltype(std::declval().process_native( + std::declval()))::element_type; + + struct Callbacks { + std::function on_prepared; + std::function on_processed; + std::function on_finished; + }; + +private: + arr2::Scene m_scene; + std::unique_ptr m_task; + std::unique_ptr m_result; + Callbacks m_cbs; + std::string m_task_msg; + +public: + void process(Ctl &ctl) override + { + ctl.call_on_main_thread([this]{ + m_task = ArrangeTaskT::create(m_scene); + m_result.reset(); + if (m_task && m_cbs.on_prepared) + m_cbs.on_prepared(*m_task); + }).wait(); + + if (!m_task) + return; + + auto count = m_task->item_count_to_process(); + + if (count == 0) // Should be taken care of by plater, but doesn't hurt + return; + + ctl.update_status(0, m_task_msg); + + auto taskctl = ArrCtl{ctl, count, m_task_msg}; + m_result = m_task->process_native(taskctl); + + ctl.update_status(100, m_task_msg); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + if (canceled || eptr || !m_result) + return; + + if (m_task && m_cbs.on_processed) + m_cbs.on_processed(*m_task); + + m_result->apply_on(m_scene.model()); + + if (m_task && m_cbs.on_finished) + m_cbs.on_finished(*m_result); + } + + explicit ArrangeJob_(arr2::Scene &&scene, + std::string task_msg, + const Callbacks &cbs = {}) + : m_scene{std::move(scene)}, m_cbs{cbs}, m_task_msg{std::move(task_msg)} + {} +}; + +class ArrangeJob2: public ArrangeJob_> +{ + using Base = ArrangeJob_>; +public: + ArrangeJob2(arr2::Scene &&scene, const Callbacks &cbs = {}); +}; + +class FillBedJob2: public ArrangeJob_> +{ + using Base = ArrangeJob_>; +public: + FillBedJob2(arr2::Scene &&scene, const Callbacks &cbs = {}); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // ARRANGEJOB2_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 36f284f3a8..6799b9c062 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -79,8 +79,10 @@ #include "Camera.hpp" #include "Mouse3DController.hpp" #include "Tab.hpp" -#include "Jobs/ArrangeJob.hpp" -#include "Jobs/FillBedJob.hpp" +//#include "Jobs/ArrangeJob.hpp" +#include "Jobs/ArrangeJob2.hpp" + +//#include "Jobs/FillBedJob.hpp" #include "Jobs/RotoptimizeJob.hpp" #include "Jobs/SLAImportJob.hpp" #include "Jobs/SLAImportDialog.hpp" @@ -6259,8 +6261,52 @@ void Plater::fill_bed_with_instances() { auto &w = get_ui_job_worker(); if (w.is_idle()) { - p->take_snapshot(_L("Fill bed")); - replace_job(w, std::make_unique()); + + FillBedJob2::Callbacks cbs; + cbs.on_processed = [this](arr2::ArrangeTaskBase &t) { + p->take_snapshot(_L("Fill bed")); + }; + + auto scene = arr2::Scene{ + build_scene(*this, ArrangeSelectionMode::SelectionOnly)}; + + cbs.on_finished = [this](arr2::FillBedTaskResult &result) { + auto [prototype_mi, pos] = arr2::find_instance_by_id(model(), result.prototype_id); + + if (!prototype_mi) + return; + + ModelObject *model_object = prototype_mi->get_object(); + assert(model_object); + +// model_object->ensure_on_bed(); + + size_t inst_cnt = model_object->instances.size(); + if (inst_cnt == 0) + return; + + int object_idx = pos.obj_idx; + + if (object_idx < 0 || object_idx >= int(model().objects.size())) + return; + + int added_cnt = result.to_add.size(); + + if (added_cnt > 0) { + update(static_cast(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); + + // FIXME: somebody explain why this is needed for + // increase_object_instances + if (inst_cnt == 1) + added_cnt++; + + sidebar() + .obj_list() + ->increase_object_instances(object_idx, size_t(added_cnt)); + } + }; + + replace_job(w, std::make_unique(std::move(scene), cbs)); } } @@ -6338,8 +6384,8 @@ void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& ne selection.add_object((unsigned int)(last_id - i), i == 0); UIThreadWorker w; - replace_job(w, std::make_unique(ArrangeJob::SelectionOnly)); - w.process_events(); + arrange(w, true); + w.wait_for_idle(); } void Plater::export_gcode(bool prefer_removable) @@ -7229,19 +7275,70 @@ GLCanvas3D* Plater::get_current_canvas3D() return p->get_current_canvas3D(); } +static std::string concat_strings(const std::set &strings, + const std::string &delim = "\n") +{ + return std::accumulate( + strings.begin(), strings.end(), std::string(""), + [delim](const std::string &s, const std::string &name) { + return s + name + delim; + }); +} + void Plater::arrange() { if (p->can_arrange()) { auto &w = get_ui_job_worker(); - p->take_snapshot(_L("Arrange")); - - auto mode = wxGetKeyState(WXK_SHIFT) ? ArrangeJob::SelectionOnly : - ArrangeJob::Full; - - replace_job(w, std::make_unique(mode)); + arrange(w, wxGetKeyState(WXK_SHIFT)); } } +void Plater::arrange(Worker &w, bool selected) +{ + ArrangeSelectionMode mode = selected ? + ArrangeSelectionMode::SelectionOnly : + ArrangeSelectionMode::Full; + + arr2::Scene arrscene{build_scene(*this, mode)}; + + ArrangeJob2::Callbacks cbs; + + cbs.on_processed = [this](arr2::ArrangeTaskBase &t) { + p->take_snapshot(_L("Arrange")); + }; + + cbs.on_finished = [this](arr2::ArrangeTaskResult &t) { + std::set names; + + auto collect_unarranged = [this, &names](const arr2::TrafoOnlyArrangeItem &itm) { + if (!arr2::is_arranged(itm)) { + std::optional id = arr2::retrieve_id(itm); + if (id) { + auto [mi, pos] = arr2::find_instance_by_id(p->model, *id); + if (mi && mi->get_object()) { + names.insert(mi->get_object()->name); + } + } + } + }; + + for (const arr2::TrafoOnlyArrangeItem &itm : t.items) + collect_unarranged(itm); + + if (!names.empty()) { + get_notification_manager()->push_notification( + GUI::format(_L("Arrangement ignored the following objects which " + "can't fit into a single bed:\n%s"), + concat_strings(names, "\n"))); + } + + update(static_cast(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); + wxGetApp().obj_manipul()->set_dirty(); + }; + + replace_job(w, std::make_unique(std::move(arrscene), cbs)); +} + void Plater::set_current_canvas_as_dirty() { p->set_current_canvas_as_dirty(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index bed65d7d13..cc51a78628 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -337,6 +337,7 @@ public: GLCanvas3D* get_current_canvas3D(); void arrange(); + void arrange(Worker &w, bool selected); void set_current_canvas_as_dirty(); void unbind_canvas_event_handlers(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b91f75be41..d6d16db1d2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,7 +27,7 @@ endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) -add_subdirectory(libnest2d) +add_subdirectory(arrange) add_subdirectory(libslic3r) add_subdirectory(slic3rutils) add_subdirectory(fff_print) diff --git a/tests/arrange/CMakeLists.txt b/tests/arrange/CMakeLists.txt new file mode 100644 index 0000000000..26204e82b2 --- /dev/null +++ b/tests/arrange/CMakeLists.txt @@ -0,0 +1,17 @@ +get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp + test_arrange.cpp + test_arrange_integration.cpp + ../data/prusaparts.cpp +) + +target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) +set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") + +if (WIN32) + prusaslicer_copy_dlls(${_TEST_NAME}_tests) +endif() + +set(_catch_args "exclude:[NotWorking]") +list(APPEND _catch_args "${CATCH_EXTRA_ARGS}") +add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args}) diff --git a/tests/arrange/arrange_tests_main.cpp b/tests/arrange/arrange_tests_main.cpp new file mode 100644 index 0000000000..b2aa80259d --- /dev/null +++ b/tests/arrange/arrange_tests_main.cpp @@ -0,0 +1 @@ +#include diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp new file mode 100644 index 0000000000..578fc573b6 --- /dev/null +++ b/tests/arrange/test_arrange.cpp @@ -0,0 +1,1122 @@ +#include +#include "test_utils.hpp" + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include + +#include "../data/prusaparts.hpp" + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +template +static std::vector prusa_parts(double infl = 0.) { + using namespace Slic3r; + + std::vector ret; + + if(ret.empty()) { + ret.reserve(PRUSA_PART_POLYGONS.size()); + for(auto& inp : PRUSA_PART_POLYGONS) { + ExPolygons inp_cpy{ExPolygon(inp)}; + inp_cpy.back().contour.points.pop_back(); + + std::reverse(inp_cpy.back().contour.begin(), + inp_cpy.back().contour.end()); + + REQUIRE(inp_cpy.back().contour.is_counter_clockwise()); + + if (infl > 0.) + inp_cpy = offset_ex(inp_cpy, scaled(std::ceil(infl / 2.))); + + ArrItem item{Geometry::convex_hull(inp_cpy)}; + + ret.emplace_back(std::move(item)); + } + } + + return ret; +} + +static std::vector prusa_parts_ex(double infl = 0.) +{ + using namespace Slic3r; + + std::vector ret; + + if (ret.empty()) { + ret.reserve(PRUSA_PART_POLYGONS_EX.size()); + for (auto &inp : PRUSA_PART_POLYGONS_EX) { + ExPolygons inp_cpy{inp}; + + REQUIRE(std::all_of(inp_cpy.begin(), inp_cpy.end(), + [](const ExPolygon &p) { + return p.contour.is_counter_clockwise(); + })); + + if (infl > 0.) + inp_cpy = offset_ex(inp_cpy, scaled(std::ceil(infl / 2.))); + + Point c = get_extents(inp_cpy).center(); + for (auto &p : inp_cpy) + p.translate(-c); + + arr2::ArrangeItem item{inp_cpy}; + + ret.emplace_back(std::move(item)); + } + } + + return ret; +} + +using Slic3r::arr2::ArrangeItem; +using Slic3r::arr2::DecomposedShape; + +struct ItemPair { + ArrangeItem orbiter; + ArrangeItem stationary; +}; + +using Slic3r::scaled; +using Slic3r::Vec2f; + +std::vector nfp_testdata = { + { + ArrangeItem { DecomposedShape { + scaled(Vec2f{80, 50}) , + scaled(Vec2f{120, 50}), + scaled(Vec2f{100, 70}), + }}, + ArrangeItem { DecomposedShape { + scaled(Vec2f{40, 10}), + scaled(Vec2f{40, 40}), + scaled(Vec2f{10, 40}), + scaled(Vec2f{10, 10}), + }} + }, + { + ArrangeItem { + scaled(Vec2f{120, 50}), + scaled(Vec2f{140, 70}), + scaled(Vec2f{120, 90}), + scaled(Vec2f{80, 90}) , + scaled(Vec2f{60, 70}) , + scaled(Vec2f{80, 50}) , + }, + ArrangeItem { + scaled(Vec2f{40, 10}), + scaled(Vec2f{40, 40}), + scaled(Vec2f{10, 40}), + scaled(Vec2f{10, 10}), + } + }, +}; + +struct PolyPair { Slic3r::ExPolygon orbiter; Slic3r::ExPolygon stationary; }; + +std::vector nfp_concave_testdata = { + { // ItemPair + { + { + scaled(Vec2f{53.3726f, 14.2141f}), + scaled(Vec2f{53.2359f, 14.3386f}), + scaled(Vec2f{53.0141f, 14.2155f}), + scaled(Vec2f{52.8649f, 16.0091f}), + scaled(Vec2f{53.3659f, 15.7607f}), + scaled(Vec2f{53.8669f, 16.0091f}), + scaled(Vec2f{53.7178f, 14.2155f}), + scaled(Vec2f{53.4959f, 14.3386f}) + } + }, + { + { + scaled(Vec2f{11.8305f, 1.1603f}), + scaled(Vec2f{11.8311f, 2.6616f}), + scaled(Vec2f{11.3311f, 2.6611f}), + scaled(Vec2f{10.9311f, 2.9604f}), + scaled(Vec2f{10.9300f, 4.4608f}), + scaled(Vec2f{10.9311f, 4.9631f}), + scaled(Vec2f{11.3300f, 5.2636f}), + scaled(Vec2f{11.8311f, 5.2636f}), + scaled(Vec2f{11.8308f, 10.3636f}), + scaled(Vec2f{22.3830f, 10.3636f}), + scaled(Vec2f{23.6845f, 9.0642f}), + scaled(Vec2f{23.6832f, 1.1630f}), + scaled(Vec2f{23.2825f, 1.1616f}), + scaled(Vec2f{21.0149f, 1.1616f}), + scaled(Vec2f{21.1308f, 1.3625f}), + scaled(Vec2f{20.9315f, 1.7080f}), + scaled(Vec2f{20.5326f, 1.7080f}), + scaled(Vec2f{20.3334f, 1.3629f}), + scaled(Vec2f{20.4493f, 1.1616f}) + } + }, + } +}; + +static void check_nfp(const std::string & outfile_prefix, + const Slic3r::Polygons &stationary, + const Slic3r::Polygons &orbiter, + const Slic3r::ExPolygons &bedpoly, + const Slic3r::ExPolygons &nfp) +{ + using namespace Slic3r; + + auto stationary_ex = to_expolygons(stationary); + auto bedbb = get_extents(bedpoly); + bedbb.offset(scaled(1.)); + auto bedrect = arr2::to_rectangle(bedbb); + + ExPolygons bed_negative = diff_ex(bedrect, bedpoly); + ExPolygons orb_ex_r = to_expolygons(orbiter); + ExPolygons orb_ex_r_ch = {ExPolygon(Geometry::convex_hull(orb_ex_r))}; + auto orb_ex_offs_pos_r = offset_ex(orb_ex_r, SCALED_EPSILON); + auto orb_ex_offs_neg_r = offset_ex(orb_ex_r, -SCALED_EPSILON); + auto orb_ex_offs_pos_r_ch = offset_ex(orb_ex_r_ch, SCALED_EPSILON); + auto orb_ex_offs_neg_r_ch = offset_ex(orb_ex_r_ch, -SCALED_EPSILON); + + auto bedpoly_offs = offset_ex(bedpoly, SCALED_EPSILON); + + auto check_at_nfppos = [&](const Point &pos) { + ExPolygons orb_ex = orb_ex_r; + Point d = pos - reference_vertex(orbiter); + for (ExPolygon &poly : orb_ex) + poly.translate(d); + + bool touching = false; + bool check_failed = false; + + bool within_bed = false; + bool touches_fixed = false; + bool touches_bedwall = false; + + try { + auto beddiff = diff_ex(orb_ex, bedpoly_offs); + if (beddiff.empty()) + within_bed = true; + + auto orb_ex_offs_pos = orb_ex_offs_pos_r; + for (ExPolygon &poly: orb_ex_offs_pos) + poly.translate(d); + + auto orb_ex_offs_neg = orb_ex_offs_neg_r; + for (ExPolygon &poly: orb_ex_offs_neg) + poly.translate(d); + + auto orb_ex_offs_pos_ch = orb_ex_offs_pos_r_ch; + for (ExPolygon &poly: orb_ex_offs_pos_ch) + poly.translate(d); + + auto orb_ex_offs_neg_ch = orb_ex_offs_neg_r_ch; + for (ExPolygon &poly: orb_ex_offs_neg_ch) + poly.translate(d); + + if (!touches_bedwall) { + auto inters_pos = intersection_ex(bed_negative, orb_ex_offs_pos_ch); + auto inters_neg = intersection_ex(bed_negative, orb_ex_offs_neg_ch); + if (!inters_pos.empty() && inters_neg.empty()) + touches_bedwall = true; + } + + if (!touches_fixed) { + auto inters_pos = intersection_ex(stationary_ex, orb_ex_offs_pos); + auto inters_neg = intersection_ex(stationary_ex, orb_ex_offs_neg); + if (!inters_pos.empty() && inters_neg.empty()) + touches_fixed = true; + } + + touching = within_bed && (touches_fixed || touches_bedwall); + } catch (...) { + check_failed = true; + touching = false; + } + +#ifndef NDEBUG + if (!touching || check_failed) { + + auto bb = get_extents(bedpoly); + SVG svg(outfile_prefix + ".svg", bb, 0, true); + svg.draw(orbiter, "orange"); + svg.draw(stationary, "yellow"); + svg.draw(bed_negative, "blue", 0.5f); + svg.draw(nfp, "green", 0.5f); + svg.draw(orb_ex, "red"); + + svg.Close(); + } +#endif + REQUIRE(!check_failed); + REQUIRE(touching); + }; + + if (nfp.empty()) { + auto bb = get_extents(bedpoly); + SVG svg(outfile_prefix + ".svg", bb, 0, true); + svg.draw(orbiter, "orange"); + svg.draw(stationary, "yellow"); + svg.draw(bedpoly, "blue", 0.5f); + + svg.Close(); + } + + REQUIRE(!nfp.empty()); + + for (const ExPolygon &nfp_part : nfp) { + for (const Point &nfp_pos : nfp_part.contour) { + check_at_nfppos(nfp_pos); + } + for (const Polygon &h : nfp_part.holes) + for (const Point &nfp_pos : h) { + check_at_nfppos(nfp_pos); + } + } +} + +template +void test_itempairs(const std::vector &testdata, + const Bed &bed, + const std::string &outfile_prefix = "") +{ + using namespace Slic3r; + + size_t testnum = 0; + + ExPolygons bedshape = arr2::to_expolygons(bed); + + for(auto td : testdata) { + Polygons orbiter = td.orbiter.envelope().transformed_outline(); + Polygons stationary = td.stationary.shape().transformed_outline(); + Point center = bounding_box(bed).center(); + Point stat_c = get_extents(stationary).center(); + Point d = center - stat_c; + arr2::translate(td.stationary, d); + stationary = td.stationary.shape().transformed_outline(); + + std::array, 1> fixed = {{td.stationary}}; + auto nfp = arr2::calculate_nfp(td.orbiter, arr2::default_context(fixed), bed); + + check_nfp(outfile_prefix + "nfp_test_" + std::to_string(testnum), + stationary, + orbiter, + bedshape, + nfp); + + testnum++; + } +} + +template +static void foreach_combo(const Slic3r::Range &range, const Fn &fn) +{ + std::vector pairs(range.size(), false); + + assert(range.size() >= 2); + pairs[range.size() - 1] = true; + pairs[range.size() - 2] = true; + + do { + std::vector::value_type> items; + for (size_t i = 0; i < pairs.size(); i++) { + if (pairs[i]) { + auto it = range.begin(); + std::advance(it, i); + items.emplace_back(*it); + } + } + fn (items[0], items[1]); + } while (std::next_permutation(pairs.begin(), pairs.end())); +} + +TEST_CASE("Static type tests for arrange items", "[arrange2]") +{ + using namespace Slic3r; + + REQUIRE(arr2::IsDataStore); + REQUIRE(arr2::IsWritableItem); + + REQUIRE(! arr2::IsDataStore); + REQUIRE(arr2::IsWritableItem); + + REQUIRE(arr2::IsDataStore); + REQUIRE(arr2::IsWritableItem); +} + +template Bed init_bed() { return {}; } +template<> inline Slic3r::arr2::InfiniteBed init_bed() +{ + return Slic3r::arr2::InfiniteBed{{scaled(250.) / 2., scaled(210.) / 2.}}; +} + +template<> inline Slic3r::arr2::RectangleBed init_bed() +{ + return Slic3r::arr2::RectangleBed{scaled(500.), scaled(500.)}; +} + +template<> inline Slic3r::arr2::CircleBed init_bed() +{ + return Slic3r::arr2::CircleBed{Slic3r::Point::Zero(), scaled(300.)}; +} + +template<> inline Slic3r::arr2::IrregularBed init_bed() +{ + using namespace Slic3r; + BoundingBox bb_outer{Point::Zero(), {scaled(500.), scaled(500.)}}; + BoundingBox corner{Point::Zero(), {scaled(50.), scaled(50.)}}; + + auto transl = [](BoundingBox bb, Point t) { bb.translate(t); return bb; }; + + Polygons rect_outer = {arr2::to_rectangle(bb_outer)}; + Polygons corners = {arr2::to_rectangle(transl(corner, {scaled(10.), scaled(10.)})), + arr2::to_rectangle(transl(corner, {scaled(440.), scaled(10.)})), + arr2::to_rectangle(transl(corner, {scaled(440.), scaled(440.)})), + arr2::to_rectangle(transl(corner, {scaled(10.), scaled(440.)})), + arr2::to_rectangle(BoundingBox({scaled(80.), scaled(450.)}, {scaled(420.), scaled(510.)})), + arr2::to_rectangle(BoundingBox({scaled(80.), scaled(-10.)}, {scaled(420.), scaled(50.)}))}; + + ExPolygons bedshape = diff_ex(rect_outer, corners); + + return arr2::IrregularBed{bedshape}; +} + +template std::string bedtype_str(const Bed &bed) +{ + return ""; +} + +inline std::string bedtype_str(const Slic3r::arr2::RectangleBed &bed) +{ + return "RectangleBed"; +} + +inline std::string bedtype_str(const Slic3r::arr2::CircleBed &bed) +{ + return "CircleBed"; +} + +inline std::string bedtype_str(const Slic3r::arr2::InfiniteBed &bed) +{ + return "InfiniteBed"; +} + +inline std::string bedtype_str(const Slic3r::arr2::IrregularBed &bed) +{ + return "IrregularBed"; +} + +TEST_CASE("NFP should be empty if item cannot fit into bed", "[arrange2]") { + using namespace Slic3r; + + arr2::RectangleBed bed{scaled(10.), scaled(10.)}; + ExPolygons bedshape = arr2::to_expolygons(bed); + + for(auto& td : nfp_testdata) { + REQUIRE(&(td.orbiter.envelope()) == &(td.orbiter.shape())); + REQUIRE(&(td.stationary.envelope()) == &(td.stationary.shape())); + REQUIRE(td.orbiter.envelope().reference_vertex() == + td.orbiter.shape().reference_vertex()); + REQUIRE(td.stationary.envelope().reference_vertex() == + td.stationary.shape().reference_vertex()); + + ArrangeItem cpy = td.stationary; + REQUIRE(&(cpy.envelope()) == &(cpy.shape())); + REQUIRE(cpy.envelope().reference_vertex() == + cpy.shape().reference_vertex()); + + std::array, 1> fixed = + {{td.stationary}}; + + auto nfp = arr2::calculate_nfp(td.orbiter, default_context(fixed), bed); + + REQUIRE(nfp.empty()); + } +} + +#include +#include + +TEMPLATE_TEST_CASE("NFP algorithm test", + "[arrange2]", + Slic3r::arr2::InfiniteBed, + Slic3r::arr2::RectangleBed, + Slic3r::arr2::CircleBed, + Slic3r::arr2::IrregularBed) +{ + using namespace Slic3r; + + auto bed = init_bed(); + std::string bedtypestr = bedtype_str(bed); + + SECTION("Predefined simple polygons for debugging") { + test_itempairs(nfp_testdata, bed, bedtypestr + "_"); + } + + SECTION("All combinations of convex prusa parts without inflation") { + auto parts = prusa_parts(); + + std::vector testdata; + foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){ + testdata.emplace_back(ItemPair{i1, i2}); + }); + + test_itempairs(testdata, bed, bedtypestr + "_prusacombos"); + } + + SECTION("All combinations of prusa parts with random inflation") { + std::random_device rd; + auto seed = rd(); + std::mt19937 rng{seed}; + std::uniform_real_distribution distr{0., 50.}; + + INFO ("Seed = " << seed); + + auto parts = prusa_parts(distr(rng)); + + std::vector testdata; + foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){ + testdata.emplace_back(ItemPair{i1, i2}); + }); + + test_itempairs(testdata, bed, bedtypestr + "_prusacombos_infl"); + } + + SECTION("All combinations of concave-holed prusa parts without inflation") + { + auto parts = prusa_parts_ex(); + for (ArrangeItem &itm : parts) { + itm.set_envelope(arr2::DecomposedShape{itm.shape().convex_hull()}); + } + + std::vector testdata; + foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){ + testdata.emplace_back(ItemPair{i1, i2}); + }); + + test_itempairs(testdata, bed, bedtypestr + "_prusacombos_ex"); + } + + SECTION("All combinations of concave-holed prusa parts with inflation") { + std::random_device rd; + auto seed = rd(); + std::mt19937 rng{seed}; + std::uniform_real_distribution distr{0., 50.}; + + INFO ("Seed = " << seed); + + auto parts = prusa_parts_ex(distr(rng)); + for (ArrangeItem &itm : parts) { + itm.set_envelope(arr2::DecomposedShape{itm.shape().convex_hull()}); + } + + std::vector testdata; + foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){ + testdata.emplace_back(ItemPair{i1, i2}); + }); + + test_itempairs(testdata, bed, bedtypestr + "_prusacombos_ex_infl"); + } +} + +TEST_CASE("EdgeCache tests", "[arrange2]") { + using namespace Slic3r; + + SECTION ("Empty polygon should produce empty edge-cache") { + ExPolygon emptypoly; + + arr2::EdgeCache ep {&emptypoly}; + + std::vector samples; + ep.sample_contour(1., samples); + REQUIRE(samples.empty()); + } + + SECTION ("Single edge polygon should be considered as 2 lines") { + ExPolygon poly{scaled(Vec2f{0.f, 0.f}), scaled(Vec2f{10., 10.})}; + + arr2::EdgeCache ep{&poly}; + std::vector samples; + + double accuracy = 1.; + ep.sample_contour(accuracy, samples); + + REQUIRE(samples.size() == 2); + REQUIRE(ep.coords(samples[0]) == poly.contour[1]); + REQUIRE(ep.coords(samples[1]) == poly.contour[0]); + REQUIRE(ep.coords({0, 0.}) == ep.coords({0, 1.})); + } + + SECTION ("Test address range") { + // Single edge on the int range boundary + ExPolygon poly{scaled(Vec2f{-2000.f, 0.f}), scaled(Vec2f{2000.f, 0.f})}; + + arr2::EdgeCache ep{&poly}; + REQUIRE(ep.coords({0, 0.25}) == Vec2crd{0, 0}); + REQUIRE(ep.coords({0, 0.75}) == Vec2crd{0, 0}); + + // Multiple edges on the int range boundary + ExPolygon squ{arr2::to_rectangle(scaled(BoundingBoxf{{0., 0.}, {2000., 2000.}}))}; + + arr2::EdgeCache ep2{&squ}; + REQUIRE(ep2.coords({0, 0.}) == Vec2crd{0, 0}); + REQUIRE(ep2.coords({0, 0.25}) == Vec2crd{2000000000, 0}); + REQUIRE(ep2.coords({0, 0.5}) == Vec2crd{2000000000, 2000000000}); + REQUIRE(ep2.coords({0, 0.75}) == Vec2crd{0, 2000000000}); + REQUIRE(ep2.coords({0, 1.}) == Vec2crd{0, 0}); + } + + SECTION("Accuracy argument should skip corners correctly") { + ExPolygon poly{arr2::to_rectangle(scaled(BoundingBoxf{{0., 0.}, {10., 10.}}))}; + + double accuracy = 1.; + arr2::EdgeCache ep{&poly}; + std::vector samples; + ep.sample_contour(accuracy, samples); + REQUIRE(samples.size() == poly.contour.size()); + for (size_t i = 0; i < samples.size(); ++i) { + auto &cr = samples[i]; + REQUIRE(ep.coords(cr) == poly.contour.points[(i + 1) % poly.contour.size()]); + } + + accuracy = 0.; + arr2::EdgeCache ep0{&poly}; + samples.clear(); + ep0.sample_contour(accuracy, samples); + REQUIRE(samples.size() == 1); + REQUIRE(ep0.coords(samples[0]) == poly.contour.points[1]); + } +} + +// Mock packing strategy that places N items to the center of the +// bed bounding box, if the bed is larger than the item. +template +struct RectangleToCenterPackStrategy { static constexpr int Capacity = Cap; }; + +namespace Slic3r { namespace arr2 { +struct RectangleToCenterPackTag {}; + +template struct PackStrategyTag_> { + using Tag = RectangleToCenterPackTag; +}; + +// Dummy arrangeitem that is a rectangle +struct RectangleItem { + int bed_index = Unarranged; + BoundingBox shape = {{0, 0}, scaled(Vec2d{10., 10.})}; + Vec2crd translation = {0, 0}; + double rotation = 0; + + int priority = 0; + int packed_num = 0; + + void set_bed_index(int idx) { bed_index = idx; } + int get_bed_index() const noexcept { return bed_index; } + + void set_translation(const Vec2crd &tr) { translation = tr; } + const Vec2crd & get_translation() const noexcept { return translation; } + + void set_rotation(double r) { rotation = r; } + double get_rotation() const noexcept { return rotation; } + + int get_priority() const noexcept { return priority; } +}; + +template +bool pack(Strategy &&strategy, + const Bed &bed, + RectangleItem &item, + const PackStrategyContext &packing_context, + const Range &remaining_items, + const RectangleToCenterPackTag &) +{ + bool ret = false; + + auto bedbb = bounding_box(bed); + auto itmbb = item.shape; + + Vec2crd tr = bedbb.center() - itmbb.center(); + itmbb.translate(tr); + + auto fixed_items = all_items_range(packing_context); + + if (fixed_items.size() < Slic3r::StripCVRef::Capacity && + bedbb.contains(itmbb)) + { + translate(item, tr); + ret = true; + } + + return ret; +} + +}} // namespace Slic3r::arr2 + +using Slic3r::arr2::RectangleItem; + +TEST_CASE("First fit selection strategy", "[arrange2]") +{ + using ArrItem = RectangleItem; + using Cmp = Slic3r::arr2::firstfit::DefaultItemCompareFn; + + auto create_items_n = [](size_t count) { + INFO ("Item count = " << count); + + auto items = Slic3r::reserve_vector(count); + std::generate_n(std::back_inserter(items), count, [] { return ArrItem{}; }); + + return items; + }; + + auto bed = Slic3r::arr2::RectangleBed(scaled(100.), scaled(100.)); + + GIVEN("A packing strategy that does not accept any items") + { + using PackStrategy = RectangleToCenterPackStrategy<0>; + + int on_arrange_call_count = 0; + auto on_arranged_fn = [&on_arrange_call_count](ArrItem &itm, + auto &bed, auto &packed, + auto &rem) { + ++on_arrange_call_count; + + Slic3r::arr2::firstfit::DefaultOnArrangedFn{}(itm, bed, packed, rem); + }; + + int cancel_call_count = 0; + auto stop_cond = [&cancel_call_count] { + ++cancel_call_count; + return false; + }; + + WHEN ("attempting to pack a single item with a valid bed index") + { + auto items = create_items_n(1); + + Slic3r::arr2::set_bed_index(items.front(), random_value(0, 1000)); + + auto sel = Slic3r::arr2::firstfit::SelectionStrategy{Cmp{}, on_arranged_fn, stop_cond}; + + Slic3r::arr2::arrange(sel, + PackStrategy{}, + Slic3r::range(items), + bed); + + THEN ("the original bed index should be ignored and set to Unarranged") + { + REQUIRE(Slic3r::arr2::get_bed_index(items.front()) == + Slic3r::arr2::Unarranged); + } + + THEN("the arrange callback should not be called") + { + REQUIRE(on_arrange_call_count == 0); + } + + THEN("the stop condition should be called at least once") + { + REQUIRE(cancel_call_count > 0); + } + } + + WHEN("attempting to pack arbitrary number > 1 of items into the bed") + { + auto items = create_items_n(random_value(1, 100)); + + CHECK(cancel_call_count == 0); + CHECK(on_arrange_call_count == 0); + + Slic3r::arr2::arrange( + Slic3r::arr2::firstfit::SelectionStrategy{Cmp{}, on_arranged_fn, stop_cond}, + PackStrategy{}, Slic3r::range(items), bed); + + THEN("The item should be left unpacked") + { + REQUIRE(std::all_of(items.begin(), items.end(), [](auto &itm) { + return !Slic3r::arr2::is_arranged(itm); + })); + } + + THEN("the arrange callback should not be called") + { + REQUIRE(on_arrange_call_count == 0); + } + + THEN("the stop condition should be called at least once for each item") + { + INFO("items count = " << items.size()); + REQUIRE(cancel_call_count >= static_cast(items.size())); + } + } + } + + GIVEN("A pack strategy that accepts only a single item") + { + using PackStrategy = RectangleToCenterPackStrategy<1>; + + WHEN ("attempting to pack a single item with a valid bed index") + { + auto items = create_items_n(1); + + Slic3r::arr2::set_bed_index(items.front(), random_value(0, 1000)); + + Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy<>{}, + PackStrategy{}, + Slic3r::range(items), + bed); + + THEN ("the original bed index should be ignored and set to zero") + { + REQUIRE(Slic3r::arr2::get_bed_index(items.front()) == 0); + } + } + + WHEN("attempting to pack arbitrary number > 1 of items into bed") + { + auto items = create_items_n(random_value(1, 100)); + + Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy<>{}, + PackStrategy{}, + Slic3r::range(items), + bed); + + THEN ("The number of beds created should match the number of items") + { + auto bed_count = Slic3r::arr2::get_bed_count(Slic3r::range(items)); + + REQUIRE(bed_count == items.size()); + } + + THEN("All items should reside on their respective beds") + { + for (size_t i = 0; i < items.size(); ++i) + REQUIRE(Slic3r::arr2::get_bed_index(items[i]) == static_cast(i)); + } + } + } + + GIVEN ("Two packed beds with an unpacked bed between them") + { + using PackStrategy = RectangleToCenterPackStrategy<1>; + + auto bed = Slic3r::arr2::RectangleBed(scaled(100.), scaled(100.)); + auto fixed = create_items_n(2); + std::for_each(fixed.begin(), fixed.end(), [&bed](auto &itm){ + Slic3r::arr2::pack(PackStrategy{}, bed, itm); + }); + for (auto [i, idx] : {std::make_pair(0, 0), std::make_pair(1, 2)}) + Slic3r::arr2::set_bed_index(fixed[i], idx); + + WHEN("attempting to pack a single item") + { + auto items = create_items_n(1); + + Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy<>{}, + PackStrategy{}, + Slic3r::range(items), + Slic3r::crange(fixed), + bed); + + THEN("the item should end up on the first free bed") + { + REQUIRE(Slic3r::arr2::get_bed_index(items.front()) == 1); + } + } + } + + GIVEN ("A 100 items with increasing priorities and a packer that accepts 20 items") + { + static constexpr int Capacity = 20; + static constexpr int Count = 5 * Capacity; + + using PackStrategy = RectangleToCenterPackStrategy; + auto items = create_items_n(Count); + + for (size_t i = 0; i < items.size(); ++i) + items[i].priority = static_cast(i); + + WHEN("attempting to pack all items") + { + auto on_arranged_fn = [](ArrItem &itm, auto &bed, auto &packed, + auto &rem) { + itm.packed_num = packed.size(); + Slic3r::arr2::firstfit::DefaultOnArrangedFn{}(itm, bed, packed, rem); + }; + + Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy{Cmp{}, on_arranged_fn}, + PackStrategy{}, + Slic3r::range(items), + bed); + + THEN("all items should fit onto the beds from index 0 to 4") + { + REQUIRE(std::all_of(items.begin(), items.end(), [](auto &itm) { + auto bed_idx = Slic3r::arr2::get_bed_index(itm); + return bed_idx >= 0 && bed_idx < Count / Capacity; + })); + } + + // Highest priority goes first + THEN("all the items should be packed in reverse order of their priority value") + { + REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) { + return itm.packed_num == (99 - itm.priority); + })); + } + } + } +} + +template<> +Slic3r::BoundingBox Slic3r::arr2::NFPArrangeItemTraits_< + RectangleItem>::envelope_bounding_box(const RectangleItem &itm) +{ + return itm.shape; +} + +template<> +Slic3r::Vec2crd Slic3r::arr2::NFPArrangeItemTraits_< + RectangleItem>::reference_vertex(const RectangleItem &itm) +{ + return itm.shape.center(); +} + +TEST_CASE("Optimal nfp position search with GravityKernel using RectangleItem and InfiniteBed", + "[arrange2]") +{ + auto bed = Slic3r::arr2::InfiniteBed{}; + auto strategy = Slic3r::arr2::PackStrategyNFP{Slic3r::arr2::GravityKernel{bed.center}}; + + GIVEN("An nfp made of a single point coincident with the bed center") + { + WHEN ("searching for optimal position") + { + THEN ("the optimum should be at the single nfp point") + { + Slic3r::ExPolygons nfp; + nfp.emplace_back(Slic3r::ExPolygon{{bed.center}}); + + auto item = RectangleItem{}; + + double score = pick_best_spot_on_nfp_verts_only(item, nfp, bed, strategy); + + Slic3r::Vec2crd D = bed.center - item.shape.center(); + REQUIRE(item.translation == D); + REQUIRE(score == Approx(0.)); + } + } + } +} + +TEST_CASE("RectangleOverfitPackingStrategy test", "[arrange2]") +{ + using Slic3r::arr2::RectangleOverfitPackingStrategy; + using Slic3r::arr2::PackStrategyNFP; + using Slic3r::arr2::GravityKernel; + using Slic3r::arr2::get_bed_index; + + namespace firstfit = Slic3r::arr2::firstfit; + + using ArrItem = Slic3r::arr2::SimpleArrangeItem; + + auto frontleft_align_fn = [](const Slic3r::BoundingBox &bedbb, + const Slic3r::BoundingBox &pilebb) { + return bedbb.min - pilebb.min; + }; + + RectangleOverfitPackingStrategy pstrategy{PackStrategyNFP{GravityKernel{}}, + frontleft_align_fn}; + + auto bed = Slic3r::arr2::RectangleBed{scaled(100.), scaled(100.)}; + auto item_blueprint = Slic3r::arr2::to_rectangle( + Slic3r::BoundingBox{{0, 0}, {scaled(20.), scaled(20.)}}); + + auto item_gen_fn = [&item_blueprint] { return ArrItem{item_blueprint}; }; + + GIVEN("One empty logical rectangular 100x100 mm bed ") { + + WHEN("attempting to pack one rectangle") { + constexpr auto count = size_t{1}; + auto items = Slic3r::reserve_vector(count); + + std::generate_n(std::back_inserter(items), count, item_gen_fn); + + Slic3r::arr2::arrange(firstfit::SelectionStrategy<>{}, pstrategy, + Slic3r::range(items), bed); + + THEN ("Overfit kernel should take over and align the single item") { + auto pilebb = bounding_box(Slic3r::range(items)); + + Slic3r::Vec2crd D = frontleft_align_fn(bounding_box(bed), pilebb); + REQUIRE(D.squaredNorm() == 0); + } + } + + WHEN("attempting to pack two rectangles") { + + constexpr auto count = size_t{2}; + auto items = Slic3r::reserve_vector(count); + + std::generate_n(std::back_inserter(items), count, item_gen_fn); + + Slic3r::arr2::arrange(firstfit::SelectionStrategy<>{}, pstrategy, + Slic3r::range(items), bed); + + THEN("Overfit kernel should take over and align the single item") + { + auto pilebb = bounding_box(Slic3r::range(items)); + + Slic3r::Vec2crd D = frontleft_align_fn(bounding_box(bed), pilebb); + REQUIRE(D.squaredNorm() == 0); + } + } + } + + GIVEN("Two logical rectangular beds, the second having fixed items") { + + auto fixed_item_bb = Slic3r::BoundingBox{{0, 0}, {scaled(20.), scaled(20.)}}; + std::vector fixed = { + ArrItem{Slic3r::arr2::to_rectangle(fixed_item_bb)}}; + + Slic3r::arr2::set_bed_index(fixed.front(), 1); + + WHEN("attempting to pack 3 rectangles, 1 filling the first bed") { + + auto items = Slic3r::reserve_vector(3); + + // Add a big rectangle this will fill the first bed so that + // smaller rectangles will fit only into the next bed + items.emplace_back(ArrItem{Slic3r::arr2::to_rectangle( + Slic3r::BoundingBox{{0, 0}, {scaled(90.), scaled(90.)}})}); + + std::generate_n(std::back_inserter(items), 2, item_gen_fn); + + Slic3r::arr2::arrange(firstfit::SelectionStrategy<>{}, pstrategy, + Slic3r::range(items), Slic3r::crange(fixed), + bed); + + THEN("Overfit kernel should handle the 0th bed and gravity kernel handles the 1st bed") + { + REQUIRE(get_bed_index(items.front()) == 0); + + auto pilebb = bounding_box_on_bedidx(Slic3r::range(items), 0); + Slic3r::Vec2crd D = frontleft_align_fn(bounding_box(bed), pilebb); + REQUIRE(D.squaredNorm() == 0); + + REQUIRE((get_bed_index(items[1]) == get_bed_index(items[2]) == 1)); + + auto pilebb1 = bounding_box_on_bedidx(Slic3r::range(items), 1); + REQUIRE(pilebb1.overlap(fixed_item_bb)); + + Slic3r::Vec2crd D1 = frontleft_align_fn(bounding_box(bed), pilebb1); + REQUIRE(D1.squaredNorm() != 0); + } + } + } +} + +TEMPLATE_TEST_CASE("Test if allowed item rotations are considered", "[arrange2]", + Slic3r::arr2::ArrangeItem) +{ + using ArrItem = TestType; + + auto item_blueprint = Slic3r::arr2::to_rectangle( + Slic3r::BoundingBox{{0, 0}, {scaled(20.), scaled(20.)}}); + + ArrItem itm{item_blueprint}; + + auto bed = Slic3r::arr2::RectangleBed{scaled(100.), scaled(100.)}; + + set_allowed_rotations(itm, {PI}); + + Slic3r::arr2::PackStrategyNFP strategy{Slic3r::arr2::GravityKernel{}}; + + bool packed = pack(strategy, bed, itm); + + REQUIRE(packed); + REQUIRE(get_rotation(itm) == Approx(PI)); +} + +//TEST_CASE("NFP optimizing test", "[arrange2]") { +// using namespace Slic3r; + +// auto itemshape = arr2::to_rectangle(BoundingBox{{scaled(-25.), scaled(-25.)}, {scaled(25.), scaled(25.)}}); + +// arr2::ArrangeItem item{arr2::DecomposedShape{itemshape}}; + +// ExPolygons nfp = { ExPolygon {{scaled(-2000.), scaled(25.)}, {scaled(2000.), scaled(25.)}} }; + +// struct K : public arr2::GravityKernel { +// size_t &fncnt; +// K(size_t &counter, Vec2crd gpos): arr2::GravityKernel{gpos}, fncnt{counter} {} +// double placement_fitness(const arr2::ArrangeItem &itm, const Vec2crd &tr) const +// { +// ++fncnt; +// return arr2::GravityKernel::placement_fitness(itm, tr); +// } +// }; + +// size_t counter = 0; +// K k{counter, Vec2crd{0, 0}}; +// opt::Optimizer solver_ref({}, 1000); +// opt::Optimizer solver (opt::StopCriteria{} +// .max_iterations(1000) +// /*.rel_score_diff(1e-20)*/); + +// double accuracy = 1.; +// arr2::PackStrategyNFP strategy_ref(solver_ref, k, ex_seq, accuracy); +// arr2::PackStrategyNFP strategy(solver, k, ex_seq, accuracy); + +// SVG svg("nfp_optimizing.svg"); +// svg.flipY = true; +// svg.draw_outline(nfp, "green"); + +// svg.draw_outline(item.shape().transformed_outline(), "yellow"); + +// double score = pick_best_spot_on_nfp(item, nfp, arr2::InfiniteBed{}, strategy); +// svg.draw_outline(item.shape().transformed_outline(), "red"); + +// counter = 0; +// double score_ref = pick_best_spot_on_nfp(item, nfp, arr2::InfiniteBed{}, strategy_ref); +// svg.draw_outline(item.shape().transformed_outline(), "blue"); + +//#ifndef NDEBUG +// std::cout << "fitness called: " << k.fncnt << " times" << std::endl; +// std::cout << "score = " << score << " score_ref = " << score_ref << std::endl; +//#endif + +// REQUIRE(!std::isnan(score)); +// REQUIRE(!std::isnan(score_ref)); +// REQUIRE(score >= score_ref); +//} + + diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp new file mode 100644 index 0000000000..a7c9275600 --- /dev/null +++ b/tests/arrange/test_arrange_integration.cpp @@ -0,0 +1,1036 @@ +#include +#include "test_utils.hpp" + +#include +#include +#include + +#include + +#include "libslic3r/Model.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Format/3mf.hpp" +#include "libslic3r/ModelArrange.hpp" + +static Slic3r::Model get_example_model_with_20mm_cube() +{ + using namespace Slic3r; + + Model model; + + ModelObject* new_object = model.add_object(); + new_object->name = "20mm_cube"; + new_object->add_instance(); + TriangleMesh mesh = make_cube(20., 20., 20.); + mesh.translate(Vec3f{-10.f, -10.f, 0.}); + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + return model; +} + +[[maybe_unused]] +static Slic3r::Model get_example_model_with_random_cube_objects(size_t N = 0) +{ + using namespace Slic3r; + + Model model; + + auto cube_count = N == 0 ? random_value(size_t(1), size_t(100)) : N; + + INFO("Cube count " << cube_count); + + ModelObject* new_object = model.add_object(); + new_object->name = "20mm_cube"; + TriangleMesh mesh = make_cube(20., 20., 20.); + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + for (size_t i = 0; i < cube_count; ++i) { + ModelInstance *inst = new_object->add_instance(); + arr2::transform_instance(*inst, + Vec2d{random_value(-200., 200.), + random_value(-200., 200.)}, + random_value(0., 2 * PI)); + } + + return model; +} + +static Slic3r::Model get_example_model_with_arranged_primitives() +{ + using namespace Slic3r; + + Model model; + + ModelObject* new_object = model.add_object(); + new_object->name = "20mm_cube"; + ModelInstance *cube_inst = new_object->add_instance(); + TriangleMesh mesh = make_cube(20., 20., 20.); + mesh.translate(Vec3f{-10.f, -10.f, 0.}); + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + ModelInstance *inst = new_object->add_instance(*cube_inst); + auto tr = inst->get_matrix(); + tr.translate(Vec3d{25., 0., 0.}); + inst->set_transformation(Geometry::Transformation{tr}); + + new_object = model.add_object(); + new_object->name = "20mm_cyl"; + new_object->add_instance(); + mesh = make_cylinder(10., 20.); + mesh.translate(Vec3f{0., -25.f, 0.}); + new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + new_object = model.add_object(); + new_object->name = "20mm_sphere"; + new_object->add_instance(); + mesh = make_sphere(10.); + mesh.translate(Vec3f{25., -25.f, 0.}); + new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + return model; +} + + +class RandomArrangeSettings: public Slic3r::arr2::ArrangeSettingsView { + Slic3r::arr2::ArrangeSettingsDb::Values m_v; + + std::mt19937 m_rng; +public: + explicit RandomArrangeSettings(int seed) : m_rng(seed) + { + std::uniform_real_distribution fdist(0., 100.f); + std::uniform_int_distribution<> bdist(0, 1); + std::uniform_int_distribution<> dist; + m_v.d_obj = fdist(m_rng); + m_v.d_bed = fdist(m_rng); + m_v.rotations = bdist(m_rng); + m_v.geom_handling = static_cast(dist(m_rng) % ghCount); + m_v.arr_strategy = static_cast(dist(m_rng) % asCount); + m_v.xl_align = static_cast(dist(m_rng) % xlpCount); + } + explicit RandomArrangeSettings() : m_rng(std::random_device{} ()) {} + + float get_distance_from_objects() const override { return m_v.d_obj; } + float get_distance_from_bed() const override { return m_v.d_bed; } + bool is_rotation_enabled() const override { return m_v.rotations; } + XLPivots get_xl_alignment() const override { return m_v.xl_align; } + GeometryHandling get_geometry_handling() const override { return m_v.geom_handling; } + ArrangeStrategy get_arrange_strategy() const override { return m_v.arr_strategy; } +}; + + +TEST_CASE("ModelInstance should be retrievable when imbued into ArrangeItem", + "[arrange2][integration]") +{ + using namespace Slic3r; + + Model model = get_example_model_with_20mm_cube(); + auto mi = model.objects.front()->instances.front(); + + arr2::ArrangeItem itm; + arr2::PhysicalOnlyVBedHandler vbedh; + auto vbedh_ptr = static_cast(&vbedh); + auto arrbl = arr2::ArrangeableModelInstance{mi, vbedh_ptr, nullptr, {0, 0}}; + arr2::imbue_id(itm, arrbl.id()); + + std::optional id_returned = arr2::retrieve_id(itm); + + REQUIRE((id_returned && *id_returned == mi->id())); +} + +struct PhysicalBed +{ + Slic3r::arr2::InfiniteBed bed; + Slic3r::arr2::PhysicalOnlyVBedHandler vbedh; + int bed_idx_min = 0, bed_idx_max = 0; +}; + +struct XStriderBed +{ + Slic3r::arr2::RectangleBed bed; + Slic3r::arr2::XStriderVBedHandler vbedh; + int bed_idx_min = 0, bed_idx_max = 100; + + XStriderBed() : + bed{Slic3r::scaled(250.), Slic3r::scaled(210.)}, + vbedh{bounding_box(bed), bounding_box(bed).size().x() / 10} {} +}; + +TEMPLATE_TEST_CASE("Writing arrange transformations into ModelInstance should be correct", + "[arrange2][integration]", + PhysicalBed, + XStriderBed) +{ + auto [tx, ty, rot] = GENERATE(map( + [](int i) { + return std::make_tuple(-500. + i * 20., -500. + i * 20., + -PI + i * (2 * PI / 100.)); + }, + range(0, 100))); + + using namespace Slic3r; + + Model model = get_example_model_with_20mm_cube(); + + auto transl = scaled(Vec2d(tx, ty)); + + INFO("Translation = : " << transl.transpose()); + INFO("Rotation is: " << rot * 180 / PI); + + auto mi = model.objects.front()->instances.front(); + + BoundingBox bb_before = scaled(to_2d(arr2::instance_bounding_box(*mi))); + + TestType bed_case; + auto bed_index = random_value(bed_case.bed_idx_min, bed_case.bed_idx_max); + + bed_case.vbedh.assign_bed(arr2::VBedPlaceableMI{*mi}, bed_index); + INFO("bed_index = " << bed_index); + + auto builder = arr2::SceneBuilder{} + .set_bed(bed_case.bed) + .set_model(model) + .set_arrange_settings(arr2::ArrangeSettings{}.set_distance_from_objects(0.)) + .set_virtual_bed_handler(&bed_case.vbedh); + + arr2::Scene scene{std::move(builder)}; + + using ArrItem = arr2::ArrangeItem; + + auto cvt = arr2::ArrangeableToItemConverter::create(scene); + + ArrItem itm; + scene.model().visit_arrangeable(model.objects.front()->instances.front()->id(), + [&cvt, &itm](const arr2::Arrangeable &arrbl){ + itm = cvt->convert(arrbl); + }); + + BoundingBox bb_itm_before = arr2::fixed_bounding_box(itm); + REQUIRE((bb_itm_before.min - bb_before.min).norm() < SCALED_EPSILON); + REQUIRE((bb_itm_before.max - bb_before.max).norm() < SCALED_EPSILON); + + arr2::rotate(itm, rot); + arr2::translate(itm, transl); + arr2::set_bed_index(itm, arr2::PhysicalBedId); + + if (auto id = retrieve_id(itm)) { + scene.model().visit_arrangeable(*id, [&itm](arr2::Arrangeable &arrbl) { + arrbl.transform(unscaled(get_translation(itm)), get_rotation(itm)); + }); + } + + auto phys_tr = bed_case.vbedh.get_physical_bed_trafo(bed_index); + auto outline = arr2::extract_convex_outline(*mi, phys_tr); + BoundingBox bb_after = get_extents(outline); + BoundingBox bb_itm_after = arr2::fixed_bounding_box(itm); + REQUIRE((bb_itm_after.min - bb_after.min).norm() < 2 * SCALED_EPSILON); + REQUIRE((bb_itm_after.max - bb_after.max).norm() < 2 * SCALED_EPSILON); +} + +struct OutlineExtractorConvex { + auto operator() (const Slic3r::ModelInstance *mi) + { + return Slic3r::arr2::extract_convex_outline(*mi); + } +}; + +struct OutlineExtractorFull { + auto operator() (const Slic3r::ModelInstance *mi) + { + return Slic3r::arr2::extract_full_outline(*mi); + } +}; + +TEMPLATE_TEST_CASE("Outline extraction from ModelInstance", + "[arrange2][integration]", + OutlineExtractorConvex, + OutlineExtractorFull) +{ + using namespace Slic3r; + using OutlineExtractor = TestType; + + Model model = get_example_model_with_20mm_cube(); + + ModelInstance *mi = model.objects.front()->instances.front(); + auto matrix = mi->get_matrix(); + matrix.scale(Vec3d{random_value(0.1, 5.), + random_value(0.1, 5.), + random_value(0.1, 5.)}); + + matrix.rotate(Eigen::AngleAxisd(random_value(-PI, PI), Vec3d::UnitZ())); + + matrix.translate(Vec3d{random_value(-100., 100.), + random_value(-100., 100.), + random_value(0., 100.)}); + + mi->set_transformation(Geometry::Transformation{matrix}); + + GIVEN("An empty ModelInstance without mesh") + { + const ModelInstance *mi = model.add_object()->add_instance(); + + WHEN("the outline is generated") { + auto outline = OutlineExtractor{}(mi); + + THEN ("the outline is empty") { + REQUIRE(outline.empty()); + } + } + } + + GIVEN("A simple cube as outline") { + const ModelInstance *mi = model.objects.front()->instances.front(); + + WHEN("the outline is generated") { + auto outline = OutlineExtractor{}(mi); + + THEN("the 2D ortho projection of the model bounding box is the " + "same as the outline's bb") + { + auto bb = unscaled(get_extents(outline)); + auto modelbb = to_2d(model.bounding_box_exact()); + + REQUIRE((bb.min - modelbb.min).norm() < EPSILON); + REQUIRE((bb.max - modelbb.max).norm() < EPSILON); + } + } + } +} + +template +auto create_vbed_handler(const Slic3r::BoundingBox &bedbb, coord_t gap) +{ + return VBH{}; +} + +template<> +auto create_vbed_handler(const Slic3r::BoundingBox &bedbb, coord_t gap) +{ + return Slic3r::arr2::PhysicalOnlyVBedHandler{}; +} + +template<> +auto create_vbed_handler(const Slic3r::BoundingBox &bedbb, coord_t gap) +{ + return Slic3r::arr2::XStriderVBedHandler{bedbb, gap}; +} + +template<> +auto create_vbed_handler(const Slic3r::BoundingBox &bedbb, coord_t gap) +{ + return Slic3r::arr2::YStriderVBedHandler{bedbb, gap}; +} + +TEMPLATE_TEST_CASE("Common virtual bed handlers", "[arrange2][integration][vbeds]", + Slic3r::arr2::PhysicalOnlyVBedHandler, + Slic3r::arr2::XStriderVBedHandler, + Slic3r::arr2::YStriderVBedHandler) +{ + using namespace Slic3r; + using VBP = arr2::VBedPlaceableMI; + + Model model = get_example_model_with_20mm_cube(); + + const auto bedsize = Vec2d{random_value(21., 500.), random_value(21., 500.)}; + + const Vec2crd bed_displace = {random_value(scaled(-100.), scaled(100.)), + random_value(scaled(-100.), scaled(100.))}; + + const BoundingBox bedbb{bed_displace, scaled(bedsize) + bed_displace}; + + INFO("Bed boundaries bedbb = { {" << unscaled(bedbb.min).transpose() << "}, {" + << unscaled(bedbb.max).transpose() << "} }" ); + + auto modelbb = model.bounding_box_exact(); + + // Center the single instance within the model + arr2::transform_instance(*model.objects.front()->instances.front(), + unscaled(bedbb.center()) - to_2d(modelbb.center()), + 0.); + + const auto vbed_gap = GENERATE(0, random_value(1, scaled(100.))); + + INFO("vbed_gap = " << unscaled(vbed_gap)); + + std::unique_ptr vbedh = std::make_unique( + create_vbed_handler(bedbb, vbed_gap)); + + GIVEN("A ModelInstance on the physical bed") + { + ModelInstance *mi = model.objects.front()->instances.front(); + + WHEN ("trying to move the item to an invalid bed index") + { + auto &mi_to_move = *model.objects.front()->add_instance(*mi); + Transform3d mi_trafo_before = mi_to_move.get_matrix(); + bool was_accepted = vbedh->assign_bed(VBP{mi_to_move}, arr2::Unarranged); + + Transform3d mi_trafo_after = mi_to_move.get_matrix(); + + THEN("the model instance should be unchanged") { + REQUIRE(!was_accepted); + REQUIRE(mi_trafo_before.isApprox(mi_trafo_after)); + } + } + } + + GIVEN("A ModelInstance being assigned to a virtual bed") + { + ModelInstance *mi = model.objects.front()->instances.front(); + + auto bedidx_to = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000)); + INFO("bed index = " << bedidx_to); + + auto &mi_to_move = *model.objects.front()->add_instance(*mi); + + // Move model instance to the given virtual bed + bool was_accepted = vbedh->assign_bed(VBP{mi_to_move}, bedidx_to); + + WHEN ("querying the virtual bed index of this item") + { + int bedidx_on = vbedh->get_bed_index(VBP{mi_to_move}); + + THEN("should actually be on that bed, or the assign should be discarded") { + REQUIRE(((!was_accepted) || (bedidx_to == bedidx_on))); + } + + THEN("assigning the same bed index again should produce the same result") + { + auto &mi_to_move_cpy = *model.objects.front()->add_instance(mi_to_move); + bool was_accepted_rep = vbedh->assign_bed(VBP{mi_to_move_cpy}, bedidx_to); + int bedidx_on_rep = vbedh->get_bed_index(VBP{mi_to_move_cpy}); + + REQUIRE(was_accepted_rep == was_accepted); + REQUIRE(((!was_accepted_rep) || (bedidx_to == bedidx_on_rep))); + } + } + + WHEN ("moving back to the physical bed") + { + auto &mi_back_to_phys = *model.objects.front()->add_instance(mi_to_move); + bool moved_back_to_physical = vbedh->assign_bed(VBP{mi_back_to_phys}, 0); + + THEN("model instance should actually move back to the physical bed") + { + REQUIRE(moved_back_to_physical); + int bedidx_mi2 = vbedh->get_bed_index(VBP{mi_back_to_phys}); + REQUIRE(bedidx_mi2 == 0); + } + + THEN("the bounding box should be inside bed") + { + auto bbf = arr2::instance_bounding_box(mi_back_to_phys); + auto bb = BoundingBox{scaled(to_2d(bbf))}; + INFO("bb = { {" << unscaled(bb.min).transpose() << "}, {" + << unscaled(bb.max).transpose() << "} }" ); + + REQUIRE(bedbb.contains(bb)); + } + } + + WHEN("extracting transformed model instance bounding box using the " + "physical bed trafo") + { + int from_bed_idx = vbedh->get_bed_index(VBP{mi_to_move}); + auto physical_bed_trafo = vbedh->get_physical_bed_trafo(from_bed_idx); + + auto &mi_back_to_phys = *model.objects.front()->add_instance(mi_to_move); + mi_back_to_phys.set_transformation(Geometry::Transformation{ + physical_bed_trafo * mi_back_to_phys.get_matrix()}); + + auto bbf = arr2::instance_bounding_box(mi_back_to_phys); + + auto bb = BoundingBox{scaled(to_2d(bbf))}; + + THEN("the bounding box should be inside bed") + { + INFO("bb = { {" << unscaled(bb.min).transpose() << "}, {" + << unscaled(bb.max).transpose() << "} }" ); + + REQUIRE(bedbb.contains(bb)); + } + + THEN("the outline should be inside the physical bed") + { + auto outline = arr2::extract_convex_outline(mi_to_move, + physical_bed_trafo); + auto bb = get_extents(outline); + INFO("bb = { {" << bb.min.transpose() << "}, {" + << bb.max.transpose() << "} }" ); + + REQUIRE(bedbb.contains(bb)); + } + } + } +} + +TEST_CASE("Virtual bed handlers - StriderVBedHandler", "[arrange2][integration][vbeds]") +{ + using namespace Slic3r; + using VBP = arr2::VBedPlaceableMI; + + Model model = get_example_model_with_20mm_cube(); + + static const Vec2d bedsize{250., 210.}; + static const BoundingBox bedbb{{0, 0}, scaled(bedsize)}; + static const auto modelbb = model.bounding_box_exact(); + + GIVEN("An instance of StriderVBedHandler with a stride of the bed width" + " and random non-negative gap") + { + auto [instance_pos, instance_displace] = GENERATE(table({ + {"start", unscaled(bedbb.min) - to_2d(modelbb.min) + Vec2d::Ones() * EPSILON}, // at the min edge of vbed + {"middle", unscaled(bedbb.center()) - to_2d(modelbb.center())}, // at the center + {"end", unscaled(bedbb.max) - to_2d(modelbb.max) - Vec2d::Ones() * EPSILON} // at the max edge of vbed + })); + + // Center the single instance within the model + arr2::transform_instance(*model.objects.front()->instances.front(), + instance_displace, + 0.); + + INFO("Instance pos at " << instance_pos << " of bed"); + + coord_t gap = GENERATE(0, random_value(1, scaled(100.))); + + INFO("Gap is " << unscaled(gap)); + + arr2::XStriderVBedHandler vbh{bedbb, gap}; + + WHEN("a model instance is on the Nth virtual bed (spatially)") + { + ModelInstance *mi = model.objects.front()->instances.front(); + auto &mi_to_move = *model.objects.front()->add_instance(*mi); + + auto bed_index = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000)); + INFO("N is " << bed_index); + + double bed_disp = bed_index * unscaled(vbh.stride_scaled()); + arr2::transform_instance(mi_to_move, Vec2d{bed_disp, 0.}, 0.); + + THEN("the bed index of this model instance should be max(0, N)") + { + REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == bed_index); + } + + THEN("the physical trafo should move the instance back to bed 0") + { + auto tr = vbh.get_physical_bed_trafo(bed_index); + mi_to_move.set_transformation(Geometry::Transformation{tr * mi_to_move.get_matrix()}); + REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == 0); + + auto instbb = BoundingBox{scaled(to_2d(arr2::instance_bounding_box(mi_to_move)))}; + INFO("bedbb = { {" << bedbb.min.transpose() << "}, {" << bedbb.max.transpose() << "} }" ); + INFO("instbb = { {" << instbb.min.transpose() << "}, {" << instbb.max.transpose() << "} }" ); + + REQUIRE(bedbb.contains(instbb)); + } + } + + WHEN("a model instance is on the physical bed") + { + ModelInstance *mi = model.objects.front()->instances.front(); + auto &mi_to_move = *model.objects.front()->add_instance(*mi); + + THEN("assigning the model instance to the Nth bed will move it N*stride in the X axis") + { + auto bed_index = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000)); + INFO("N is " << bed_index); + + if (vbh.assign_bed(VBP{mi_to_move}, bed_index)) + REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == bed_index); + else + REQUIRE(bed_index < 0); + + auto tr = vbh.get_physical_bed_trafo(bed_index); + auto ref_pos = tr * Vec3d::Zero(); + + auto displace = bed_index * (unscaled(vbh.stride_scaled())); + REQUIRE(ref_pos.x() == Approx(-displace)); + + auto ref_pos_mi = mi_to_move.get_matrix() * Vec3d::Zero(); + REQUIRE(ref_pos_mi.x() == Approx(instance_displace.x() + (bed_index >= 0) * displace)); + } + } + } + + GIVEN("An instance of StriderVBedHandler with a stride of the bed width" + " and a 100mm gap") + { + coord_t gap = scaled(100.); + + arr2::XStriderVBedHandler vbh{bedbb, gap}; + + WHEN("a model instance is within the gap on the Nth virtual bed") + { + ModelInstance *mi = model.objects.front()->instances.front(); + auto &mi_to_move = *model.objects.front()->add_instance(*mi); + + auto bed_index = GENERATE(random_value(-1000, -1), 0, random_value(1, 1000)); + INFO("N is " << bed_index); + + auto bed_disp = Vec2d{bed_index * unscaled(vbh.stride_scaled()), 0.}; + auto instbb_before = to_2d(arr2::instance_bounding_box(mi_to_move)); + + auto transl_to_bed_end = Vec2d{bed_disp + unscaled(bedbb.max) + - instbb_before.min + Vec2d::Ones() * EPSILON}; + + arr2::transform_instance(mi_to_move, + transl_to_bed_end + Vec2d{unscaled(gap / 2), 0.}, + 0.); + + THEN("the model instance should reside on the Nth logical bed but " + "outside of the bed boundaries") + { + REQUIRE(vbh.get_bed_index(VBP{mi_to_move}) == bed_index); + + auto instbb = BoundingBox{scaled(to_2d(arr2::instance_bounding_box(mi_to_move)))}; + INFO("bedbb = { {" << bedbb.min.transpose() << "}, {" << bedbb.max.transpose() << "} }" ); + INFO("instbb = { {" << instbb.min.transpose() << "}, {" << instbb.max.transpose() << "} }" ); + + REQUIRE(! bedbb.contains(instbb)); + } + } + } +} + +TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes", + "[arrange2][integration][bedfilling]", + Slic3r::arr2::ArrangeItem) +{ + using namespace Slic3r; + using ArrItem = TestType; + + std::string basepath = TEST_DATA_DIR PATH_SEPARATOR; + + DynamicPrintConfig cfg; + cfg.load_from_ini(basepath + "default_fff.ini", + ForwardCompatibilitySubstitutionRule::Enable); + cfg.set_key_value("bed_shape", + new ConfigOptionPoints( + {{0., 0.}, {100., 0.}, {100., 100.}, {0, 100.}})); + + Model m; + + ModelObject* new_object = m.add_object(); + new_object->name = "10mm_box"; + new_object->add_instance(); + TriangleMesh mesh = make_cube(10., 10., 10.); + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + store_3mf("fillbed_10mm.3mf", &m, &cfg, false); + + arr2::ArrangeSettings settings; + settings.values().d_obj = 0.; + settings.values().d_bed = 0.; + + arr2::FixedSelection sel({{true}}); + + arr2::Scene scene{arr2::SceneBuilder{} + .set_model(m) + .set_arrange_settings(settings) + .set_selection(&sel) + .set_bed(cfg)}; + + auto task = arr2::FillBedTask::create(scene); + auto result = task->process_native(arr2::DummyCtl{}); + result->apply_on(scene.model()); + + store_3mf("fillbed_10mm_result.3mf", &m, &cfg, false); + + Points bedpts = get_bed_shape(cfg); + arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); + + REQUIRE(bed.which() == 1); // Rectangle bed + + auto bedbb = unscaled(bounding_box(bed)); + auto bedbbsz = bedbb.size(); + + REQUIRE(m.objects.size() == 1); + REQUIRE(m.objects.front()->instances.size() == + bedbbsz.x() * bedbbsz.y() / 100); + + REQUIRE(task->unselected.empty()); + REQUIRE(result->to_add.size() + result->arranged_items.size() == arr2::model_instance_count(m)); + + REQUIRE( + std::all_of(task->selected.begin(), task->selected.end(), [](auto &itm) { + return arr2::get_bed_index(itm) == 0; + })); + REQUIRE( + std::all_of(result->to_add.begin(), result->to_add.end(), [](auto &itm) { + return arr2::get_bed_index(itm) == 0; + })); +} + +template +static void foreach_combo(const Slic3r::Range &range, const Fn &fn) +{ + std::vector pairs(range.size(), false); + + assert(range.size() >= 2); + pairs[range.size() - 1] = true; + pairs[range.size() - 2] = true; + + do { + std::vector::value_type> items; + for (size_t i = 0; i < pairs.size(); i++) { + if (pairs[i]) { + auto it = range.begin(); + std::advance(it, i); + items.emplace_back(*it); + } + } + fn (items[0], items[1]); + } while (std::next_permutation(pairs.begin(), pairs.end())); +} + +TEST_CASE("Testing minimum area bounding box rotation on simple cubes", "[arrange2][integration]") +{ + using namespace Slic3r; + + BoundingBox bb{Point::Zero(), scaled(Vec2d(10., 10.))}; + Polygon sh = arr2::to_rectangle(bb); + + auto prot = random_value(0., 2 * PI); + sh.translate(Vec2crd{random_value(-scaled(10.), scaled(10.)), + random_value(-scaled(10.), scaled(10.))}); + sh.rotate(prot); + + INFO("box item is rotated by: " << prot << " rads"); + + arr2::ArrangeItem itm{sh}; + arr2::rotate(itm, random_value(0., 2 * PI)); + + double rot = arr2::get_min_area_bounding_box_rotation(itm); + + arr2::translate(itm, + Vec2crd{random_value(-scaled(10.), scaled(10.)), + random_value(-scaled(10.), scaled(10.))}); + arr2::rotate(itm, rot); + + auto itmbb = arr2::fixed_bounding_box(itm); + REQUIRE(std::abs(itmbb.size().norm() - bb.size().norm()) < + SCALED_EPSILON * SCALED_EPSILON); +} + +template +bool is_collision_free(const Slic3r::Range &item_range) +{ + using namespace Slic3r; + + bool collision_free = true; + foreach_combo(item_range, [&collision_free](auto &itm1, auto &itm2) { + auto outline1 = offset(arr2::fixed_outline(itm1), -SCALED_EPSILON); + auto outline2 = offset(arr2::fixed_outline(itm2), -SCALED_EPSILON); + + auto inters = intersection(outline1, outline2); + collision_free = collision_free && inters.empty(); + }); + + return collision_free; +} + +TEST_CASE("Testing a simple arrange on cubes", "[arrange2][integration]") +{ + using namespace Slic3r; + + Model model = get_example_model_with_random_cube_objects(size_t{10}); + + arr2::ArrangeSettings settings; + settings.set_rotation_enabled(true); + + auto bed = arr2::RectangleBed{scaled(250.), scaled(210.)}; + + arr2::Scene scene{arr2::SceneBuilder{} + .set_model(model) + .set_arrange_settings(settings) + .set_bed(bed)}; + + auto task = arr2::ArrangeTask::create(scene); + + REQUIRE(task->printable.selected.size() == arr2::model_instance_count(model)); + + auto result = task->process_native(arr2::DummyCtl{}); + + REQUIRE(result); + + REQUIRE(result->items.size() == task->printable.selected.size()); + + bool applied = result->apply_on(scene.model()); + + REQUIRE(applied); + + REQUIRE(std::all_of(result->items.begin(), + result->items.end(), + [](auto &item) { return arr2::is_arranged(item); })); + + REQUIRE(std::all_of(task->printable.selected.begin(), task->printable.selected.end(), + [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); })); + + REQUIRE(std::all_of(task->unprintable.selected.begin(), task->unprintable.selected.end(), + [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); })); + + REQUIRE(is_collision_free(range(task->printable.selected))); + +} + +bool settings_eq(const Slic3r::arr2::ArrangeSettingsView &v1, + const Slic3r::arr2::ArrangeSettingsView &v2) +{ + return v1.is_rotation_enabled() == v2.is_rotation_enabled() && + v1.get_arrange_strategy() == v2.get_arrange_strategy() && + v1.get_distance_from_bed() == Approx(v2.get_distance_from_bed()) && + v1.get_distance_from_objects() == Approx(v2.get_distance_from_objects()) && + v1.get_geometry_handling() == v2.get_geometry_handling() && + v1.get_xl_alignment() == v2.get_xl_alignment(); + ; +} + +namespace Slic3r { namespace arr2 { + +class MocWT: public ArrangeableWipeTowerBase { +public: + using ArrangeableWipeTowerBase::ArrangeableWipeTowerBase; +}; + +class MocWTH : public WipeTowerHandler { + std::function m_sel_pred; + ObjectID m_id; + +public: + MocWTH(const ObjectID &id) : m_id{id} {} + + void visit(std::function fn) override + { + MocWT wt{m_id, Polygon{}, Point::Zero(), 0., m_sel_pred}; + fn(wt); + } + void visit(std::function fn) const override + { + MocWT wt{m_id, Polygon{}, Point::Zero(), 0., m_sel_pred}; + fn(wt); + } + void set_selection_predicate(std::function pred) override + { + m_sel_pred = std::move(pred); + } +}; + +}} // namespace Slic3r::arr2 + +TEST_CASE("Test SceneBuilder", "[arrange2][integration]") +{ + using namespace Slic3r; + + GIVEN("An empty SceneBuilder") + { + arr2::SceneBuilder bld; + + WHEN("building an ArrangeScene from it") + { + arr2::Scene scene{std::move(bld)}; + + THEN("The scene should still be initialized consistently with empty model") + { + // This would segfault if model_wt isn't initialized properly + REQUIRE(scene.model().arrangeable_count() == 0); + REQUIRE(settings_eq(scene.settings(), arr2::ArrangeSettings{})); + REQUIRE(scene.selected_ids().empty()); + } + + THEN("The associated bed should be an instance of InfiniteBed") + { + scene.visit_bed([](auto &bed){ + REQUIRE(std::is_convertible_v); + }); + } + } + + WHEN("pushing random settings into the builder") + { + RandomArrangeSettings settings; + auto bld2 = arr2::SceneBuilder{}.set_arrange_settings(&settings); + arr2::Scene scene{std::move(bld)}; + + THEN("settings of the resulting scene should be equal") + { + REQUIRE(settings_eq(scene.settings(), settings)); + } + } + } + + GIVEN("An existing instance of the class Model") + { + auto N = random_value(1, 20); + Model model = get_example_model_with_random_cube_objects(N); + INFO("model object count " << N); + + WHEN("a scene is built from a builder that holds a reference to an existing model") + { + arr2::Scene scene{arr2::SceneBuilder{}.set_model(&model)}; + + THEN("the model of the constructed scene should have the same number of arrangeables") { + REQUIRE(scene.model().arrangeable_count() == arr2::model_instance_count(model)); + } + } + } + + GIVEN("An instance of DynamicPrintConfig with rectangular bed") + { + std::string basepath = TEST_DATA_DIR PATH_SEPARATOR; + + DynamicPrintConfig cfg; + cfg.load_from_ini(basepath + "default_fff.ini", + ForwardCompatibilitySubstitutionRule::Enable); + + WHEN("a scene is built with a bed initialized from this DynamicPrintConfig") + { + arr2::Scene scene(arr2::SceneBuilder{}.set_bed(cfg)); + + auto bedbb = bounding_box(get_bed_shape(cfg)); + + THEN("the bed should be a rectangular bed with the same dimensions as the bed points") + { + scene.visit_bed([&bedbb, &scene](auto &bed) { + constexpr bool is_rect = std::is_convertible_v< + decltype(bed), arr2::RectangleBed>; + + REQUIRE(is_rect); + + if constexpr (is_rect) { + bedbb.offset(scaled(scene.settings().get_distance_from_objects() / 2.)); + REQUIRE(bedbb.size().x() == bed.width()); + REQUIRE(bedbb.size().y() == bed.height()); + } + }); + } + } + } + + GIVEN("A wipe tower handler that uses the builder's selection mask") + { + arr2::SceneBuilder bld; + Model mdl; + bld.set_model(mdl); + bld.set_wipe_tower_handler(std::make_unique(mdl.wipe_tower.id())); + + WHEN("the selection mask is initialized as a fallback default in the created scene") + { + arr2::Scene scene{std::move(bld)}; + + THEN("the wipe tower should use the fallback selmask (created after set_wipe_tower)") + { + // Should be the wipe tower + REQUIRE(scene.model().arrangeable_count() == 1); + + bool wt_selected = false; + scene.model() + .visit_arrangeable(mdl.wipe_tower.id(), + [&wt_selected]( + const arr2::Arrangeable &arrbl) { + wt_selected = arrbl.is_selected(); + }); + + REQUIRE(wt_selected); + } + } + } +} + +TEST_CASE("Testing duplicate function to really duplicate the whole Model", + "[arrange2][integration]") +{ + using namespace Slic3r; + + Model model = get_example_model_with_arranged_primitives(); + + store_3mf("dupl_example.3mf", &model, nullptr, false); + + size_t instcnt = arr2::model_instance_count(model); + + size_t copies_num = random_value(1, 10); + + INFO("Copies: " << copies_num); + + auto bed = arr2::InfiniteBed{}; + arr2::ArrangeSettings settings; + settings.set_arrange_strategy(arr2::ArrangeSettings::asPullToCenter); + arr2::DuplicableModel dup_model{&model, arr2::VirtualBedHandler::create(bed), bounding_box(bed)}; + + arr2::Scene scene{arr2::BasicSceneBuilder{} + .set_arrangeable_model(&dup_model) + .set_arrange_settings(&settings) + .set_bed(bed)}; + + auto task = arr2::MultiplySelectionTask::create(scene, copies_num); + auto result = task->process_native(arr2::DummyCtl{}); + bool applied = result->apply_on(scene.model()); + if (applied) { + dup_model.apply_duplicates(); + store_3mf("dupl_example_result.3mf", &model, nullptr, false); + REQUIRE(applied); + } + + size_t new_instcnt = arr2::model_instance_count(model); + + REQUIRE(new_instcnt == (copies_num + 1) * instcnt); + + REQUIRE(std::all_of(result->arranged_items.begin(), + result->arranged_items.end(), + [](auto &item) { return arr2::is_arranged(item); })); + + REQUIRE(std::all_of(result->to_add.begin(), + result->to_add.end(), + [](auto &item) { return arr2::is_arranged(item); })); + + REQUIRE(std::all_of(task->selected.begin(), task->selected.end(), + [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); })); + + REQUIRE(is_collision_free(range(task->selected))); +} + +// TODO: +//TEST_CASE("Testing fit-into-bed rotation search", "[arrange2][integration]") +//{ +// using namespace Slic3r; + +// Model model; + +// ModelObject* new_object = model.add_object(); +// new_object->name = "big_cube"; +// new_object->add_instance(); +// TriangleMesh mesh = make_cube(205., 220., 10.); +// mesh.rotate_z(15 * PI / 180); + +// ModelVolume* new_volume = new_object->add_volume(mesh); +// new_volume->name = new_object->name; + +// store_3mf("rotfail.3mf", &model, nullptr, false); + +// arr2::RectangleBed bed{scaled(250.), scaled(210.)}; + +// arr2::Scene scene{ +// arr2::SceneBuilder{} +// .set_bed(bed) +// .set_model(model) +// .set_arrange_settings(arr2::ArrangeSettings{} +// .set_distance_from_objects(0.) +// .set_rotation_enabled(true) +// ) +// }; + +// auto task = arr2::ArrangeTask::create(scene); +// auto result = task->process_native(arr2::DummyCtl{}); + +// REQUIRE(result->items.size() == 1); +// REQUIRE(arr2::get_rotation(result->items.front()) > 0.); +// REQUIRE(arr2::is_arranged(result->items.front())); +//} + diff --git a/tests/data/default_fff.ini b/tests/data/default_fff.ini new file mode 100644 index 0000000000..1b7cfa6a2d --- /dev/null +++ b/tests/data/default_fff.ini @@ -0,0 +1,313 @@ +; generated by PrusaSlicer 2.6.0-beta2 on 2023-05-30 at 07:06:06 UTC + +autoemit_temperature_commands = 1 +avoid_crossing_curled_overhangs = 0 +avoid_crossing_perimeters = 0 +avoid_crossing_perimeters_max_detour = 0 +bed_custom_model = +bed_custom_texture = +bed_shape = 0x0,250x0,250x210,0x210 +bed_temperature = 110 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]\n\n +between_objects_gcode = +bottom_fill_pattern = monotonic +bottom_solid_layers = 4 +bottom_solid_min_thickness = 0.5 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_fan_speed = 25 +bridge_flow_ratio = 0.95 +bridge_speed = 25 +brim_separation = 0.1 +brim_type = outer_only +brim_width = 0 +color_change_gcode = M600\nG1 E0.4 F1500 ; prime after color change +colorprint_heights = +compatible_printers_condition_cummulative = "printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4";"nozzle_diameter[0]!=0.8 and printer_model!=\"MINI\" and printer_notes!~/.*PG.*/ and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)" +complete_objects = 0 +cooling = 1 +cooling_tube_length = 5 +cooling_tube_retraction = 91.5 +default_acceleration = 1000 +default_filament_profile = "Prusament PLA" +default_print_profile = 0.15mm QUALITY @MK3 +deretract_speed = 0 +disable_fan_first_layers = 4 +dont_support_bridges = 0 +draft_shield = disabled +duplicate_distance = 6 +elefant_foot_compensation = 0.2 +enable_dynamic_fan_speeds = 0 +enable_dynamic_overhang_speeds = 1 +end_filament_gcode = "; Filament-specific end gcode" +end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y200 F3600 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM84 ; disable motors\n; max_layer_z = [max_layer_z] +external_perimeter_acceleration = 0 +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 25 +external_perimeters_first = 0 +extra_loading_move = -2 +extra_perimeters = 0 +extra_perimeters_on_overhangs = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 45 +extruder_colour = "" +extruder_offset = 0x0 +extrusion_axis = E +extrusion_multiplier = 1 +extrusion_width = 0.45 +fan_always_on = 0 +fan_below_layer_time = 30 +filament_colour = #FFF2EC +filament_cooling_final_speed = 3.4 +filament_cooling_initial_speed = 2.2 +filament_cooling_moves = 4 +filament_cost = 27.82 +filament_density = 1.04 +filament_deretract_speed = nil +filament_diameter = 1.75 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_volumetric_speed = 11 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "" +filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_retract_before_travel = nil +filament_retract_before_wipe = nil +filament_retract_layer_change = nil +filament_retract_length = nil +filament_retract_lift = nil +filament_retract_lift_above = nil +filament_retract_lift_below = nil +filament_retract_restart_extra = nil +filament_retract_speed = nil +filament_settings_id = "Generic ABS" +filament_soluble = 0 +filament_spool_weight = 0 +filament_toolchange_delay = 0 +filament_type = ABS +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_vendor = Generic +filament_wipe = nil +fill_angle = 45 +fill_density = 15% +fill_pattern = gyroid +first_layer_acceleration = 800 +first_layer_acceleration_over_raft = 0 +first_layer_bed_temperature = 100 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 20 +first_layer_speed_over_raft = 30 +first_layer_temperature = 255 +full_fan_speed_layer = 0 +fuzzy_skin = none +fuzzy_skin_point_dist = 0.8 +fuzzy_skin_thickness = 0.3 +gap_fill_enabled = 1 +gap_fill_speed = 40 +gcode_comments = 0 +gcode_flavor = marlin +gcode_label_objects = 1 +gcode_resolution = 0.0125 +gcode_substitutions = +high_current_on_filament_swap = 0 +host_type = prusalink +idle_temperature = nil +infill_acceleration = 1000 +infill_anchor = 2.5 +infill_anchor_max = 12 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_overlap = 10% +infill_speed = 80 +interface_shells = 0 +ironing = 0 +ironing_flowrate = 15% +ironing_spacing = 0.1 +ironing_speed = 15 +ironing_type = top +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +layer_height = 0.2 +machine_limits_usage = emit_to_gcode +machine_max_acceleration_e = 5000,5000 +machine_max_acceleration_extruding = 1250,1250 +machine_max_acceleration_retracting = 1250,1250 +machine_max_acceleration_travel = 1500,1250 +machine_max_acceleration_x = 1000,960 +machine_max_acceleration_y = 1000,960 +machine_max_acceleration_z = 200,200 +machine_max_feedrate_e = 120,120 +machine_max_feedrate_x = 200,100 +machine_max_feedrate_y = 200,100 +machine_max_feedrate_z = 12,12 +machine_max_jerk_e = 4.5,4.5 +machine_max_jerk_x = 8,8 +machine_max_jerk_y = 8,8 +machine_max_jerk_z = 0.4,0.4 +machine_min_extruding_rate = 0,0 +machine_min_travel_rate = 0,0 +max_fan_speed = 15 +max_layer_height = 0.25 +max_print_height = 210 +max_print_speed = 200 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_bead_width = 85% +min_fan_speed = 15 +min_feature_size = 25% +min_layer_height = 0.07 +min_print_speed = 15 +min_skirt_length = 4 +mmu_segmented_region_max_width = 0 +notes = +nozzle_diameter = 0.4 +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = {input_filename_base}_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode +overhang_fan_speed_0 = 0 +overhang_fan_speed_1 = 0 +overhang_fan_speed_2 = 0 +overhang_fan_speed_3 = 0 +overhang_speed_0 = 15 +overhang_speed_1 = 15 +overhang_speed_2 = 20 +overhang_speed_3 = 25 +overhangs = 1 +parking_pos_retraction = 92 +pause_print_gcode = M601 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_generator = arachne +perimeter_speed = 45 +perimeters = 2 +physical_printer_settings_id = +post_process = +print_settings_id = 0.20mm QUALITY @MK3 +printer_model = MK3 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n +printer_settings_id = Original Prusa i3 MK3 +printer_technology = FFF +printer_variant = 0.4 +printer_vendor = +raft_contact_distance = 0.2 +raft_expansion = 1.5 +raft_first_layer_density = 90% +raft_first_layer_expansion = 3 +raft_layers = 0 +remaining_times = 1 +resolution = 0 +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 4 +retract_lift = 0.4 +retract_lift_above = 0 +retract_lift_below = 209 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +seam_position = aligned +silent_mode = 1 +single_extruder_multi_material = 0 +single_extruder_multi_material_priming = 0 +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +slice_closing_radius = 0.049 +slicing_mode = regular +slowdown_below_layer_time = 20 +small_perimeter_speed = 25 +solid_infill_acceleration = 0 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 80 +spiral_vase = 0 +staggered_inner_seams = 0 +standby_temperature_delta = -5 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.02{else}0.04{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K12{elsif nozzle_diameter[0]==0.8};{else}M900 K20{endif} ; Filament gcode LA 1.0" +start_gcode = M862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +support_material = 0 +support_material_angle = 0 +support_material_auto = 1 +support_material_bottom_contact_distance = 0 +support_material_bottom_interface_layers = 0 +support_material_buildplate_only = 0 +support_material_closing_radius = 2 +support_material_contact_distance = 0.2 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_pattern = rectilinear +support_material_interface_spacing = 0.2 +support_material_interface_speed = 80% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_style = grid +support_material_synchronize_layers = 0 +support_material_threshold = 50 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +support_tree_angle = 40 +support_tree_angle_slow = 25 +support_tree_branch_diameter = 2 +support_tree_branch_diameter_angle = 5 +support_tree_branch_diameter_double_wall = 3 +support_tree_branch_distance = 1 +support_tree_tip_diameter = 0.8 +support_tree_top_rate = 15% +temperature = 255 +template_custom_gcode = +thick_bridges = 0 +thin_walls = 0 +threads = 24 +thumbnails = 160x120 +thumbnails_format = PNG +toolchange_gcode = +top_fill_pattern = monotoniclines +top_infill_extrusion_width = 0.4 +top_solid_infill_acceleration = 0 +top_solid_infill_speed = 40 +top_solid_layers = 5 +top_solid_min_thickness = 0.7 +travel_acceleration = 0 +travel_speed = 180 +travel_speed_z = 12 +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wall_distribution_count = 1 +wall_transition_angle = 10 +wall_transition_filter_deviation = 25% +wall_transition_length = 100% +wipe = 1 +wipe_into_infill = 0 +wipe_into_objects = 0 +wipe_tower = 1 +wipe_tower_bridging = 10 +wipe_tower_brim_width = 2 +wipe_tower_cone_angle = 0 +wipe_tower_extra_spacing = 100% +wipe_tower_no_sparse_layers = 0 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 170 +wipe_tower_y = 125 +wiping_volumes_extruders = 70,70 +wiping_volumes_matrix = 0 +xy_size_compensation = 0 +z_offset = 0 diff --git a/tests/data/prusaparts.cpp b/tests/data/prusaparts.cpp new file mode 100644 index 0000000000..72d5c7d9bc --- /dev/null +++ b/tests/data/prusaparts.cpp @@ -0,0 +1,5981 @@ +#include "prusaparts.hpp" + +const TestData PRUSA_PART_POLYGONS = +{ + { + {-5000000, 8954050}, + {5000000, 8954050}, + {5000000, -45949}, + {4972609, -568550}, + {3500000, -8954050}, + {-3500000, -8954050}, + {-4972609, -568550}, + {-5000000, -45949}, + {-5000000, 8954050}, + }, + { + {-63750000, -8000000}, + {-54750000, 46000000}, + {50750000, 46000000}, + {63750000, 33000000}, + {63750000, -46000000}, + {-54750000, -46000000}, + {-63750000, -28000000}, + {-63750000, -8000000}, + }, + { + {-52750000, 41512348}, + {-31250000, 45987651}, + {52750000, 45987651}, + {52750000, -45987651}, + {-52750000, -45987651}, + {-52750000, 41512348}, + }, + { + {-3900000, 14000000}, + {-2167950, 14000000}, + {1721454, 7263400}, + {3828529, 3613790}, + {3838809, 3582149}, + {3871560, 3270569}, + {3900000, 3000000}, + {3500000, -3000000}, + {3471560, -3270565}, + {3447549, -3498986}, + {3292510, -3976167}, + {3099999, -4512949}, + {2530129, -5500000}, + {807565, -8483570}, + {-2377349, -14000000}, + {-3900000, -14000000}, + {-3900000, 14000000}, + }, + { + {-31750000, -1000000}, + {-25250000, 40500000}, + {-18250000, 47500000}, + {10750000, 47500000}, + {16750000, 41500000}, + {31750000, -37000000}, + {31750000, -43857898}, + {28107900, -47500000}, + {18392099, -47500000}, + {-20750000, -46500000}, + {-31750000, -4000000}, + {-31750000, -1000000}, + }, + { + {-34625000, -14265399}, + {-10924999, 24875000}, + {33325000, 24875000}, + {37575000, 20625000}, + {37575000, 17625000}, + {26575000, -24875000}, + {-8924999, -24875000}, + {-34625000, -24484600}, + {-37575000, -19375000}, + {-34625000, -14265399}, + }, + { + {-14000000, 9000000}, + {-11000000, 17000000}, + {14000000, 17000000}, + {14000000, -17000000}, + {-11000000, -17000000}, + {-14000000, -8000000}, + {-14000000, 9000000}, + }, + { + {-5300000, 2227401}, + {-237800, 5150001}, + {5299999, 5150001}, + {5299999, 650001}, + {4699999, -5149997}, + {-5300000, -5149997}, + {-5300000, 2227401}, + }, + { + {-12000000, 18000000}, + {12000000, 18000000}, + {12000000, -18000000}, + {-12000000, -18000000}, + {-12000000, 18000000}, + }, + { + {-18000000, -1000000}, + {-15000000, 22000000}, + {-11000000, 26000000}, + {11000000, 26000000}, + {15000000, 22000000}, + {18000000, -1000000}, + {18000000, -26000000}, + {-18000000, -26000000}, + {-18000000, -1000000}, + }, + { + {-77500000, 30000000}, + {-72500000, 35000000}, + {72500000, 35000000}, + {77500000, 30000000}, + {77500000, -32928901}, + {75428901, -35000000}, + {-75428901, -35000000}, + {-77500000, -32928901}, + {-77500000, 30000000}, + }, + { + {-9945219, -3065619}, + {-9781479, -2031780}, + {-9510560, -1020730}, + {-9135450, -43529}, + {-2099999, 14110899}, + {2099999, 14110899}, + {9135450, -43529}, + {9510560, -1020730}, + {9781479, -2031780}, + {9945219, -3065619}, + {10000000, -4110899}, + {9945219, -5156179}, + {9781479, -6190019}, + {9510560, -7201069}, + {9135450, -8178270}, + {8660249, -9110899}, + {8090169, -9988750}, + {7431449, -10802209}, + {6691309, -11542349}, + {5877850, -12201069}, + {5000000, -12771149}, + {4067369, -13246350}, + {3090169, -13621459}, + {2079119, -13892379}, + {1045279, -14056119}, + {0, -14110899}, + {-1045279, -14056119}, + {-2079119, -13892379}, + {-3090169, -13621459}, + {-4067369, -13246350}, + {-5000000, -12771149}, + {-5877850, -12201069}, + {-6691309, -11542349}, + {-7431449, -10802209}, + {-8090169, -9988750}, + {-8660249, -9110899}, + {-9135450, -8178270}, + {-9510560, -7201069}, + {-9781479, -6190019}, + {-9945219, -5156179}, + {-10000000, -4110899}, + {-9945219, -3065619}, + }, + { + {-34192394, -5192389}, + {-31499996, 39000000}, + {-8183795, 47668998}, + {-6769596, 47668998}, + {-4648197, 45547698}, + {34192394, 6707109}, + {34192394, 5192389}, + {31500003, -39000000}, + {8183803, -47668998}, + {6769603, -47668998}, + {4648202, -45547698}, + {-32474895, -8424619}, + {-34192394, -6707109}, + {-34192394, -5192389}, + }, + { + {-23475500, -11910099}, + {-18000000, 8217699}, + {-11139699, 20100000}, + {-10271400, 20899999}, + {9532010, 20899999}, + {11199999, 20100000}, + {18500000, 8600000}, + {23475500, -11910099}, + {23799999, -14899999}, + {23706600, -15788900}, + {23668899, -16147499}, + {23281299, -17340400}, + {22654100, -18426700}, + {21814800, -19358900}, + {20799999, -20096199}, + {19654100, -20606300}, + {18427200, -20867099}, + {17799999, -20899999}, + {-17799999, -20899999}, + {-18427200, -20867099}, + {-19654100, -20606300}, + {-20799999, -20096199}, + {-21814800, -19358900}, + {-22654100, -18426700}, + {-23281299, -17340400}, + {-23668899, -16147499}, + {-23799999, -14899999}, + {-23475500, -11910099}, + }, + { + {-32000000, 10000000}, + {-31934440, 10623733}, + {-31740640, 11220210}, + {-31427049, 11763360}, + {-31007389, 12229430}, + {-30500000, 12598079}, + {-29927051, 12853170}, + {-29313585, 12983570}, + {16000000, 16000000}, + {26000000, 16000000}, + {31007400, 12229430}, + {31427101, 11763360}, + {31740600, 11220210}, + {31934398, 10623733}, + {32000000, 10000000}, + {32000000, -13000000}, + {31934398, -13623699}, + {31740600, -14220199}, + {31427101, -14763399}, + {31007400, -15229400}, + {30500000, -15598100}, + {29927101, -15853200}, + {29313598, -15983600}, + {29000000, -16000000}, + {-28000000, -16000000}, + {-29313585, -15983600}, + {-29927051, -15853200}, + {-30500000, -15598100}, + {-31007389, -15229400}, + {-31427049, -14763399}, + {-31740640, -14220199}, + {-31934440, -13623699}, + {-32000000, -13000000}, + {-32000000, 10000000}, + }, + { + {-36133789, -46431022}, + {-36040100, -46171817}, + {-35852722, -45653411}, + {2200073, 59616485}, + {12112792, 87039184}, + {14274505, 93019332}, + {14382049, 93291641}, + {14508483, 93563430}, + {14573425, 93688369}, + {14654052, 93832443}, + {14818634, 94096328}, + {14982757, 94327621}, + {15001708, 94352630}, + {15202392, 94598999}, + {15419342, 94833160}, + {15497497, 94910552}, + {15650848, 95053039}, + {15894866, 95256866}, + {16104309, 95412185}, + {16149047, 95443206}, + {16410888, 95611038}, + {16677795, 95759750}, + {16782348, 95812332}, + {16947143, 95889144}, + {17216400, 95999465}, + {17483123, 96091293}, + {17505554, 96098251}, + {17745178, 96165542}, + {18000671, 96223373}, + {18245880, 96265884}, + {18484039, 96295257}, + {18976715, 96319580}, + {31135131, 96319580}, + {31697082, 96287902}, + {31746368, 96282104}, + {32263000, 96190719}, + {32338623, 96172576}, + {32821411, 96026641}, + {32906188, 95995391}, + {33360565, 95797012}, + {33443420, 95754882}, + {33869171, 95505874}, + {33900756, 95485122}, + {34136413, 95318618}, + {34337127, 95159790}, + {34377288, 95125930}, + {34619628, 94905410}, + {34756286, 94767364}, + {34859008, 94656143}, + {35090606, 94378067}, + {35120849, 94338546}, + {35309295, 94072113}, + {35434875, 93871475}, + {35510070, 93740310}, + {35688232, 93385772}, + {35699096, 93361679}, + {35839782, 93012557}, + {35905487, 92817459}, + {35961578, 92625488}, + {36048004, 92249023}, + {36051574, 92229934}, + {36108856, 91831405}, + {36122985, 91667816}, + {36133789, 91435317}, + {36129669, 91085830}, + {36127685, 91046661}, + {36092742, 90669830}, + {36069946, 90514739}, + {36031829, 90308425}, + {35948211, 89965225}, + {34482635, 84756820}, + {27911407, 61403976}, + {-5872558, -58657440}, + {-14243621, -88406509}, + {-14576812, -89590599}, + {-15421997, -92594200}, + {-15657684, -93431732}, + {-16038940, -93720520}, + {-16420196, -94009307}, + {-17182708, -94586875}, + {-18834838, -95838272}, + {-19470275, -96319580}, + {-21368133, -96319580}, + {-22763854, -96319534}, + {-29742462, -96319274}, + {-32533935, -96319168}, + {-36133789, -54619018}, + {-36133789, -46431022}, + }, + { + {-26000000, 25500000}, + {-6500000, 45000000}, + {17499998, 45000000}, + {23966310, 38533699}, + {26000000, 36500000}, + {26000000, -19000000}, + {25950000, -24500000}, + {17000000, -42214698}, + {14300000, -45000000}, + {-14299999, -45000000}, + {-17500000, -41714698}, + {-23400001, -24500000}, + {-26000000, -10464000}, + {-26000000, 25500000}, + }, + { + {-26000000, 16636100}, + {-25072200, 18777799}, + {-16500000, 35299999}, + {-15050000, 36750000}, + {13550000, 36750000}, + {15000000, 35299999}, + {26000000, 16045200}, + {26000000, -2750000}, + {16500000, -34507900}, + {14840600, -36167301}, + {14257900, -36750000}, + {-14257900, -36750000}, + {-16500000, -34507900}, + {-26000000, -2750000}, + {-26000000, 16636100}, + }, + { + {-18062349, 18950099}, + {4644938, 20049900}, + {6230361, 20049900}, + {7803279, 19851200}, + {9338899, 19456899}, + {10812990, 18873300}, + {12202310, 18109500}, + {13484951, 17177600}, + {14640670, 16092300}, + {15651250, 14870700}, + {16500749, 13532100}, + {17175849, 12097599}, + {17665750, 10589700}, + {17962850, 9032400}, + {18062349, 7450099}, + {17962850, 5867799}, + {15810750, -11007740}, + {15683750, -11727769}, + {15506849, -12437200}, + {15280929, -13132559}, + {15007040, -13810470}, + {14686531, -14467609}, + {14320949, -15100799}, + {13912099, -15706950}, + {13461959, -16283100}, + {12972730, -16826450}, + {12446790, -17334339}, + {11886699, -17804309}, + {11295190, -18234069}, + {10675149, -18621520}, + {10029590, -18964771}, + {9361650, -19262149}, + {8674600, -19512220}, + {7971780, -19713699}, + {7256609, -19865798}, + {6532589, -19967498}, + {5803222, -20018501}, + {5437650, -20024900}, + {-1062349, -20049900}, + {-16562349, -20049900}, + {-18062349, -18549900}, + {-18062349, 18950099}, + }, + { + {-18062349, 41299900}, + {-1062349, 41299900}, + {15280929, -8117440}, + {15506849, -8812799}, + {15683750, -9522230}, + {15810750, -10242259}, + {17962850, -27117799}, + {18062349, -28700099}, + {17962850, -30282400}, + {17665750, -31839700}, + {17175849, -33347599}, + {16500749, -34782100}, + {15651250, -36120700}, + {14640670, -37342300}, + {13484951, -38427600}, + {12202310, -39359500}, + {10812990, -40123298}, + {9338899, -40706901}, + {7803279, -41101200}, + {6230361, -41299900}, + {4644938, -41299900}, + {-18062349, -40200099}, + {-18062349, 41299900}, + }, + { + {-11750000, 13057900}, + {-9807860, 15000000}, + {4392139, 24000000}, + {11750000, 24000000}, + {11750000, -24000000}, + {4392139, -24000000}, + {-9807860, -15000000}, + {-11750000, -13057900}, + {-11750000, 13057900}, + }, + { + {-12500000, 17500000}, + {12500000, 17500000}, + {12500000, -17500000}, + {-12500000, -17500000}, + {-12500000, 17500000}, + }, + { + {-23500000, 11500000}, + {-13857859, 21000000}, + {-11000000, 21000000}, + {18500000, 500000}, + {23500000, -4500000}, + {23500000, -19500000}, + {22000000, -21000000}, + {-23500000, -21000000}, + {-23500000, 11500000}, + }, + { + {-13000000, 5250000}, + {-4000000, 6750000}, + {4000000, 6750000}, + {13000000, 5250000}, + {13000000, 838459}, + {11376299, -1973939}, + {10350899, -3750000}, + {8618800, -6750000}, + {-8498290, -6750000}, + {-13000000, 1047180}, + {-13000000, 5250000}, + }, + { + {-25000000, 50500000}, + {-21500000, 54000000}, + {18286800, 54000000}, + {25000000, 47286800}, + {25000000, -47286800}, + {18286800, -54000000}, + {-21500000, -54000000}, + {-25000000, -50500000}, + {-25000000, 50500000}, + }, + { + {-19000000, 46000000}, + {-16799999, 46000000}, + {14000000, 34000000}, + {19000000, 29000000}, + {19000000, -29000000}, + {14000000, -34000000}, + {-16799999, -46000000}, + {-19000000, -46000000}, + {-19000000, 46000000}, + }, + { + {-7956170, 836226}, + {-7825180, 1663290}, + {-7767529, 1914530}, + {-7608449, 2472140}, + {-7308360, 3253890}, + {-7083650, 3717780}, + {-6928199, 4000000}, + {-6472139, 4702280}, + {-5988090, 5304979}, + {-5945159, 5353040}, + {-5353040, 5945159}, + {-4702280, 6472139}, + {-4544519, 6583869}, + {-4000000, 6928199}, + {-3253890, 7308360}, + {-2836839, 7480130}, + {-2472140, 7608449}, + {-1663290, 7825180}, + {-964293, 7941669}, + {-836226, 7956170}, + {0, 8000000}, + {836226, 7956170}, + {964293, 7941669}, + {1663290, 7825180}, + {2472140, 7608449}, + {2836839, 7480130}, + {3253890, 7308360}, + {4000000, 6928199}, + {4544519, 6583869}, + {4702280, 6472139}, + {5353040, 5945159}, + {5945159, 5353040}, + {5988090, 5304979}, + {6472139, 4702280}, + {6928199, 4000000}, + {7083650, 3717780}, + {7308360, 3253890}, + {7608449, 2472140}, + {7767529, 1914530}, + {7825180, 1663290}, + {7956170, 836226}, + {8000000, 0}, + {7956170, -836226}, + {7825180, -1663290}, + {7767529, -1914530}, + {7608449, -2472140}, + {7308360, -3253890}, + {7083650, -3717780}, + {6928199, -4000000}, + {6472139, -4702280}, + {5988090, -5304979}, + {5945159, -5353040}, + {5353040, -5945159}, + {4702280, -6472139}, + {4544519, -6583869}, + {4000000, -6928199}, + {3253890, -7308360}, + {2836839, -7480130}, + {2472140, -7608449}, + {1663290, -7825180}, + {964293, -7941669}, + {836226, -7956170}, + {0, -8000000}, + {-836226, -7956170}, + {-964293, -7941669}, + {-1663290, -7825180}, + {-2472140, -7608449}, + {-2836839, -7480130}, + {-3253890, -7308360}, + {-4000000, -6928199}, + {-4544519, -6583869}, + {-4702280, -6472139}, + {-5353040, -5945159}, + {-5945159, -5353040}, + {-5988090, -5304979}, + {-6472139, -4702280}, + {-6928199, -4000000}, + {-7083650, -3717780}, + {-7308360, -3253890}, + {-7608449, -2472140}, + {-7767529, -1914530}, + {-7825180, -1663290}, + {-7956170, -836226}, + {-8000000, 0}, + {-7956170, 836226}, + }, +}; + +const TestData PRUSA_STEGOSAUR_POLYGONS = +{ + { + {113210205, 107034095}, + {113561798, 109153793}, + {113750099, 109914001}, + {114396499, 111040199}, + {114599197, 111321998}, + {115570404, 112657096}, + {116920097, 114166595}, + {117630599, 114609390}, + {119703704, 115583900}, + {120559494, 115811996}, + {121045410, 115754493}, + {122698097, 115526496}, + {123373001, 115370193}, + {123482406, 115315689}, + {125664199, 114129798}, + {125920303, 113968193}, + {128551208, 111866195}, + {129075592, 111443199}, + {135044692, 106572608}, + {135254898, 106347694}, + {135415100, 106102897}, + {136121704, 103779891}, + {136325103, 103086303}, + {136690093, 101284896}, + {136798309, 97568496}, + {136798309, 97470397}, + {136787399, 97375297}, + {136753295, 97272102}, + {136687988, 97158699}, + {136539794, 96946899}, + {135526702, 95550994}, + {135388488, 95382293}, + {135272491, 95279098}, + {135214904, 95250595}, + {135122894, 95218002}, + {134966705, 95165191}, + {131753997, 94380798}, + {131226806, 94331001}, + {129603393, 94193893}, + {129224197, 94188003}, + {127874107, 94215103}, + {126812797, 94690200}, + {126558197, 94813896}, + {118361801, 99824195}, + {116550796, 101078796}, + {116189704, 101380493}, + {114634002, 103027999}, + {114118103, 103820297}, + {113399200, 105568000}, + {113201705, 106093597}, + {113210205, 107034095}, + }, + { + {77917999, 130563003}, + {77926300, 131300903}, + {77990196, 132392700}, + {78144195, 133328002}, + {78170593, 133427093}, + {78235900, 133657592}, + {78799598, 135466705}, + {78933296, 135832397}, + {79112899, 136247604}, + {79336303, 136670898}, + {79585197, 137080596}, + {79726303, 137309005}, + {79820297, 137431900}, + {79942199, 137549407}, + {90329193, 145990203}, + {90460197, 146094390}, + {90606399, 146184509}, + {90715194, 146230010}, + {90919601, 146267211}, + {142335296, 153077697}, + {143460296, 153153594}, + {143976593, 153182189}, + {145403991, 153148605}, + {145562301, 153131195}, + {145705993, 153102905}, + {145938796, 153053192}, + {146134094, 153010101}, + {146483184, 152920196}, + {146904693, 152806396}, + {147180099, 152670196}, + {147357788, 152581695}, + {147615295, 152423095}, + {147782287, 152294708}, + {149281799, 150908386}, + {149405303, 150784912}, + {166569305, 126952499}, + {166784301, 126638099}, + {166938491, 126393699}, + {167030899, 126245101}, + {167173004, 126015899}, + {167415298, 125607200}, + {167468292, 125504699}, + {167553100, 125320899}, + {167584594, 125250694}, + {167684997, 125004394}, + {167807098, 124672401}, + {167938995, 124255203}, + {168052307, 123694000}, + {170094100, 112846900}, + {170118408, 112684204}, + {172079101, 88437797}, + {172082000, 88294403}, + {171916290, 82827606}, + {171911590, 82705703}, + {171874893, 82641906}, + {169867004, 79529907}, + {155996795, 58147998}, + {155904998, 58066299}, + {155864791, 58054199}, + {134315704, 56830902}, + {134086486, 56817901}, + {98200096, 56817798}, + {97838195, 56818599}, + {79401695, 56865097}, + {79291297, 56865501}, + {79180694, 56869499}, + {79058799, 56885097}, + {78937301, 56965301}, + {78324691, 57374599}, + {77932998, 57638401}, + {77917999, 57764297}, + {77917999, 130563003}, + }, + { + {75566848, 109289947}, + {75592651, 109421951}, + {75644248, 109534446}, + {95210548, 141223846}, + {95262649, 141307449}, + {95487854, 141401443}, + {95910850, 141511642}, + {96105651, 141550338}, + {106015045, 142803451}, + {106142852, 142815155}, + {166897460, 139500244}, + {167019348, 139484741}, + {168008239, 138823043}, + {168137542, 138735153}, + {168156250, 138616851}, + {173160751, 98882049}, + {174381546, 87916046}, + {174412246, 87579048}, + {174429443, 86988746}, + {174436141, 86297348}, + {174438949, 84912048}, + {174262939, 80999145}, + {174172546, 80477546}, + {173847549, 79140846}, + {173623840, 78294349}, + {173120239, 76485046}, + {173067138, 76300544}, + {173017852, 76137542}, + {172941543, 75903045}, + {172892547, 75753143}, + {172813537, 75533348}, + {172758453, 75387046}, + {172307556, 74196746}, + {171926544, 73192848}, + {171891448, 73100448}, + {171672546, 72524147}, + {171502441, 72085144}, + {171414459, 71859146}, + {171294250, 71552352}, + {171080139, 71019744}, + {171039245, 70928146}, + {170970550, 70813346}, + {170904235, 70704040}, + {170786254, 70524353}, + {168063247, 67259048}, + {167989547, 67184844}, + {83427947, 67184844}, + {78360847, 67201248}, + {78238845, 67220550}, + {78151550, 67350547}, + {77574554, 68220550}, + {77494949, 68342651}, + {77479949, 68464546}, + {75648345, 106513351}, + {75561050, 109165740}, + {75566848, 109289947}, + }, + { + {75619415, 108041595}, + {83609863, 134885772}, + {83806945, 135450820}, + {83943908, 135727371}, + {84799934, 137289794}, + {86547897, 140033782}, + {86674118, 140192962}, + {86810661, 140364715}, + {87045211, 140619918}, + {88187042, 141853240}, + {93924575, 147393783}, + {94058013, 147454803}, + {111640083, 153754562}, + {111762550, 153787933}, + {111975250, 153835311}, + {112127426, 153842803}, + {116797996, 154005157}, + {116969688, 154010681}, + {117141731, 154005935}, + {117333145, 153988037}, + {118007507, 153919952}, + {118159675, 153902130}, + {118931480, 153771942}, + {120878150, 153379089}, + {121172164, 153319259}, + {122074508, 153034362}, + {122260681, 152970367}, + {122313438, 152949584}, + {130755096, 149423736}, + {130996063, 149316818}, + {138893524, 144469665}, + {138896423, 144466918}, + {169883666, 97686134}, + {170115036, 96518981}, + {170144317, 96365257}, + {174395645, 67672065}, + {174396560, 67664222}, + {174288452, 66839241}, + {174170364, 66096923}, + {174112731, 65952033}, + {174021377, 65823486}, + {173948608, 65743225}, + {173863830, 65654769}, + {170408340, 63627494}, + {170004867, 63394714}, + {169585632, 63194389}, + {169441162, 63137046}, + {168944274, 62952133}, + {160605072, 60214218}, + {160331573, 60126396}, + {159674743, 59916877}, + {150337249, 56943778}, + {150267730, 56922073}, + {150080139, 56864868}, + {149435333, 56676422}, + {149310241, 56640579}, + {148055419, 56285041}, + {147828796, 56230949}, + {147598205, 56181800}, + {147149963, 56093917}, + {146834457, 56044700}, + {146727966, 56028717}, + {146519729, 56004882}, + {146328521, 55989326}, + {146170684, 55990036}, + {146151321, 55990745}, + {145800170, 56003616}, + {145639526, 56017753}, + {145599426, 56022491}, + {145481338, 56039184}, + {145389556, 56052757}, + {145325134, 56062591}, + {145176574, 56086135}, + {145017272, 56113922}, + {107163085, 63504539}, + {101013870, 65454101}, + {100921798, 65535285}, + {95362182, 74174079}, + {75652366, 107803443}, + {75635391, 107834983}, + {75628814, 107853294}, + {75603431, 107933692}, + {75619415, 108041595}, + }, + { + {83617141, 120264900}, + {84617370, 126416427}, + {84648635, 126601341}, + {84693695, 126816085}, + {84762496, 127082641}, + {84772140, 127117034}, + {84860748, 127391693}, + {84927398, 127550239}, + {85072967, 127789642}, + {85155151, 127908851}, + {86745422, 130042907}, + {86982666, 130317489}, + {89975143, 133230743}, + {90091384, 133338500}, + {96260833, 138719818}, + {96713928, 139103668}, + {98139297, 140307388}, + {102104766, 143511505}, + {102142089, 143536468}, + {102457626, 143735107}, + {103386764, 144312988}, + {103845001, 144579177}, + {104139175, 144737136}, + {104551254, 144932250}, + {104690155, 144985778}, + {104844238, 145010009}, + {105020034, 145010375}, + {128999633, 144082305}, + {129096542, 144076141}, + {133932327, 143370178}, + {134130615, 143326751}, + {134281250, 143289520}, + {135247116, 142993438}, + {150774948, 137828704}, + {150893478, 137786178}, + {151350921, 137608901}, + {159797760, 134318115}, + {159979827, 134244384}, + {159988128, 134240997}, + {160035186, 134221633}, + {160054962, 134211486}, + {160168762, 134132736}, + {160181228, 134121047}, + {160336425, 133961502}, + {160689147, 133564331}, + {161446258, 132710739}, + {163306427, 130611648}, + {164845474, 128873855}, + {165270233, 128393600}, + {165281478, 128380706}, + {165300598, 128358673}, + {165303497, 128355194}, + {166411590, 122772674}, + {166423767, 122708648}, + {164745605, 66237312}, + {164740341, 66193061}, + {164721755, 66082092}, + {164721160, 66078750}, + {164688476, 65914146}, + {164668426, 65859436}, + {164563110, 65765937}, + {164431152, 65715034}, + {163997619, 65550788}, + {163946426, 65531440}, + {162998107, 65173629}, + {162664978, 65049140}, + {162482696, 64991668}, + {162464660, 64989639}, + {148029083, 66896141}, + {147862396, 66932853}, + {130087829, 73341102}, + {129791564, 73469726}, + {100590927, 90307685}, + {100483535, 90373847}, + {100364990, 90458930}, + {96447448, 93276664}, + {95179656, 94189010}, + {93692718, 95260208}, + {87904327, 99430885}, + {87663711, 99606147}, + {87576202, 99683990}, + {87498199, 99801719}, + {85740264, 104173728}, + {85538925, 104710494}, + {84786132, 107265830}, + {84635955, 107801383}, + {84619506, 107868064}, + {84518463, 108287200}, + {84456848, 108613471}, + {84419158, 108826194}, + {84375244, 109093818}, + {84329818, 109435180}, + {84249862, 110179664}, + {84218429, 110572166}, + {83630020, 117995208}, + {83595535, 118787673}, + {83576217, 119290679}, + {83617141, 120264900}, + }, + { + {91735549, 117640846}, + {91748252, 117958145}, + {91823547, 118515449}, + {92088752, 119477249}, + {97995346, 140538452}, + {98031051, 140660446}, + {98154449, 141060241}, + {98179855, 141133758}, + {98217056, 141232849}, + {98217147, 141233047}, + {98269256, 141337051}, + {98298950, 141387954}, + {98337753, 141445755}, + {99455047, 142984451}, + {99656250, 143247344}, + {102567855, 146783752}, + {102685150, 146906845}, + {102828948, 147031250}, + {102972457, 147120452}, + {103676147, 147539642}, + {103758956, 147586151}, + {103956756, 147682144}, + {104479949, 147931457}, + {104744453, 148044143}, + {104994750, 148123443}, + {105375648, 148158645}, + {109266250, 148178253}, + {109447753, 148169052}, + {109693649, 148129150}, + {113729949, 147337448}, + {113884552, 147303054}, + {115155349, 146956146}, + {117637145, 146174346}, + {154694046, 134048049}, + {156979949, 133128555}, + {157076843, 133059356}, + {157125045, 133001449}, + {157561340, 132300750}, + {157865753, 131795959}, + {157923156, 131667358}, + {158007049, 131297653}, + {158112747, 130777053}, + {158116653, 130640853}, + {158268951, 119981643}, + {158260040, 119824752}, + {158229949, 119563751}, + {149914047, 73458648}, + {149877548, 73331748}, + {144460754, 66413558}, + {144230545, 66153152}, + {144128051, 66075057}, + {143974853, 65973152}, + {142812744, 65353149}, + {141810943, 64837249}, + {141683349, 64805152}, + {141505157, 64784652}, + {108214355, 61896251}, + {107826354, 61866352}, + {107072151, 61821750}, + {106938850, 61873550}, + {106584251, 62055152}, + {106419952, 62147548}, + {100459152, 65546951}, + {100343849, 65615150}, + {100198852, 65716949}, + {99825149, 65979751}, + {94619247, 70330352}, + {94492355, 70480850}, + {94445846, 70547355}, + {94425354, 70588752}, + {94379753, 70687652}, + {94110252, 71443450}, + {94095252, 71569053}, + {91737251, 117308746}, + {91731048, 117430946}, + {91735549, 117640846}, + }, + { + {108231399, 111763748}, + {108335403, 111927955}, + {108865203, 112754745}, + {109206703, 113283851}, + {127117500, 125545951}, + {127212097, 125560951}, + {127358497, 125563652}, + {131348007, 125551147}, + {131412002, 125550849}, + {131509506, 125535446}, + {131579391, 125431343}, + {132041000, 124735656}, + {132104690, 124637847}, + {144108505, 100950546}, + {144120605, 100853042}, + {144123291, 100764648}, + {144122695, 100475143}, + {144086898, 85637748}, + {144083602, 85549346}, + {144071105, 85451843}, + {144007003, 85354545}, + {143679595, 84864547}, + {143468597, 84551048}, + {143367889, 84539146}, + {109847702, 84436347}, + {109684700, 84458953}, + {105946502, 89406143}, + {105915901, 91160446}, + {105880905, 93187744}, + {105876701, 93441345}, + {108231399, 111763748}, + }, + { + {102614700, 117684249}, + {102675102, 118074157}, + {102888999, 118743148}, + {103199707, 119517555}, + {103446800, 120099655}, + {103488204, 120193450}, + {104063903, 121373947}, + {104535499, 122192245}, + {104595802, 122295249}, + {104663002, 122402854}, + {104945701, 122854858}, + {105740501, 124038848}, + {106809700, 125479354}, + {107564399, 126380050}, + {108116203, 126975646}, + {123724700, 142516540}, + {124938400, 143705444}, + {127919601, 146599243}, + {128150894, 146821456}, + {128251602, 146917251}, + {128383605, 147041839}, + {128527709, 147176147}, + {128685699, 147321456}, + {128861007, 147481246}, + {132825103, 151046661}, + {133005493, 151205657}, + {133389007, 151488143}, + {133896499, 151858062}, + {134172302, 151991546}, + {134375000, 152063140}, + {135316101, 152300949}, + {136056304, 152220947}, + {136242706, 152186843}, + {136622207, 152016448}, + {136805404, 151908355}, + {147099594, 145766845}, + {147246704, 144900756}, + {147387603, 144048461}, + {144353698, 99345855}, + {144333801, 99232254}, + {144244598, 98812850}, + {144228698, 98757858}, + {144174606, 98616455}, + {133010101, 72396743}, + {132018905, 70280853}, + {130667404, 67536949}, + {129167297, 64854446}, + {128569198, 64098350}, + {124458503, 59135948}, + {124260597, 58946949}, + {123908706, 58658851}, + {123460098, 58327850}, + {122674499, 57840648}, + {122041801, 57712150}, + {121613403, 57699047}, + {121359901, 57749351}, + {121123199, 57826450}, + {120953498, 57882247}, + {120431701, 58198547}, + {120099205, 58599349}, + {119892303, 58903049}, + {102835296, 115179351}, + {102686599, 115817245}, + {102612396, 116540557}, + {102614700, 117684249}, + }, + { + {98163757, 71203430}, + {98212463, 73314544}, + {98326538, 74432693}, + {98402908, 75169799}, + {98524154, 76328353}, + {99088806, 79911361}, + {99304885, 80947769}, + {100106689, 84244186}, + {100358123, 85080337}, + {101715545, 89252807}, + {101969528, 89987213}, + {107989440, 106391418}, + {126299575, 140277343}, + {127061813, 141486663}, + {127405746, 141872253}, + {127846908, 142318450}, + {130818496, 145301574}, + {134366424, 148100921}, + {135308380, 148798828}, + {135745666, 149117523}, + {136033020, 149251800}, + {136500579, 149387725}, + {136662719, 149418395}, + {136973922, 149474822}, + {137184890, 149484375}, + {137623748, 149434356}, + {137830810, 149355072}, + {138681732, 148971343}, + {139374465, 148463409}, + {139589187, 148264312}, + {139809707, 148010711}, + {139985610, 147685028}, + {140196029, 147284973}, + {140355834, 146978668}, + {142079666, 142575622}, + {146702194, 129469726}, + {151285888, 113275238}, + {151543731, 112046264}, + {151701629, 110884704}, + {151837020, 108986206}, + {151837097, 107724029}, + {151760101, 106529205}, + {151581970, 105441925}, + {151577301, 105413757}, + {151495269, 105014709}, + {151393142, 104551513}, + {151058502, 103296112}, + {150705520, 102477264}, + {150137725, 101686370}, + {149427032, 100938537}, + {102979965, 60772064}, + {101930953, 60515609}, + {101276748, 60634414}, + {100717803, 60918136}, + {100125732, 61584625}, + {99618148, 62413436}, + {99457214, 62709442}, + {99368347, 62914794}, + {99166992, 63728332}, + {98313827, 69634780}, + {98176910, 70615707}, + {98162902, 70798233}, + {98163757, 71203430}, + }, + { + {79090698, 116426399}, + {80959800, 137087692}, + {81030303, 137762298}, + {81190704, 138903503}, + {81253700, 139084197}, + {81479301, 139544998}, + {81952003, 140118896}, + {82319900, 140523895}, + {82967803, 140993896}, + {83022903, 141032104}, + {83777900, 141493606}, + {84722099, 141849899}, + {84944396, 141887207}, + {86144699, 141915893}, + {87643997, 141938095}, + {88277503, 141887695}, + {88582099, 141840606}, + {89395401, 141712203}, + {90531204, 141528396}, + {91014801, 141438400}, + {92097595, 141190093}, + {123348297, 132876998}, + {123399505, 132860000}, + {123452804, 132841506}, + {123515502, 132818908}, + {123543800, 132806198}, + {124299598, 132437393}, + {124975502, 132042098}, + {125047500, 131992202}, + {125119506, 131930603}, + {166848800, 86317703}, + {168976409, 83524902}, + {169359603, 82932701}, + {169852600, 81917800}, + {170686904, 79771202}, + {170829406, 79245597}, + {170885498, 78796295}, + {170909301, 78531898}, + {170899703, 78238700}, + {170842803, 77553199}, + {170701293, 76723495}, + {170302307, 75753898}, + {169924301, 75067398}, + {169359802, 74578796}, + {168148605, 73757499}, + {163261596, 71124702}, + {162986007, 70977798}, + {162248703, 70599098}, + {158193405, 68923995}, + {157514297, 68667495}, + {156892700, 68495201}, + {156607299, 68432998}, + {154301895, 68061904}, + {93440299, 68061904}, + {88732002, 68255996}, + {88627304, 68298500}, + {88111396, 68541900}, + {86393898, 69555404}, + {86138298, 69706695}, + {85871704, 69913200}, + {85387199, 70393402}, + {79854499, 76783203}, + {79209701, 77649398}, + {79108505, 78072502}, + {79090698, 78472198}, + {79090698, 116426399}, + }, + { + {90956314, 84639938}, + {91073814, 85141891}, + {91185752, 85505371}, + {109815368, 137196487}, + {110342590, 138349899}, + {110388549, 138447540}, + {110652862, 138971343}, + {110918045, 139341140}, + {114380859, 143159042}, + {114446723, 143220352}, + {114652198, 143392166}, + {114712196, 143437301}, + {114782165, 143476028}, + {114873054, 143514923}, + {115217086, 143660934}, + {115306060, 143695526}, + {115344009, 143707580}, + {115444541, 143737747}, + {115589378, 143779937}, + {115751358, 143823989}, + {115802780, 143825820}, + {116872810, 143753616}, + {116927055, 143744644}, + {154690734, 133504180}, + {155009704, 133371856}, + {155029907, 133360061}, + {155089141, 133323181}, + {155342315, 133163360}, + {155602294, 132941406}, + {155669158, 132880294}, + {155821624, 132737884}, + {155898986, 132656890}, + {155934936, 132608932}, + {155968627, 132562713}, + {156062896, 132431808}, + {156111694, 132363174}, + {156148147, 132297180}, + {158738342, 127281066}, + {159026672, 126378631}, + {159073699, 125806335}, + {159048522, 125299743}, + {159040313, 125192901}, + {158898300, 123934677}, + {149829376, 70241508}, + {149763031, 69910629}, + {149684692, 69628723}, + {149557800, 69206214}, + {149366485, 68864326}, + {149137390, 68578514}, + {148637466, 68048767}, + {147027725, 66632934}, + {146228607, 66257507}, + {146061309, 66184646}, + {146017929, 66174186}, + {145236465, 66269500}, + {144802490, 66345039}, + {144673995, 66376220}, + {93732284, 79649864}, + {93345336, 79785865}, + {93208084, 79840286}, + {92814521, 79997779}, + {92591087, 80098968}, + {92567016, 80110511}, + {92032684, 80860725}, + {91988853, 80930152}, + {91471725, 82210029}, + {91142349, 83076683}, + {90969284, 83653182}, + {90929664, 84043212}, + {90926315, 84325256}, + {90956314, 84639938}, + }, + { + {114758499, 88719909}, + {114771591, 88860549}, + {115515533, 94195907}, + {115559539, 94383651}, + {119882980, 109502059}, + {120660522, 111909683}, + {126147735, 124949630}, + {127127212, 127107215}, + {129976379, 132117279}, + {130754470, 133257080}, + {130820968, 133340835}, + {130889312, 133423858}, + {131094787, 133652832}, + {131257629, 133828247}, + {131678619, 134164276}, + {131791107, 134248901}, + {131969482, 134335189}, + {132054107, 134373718}, + {132927368, 134701141}, + {133077072, 134749313}, + {133196075, 134785705}, + {133345230, 134804351}, + {133498809, 134809051}, + {133611541, 134797607}, + {134621170, 134565322}, + {134741165, 134527511}, + {134892089, 134465240}, + {135071212, 134353820}, + {135252029, 134185821}, + {135384979, 134003631}, + {135615585, 133576675}, + {135793029, 132859008}, + {135890228, 131382904}, + {135880828, 131261657}, + {135837570, 130787963}, + {135380661, 127428909}, + {132830596, 109495368}, + {132815826, 109411666}, + {132765869, 109199302}, + {132724380, 109068161}, + {127490066, 93353515}, + {125330810, 87852828}, + {125248336, 87647026}, + {125002182, 87088424}, + {124894592, 86872482}, + {121007278, 80019584}, + {120962829, 79941261}, + {120886489, 79833923}, + {120154983, 78949615}, + {119366561, 78111709}, + {119014755, 77776794}, + {116728790, 75636238}, + {116660522, 75593933}, + {116428192, 75458541}, + {116355255, 75416870}, + {116264663, 75372528}, + {115952728, 75233367}, + {115865554, 75205482}, + {115756835, 75190956}, + {115564163, 75197830}, + {115481170, 75202087}, + {115417144, 75230400}, + {115226959, 75337806}, + {115203842, 75351448}, + {114722015, 75746932}, + {114672103, 75795661}, + {114594619, 75891891}, + {114565811, 75973831}, + {114478256, 76240814}, + {114178039, 77252197}, + {114137664, 77769668}, + {114109771, 78154464}, + {114758499, 88719909}, + }, + { + {108135070, 109828002}, + {108200347, 110091529}, + {108319419, 110298500}, + {108439025, 110488388}, + {108663574, 110766731}, + {108812957, 110935768}, + {109321914, 111398925}, + {109368087, 111430320}, + {109421295, 111466331}, + {110058998, 111849746}, + {127160308, 120588981}, + {127350692, 120683456}, + {128052749, 120997207}, + {128326919, 121113449}, + {131669586, 122213058}, + {131754745, 122240592}, + {131854583, 122264770}, + {132662048, 122449813}, + {132782669, 122449897}, + {132909118, 122443687}, + {133013442, 122436058}, + {140561035, 121609939}, + {140786346, 121583320}, + {140876144, 121570228}, + {140962356, 121547996}, + {141052612, 121517837}, + {141231292, 121442184}, + {141309371, 121390007}, + {141370132, 121327003}, + {141456008, 121219932}, + {141591598, 121045005}, + {141905761, 120634796}, + {141894607, 120305725}, + {141881881, 120110855}, + {141840881, 119885009}, + {141685043, 119238922}, + {141617416, 118962882}, + {141570434, 118858856}, + {131617462, 100598548}, + {131542846, 100487213}, + {131229385, 100089019}, + {131091476, 99928108}, + {119824127, 90297180}, + {119636337, 90142387}, + {119507492, 90037765}, + {119436744, 89983657}, + {119423942, 89974159}, + {119207366, 89822471}, + {119117149, 89767097}, + {119039489, 89726867}, + {116322929, 88522857}, + {114817031, 87882110}, + {114683975, 87826751}, + {114306411, 87728507}, + {113876434, 87646003}, + {113792106, 87629974}, + {113658988, 87615974}, + {113574333, 87609275}, + {112813575, 87550102}, + {112578567, 87560157}, + {112439880, 87571647}, + {112306922, 87599395}, + {112225082, 87622535}, + {112132568, 87667175}, + {112103477, 87682830}, + {110795242, 88511634}, + {110373565, 88847793}, + {110286537, 88934989}, + {109730873, 89531501}, + {109648735, 89628883}, + {109552581, 89768859}, + {109514228, 89838470}, + {109501640, 89877586}, + {109480964, 89941864}, + {109461761, 90032417}, + {109457778, 90055458}, + {108105194, 109452575}, + {108094238, 109620979}, + {108135070, 109828002}, + }, + { + {108764694, 108910400}, + {108965499, 112306495}, + {109598602, 120388298}, + {110573898, 128289596}, + {110597801, 128427795}, + {113786201, 137983795}, + {113840301, 138134704}, + {113937202, 138326904}, + {114046005, 138520401}, + {114150802, 138696792}, + {114164703, 138717895}, + {114381896, 139021194}, + {114701004, 139425292}, + {114997398, 139747497}, + {115065597, 139805191}, + {115134498, 139850891}, + {115167098, 139871704}, + {115473396, 139992797}, + {115537498, 139995101}, + {116762596, 139832000}, + {116897499, 139808593}, + {118401802, 139225585}, + {118437500, 139209594}, + {118488204, 139182189}, + {118740097, 139033996}, + {118815795, 138967285}, + {134401000, 116395492}, + {134451507, 116309997}, + {135488098, 113593597}, + {137738006, 106775695}, + {140936492, 97033889}, + {140960006, 96948997}, + {141026504, 96660995}, + {141067291, 96467094}, + {141124893, 95771896}, + {141511795, 90171600}, + {141499801, 90026000}, + {141479598, 89907798}, + {141276794, 88844596}, + {141243804, 88707397}, + {140778305, 87031593}, + {140733306, 86871696}, + {140697204, 86789993}, + {140619796, 86708190}, + {140398391, 86487396}, + {125798797, 72806198}, + {125415802, 72454498}, + {123150398, 70566093}, + {123038803, 70503997}, + {122681198, 70305397}, + {121919204, 70104797}, + {121533699, 70008094}, + {121273696, 70004898}, + {121130599, 70020797}, + {121045097, 70033294}, + {120847099, 70082298}, + {120481895, 70278999}, + {120367004, 70379692}, + {120272796, 70475097}, + {119862098, 71004791}, + {119745101, 71167297}, + {119447799, 71726997}, + {119396499, 71825798}, + {119348701, 71944496}, + {109508796, 98298797}, + {109368598, 98700897}, + {109298400, 98926391}, + {108506301, 102750991}, + {108488197, 102879898}, + {108764694, 108910400}, + }, + { + {106666252, 87231246}, + {106673248, 87358055}, + {107734146, 101975646}, + {107762649, 102357955}, + {108702445, 111208351}, + {108749450, 111345153}, + {108848350, 111542648}, + {110270645, 114264358}, + {110389648, 114445144}, + {138794845, 143461151}, + {139048355, 143648956}, + {139376144, 143885345}, + {139594451, 144022644}, + {139754043, 144110046}, + {139923950, 144185852}, + {140058242, 144234451}, + {140185653, 144259552}, + {140427551, 144292648}, + {141130950, 144281448}, + {141157653, 144278152}, + {141214355, 144266555}, + {141347457, 144223449}, + {141625350, 144098953}, + {141755142, 144040145}, + {141878143, 143971557}, + {142011444, 143858154}, + {142076843, 143796356}, + {142160644, 143691055}, + {142224456, 143560852}, + {142925842, 142090850}, + {142935653, 142065353}, + {142995956, 141899154}, + {143042556, 141719757}, + {143102951, 141436157}, + {143129257, 141230453}, + {143316055, 139447250}, + {143342544, 133704650}, + {143307556, 130890960}, + {142461257, 124025558}, + {141916046, 120671051}, + {141890457, 120526153}, + {140002349, 113455749}, + {139909149, 113144149}, + {139853454, 112974456}, + {137303756, 105228057}, + {134700546, 98161254}, + {134617950, 97961547}, + {133823547, 96118057}, + {133688751, 95837356}, + {133481353, 95448059}, + {133205444, 94948150}, + {131178955, 91529853}, + {131144744, 91482055}, + {113942047, 67481246}, + {113837051, 67360549}, + {113048950, 66601745}, + {112305549, 66002746}, + {112030853, 65790351}, + {111970649, 65767547}, + {111912445, 65755249}, + {111854248, 65743453}, + {111657447, 65716354}, + {111576950, 65707351}, + {111509750, 65708549}, + {111443550, 65718551}, + {111397247, 65737449}, + {111338546, 65764648}, + {111129547, 65863349}, + {111112449, 65871551}, + {110995254, 65927856}, + {110968849, 65946151}, + {110941444, 65966751}, + {110836448, 66057853}, + {110490447, 66445449}, + {110404144, 66576751}, + {106802055, 73202148}, + {106741950, 73384948}, + {106715454, 73469650}, + {106678054, 73627151}, + {106657455, 75433448}, + {106666252, 87231246}, + }, + { + {101852752, 106261352}, + {101868949, 106406051}, + {102347549, 108974250}, + {112286750, 152027954}, + {112305648, 152106536}, + {112325752, 152175857}, + {112391448, 152290863}, + {113558250, 154187454}, + {113592048, 154226745}, + {113694351, 154313156}, + {113736549, 154335647}, + {113818145, 154367462}, + {114284454, 154490951}, + {114415847, 154504547}, + {114520751, 154489151}, + {114571350, 154478057}, + {114594551, 154472854}, + {114630546, 154463958}, + {114715148, 154429443}, + {146873657, 136143051}, + {146941741, 136074249}, + {147190155, 135763549}, + {147262649, 135654937}, + {147309951, 135557159}, + {147702255, 133903945}, + {147934143, 131616348}, + {147967041, 131273864}, + {148185852, 127892250}, + {148195648, 127669754}, + {148179656, 126409851}, + {148119552, 126182151}, + {147874053, 125334152}, + {147818954, 125150352}, + {146958557, 122656646}, + {139070251, 101025955}, + {139002655, 100879051}, + {119028450, 63067649}, + {118846649, 62740753}, + {115676048, 57814651}, + {115550453, 57629852}, + {115330352, 57319751}, + {115094749, 56998352}, + {114978347, 56847454}, + {114853050, 56740550}, + {114695053, 56609550}, + {114582252, 56528148}, + {114210449, 56375953}, + {113636245, 56214950}, + {113470352, 56171649}, + {109580749, 55503551}, + {109491645, 55495452}, + {109238754, 55511550}, + {109080352, 55534049}, + {108027748, 55687351}, + {107839950, 55732349}, + {107614456, 55834953}, + {107488143, 55925952}, + {107302551, 56062553}, + {107218353, 56145751}, + {107199447, 56167251}, + {107052749, 56354850}, + {106978652, 56476348}, + {106869644, 56710754}, + {104541351, 62448753}, + {104454551, 62672554}, + {104441253, 62707351}, + {104231750, 63366348}, + {104222648, 63419952}, + {104155746, 63922649}, + {104127349, 64147552}, + {104110847, 64299957}, + {102235450, 92366752}, + {101804351, 102877655}, + {101852752, 106261352}, + }, + { + {106808700, 120885696}, + {106818695, 120923103}, + {106873901, 121057098}, + {115123603, 133614700}, + {115128799, 133619598}, + {115182197, 133661804}, + {115330101, 133740707}, + {115455398, 133799407}, + {115595001, 133836807}, + {115651000, 133851806}, + {116413604, 134055206}, + {116654495, 134097900}, + {116887603, 134075210}, + {117071098, 134040405}, + {117458801, 133904891}, + {118057998, 133572601}, + {118546997, 133261001}, + {118578498, 133239395}, + {118818603, 133011596}, + {121109695, 130501495}, + {122661598, 128760101}, + {142458190, 102765197}, + {142789001, 102099601}, + {143105010, 101386505}, + {143154800, 101239700}, + {143193908, 100825500}, + {143160507, 100282501}, + {143133499, 100083602}, + {143092697, 99880500}, + {143050689, 99766700}, + {142657501, 98974502}, + {142580307, 98855201}, + {122267196, 76269897}, + {122036399, 76105003}, + {121832000, 76028305}, + {121688796, 75983108}, + {121591598, 75955001}, + {121119697, 75902099}, + {120789596, 75953498}, + {120487495, 76041900}, + {120042701, 76365798}, + {119886695, 76507301}, + {119774200, 76635299}, + {119739097, 76686904}, + {119685195, 76798202}, + {119456199, 77320098}, + {106877601, 119561401}, + {106854797, 119645103}, + {106849098, 119668807}, + {106847099, 119699005}, + {106840400, 119801406}, + {106807800, 120719299}, + {106806098, 120862808}, + {106808700, 120885696}, + }, + { + {99663352, 105328948}, + {99690048, 105797050}, + {99714050, 105921447}, + {99867248, 106439949}, + {100111557, 107256546}, + {104924850, 120873649}, + {105106155, 121284049}, + {105519149, 122184753}, + {105586051, 122292655}, + {105665054, 122400154}, + {106064147, 122838455}, + {106755355, 123453453}, + {106929054, 123577651}, + {107230346, 123771949}, + {107760650, 123930648}, + {108875854, 124205154}, + {108978752, 124228050}, + {131962051, 123738754}, + {135636047, 123513954}, + {135837249, 123500747}, + {136357345, 123442749}, + {136577346, 123394454}, + {136686645, 123367752}, + {137399353, 123185050}, + {137733947, 123063156}, + {137895355, 122997154}, + {138275650, 122829154}, + {138394256, 122767753}, + {138516845, 122670150}, + {139987045, 121111251}, + {149171646, 108517349}, + {149274353, 108372848}, + {149314758, 108314247}, + {149428848, 108140846}, + {149648651, 107650550}, + {149779541, 107290252}, + {149833343, 107115249}, + {149891357, 106920051}, + {150246353, 105630249}, + {150285842, 105423454}, + {150320953, 105233749}, + {150336639, 104981552}, + {150298049, 104374053}, + {150287948, 104271850}, + {150026153, 103481147}, + {149945449, 103301651}, + {149888946, 103213455}, + {149800949, 103103851}, + {149781143, 103079650}, + {149714141, 103005447}, + {149589950, 102914146}, + {149206054, 102698951}, + {128843856, 91378150}, + {128641754, 91283050}, + {119699851, 87248046}, + {117503555, 86311950}, + {117145851, 86178054}, + {116323654, 85925048}, + {115982551, 85834045}, + {115853050, 85819252}, + {115222549, 85771949}, + {107169357, 85771949}, + {107122650, 85776451}, + {106637145, 85831550}, + {105095046, 86423950}, + {104507850, 86703750}, + {104384155, 86763153}, + {104332351, 86790145}, + {104198257, 86882644}, + {103913757, 87109451}, + {103592346, 87388450}, + {103272651, 87666748}, + {103198051, 87779052}, + {101698654, 90600952}, + {101523551, 90958450}, + {101360054, 91347450}, + {101295349, 91542144}, + {99774551, 98278152}, + {99746749, 98417755}, + {99704055, 98675453}, + {99663352, 99022949}, + {99663352, 105328948}, + }, + { + {95036499, 101778106}, + {95479103, 102521301}, + {95587295, 102700103}, + {98306503, 106984901}, + {98573303, 107377700}, + {100622406, 110221702}, + {101252304, 111089599}, + {104669502, 115750198}, + {121838500, 131804107}, + {122000503, 131943695}, + {122176803, 132023406}, + {122474105, 132025390}, + {122703804, 132023101}, + {123278808, 131878112}, + {124072998, 131509109}, + {124466506, 131102508}, + {152779296, 101350906}, + {153016510, 101090606}, + {153269699, 100809097}, + {153731994, 100214096}, + {153927902, 99939796}, + {154641098, 98858100}, + {154864303, 98517601}, + {155056594, 97816604}, + {155083511, 97645599}, + {155084899, 97462097}, + {154682601, 94386100}, + {154376007, 92992599}, + {154198593, 92432403}, + {153830505, 91861701}, + {153686904, 91678695}, + {151907104, 90314605}, + {151368896, 89957603}, + {146983306, 87632202}, + {139082397, 84273605}, + {128947692, 80411399}, + {121179000, 78631301}, + {120264701, 78458198}, + {119279510, 78304603}, + {116913101, 77994102}, + {116151504, 77974601}, + {115435104, 78171401}, + {113544105, 78709106}, + {113231002, 78879898}, + {112726303, 79163604}, + {112310501, 79411102}, + {96169998, 97040802}, + {95196304, 98364402}, + {95167800, 98409599}, + {95083503, 98570701}, + {94986999, 99022201}, + {94915100, 100413299}, + {95036499, 101778106}, + }, + { + {82601348, 96004745}, + {83443847, 128861953}, + {84173248, 136147354}, + {104268249, 141388839}, + {104373649, 141395355}, + {105686950, 141389541}, + {149002243, 140435653}, + {159095748, 133388244}, + {159488143, 133112655}, + {159661849, 132894653}, + {163034149, 128290847}, + {164801849, 124684249}, + {167405746, 72553245}, + {167330444, 71960746}, + {167255050, 71791847}, + {167147155, 71572044}, + {166999557, 71341545}, + {166723937, 70961448}, + {166238250, 70611541}, + {165782348, 70359649}, + {165649444, 70286849}, + {165332946, 70122344}, + {165164154, 70062248}, + {164879150, 69967544}, + {164744949, 69928947}, + {164691452, 69915245}, + {164669448, 69910247}, + {159249938, 68738952}, + {158528259, 68704742}, + {147564254, 68604644}, + {116196655, 68982742}, + {115364944, 69005050}, + {115193145, 69013549}, + {101701248, 70984146}, + {93918449, 72233047}, + {93789749, 72285247}, + {93777046, 72292648}, + {93586044, 72444046}, + {93366348, 72662345}, + {93301147, 72745452}, + {93260345, 72816345}, + {83523948, 92593849}, + {83430145, 92810241}, + {82815048, 94665542}, + {82755554, 94858551}, + {82722953, 95014350}, + {82594253, 95682350}, + {82601348, 96004745}, + }, + { + {110371345, 125796493}, + {110411544, 126159599}, + {110445251, 126362899}, + {111201950, 127863800}, + {112030052, 129270492}, + {112367050, 129799301}, + {113088348, 130525604}, + {113418144, 130853698}, + {117363449, 134705505}, + {118131149, 135444793}, + {118307449, 135607299}, + {119102546, 136297195}, + {119385047, 136531906}, + {120080848, 137094390}, + {120794845, 137645401}, + {121150344, 137896392}, + {121528945, 138162506}, + {121644546, 138242095}, + {122142349, 138506408}, + {127540847, 141363006}, + {127933448, 141516204}, + {128728256, 141766799}, + {129877151, 141989898}, + {130626052, 142113891}, + {130912246, 142135192}, + {131246841, 142109100}, + {131496047, 142027404}, + {131596252, 141957794}, + {131696350, 141873504}, + {131741043, 141803405}, + {138788452, 128037704}, + {139628646, 125946197}, + {138319351, 112395401}, + {130035354, 78066703}, + {124174049, 69908798}, + {123970649, 69676895}, + {123874252, 69571899}, + {123246643, 68961303}, + {123193954, 68924400}, + {121952049, 68110000}, + {121787345, 68021896}, + {121661544, 67970306}, + {121313446, 67877502}, + {121010650, 67864799}, + {120995346, 67869705}, + {120583747, 68122207}, + {120509750, 68170600}, + {120485847, 68189102}, + {112160148, 77252403}, + {111128646, 78690704}, + {110969650, 78939407}, + {110512550, 79663406}, + {110397247, 79958206}, + {110371345, 80038299}, + {110371345, 125796493}, + }, + { + {112163948, 137752700}, + {112171150, 137837997}, + {112203048, 137955993}, + {112240150, 138008209}, + {112343246, 138111099}, + {112556243, 138223205}, + {112937149, 138307998}, + {113318748, 138331909}, + {126076446, 138428298}, + {126165245, 138428695}, + {126312446, 138417907}, + {134075546, 136054504}, + {134322753, 135949401}, + {134649948, 135791198}, + {135234954, 135493408}, + {135290145, 135464691}, + {135326248, 135443695}, + {135920043, 135032592}, + {135993850, 134975799}, + {136244247, 134761199}, + {136649444, 134378692}, + {137067153, 133964294}, + {137188156, 133839096}, + {137298049, 133704498}, + {137318954, 133677795}, + {137413543, 133522201}, + {137687347, 133043792}, + {137816055, 132660705}, + {137836044, 131747695}, + {137807144, 131318603}, + {136279342, 119078704}, + {136249053, 118945800}, + {127306152, 81348602}, + {127114852, 81065505}, + {127034248, 80951400}, + {126971649, 80893707}, + {125093551, 79178001}, + {124935745, 79036003}, + {115573745, 71767601}, + {115411148, 71701805}, + {115191947, 71621002}, + {115017051, 71571304}, + {114870147, 71572898}, + {113869552, 71653900}, + {112863349, 72976104}, + {112756347, 73223899}, + {112498947, 73832206}, + {112429351, 73998504}, + {112366050, 74168098}, + {112273246, 74487098}, + {112239250, 74605400}, + {112195549, 74899902}, + {112163948, 75280700}, + {112163948, 137752700}, + }, + { + {78562347, 141451843}, + {79335624, 142828186}, + {79610343, 143188140}, + {79845077, 143445724}, + {81379173, 145126678}, + {81826751, 145577178}, + {82519126, 146209472}, + {83964973, 147280502}, + {85471343, 148377868}, + {86115539, 148760803}, + {88839988, 150281188}, + {89021247, 150382217}, + {90775917, 151320526}, + {91711380, 151767288}, + {92757591, 152134277}, + {93241058, 152201766}, + {113402145, 153091995}, + {122065994, 146802825}, + {164111053, 91685104}, + {164812759, 90470565}, + {165640182, 89037384}, + {171027435, 66211853}, + {171450805, 64406951}, + {171463150, 64349624}, + {171469787, 64317184}, + {171475585, 64282028}, + {171479812, 64253036}, + {171483596, 64210433}, + {171484405, 64153488}, + {171483001, 64140785}, + {171481719, 64132751}, + {171478668, 64115478}, + {171472702, 64092437}, + {171462768, 64075408}, + {171448089, 64061347}, + {171060333, 63854789}, + {169640502, 63197738}, + {169342147, 63086711}, + {166413101, 62215766}, + {151881774, 58826736}, + {146010574, 57613151}, + {141776962, 56908004}, + {140982940, 57030628}, + {139246154, 57540817}, + {139209609, 57566974}, + {127545310, 66015594}, + {127476654, 66104812}, + {105799087, 98784980}, + {85531921, 129338897}, + {79319717, 138704513}, + {78548156, 140188079}, + {78530448, 140530456}, + {78515594, 141299987}, + {78562347, 141451843}, + }, + { + {77755004, 128712387}, + {78073547, 130552612}, + {78433593, 132017822}, + {79752693, 136839645}, + {80479461, 138929260}, + {80903221, 140119674}, + {81789848, 141978454}, + {82447387, 143105575}, + {83288436, 144264328}, + {84593582, 145846542}, + {84971939, 146242813}, + {86905578, 147321304}, + {87874191, 147594131}, + {89249092, 147245132}, + {89541542, 147169052}, + {98759140, 144071609}, + {98894233, 144024261}, + {113607818, 137992843}, + {128324356, 131649307}, + {139610076, 126210189}, + {146999572, 122112884}, + {147119415, 122036041}, + {148717330, 120934616}, + {149114776, 120652725}, + {171640289, 92086624}, + {171677917, 92036224}, + {171721191, 91973869}, + {171851608, 91721557}, + {171927795, 91507644}, + {172398696, 89846351}, + {172436752, 89559959}, + {169361663, 64753852}, + {169349029, 64687164}, + {169115127, 63616458}, + {168965728, 63218254}, + {168911788, 63121219}, + {168901611, 63106807}, + {168896896, 63100486}, + {168890686, 63092460}, + {168876586, 63081058}, + {168855529, 63067909}, + {168808746, 63046024}, + {167251068, 62405864}, + {164291717, 63716899}, + {152661651, 69910156}, + {142312393, 75421356}, + {78778053, 111143295}, + {77887222, 113905914}, + {77591979, 124378433}, + {77563247, 126586669}, + {77755004, 128712387}, + }, + { + {105954101, 131182754}, + {105959197, 131275848}, + {105972801, 131473556}, + {105981498, 131571044}, + {106077903, 132298553}, + {106134094, 132715255}, + {106155700, 132832351}, + {106180099, 132942657}, + {106326797, 133590347}, + {106375099, 133719345}, + {106417602, 133829345}, + {106471000, 133930343}, + {106707901, 134308654}, + {106728401, 134340545}, + {106778198, 134417556}, + {106832397, 134491851}, + {106891296, 134562957}, + {106981300, 134667358}, + {107044204, 134736557}, + {107111000, 134802658}, + {107180999, 134865661}, + {107291099, 134961349}, + {107362998, 135020355}, + {107485397, 135112854}, + {107558998, 135166946}, + {107690399, 135256256}, + {107765098, 135305252}, + {107903594, 135390548}, + {108183898, 135561843}, + {108459503, 135727951}, + {108532501, 135771850}, + {108796096, 135920059}, + {108944099, 135972549}, + {109102401, 136010757}, + {109660598, 136071044}, + {109971595, 136100250}, + {110209594, 136116851}, + {110752799, 136122344}, + {111059906, 136105758}, + {111152900, 136100357}, + {111237197, 136091354}, + {111316101, 136075057}, + {111402000, 136050949}, + {111475296, 136026657}, + {143546600, 123535949}, + {143899002, 122454353}, + {143917404, 122394348}, + {143929199, 122354652}, + {143944793, 122295753}, + {143956207, 122250953}, + {143969497, 122192253}, + {143980102, 122143249}, + {143991302, 122083053}, + {144000396, 122031753}, + {144009796, 121970954}, + {144017303, 121917655}, + {144025405, 121850250}, + {144030609, 121801452}, + {144036804, 121727455}, + {144040008, 121683456}, + {144043502, 121600952}, + {144044708, 121565048}, + {144045700, 121470352}, + {144045898, 121446952}, + {144041503, 121108657}, + {144037506, 121023452}, + {143733795, 118731750}, + {140461395, 95238647}, + {140461105, 95236755}, + {140433807, 95115249}, + {140392608, 95011650}, + {134840805, 84668952}, + {134824996, 84642456}, + {134781494, 84572952}, + {134716796, 84480850}, + {127473899, 74425453}, + {127467002, 74417152}, + {127431701, 74381652}, + {127402603, 74357147}, + {127375503, 74334457}, + {127294906, 74276649}, + {127181900, 74207649}, + {127177597, 74205451}, + {127123901, 74178451}, + {127078903, 74155853}, + {127028999, 74133148}, + {126870803, 74070953}, + {126442901, 73917648}, + {126432403, 73914955}, + {126326004, 73889846}, + {126262405, 73880645}, + {126128097, 73878456}, + {125998199, 73877655}, + {108701095, 74516647}, + {108644599, 74519348}, + {108495201, 74528953}, + {108311302, 74556457}, + {108252799, 74569458}, + {108079002, 74612152}, + {107981399, 74638954}, + {107921295, 74657951}, + {107862197, 74685951}, + {107601303, 74828948}, + {107546997, 74863449}, + {107192794, 75098846}, + {107131202, 75151153}, + {106260002, 76066146}, + {106195098, 76221145}, + {106168502, 76328453}, + {106144699, 76437454}, + {106124496, 76538452}, + {106103698, 76649650}, + {106084197, 76761650}, + {106066299, 76874450}, + {106049903, 76987457}, + {106034797, 77101150}, + {106020904, 77214950}, + {106008201, 77328948}, + {105996902, 77443145}, + {105986099, 77565849}, + {105977005, 77679649}, + {105969299, 77793151}, + {105963096, 77906349}, + {105958297, 78019149}, + {105955299, 78131454}, + {105954101, 78242950}, + {105954101, 131182754}, + }, + { + {91355499, 77889205}, + {114834197, 120804504}, + {114840301, 120815200}, + {124701507, 132324798}, + {124798805, 132436706}, + {124901504, 132548309}, + {125126602, 132788909}, + {125235000, 132901901}, + {125337707, 133005401}, + {125546302, 133184707}, + {125751602, 133358703}, + {126133300, 133673004}, + {126263900, 133775604}, + {126367401, 133855499}, + {126471908, 133935104}, + {126596008, 134027496}, + {127119308, 134397094}, + {127135101, 134408203}, + {127433609, 134614303}, + {127554107, 134695709}, + {128155395, 135070907}, + {128274505, 135141799}, + {129132003, 135573211}, + {129438003, 135713195}, + {129556106, 135767196}, + {131512695, 136648498}, + {132294509, 136966598}, + {132798400, 137158798}, + {133203796, 137294494}, + {133377410, 137350799}, + {133522399, 137396606}, + {133804397, 137480697}, + {134017807, 137542205}, + {134288696, 137618408}, + {134564208, 137680099}, + {134844696, 137740097}, + {135202606, 137807098}, + {135489105, 137849807}, + {135626800, 137864898}, + {135766906, 137878692}, + {135972808, 137895797}, + {136110107, 137905502}, + {136235000, 137913101}, + {136485809, 137907196}, + {139194305, 136979202}, + {140318298, 136536209}, + {140380004, 136505004}, + {140668197, 136340499}, + {140724304, 136298904}, + {140808197, 136228210}, + {140861801, 136180603}, + {140917404, 136129104}, + {140979202, 136045104}, + {141022903, 135984207}, + {147591094, 126486999}, + {147661315, 126356101}, + {147706100, 126261901}, + {147749099, 126166000}, + {147817108, 126007507}, + {147859100, 125908599}, + {153693206, 111901100}, + {153731109, 111807800}, + {153760894, 111698806}, + {158641998, 92419303}, + {158644500, 92263702}, + {158539703, 92013504}, + {158499603, 91918899}, + {158335510, 91626800}, + {158264007, 91516304}, + {158216308, 91449203}, + {158178314, 91397506}, + {158094299, 91283203}, + {157396408, 90368202}, + {157285491, 90224700}, + {157169906, 90079200}, + {157050003, 89931304}, + {156290603, 89006805}, + {156221099, 88922897}, + {156087707, 88771003}, + {155947906, 88620498}, + {155348602, 88004203}, + {155113204, 87772796}, + {154947296, 87609703}, + {154776306, 87448204}, + {154588806, 87284301}, + {153886306, 86716400}, + {153682403, 86560501}, + {152966705, 86032402}, + {152687805, 85828704}, + {152484313, 85683204}, + {152278808, 85539001}, + {150878204, 84561401}, + {150683013, 84426498}, + {150599395, 84372703}, + {150395599, 84243202}, + {149988906, 83989395}, + {149782897, 83864501}, + {149568908, 83739799}, + {148872100, 83365303}, + {148625396, 83242202}, + {128079010, 73079605}, + {127980506, 73031005}, + {126701103, 72407104}, + {126501701, 72312202}, + {126431503, 72280601}, + {126311706, 72230606}, + {126260101, 72210899}, + {126191902, 72187599}, + {126140106, 72170303}, + {126088203, 72155303}, + {126036102, 72142700}, + {125965904, 72126899}, + {125913009, 72116600}, + {125859603, 72108505}, + {125788101, 72100296}, + {125733505, 72094398}, + {125678100, 72090400}, + {125621398, 72088302}, + {125548805, 72087303}, + {125490707, 72086898}, + {125430908, 72088203}, + {125369804, 72091094}, + {125306900, 72095306}, + {125233505, 72100997}, + {125168609, 72106506}, + {125102203, 72113601}, + {125034103, 72122207}, + {124964309, 72132095}, + {124890701, 72143707}, + {124819305, 72155105}, + {91355499, 77889099}, + {91355499, 77889205}, + }, + { + {84531845, 127391708}, + {84916946, 130417510}, + {86133247, 131166900}, + {86338447, 131292892}, + {86748847, 131544799}, + {102193946, 136599502}, + {103090942, 136796798}, + {103247146, 136822509}, + {104083549, 136911499}, + {106119346, 137109802}, + {106265853, 137122207}, + {106480247, 137139205}, + {110257850, 137133605}, + {116917747, 136131408}, + {117054946, 136106704}, + {119043945, 135244293}, + {119249046, 135154708}, + {136220947, 126833007}, + {165896347, 91517105}, + {166032546, 91314697}, + {166055435, 91204902}, + {166056152, 91176803}, + {166047256, 91100006}, + {166039733, 91063705}, + {165814849, 90080802}, + {165736450, 89837707}, + {165677246, 89732101}, + {165676956, 89731803}, + {165560241, 89629302}, + {154419952, 82608505}, + {153822143, 82239700}, + {137942749, 74046104}, + {137095245, 73845504}, + {135751342, 73537704}, + {134225952, 73208602}, + {132484344, 72860801}, + {124730346, 73902000}, + {120736549, 74464401}, + {100401245, 78685401}, + {90574645, 90625701}, + {90475944, 90748809}, + {90430747, 90808700}, + {90321548, 90958305}, + {90254852, 91077903}, + {90165641, 91244003}, + {90134941, 91302398}, + {84474647, 103745697}, + {84328048, 104137901}, + {84288543, 104327606}, + {84038047, 106164604}, + {84013351, 106368698}, + {83943847, 110643203}, + {84531845, 127391708}, + }, +}; + +using Slic3r::ExPolygon; +using Slic3r::Polygon; +using Slic3r::Polygons; +using Slic3r::ExPolygons; + +struct MyPoly { + ExPolygon poly; + MyPoly(Polygon contour, Polygons holes) + : poly(std::move(contour)) + { + poly.holes = std::move(holes); + } + + operator ExPolygon () { return poly; } +}; + +const TestDataEx PRUSA_PART_POLYGONS_EX = { + ExPolygons{ + // "x-carriage.stl": + MyPoly{{ + {-22097700, -14878600}, {-21981300, -14566100}, + {-21807600, -14303900}, {-21354100, -13619200}, + {-20514800, -12806600}, {-19500000, -12163900}, + {-18553700, -11796600}, {-18354100, -11719200}, + {-18146200, -11680600}, {-17127200, -11491800}, + {-15872800, -11491800}, {-14853800, -11680600}, + {-14645900, -11719200}, {-14446300, -11796600}, + {-13500000, -12163900}, {-12485200, -12806600}, + {-11645900, -13619200}, {-11192400, -14303900}, + {-11018700, -14566100}, {-10902300, -14878600}, + {-10857000, -15000000}, {-2200000, -15000000}, + {-2242640, -14957400}, {500000, -12214700}, + {500000, 5500000}, {9450000, 5500000}, + {9450000, 7500000}, {273885, 7500000}, + {273885, 11050000}, {2706110, 11050000}, + {2706110, 11000000}, {9500000, 11000000}, + {9500000, 66500000}, {7466310, 68533696}, + {999999, 75000000}, {-8500000, 75000000}, + {-8500000, 74250000}, {-7500000, 74250000}, + {-7500000, 71750000}, {-8500000, 71750000}, + {-8500000, 68250000}, {-7500000, 68250000}, + {-7500000, 65750000}, {-8500000, 65750000}, + {-8500000, 64000000}, {-12500000, 64000000}, + {-12500000, 67000000}, {-14500000, 67000000}, + {-14500000, 73000000}, {-12500000, 73000000}, + {-12500000, 75000000}, {-23000000, 75000000}, + {-23000000, 59500000}, {-38500000, 59500000}, + {-42500000, 55500000}, {-42500000, 19536000}, + {-36767700, 18000000}, {-34000000, 18000000}, + {-34000000, 13000000}, {-39900000, 13000000}, + {-39900000, 11000000}, {-34000000, 11000000}, + {-34000000, 7500000}, {-39900000, 7500000}, + {-39900000, 5500000}, {-34000000, 5500000}, + {-34000000, -11714700}, {-30757400, -14957400}, + {-30800000, -15000000}, {-22143000, -15000000}, + }, + { + { + {2311850, 65709900}, {2076590, 65759904}, + {1943770, 65788100}, {1600000, 65941200}, + {1362567, 66113636}, {1329590, 66137604}, + {1295560, 66162300}, {1043769, 66442000}, + {855618, 66767900}, {739334, 67125800}, + {714193, 67365000}, {700000, 67500000}, + {714193, 67635000}, {739334, 67874200}, + {855618, 68232104}, {1043769, 68558000}, + {1295560, 68837696}, {1329590, 68862400}, + {1352596, 68879119}, {1600000, 69058800}, + {1943770, 69211896}, {2076590, 69240096}, + {2311850, 69290104}, {2688150, 69290104}, + {3056230, 69211896}, {3400000, 69058800}, + {3541910, 68955704}, {3704430, 68837696}, + {3762210, 68773496}, {3865370, 68658896}, + {3956230, 68558000}, {4024119, 68440400}, + {4065821, 68368176}, {4144380, 68232104}, + {4260660, 67874200}, {4300000, 67500000}, + {4260660, 67125800}, {4144380, 66767900}, + {4024119, 66559600}, {3956230, 66442000}, + {3865370, 66341104}, {3762210, 66226500}, + {3704430, 66162300}, {3541910, 66044296}, + {3400000, 65941200}, {3056230, 65788100}, + {2688150, 65709900}, + }, + { + {-27606700, 54303400}, {-27818500, 54330100}, + {-27896000, 54350000}, {-28025300, 54383200}, + {-28223800, 54461800}, {-28410900, 54564600}, + {-28583600, 54690100}, {-28739200, 54836300}, + {-28875300, 55000800}, {-28989700, 55181000}, + {-29080600, 55374200}, {-29146600, 55577200}, + {-29150000, 55595100}, {-29186600, 55786900}, + {-29200000, 56000000}, {-29186600, 56213100}, + {-29150000, 56404900}, {-29146600, 56422800}, + {-29080600, 56625800}, {-28989700, 56819000}, + {-28875300, 56999200}, {-28739200, 57163700}, + {-28583600, 57309900}, {-28410900, 57435400}, + {-28223800, 57538200}, {-28025300, 57616800}, + {-27896000, 57650000}, {-27818500, 57669900}, + {-27606700, 57696600}, {-27393300, 57696600}, + {-27181400, 57669900}, {-27104000, 57650000}, + {-26974700, 57616800}, {-26776200, 57538200}, + {-26589100, 57435400}, {-26416400, 57309900}, + {-26260800, 57163700}, {-26124700, 56999200}, + {-26010300, 56819000}, {-25919400, 56625800}, + {-25853400, 56422800}, {-25850000, 56404900}, + {-25813400, 56213100}, {-25800000, 56000000}, + {-25813400, 55786900}, {-25850000, 55595100}, + {-25853400, 55577200}, {-25919400, 55374200}, + {-26010300, 55181000}, {-26124700, 55000800}, + {-26260800, 54836300}, {-26416400, 54690100}, + {-26589100, 54564600}, {-26776200, 54461800}, + {-26974700, 54383200}, {-27104000, 54350000}, + {-27181400, 54330100}, {-27393300, 54303400}, + }, + { + {-4106740, 54303400}, {-4318550, 54330100}, + {-4396010, 54350000}, {-4525330, 54383200}, + {-4723820, 54461800}, {-4910900, 54564600}, + {-5083620, 54690100}, {-5239250, 54836300}, + {-5375330, 55000800}, {-5489720, 55181000}, + {-5580620, 55374200}, {-5646590, 55577200}, + {-5650000, 55595100}, {-5686590, 55786900}, + {-5700000, 56000000}, {-5686590, 56213100}, + {-5650000, 56404900}, {-5646590, 56422800}, + {-5580620, 56625800}, {-5489720, 56819000}, + {-5375330, 56999200}, {-5239250, 57163700}, + {-5083620, 57309900}, {-4910900, 57435400}, + {-4723820, 57538200}, {-4525330, 57616800}, + {-4396010, 57650000}, {-4318550, 57669900}, + {-4106740, 57696600}, {-3893260, 57696600}, + {-3681450, 57669900}, {-3603990, 57650000}, + {-3474670, 57616800}, {-3276170, 57538200}, + {-3089090, 57435400}, {-2916380, 57309900}, + {-2760750, 57163700}, {-2624670, 56999200}, + {-2510280, 56819000}, {-2419380, 56625800}, + {-2353410, 56422800}, {-2350000, 56404900}, + {-2313400, 56213100}, {-2300000, 56000000}, + {-2313400, 55786900}, {-2350000, 55595100}, + {-2353410, 55577200}, {-2419380, 55374200}, + {-2510280, 55181000}, {-2624670, 55000800}, + {-2760750, 54836300}, {-2916380, 54690100}, + {-3089090, 54564600}, {-3276170, 54461800}, + {-3474670, 54383200}, {-3603990, 54350000}, + {-3681450, 54330100}, {-3893260, 54303400}, + }, + { + {-16103600, 27353300}, {-16309200, 27379200}, + {-16509899, 27430800}, {-16702499, 27507000}, + {-16884100, 27606900}, {-17051800, 27728700}, + {-17202800, 27870500}, {-17334900, 28030200}, + {-17445900, 28205100}, {-17534100, 28392600}, + {-17598200, 28589700}, {-17637000, 28793200}, + {-17650000, 29000000}, {-17637000, 29206800}, + {-17598200, 29410300}, {-17534100, 29607400}, + {-17445900, 29794900}, {-17334900, 29969800}, + {-17202800, 30129500}, {-17051800, 30271300}, + {-16884100, 30393100}, {-16702499, 30493000}, + {-16509899, 30569200}, {-16309200, 30620800}, + {-16103600, 30646700}, {-15896400, 30646700}, + {-15690800, 30620800}, {-15490100, 30569200}, + {-15297500, 30493000}, {-15115900, 30393100}, + {-14948200, 30271300}, {-14797200, 30129500}, + {-14665100, 29969800}, {-14554100, 29794900}, + {-14465900, 29607400}, {-14401800, 29410300}, + {-14363000, 29206800}, {-14350000, 29000000}, + {-14363000, 28793200}, {-14401800, 28589700}, + {-14465900, 28392600}, {-14554100, 28205100}, + {-14665100, 28030200}, {-14797200, 27870500}, + {-14948200, 27728700}, {-15115900, 27606900}, + {-15297500, 27507000}, {-15490100, 27430800}, + {-15690800, 27379200}, {-15896400, 27353300}, + }, + { + {-5809180, 22879200}, {-6202540, 23007000}, + {-6551750, 23228700}, {-6834880, 23530200}, + {-7034130, 23892600}, {-7136990, 24293200}, + {-7136990, 24706800}, {-7034130, 25107400}, + {-6834880, 25469800}, {-6551750, 25771300}, + {-6202540, 25993000}, {-5809180, 26120800}, + {-5396390, 26146700}, {-4990120, 26069200}, + {-4615890, 25893100}, {-4297200, 25629500}, + {-4054090, 25294900}, {-3901840, 24910300}, + {-3850000, 24500000}, {-3901840, 24089700}, + {-4054090, 23705100}, {-4297200, 23370500}, + {-4615890, 23106900}, {-4990120, 22930800}, + {-5396390, 22853300}, + }, + { + {-28809200, 22879200}, {-29202500, 23007000}, + {-29551800, 23228700}, {-29834900, 23530200}, + {-30034100, 23892600}, {-30137000, 24293200}, + {-30137000, 24706800}, {-30034100, 25107400}, + {-29834900, 25469800}, {-29551800, 25771300}, + {-29202500, 25993000}, {-28809200, 26120800}, + {-28396400, 26146700}, {-27990100, 26069200}, + {-27615900, 25893100}, {-27297200, 25629500}, + {-27054100, 25294900}, {-26901800, 24910300}, + {-26850000, 24500000}, {-26901800, 24089700}, + {-27054100, 23705100}, {-27297200, 23370500}, + {-27615900, 23106900}, {-27990100, 22930800}, + {-28396400, 22853300}, + }, + { + {-15718329, 8800000}, + {-15729700, 8808230}, + {-15736300, 8814060}, + {-15742800, 8833890}, + {-15876410, 9243607}, + {-15729700, 9696850}, + {-14969700, 10251100}, + {-14030300, 10251100}, + {-13270300, 9696850}, + {-13123590, 9243607}, + {-13257200, 8833890}, + {-13263700, 8814060}, + {-13270300, 8808230}, + {-13281671, 8800000}, + }, + }}, + }, + ExPolygons{ + // "Spool-holder.stl": + MyPoly{{ + {338485792, -31307222}, {338867040, -31018436}, + {339248320, -30729652}, {339769915, -30334566}, + {340010848, -30152082}, {340392096, -29863298}, + {340773344, -29574512}, {341244704, -27899436}, + {341480384, -27061900}, {342060734, -24999444}, + {342187424, -24549286}, {343068058, -21419626}, + {343130112, -21199134}, {343521972, -19806477}, + {344953440, -14719350}, {345583712, -12479458}, + {345898880, -11359512}, {346213984, -10239566}, + {346529152, -9119620}, {346684120, -8568830}, + {347258496, -6527694}, {348879776, -765989}, + {351121248, 7199785}, {351160318, 7338666}, + {351581888, 8836852}, {358349952, 32889144}, + {361733984, 44915292}, {362502080, 47644968}, + {365226370, 57326618}, {367181933, 64276284}, + {369782208, 73517048}, {372004549, 81414857}, + {375270080, 93019880}, {377062304, 99389120}, + {380702368, 112325160}, {387982496, 138197232}, + {390913664, 148614048}, {392379232, 153822448}, + {392462848, 154165648}, {392500992, 154371968}, + {392523776, 154527056}, {392558720, 154903888}, + {392560704, 154943056}, {392564832, 155292544}, + {392554016, 155525040}, {392539872, 155688624}, + {392482592, 156087152}, {392479040, 156106240}, + {392392608, 156482704}, {392336512, 156674688}, + {392270816, 156869776}, {392130112, 157218896}, + {392119264, 157242992}, {391941088, 157597536}, + {391865920, 157728704}, {391740320, 157929344}, + {391551872, 158195776}, {391521632, 158235296}, + {391290048, 158513360}, {391187328, 158624592}, + {391050656, 158762640}, {390808320, 158983152}, + {390768160, 159017008}, {390567456, 159175840}, + {390331776, 159342352}, {390300192, 159363104}, + {389874464, 159612112}, {389791584, 159654240}, + {389337216, 159852608}, {389252448, 159883872}, + {388769664, 160029808}, {388694016, 160047936}, + {388177408, 160139328}, {388128128, 160145120}, + {387566176, 160176800}, {375407744, 160176800}, + {374915072, 160152480}, {374676896, 160123104}, + {374431712, 160080592}, {374176224, 160022768}, + {373936576, 159955472}, {373914144, 159948512}, + {373647424, 159856688}, {373378176, 159746368}, + {373213376, 159669552}, {373108832, 159616976}, + {372841920, 159468256}, {372580064, 159300432}, + {372535328, 159269408}, {372325888, 159114096}, + {372081888, 158910256}, {371928512, 158767776}, + {371850368, 158690384}, {371633408, 158456224}, + {371432736, 158209856}, {371413792, 158184848}, + {371249664, 157953552}, {371085088, 157689664}, + {371004448, 157545600}, {370939520, 157420656}, + {370813088, 157148864}, {370705536, 156876560}, + {368543808, 150896416}, {363439712, 136776352}, + {358631104, 123473712}, {358408023, 122856568}, + {356409504, 117327816}, {355922364, 115980139}, + {355915437, 115960977}, {352669088, 106980280}, + {351986938, 105093166}, {351083520, 102593952}, + {349781616, 98992320}, {348098080, 94334952}, + {340262048, 72657256}, {338954668, 69040480}, + {336962208, 63528480}, {332255104, 50506656}, + {327202016, 36527760}, {322789144, 24319856}, + {322760544, 24240790}, {322533686, 23613197}, + {322519552, 23574124}, {320672032, 18463012}, + {320578304, 18203810}, {320531456, 18074210}, + {320484608, 17944606}, {320437760, 17815006}, + {320297248, 17426202}, {320297248, 9238203}, + {321164066, 9238176}, {321689312, 9238155}, + {323777376, 9238073}, {324473408, 9238046}, + {325169440, 9238018}, {325496384, 10782991}, + {325496360, 12868520}, {327892320, 12868504}, + {329090336, 12868496}, {330288320, 12868487}, + {331486336, 12868480}, {332684279, 12868472}, + {332096736, 10092249}, {332096736, -26761938}, + {325697024, -26761692}, {325697024, -26311692}, + {324897056, -26311692}, {323897056, -27061692}, + {323897056, -29536704}, {323897088, -30511784}, + {323897088, -32461944}, {323897091, -32461944}, + {328084288, -32462100}, {329480000, -32462150}, + {332271456, -32462258}, {335062912, -32462360}, + {336960768, -32462360}, + }, + { + { + {376588032, 136952960}, {375810912, 137178704}, + {375411104, 137361136}, {375032960, 137582800}, + {374679360, 137841776}, {374353152, 138136224}, + {374058688, 138462448}, {373799680, 138816048}, + {373578048, 139194192}, {373395584, 139594016}, + {373169856, 140371136}, {373093728, 141176800}, + {373169856, 141982464}, {373395584, 142759600}, + {373578048, 143159392}, {373799680, 143537536}, + {374058688, 143891136}, {374353152, 144217360}, + {374679360, 144511824}, {375032960, 144770816}, + {375411104, 144992464}, {375810912, 145174896}, + {376588032, 145400656}, {377393696, 145476800}, + {378199360, 145400656}, {378976512, 145174896}, + {379376320, 144992480}, {379754464, 144770816}, + {380108064, 144511808}, {380434272, 144217360}, + {380728736, 143891136}, {380987744, 143537536}, + {381209376, 143159392}, {381391776, 142759600}, + {381617568, 141982464}, {381693696, 141176800}, + {381617568, 140371136}, {381391776, 139594000}, + {381209376, 139194192}, {380987744, 138816064}, + {380728736, 138462464}, {380434272, 138136240}, + {380108064, 137841792}, {379754464, 137582800}, + {379376320, 137361136}, {378976512, 137178704}, + {378199360, 136952960}, {377393696, 136876800}, + }, + { + {354604704, 97626944}, {355293600, 99532704}, + {355982496, 101438472}, {356671392, 103344232}, + {357360288, 105250000}, {358424054, 108192839}, + {358738080, 109061520}, {359426976, 110967296}, + {360115840, 112873056}, {362111392, 113825960}, + {368097952, 116684672}, {370093472, 117637584}, + {372089024, 118590488}, {374084544, 119543392}, + {378075584, 121449192}, {377003072, 117637704}, + {375930560, 113826200}, {375394304, 111920456}, + {374321792, 108108952}, {373249280, 104297464}, + {368952928, 102391616}, {362508448, 99532856}, + {360360288, 98579944}, {358212128, 97627024}, + {356063968, 96674096}, {353915808, 95721176}, + }, + { + {342204640, 63323192}, {342893536, 65228960}, + {344271328, 69040480}, {344960192, 70946248}, + {345649120, 72852016}, {346338016, 74757776}, + {347026880, 76663536}, {347715776, 78569304}, + {354618176, 81428112}, {356918976, 82381040}, + {359219776, 83333976}, {361520576, 84286920}, + {363821376, 85239856}, {366122176, 86192784}, + {368422976, 87145720}, {367886720, 85239976}, + {366814208, 81428472}, {366277952, 79522728}, + {365741664, 77616984}, {365205408, 75711224}, + {364132896, 71899736}, {363596640, 69993984}, + {361143232, 69041032}, {356236352, 67135128}, + {353782944, 66182184}, {351329504, 65229232}, + {348876064, 64276284}, {346422592, 63323328}, + {343969184, 62370380}, {341515744, 61417428}, + }, + { + {329804576, 29019442}, {330493472, 30925208}, + {331182368, 32830970}, {331871232, 34736732}, + {332560160, 36642500}, {333249056, 38548264}, + {333937920, 40454024}, {334626816, 42359792}, + {335315744, 44265552}, {337921792, 45218520}, + {340527872, 46171484}, {343133952, 47124452}, + {345740000, 48077416}, {348346080, 49030384}, + {350952160, 49983348}, {353558208, 50936312}, + {356164288, 51889284}, {358770368, 52842248}, + {358234112, 50936496}, {357697856, 49030752}, + {357161568, 47125000}, {356089056, 43313504}, + {355552800, 41407752}, {355016544, 39502008}, + {354480288, 37596256}, {353944032, 35690508}, + {351185344, 34737524}, {348426624, 33784544}, + {345667904, 32831566}, {342909216, 31878584}, + {340150528, 30925604}, {334633088, 29019640}, + {331874400, 28066660}, {329115680, 27113678}, + }, + }}, + }, + ExPolygons{ + // "x-end-idler.stl": + MyPoly{{ + {-6500000, -10475000}, {0, -10475000}, + {0, -10468600}, {365572, -10468600}, + {1094940, -10417600}, {1818960, -10315900}, + {2534130, -10163800}, {3236950, -9962320}, + {3924000, -9712250}, {4591940, -9414870}, + {5237500, -9071620}, {5857540, -8684170}, + {6449050, -8254410}, {7009140, -7784440}, + {7535080, -7276550}, {8024310, -6733200}, + {8474450, -6157050}, {8883300, -5550900}, + {9248880, -4917710}, {9569390, -4260570}, + {9843280, -3582660}, {10069200, -2887300}, + {10246100, -2177870}, {10373100, -1457840}, + {10449500, -730699}, {10475000, 0}, + {10449500, 730699}, {10373100, 1457840}, + {10246100, 2177870}, {10069200, 2887300}, + {9843280, 3582660}, {9569390, 4260570}, + {9248880, 4917710}, {8883300, 5550900}, + {8474450, 6157050}, {8024310, 6733200}, + {7739860, 7049120}, {8047300, 7272490}, + {9203020, 8357790}, {10213600, 9579380}, + {11063100, 10918000}, {11738200, 12352500}, + {12228100, 13860400}, {12525200, 15417700}, + {12624700, 17000000}, {12525200, 18582300}, + {12228100, 20139600}, {11738200, 21647500}, + {11063100, 23082000}, {10213600, 24420600}, + {9203020, 25642200}, {8047300, 26727500}, + {6764660, 27659400}, {5375340, 28423200}, + {3901250, 29006800}, {2365630, 29401100}, + {792712, 29599800}, {-792712, 29599800}, + {-2365630, 29401100}, {-3901250, 29006800}, + {-5181320, 28500000}, {-23500000, 28500000}, + {-23500000, -9000000}, {-22000000, -10500000}, + {-6500000, -10500000}, + }, + { + { + {6562230, 22074800}, {6357580, 22107300}, + {6158600, 22165100}, {5968430, 22247400}, + {5790080, 22352800}, {5626350, 22479800}, + {5479830, 22626400}, {5352830, 22790100}, + {5247350, 22968400}, {5165060, 23158600}, + {5107250, 23357600}, {5074840, 23562200}, + {5068330, 23769300}, {5087830, 23975600}, + {5133030, 24177800}, {5203220, 24372800}, + {5297290, 24557400}, {5413760, 24728800}, + {5550790, 24884200}, {5706220, 25021300}, + {5877600, 25137700}, {6062220, 25231800}, + {6257180, 25302000}, {6459400, 25347200}, + {6665690, 25366700}, {6872790, 25360200}, + {7077450, 25327800}, {7276430, 25270000}, + {7466600, 25187700}, {7644950, 25082200}, + {7808680, 24955200}, {7955200, 24808700}, + {8082200, 24645000}, {8187670, 24466600}, + {8269970, 24276400}, {8327780, 24077400}, + {8360190, 23872800}, {8366700, 23665700}, + {8347200, 23459400}, {8302000, 23257200}, + {8231809, 23062200}, {8137740, 22877600}, + {8021270, 22706200}, {7884240, 22550800}, + {7728810, 22413800}, {7557430, 22297300}, + {7372810, 22203200}, {7177850, 22133000}, + {6975630, 22087800}, {6769340, 22068300}, + }, + { + {1094940, 10417600}, {365572, 10468600}, + {0, 10468600}, {0, 10475000}, + {-1431080, 10475000}, {-6000000, 15108169}, + {-6000000, 19962102}, {-5802370, 20350000}, + {-5420410, 20938200}, {-4979070, 21483200}, + {-4483170, 21979100}, {-3938160, 22420400}, + {-3350000, 22802400}, {-2725130, 23120800}, + {-2070410, 23372100}, {-1393010, 23553600}, + {-700340, 23663300}, {0, 23700000}, + {700340, 23663300}, {1393010, 23553600}, + {2070410, 23372100}, {2725130, 23120800}, + {3350000, 22802400}, {3938160, 22420400}, + {4483170, 21979100}, {4979070, 21483200}, + {5420410, 20938200}, {5802370, 20350000}, + {6120750, 19725100}, {6372080, 19070400}, + {6553590, 18393000}, {6663300, 17700300}, + {6700000, 17000000}, {6663300, 16299700}, + {6553590, 15607000}, {6372080, 14929600}, + {6120750, 14274900}, {5802370, 13650000}, + {5420410, 13061800}, {4979070, 12516800}, + {4483170, 12020900}, {3938160, 11579600}, + {3350000, 11197600}, {2725130, 10879200}, + {2070410, 10627900}, {1393010, 10446400}, + {1156540, 10409000}, + }, + { + {-1455380, -6847030}, {-2847160, -6394820}, + {-4114500, -5663120}, {-5202010, -4683910}, + {-6062180, -3500000}, {-6657400, -2163120}, + {-6961650, -731699}, {-6961650, 731699}, + {-6657400, 2163120}, {-6062180, 3500000}, + {-5202010, 4683910}, {-4114500, 5663120}, + {-2847160, 6394820}, {-1455380, 6847030}, + {0, 7000000}, {1455380, 6847030}, + {2847160, 6394820}, {4114500, 5663120}, + {5018810, 4848870}, {5421400, 5186681}, + {5472037, 5130444}, {6083180, 4451690}, + {6090937, 4443078}, {6007070, 4372710}, + {5647390, 4070900}, {6062180, 3500000}, + {6657400, 2163120}, {6961650, 731699}, + {6961650, -731699}, {6657400, -2163120}, + {6062180, -3500000}, {5202010, -4683910}, + {4114500, -5663120}, {2847160, -6394820}, + {1455380, -6847030}, {0, -7000000}, + }, + }}, + }, + ExPolygons{ + // "Einsy-hinges.stl": + MyPoly{ + { + {865247, 3337040}, {1400000, 3575130}, {1873570, 3919190}, + {2265250, 4354200}, {2557930, 4861140}, {2738810, 5417850}, + {2762880, 5646830}, {2785290, 5860020}, {2786450, 5871067}, + {2796080, 5962680}, {2800000, 6000000}, {2738810, 6582150}, + {2728530, 6613790}, {2344020, 7279790}, {2195738, 7536616}, + {1639530, 8500000}, {1552105, 8651421}, {935040, 9720210}, + {621454, 10263400}, {-3267950, 17000000}, {-5000000, 17000000}, + {-5000000, 6000000}, {-2800000, 6000000}, {-2738810, 5417850}, + {-2557930, 4861140}, {-2265250, 4354200}, {-1873570, 3919190}, + {-1400000, 3575130}, {-865247, 3337040}, {-292679, 3215340}, + {292679, 3215340}, + }, + {}}, + MyPoly{{ + {412054, -4263360}, {725639, -3720210}, + {1315606, -2698356}, {1430130, -2500000}, + {2000000, -1512950}, {2000000, -1309600}, + {2192510, -976168}, {2347550, -498987}, + {2400000, 0}, {2382076, 170521}, + {2362880, 353169}, {2349180, 483565}, + {2347550, 498987}, {2192510, 976168}, + {1941640, 1410680}, {1605910, 1783550}, + {1200000, 2078460}, {741640, 2282540}, + {250868, 2386850}, {-250868, 2386850}, + {-741640, 2282540}, {-1200000, 2078460}, + {-1605910, 1783550}, {-1941640, 1410680}, + {-2192510, 976168}, {-2347550, 498987}, + {-2400000, 0}, {-5000000, 0}, + {-5000000, -11000000}, {-3477350, -11000000}, + }, + {}}, + }, + ExPolygons{ + // "LCD-cover-ORIGINAL-MK3.stl": + MyPoly{{ + {78000000, -11928900}, + {78000000, 51000000}, + {73000000, 56000000}, + {-72000000, 56000000}, + {-77000000, 51000000}, + {-77000000, -11928900}, + {-74928904, -14000000}, + {75928904, -14000000}, + }, + { + { + {44000000, 26000000}, {44000000, 31980000}, + {43992900, 31987100}, {44000000, 31994200}, + {44000000, 32000000}, {44005800, 32000000}, + {56000000, 43994200}, {56000000, 44000000}, + {56005800, 44000000}, {56013700, 44007900}, + {56021600, 44000000}, {69500000, 44000000}, + {69500000, 36020800}, {69510400, 36010400}, + {63500000, 30000000}, {50900000, 30000000}, + {49000000, 28100000}, {49000000, 26000000}, + {48000000, 26000000}, {48000000, 28500000}, + {47992900, 28507100}, {50503100, 31017300}, + {50520500, 31000000}, {63085800, 31000000}, + {68500000, 36414200}, {68500000, 43000000}, + {56420000, 43000000}, {45000000, 31580000}, + {45000000, 26000000}, + }, + { + {-54500000, 8000000}, + {-54500000, 38500000}, + {30500000, 38500000}, + {30500000, 8000000}, + }, + { + {61872800, 15032900}, {60645900, 15293700}, + {59500000, 15803800}, {58485200, 16541100}, + {57645900, 17473300}, {57018700, 18559600}, + {56631100, 19752500}, {56500000, 21000000}, + {56631100, 22247500}, {57018700, 23440400}, + {57645900, 24526700}, {58485200, 25458900}, + {59500000, 26196200}, {60645900, 26706300}, + {61872800, 26967100}, {63127200, 26967100}, + {64354104, 26706300}, {65500000, 26196200}, + {66514800, 25458900}, {67354104, 24526700}, + {67981304, 23440400}, {68368896, 22247500}, + {68500000, 21000000}, {68368896, 19752500}, + {67981304, 18559600}, {67354104, 17473300}, + {66514800, 16541100}, {65500000, 15803800}, + {64354104, 15293700}, {63127200, 15032900}, + }, + { + {57000000, 1500000}, + {57000000, 5500000}, + {58300000, 5500000}, + {58300000, 1500000}, + }, + { + {55000000, 1500000}, + {55000000, 5500000}, + {56300000, 5500000}, + {56300000, 1500000}, + }, + { + {59000000, 1500000}, + {59000000, 5500000}, + {60300000, 5500000}, + {60300000, 1500000}, + }, + { + {61000000, 1500000}, + {61000000, 5500000}, + {62300000, 5500000}, + {62300000, 1500000}, + }, + { + {63000000, 1500000}, + {63000000, 5500000}, + {64300004, 5500000}, + {64300004, 1500000}, + }, + { + {65000000, 1500000}, + {65000000, 5500000}, + {66300004, 5500000}, + {66300004, 1500000}, + }, + { + {67000000, 1500000}, + {67000000, 5500000}, + {68300000, 5500000}, + {68300000, 1500000}, + }, + }}, + }, + ExPolygons{ + // "x-end-motor.stl": + MyPoly{{ + {2365630, -29401100}, {3901250, -29006800}, + {5375340, -28423200}, {6764660, -27659400}, + {8047300, -26727500}, {9203020, -25642200}, + {10213600, -24420600}, {11063100, -23082000}, + {11738200, -21647500}, {12228100, -20139600}, + {12525200, -18582300}, {12624700, -17000000}, + {12525200, -15417700}, {12228100, -13860400}, + {11738200, -12352500}, {11063100, -10918000}, + {10213600, -9579380}, {9203020, -8357790}, + {8047300, -7272490}, {7739860, -7049120}, + {8024310, -6733200}, {8474450, -6157050}, + {8883300, -5550900}, {9248880, -4917710}, + {9569390, -4260570}, {9843280, -3582660}, + {10069200, -2887300}, {10246100, -2177870}, + {10373100, -1457840}, {10449500, -730699}, + {10475000, 0}, {10449500, 730699}, + {10373100, 1457840}, {10246100, 2177870}, + {10069200, 2887300}, {9843280, 3582660}, + {9569390, 4260570}, {9248880, 4917710}, + {8883300, 5550900}, {8474450, 6157050}, + {8024310, 6733200}, {7535080, 7276550}, + {7009140, 7784440}, {6449050, 8254410}, + {5857540, 8684170}, {5237500, 9071620}, + {4591940, 9414870}, {3924000, 9712250}, + {3236950, 9962320}, {2534130, 10163800}, + {1818960, 10315900}, {1094940, 10417600}, + {365572, 10468600}, {0, 10468600}, + {0, 10475000}, {-6500000, 10475000}, + {-6500000, 53000000}, {-23500000, 53000000}, + {-23500000, -28500000}, {-5181320, -28500000}, + {-3901250, -29006800}, {-2365630, -29401100}, + {-792712, -29599800}, {792712, -29599800}, + }, + { + { + {-1455380, -6847030}, {-2847160, -6394820}, + {-4114500, -5663120}, {-5202010, -4683910}, + {-6062180, -3500000}, {-6657400, -2163120}, + {-6961650, -731699}, {-6961650, 731699}, + {-6657400, 2163120}, {-6062180, 3500000}, + {-5202010, 4683910}, {-4114500, 5663120}, + {-2847160, 6394820}, {-1455380, 6847030}, + {0, 7000000}, {1455380, 6847030}, + {2847160, 6394820}, {4114500, 5663120}, + {5202010, 4683910}, {6062180, 3500000}, + {6657400, 2163120}, {6961650, 731699}, + {6961650, -731699}, {6657400, -2163120}, + {6062180, -3500000}, {5641320, -4079259}, + {6084032, -4450744}, {6083180, -4451690}, + {5472037, -5130444}, {5414502, -5194343}, + {5328022, -5121778}, {5011080, -4855830}, + {4114500, -5663120}, {2847160, -6394820}, + {1455380, -6847030}, {0, -7000000}, + }, + { + {-700340, -23663300}, {-1393010, -23553600}, + {-2070410, -23372100}, {-2725130, -23120800}, + {-3350000, -22802400}, {-3938160, -22420400}, + {-4483170, -21979100}, {-4979070, -21483200}, + {-5420410, -20938200}, {-5802370, -20350000}, + {-6120750, -19725100}, {-6372080, -19070400}, + {-6500000, -18593000}, {-6500000, -15515603}, + {-1406282, -10475000}, {0, -10475000}, + {0, -10468600}, {365572, -10468600}, + {1094940, -10417600}, {1156540, -10409000}, + {1393010, -10446400}, {2070410, -10627900}, + {2725130, -10879200}, {3350000, -11197600}, + {3938160, -11579600}, {4483170, -12020900}, + {4979070, -12516800}, {5420410, -13061800}, + {5802370, -13650000}, {6120750, -14274900}, + {6372080, -14929600}, {6553590, -15607000}, + {6663300, -16299700}, {6700000, -17000000}, + {6663300, -17700300}, {6553590, -18393000}, + {6372080, -19070400}, {6120750, -19725100}, + {5802370, -20350000}, {5420410, -20938200}, + {4979070, -21483200}, {4483170, -21979100}, + {3938160, -22420400}, {3350000, -22802400}, + {2725130, -23120800}, {2070410, -23372100}, + {1393010, -23553600}, {700340, -23663300}, + {0, -23700000}, + }, + { + {6459400, -25347200}, {6257180, -25302000}, + {6062220, -25231800}, {5877600, -25137700}, + {5706220, -25021300}, {5550790, -24884200}, + {5413760, -24728800}, {5297290, -24557400}, + {5203220, -24372800}, {5133030, -24177800}, + {5087830, -23975600}, {5068330, -23769300}, + {5074840, -23562200}, {5107250, -23357600}, + {5165060, -23158600}, {5247350, -22968400}, + {5352830, -22790100}, {5479830, -22626400}, + {5626350, -22479800}, {5790080, -22352800}, + {5968430, -22247400}, {6158600, -22165100}, + {6357580, -22107300}, {6562230, -22074800}, + {6769340, -22068300}, {6975630, -22087800}, + {7177850, -22133000}, {7372810, -22203200}, + {7557430, -22297300}, {7728810, -22413800}, + {7884240, -22550800}, {8021270, -22706200}, + {8137740, -22877600}, {8231809, -23062200}, + {8302000, -23257200}, {8347200, -23459400}, + {8366700, -23665700}, {8360190, -23872800}, + {8327780, -24077400}, {8269970, -24276400}, + {8187670, -24466600}, {8082200, -24645000}, + {7955200, -24808700}, {7808680, -24955200}, + {7644950, -25082200}, {7466600, -25187700}, + {7276430, -25270000}, {7077450, -25327800}, + {6872790, -25360200}, {6665690, -25366700}, + }, + }}, + }, + ExPolygons{ + // "y-belt-idler.stl": + MyPoly{{ + {12500000, 40000000}, + {-12500000, 40000000}, + {-12500000, 5000000}, + {12500000, 5000000}, + }, + { + { + {-103604, 34353300}, {-309178, 34379200}, + {-509877, 34430800}, {-702536, 34507000}, + {-884113, 34606900}, {-1051750, 34728700}, + {-1202800, 34870500}, {-1334880, 35030200}, + {-1445910, 35205100}, {-1534130, 35392600}, + {-1598160, 35589700}, {-1636990, 35793200}, + {-1650000, 36000000}, {-1636990, 36206800}, + {-1598160, 36410300}, {-1534130, 36607400}, + {-1445910, 36794900}, {-1334880, 36969800}, + {-1202800, 37129500}, {-1051750, 37271300}, + {-884113, 37393100}, {-702536, 37493000}, + {-509877, 37569200}, {-309178, 37620800}, + {-103604, 37646700}, {103604, 37646700}, + {309178, 37620800}, {509877, 37569200}, + {702536, 37493000}, {884113, 37393100}, + {1051750, 37271300}, {1202800, 37129500}, + {1334880, 36969800}, {1445910, 36794900}, + {1534130, 36607400}, {1598160, 36410300}, + {1636990, 36206800}, {1650000, 36000000}, + {1636990, 35793200}, {1598160, 35589700}, + {1534130, 35392600}, {1445910, 35205100}, + {1334880, 35030200}, {1202800, 34870500}, + {1051750, 34728700}, {884113, 34606900}, + {702536, 34507000}, {509877, 34430800}, + {309178, 34379200}, {103604, 34353300}, + }, + { + {-103604, 8353260}, {-309178, 8379229}, + {-509877, 8430760}, {-702536, 8507040}, + {-884113, 8606860}, {-1051750, 8728650}, + {-1202800, 8870500}, {-1334880, 9030150}, + {-1445910, 9205110}, {-1534130, 9392590}, + {-1598160, 9589660}, {-1636990, 9793200}, + {-1650000, 10000000}, {-1636990, 10206800}, + {-1598160, 10410300}, {-1534130, 10607400}, + {-1445910, 10794900}, {-1334880, 10969800}, + {-1202800, 11129500}, {-1051750, 11271300}, + {-884113, 11393100}, {-702536, 11493000}, + {-509877, 11569200}, {-309178, 11620800}, + {-103604, 11646700}, {103604, 11646700}, + {309178, 11620800}, {509877, 11569200}, + {702536, 11493000}, {884113, 11393100}, + {1051750, 11271300}, {1202800, 11129500}, + {1334880, 10969800}, {1445910, 10794900}, + {1534130, 10607400}, {1598160, 10410300}, + {1636990, 10206800}, {1650000, 10000000}, + {1636990, 9793200}, {1598160, 9589660}, + {1534130, 9392590}, {1445910, 9205110}, + {1334880, 9030150}, {1202800, 8870500}, + {1051750, 8728650}, {884113, 8606860}, + {702536, 8507040}, {509877, 8430760}, + {309178, 8379229}, {103604, 8353260}, + }, + }}, + }, + ExPolygons{ + // "z-screw-cover.stl": + MyPoly{{ + {836227, -7956170}, {927804, -7941670}, + {964293, -7941670}, {1029899, -7925500}, + {1663290, -7825180}, {2472140, -7608450}, + {2751896, -7501064}, {2836840, -7480130}, + {2919650, -7436670}, {3253890, -7308360}, + {4000000, -6928200}, {4470019, -6622970}, + {4544520, -6583870}, {4583731, -6549125}, + {4702280, -6472140}, {5353040, -5945160}, + {5945160, -5353040}, {5973910, -5317540}, + {5988090, -5304980}, {6011204, -5271483}, + {6472140, -4702280}, {6928200, -4000000}, + {7039150, -3782250}, {7083650, -3717780}, + {7117560, -3628360}, {7308360, -3253890}, + {7608450, -2472140}, {7734580, -2001420}, + {7767530, -1914530}, {7775550, -1848520}, + {7825180, -1663290}, {7956170, -836227}, + {8000000, 0}, {7956170, 836227}, + {7825180, 1663290}, {7775550, 1848520}, + {7767530, 1914530}, {7734580, 2001420}, + {7608450, 2472140}, {7308360, 3253890}, + {7117560, 3628360}, {7083650, 3717780}, + {7039150, 3782250}, {6928200, 4000000}, + {6472140, 4702280}, {6011204, 5271483}, + {5988090, 5304980}, {5973910, 5317540}, + {5945160, 5353040}, {5353040, 5945160}, + {4702280, 6472140}, {4583731, 6549125}, + {4544520, 6583870}, {4470019, 6622970}, + {4000000, 6928200}, {3253890, 7308360}, + {2919650, 7436670}, {2836840, 7480130}, + {2751896, 7501064}, {2472140, 7608450}, + {1663290, 7825180}, {1029899, 7925500}, + {964293, 7941670}, {927804, 7941670}, + {836227, 7956170}, {0, 8000000}, + {-836227, 7956170}, {-927804, 7941670}, + {-964293, 7941670}, {-1029899, 7925500}, + {-1663290, 7825180}, {-2472140, 7608450}, + {-2751896, 7501064}, {-2836840, 7480130}, + {-2919650, 7436670}, {-3253890, 7308360}, + {-4000000, 6928200}, {-4470019, 6622970}, + {-4544520, 6583870}, {-4583731, 6549125}, + {-4702280, 6472140}, {-5353040, 5945160}, + {-5945160, 5353040}, {-5973910, 5317540}, + {-5988090, 5304980}, {-6011204, 5271483}, + {-6472140, 4702280}, {-6928200, 4000000}, + {-7039150, 3782250}, {-7083650, 3717780}, + {-7117560, 3628360}, {-7308360, 3253890}, + {-7608450, 2472140}, {-7734580, 2001420}, + {-7767530, 1914530}, {-7775550, 1848520}, + {-7825180, 1663290}, {-7956170, 836227}, + {-8000000, 0}, {-7956170, -836227}, + {-7825180, -1663290}, {-7775550, -1848520}, + {-7767530, -1914530}, {-7734580, -2001420}, + {-7608450, -2472140}, {-7308360, -3253890}, + {-7117560, -3628360}, {-7083650, -3717780}, + {-7039150, -3782250}, {-6928200, -4000000}, + {-6472140, -4702280}, {-6011204, -5271483}, + {-5988090, -5304980}, {-5973910, -5317540}, + {-5945160, -5353040}, {-5353040, -5945160}, + {-4702280, -6472140}, {-4583731, -6549125}, + {-4544520, -6583870}, {-4470019, -6622970}, + {-4000000, -6928200}, {-3253890, -7308360}, + {-2919650, -7436670}, {-2836840, -7480130}, + {-2751896, -7501064}, {-2472140, -7608450}, + {-1663290, -7825180}, {-1029899, -7925500}, + {-964293, -7941670}, {-927804, -7941670}, + {-836227, -7956170}, {0, -8000000}, + }, + { + { + {400000, -3200000}, {-400000, -3200000}, + {-440000, -3400000}, {-799999, -3400000}, + {-875001, -3600000}, {-1300000, -3600000}, + {-1416318, -3948965}, {-1708290, -3836890}, + {-2100000, -3637310}, {-2468700, -3397870}, + {-2810350, -3121210}, {-3121210, -2810350}, + {-3397870, -2468700}, {-3637310, -2100000}, + {-3836890, -1708290}, {-3994440, -1297870}, + {-4108220, -873229}, {-4152979, -590596}, + {-3200000, -400000}, {-3200000, 400000}, + {-3400000, 440000}, {-3400000, 799999}, + {-3600000, 874998}, {-3600000, 1300000}, + {-3948965, 1416318}, {-3836890, 1708290}, + {-3637310, 2100000}, {-3397870, 2468700}, + {-3121210, 2810350}, {-2810350, 3121210}, + {-2468700, 3397870}, {-2100000, 3637310}, + {-1708290, 3836890}, {-1297870, 3994440}, + {-873229, 4108220}, {-590596, 4152979}, + {-400000, 3200000}, {400000, 3200000}, + {440000, 3400000}, {799999, 3400000}, + {874998, 3600000}, {1300000, 3600000}, + {1416318, 3948965}, {1708290, 3836890}, + {2100000, 3637310}, {2468700, 3397870}, + {2810350, 3121210}, {3121210, 2810350}, + {3397870, 2468700}, {3637310, 2100000}, + {3836890, 1708290}, {3994440, 1297870}, + {4108220, 873229}, {4152979, 590596}, + {3200000, 400000}, {3200000, -400000}, + {3400000, -440000}, {3400000, -799999}, + {3600000, -874998}, {3600000, -1300000}, + {3948965, -1416318}, {3836890, -1708290}, + {3637310, -2100000}, {3397870, -2468700}, + {3121210, -2810350}, {2810350, -3121210}, + {2468700, -3397870}, {2100000, -3637310}, + {1708290, -3836890}, {1297870, -3994440}, + {873229, -4108220}, {590596, -4152979}, + }, + }}, + }, + ExPolygons{ + // "cable-holder.stl": + MyPoly{{ + {-2043150, -34799100}, {-1990180, -34549300}, + {-1988820, -34542900}, {-1986150, -34536800}, + {-1882530, -34303500}, {-1732900, -34097100}, + {-1728930, -34091600}, {-1723900, -34087100}, + {-1534730, -33916300}, {-1308420, -33785400}, + {-1059890, -33704400}, {-806907, -33677700}, + {-799999, -33677000}, {-793091, -33677700}, + {-540110, -33704400}, {-291578, -33785400}, + {-65267, -33916300}, {123903, -34087100}, + {128930, -34091600}, {132903, -34097100}, + {282532, -34303500}, {386150, -34536800}, + {388820, -34542900}, {390183, -34549300}, + {443151, -34799100}, {443151, -34908100}, + {556848, -34908100}, {556848, -34799100}, + {609816, -34549300}, {611179, -34542900}, + {613849, -34536800}, {717467, -34303500}, + {867096, -34097100}, {871069, -34091600}, + {876096, -34087100}, {1065270, -33916300}, + {1291580, -33785400}, {1540110, -33704400}, + {1793090, -33677700}, {1800000, -33677000}, + {1806910, -33677700}, {2059890, -33704400}, + {2308420, -33785400}, {2534730, -33916300}, + {2723900, -34087100}, {2728930, -34091600}, + {2732900, -34097100}, {2882530, -34303500}, + {2986150, -34536800}, {2988820, -34542900}, + {2990180, -34549300}, {3043150, -34799100}, + {3043150, -34908100}, {4000000, -34908100}, + {4000000, -29539900}, {4215720, -29345700}, + {4830130, -28500000}, {5255280, -27545100}, + {5472610, -26522600}, {5472610, -26000000}, + {5500000, -26000000}, {5500000, -17000000}, + {4805710, -17000000}, {4215720, -17815100}, + {3438930, -18517200}, {2533680, -19041900}, + {1539560, -19366100}, {499999, -19475800}, + {-539558, -19366100}, {-1533680, -19041900}, + {-2438930, -18517200}, {-3215720, -17815100}, + {-3805710, -17000000}, {-4500000, -17000000}, + {-4500000, -26000000}, {-4472610, -26000000}, + {-4472610, -26522600}, {-4255280, -27545100}, + {-3830130, -28500000}, {-3215720, -29345700}, + {-3000000, -29539900}, {-3000000, -34908100}, + {-2043150, -34908100}, + }, + { + { + {136154, -28711800}, {-211788, -28598700}, + {-382749, -28500000}, {-528624, -28415800}, + {-800503, -28171000}, {-1015540, -27875000}, + {-1164350, -27540800}, {-1240410, -27182900}, + {-1240410, -26817100}, {-1164350, -26459200}, + {-1015540, -26125000}, {-800503, -25829000}, + {-528624, -25584200}, {-382751, -25500000}, + {-211788, -25401300}, {136154, -25288200}, + {500000, -25250000}, {863845, -25288200}, + {1211790, -25401300}, {1382750, -25500000}, + {1528620, -25584200}, {1800500, -25829000}, + {2015539, -26125000}, {2164350, -26459200}, + {2240410, -26817100}, {2240410, -27182900}, + {2164350, -27540800}, {2015539, -27875000}, + {1800500, -28171000}, {1528620, -28415800}, + {1382750, -28500000}, {1211790, -28598700}, + {863845, -28711800}, {499999, -28750000}, + }, + }}, + }, + ExPolygons{ + // "Einsy-doors.stl": + MyPoly{{ + {105500000, 91975304}, + {21500000, 91975304}, + {21500000, 87500000}, + {0, 87500000}, + {0, 0}, + {105500000, 0}, + }, + { + { + {46000000, 60500000}, + {46000000, 79500000}, + {49650000, 79500000}, + {49650000, 60500000}, + }, + { + {58000000, 60500000}, + {58000000, 79500000}, + {61650000, 79500000}, + {61650000, 60500000}, + }, + { + {70000000, 60500000}, + {70000000, 79500000}, + {73650000, 79500000}, + {73650000, 60500000}, + }, + { + {64000000, 60500000}, + {64000000, 79500000}, + {67650000, 79500000}, + {67650000, 60500000}, + }, + { + {94000000, 60500000}, + {94000000, 79500000}, + {97650000, 79500000}, + {97650000, 60500000}, + }, + { + {52000000, 60500000}, + {52000000, 79500000}, + {55650000, 79500000}, + {55650000, 60500000}, + }, + { + {88000000, 60500000}, + {88000000, 79500000}, + {91650000, 79500000}, + {91650000, 60500000}, + }, + { + {82000000, 60500000}, + {82000000, 79500000}, + {85650000, 79500000}, + {85650000, 60500000}, + }, + { + {40000000, 60500000}, + {40000000, 79500000}, + {43650000, 79500000}, + {43650000, 60500000}, + }, + { + {76000000, 60500000}, + {76000000, 79500000}, + {79650000, 79500000}, + {79650000, 60500000}, + }, + { + {52000000, 35500000}, + {52000000, 54500000}, + {55650000, 54500000}, + {55650000, 35500000}, + }, + { + {40000000, 35500000}, + {40000000, 54500000}, + {43650000, 54500000}, + {43650000, 35500000}, + }, + { + {58000000, 35500000}, + {58000000, 54500000}, + {61650000, 54500000}, + {61650000, 35500000}, + }, + { + {76000000, 35500000}, + {76000000, 54500000}, + {79650000, 54500000}, + {79650000, 35500000}, + }, + { + {94000000, 35500000}, + {94000000, 54500000}, + {97650000, 54500000}, + {97650000, 35500000}, + }, + { + {82000000, 35500000}, + {82000000, 54500000}, + {85650000, 54500000}, + {85650000, 35500000}, + }, + { + {64000000, 35500000}, + {64000000, 54500000}, + {67650000, 54500000}, + {67650000, 35500000}, + }, + { + {70000000, 35500000}, + {70000000, 54500000}, + {73650000, 54500000}, + {73650000, 35500000}, + }, + { + {46000000, 35500000}, + {46000000, 54500000}, + {49650000, 54500000}, + {49650000, 35500000}, + }, + { + {88000000, 35500000}, + {88000000, 54500000}, + {91650000, 54500000}, + {91650000, 35500000}, + }, + { + {40000000, 10500000}, + {40000000, 29500000}, + {43650000, 29500000}, + {43650000, 10500000}, + }, + { + {82000000, 10500000}, + {82000000, 29500000}, + {85650000, 29500000}, + {85650000, 10500000}, + }, + { + {94000000, 10500000}, + {94000000, 29500000}, + {97650000, 29500000}, + {97650000, 10500000}, + }, + { + {88000000, 10500000}, + {88000000, 29500000}, + {91650000, 29500000}, + {91650000, 10500000}, + }, + { + {76000000, 10500000}, + {76000000, 29500000}, + {79650000, 29500000}, + {79650000, 10500000}, + }, + { + {64000000, 10500000}, + {64000000, 29500000}, + {67650000, 29500000}, + {67650000, 10500000}, + }, + { + {52000000, 10500000}, + {52000000, 29500000}, + {55650000, 29500000}, + {55650000, 10500000}, + }, + { + {70000000, 10500000}, + {70000000, 29500000}, + {73650000, 29500000}, + {73650000, 10500000}, + }, + { + {46000000, 10500000}, + {46000000, 29500000}, + {49650000, 29500000}, + {49650000, 10500000}, + }, + { + {58000000, 10500000}, + {58000000, 29500000}, + {61650000, 29500000}, + {61650000, 10500000}, + }, + }}, + }, + ExPolygons{ + // "y-motor-holder.stl": + MyPoly{{ + {47000000, 0}, {47000000, 15000000}, + {42000000, 20000000}, {37468500, 20000000}, + {37500000, 19500000}, {37409300, 18058700}, + {37138700, 16640100}, {36692400, 15266600}, + {36077500, 13959800}, {35303700, 12740500}, + {34383100, 11627700}, {33330398, 10639100}, + {32161998, 9790230}, {30896500, 9094490}, + {29553700, 8562850}, {28154900, 8203700}, + {26722100, 8022690}, {25277900, 8022690}, + {23845100, 8203700}, {22446300, 8562850}, + {21103500, 9094490}, {19838000, 9790230}, + {18669600, 10639100}, {17616900, 11627700}, + {16696301, 12740500}, {15922500, 13959800}, + {15307600, 15266600}, {14861300, 16640100}, + {14590700, 18058700}, {14500000, 19500000}, + {14590700, 20941300}, {14861300, 22359900}, + {15000000, 22786800}, {15000000, 38000000}, + {12500000, 40500000}, {9642140, 40500000}, + {71067, 30928900}, {0, 31000000}, + {0, -1500000}, {45500000, -1500000}, + }, + { + { + {10396400, 33353298}, {10190800, 33379200}, + {9990120, 33430802}, {9797460, 33507000}, + {9615890, 33606900}, {9448250, 33728700}, + {9297200, 33870500}, {9165120, 34030200}, + {9054090, 34205100}, {8965870, 34392600}, + {8901840, 34589700}, {8863010, 34793200}, + {8850000, 35000000}, {8863010, 35206800}, + {8901840, 35410300}, {8965870, 35607400}, + {9054090, 35794900}, {9165120, 35969800}, + {9297200, 36129500}, {9448250, 36271300}, + {9615890, 36393100}, {9797460, 36493000}, + {9990120, 36569200}, {10190800, 36620800}, + {10396400, 36646700}, {10603600, 36646700}, + {10809200, 36620800}, {11009900, 36569200}, + {11202500, 36493000}, {11384100, 36393100}, + {11551700, 36271300}, {11702800, 36129500}, + {11834900, 35969800}, {11945900, 35794900}, + {12034100, 35607400}, {12098200, 35410300}, + {12137000, 35206800}, {12150000, 35000000}, + {12137000, 34793200}, {12098200, 34589700}, + {12034100, 34392600}, {11945900, 34205100}, + {11834900, 34030200}, {11702800, 33870500}, + {11551700, 33728700}, {11384100, 33606900}, + {11202500, 33507000}, {11009900, 33430802}, + {10809200, 33379200}, {10603600, 33353298}, + }, + { + {41396400, 2353260}, {41190800, 2379230}, + {40990100, 2430760}, {40797500, 2507040}, + {40615900, 2606860}, {40448200, 2728650}, + {40297200, 2870500}, {40165100, 3030150}, + {40054100, 3205110}, {39965900, 3392590}, + {39901800, 3589660}, {39863000, 3793200}, + {39850000, 4000000}, {39863000, 4206800}, + {39901800, 4410340}, {39965900, 4607400}, + {40054100, 4794890}, {40165100, 4969840}, + {40297200, 5129500}, {40448200, 5271350}, + {40615900, 5393140}, {40797500, 5492960}, + {40990100, 5569240}, {41190800, 5620770}, + {41396400, 5646740}, {41603600, 5646740}, + {41809200, 5620770}, {42009900, 5569240}, + {42202500, 5492960}, {42384100, 5393140}, + {42551800, 5271350}, {42702800, 5129500}, + {42834900, 4969840}, {42945900, 4794890}, + {43034100, 4607400}, {43098200, 4410340}, + {43137000, 4206800}, {43150000, 4000000}, + {43137000, 3793200}, {43098200, 3589660}, + {43034100, 3392590}, {42945900, 3205110}, + {42834900, 3030150}, {42702800, 2870500}, + {42551800, 2728650}, {42384100, 2606860}, + {42202500, 2507040}, {42009900, 2430760}, + {41809200, 2379230}, {41603600, 2353260}, + }, + }}, + }, + ExPolygons{ + // "heatbed-cable-cover.stl": + MyPoly{{ + {15000000, 48000000}, + {11000000, 52000000}, + {-11000000, 52000000}, + {-15000000, 48000000}, + {-15000000, 35500000}, + {15000000, 35500000}, + }, + { + { + {-10100500, 43403200}, {-10299800, 43428300}, + {-10494400, 43478300}, {-10681200, 43552300}, + {-10857300, 43649100}, {-11019900, 43767200}, + {-11166300, 43904700}, {-11294400, 44059500}, + {-11402100, 44229200}, {-11487600, 44411000}, + {-11549700, 44602100}, {-11587400, 44799500}, + {-11600000, 45000000}, {-11587400, 45200500}, + {-11549700, 45397900}, {-11487600, 45589000}, + {-11402100, 45770800}, {-11294400, 45940500}, + {-11166300, 46095300}, {-11019900, 46232800}, + {-10857300, 46350900}, {-10681200, 46447700}, + {-10494400, 46521700}, {-10299800, 46571700}, + {-10100500, 46596800}, {-9899530, 46596800}, + {-9700190, 46571700}, {-9505570, 46521700}, + {-9318750, 46447700}, {-9142680, 46350900}, + {-8980120, 46232800}, {-8833650, 46095300}, + {-8705570, 45940500}, {-8597910, 45770800}, + {-8512360, 45589000}, {-8450270, 45397900}, + {-8412620, 45200500}, {-8400000, 45000000}, + {-8412620, 44799500}, {-8450270, 44602100}, + {-8512360, 44411000}, {-8597910, 44229200}, + {-8705570, 44059500}, {-8833650, 43904700}, + {-8980120, 43767200}, {-9142680, 43649100}, + {-9318750, 43552300}, {-9505570, 43478300}, + {-9700190, 43428300}, {-9899530, 43403200}, + }, + { + {9899530, 43403200}, {9700190, 43428300}, + {9505570, 43478300}, {9318750, 43552300}, + {9142680, 43649100}, {8980120, 43767200}, + {8833650, 43904700}, {8705570, 44059500}, + {8597910, 44229200}, {8512360, 44411000}, + {8450270, 44602100}, {8412620, 44799500}, + {8400000, 45000000}, {8412620, 45200500}, + {8450270, 45397900}, {8512360, 45589000}, + {8597910, 45770800}, {8705570, 45940500}, + {8833650, 46095300}, {8980120, 46232800}, + {9142680, 46350900}, {9318750, 46447700}, + {9505570, 46521700}, {9700190, 46571700}, + {9899530, 46596800}, {10100500, 46596800}, + {10299800, 46571700}, {10494400, 46521700}, + {10681200, 46447700}, {10857300, 46350900}, + {11019900, 46232800}, {11166300, 46095300}, + {11294400, 45940500}, {11402100, 45770800}, + {11487600, 45589000}, {11549700, 45397900}, + {11587400, 45200500}, {11600000, 45000000}, + {11587400, 44799500}, {11549700, 44602100}, + {11487600, 44411000}, {11402100, 44229200}, + {11294400, 44059500}, {11166300, 43904700}, + {11019900, 43767200}, {10857300, 43649100}, + {10681200, 43552300}, {10494400, 43478300}, + {10299800, 43428300}, {10100500, 43403200}, + }, + }}, + MyPoly{{ + {18000000, 25000000}, + {16426001, 26574000}, + {11000000, 32000000}, + {-11000000, 32000000}, + {-18000000, 25000000}, + {-18000000, 0}, + {18000000, 0}, + }, + { + { + {-10100500, 23403200}, {-10299800, 23428300}, + {-10494400, 23478300}, {-10681200, 23552300}, + {-10857300, 23649100}, {-11019900, 23767200}, + {-11166300, 23904700}, {-11294400, 24059500}, + {-11402100, 24229200}, {-11487600, 24411000}, + {-11549700, 24602100}, {-11587400, 24799500}, + {-11600000, 25000000}, {-11587400, 25200500}, + {-11549700, 25397900}, {-11487600, 25589000}, + {-11402100, 25770800}, {-11294400, 25940500}, + {-11166300, 26095300}, {-11019900, 26232800}, + {-10857300, 26350900}, {-10681200, 26447700}, + {-10494400, 26521700}, {-10299800, 26571700}, + {-10100500, 26596800}, {-9899530, 26596800}, + {-9700190, 26571700}, {-9505570, 26521700}, + {-9318750, 26447700}, {-9142680, 26350900}, + {-8980120, 26232800}, {-8833650, 26095300}, + {-8705570, 25940500}, {-8597910, 25770800}, + {-8512360, 25589000}, {-8450270, 25397900}, + {-8412620, 25200500}, {-8400000, 25000000}, + {-8412620, 24799500}, {-8450270, 24602100}, + {-8512360, 24411000}, {-8597910, 24229200}, + {-8705570, 24059500}, {-8833650, 23904700}, + {-8980120, 23767200}, {-9142680, 23649100}, + {-9318750, 23552300}, {-9505570, 23478300}, + {-9700190, 23428300}, {-9899530, 23403200}, + }, + { + {9899530, 23403200}, {9700190, 23428300}, + {9505570, 23478300}, {9318750, 23552300}, + {9142680, 23649100}, {8980120, 23767200}, + {8833650, 23904700}, {8705570, 24059500}, + {8597910, 24229200}, {8512360, 24411000}, + {8450270, 24602100}, {8412620, 24799500}, + {8400000, 25000000}, {8412620, 25200500}, + {8450270, 25397900}, {8512360, 25589000}, + {8597910, 25770800}, {8705570, 25940500}, + {8833650, 26095300}, {8980120, 26232800}, + {9142680, 26350900}, {9318750, 26447700}, + {9505570, 26521700}, {9700190, 26571700}, + {9899530, 26596800}, {10100500, 26596800}, + {10299800, 26571700}, {10494400, 26521700}, + {10681200, 26447700}, {10857300, 26350900}, + {11019900, 26232800}, {11166300, 26095300}, + {11294400, 25940500}, {11402100, 25770800}, + {11487600, 25589000}, {11549700, 25397900}, + {11587400, 25200500}, {11600000, 25000000}, + {11587400, 24799500}, {11549700, 24602100}, + {11487600, 24411000}, {11402100, 24229200}, + {11294400, 24059500}, {11166300, 23904700}, + {11019900, 23767200}, {10857300, 23649100}, + {10681200, 23552300}, {10494400, 23478300}, + {10299800, 23428300}, {10100500, 23403200}, + }, + { + {-100465, 5903160}, {-299809, 5928340}, + {-494427, 5978310}, {-681247, 6052280}, + {-857323, 6149070}, {-1019880, 6267180}, + {-1166350, 6404720}, {-1294430, 6559540}, + {-1402090, 6729190}, {-1487640, 6911000}, + {-1549730, 7102100}, {-1587380, 7299470}, + {-1600000, 7500000}, {-1587380, 7700530}, + {-1549730, 7897900}, {-1487640, 8088999}, + {-1402090, 8270810}, {-1294430, 8440460}, + {-1166350, 8595270}, {-1019880, 8732820}, + {-857323, 8850920}, {-681247, 8947720}, + {-494427, 9021690}, {-299809, 9071660}, + {-100465, 9096840}, {100465, 9096840}, + {299809, 9071660}, {494427, 9021690}, + {681247, 8947720}, {857323, 8850920}, + {1019880, 8732820}, {1166350, 8595270}, + {1294430, 8440460}, {1402090, 8270810}, + {1487640, 8088999}, {1549730, 7897900}, + {1587380, 7700530}, {1600000, 7500000}, + {1587380, 7299470}, {1549730, 7102100}, + {1487640, 6911000}, {1402090, 6729190}, + {1294430, 6559540}, {1166350, 6404720}, + {1019880, 6267180}, {857323, 6149070}, + {681247, 6052280}, {494427, 5978310}, + {299809, 5928340}, {100465, 5903160}, + }, + }}, + }, + ExPolygons{ + // "y-rod-holder.stl": + MyPoly{{ + {-4130159, -11630200}, {-4125905, -11625938}, + {-4036359, -11536400}, {-3977214, -11477197}, + {-3893620, -11393600}, {-3968460, -11001300}, + {-4000000, -10500000}, {-3968460, -9998670}, + {-3874330, -9505240}, {-3719110, -9027500}, + {-3505230, -8572980}, {-3236070, -8148860}, + {-2915870, -7761810}, {-2549700, -7417950}, + {-2143310, -7122690}, {-1920140, -7000000}, + {-1703120, -6880690}, {-1236070, -6695770}, + {-749525, -6570850}, {-251162, -6507890}, + {251162, -6507890}, {749525, -6570850}, + {1236070, -6695770}, {1703120, -6880690}, + {1920140, -7000000}, {2143310, -7122690}, + {2549700, -7417950}, {2915870, -7761810}, + {3236070, -8148860}, {3505230, -8572980}, + {3719110, -9027500}, {3874330, -9505240}, + {3968460, -9998670}, {4000000, -10500000}, + {3968460, -11001300}, {3916390, -11274300}, + {4089891, -11447789}, {4113299, -11471200}, + {4642140, -12000000}, {8618800, -12000000}, + {10350900, -9000000}, {11376300, -7223940}, + {13000000, -4411540}, {13000000, 0}, + {4000000, 0}, {4000000, 1500000}, + {-4000000, 1500000}, {-4000000, 0}, + {-13000000, 0}, {-13000000, -4202820}, + {-8498290, -12000000}, {-4500000, -12000000}, + }, + {}}, + }, + ExPolygons{ + // "extruder-body.stl": + MyPoly{{ + {32000000, -41357900}, {32000000, -34500000}, + {27500000, -30000000}, {22600000, -30000000}, + {22600000, -29900000}, {22500000, -30000000}, + {17000000, -24500000}, {17000000, -12000000}, + {23000000, -12000000}, {23000000, -18000000}, + {23928900, -18000000}, {26000000, -15928900}, + {26000000, -10000000}, {23000000, -7000000}, + {17000000, -7000000}, {17000000, 44000000}, + {11000000, 50000000}, {-18000000, 50000000}, + {-25000000, 43000000}, {-25000000, 5750000}, + {-27250000, 5750000}, {-31500000, 1500000}, + {-31500000, -1500000}, {-24500000, -1500000}, + {-24500000, 2500000}, {-21309400, 2500000}, + {-20500000, 1098080}, {-20500000, -44000000}, + {-15500000, -44000000}, {-15500000, -38000000}, + {-14000000, -36500000}, {14000000, -36500000}, + {14000000, -38916000}, {14423151, -39823468}, + {14452100, -39885600}, {15259800, -41617700}, + {18642100, -45000000}, {28357900, -45000000}, + }, + { + { + {-19603600, 40853300}, {-19790500, 40876900}, + {-19809200, 40879200}, {-19991600, 40926100}, + {-20009900, 40930800}, {-20202500, 41007000}, + {-20384100, 41106900}, {-20491899, 41185194}, + {-20551700, 41228700}, {-20551800, 41228700}, + {-20702800, 41370500}, {-20834900, 41530200}, + {-20945900, 41705100}, {-21034100, 41892600}, + {-21098200, 42089700}, {-21137000, 42293200}, + {-21150000, 42500000}, {-21137000, 42706800}, + {-21098200, 42910300}, {-21034100, 43107400}, + {-20945900, 43294900}, {-20834900, 43469800}, + {-20702800, 43629500}, {-20551800, 43771300}, + {-20551700, 43771300}, {-20491899, 43814806}, + {-20384100, 43893100}, {-20202500, 43993000}, + {-20009900, 44069200}, {-19991600, 44073900}, + {-19809200, 44120800}, {-19790500, 44123100}, + {-19603600, 44146700}, {-19396400, 44146700}, + {-19209500, 44123100}, {-19190800, 44120800}, + {-19008400, 44073900}, {-18990100, 44069200}, + {-18797500, 43993000}, {-18615900, 43893100}, + {-18448200, 43771300}, {-18297200, 43629500}, + {-18165100, 43469800}, {-18054100, 43294900}, + {-17965900, 43107400}, {-17901800, 42910300}, + {-17863000, 42706800}, {-17850000, 42500000}, + {-17863000, 42293200}, {-17901800, 42089700}, + {-17965900, 41892600}, {-18054100, 41705100}, + {-18165100, 41530200}, {-18297200, 41370500}, + {-18448200, 41228700}, {-18615900, 41106900}, + {-18797500, 41007000}, {-18990100, 40930800}, + {-19008400, 40926100}, {-19190800, 40879200}, + {-19209500, 40876900}, {-19396400, 40853300}, + }, + { + {11396400, 40853300}, {11190800, 40879200}, + {10990100, 40930800}, {10797500, 41007000}, + {10615900, 41106900}, {10448200, 41228700}, + {10297200, 41370500}, {10165100, 41530200}, + {10054100, 41705100}, {9965870, 41892600}, + {9901840, 42089700}, {9863010, 42293200}, + {9850000, 42500000}, {9863010, 42706800}, + {9901840, 42910300}, {9965870, 43107400}, + {10054100, 43294900}, {10165100, 43469800}, + {10297200, 43629500}, {10448200, 43771300}, + {10615900, 43893100}, {10797500, 43993000}, + {10990100, 44069200}, {11190800, 44120800}, + {11396400, 44146700}, {11603600, 44146700}, + {11809200, 44120800}, {12009900, 44069200}, + {12202500, 43993000}, {12384100, 43893100}, + {12551700, 43771300}, {12702800, 43629500}, + {12834900, 43469800}, {12945900, 43294900}, + {13034100, 43107400}, {13098200, 42910300}, + {13137000, 42706800}, {13150000, 42500000}, + {13137000, 42293200}, {13098200, 42089700}, + {13034100, 41892600}, {12945900, 41705100}, + {12834900, 41530200}, {12702800, 41370500}, + {12551700, 41228700}, {12384100, 41106900}, + {12202500, 41007000}, {12009900, 40930800}, + {11809200, 40879200}, {11603600, 40853300}, + }, + { + {-3181780, 21500000}, {-15111782, 22578598}, + {-15737800, 24505100}, {-15797300, 25071800}, + {-15997500, 26976600}, {-16000000, 27000000}, + {-15737800, 29494900}, {-14962500, 31880800}, + {-13708200, 34053400}, {-12029600, 35917700}, + {-10000000, 37392300}, {-8500000, 38060100}, + {-7708200, 38412700}, {-5282850, 38928200}, + {-5254340, 38934300}, {-3000000, 38934300}, + {-3000000, 33500000}, {-1092350, 31592300}, + {-1091812, 31591773}, {-1065440, 31565400}, + {-1063162, 31563127}, {-963937, 31463900}, + {-889918, 31389900}, {-860013, 31360013}, + {-612800, 31112800}, {-589409, 31089400}, + {-366049, 30866044}, {-339918, 30839900}, + {-206119, 30706100}, {0, 30500000}, + {206119, 30706100}, {339918, 30839900}, + {362385, 30862377}, {889918, 31389900}, + {963937, 31463900}, {1063442, 31563406}, + {1065440, 31565400}, {1091822, 31591782}, + {1092350, 31592300}, {4000000, 34500000}, + {4000000, 35939184}, {4029570, 35917700}, + {5708200, 34053400}, {6962550, 31880800}, + {7737770, 29494900}, {8000000, 27000000}, + {7737770, 24505100}, {6962550, 22119200}, + {6605070, 21500000}, {1727920, 21500000}, + {1698940, 21529000}, {1530830, 21697100}, + {1516490, 21711437}, {1125240, 22102700}, + {894136, 22333800}, {648935, 22579000}, + {318574, 22909343}, {131860, 23096100}, + {0, 23227900}, {-131860, 23096100}, + {-318574, 22909343}, {-648935, 22579000}, + {-894136, 22333800}, {-1125240, 22102700}, + {-1516490, 21711437}, {-1530830, 21697100}, + {-1698940, 21529000}, {-1727920, 21500000}, + }, + { + {-19603600, 9853260}, {-19809200, 9879230}, + {-20009900, 9930760}, {-20202500, 10007000}, + {-20384100, 10106900}, {-20551800, 10228700}, + {-20702800, 10370500}, {-20834900, 10530200}, + {-20945900, 10705100}, {-21034100, 10892600}, + {-21098200, 11089700}, {-21137000, 11293200}, + {-21150000, 11500000}, {-21137000, 11706800}, + {-21098200, 11910300}, {-21034100, 12107400}, + {-20945900, 12294900}, {-20834900, 12469800}, + {-20702800, 12629500}, {-20551800, 12771300}, + {-20384100, 12893100}, {-20202500, 12993000}, + {-20009900, 13069200}, {-19809200, 13120800}, + {-19603600, 13146700}, {-19396400, 13146700}, + {-19190800, 13120800}, {-18990100, 13069200}, + {-18797500, 12993000}, {-18615900, 12893100}, + {-18448200, 12771300}, {-18297200, 12629500}, + {-18165100, 12469800}, {-18054100, 12294900}, + {-17965900, 12107400}, {-17901800, 11910300}, + {-17863000, 11706800}, {-17850000, 11500000}, + {-17863000, 11293200}, {-17901800, 11089700}, + {-17965900, 10892600}, {-18054100, 10705100}, + {-18165100, 10530200}, {-18297200, 10370500}, + {-18448200, 10228700}, {-18615900, 10106900}, + {-18797500, 10007000}, {-18990100, 9930760}, + {-19190800, 9879230}, {-19396400, 9853260}, + }, + { + {11396400, 9853260}, {11190800, 9879230}, + {10990100, 9930760}, {10797500, 10007000}, + {10615900, 10106900}, {10448200, 10228700}, + {10297200, 10370500}, {10165100, 10530200}, + {10054100, 10705100}, {9965870, 10892600}, + {9901840, 11089700}, {9863010, 11293200}, + {9850000, 11500000}, {9863010, 11706800}, + {9901840, 11910300}, {9965870, 12107400}, + {10054100, 12294900}, {10165100, 12469800}, + {10297200, 12629500}, {10448200, 12771300}, + {10615900, 12893100}, {10797500, 12993000}, + {10990100, 13069200}, {11190800, 13120800}, + {11396400, 13146700}, {11603600, 13146700}, + {11809200, 13120800}, {12009900, 13069200}, + {12202500, 12993000}, {12384100, 12893100}, + {12551700, 12771300}, {12702800, 12629500}, + {12834900, 12469800}, {12945900, 12294900}, + {13034100, 12107400}, {13098200, 11910300}, + {13137000, 11706800}, {13150000, 11500000}, + {13137000, 11293200}, {13098200, 11089700}, + {13034100, 10892600}, {12945900, 10705100}, + {12834900, 10530200}, {12702800, 10370500}, + {12551700, 10228700}, {12384100, 10106900}, + {12202500, 10007000}, {12009900, 9930760}, + {11809200, 9879230}, {11603600, 9853260}, + }, + { + {-17177700, 1309310}, {-17525300, 1383200}, + {-17850000, 1527760}, {-18137500, 1736650}, + {-18375300, 2000760}, {-18553000, 2308550}, + {-18662800, 2646550}, {-18700000, 3000000}, + {-18662800, 3353450}, {-18553000, 3691450}, + {-18375300, 3999230}, {-18137500, 4263350}, + {-17850000, 4472240}, {-17525300, 4616800}, + {-17177700, 4690690}, {-16822300, 4690690}, + {-16474701, 4616800}, {-16150000, 4472240}, + {-15862500, 4263350}, {-15624700, 3999230}, + {-15447000, 3691450}, {-15337100, 3353450}, + {-15300000, 3000000}, {-15337100, 2646550}, + {-15447000, 2308550}, {-15624700, 2000760}, + {-15862500, 1736650}, {-16150000, 1527760}, + {-16474701, 1383200}, {-16822300, 1309310}, + }, + { + {-11603600, -2146740}, {-11809200, -2120770}, + {-12009900, -2069240}, {-12202500, -1992960}, + {-12384100, -1893140}, {-12551700, -1771350}, + {-12702800, -1629500}, {-12834900, -1469840}, + {-12945900, -1294890}, {-13034100, -1107400}, + {-13098200, -910337}, {-13137000, -706800}, + {-13150000, -500000}, {-13137000, -293200}, + {-13098200, -89661}, {-13034100, 107405}, + {-12945900, 294893}, {-12834900, 469845}, + {-12702800, 629502}, {-12551700, 771346}, + {-12384100, 893141}, {-12202500, 992964}, + {-12009900, 1069240}, {-11809200, 1120770}, + {-11603600, 1146740}, {-11396400, 1146740}, + {-11190800, 1120770}, {-10990100, 1069240}, + {-10797500, 992964}, {-10615900, 893141}, + {-10448200, 771346}, {-10297200, 629502}, + {-10165100, 469845}, {-10054100, 294893}, + {-9965870, 107405}, {-9901840, -89661}, + {-9863010, -293200}, {-9850000, -499999}, + {-9863010, -706800}, {-9901840, -910337}, + {-9965870, -1107400}, {-10054100, -1294890}, + {-10165100, -1469840}, {-10297200, -1629500}, + {-10448200, -1771350}, {-10615900, -1893140}, + {-10797500, -1992960}, {-10990100, -2069240}, + {-11190800, -2120770}, {-11396400, -2146740}, + }, + { + {11396400, -2146740}, {11190800, -2120770}, + {10990100, -2069240}, {10797500, -1992960}, + {10615900, -1893140}, {10448200, -1771350}, + {10297200, -1629500}, {10165100, -1469840}, + {10054100, -1294890}, {9965870, -1107400}, + {9901840, -910337}, {9863010, -706800}, + {9850000, -500000}, {9863010, -293200}, + {9901840, -89661}, {9965870, 107405}, + {10054100, 294893}, {10165100, 469845}, + {10297200, 629502}, {10448200, 771346}, + {10615900, 893141}, {10797500, 992964}, + {10990100, 1069240}, {11190800, 1120770}, + {11396400, 1146740}, {11603600, 1146740}, + {11809200, 1120770}, {12009900, 1069240}, + {12202500, 992964}, {12384100, 893141}, + {12551700, 771346}, {12702800, 629502}, + {12834900, 469845}, {12945900, 294893}, + {13034100, 107405}, {13098200, -89661}, + {13137000, -293200}, {13150000, -499999}, + {13137000, -706800}, {13098200, -910337}, + {13034100, -1107400}, {12945900, -1294890}, + {12834900, -1469840}, {12702800, -1629500}, + {12551700, -1771350}, {12384100, -1893140}, + {12202500, -1992960}, {12009900, -2069240}, + {11809200, -2120770}, {11603600, -2146740}, + }, + }}, + }, + ExPolygons{ + // "z-axis-top.stl": + MyPoly{{ + {34521100, -3478930}, + {38000000, 0}, + {38000000, 23000000}, + {33000000, 28000000}, + {24000000, 28000000}, + {20000000, 21071800}, + {12000000, 21071800}, + {8000000, 28000000}, + {8000000, 34200000}, + {2200000, 40000000}, + {0, 40000000}, + {0, 1000000}, + {6000000, -5000000}, + {33000000, -5000000}, + }, + { + { + {12000000, 3071800}, + {8000000, 10000000}, + {12000000, 16928200}, + {20000000, 16928200}, + {24000000, 10000000}, + {20000000, 3071800}, + }, + }}, + MyPoly{{ + {8000000, -46200000}, + {8000000, -40000000}, + {12000000, -33071800}, + {20000000, -33071800}, + {24000000, -40000000}, + {33000000, -40000000}, + {38000000, -35000000}, + {38000000, -12000000}, + {34521100, -8521070}, + {33000000, -7000000}, + {6000000, -7000000}, + {0, -13000000}, + {0, -52000000}, + {2200000, -52000000}, + }, + { + { + {12000000, -28928200}, + {8000000, -22000000}, + {12000000, -15071800}, + {20000000, -15071800}, + {24000000, -22000000}, + {20000000, -28928200}, + }, + }}, + }, + ExPolygons{ + // "y-belt-holder.stl": + MyPoly{{ + {12500000, 24000000}, + {5142140, 24000000}, + {4500000, 23357900}, + {4500000, 15000000}, + {-9057860, 15000000}, + {-11000000, 13057900}, + {-11000000, -13057900}, + {-9057860, -15000000}, + {4500000, -15000000}, + {4500000, -23357900}, + {5142140, -24000000}, + {12500000, -24000000}, + }, + {}}, + }, + ExPolygons{ + // "LCD-knob.stl": + MyPoly{{ + {1045280, -9945220}, {2079119, -9781480}, + {3090170, -9510560}, {4067370, -9135450}, + {5000000, -8660250}, {5877850, -8090170}, + {6691310, -7431450}, {7431450, -6691310}, + {8090170, -5877850}, {8660250, -5000000}, + {9135450, -4067370}, {9510560, -3090170}, + {9781480, -2079119}, {9945220, -1045280}, + {10000000, 0}, {9945220, 1045280}, + {9781480, 2079119}, {9510560, 3090170}, + {9135450, 4067370}, {8660250, 5000000}, + {8090170, 5877850}, {7431450, 6691310}, + {6691310, 7431450}, {5877850, 8090170}, + {5000000, 8660250}, {4067370, 9135450}, + {3090170, 9510560}, {2100000, 9775880}, + {2100000, 18221800}, {-2100000, 18221800}, + {-2100000, 9775880}, {-3090170, 9510560}, + {-4067370, 9135450}, {-5000000, 8660250}, + {-5877850, 8090170}, {-6691310, 7431450}, + {-7431450, 6691310}, {-8090170, 5877850}, + {-8660250, 5000000}, {-9135450, 4067370}, + {-9510560, 3090170}, {-9781480, 2079119}, + {-9945220, 1045280}, {-10000000, 0}, + {-9945220, -1045280}, {-9781480, -2079119}, + {-9510560, -3090170}, {-9135450, -4067370}, + {-8660250, -5000000}, {-8090170, -5877850}, + {-7431450, -6691310}, {-6691310, -7431450}, + {-5877850, -8090170}, {-5000000, -8660250}, + {-4067370, -9135450}, {-3090170, -9510560}, + {-2079119, -9781480}, {-1045280, -9945220}, + {0, -10000000}, + }, + {}}, + }, + ExPolygons{ + // "rpi-zero-frame.stl": + MyPoly{{ + {58000000, -25983600}, {58313600, -25983600}, + {58927100, -25853200}, {59500000, -25598100}, + {60007400, -25229400}, {60427100, -24763400}, + {60740600, -24220200}, {60934400, -23623700}, + {61000000, -23000000}, {61000000, 0}, + {60934400, 623734}, {60740600, 1220210}, + {60427100, 1763360}, {60007400, 2229430}, + {59500000, 2598080}, {58927100, 2853170}, + {58313600, 2983570}, {58000000, 2983570}, + {58000000, 3000000}, {55000000, 3000000}, + {55000000, 6000000}, {45000000, 6000000}, + {45000000, 3000000}, {0, 3000000}, + {0, 2983570}, {-313585, 2983570}, + {-927051, 2853170}, {-1500000, 2598080}, + {-2007390, 2229430}, {-2427050, 1763360}, + {-2740640, 1220210}, {-2934440, 623734}, + {-3000000, 0}, {-3000000, -23000000}, + {-2934440, -23623700}, {-2740640, -24220200}, + {-2427050, -24763400}, {-2007390, -25229400}, + {-1500000, -25598100}, {-927051, -25853200}, + {-313585, -25983600}, {313585, -25983600}, + {927051, -25853200}, {1000000, -25820720}, + {1000000, -26000000}, {58000000, -26000000}, + }, + { + { + {44883600, -2063829}, + {44638500, -2012070}, + {44409000, -1909530}, + {44205900, -1762070}, + {44037900, -1575550}, + {43912900, -1358750}, + {43834800, -1120470}, + {43822600, -1000000}, + {46195173, -1000000}, + {46182500, -1120470}, + {46105300, -1358750}, + {45979300, -1575550}, + {45812300, -1762070}, + {45609200, -1909530}, + {45379700, -2012070}, + {45134600, -2063829}, + }, + { + {51045700, -1970080}, + {50800600, -1918320}, + {50571100, -1815780}, + {50368000, -1668320}, + {50200000, -1481800}, + {50075000, -1265000}, + {49996900, -1025740}, + {49994200, -1000000}, + {52347300, -1000000}, + {52344600, -1025740}, + {52267400, -1265000}, + {52141400, -1481800}, + {51973500, -1668320}, + {51771300, -1815780}, + {51541800, -1918320}, + {51296700, -1970080}, + }, + { + {3000000, -20000000}, + {3000000, -3000000}, + {43887500, -3000000}, + {43912900, -2922230}, + {44037900, -2705430}, + {44205900, -2518910}, + {44409000, -2371440}, + {44638500, -2268910}, + {44883600, -2217150}, + {45134600, -2217150}, + {45379700, -2268910}, + {45609200, -2371440}, + {45812300, -2518910}, + {45979300, -2705430}, + {46105300, -2922230}, + {46130400, -3000000}, + {55000000, -3000000}, + {55000000, -20000000}, + }, + { + {22525500, -22729500}, {22321000, -22686100}, + {22130000, -22601000}, {21960900, -22478100}, + {21821000, -22322800}, {21716500, -22141700}, + {21651900, -21942900}, {21630000, -21735000}, + {21651900, -21527100}, {21716500, -21328300}, + {21821000, -21147200}, {21960900, -20991900}, + {22130000, -20869000}, {22321000, -20783900}, + {22525500, -20740500}, {22734500, -20740500}, + {22939000, -20783900}, {23130000, -20869000}, + {23299100, -20991900}, {23439000, -21147200}, + {23543500, -21328300}, {23608100, -21527100}, + {23630000, -21735000}, {23608100, -21942900}, + {23543500, -22141700}, {23439000, -22322800}, + {23299100, -22478100}, {23130000, -22601000}, + {22939000, -22686100}, {22734500, -22729500}, + }, + { + {7285470, -25269500}, {7080980, -25226100}, + {6890000, -25141000}, {6720870, -25018100}, + {6580980, -24862800}, {6476450, -24681700}, + {6411850, -24482900}, {6390000, -24275000}, + {6411850, -24067100}, {6476450, -23868300}, + {6580980, -23687200}, {6720870, -23531900}, + {6890000, -23409000}, {7080980, -23323900}, + {7285470, -23280500}, {7494530, -23280500}, + {7699020, -23323900}, {7890000, -23409000}, + {8059129, -23531900}, {8199020, -23687200}, + {8303540, -23868300}, {8368150, -24067100}, + {8390000, -24275000}, {8368150, -24482900}, + {8303540, -24681700}, {8199020, -24862800}, + {8059129, -25018100}, {7890000, -25141000}, + {7699020, -25226100}, {7494530, -25269500}, + }, + { + {22525500, -25269500}, {22321000, -25226100}, + {22130000, -25141000}, {21960900, -25018100}, + {21821000, -24862800}, {21716500, -24681700}, + {21651900, -24482900}, {21630000, -24275000}, + {21651900, -24067100}, {21716500, -23868300}, + {21821000, -23687200}, {21960900, -23531900}, + {22130000, -23409000}, {22321000, -23323900}, + {22525500, -23280500}, {22734500, -23280500}, + {22939000, -23323900}, {23130000, -23409000}, + {23299100, -23531900}, {23439000, -23687200}, + {23543500, -23868300}, {23608100, -24067100}, + {23630000, -24275000}, {23608100, -24482900}, + {23543500, -24681700}, {23439000, -24862800}, + {23299100, -25018100}, {23130000, -25141000}, + {22939000, -25226100}, {22734500, -25269500}, + }, + { + {14905500, -25269500}, {14701000, -25226100}, + {14510000, -25141000}, {14340900, -25018100}, + {14201000, -24862800}, {14096500, -24681700}, + {14031900, -24482900}, {14010000, -24275000}, + {14031900, -24067100}, {14096500, -23868300}, + {14201000, -23687200}, {14340900, -23531900}, + {14510000, -23409000}, {14701000, -23323900}, + {14905500, -23280500}, {15114500, -23280500}, + {15319000, -23323900}, {15510000, -23409000}, + {15679100, -23531900}, {15819000, -23687200}, + {15923500, -23868300}, {15988100, -24067100}, + {16010000, -24275000}, {15988100, -24482900}, + {15923500, -24681700}, {15819000, -24862800}, + {15679100, -25018100}, {15510000, -25141000}, + {15319000, -25226100}, {15114500, -25269500}, + }, + { + {12365500, -25269500}, {12161000, -25226100}, + {11970000, -25141000}, {11800900, -25018100}, + {11661000, -24862800}, {11556500, -24681700}, + {11491900, -24482900}, {11470000, -24275000}, + {11491900, -24067100}, {11556500, -23868300}, + {11661000, -23687200}, {11800900, -23531900}, + {11970000, -23409000}, {12161000, -23323900}, + {12365500, -23280500}, {12574500, -23280500}, + {12779000, -23323900}, {12970000, -23409000}, + {13139100, -23531900}, {13279000, -23687200}, + {13383500, -23868300}, {13448100, -24067100}, + {13470000, -24275000}, {13448100, -24482900}, + {13383500, -24681700}, {13279000, -24862800}, + {13139100, -25018100}, {12970000, -25141000}, + {12779000, -25226100}, {12574500, -25269500}, + }, + { + {9825470, -25269500}, {9620980, -25226100}, + {9430000, -25141000}, {9260870, -25018100}, + {9120980, -24862800}, {9016450, -24681700}, + {8951850, -24482900}, {8930000, -24275000}, + {8951850, -24067100}, {9016450, -23868300}, + {9120980, -23687200}, {9260870, -23531900}, + {9430000, -23409000}, {9620980, -23323900}, + {9825470, -23280500}, {10034500, -23280500}, + {10239000, -23323900}, {10430000, -23409000}, + {10599100, -23531900}, {10739000, -23687200}, + {10843500, -23868300}, {10908100, -24067100}, + {10930000, -24275000}, {10908100, -24482900}, + {10843500, -24681700}, {10739000, -24862800}, + {10599100, -25018100}, {10430000, -25141000}, + {10239000, -25226100}, {10034500, -25269500}, + }, + }}, + }, + ExPolygons{ + // "extruder-idler.stl": + MyPoly{{ + {31500000, 47000000}, {21500000, 47000000}, + {21500000, 43000000}, {21483600, 43000000}, + {21483600, 42686400}, {21443900, 42500000}, + {21391492, 42253213}, {21356700, 42089700}, + {21353200, 42072900}, {21302200, 41958400}, + {21234000, 41805300}, {21184936, 41695077}, + {21111500, 41530200}, {21098100, 41500000}, + {21058200, 41445200}, {20966500, 41319000}, + {20900900, 41228700}, {20812400, 41106900}, + {20729400, 40992600}, {20660700, 40930800}, + {20566175, 40845649}, {20359300, 40659400}, + {20263400, 40572900}, {19720200, 40259400}, + {19123700, 40065600}, {18688436, 40019806}, + {18500000, 40000000}, {18409800, 40009500}, + {17876300, 40065600}, {17279800, 40259400}, + {16736601, 40572900}, {16640699, 40659400}, + {16435924, 40843758}, {16339300, 40930800}, + {16270599, 40992600}, {16187599, 41106900}, + {16099100, 41228700}, {15996000, 41370500}, + {16270599, 40992600}, {15901900, 41500000}, + {15888500, 41530200}, {15810600, 41705100}, + {15755314, 41829246}, {15735700, 41873300}, + {15646800, 42072900}, {15643300, 42089700}, + {15608508, 42253213}, {15556100, 42500000}, + {15516400, 42686400}, {15516400, 43000000}, + {15500000, 43000000}, {15500000, 47000000}, + {6500000, 47000000}, {6500000, 39000000}, + {3500000, 39000000}, {3500000, 22000000}, + {6500000, 22000000}, {6500000, 13000000}, + {31500000, 13000000}, + }, + { + { + {12923500, 25400000}, + {12144000, 25850000}, + {12144000, 32150002}, + {12923500, 32599998}, + {17600000, 35300000}, + {18379400, 34850000}, + {23056000, 32150002}, + {23056000, 32000000}, + {22750000, 32000000}, + {22750000, 30992100}, + {21750000, 29994100}, + {21750000, 25096023}, + {17600000, 22700000}, + }, + { + {26393300, 16803400}, {26181400, 16830100}, + {25974700, 16883200}, {25776200, 16961800}, + {25589100, 17064600}, {25416400, 17190100}, + {25260800, 17336300}, {25124700, 17500800}, + {25010300, 17681000}, {24919400, 17874200}, + {24853400, 18077200}, {24813400, 18286900}, + {24800000, 18500000}, {24813400, 18713100}, + {24853400, 18922800}, {24919400, 19125800}, + {25010300, 19319000}, {25124700, 19499200}, + {25260800, 19663700}, {25416400, 19809900}, + {25589100, 19935400}, {25776200, 20038200}, + {25974700, 20116800}, {26181400, 20169900}, + {26393300, 20196600}, {26606700, 20196600}, + {26818500, 20169900}, {27025300, 20116800}, + {27223800, 20038200}, {27410900, 19935400}, + {27583600, 19809900}, {27739200, 19663700}, + {27875300, 19499200}, {27989700, 19319000}, + {28080600, 19125800}, {28146600, 18922800}, + {28186600, 18713100}, {28200000, 18500000}, + {28186600, 18286900}, {28146600, 18077200}, + {28080600, 17874200}, {27989700, 17681000}, + {27875300, 17500800}, {27739200, 17336300}, + {27583600, 17190100}, {27410900, 17064600}, + {27223800, 16961800}, {27025300, 16883200}, + {26818500, 16830100}, {26606700, 16803400}, + }, + { + {11393300, 16803400}, {11181500, 16830100}, + {10974700, 16883200}, {10776200, 16961800}, + {10589100, 17064600}, {10416400, 17190100}, + {10260800, 17336300}, {10124700, 17500800}, + {10010300, 17681000}, {9919380, 17874200}, + {9853410, 18077200}, {9813400, 18286900}, + {9800000, 18500000}, {9813400, 18713100}, + {9853410, 18922800}, {9919380, 19125800}, + {10010300, 19319000}, {10124700, 19499200}, + {10260800, 19663700}, {10416400, 19809900}, + {10589100, 19935400}, {10776200, 20038200}, + {10974700, 20116800}, {11181500, 20169900}, + {11393300, 20196600}, {11606700, 20196600}, + {11818500, 20169900}, {12025300, 20116800}, + {12223800, 20038200}, {12410900, 19935400}, + {12583600, 19809900}, {12739200, 19663700}, + {12875300, 19499200}, {12989700, 19319000}, + {13080600, 19125800}, {13146600, 18922800}, + {13186600, 18713100}, {13200000, 18500000}, + {13186600, 18286900}, {13146600, 18077200}, + {13080600, 17874200}, {12989700, 17681000}, + {12875300, 17500800}, {12739200, 17336300}, + {12583600, 17190100}, {12410900, 17064600}, + {12223800, 16961800}, {12025300, 16883200}, + {11818500, 16830100}, {11606700, 16803400}, + }, + }}, + }, + ExPolygons{ + // "filament-sensor-cover.stl": + MyPoly{{ + {18000000, 30500000}, + {-6000000, 30500000}, + {-6000000, -5500000}, + {18000000, -5500000}, + }, + { + { + {-1167240, 22908800}, {-1494430, 22978300}, + {-1800000, 23114400}, {-2070610, 23311000}, + {-2294430, 23559500}, {-2461670, 23849200}, + {-2565040, 24167300}, {-2582520, 24333700}, + {-2600000, 24500000}, {-2582520, 24666300}, + {-2565040, 24832700}, {-2461670, 25150800}, + {-2294430, 25440500}, {-2070610, 25689000}, + {-1800000, 25885600}, {-1494430, 26021700}, + {-1167240, 26091200}, {-832754, 26091200}, + {-505572, 26021700}, {-200000, 25885600}, + {70608, 25689000}, {294427, 25440500}, + {461672, 25150800}, {565036, 24832700}, + {582518, 24666300}, {599999, 24500000}, + {582518, 24333700}, {565036, 24167300}, + {461672, 23849200}, {294427, 23559500}, + {70608, 23311000}, {-200000, 23114400}, + {-505572, 22978300}, {-832754, 22908800}, + }, + { + {-144249, 15627600}, {-426443, 15687500}, + {-689999, 15804900}, {-740738, 15841700}, + {-923400, 15974500}, {-965366, 16021099}, + {-1116440, 16188900}, {-1229330, 16384399}, + {-1260690, 16438700}, {-1280070, 16498400}, + {-1349840, 16713100}, {-1373440, 16937600}, + {-1380000, 17000000}, {-1373440, 17062400}, + {-1349840, 17286900}, {-1280070, 17501600}, + {-1260690, 17561300}, {-1229330, 17615600}, + {-1116440, 17811100}, {-965366, 17978900}, + {-923400, 18025500}, {-872661, 18062400}, + {-689999, 18195100}, {-483738, 18286900}, + {-426443, 18312500}, {-144249, 18372400}, + {144249, 18372400}, {426443, 18312500}, + {483738, 18286900}, {689999, 18195100}, + {872661, 18062400}, {923400, 18025500}, + {965366, 17978900}, {1116440, 17811100}, + {1229330, 17615600}, {1260690, 17561300}, + {1280070, 17501600}, {1349840, 17286900}, + {1373440, 17062400}, {1380000, 17000000}, + {1373440, 16937600}, {1349840, 16713100}, + {1280070, 16498400}, {1260690, 16438700}, + {1229330, 16384399}, {1116440, 16188900}, + {965366, 16021099}, {923400, 15974500}, + {872661, 15937600}, {689999, 15804900}, + {483738, 15713100}, {426443, 15687500}, + {144249, 15627600}, + }, + { + {11832800, 10408800}, {11505600, 10478300}, + {11200000, 10614400}, {10929400, 10811000}, + {10705600, 11059500}, {10538300, 11349200}, + {10435000, 11667300}, {10400000, 12000000}, + {10435000, 12332700}, {10538300, 12650800}, + {10705600, 12940500}, {10929400, 13189000}, + {11200000, 13385600}, {11505600, 13521700}, + {11832800, 13591200}, {12167200, 13591200}, + {12494400, 13521700}, {12800000, 13385600}, + {13070600, 13189000}, {13294400, 12940500}, + {13461700, 12650800}, {13565000, 12332700}, + {13585100, 12141400}, {13600000, 12000000}, + {13582500, 11833700}, {13565000, 11667300}, + {13461700, 11349200}, {13294400, 11059500}, + {13070600, 10811000}, {12800000, 10614400}, + {12494400, 10478300}, {12167200, 10408800}, + }, + }}, + }, + ExPolygons{ + // "nozzle-fan.stl": + MyPoly{{ + {-14922022, 12367866}, {-14205200, 14337200}, + {-13800000, 15450500}, {-13800000, 17000000}, + {-13789800, 17000000}, {-13704300, 17813300}, + {-13694100, 17910800}, {-12789600, 20694300}, + {-11326200, 23229000}, {-9367830, 25404000}, + {-7000000, 27124400}, {-5253290, 27902000}, + {-4326240, 28314800}, {-1463400, 28923300}, + {1463400, 28923300}, {4326240, 28314800}, + {5253290, 27902000}, {7000000, 27124400}, + {9367830, 25404000}, {11326200, 23229000}, + {12789600, 20694300}, {13694100, 17910800}, + {13704300, 17813300}, {13789800, 17000000}, + {13800000, 17000000}, {13800000, 15606900}, + {14240100, 14397700}, {15015200, 12268200}, + {15476800, 11000000}, {17800000, 11000000}, + {17800000, 11032900}, {18427200, 11032900}, + {19654100, 11293700}, {20800000, 11803800}, + {21814800, 12541100}, {22654100, 13473300}, + {23281300, 14559600}, {23668900, 15752500}, + {23706600, 16111099}, {23800000, 17000000}, + {23789800, 17000000}, {23475500, 19989900}, + {21925100, 24761700}, {19416400, 29106800}, + {18612200, 30000000}, {18000000, 30679900}, + {18000000, 35500000}, {18500000, 35500000}, + {18500000, 40500000}, {17839500, 40500000}, + {11200000, 52000000}, {9532010, 52000000}, + {9532010, 52800000}, {5416000, 52800000}, + {5416000, 52000000}, {4793090, 52000000}, + {4793090, 52800000}, {-65918, 52800000}, + {-65918, 52000000}, {-296738, 52000000}, + {-296738, 52800000}, {-4368740, 52800000}, + {-4368740, 52000000}, {-4995880, 52000000}, + {-4995880, 52800000}, {-5997880, 52800000}, + {-5997880, 52000000}, {-6891430, 52000000}, + {-6891430, 52800000}, {-10271400, 52800000}, + {-10271400, 52000000}, {-11139700, 52000000}, + {-18000000, 40117700}, {-18000000, 30679900}, + {-18612200, 30000000}, {-19416400, 29106800}, + {-21925100, 24761700}, {-23475500, 19989900}, + {-23789800, 17000000}, {-23800000, 17000000}, + {-23668900, 15752500}, {-23281300, 14559600}, + {-22654100, 13473300}, {-21814800, 12541100}, + {-20800000, 11803800}, {-19654100, 11293700}, + {-18427200, 11032900}, {-17800000, 11032900}, + {-17800000, 11000000}, {-15419900, 11000000}, + }, + {}}, + }, + ExPolygons{ + // "x-carriage-back.stl": + MyPoly{{ + {-5981270, -38729200}, {-5354100, -37638700}, + {-4514780, -36703000}, {-3500000, -35962900}, + {-2354100, -35450800}, {-1969730, -35368800}, + {-1127170, -35189000}, {127170, -35189000}, + {969727, -35368800}, {1354100, -35450800}, + {2500000, -35962900}, {3514780, -36703000}, + {4354100, -37638700}, {4981270, -38729200}, + {5068930, -39000000}, {13757900, -39000000}, + {16000000, -36757900}, {16000000, -5000000}, + {25500000, -5000000}, {25500000, 13795200}, + {25057400, 14352100}, {24542500, 15342500}, + {24400000, 15200000}, {14600000, 25000000}, + {14500000, 25000000}, {14500000, 33050000}, + {13050000, 34500000}, {-500000, 34500000}, + {-500000, 28500000}, {-8000000, 28500000}, + {-8000000, 32500000}, {-5000000, 32500000}, + {-5000000, 34500000}, {-15550000, 34500000}, + {-17000000, 33050000}, {-17000000, 25000000}, + {-17100000, 25000000}, {-25572200, 16527800}, + {-25903900, 15532800}, {-26500000, 14386100}, + {-26500000, -5000000}, {-17000000, -5000000}, + {-17000000, -36757900}, {-14757900, -39000000}, + {-6068930, -39000000}, + }, + { + { + {-13103600, 29353300}, {-13309200, 29379200}, + {-13509900, 29430800}, {-13702500, 29507000}, + {-13884100, 29606900}, {-14051700, 29728700}, + {-14202800, 29870500}, {-14334900, 30030200}, + {-14445900, 30205100}, {-14534100, 30392600}, + {-14598200, 30589700}, {-14637000, 30793200}, + {-14650000, 31000000}, {-14637000, 31206800}, + {-14598200, 31410300}, {-14534100, 31607400}, + {-14445900, 31794900}, {-14334900, 31969800}, + {-14202800, 32129502}, {-14051700, 32271302}, + {-13884100, 32393100}, {-13702500, 32493000}, + {-13509900, 32569198}, {-13309200, 32620800}, + {-13103600, 32646702}, {-12896400, 32646702}, + {-12690800, 32620800}, {-12490100, 32569198}, + {-12297500, 32493000}, {-12115900, 32393100}, + {-11948200, 32271302}, {-11797200, 32129502}, + {-11665100, 31969800}, {-11554100, 31794900}, + {-11465900, 31607400}, {-11401800, 31410300}, + {-11363000, 31206800}, {-11350000, 31000000}, + {-11363000, 30793200}, {-11401800, 30589700}, + {-11465900, 30392600}, {-11554100, 30205100}, + {-11665100, 30030200}, {-11797200, 29870500}, + {-11948200, 29728700}, {-12115900, 29606900}, + {-12297500, 29507000}, {-12490100, 29430800}, + {-12690800, 29379200}, {-12896400, 29353300}, + }, + { + {10396400, 29353300}, {10190800, 29379200}, + {9990120, 29430800}, {9797460, 29507000}, + {9615890, 29606900}, {9448250, 29728700}, + {9297200, 29870500}, {9165120, 30030200}, + {9054090, 30205100}, {8965870, 30392600}, + {8901840, 30589700}, {8863010, 30793200}, + {8850000, 31000000}, {8863010, 31206800}, + {8901840, 31410300}, {8965870, 31607400}, + {9054090, 31794900}, {9165120, 31969800}, + {9297200, 32129502}, {9448250, 32271302}, + {9615890, 32393100}, {9797460, 32493000}, + {9990120, 32569198}, {10190800, 32620800}, + {10396400, 32646702}, {10603600, 32646702}, + {10809200, 32620800}, {11009900, 32569198}, + {11202500, 32493000}, {11384100, 32393100}, + {11551700, 32271302}, {11702800, 32129502}, + {11834900, 31969800}, {11945900, 31794900}, + {12034100, 31607400}, {12098200, 31410300}, + {12137000, 31206800}, {12150000, 31000000}, + {12137000, 30793200}, {12098200, 30589700}, + {12034100, 30392600}, {11945900, 30205100}, + {11834900, 30030200}, {11702800, 29870500}, + {11551700, 29728700}, {11384100, 29606900}, + {11202500, 29507000}, {11009900, 29430800}, + {10809200, 29379200}, {10603600, 29353300}, + }, + { + {-8000000, 17500000}, + {-8000000, 22500000}, + {-4500000, 22500000}, + {-4500000, 17500000}, + }, + { + {-1103600, 2353260}, {-1309180, 2379230}, + {-1509880, 2430760}, {-1702540, 2507040}, + {-1884110, 2606860}, {-2051750, 2728650}, + {-2202800, 2870500}, {-2334880, 3030150}, + {-2445910, 3205110}, {-2534130, 3392590}, + {-2598160, 3589660}, {-2636990, 3793200}, + {-2650000, 4000000}, {-2636990, 4206800}, + {-2598160, 4410340}, {-2534130, 4607400}, + {-2445910, 4794890}, {-2334880, 4969840}, + {-2202800, 5129500}, {-2051750, 5271350}, + {-1884110, 5393140}, {-1702540, 5492960}, + {-1509880, 5569240}, {-1309180, 5620770}, + {-1103600, 5646740}, {-896395, 5646740}, + {-690821, 5620770}, {-490122, 5569240}, + {-297463, 5492960}, {-115886, 5393140}, + {51749, 5271350}, {202798, 5129500}, + {334878, 4969840}, {445906, 4794890}, + {534131, 4607400}, {598162, 4410340}, + {636989, 4206800}, {650000, 4000000}, + {636989, 3793200}, {598162, 3589660}, + {534131, 3392590}, {445906, 3205110}, + {334878, 3030150}, {202798, 2870500}, + {51749, 2728650}, {-115886, 2606860}, + {-297463, 2507040}, {-490122, 2430760}, + {-690821, 2379230}, {-896395, 2353260}, + }, + { + {10876300, -3434440}, {10279800, -3240640}, + {9736640, -2927050}, {9270570, -2507390}, + {8901920, -2000000}, {8646830, -1427050}, + {8516430, -813585}, {8516430, -186414}, + {8646830, 427051}, {8901920, 999999}, + {9270570, 1507390}, {9736640, 1927050}, + {10279800, 2240640}, {10876300, 2434440}, + {11500000, 2500000}, {12123700, 2434440}, + {12720200, 2240640}, {13263400, 1927050}, + {13729400, 1507390}, {14098100, 1000000}, + {14353200, 427051}, {14483600, -186414}, + {14483600, -813585}, {14353200, -1427050}, + {14098100, -2000000}, {13729400, -2507390}, + {13263400, -2927050}, {12720200, -3240640}, + {12123700, -3434440}, {11500000, -3500000}, + }, + { + {-12123700, -3434440}, {-12720200, -3240640}, + {-13263400, -2927050}, {-13729400, -2507390}, + {-14098100, -2000000}, {-14353200, -1427050}, + {-14483600, -813585}, {-14483600, -186414}, + {-14353200, 427051}, {-14098100, 999999}, + {-13729400, 1507390}, {-13263400, 1927050}, + {-12720200, 2240640}, {-12123700, 2434440}, + {-11500000, 2500000}, {-10876300, 2434440}, + {-10279800, 2240640}, {-9736640, 1927050}, + {-9270570, 1507390}, {-8901920, 1000000}, + {-8646830, 427051}, {-8516430, -186414}, + {-8516430, -813585}, {-8646830, -1427050}, + {-8901920, -2000000}, {-9270570, -2507390}, + {-9736640, -2927050}, {-10279800, -3240640}, + {-10876300, -3434440}, {-11500000, -3500000}, + }, + { + {-1539560, -22890700}, {-2533680, -22567700}, + {-3438930, -22045100}, {-3590280, -21908800}, + {-4215720, -21345700}, {-4806270, -20532800}, + {-4830130, -20500000}, {-5012570, -20090200}, + {-5255280, -19545100}, {-5472610, -18522600}, + {-5472610, -18000000}, {-5500000, -18000000}, + {-5500000, -14000000}, {-5472610, -14000000}, + {-5472610, -13477400}, {-5255280, -12454900}, + {-5052740, -12000000}, {-4830130, -11500000}, + {-4215720, -10654300}, {-3438930, -9954910}, + {-3189080, -9810670}, {-2533680, -9432270}, + {-1539560, -9109260}, {-500000, -9000000}, + {539558, -9109260}, {1309400, -9359400}, + {1533680, -9432270}, {2438930, -9954910}, + {3215720, -10654300}, {3830130, -11500000}, + {4052740, -12000000}, {4255280, -12454900}, + {4472610, -13477400}, {4472610, -14000000}, + {4500000, -14000000}, {4500000, -18000000}, + {4472610, -18000000}, {4472610, -18522600}, + {4255280, -19545100}, {4012570, -20090200}, + {3830130, -20500000}, {3806270, -20532800}, + {3215720, -21345700}, {2590280, -21908800}, + {2438930, -22045100}, {1533680, -22567700}, + {539558, -22890700}, {-499999, -23000000}, + }, + { + {-832658, -28565000}, {-1150780, -28461700}, + {-1440460, -28294400}, {-1689030, -28070600}, + {-1885640, -27800000}, {-2021689, -27494400}, + {-2091229, -27167200}, {-2091229, -26832800}, + {-2021689, -26505600}, {-1885640, -26200000}, + {-1689030, -25929400}, {-1440460, -25705600}, + {-1150780, -25538300}, {-832658, -25435000}, + {-500000, -25400000}, {-167341, -25435000}, + {150778, -25538300}, {440456, -25705600}, + {689032, -25929400}, {885640, -26200000}, + {1021690, -26505600}, {1091230, -26832800}, + {1091230, -27167200}, {1021690, -27494400}, + {885640, -27800000}, {689032, -28070600}, + {440456, -28294400}, {150778, -28461700}, + {-167341, -28565000}, {-499999, -28600000}, + }, + { + {9396390, -37646700}, {9190820, -37620800}, + {8990120, -37569200}, {8797460, -37493000}, + {8615890, -37393100}, {8448250, -37271300}, + {8297200, -37129500}, {8165120, -36969800}, + {8054089, -36794900}, {7965870, -36607400}, + {7901840, -36410300}, {7863010, -36206800}, + {7850000, -36000000}, {7863010, -35793200}, + {7901840, -35589700}, {7965870, -35392600}, + {8054089, -35205100}, {8165120, -35030200}, + {8297200, -34870500}, {8448250, -34728700}, + {8615890, -34606900}, {8797460, -34507000}, + {8990120, -34430800}, {9190820, -34379200}, + {9396390, -34353300}, {9603600, -34353300}, + {9809180, -34379200}, {10009900, -34430800}, + {10202500, -34507000}, {10384100, -34606900}, + {10551700, -34728700}, {10702800, -34870500}, + {10834900, -35030200}, {10945900, -35205100}, + {11034100, -35392600}, {11098200, -35589700}, + {11137000, -35793200}, {11150000, -36000000}, + {11137000, -36206800}, {11098200, -36410300}, + {11034100, -36607400}, {10945900, -36794900}, + {10834900, -36969800}, {10702800, -37129500}, + {10551700, -37271300}, {10384100, -37393100}, + {10202500, -37493000}, {10009900, -37569200}, + {9809180, -37620800}, {9603600, -37646700}, + }, + { + {-10603600, -37646700}, {-10809200, -37620800}, + {-11009900, -37569200}, {-11202500, -37493000}, + {-11384100, -37393100}, {-11551700, -37271300}, + {-11702800, -37129500}, {-11834900, -36969800}, + {-11945900, -36794900}, {-12034100, -36607400}, + {-12098200, -36410300}, {-12137000, -36206800}, + {-12150000, -36000000}, {-12137000, -35793200}, + {-12098200, -35589700}, {-12034100, -35392600}, + {-11945900, -35205100}, {-11834900, -35030200}, + {-11702800, -34870500}, {-11551700, -34728700}, + {-11384100, -34606900}, {-11202500, -34507000}, + {-11009900, -34430800}, {-10809200, -34379200}, + {-10603600, -34353300}, {-10396400, -34353300}, + {-10190800, -34379200}, {-9990120, -34430800}, + {-9797460, -34507000}, {-9615890, -34606900}, + {-9448250, -34728700}, {-9297200, -34870500}, + {-9165120, -35030200}, {-9054090, -35205100}, + {-8965870, -35392600}, {-8901840, -35589700}, + {-8863010, -35793200}, {-8850000, -36000000}, + {-8863010, -36206800}, {-8901840, -36410300}, + {-8965870, -36607400}, {-9054090, -36794900}, + {-9165120, -36969800}, {-9297200, -37129500}, + {-9448250, -37271300}, {-9615890, -37393100}, + {-9797460, -37493000}, {-9990120, -37569200}, + {-10190800, -37620800}, {-10396400, -37646700}, + }, + }}, + }, + ExPolygons{ + // "extruder-idler-plug.stl": + MyPoly{{ + {-13000000, 42500000}, {-12967200, 42811900}, + {-12906100, 43000000}, {-12870300, 43110100}, + {-12713500, 43381700}, {-12503700, 43614700}, + {-12250000, 43799000}, {-11963500, 43926600}, + {-11656800, 43991800}, {-11343200, 43991800}, + {-11036500, 43926600}, {-10750000, 43799000}, + {-10496300, 43614700}, {-10286500, 43381700}, + {-10129700, 43110100}, {-10093900, 43000000}, + {-10032800, 42811900}, {-10000000, 42500000}, + {-10000000, 40200000}, {-7000000, 40200000}, + {-7000000, 46000000}, {-6400000, 46000000}, + {-6400000, 50500000}, {-11937800, 50500000}, + {-17000000, 47577400}, {-17000000, 40200000}, + {-13000000, 40200000}, + }, + {}}, + }, + ExPolygons{ + // "z-axis-bottom.stl": + MyPoly{{ + {45101600, -4898420}, + {50000000, 0}, + {50000000, 40786800}, + {43286800, 47500000}, + {3500000, 47500000}, + {0, 44000000}, + {0, -2000000}, + {3000000, -5000000}, + {45000000, -5000000}, + }, + { + { + {13696400, 33853300}, {13509500, 33876900}, + {13490800, 33879200}, {13308400, 33926100}, + {13290100, 33930800}, {13097500, 34007000}, + {12915900, 34106900}, {12748300, 34228700}, + {12597200, 34370500}, {12465100, 34530200}, + {12354100, 34705100}, {12265900, 34892600}, + {12201800, 35089700}, {12163000, 35293200}, + {12150000, 35500000}, {12163000, 35706800}, + {12201800, 35910300}, {12265900, 36107400}, + {12354100, 36294900}, {12465100, 36469800}, + {12597200, 36629500}, {12748300, 36771300}, + {12915900, 36893100}, {13097500, 36993000}, + {13290100, 37069200}, {13308400, 37073900}, + {13490800, 37120800}, {13509500, 37123100}, + {13696400, 37146700}, {13903600, 37146700}, + {14090500, 37123100}, {14109200, 37120800}, + {14291600, 37073900}, {14309900, 37069200}, + {14502500, 36993000}, {14684100, 36893100}, + {14791899, 36814806}, {14851700, 36771300}, + {14851800, 36771300}, {15002800, 36629500}, + {15134900, 36469800}, {15245900, 36294900}, + {15334100, 36107400}, {15398200, 35910300}, + {15437000, 35706800}, {15450000, 35500000}, + {15437000, 35293200}, {15398200, 35089700}, + {15334100, 34892600}, {15245900, 34705100}, + {15134900, 34530200}, {15002800, 34370500}, + {14851800, 34228700}, {14851700, 34228700}, + {14791899, 34185194}, {14684100, 34106900}, + {14502500, 34007000}, {14309900, 33930800}, + {14291600, 33926100}, {14109200, 33879200}, + {14090500, 33876900}, {13903600, 33853300}, + }, + { + {44696400, 33853300}, {44509500, 33876900}, + {44490800, 33879200}, {44308400, 33926100}, + {44290100, 33930800}, {44097500, 34007000}, + {43915900, 34106900}, {43748200, 34228700}, + {43597200, 34370500}, {43465100, 34530200}, + {43354100, 34705100}, {43265900, 34892600}, + {43201800, 35089700}, {43163000, 35293200}, + {43150000, 35500000}, {43163000, 35706800}, + {43201800, 35910300}, {43265900, 36107400}, + {43354100, 36294900}, {43465100, 36469800}, + {43597200, 36629500}, {43748200, 36771300}, + {43915900, 36893100}, {44097500, 36993000}, + {44290100, 37069200}, {44308400, 37073900}, + {44490800, 37120800}, {44509500, 37123100}, + {44696400, 37146700}, {44903600, 37146700}, + {45090500, 37123100}, {45109200, 37120800}, + {45291600, 37073900}, {45309900, 37069200}, + {45502500, 36993000}, {45684100, 36893100}, + {45851700, 36771300}, {46002800, 36629500}, + {46134900, 36469800}, {46245900, 36294900}, + {46334100, 36107400}, {46398200, 35910300}, + {46437000, 35706800}, {46450000, 35500000}, + {46437000, 35293200}, {46398200, 35089700}, + {46334100, 34892600}, {46245900, 34705100}, + {46134900, 34530200}, {46002800, 34370500}, + {45851700, 34228700}, {45684100, 34106900}, + {45502500, 34007000}, {45309900, 33930800}, + {45291600, 33926100}, {45109200, 33879200}, + {45090500, 33876900}, {44903600, 33853300}, + }, + { + {28300000, 8702230}, {28300000, 8861350}, + {28129300, 8861350}, {25839000, 9348170}, + {23700000, 10300500}, {21805700, 11676800}, + {20239000, 13416800}, {19068300, 15444600}, + {18344700, 17671400}, {18100000, 20000000}, + {18344700, 22328600}, {19068300, 24555500}, + {20239000, 26583200}, {21805700, 28323200}, + {23700000, 29699500}, {25839000, 30651800}, + {28129300, 31138600}, {30470700, 31138600}, + {32761002, 30651800}, {34900000, 29699500}, + {36794300, 28323200}, {38361000, 26583200}, + {39531700, 24555500}, {40255300, 22328600}, + {40500000, 20000000}, {40255300, 17671400}, + {39531700, 15444600}, {38361000, 13416800}, + {36794300, 11676800}, {34900000, 10300500}, + {32761002, 9348170}, {30470700, 8861350}, + {30300000, 8861350}, {30300000, 8702230}, + }, + { + {29045700, -1042009}, {28541100, -978263}, + {28048500, -851778}, {27575600, -664549}, + {27129900, -419528}, {26718400, -120578}, + {26347700, 227584}, {26023500, 619470}, + {25751000, 1048900}, {25534400, 1509100}, + {25377200, 1992810}, {25281900, 2492400}, + {25250000, 3000000}, {25281900, 3507600}, + {25377200, 4007190}, {25534400, 4490900}, + {25751000, 4951100}, {26023500, 5380530}, + {26347700, 5772420}, {26718400, 6120580}, + {27129900, 6419530}, {27575600, 6664550}, + {28048500, 6851780}, {28300000, 6916360}, + {28300000, 7213590}, {28487100, 7261610}, + {28717100, 7290670}, {29027600, 7329900}, + {29572400, 7329900}, {29882900, 7290670}, + {30112900, 7261610}, {30300000, 7213590}, + {30300000, 6916360}, {30551500, 6851780}, + {31024400, 6664550}, {31470100, 6419530}, + {31881600, 6120580}, {32252300, 5772420}, + {32576500, 5380530}, {32849000, 4951100}, + {33065602, 4490900}, {33222802, 4007190}, + {33318100, 3507600}, {33349998, 3000000}, + {33318100, 2492400}, {33222802, 1992810}, + {33065602, 1509100}, {32849000, 1048900}, + {32576500, 619470}, {32252300, 227584}, + {31881600, -120578}, {31470100, -419528}, + {31024400, -664549}, {30551500, -851778}, + {30058900, -978263}, {29554300, -1042009}, + }, + { + {44696400, 2853260}, {44509500, 2876870}, + {44490800, 2879230}, {44308400, 2926070}, + {44290100, 2930760}, {44097500, 3007040}, + {43915900, 3106860}, {43748200, 3228650}, + {43597200, 3370500}, {43465100, 3530160}, + {43354100, 3705110}, {43265900, 3892600}, + {43201800, 4089660}, {43163000, 4293200}, + {43150000, 4500000}, {43163000, 4706800}, + {43201800, 4910340}, {43265900, 5107410}, + {43354100, 5294890}, {43465100, 5469850}, + {43597200, 5629500}, {43748200, 5771350}, + {43915900, 5893140}, {44097500, 5992960}, + {44290100, 6069240}, {44308400, 6073930}, + {44490800, 6120770}, {44696400, 6146740}, + {44903600, 6146740}, {45109200, 6120770}, + {45291600, 6073930}, {45309900, 6069240}, + {45502500, 5992960}, {45684100, 5893140}, + {45851700, 5771350}, {46002800, 5629500}, + {46134900, 5469850}, {46245900, 5294890}, + {46334100, 5107410}, {46398200, 4910340}, + {46437000, 4706800}, {46450000, 4500000}, + {46437000, 4293200}, {46398200, 4089660}, + {46334100, 3892600}, {46245900, 3705110}, + {46134900, 3530160}, {46002800, 3370500}, + {45851700, 3228650}, {45684100, 3106860}, + {45502500, 3007040}, {45309900, 2930760}, + {45291600, 2926070}, {45109200, 2879230}, + {45090500, 2876870}, {44903600, 2853260}, + }, + { + {13696400, 2853260}, {13509500, 2876870}, + {13490800, 2879230}, {13308400, 2926070}, + {13290100, 2930760}, {13097500, 3007040}, + {12915900, 3106860}, {12748300, 3228650}, + {12597200, 3370500}, {12465100, 3530160}, + {12354100, 3705110}, {12265900, 3892600}, + {12201800, 4089660}, {12163000, 4293200}, + {12150000, 4500000}, {12163000, 4706800}, + {12201800, 4910340}, {12265900, 5107410}, + {12354100, 5294890}, {12465100, 5469850}, + {12597200, 5629500}, {12748300, 5771350}, + {12915900, 5893140}, {13097500, 5992960}, + {13290100, 6069240}, {13308400, 6073930}, + {13490800, 6120770}, {13696400, 6146740}, + {13903600, 6146740}, {14109200, 6120770}, + {14291600, 6073930}, {14309900, 6069240}, + {14502500, 5992960}, {14684100, 5893140}, + {14754724, 5841850}, {14851700, 5771350}, + {14851800, 5771350}, {15002800, 5629500}, + {15134900, 5469850}, {15245900, 5294890}, + {15334100, 5107410}, {15398200, 4910340}, + {15437000, 4706800}, {15450000, 4500000}, + {15437000, 4293200}, {15398200, 4089660}, + {15334100, 3892600}, {15245900, 3705110}, + {15134900, 3530160}, {15002800, 3370500}, + {14851800, 3228650}, {14851700, 3228650}, + {14754724, 3158150}, {14684100, 3106860}, + {14502500, 3007040}, {14309900, 2930760}, + {14291600, 2926070}, {14109200, 2879230}, + {14090500, 2876870}, {13903600, 2853260}, + }, + }}, + MyPoly{{ + {50000000, -53786800}, + {50000000, -13000000}, + {45101600, -8101579}, + {45000000, -8000000}, + {3000000, -8000000}, + {0, -11000000}, + {0, -57000000}, + {3500000, -60500000}, + {43286800, -60500000}, + }, + { + { + {29027600, -20329900}, {28717100, -20290700}, + {28487100, -20261600}, {28300000, -20213600}, + {28300000, -19916400}, {28048500, -19851800}, + {27575600, -19664500}, {27129900, -19419500}, + {26718400, -19120600}, {26347700, -18772400}, + {26023500, -18380500}, {25751000, -17951100}, + {25534400, -17490900}, {25377200, -17007200}, + {25281900, -16507601}, {25250000, -16000000}, + {25281900, -15492400}, {25377200, -14992800}, + {25534400, -14509100}, {25751000, -14048900}, + {26023500, -13619500}, {26347700, -13227600}, + {26718400, -12879400}, {27129900, -12580500}, + {27575600, -12335500}, {28048500, -12148200}, + {28541100, -12021700}, {29045700, -11958000}, + {29554300, -11958000}, {30058900, -12021700}, + {30551500, -12148200}, {31024400, -12335500}, + {31470100, -12580500}, {31881600, -12879400}, + {32252300, -13227600}, {32576500, -13619500}, + {32849000, -14048900}, {33065602, -14509100}, + {33222802, -14992800}, {33318100, -15492400}, + {33349998, -16000000}, {33318100, -16507601}, + {33222802, -17007200}, {33065602, -17490900}, + {32849000, -17951100}, {32576500, -18380500}, + {32252300, -18772400}, {31881600, -19120600}, + {31470100, -19419500}, {31024400, -19664500}, + {30551500, -19851800}, {30300000, -19916400}, + {30300000, -20213600}, {30112900, -20261600}, + {29882900, -20290700}, {29572400, -20329900}, + }, + { + {13696400, -19146700}, {13509500, -19123100}, + {13490800, -19120800}, {13308400, -19073900}, + {13290100, -19069200}, {13097500, -18993000}, + {12915900, -18893100}, {12748300, -18771300}, + {12597200, -18629500}, {12465100, -18469800}, + {12354100, -18294900}, {12265900, -18107400}, + {12201800, -17910300}, {12163000, -17706800}, + {12150000, -17500000}, {12163000, -17293200}, + {12201800, -17089700}, {12265900, -16892600}, + {12354100, -16705099}, {12465100, -16530199}, + {12597200, -16370501}, {12748300, -16228701}, + {12915900, -16106899}, {13097500, -16007000}, + {13290100, -15930800}, {13308400, -15926100}, + {13490800, -15879200}, {13509500, -15876900}, + {13696400, -15853300}, {13903600, -15853300}, + {14090500, -15876900}, {14109200, -15879200}, + {14291600, -15926100}, {14309900, -15930800}, + {14502500, -16007000}, {14684100, -16106899}, + {14791305, -16184763}, {14851700, -16228701}, + {14851800, -16228701}, {15002800, -16370501}, + {15134900, -16530199}, {15245900, -16705099}, + {15334100, -16892600}, {15398200, -17089700}, + {15437000, -17293200}, {15450000, -17500000}, + {15437000, -17706800}, {15398200, -17910300}, + {15334100, -18107400}, {15245900, -18294900}, + {15134900, -18469800}, {15002800, -18629500}, + {14851800, -18771300}, {14851700, -18771300}, + {14791899, -18814806}, {14684100, -18893100}, + {14502500, -18993000}, {14309900, -19069200}, + {14291600, -19073900}, {14109200, -19120800}, + {14090500, -19123100}, {13903600, -19146700}, + }, + { + {44696400, -19146700}, {44509500, -19123100}, + {44490800, -19120800}, {44308400, -19073900}, + {44290100, -19069200}, {44097500, -18993000}, + {43915900, -18893100}, {43748200, -18771300}, + {43597200, -18629500}, {43465100, -18469800}, + {43354100, -18294900}, {43265900, -18107400}, + {43201800, -17910300}, {43163000, -17706800}, + {43150000, -17500000}, {43163000, -17293200}, + {43201800, -17089700}, {43265900, -16892600}, + {43354100, -16705099}, {43465100, -16530199}, + {43597200, -16370501}, {43748200, -16228701}, + {43915900, -16106899}, {44097500, -16007000}, + {44290100, -15930800}, {44308400, -15926100}, + {44490800, -15879200}, {44509500, -15876900}, + {44696400, -15853300}, {44903600, -15853300}, + {45090500, -15876900}, {45109200, -15879200}, + {45291600, -15926100}, {45309900, -15930800}, + {45502500, -16007000}, {45684100, -16106899}, + {45851700, -16228701}, {46002800, -16370501}, + {46134900, -16530199}, {46245900, -16705099}, + {46334100, -16892600}, {46398200, -17089700}, + {46437000, -17293200}, {46450000, -17500000}, + {46437000, -17706800}, {46398200, -17910300}, + {46334100, -18107400}, {46245900, -18294900}, + {46134900, -18469800}, {46002800, -18629500}, + {45851700, -18771300}, {45684100, -18893100}, + {45502500, -18993000}, {45309900, -19069200}, + {45291600, -19073900}, {45109200, -19120800}, + {45090500, -19123100}, {44903600, -19146700}, + }, + { + {28129300, -44138600}, {25839000, -43651800}, + {23700000, -42699500}, {21805700, -41323200}, + {20239000, -39583200}, {19068300, -37555500}, + {18344700, -35328600}, {18100000, -33000000}, + {18344700, -30671400}, {19068300, -28444500}, + {20239000, -26416800}, {21805700, -24676800}, + {23700000, -23300500}, {25839000, -22348200}, + {28129300, -21861400}, {28300000, -21861400}, + {28300000, -21702200}, {30300000, -21702200}, + {30300000, -21861400}, {30470700, -21861400}, + {32761002, -22348200}, {34900000, -23300500}, + {36794300, -24676800}, {38361000, -26416800}, + {39531700, -28444500}, {40255300, -30671400}, + {40500000, -33000000}, {40255300, -35328600}, + {39531700, -37555500}, {38361000, -39583200}, + {36794300, -41323200}, {34900000, -42699500}, + {32761002, -43651800}, {30470700, -44138600}, + }, + { + {44696400, -50146700}, {44509500, -50123100}, + {44490800, -50120800}, {44308400, -50073900}, + {44290100, -50069200}, {44097500, -49993000}, + {43915900, -49893100}, {43748200, -49771300}, + {43597200, -49629500}, {43465100, -49469800}, + {43354100, -49294900}, {43265900, -49107400}, + {43201800, -48910300}, {43163000, -48706800}, + {43150000, -48500000}, {43163000, -48293200}, + {43201800, -48089700}, {43265900, -47892600}, + {43354100, -47705100}, {43465100, -47530200}, + {43597200, -47370500}, {43748200, -47228700}, + {43915900, -47106900}, {44097500, -47007000}, + {44290100, -46930800}, {44308400, -46926100}, + {44490800, -46879200}, {44509500, -46876900}, + {44696400, -46853300}, {44903600, -46853300}, + {45090500, -46876900}, {45109200, -46879200}, + {45291600, -46926100}, {45309900, -46930800}, + {45502500, -47007000}, {45684100, -47106900}, + {45851700, -47228700}, {46002800, -47370500}, + {46134900, -47530200}, {46245900, -47705100}, + {46334100, -47892600}, {46398200, -48089700}, + {46437000, -48293200}, {46450000, -48500000}, + {46437000, -48706800}, {46398200, -48910300}, + {46334100, -49107400}, {46245900, -49294900}, + {46134900, -49469800}, {46002800, -49629500}, + {45851700, -49771300}, {45684100, -49893100}, + {45502500, -49993000}, {45309900, -50069200}, + {45291600, -50073900}, {45109200, -50120800}, + {45090500, -50123100}, {44903600, -50146700}, + }, + { + {13696400, -50146700}, {13509500, -50123100}, + {13490800, -50120800}, {13308400, -50073900}, + {13290100, -50069200}, {13097500, -49993000}, + {12915900, -49893100}, {12748300, -49771300}, + {12597200, -49629500}, {12465100, -49469800}, + {12354100, -49294900}, {12265900, -49107400}, + {12201800, -48910300}, {12163000, -48706800}, + {12150000, -48500000}, {12163000, -48293200}, + {12201800, -48089700}, {12265900, -47892600}, + {12354100, -47705100}, {12465100, -47530200}, + {12597200, -47370500}, {12748300, -47228700}, + {12915900, -47106900}, {13097500, -47007000}, + {13290100, -46930800}, {13308400, -46926100}, + {13490800, -46879200}, {13509500, -46876900}, + {13696400, -46853300}, {13903600, -46853300}, + {14090500, -46876900}, {14109200, -46879200}, + {14291600, -46926100}, {14309900, -46930800}, + {14502500, -47007000}, {14684100, -47106900}, + {14791899, -47185194}, {14851700, -47228700}, + {14851800, -47228700}, {15002800, -47370500}, + {15134900, -47530200}, {15245900, -47705100}, + {15334100, -47892600}, {15398200, -48089700}, + {15437000, -48293200}, {15450000, -48500000}, + {15437000, -48706800}, {15398200, -48910300}, + {15334100, -49107400}, {15245900, -49294900}, + {15134900, -49469800}, {15002800, -49629500}, + {14851800, -49771300}, {14851700, -49771300}, + {14791899, -49814806}, {14684100, -49893100}, + {14502500, -49993000}, {14309900, -50069200}, + {14291600, -50073900}, {14109200, -50120800}, + {14090500, -50123100}, {13903600, -50146700}, + }, + }}, + }, + ExPolygons{ + // "extruder-cover.stl": + MyPoly{{ + {20500000, 366025}, {21732100, 2500000}, + {24500000, 2500000}, {24500000, -1500000}, + {31500000, -1500000}, {31500000, 1500000}, + {27250000, 5750000}, {-17000000, 5750000}, + {-17000000, -26799100}, {-35109600, -33390400}, + {-40700000, -33390400}, {-43650000, -38500000}, + {-40700000, -43609600}, {-34800000, -43609600}, + {-33470820, -41307370}, {-17000000, -35312500}, + {-17000000, -36500000}, {-15000000, -36500000}, + {-15000000, -44000000}, {20500000, -44000000}, + }, + { + { + {16832800, 1408760}, {16667299, 1434960}, + {16505600, 1478310}, {16349199, 1538330}, + {16200001, 1614360}, {16059500, 1705570}, + {15929400, 1810970}, {15811000, 1929390}, + {15705600, 2059540}, {15614400, 2200000}, + {15538300, 2349220}, {15478300, 2505570}, + {15435000, 2667340}, {15408800, 2832750}, + {15400000, 3000000}, {15408800, 3167240}, + {15435000, 3332660}, {15478300, 3494430}, + {15538300, 3650780}, {15614400, 3800000}, + {15705600, 3940460}, {15811000, 4070610}, + {15929400, 4189030}, {16059500, 4294430}, + {16200001, 4385640}, {16349199, 4461670}, + {16505600, 4521690}, {16667299, 4565040}, + {16832800, 4591230}, {17000000, 4600000}, + {17167200, 4591230}, {17332700, 4565040}, + {17494400, 4521690}, {17650800, 4461670}, + {17800000, 4385640}, {17940500, 4294430}, + {18070600, 4189030}, {18189000, 4070610}, + {18294400, 3940460}, {18385600, 3800000}, + {18461700, 3650780}, {18521700, 3494430}, + {18565000, 3332660}, {18591200, 3167240}, + {18600000, 3000000}, {18591200, 2832750}, + {18565000, 2667340}, {18521700, 2505570}, + {18461700, 2349220}, {18385600, 2200000}, + {18294400, 2059540}, {18189000, 1929390}, + {18070600, 1810970}, {17940500, 1705570}, + {17800000, 1614360}, {17650800, 1538330}, + {17494400, 1478310}, {17332700, 1434960}, + {17167200, 1408760}, {17000000, 1400000}, + }, + { + {-11603600, -2146740}, {-11809200, -2120770}, + {-12009900, -2069240}, {-12202500, -1992960}, + {-12384100, -1893140}, {-12551700, -1771350}, + {-12702800, -1629500}, {-12834900, -1469840}, + {-12945900, -1294890}, {-13034100, -1107400}, + {-13098200, -910337}, {-13137000, -706800}, + {-13150000, -499999}, {-13137000, -293200}, + {-13098200, -89661}, {-13034100, 107405}, + {-12945900, 294893}, {-12834900, 469845}, + {-12702800, 629502}, {-12551700, 771346}, + {-12384100, 893141}, {-12202500, 992964}, + {-12009900, 1069240}, {-11809200, 1120770}, + {-11603600, 1146740}, {-11396400, 1146740}, + {-11190800, 1120770}, {-10990100, 1069240}, + {-10797500, 992964}, {-10615900, 893141}, + {-10448200, 771346}, {-10297200, 629502}, + {-10165100, 469845}, {-10054100, 294893}, + {-9965870, 107405}, {-9901840, -89661}, + {-9863010, -293200}, {-9850000, -500000}, + {-9863010, -706800}, {-9901840, -910337}, + {-9965870, -1107400}, {-10054100, -1294890}, + {-10165100, -1469840}, {-10297200, -1629500}, + {-10448200, -1771350}, {-10615900, -1893140}, + {-10797500, -1992960}, {-10990100, -2069240}, + {-11190800, -2120770}, {-11396400, -2146740}, + }, + { + {-37917200, -40091200}, {-38244400, -40021700}, + {-38550000, -39885600}, {-38820600, -39689000}, + {-39044400, -39440500}, {-39211700, -39150800}, + {-39315000, -38832700}, {-39350000, -38500000}, + {-39315000, -38167300}, {-39211700, -37849200}, + {-39044400, -37559500}, {-38820600, -37311000}, + {-38550000, -37114400}, {-38244400, -36978300}, + {-37917200, -36908800}, {-37582800, -36908800}, + {-37255600, -36978300}, {-36950000, -37114400}, + {-36679400, -37311000}, {-36455600, -37559500}, + {-36288300, -37849200}, {-36185000, -38167300}, + {-36150000, -38500000}, {-36185000, -38832700}, + {-36288300, -39150800}, {-36455600, -39440500}, + {-36679400, -39689000}, {-36950000, -39885600}, + {-37255600, -40021700}, {-37582800, -40091200}, + }, + { + {14353700, -41892300}, {14067400, -41831500}, + {13800000, -41712400}, {13563200, -41540400}, + {13367400, -41322900}, {13221000, -41069400}, + {13130600, -40791100}, {13100000, -40500000}, + {13130600, -40208900}, {13221000, -39930600}, + {13367400, -39677100}, {13563200, -39459600}, + {13800000, -39287600}, {14067400, -39168500}, + {14353700, -39107700}, {14646300, -39107700}, + {14932600, -39168500}, {15200000, -39287600}, + {15436800, -39459600}, {15632600, -39677100}, + {15779000, -39930600}, {15869400, -40208900}, + {15900000, -40500000}, {15869400, -40791100}, + {15779000, -41069400}, {15632600, -41322900}, + {15436800, -41540400}, {15200000, -41712400}, + {14932600, -41831500}, {14646300, -41892300}, + }, + }}, + }, + ExPolygons{ + // "Einsy-base.stl": + MyPoly{{ + {85000000, 2000000}, + {87000000, 5464100}, + {91000000, 5464100}, + {93000000, 2000000}, + {91845296, 0}, + {118500000, 0}, + {118500000, 79000000}, + {105500000, 92000000}, + {0, 92000000}, + {0, 41000000}, + {-5000000, 41000000}, + {-9000000, 38000000}, + {-9000000, 18000000}, + {-5000000, 15000000}, + {0, 15000000}, + {0, 0}, + {86154704, 0}, + }, + { + { + {58301400, 86110400}, {57912900, 86193000}, + {57550000, 86354600}, {57228700, 86588000}, + {56962900, 86883200}, {56764300, 87227200}, + {56641500, 87605000}, {56600000, 88000000}, + {56641500, 88395000}, {56764300, 88772800}, + {56962900, 89116800}, {57228700, 89412000}, + {57550000, 89645400}, {57912900, 89807000}, + {58301400, 89889600}, {58698600, 89889600}, + {59087100, 89807000}, {59450000, 89645400}, + {59771300, 89412000}, {60037100, 89116800}, + {60235700, 88772800}, {60358500, 88395000}, + {60400000, 88000000}, {60358500, 87605000}, + {60235700, 87227200}, {60037100, 86883200}, + {59771300, 86588000}, {59450000, 86354600}, + {59087100, 86193000}, {58698600, 86110400}, + }, + { + {78916400, 80204400}, {78752800, 80239200}, + {78600000, 80307200}, {78464696, 80405504}, + {78352800, 80529800}, {78269200, 80674600}, + {78217496, 80833704}, {78200000, 81000000}, + {78217496, 81166296}, {78269200, 81325400}, + {78352800, 81470200}, {78464696, 81594496}, + {78600000, 81692800}, {78752800, 81760800}, + {78916400, 81795600}, {79083600, 81795600}, + {79247200, 81760800}, {79400000, 81692800}, + {79535304, 81594496}, {79647200, 81470200}, + {79730800, 81325400}, {79782504, 81166296}, + {79800000, 81000000}, {79782504, 80833704}, + {79730800, 80674600}, {79647200, 80529800}, + {79535304, 80405504}, {79400000, 80307200}, + {79247200, 80239200}, {79083600, 80204400}, + }, + { + {20916400, 80204400}, {20752800, 80239200}, + {20600000, 80307200}, {20464700, 80405504}, + {20352800, 80529800}, {20269200, 80674600}, + {20217500, 80833704}, {20200000, 81000000}, + {20217500, 81166296}, {20269200, 81325400}, + {20352800, 81470200}, {20464700, 81594496}, + {20600000, 81692800}, {20752800, 81760800}, + {20916400, 81795600}, {21083600, 81795600}, + {21247200, 81760800}, {21400000, 81692800}, + {21535300, 81594496}, {21647200, 81470200}, + {21730800, 81325400}, {21782500, 81166296}, + {21800000, 81000000}, {21782500, 80833704}, + {21730800, 80674600}, {21647200, 80529800}, + {21535300, 80405504}, {21400000, 80307200}, + {21247200, 80239200}, {21083600, 80204400}, + }, + { + {81000000, 60500000}, + {81000000, 78500000}, + {84650000, 78500000}, + {84650000, 60500000}, + }, + { + {70000000, 60500000}, + {70000000, 78500000}, + {73650000, 78500000}, + {73650000, 60500000}, + }, + { + {75500000, 60500000}, + {75500000, 78500000}, + {79150000, 78500000}, + {79150000, 60500000}, + }, + { + {26000000, 60500000}, + {26000000, 78500000}, + {29650000, 78500000}, + {29650000, 60500000}, + }, + { + {86500000, 60500000}, + {86500000, 78500000}, + {90150000, 78500000}, + {90150000, 60500000}, + }, + { + {48000000, 60500000}, + {48000000, 78500000}, + {51650000, 78500000}, + {51650000, 60500000}, + }, + { + {64500000, 60500000}, + {64500000, 78500000}, + {68150000, 78500000}, + {68150000, 60500000}, + }, + { + {59000000, 60500000}, + {59000000, 78500000}, + {62650000, 78500000}, + {62650000, 60500000}, + }, + { + {20500000, 60500000}, + {20500000, 78500000}, + {24150000, 78500000}, + {24150000, 60500000}, + }, + { + {92000000, 60500000}, + {92000000, 78500000}, + {95650000, 78500000}, + {95650000, 60500000}, + }, + { + {42500000, 60500000}, + {42500000, 78500000}, + {46150000, 78500000}, + {46150000, 60500000}, + }, + { + {31500000, 60500000}, + {31500000, 78500000}, + {35150000, 78500000}, + {35150000, 60500000}, + }, + { + {37000000, 60500000}, + {37000000, 78500000}, + {40650000, 78500000}, + {40650000, 60500000}, + }, + { + {53500000, 60500000}, + {53500000, 78500000}, + {57150000, 78500000}, + {57150000, 60500000}, + }, + { + {7301400, 73110400}, {6912870, 73193000}, + {6550000, 73354600}, {6228650, 73588000}, + {5962870, 73883200}, {5900000, 73992104}, + {5764260, 74227200}, {5641520, 74605000}, + {5600000, 75000000}, {5641520, 75395000}, + {5764260, 75772800}, {5900000, 76007896}, + {5962870, 76116800}, {6228650, 76412000}, + {6550000, 76645400}, {6912870, 76807000}, + {7301400, 76889600}, {7698600, 76889600}, + {8087129, 76807000}, {8450000, 76645400}, + {8771350, 76412000}, {9037130, 76116800}, + {9100000, 76007896}, {9235740, 75772800}, + {9358480, 75395000}, {9400000, 75000000}, + {9358480, 74605000}, {9235740, 74227200}, + {9100000, 73992104}, {9037130, 73883200}, + {8771350, 73588000}, {8450000, 73354600}, + {8087129, 73193000}, {7698600, 73110400}, + }, + { + {102301000, 73110400}, {101913000, 73193000}, + {101550000, 73354600}, {101229000, 73588000}, + {100963000, 73883200}, {100764000, 74227200}, + {100642000, 74605000}, {100600000, 75000000}, + {100642000, 75395000}, {100764000, 75772800}, + {100963000, 76116800}, {101229000, 76412000}, + {101550000, 76645400}, {101913000, 76807000}, + {102301000, 76889600}, {102699000, 76889600}, + {103087000, 76807000}, {103450000, 76645400}, + {103771000, 76412000}, {104037000, 76116800}, + {104236000, 75772800}, {104358000, 75395000}, + {104400000, 75000000}, {104358000, 74605000}, + {104236000, 74227200}, {104037000, 73883200}, + {103771000, 73588000}, {103450000, 73354600}, + {103087000, 73193000}, {102699000, 73110400}, + }, + { + {37000000, 35500000}, + {37000000, 53500000}, + {40650000, 53500000}, + {40650000, 35500000}, + }, + { + {53500000, 35500000}, + {53500000, 53500000}, + {57150000, 53500000}, + {57150000, 35500000}, + }, + { + {75500000, 35500000}, + {75500000, 53500000}, + {79150000, 53500000}, + {79150000, 35500000}, + }, + { + {31500000, 35500000}, + {31500000, 53500000}, + {35150000, 53500000}, + {35150000, 35500000}, + }, + { + {92000000, 35500000}, + {92000000, 53500000}, + {95650000, 53500000}, + {95650000, 35500000}, + }, + { + {81000000, 35500000}, + {81000000, 53500000}, + {84650000, 53500000}, + {84650000, 35500000}, + }, + { + {86500000, 35500000}, + {86500000, 53500000}, + {90150000, 53500000}, + {90150000, 35500000}, + }, + { + {48000000, 35500000}, + {48000000, 53500000}, + {51650000, 53500000}, + {51650000, 35500000}, + }, + { + {42500000, 35500000}, + {42500000, 53500000}, + {46150000, 53500000}, + {46150000, 35500000}, + }, + { + {70000000, 35500000}, + {70000000, 53500000}, + {73650000, 53500000}, + {73650000, 35500000}, + }, + { + {20500000, 35500000}, + {20500000, 53500000}, + {24150000, 53500000}, + {24150000, 35500000}, + }, + { + {59000000, 35500000}, + {59000000, 53500000}, + {62650000, 53500000}, + {62650000, 35500000}, + }, + { + {64500000, 35500000}, + {64500000, 53500000}, + {68150000, 53500000}, + {68150000, 35500000}, + }, + { + {26000000, 35500000}, + {26000000, 53500000}, + {29650000, 53500000}, + {29650000, 35500000}, + }, + { + {16290899, 8010959}, {15882000, 8097890}, + {15500000, 8267950}, {15161700, 8513710}, + {14882000, 8824430}, {14672900, 9186530}, + {14543700, 9584180}, {14500000, 10000000}, + {14500000, 34000000}, {14543700, 34415800}, + {14672900, 34813500}, {14882000, 35175600}, + {15161700, 35486300}, {15500000, 35732000}, + {15882000, 35902100}, {16290899, 35989000}, + {16709101, 35989000}, {17118000, 35902100}, + {17500000, 35732000}, {17838300, 35486300}, + {18118000, 35175600}, {18327100, 34813500}, + {18456300, 34415800}, {18500000, 34000000}, + {18500000, 10000000}, {18456300, 9584180}, + {18327100, 9186530}, {18118000, 8824430}, + {17838300, 8513710}, {17500000, 8267950}, + {17118000, 8097890}, {16709101, 8010959}, + }, + { + {59000000, 10500000}, + {59000000, 28500000}, + {62650000, 28500000}, + {62650000, 10500000}, + }, + { + {81000000, 10500000}, + {81000000, 28500000}, + {84650000, 28500000}, + {84650000, 10500000}, + }, + { + {75500000, 10500000}, + {75500000, 28500000}, + {79150000, 28500000}, + {79150000, 10500000}, + }, + { + {70000000, 10500000}, + {70000000, 28500000}, + {73650000, 28500000}, + {73650000, 10500000}, + }, + { + {20500000, 10500000}, + {20500000, 28500000}, + {24150000, 28500000}, + {24150000, 10500000}, + }, + { + {92000000, 10500000}, + {92000000, 28500000}, + {95650000, 28500000}, + {95650000, 10500000}, + }, + { + {26000000, 10500000}, + {26000000, 28500000}, + {29650000, 28500000}, + {29650000, 10500000}, + }, + { + {53500000, 10500000}, + {53500000, 28500000}, + {57150000, 28500000}, + {57150000, 10500000}, + }, + { + {48000000, 10500000}, + {48000000, 28500000}, + {51650000, 28500000}, + {51650000, 10500000}, + }, + { + {42500000, 10500000}, + {42500000, 28500000}, + {46150000, 28500000}, + {46150000, 10500000}, + }, + { + {37000000, 10500000}, + {37000000, 28500000}, + {40650000, 28500000}, + {40650000, 10500000}, + }, + { + {31500000, 10500000}, + {31500000, 28500000}, + {35150000, 28500000}, + {35150000, 10500000}, + }, + { + {64500000, 10500000}, + {64500000, 28500000}, + {68150000, 28500000}, + {68150000, 10500000}, + }, + { + {86500000, 10500000}, + {86500000, 28500000}, + {90150000, 28500000}, + {90150000, 10500000}, + }, + { + {102301000, 12110400}, {101913000, 12193000}, + {101550000, 12354600}, {101229000, 12588000}, + {100963000, 12883200}, {100764000, 13227200}, + {100642000, 13605000}, {100600000, 14000000}, + {100642000, 14395000}, {100764000, 14772800}, + {100963000, 15116800}, {101229000, 15412000}, + {101550000, 15645400}, {101913000, 15807000}, + {102301000, 15889600}, {102699000, 15889600}, + {103087000, 15807000}, {103450000, 15645400}, + {103771000, 15412000}, {104037000, 15116800}, + {104236000, 14772800}, {104358000, 14395000}, + {104400000, 14000000}, {104358000, 13605000}, + {104236000, 13227200}, {104037000, 12883200}, + {103771000, 12588000}, {103450000, 12354600}, + {103087000, 12193000}, {102699000, 12110400}, + }, + { + {7301400, 12110400}, {6912870, 12193000}, + {6550000, 12354600}, {6228650, 12588000}, + {5962870, 12883200}, {5900000, 12992100}, + {5764260, 13227200}, {5641520, 13605000}, + {5600000, 14000000}, {5641520, 14395000}, + {5764260, 14772800}, {5900000, 15007900}, + {5962870, 15116800}, {6228650, 15412000}, + {6550000, 15645400}, {6912870, 15807000}, + {7301400, 15889600}, {7698600, 15889600}, + {8087129, 15807000}, {8450000, 15645400}, + {8771350, 15412000}, {9037130, 15116800}, + {9100000, 15007900}, {9235740, 14772800}, + {9358480, 14395000}, {9400000, 14000000}, + {9358480, 13605000}, {9235740, 13227200}, + {9100000, 12992100}, {9037130, 12883200}, + {8771350, 12588000}, {8450000, 12354600}, + {8087129, 12193000}, {7698600, 12110400}, + }, + }}, + }, + ExPolygons{ + // "lcd-supports.stl": + MyPoly{{ + {4192390, 4192390}, {4192390, 5707110}, + {2474870, 7424620}, {1626350, 6576090}, + {3040560, 5161880}, {1767770, 3889090}, + {-2474870, 8131730}, {-5303300, 5303300}, + {-36769600, 36769600}, {-33941100, 39598000}, + {-38183750, 43840650}, {-36911000, 45113400}, + {-35496800, 43699200}, {-34648200, 44547700}, + {-36769600, 46669000}, {-38183800, 46669000}, + {-46852800, 38000000}, {-61500000, 38000000}, + {-61500000, 12000000}, {-50000000, 12000000}, + {-50000000, 11984300}, {-37204500, -811183}, + {-811183, -811183}, + }, + { + { + {-36000000, 8000000}, + {-51500000, 23500000}, + {-37357900, 23500000}, + {-21857900, 8000000}, + }, + }}, + MyPoly{{ + {-13147200, -40000000}, {1500000, -40000000}, + {1500000, -14000000}, {-10000000, -14000000}, + {-10000000, -13984300}, {-22795500, -1188820}, + {-59188800, -1188820}, {-64192400, -6192390}, + {-64192400, -7707110}, {-62474900, -9424620}, + {-61626300, -8576090}, {-63040571, -7161851}, + {-61767800, -5889090}, {-57525100, -10131700}, + {-54696700, -7303300}, {-23230400, -38769600}, + {-26058900, -41598000}, {-21816250, -45840650}, + {-23089000, -47113400}, {-24503200, -45699200}, + {-25351800, -46547700}, {-23230400, -48669000}, + {-21816200, -48669000}, + }, + { + { + {-22642100, -25500000}, + {-38142100, -10000000}, + {-24000000, -10000000}, + {-9357800, -24642200}, + {-9288210, -24711800}, + {-8500000, -25500000}, + }, + }}, + }, +}; diff --git a/tests/data/prusaparts.hpp b/tests/data/prusaparts.hpp new file mode 100644 index 0000000000..abe598b137 --- /dev/null +++ b/tests/data/prusaparts.hpp @@ -0,0 +1,14 @@ +#ifndef PRUSAPARTS_H +#define PRUSAPARTS_H + +#include +#include + +using TestData = std::vector; +using TestDataEx = std::vector; + +extern const TestData PRUSA_PART_POLYGONS; +extern const TestData PRUSA_STEGOSAUR_POLYGONS; +extern const TestDataEx PRUSA_PART_POLYGONS_EX; + +#endif // PRUSAPARTS_H diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 6d87560579..8f9367ce51 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -228,7 +228,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r object->add_volume(std::move(t)); object->add_instance(); } - arrange_objects(model, InfiniteBed{}, ArrangeParams{ scaled(min_object_distance(config))}); + arrange_objects(model, arr2::InfiniteBed{}, arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); model.center_instances_around_point({100, 100}); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index 23c4a2d9a0..d59a655663 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -42,7 +42,7 @@ SCENARIO("Model construction", "[Model]") { } } model_object->add_instance(); - arrange_objects(model, InfiniteBed{scaled(Vec2d(100, 100))}, ArrangeParams{scaled(min_object_distance(config))}); + arrange_objects(model, arr2::InfiniteBed{scaled(Vec2d(100, 100))}, arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); model_object->ensure_on_bed(); print.auto_assign_extruders(model_object); THEN("Print works?") { diff --git a/tests/libnest2d/CMakeLists.txt b/tests/libnest2d/CMakeLists.txt deleted file mode 100644 index 8dabc688d3..0000000000 --- a/tests/libnest2d/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp printer_parts.cpp printer_parts.hpp) - -# mold linker for successful linking needs also to link TBB library and link it before libslic3r. -target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb TBB::tbbmalloc libnest2d ) -set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") - -# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") -set(_catch_args "exclude:[NotWorking]") -list(APPEND _catch_args "${CATCH_EXTRA_ARGS}") -add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args}) diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp deleted file mode 100644 index f6df83b779..0000000000 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ /dev/null @@ -1,1233 +0,0 @@ -#include - -#include -#include - -#include -#include "printer_parts.hpp" -//#include -#include "../tools/svgtools.hpp" -#include - -#if defined(_MSC_VER) && defined(__clang__) -#define BOOST_NO_CXX17_HDR_STRING_VIEW -#endif - -#include "boost/multiprecision/integer.hpp" -#include "boost/rational.hpp" - -//#include "../tools/libnfpglue.hpp" -//#include "../tools/nfp_svgnest_glue.hpp" - -namespace libnest2d { -#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) -using LargeInt = __int128; -#else -using LargeInt = boost::multiprecision::int128_t; -template<> struct _NumTag { using Type = ScalarTag; }; -#endif -template struct _NumTag> { using Type = RationalTag; }; - -using RectangleItem = libnest2d::Rectangle; - -namespace nfp { - -template -struct NfpImpl -{ - NfpResult operator()(const S &sh, const S &other) - { - return nfpConvexOnly>(sh, other); - } -}; - -} -} - -namespace { -using namespace libnest2d; - -template -void exportSVG(const char *loc, It from, It to) { - - static const char* svg_header = - R"raw( - - -)raw"; - - // for(auto r : result) { - std::fstream out(loc, std::fstream::out); - if(out.is_open()) { - out << svg_header; - // Item rbin( RectangleItem(bin.width(), bin.height()) ); - // for(unsigned j = 0; j < rbin.vertexCount(); j++) { - // auto v = rbin.vertex(j); - // setY(v, -getY(v)/SCALE + 500 ); - // setX(v, getX(v)/SCALE); - // rbin.setVertex(j, v); - // } - // out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(auto it = from; it != to; ++it) { - const Item &itm = *it; - Item tsh(itm.transformedShape()); - for(unsigned j = 0; j < tsh.vertexCount(); j++) { - auto v = tsh.vertex(j); - setY(v, -getY(v)/SCALE + 500); - setX(v, getX(v)/SCALE); - tsh.setVertex(j, v); - } - out << shapelike::serialize(tsh.rawShape()) << std::endl; - } - out << "\n" << std::endl; - } - out.close(); - - // i++; - // } -} - -template -void exportSVG(std::vector>& result, int idx = 0) { - exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(), - result.begin(), result.end()); -} -} - -static std::vector& prusaParts() { - using namespace libnest2d; - - static std::vector ret; - - if(ret.empty()) { - ret.reserve(PRINTER_PART_POLYGONS.size()); - for(auto& inp : PRINTER_PART_POLYGONS) { - auto inp_cpy = inp; - - if (ClosureTypeV == Closure::OPEN) - inp_cpy.points.pop_back(); - - if constexpr (!libnest2d::is_clockwise()) - std::reverse(inp_cpy.begin(), inp_cpy.end()); - - ret.emplace_back(inp_cpy); - } - } - - return ret; -} - -TEST_CASE("Angles", "[Geometry]") -{ - - using namespace libnest2d; - - Degrees deg(180); - Radians rad(deg); - Degrees deg2(rad); - - REQUIRE(Approx(rad) == Pi); - REQUIRE(Approx(deg) == 180); - REQUIRE(Approx(deg2) == 180); - REQUIRE(Approx(rad) == Radians(deg)); - REQUIRE(Approx(Degrees(rad)) == deg); - - REQUIRE(rad == deg); - - Segment seg = {{0, 0}, {12, -10}}; - - REQUIRE(Degrees(seg.angleToXaxis()) > 270); - REQUIRE(Degrees(seg.angleToXaxis()) < 360); - - seg = {{0, 0}, {12, 10}}; - - REQUIRE(Degrees(seg.angleToXaxis()) > 0); - REQUIRE(Degrees(seg.angleToXaxis()) < 90); - - seg = {{0, 0}, {-12, 10}}; - - REQUIRE(Degrees(seg.angleToXaxis()) > 90); - REQUIRE(Degrees(seg.angleToXaxis()) < 180); - - seg = {{0, 0}, {-12, -10}}; - - REQUIRE(Degrees(seg.angleToXaxis()) > 180); - REQUIRE(Degrees(seg.angleToXaxis()) < 270); - - seg = {{0, 0}, {1, 0}}; - - REQUIRE(Degrees(seg.angleToXaxis()) == Approx(0.)); - - seg = {{0, 0}, {0, 1}}; - - REQUIRE(Degrees(seg.angleToXaxis()) == Approx(90.)); - - seg = {{0, 0}, {-1, 0}}; - - REQUIRE(Degrees(seg.angleToXaxis()) == Approx(180.)); - - seg = {{0, 0}, {0, -1}}; - - REQUIRE(Degrees(seg.angleToXaxis()) == Approx(270.)); -} - -// Simple TEST_CASE, does not use gmock -TEST_CASE("ItemCreationAndDestruction", "[Nesting]") -{ - using namespace libnest2d; - - Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; - - REQUIRE(sh.vertexCount() == 4u); - - Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} }); - - REQUIRE(sh2.vertexCount() == 4u); - - // copy - Item sh3 = sh2; - - REQUIRE(sh3.vertexCount() == 4u); - - sh2 = {}; - - REQUIRE(sh2.vertexCount() == 0u); - REQUIRE(sh3.vertexCount() == 4u); -} - -TEST_CASE("boundingCircle", "[Geometry]") { - using namespace libnest2d; - using placers::boundingCircle; - - PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; - Circle c = boundingCircle(p); - - REQUIRE(getX(c.center()) == 0); - REQUIRE(getY(c.center()) == 0); - REQUIRE(c.radius() == Approx(10)); - - shapelike::translate(p, PointImpl{10, 10}); - c = boundingCircle(p); - - REQUIRE(getX(c.center()) == 10); - REQUIRE(getY(c.center()) == 10); - REQUIRE(c.radius() == Approx(10)); - - auto parts = prusaParts(); - - int i = 0; - for(auto& part : parts) { - c = boundingCircle(part.transformedShape()); - if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; - - else for(auto v : shapelike::contour(part.transformedShape()) ) { - auto d = pointlike::distance(v, c.center()); - if(d > c.radius() ) { - auto e = std::abs( 1.0 - d/c.radius()); - REQUIRE(e <= 1e-3); - } - } - i++; - } - -} - -TEST_CASE("Distance", "[Geometry]") { - using namespace libnest2d; - - Point p1 = {0, 0}; - - Point p2 = {10, 0}; - Point p3 = {10, 10}; - - REQUIRE(pointlike::distance(p1, p2) == Approx(10)); - REQUIRE(pointlike::distance(p1, p3) == Approx(sqrt(200))); - - Segment seg(p1, p3); - - // REQUIRE(pointlike::distance(p2, seg) == Approx(7.0710678118654755)); - - auto result = pointlike::horizontalDistance(p2, seg); - - auto check = [](TCompute val, TCompute expected) { - if(std::is_floating_point>::value) - REQUIRE(static_cast(val) == - Approx(static_cast(expected))); - else - REQUIRE(val == expected); - }; - - REQUIRE(result.second); - check(result.first, 10); - - result = pointlike::verticalDistance(p2, seg); - REQUIRE(result.second); - check(result.first, -10); - - result = pointlike::verticalDistance(Point{10, 20}, seg); - REQUIRE(result.second); - check(result.first, 10); - - - Point p4 = {80, 0}; - Segment seg2 = { {0, 0}, {0, 40} }; - - result = pointlike::horizontalDistance(p4, seg2); - - REQUIRE(result.second); - check(result.first, 80); - - result = pointlike::verticalDistance(p4, seg2); - // Point should not be related to the segment - REQUIRE_FALSE(result.second); - -} - -TEST_CASE("Area", "[Geometry]") { - using namespace libnest2d; - - RectangleItem rect(10, 10); - - REQUIRE(rect.area() == Approx(100)); - - RectangleItem rect2 = {100, 100}; - - REQUIRE(rect2.area() == Approx(10000)); - - Item item = { - {61, 97}, - {70, 151}, - {176, 151}, - {189, 138}, - {189, 59}, - {70, 59}, - {61, 77}, - {61, 97} - }; - - REQUIRE(std::abs(shapelike::area(item.transformedShape())) > 0 ); -} - -TEST_CASE("IsPointInsidePolygon", "[Geometry]") { - using namespace libnest2d; - - RectangleItem rect(10, 10); - - Point p = {1, 1}; - - REQUIRE(rect.isInside(p)); - - p = {11, 11}; - - REQUIRE_FALSE(rect.isInside(p)); - - - p = {11, 12}; - - REQUIRE_FALSE(rect.isInside(p)); - - - p = {3, 3}; - - REQUIRE(rect.isInside(p)); - -} - -//TEST_CASE(GeometryAlgorithms, Intersections) { -// using namespace binpack2d; - -// RectangleItem rect(70, 30); - -// rect.translate({80, 60}); - -// RectangleItem rect2(80, 60); -// rect2.translate({80, 0}); - -//// REQUIRE_FALSE(Item::intersects(rect, rect2)); - -// Segment s1({0, 0}, {10, 10}); -// Segment s2({1, 1}, {11, 11}); -// REQUIRE_FALSE(ShapeLike::intersects(s1, s1)); -// REQUIRE_FALSE(ShapeLike::intersects(s1, s2)); -//} - -TEST_CASE("LeftAndDownPolygon", "[Geometry]") -{ - using namespace libnest2d; - - Box bin(100, 100); - BottomLeftPlacer placer(bin); - - PathImpl pitem = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, - {42, 20}, {35, 35}, {35, 55}, {40, 75}}; - - PathImpl pleftControl = {{40, 75}, {35, 55}, {35, 35}, - {42, 20}, {0, 20}, {0, 75}}; - - PathImpl pdownControl = {{88, 60}, {88, 0}, {35, 0}, {35, 35}, - {42, 20}, {80, 20}, {60, 30}, {65, 50}}; - - if constexpr (!is_clockwise()) { - std::reverse(sl::begin(pitem), sl::end(pitem)); - std::reverse(sl::begin(pleftControl), sl::end(pleftControl)); - std::reverse(sl::begin(pdownControl), sl::end(pdownControl)); - } - - if constexpr (ClosureTypeV == Closure::CLOSED) { - sl::addVertex(pitem, sl::front(pitem)); - sl::addVertex(pleftControl, sl::front(pleftControl)); - sl::addVertex(pdownControl, sl::front(pdownControl)); - } - - Item item{pitem}, leftControl{pleftControl}, downControl{pdownControl}; - Item leftp(placer.leftPoly(item)); - - auto valid = sl::isValid(leftp.rawShape()); - - std::vector> to_export{ leftp, leftControl }; - exportSVG<1>("leftp.svg", to_export.begin(), to_export.end()); - - REQUIRE(valid.first); - REQUIRE(leftp.vertexCount() == leftControl.vertexCount()); - - for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { - REQUIRE(getX(leftp.vertex(i)) == getX(leftControl.vertex(i))); - REQUIRE(getY(leftp.vertex(i)) == getY(leftControl.vertex(i))); - } - - Item downp(placer.downPoly(item)); - - REQUIRE(shapelike::isValid(downp.rawShape()).first); - REQUIRE(downp.vertexCount() == downControl.vertexCount()); - - for(unsigned long i = 0; i < downControl.vertexCount(); i++) { - REQUIRE(getX(downp.vertex(i)) == getX(downControl.vertex(i))); - REQUIRE(getY(downp.vertex(i)) == getY(downControl.vertex(i))); - } -} - -TEST_CASE("ArrangeRectanglesTight", "[Nesting][NotWorking]") -{ - using namespace libnest2d; - - std::vector rects = { - {80, 80}, - {60, 90}, - {70, 30}, - {80, 60}, - {60, 60}, - {60, 40}, - {40, 40}, - {10, 10}, - {10, 10}, - {10, 10}, - {10, 10}, - {10, 10}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {20, 20} }; - - Box bin(210, 250, {105, 125}); - - REQUIRE(bin.width() == 210); - REQUIRE(bin.height() == 250); - REQUIRE(getX(bin.center()) == 105); - REQUIRE(getY(bin.center()) == 125); - - _Nester arrange(bin); - - arrange.execute(rects.begin(), rects.end()); - - auto max_group = std::max_element(rects.begin(), rects.end(), - [](const Item &i1, const Item &i2) { - return i1.binId() < i2.binId(); - }); - - int groups = max_group == rects.end() ? 0 : max_group->binId() + 1; - - REQUIRE(groups == 1u); - REQUIRE( - std::all_of(rects.begin(), rects.end(), [](const RectangleItem &itm) { - return itm.binId() != BIN_ID_UNSET; - })); - - // check for no intersections, no containment: - - // exportSVG<1>("arrangeRectanglesTight.svg", rects.begin(), rects.end()); - - bool valid = true; - for(Item& r1 : rects) { - for(Item& r2 : rects) { - if(&r1 != &r2 ) { - valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); - REQUIRE(valid); - valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); - REQUIRE(valid); - } - } - } -} - -TEST_CASE("ArrangeRectanglesLoose", "[Nesting]") -{ - using namespace libnest2d; - - // std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; - std::vector rects = { - {80, 80}, - {60, 90}, - {70, 30}, - {80, 60}, - {60, 60}, - {60, 40}, - {40, 40}, - {10, 10}, - {10, 10}, - {10, 10}, - {10, 10}, - {10, 10}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {5, 5}, - {20, 20} }; - - Box bin(210, 250, {105, 125}); - - REQUIRE(bin.width() == 210); - REQUIRE(bin.height() == 250); - REQUIRE(getX(bin.center()) == 105); - REQUIRE(getY(bin.center()) == 125); - - Coord min_obj_distance = 5; - - _Nester arrange(bin, min_obj_distance); - - arrange.execute(rects.begin(), rects.end()); - - auto max_group = std::max_element(rects.begin(), rects.end(), - [](const Item &i1, const Item &i2) { - return i1.binId() < i2.binId(); - }); - - auto groups = size_t(max_group == rects.end() ? 0 : max_group->binId() + 1); - - REQUIRE(groups == 1u); - REQUIRE( - std::all_of(rects.begin(), rects.end(), [](const RectangleItem &itm) { - return itm.binId() != BIN_ID_UNSET; - })); - - // check for no intersections, no containment: - bool valid = true; - for(Item& r1 : rects) { - for(Item& r2 : rects) { - if(&r1 != &r2 ) { - valid = !Item::intersects(r1, r2); - valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); - REQUIRE(valid); - } - } - } - -} - -TEST_CASE("BottomLeftStressTest", "[Geometry][NotWorking]") { - using namespace libnest2d; - - const Coord SCALE = 1000000; - auto& input = prusaParts(); - - Box bin(210*SCALE, 250*SCALE); - BottomLeftPlacer placer(bin); - - auto it = input.begin(); - auto next = it; - int i = 0; - while(it != input.end() && ++next != input.end()) { - placer.pack(*it); - placer.pack(*next); - - auto result = placer.getItems(); - bool valid = true; - - if(result.size() == 2) { - Item& r1 = result[0]; - Item& r2 = result[1]; - valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); - valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); - if(!valid) { - std::cout << "error index: " << i << std::endl; - exportSVG(result, i); - } - REQUIRE(valid); - } else { - std::cout << "something went terribly wrong!" << std::endl; - FAIL(); - } - - placer.clearItems(); - it++; - i++; - } -} - -TEST_CASE("convexHull", "[Geometry]") { - using namespace libnest2d; - - PathImpl poly = PRINTER_PART_POLYGONS[0]; - - auto chull = sl::convexHull(poly); - - REQUIRE(chull.size() == poly.size()); -} - -TEST_CASE("PrusaPartsShouldFitIntoTwoBins", "[Nesting]") { - - // Get the input items and define the bin. - std::vector input = prusaParts(); - auto bin = Box(250000000, 210000000); - - // Do the nesting. Check in each step if the remaining items are less than - // in the previous step. (Some algorithms can place more items in one step) - size_t pcount = input.size(); - - size_t bins = libnest2d::nest(input, bin, 0, {}, - ProgressFunction{[&pcount](unsigned cnt) { - REQUIRE(cnt < pcount); - pcount = cnt; - }}); - - // For prusa parts, 2 bins should be enough... - REQUIRE(bins > 0u); - REQUIRE(bins <= 2u); - - // All parts should be processed by the algorithm - REQUIRE( - std::all_of(input.begin(), input.end(), [](const Item &itm) { - return itm.binId() != BIN_ID_UNSET; - })); - - // Gather the items into piles of arranged polygons... - using Pile = TMultiShape; - std::vector piles(bins); - - for (auto &itm : input) - piles[size_t(itm.binId())].emplace_back(itm.transformedShape()); - - // Now check all the piles, the bounding box of each pile should be inside - // the defined bin. - for (auto &pile : piles) { - auto bb = sl::boundingBox(pile); - REQUIRE(sl::isInside(bb, bin)); - } - - // Check the area of merged pile vs the sum of area of all the parts - // They should match, otherwise there is an overlap which should not happen. - for (auto &pile : piles) { - double area_sum = 0.; - - for (auto &obj : pile) - area_sum += sl::area(obj); - - auto pile_m = nfp::merge(pile); - double area_merge = sl::area(pile_m); - - REQUIRE(area_sum == Approx(area_merge)); - } -} - -TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") { - auto bin = Box(250000000, 210000000); // dummy bin - - std::vector items; - items.emplace_back(Item{}); // Emplace empty item - items.emplace_back(Item{ {0, 200} }); // Emplace zero area item - - size_t bins = libnest2d::nest(items, bin); - - REQUIRE(bins == 0u); - for (auto &itm : items) REQUIRE(itm.binId() == BIN_ID_UNSET); -} - -TEST_CASE("LargeItemShouldBeUntouched", "[Nesting]") { - auto bin = Box(250000000, 210000000); // dummy bin - - std::vector items; - items.emplace_back(RectangleItem{250000001, 210000001}); // Emplace large item - - size_t bins = libnest2d::nest(items, bin); - - REQUIRE(bins == 0u); - REQUIRE(items.front().binId() == BIN_ID_UNSET); -} - -TEST_CASE("Items can be preloaded", "[Nesting]") { - auto bin = Box({0, 0}, {250000000, 210000000}); // dummy bin - - std::vector items; - items.reserve(2); - - NestConfig<> cfg; - cfg.placer_config.alignment = NestConfig<>::Placement::Alignment::DONT_ALIGN; - - items.emplace_back(RectangleItem{10000000, 10000000}); - Item &fixed_rect = items.back(); - fixed_rect.translate(bin.center()); - - items.emplace_back(RectangleItem{20000000, 20000000}); - Item &movable_rect = items.back(); - movable_rect.translate(bin.center()); - - SECTION("Preloaded Item should be untouched") { - fixed_rect.markAsFixedInBin(0); - - size_t bins = libnest2d::nest(items, bin, 0, cfg); - - REQUIRE(bins == 1); - - REQUIRE(fixed_rect.binId() == 0); - REQUIRE(getX(fixed_rect.translation()) == getX(bin.center())); - REQUIRE(getY(fixed_rect.translation()) == getY(bin.center())); - - REQUIRE(movable_rect.binId() == 0); - REQUIRE(getX(movable_rect.translation()) != getX(bin.center())); - REQUIRE(getY(movable_rect.translation()) != getY(bin.center())); - } - - SECTION("Preloaded Item should not affect free bins") { - fixed_rect.markAsFixedInBin(1); - - size_t bins = libnest2d::nest(items, bin, 0, cfg); - - REQUIRE(bins == 2); - - REQUIRE(fixed_rect.binId() == 1); - REQUIRE(getX(fixed_rect.translation()) == getX(bin.center())); - REQUIRE(getY(fixed_rect.translation()) == getY(bin.center())); - - REQUIRE(movable_rect.binId() == 0); - - auto bb = movable_rect.boundingBox(); - REQUIRE(getX(bb.center()) == getX(bin.center())); - REQUIRE(getY(bb.center()) == getY(bin.center())); - } -} - -namespace { - -struct ItemPair { - Item orbiter; - Item stationary; -}; - -std::vector nfp_testdata = { - { - { - {80, 50}, - {100, 70}, - {120, 50} - }, - { - {10, 10}, - {10, 40}, - {40, 40}, - {40, 10} - } - }, - { - { - {80, 50}, - {60, 70}, - {80, 90}, - {120, 90}, - {140, 70}, - {120, 50} - }, - { - {10, 10}, - {10, 40}, - {40, 40}, - {40, 10} - } - }, - { - { - {40, 10}, - {30, 10}, - {20, 20}, - {20, 30}, - {30, 40}, - {40, 40}, - {50, 30}, - {50, 20} - }, - { - {80, 0}, - {80, 30}, - {110, 30}, - {110, 0} - } - }, - { - { - {117, 107}, - {118, 109}, - {120, 112}, - {122, 113}, - {128, 113}, - {130, 112}, - {132, 109}, - {133, 107}, - {133, 103}, - {132, 101}, - {130, 98}, - {128, 97}, - {122, 97}, - {120, 98}, - {118, 101}, - {117, 103} - }, - { - {102, 116}, - {111, 126}, - {114, 126}, - {144, 106}, - {148, 100}, - {148, 85}, - {147, 84}, - {102, 84} - } - }, - { - { - {99, 122}, - {108, 140}, - {110, 142}, - {139, 142}, - {151, 122}, - {151, 102}, - {142, 70}, - {139, 68}, - {111, 68}, - {108, 70}, - {99, 102} - }, - { - {107, 124}, - {128, 125}, - {133, 125}, - {136, 124}, - {140, 121}, - {142, 119}, - {143, 116}, - {143, 109}, - {141, 93}, - {139, 89}, - {136, 86}, - {134, 85}, - {108, 85}, - {107, 86} - } - }, - { - { - {91, 100}, - {94, 144}, - {117, 153}, - {118, 153}, - {159, 112}, - {159, 110}, - {156, 66}, - {133, 57}, - {132, 57}, - {91, 98} - }, - { - {101, 90}, - {103, 98}, - {107, 113}, - {114, 125}, - {115, 126}, - {135, 126}, - {136, 125}, - {144, 114}, - {149, 90}, - {149, 89}, - {148, 87}, - {145, 84}, - {105, 84}, - {102, 87}, - {101, 89} - } - } -}; - - std::vector nfp_concave_testdata = { - { // ItemPair - { - { - {533726, 142141}, - {532359, 143386}, - {530141, 142155}, - {528649, 160091}, - {533659, 157607}, - {538669, 160091}, - {537178, 142155}, - {534959, 143386} - } - }, - { - { - {118305, 11603}, - {118311, 26616}, - {113311, 26611}, - {109311, 29604}, - {109300, 44608}, - {109311, 49631}, - {113300, 52636}, - {118311, 52636}, - {118308, 103636}, - {223830, 103636}, - {236845, 90642}, - {236832, 11630}, - {232825, 11616}, - {210149, 11616}, - {211308, 13625}, - {209315, 17080}, - {205326, 17080}, - {203334, 13629}, - {204493, 11616} - } - }, - } -}; - -template -void testNfp(const std::vector& testdata) { - using namespace libnest2d; - - Box bin(210*SCALE, 250*SCALE); - - int TEST_CASEcase = 0; - - auto& exportfun = exportSVG; - - auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ - TEST_CASEcase++; - - orbiter.translate({210*SCALE, 0}); - - auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), - orbiter.transformedShape()); - - placers::correctNfpPosition(nfp, stationary, orbiter); - - auto valid = shapelike::isValid(nfp.first); - - /*Item infp(nfp.first); - if(!valid.first) { - std::cout << "TEST_CASE instance: " << TEST_CASEidx << " " - << valid.second << std::endl; - std::vector> inp = {std::ref(infp)}; - exportfun(inp, bin, TEST_CASEidx); - }*/ - - REQUIRE(valid.first); - - Item infp(nfp.first); - - int i = 0; - auto rorbiter = orbiter.transformedShape(); - auto vo = nfp::referenceVertex(rorbiter); - - REQUIRE(stationary.isInside(infp)); - - for(auto v : infp) { - auto dx = getX(v) - getX(vo); - auto dy = getY(v) - getY(vo); - - Item tmp = orbiter; - - tmp.translate({dx, dy}); - - bool touching = Item::touches(tmp, stationary); - - if(!touching || !valid.first) { - std::vector> inp = { - std::ref(stationary), std::ref(tmp), std::ref(infp) - }; - - exportfun(inp, TEST_CASEcase*i++); - } - - REQUIRE(touching); - } - }; - - unsigned tidx = 0; - for(auto& td : testdata) { - auto orbiter = td.orbiter; - auto stationary = td.stationary; - if (!libnest2d::is_clockwise()) { - auto porb = orbiter.rawShape(); - auto pstat = stationary.rawShape(); - std::reverse(sl::begin(porb), sl::end(porb)); - std::reverse(sl::begin(pstat), sl::end(pstat)); - orbiter = Item{porb}; - stationary = Item{pstat}; - } - onetest(orbiter, stationary, tidx++); - } - - tidx = 0; - for(auto& td : testdata) { - auto orbiter = td.stationary; - auto stationary = td.orbiter; - if (!libnest2d::is_clockwise()) { - auto porb = orbiter.rawShape(); - auto pstat = stationary.rawShape(); - std::reverse(sl::begin(porb), sl::end(porb)); - std::reverse(sl::begin(pstat), sl::end(pstat)); - orbiter = Item{porb}; - stationary = Item{pstat}; - } - onetest(orbiter, stationary, tidx++); - } -} -} - -TEST_CASE("nfpConvexConvex", "[Geometry]") { - testNfp(nfp_testdata); -} - -//TEST_CASE(GeometryAlgorithms, nfpConcaveConcave) { -// TEST_CASENfp(nfp_concave_TEST_CASEdata); -//} - -TEST_CASE("pointOnPolygonContour", "[Geometry]") { - using namespace libnest2d; - - RectangleItem input(10, 10); - - placers::EdgeCache ecache(input); - - auto first = *input.begin(); - REQUIRE(getX(first) == getX(ecache.coords(0))); - REQUIRE(getY(first) == getY(ecache.coords(0))); - - if constexpr (ClosureTypeV == Closure::CLOSED) { - auto last = *std::prev(input.end()); - REQUIRE(getX(last) == getX(ecache.coords(1.0))); - REQUIRE(getY(last) == getY(ecache.coords(1.0))); - } else { - auto last = *input.begin(); - REQUIRE(getX(last) == getX(ecache.coords(1.0))); - REQUIRE(getY(last) == getY(ecache.coords(1.0))); - } - - for(int i = 0; i <= 100; i++) { - auto v = ecache.coords(i*(0.01)); - REQUIRE(shapelike::touches(v, input.transformedShape())); - } -} - -TEST_CASE("mergePileWithPolygon", "[Geometry]") { - using namespace libnest2d; - - RectangleItem rect1(10, 15); - RectangleItem rect2(15, 15); - RectangleItem rect3(20, 15); - - rect2.translate({10, 0}); - rect3.translate({25, 0}); - - TMultiShape pile; - pile.push_back(rect1.transformedShape()); - pile.push_back(rect2.transformedShape()); - - auto result = nfp::merge(pile, rect3.transformedShape()); - - REQUIRE(result.size() == 1); - - RectangleItem ref(45, 15); - - REQUIRE(shapelike::area(result.front()) == Approx(ref.area())); -} - -namespace { - -long double refMinAreaBox(const PolygonImpl& p) { - - auto it = sl::cbegin(p), itx = std::next(it); - - long double min_area = std::numeric_limits::max(); - - - auto update_min = [&min_area, &it, &itx, &p]() { - Segment s(*it, *itx); - - PolygonImpl rotated = p; - sl::rotate(rotated, -s.angleToXaxis()); - auto bb = sl::boundingBox(rotated); - auto area = cast(sl::area(bb)); - if(min_area > area) min_area = area; - }; - - while(itx != sl::cend(p)) { - update_min(); - ++it; ++itx; - } - - it = std::prev(sl::cend(p)); itx = sl::cbegin(p); - update_min(); - - return min_area; -} - -template struct BoostGCD { - T operator()(const T &a, const T &b) { return boost::gcd(a, b); } -}; - -using Unit = int64_t; -using Ratio = boost::rational; - -} - -//TEST_CASE(GeometryAlgorithms, MinAreaBBCClk) { -// auto u = [](ClipperLib::cInt n) { return n*1000000; }; -// PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); - -// long double arearef = refMinAreaBox(poly); -// long double area = minAreaBoundingBox(poly).area(); - -// REQUIRE(std::abs(area - arearef) <= 500e6 ); -//} - -TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") { - long double err_epsilon = 500e6l; - - for(PathImpl rinput : PRINTER_PART_POLYGONS) { - PolygonImpl poly(rinput); - - long double arearef = refMinAreaBox(poly); - auto bb = minAreaBoundingBox(rinput); - long double area = cast(bb.area()); - - bool succ = std::abs(arearef - area) < err_epsilon; - - REQUIRE(succ); - } - - for(PathImpl rinput : STEGOSAUR_POLYGONS) { -// rinput.pop_back(); - std::reverse(rinput.begin(), rinput.end()); - - PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); - - long double arearef = refMinAreaBox(poly); - auto bb = minAreaBoundingBox(poly); - long double area = cast(bb.area()); - - - bool succ = std::abs(arearef - area) < err_epsilon; - - REQUIRE(succ); - } -} - -template MultiPolygon merged_pile(It from, It to, int bin_id) -{ - MultiPolygon pile; - pile.reserve(size_t(to - from)); - - for (auto it = from; it != to; ++it) { - if (it->binId() == bin_id) pile.emplace_back(it->transformedShape()); - } - - return nfp::merge(pile); -} - -TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]") -{ - static const constexpr Slic3r::ClipperLib::cInt W = 10000000; - - // Get the input items and define the bin. - std::vector input(9, {W, W}); - - auto bin = Box::infinite(); - - NfpPlacer::Config pconfig; - - pconfig.object_function = [](const Item &item) -> double { - return pl::magnsq(item.boundingBox().center()); - }; - - size_t bins = nest(input, bin, 0, NestConfig{pconfig}); - - REQUIRE(bins == 1); - - // Gather the items into piles of arranged polygons... - MultiPolygon pile; - pile.reserve(input.size()); - - for (auto &itm : input) { - REQUIRE(itm.binId() == 0); - pile.emplace_back(itm.transformedShape()); - } - - MultiPolygon m = merged_pile(input.begin(), input.end(), 0); - - REQUIRE(m.size() == 1); - - REQUIRE(sl::area(m) == Approx(9. * W * W)); -} - -TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") -{ - static const constexpr Slic3r::ClipperLib::cInt W = 10000000; - static const constexpr size_t N = 100; - - // Get the input items and define the bin. - std::vector input(N, {W, W}); - - auto bin = Box::infinite(); - - NfpPlacer::Config pconfig; - pconfig.rotations = {0.}; - Box pile_box; - pconfig.before_packing = - [&pile_box](const MultiPolygon &pile, - const _ItemGroup &/*packed_items*/, - const _ItemGroup &/*remaining_items*/) { - pile_box = sl::boundingBox(pile); - }; - - pconfig.object_function = [&pile_box](const Item &item) -> double { - Box b = sl::boundingBox(item.boundingBox(), pile_box); - double area = b.area() / (double(W) * W); - return -area; - }; - - size_t bins = nest(input, bin, 0, NestConfig{pconfig}); - - // To debug: - exportSVG<1000000>("out", input.begin(), input.end()); - - REQUIRE(bins == 1); - - MultiPolygon pile = merged_pile(input.begin(), input.end(), 0); - Box bb = sl::boundingBox(pile); - - // Here the result shall be a stairway of boxes - REQUIRE(pile.size() == N); - REQUIRE(bb.area() == double(N) * N * W * W); -} diff --git a/tests/libnest2d/printer_parts.cpp b/tests/libnest2d/printer_parts.cpp deleted file mode 100644 index 104b1c12fd..0000000000 --- a/tests/libnest2d/printer_parts.cpp +++ /dev/null @@ -1,3175 +0,0 @@ -#include "printer_parts.hpp" - -const TestData PRINTER_PART_POLYGONS = -{ - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568550}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568550}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-63750000, -8000000}, - {-54750000, 46000000}, - {50750000, 46000000}, - {63750000, 33000000}, - {63750000, -46000000}, - {-54750000, -46000000}, - {-63750000, -28000000}, - {-63750000, -8000000}, - }, - { - {-52750000, 41512348}, - {-31250000, 45987651}, - {52750000, 45987651}, - {52750000, -45987651}, - {-52750000, -45987651}, - {-52750000, 41512348}, - }, - { - {-3900000, 14000000}, - {-2167950, 14000000}, - {1721454, 7263400}, - {3828529, 3613790}, - {3838809, 3582149}, - {3871560, 3270569}, - {3900000, 3000000}, - {3500000, -3000000}, - {3471560, -3270565}, - {3447549, -3498986}, - {3292510, -3976167}, - {3099999, -4512949}, - {2530129, -5500000}, - {807565, -8483570}, - {-2377349, -14000000}, - {-3900000, -14000000}, - {-3900000, 14000000}, - }, - { - {-31750000, -1000000}, - {-25250000, 40500000}, - {-18250000, 47500000}, - {10750000, 47500000}, - {16750000, 41500000}, - {31750000, -37000000}, - {31750000, -43857898}, - {28107900, -47500000}, - {18392099, -47500000}, - {-20750000, -46500000}, - {-31750000, -4000000}, - {-31750000, -1000000}, - }, - { - {-34625000, -14265399}, - {-10924999, 24875000}, - {33325000, 24875000}, - {37575000, 20625000}, - {37575000, 17625000}, - {26575000, -24875000}, - {-8924999, -24875000}, - {-34625000, -24484600}, - {-37575000, -19375000}, - {-34625000, -14265399}, - }, - { - {-14000000, 9000000}, - {-11000000, 17000000}, - {14000000, 17000000}, - {14000000, -17000000}, - {-11000000, -17000000}, - {-14000000, -8000000}, - {-14000000, 9000000}, - }, - { - {-5300000, 2227401}, - {-237800, 5150001}, - {5299999, 5150001}, - {5299999, 650001}, - {4699999, -5149997}, - {-5300000, -5149997}, - {-5300000, 2227401}, - }, - { - {-12000000, 18000000}, - {12000000, 18000000}, - {12000000, -18000000}, - {-12000000, -18000000}, - {-12000000, 18000000}, - }, - { - {-18000000, -1000000}, - {-15000000, 22000000}, - {-11000000, 26000000}, - {11000000, 26000000}, - {15000000, 22000000}, - {18000000, -1000000}, - {18000000, -26000000}, - {-18000000, -26000000}, - {-18000000, -1000000}, - }, - { - {-77500000, 30000000}, - {-72500000, 35000000}, - {72500000, 35000000}, - {77500000, 30000000}, - {77500000, -32928901}, - {75428901, -35000000}, - {-75428901, -35000000}, - {-77500000, -32928901}, - {-77500000, 30000000}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190019}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802209}, - {6691309, -11542349}, - {5877850, -12201069}, - {5000000, -12771149}, - {4067369, -13246350}, - {3090169, -13621459}, - {2079119, -13892379}, - {1045279, -14056119}, - {0, -14110899}, - {-1045279, -14056119}, - {-2079119, -13892379}, - {-3090169, -13621459}, - {-4067369, -13246350}, - {-5000000, -12771149}, - {-5877850, -12201069}, - {-6691309, -11542349}, - {-7431449, -10802209}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190019}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-34192394, -5192389}, - {-31499996, 39000000}, - {-8183795, 47668998}, - {-6769596, 47668998}, - {-4648197, 45547698}, - {34192394, 6707109}, - {34192394, 5192389}, - {31500003, -39000000}, - {8183803, -47668998}, - {6769603, -47668998}, - {4648202, -45547698}, - {-32474895, -8424619}, - {-34192394, -6707109}, - {-34192394, -5192389}, - }, - { - {-23475500, -11910099}, - {-18000000, 8217699}, - {-11139699, 20100000}, - {-10271400, 20899999}, - {9532010, 20899999}, - {11199999, 20100000}, - {18500000, 8600000}, - {23475500, -11910099}, - {23799999, -14899999}, - {23706600, -15788900}, - {23668899, -16147499}, - {23281299, -17340400}, - {22654100, -18426700}, - {21814800, -19358900}, - {20799999, -20096199}, - {19654100, -20606300}, - {18427200, -20867099}, - {17799999, -20899999}, - {-17799999, -20899999}, - {-18427200, -20867099}, - {-19654100, -20606300}, - {-20799999, -20096199}, - {-21814800, -19358900}, - {-22654100, -18426700}, - {-23281299, -17340400}, - {-23668899, -16147499}, - {-23799999, -14899999}, - {-23475500, -11910099}, - }, - { - {-32000000, 10000000}, - {-31934440, 10623733}, - {-31740640, 11220210}, - {-31427049, 11763360}, - {-31007389, 12229430}, - {-30500000, 12598079}, - {-29927051, 12853170}, - {-29313585, 12983570}, - {16000000, 16000000}, - {26000000, 16000000}, - {31007400, 12229430}, - {31427101, 11763360}, - {31740600, 11220210}, - {31934398, 10623733}, - {32000000, 10000000}, - {32000000, -13000000}, - {31934398, -13623699}, - {31740600, -14220199}, - {31427101, -14763399}, - {31007400, -15229400}, - {30500000, -15598100}, - {29927101, -15853200}, - {29313598, -15983600}, - {29000000, -16000000}, - {-28000000, -16000000}, - {-29313585, -15983600}, - {-29927051, -15853200}, - {-30500000, -15598100}, - {-31007389, -15229400}, - {-31427049, -14763399}, - {-31740640, -14220199}, - {-31934440, -13623699}, - {-32000000, -13000000}, - {-32000000, 10000000}, - }, - { - {-36133789, -46431022}, - {-36040100, -46171817}, - {-35852722, -45653411}, - {2200073, 59616485}, - {12112792, 87039184}, - {14274505, 93019332}, - {14382049, 93291641}, - {14508483, 93563430}, - {14573425, 93688369}, - {14654052, 93832443}, - {14818634, 94096328}, - {14982757, 94327621}, - {15001708, 94352630}, - {15202392, 94598999}, - {15419342, 94833160}, - {15497497, 94910552}, - {15650848, 95053039}, - {15894866, 95256866}, - {16104309, 95412185}, - {16149047, 95443206}, - {16410888, 95611038}, - {16677795, 95759750}, - {16782348, 95812332}, - {16947143, 95889144}, - {17216400, 95999465}, - {17483123, 96091293}, - {17505554, 96098251}, - {17745178, 96165542}, - {18000671, 96223373}, - {18245880, 96265884}, - {18484039, 96295257}, - {18976715, 96319580}, - {31135131, 96319580}, - {31697082, 96287902}, - {31746368, 96282104}, - {32263000, 96190719}, - {32338623, 96172576}, - {32821411, 96026641}, - {32906188, 95995391}, - {33360565, 95797012}, - {33443420, 95754882}, - {33869171, 95505874}, - {33900756, 95485122}, - {34136413, 95318618}, - {34337127, 95159790}, - {34377288, 95125930}, - {34619628, 94905410}, - {34756286, 94767364}, - {34859008, 94656143}, - {35090606, 94378067}, - {35120849, 94338546}, - {35309295, 94072113}, - {35434875, 93871475}, - {35510070, 93740310}, - {35688232, 93385772}, - {35699096, 93361679}, - {35839782, 93012557}, - {35905487, 92817459}, - {35961578, 92625488}, - {36048004, 92249023}, - {36051574, 92229934}, - {36108856, 91831405}, - {36122985, 91667816}, - {36133789, 91435317}, - {36129669, 91085830}, - {36127685, 91046661}, - {36092742, 90669830}, - {36069946, 90514739}, - {36031829, 90308425}, - {35948211, 89965225}, - {34482635, 84756820}, - {27911407, 61403976}, - {-5872558, -58657440}, - {-14243621, -88406509}, - {-14576812, -89590599}, - {-15421997, -92594200}, - {-15657684, -93431732}, - {-16038940, -93720520}, - {-16420196, -94009307}, - {-17182708, -94586875}, - {-18834838, -95838272}, - {-19470275, -96319580}, - {-21368133, -96319580}, - {-22763854, -96319534}, - {-29742462, -96319274}, - {-32533935, -96319168}, - {-36133789, -54619018}, - {-36133789, -46431022}, - }, - { - {-26000000, 25500000}, - {-6500000, 45000000}, - {17499998, 45000000}, - {23966310, 38533699}, - {26000000, 36500000}, - {26000000, -19000000}, - {25950000, -24500000}, - {17000000, -42214698}, - {14300000, -45000000}, - {-14299999, -45000000}, - {-17500000, -41714698}, - {-23400001, -24500000}, - {-26000000, -10464000}, - {-26000000, 25500000}, - }, - { - {-26000000, 16636100}, - {-25072200, 18777799}, - {-16500000, 35299999}, - {-15050000, 36750000}, - {13550000, 36750000}, - {15000000, 35299999}, - {26000000, 16045200}, - {26000000, -2750000}, - {16500000, -34507900}, - {14840600, -36167301}, - {14257900, -36750000}, - {-14257900, -36750000}, - {-16500000, -34507900}, - {-26000000, -2750000}, - {-26000000, 16636100}, - }, - { - {-18062349, 18950099}, - {4644938, 20049900}, - {6230361, 20049900}, - {7803279, 19851200}, - {9338899, 19456899}, - {10812990, 18873300}, - {12202310, 18109500}, - {13484951, 17177600}, - {14640670, 16092300}, - {15651250, 14870700}, - {16500749, 13532100}, - {17175849, 12097599}, - {17665750, 10589700}, - {17962850, 9032400}, - {18062349, 7450099}, - {17962850, 5867799}, - {15810750, -11007740}, - {15683750, -11727769}, - {15506849, -12437200}, - {15280929, -13132559}, - {15007040, -13810470}, - {14686531, -14467609}, - {14320949, -15100799}, - {13912099, -15706950}, - {13461959, -16283100}, - {12972730, -16826450}, - {12446790, -17334339}, - {11886699, -17804309}, - {11295190, -18234069}, - {10675149, -18621520}, - {10029590, -18964771}, - {9361650, -19262149}, - {8674600, -19512220}, - {7971780, -19713699}, - {7256609, -19865798}, - {6532589, -19967498}, - {5803222, -20018501}, - {5437650, -20024900}, - {-1062349, -20049900}, - {-16562349, -20049900}, - {-18062349, -18549900}, - {-18062349, 18950099}, - }, - { - {-18062349, 41299900}, - {-1062349, 41299900}, - {15280929, -8117440}, - {15506849, -8812799}, - {15683750, -9522230}, - {15810750, -10242259}, - {17962850, -27117799}, - {18062349, -28700099}, - {17962850, -30282400}, - {17665750, -31839700}, - {17175849, -33347599}, - {16500749, -34782100}, - {15651250, -36120700}, - {14640670, -37342300}, - {13484951, -38427600}, - {12202310, -39359500}, - {10812990, -40123298}, - {9338899, -40706901}, - {7803279, -41101200}, - {6230361, -41299900}, - {4644938, -41299900}, - {-18062349, -40200099}, - {-18062349, 41299900}, - }, - { - {-11750000, 13057900}, - {-9807860, 15000000}, - {4392139, 24000000}, - {11750000, 24000000}, - {11750000, -24000000}, - {4392139, -24000000}, - {-9807860, -15000000}, - {-11750000, -13057900}, - {-11750000, 13057900}, - }, - { - {-12500000, 17500000}, - {12500000, 17500000}, - {12500000, -17500000}, - {-12500000, -17500000}, - {-12500000, 17500000}, - }, - { - {-23500000, 11500000}, - {-13857859, 21000000}, - {-11000000, 21000000}, - {18500000, 500000}, - {23500000, -4500000}, - {23500000, -19500000}, - {22000000, -21000000}, - {-23500000, -21000000}, - {-23500000, 11500000}, - }, - { - {-13000000, 5250000}, - {-4000000, 6750000}, - {4000000, 6750000}, - {13000000, 5250000}, - {13000000, 838459}, - {11376299, -1973939}, - {10350899, -3750000}, - {8618800, -6750000}, - {-8498290, -6750000}, - {-13000000, 1047180}, - {-13000000, 5250000}, - }, - { - {-25000000, 50500000}, - {-21500000, 54000000}, - {18286800, 54000000}, - {25000000, 47286800}, - {25000000, -47286800}, - {18286800, -54000000}, - {-21500000, -54000000}, - {-25000000, -50500000}, - {-25000000, 50500000}, - }, - { - {-19000000, 46000000}, - {-16799999, 46000000}, - {14000000, 34000000}, - {19000000, 29000000}, - {19000000, -29000000}, - {14000000, -34000000}, - {-16799999, -46000000}, - {-19000000, -46000000}, - {-19000000, 46000000}, - }, - { - {-7956170, 836226}, - {-7825180, 1663290}, - {-7767529, 1914530}, - {-7608449, 2472140}, - {-7308360, 3253890}, - {-7083650, 3717780}, - {-6928199, 4000000}, - {-6472139, 4702280}, - {-5988090, 5304979}, - {-5945159, 5353040}, - {-5353040, 5945159}, - {-4702280, 6472139}, - {-4544519, 6583869}, - {-4000000, 6928199}, - {-3253890, 7308360}, - {-2836839, 7480130}, - {-2472140, 7608449}, - {-1663290, 7825180}, - {-964293, 7941669}, - {-836226, 7956170}, - {0, 8000000}, - {836226, 7956170}, - {964293, 7941669}, - {1663290, 7825180}, - {2472140, 7608449}, - {2836839, 7480130}, - {3253890, 7308360}, - {4000000, 6928199}, - {4544519, 6583869}, - {4702280, 6472139}, - {5353040, 5945159}, - {5945159, 5353040}, - {5988090, 5304979}, - {6472139, 4702280}, - {6928199, 4000000}, - {7083650, 3717780}, - {7308360, 3253890}, - {7608449, 2472140}, - {7767529, 1914530}, - {7825180, 1663290}, - {7956170, 836226}, - {8000000, 0}, - {7956170, -836226}, - {7825180, -1663290}, - {7767529, -1914530}, - {7608449, -2472140}, - {7308360, -3253890}, - {7083650, -3717780}, - {6928199, -4000000}, - {6472139, -4702280}, - {5988090, -5304979}, - {5945159, -5353040}, - {5353040, -5945159}, - {4702280, -6472139}, - {4544519, -6583869}, - {4000000, -6928199}, - {3253890, -7308360}, - {2836839, -7480130}, - {2472140, -7608449}, - {1663290, -7825180}, - {964293, -7941669}, - {836226, -7956170}, - {0, -8000000}, - {-836226, -7956170}, - {-964293, -7941669}, - {-1663290, -7825180}, - {-2472140, -7608449}, - {-2836839, -7480130}, - {-3253890, -7308360}, - {-4000000, -6928199}, - {-4544519, -6583869}, - {-4702280, -6472139}, - {-5353040, -5945159}, - {-5945159, -5353040}, - {-5988090, -5304979}, - {-6472139, -4702280}, - {-6928199, -4000000}, - {-7083650, -3717780}, - {-7308360, -3253890}, - {-7608449, -2472140}, - {-7767529, -1914530}, - {-7825180, -1663290}, - {-7956170, -836226}, - {-8000000, 0}, - {-7956170, 836226}, - }, -}; - -const TestData STEGOSAUR_POLYGONS = -{ - { - {113210205, 107034095}, - {113561798, 109153793}, - {113750099, 109914001}, - {114396499, 111040199}, - {114599197, 111321998}, - {115570404, 112657096}, - {116920097, 114166595}, - {117630599, 114609390}, - {119703704, 115583900}, - {120559494, 115811996}, - {121045410, 115754493}, - {122698097, 115526496}, - {123373001, 115370193}, - {123482406, 115315689}, - {125664199, 114129798}, - {125920303, 113968193}, - {128551208, 111866195}, - {129075592, 111443199}, - {135044692, 106572608}, - {135254898, 106347694}, - {135415100, 106102897}, - {136121704, 103779891}, - {136325103, 103086303}, - {136690093, 101284896}, - {136798309, 97568496}, - {136798309, 97470397}, - {136787399, 97375297}, - {136753295, 97272102}, - {136687988, 97158699}, - {136539794, 96946899}, - {135526702, 95550994}, - {135388488, 95382293}, - {135272491, 95279098}, - {135214904, 95250595}, - {135122894, 95218002}, - {134966705, 95165191}, - {131753997, 94380798}, - {131226806, 94331001}, - {129603393, 94193893}, - {129224197, 94188003}, - {127874107, 94215103}, - {126812797, 94690200}, - {126558197, 94813896}, - {118361801, 99824195}, - {116550796, 101078796}, - {116189704, 101380493}, - {114634002, 103027999}, - {114118103, 103820297}, - {113399200, 105568000}, - {113201705, 106093597}, - {113210205, 107034095}, - }, - { - {77917999, 130563003}, - {77926300, 131300903}, - {77990196, 132392700}, - {78144195, 133328002}, - {78170593, 133427093}, - {78235900, 133657592}, - {78799598, 135466705}, - {78933296, 135832397}, - {79112899, 136247604}, - {79336303, 136670898}, - {79585197, 137080596}, - {79726303, 137309005}, - {79820297, 137431900}, - {79942199, 137549407}, - {90329193, 145990203}, - {90460197, 146094390}, - {90606399, 146184509}, - {90715194, 146230010}, - {90919601, 146267211}, - {142335296, 153077697}, - {143460296, 153153594}, - {143976593, 153182189}, - {145403991, 153148605}, - {145562301, 153131195}, - {145705993, 153102905}, - {145938796, 153053192}, - {146134094, 153010101}, - {146483184, 152920196}, - {146904693, 152806396}, - {147180099, 152670196}, - {147357788, 152581695}, - {147615295, 152423095}, - {147782287, 152294708}, - {149281799, 150908386}, - {149405303, 150784912}, - {166569305, 126952499}, - {166784301, 126638099}, - {166938491, 126393699}, - {167030899, 126245101}, - {167173004, 126015899}, - {167415298, 125607200}, - {167468292, 125504699}, - {167553100, 125320899}, - {167584594, 125250694}, - {167684997, 125004394}, - {167807098, 124672401}, - {167938995, 124255203}, - {168052307, 123694000}, - {170094100, 112846900}, - {170118408, 112684204}, - {172079101, 88437797}, - {172082000, 88294403}, - {171916290, 82827606}, - {171911590, 82705703}, - {171874893, 82641906}, - {169867004, 79529907}, - {155996795, 58147998}, - {155904998, 58066299}, - {155864791, 58054199}, - {134315704, 56830902}, - {134086486, 56817901}, - {98200096, 56817798}, - {97838195, 56818599}, - {79401695, 56865097}, - {79291297, 56865501}, - {79180694, 56869499}, - {79058799, 56885097}, - {78937301, 56965301}, - {78324691, 57374599}, - {77932998, 57638401}, - {77917999, 57764297}, - {77917999, 130563003}, - }, - { - {75566848, 109289947}, - {75592651, 109421951}, - {75644248, 109534446}, - {95210548, 141223846}, - {95262649, 141307449}, - {95487854, 141401443}, - {95910850, 141511642}, - {96105651, 141550338}, - {106015045, 142803451}, - {106142852, 142815155}, - {166897460, 139500244}, - {167019348, 139484741}, - {168008239, 138823043}, - {168137542, 138735153}, - {168156250, 138616851}, - {173160751, 98882049}, - {174381546, 87916046}, - {174412246, 87579048}, - {174429443, 86988746}, - {174436141, 86297348}, - {174438949, 84912048}, - {174262939, 80999145}, - {174172546, 80477546}, - {173847549, 79140846}, - {173623840, 78294349}, - {173120239, 76485046}, - {173067138, 76300544}, - {173017852, 76137542}, - {172941543, 75903045}, - {172892547, 75753143}, - {172813537, 75533348}, - {172758453, 75387046}, - {172307556, 74196746}, - {171926544, 73192848}, - {171891448, 73100448}, - {171672546, 72524147}, - {171502441, 72085144}, - {171414459, 71859146}, - {171294250, 71552352}, - {171080139, 71019744}, - {171039245, 70928146}, - {170970550, 70813346}, - {170904235, 70704040}, - {170786254, 70524353}, - {168063247, 67259048}, - {167989547, 67184844}, - {83427947, 67184844}, - {78360847, 67201248}, - {78238845, 67220550}, - {78151550, 67350547}, - {77574554, 68220550}, - {77494949, 68342651}, - {77479949, 68464546}, - {75648345, 106513351}, - {75561050, 109165740}, - {75566848, 109289947}, - }, - { - {75619415, 108041595}, - {83609863, 134885772}, - {83806945, 135450820}, - {83943908, 135727371}, - {84799934, 137289794}, - {86547897, 140033782}, - {86674118, 140192962}, - {86810661, 140364715}, - {87045211, 140619918}, - {88187042, 141853240}, - {93924575, 147393783}, - {94058013, 147454803}, - {111640083, 153754562}, - {111762550, 153787933}, - {111975250, 153835311}, - {112127426, 153842803}, - {116797996, 154005157}, - {116969688, 154010681}, - {117141731, 154005935}, - {117333145, 153988037}, - {118007507, 153919952}, - {118159675, 153902130}, - {118931480, 153771942}, - {120878150, 153379089}, - {121172164, 153319259}, - {122074508, 153034362}, - {122260681, 152970367}, - {122313438, 152949584}, - {130755096, 149423736}, - {130996063, 149316818}, - {138893524, 144469665}, - {138896423, 144466918}, - {169883666, 97686134}, - {170115036, 96518981}, - {170144317, 96365257}, - {174395645, 67672065}, - {174396560, 67664222}, - {174288452, 66839241}, - {174170364, 66096923}, - {174112731, 65952033}, - {174021377, 65823486}, - {173948608, 65743225}, - {173863830, 65654769}, - {170408340, 63627494}, - {170004867, 63394714}, - {169585632, 63194389}, - {169441162, 63137046}, - {168944274, 62952133}, - {160605072, 60214218}, - {160331573, 60126396}, - {159674743, 59916877}, - {150337249, 56943778}, - {150267730, 56922073}, - {150080139, 56864868}, - {149435333, 56676422}, - {149310241, 56640579}, - {148055419, 56285041}, - {147828796, 56230949}, - {147598205, 56181800}, - {147149963, 56093917}, - {146834457, 56044700}, - {146727966, 56028717}, - {146519729, 56004882}, - {146328521, 55989326}, - {146170684, 55990036}, - {146151321, 55990745}, - {145800170, 56003616}, - {145639526, 56017753}, - {145599426, 56022491}, - {145481338, 56039184}, - {145389556, 56052757}, - {145325134, 56062591}, - {145176574, 56086135}, - {145017272, 56113922}, - {107163085, 63504539}, - {101013870, 65454101}, - {100921798, 65535285}, - {95362182, 74174079}, - {75652366, 107803443}, - {75635391, 107834983}, - {75628814, 107853294}, - {75603431, 107933692}, - {75619415, 108041595}, - }, - { - {83617141, 120264900}, - {84617370, 126416427}, - {84648635, 126601341}, - {84693695, 126816085}, - {84762496, 127082641}, - {84772140, 127117034}, - {84860748, 127391693}, - {84927398, 127550239}, - {85072967, 127789642}, - {85155151, 127908851}, - {86745422, 130042907}, - {86982666, 130317489}, - {89975143, 133230743}, - {90091384, 133338500}, - {96260833, 138719818}, - {96713928, 139103668}, - {98139297, 140307388}, - {102104766, 143511505}, - {102142089, 143536468}, - {102457626, 143735107}, - {103386764, 144312988}, - {103845001, 144579177}, - {104139175, 144737136}, - {104551254, 144932250}, - {104690155, 144985778}, - {104844238, 145010009}, - {105020034, 145010375}, - {128999633, 144082305}, - {129096542, 144076141}, - {133932327, 143370178}, - {134130615, 143326751}, - {134281250, 143289520}, - {135247116, 142993438}, - {150774948, 137828704}, - {150893478, 137786178}, - {151350921, 137608901}, - {159797760, 134318115}, - {159979827, 134244384}, - {159988128, 134240997}, - {160035186, 134221633}, - {160054962, 134211486}, - {160168762, 134132736}, - {160181228, 134121047}, - {160336425, 133961502}, - {160689147, 133564331}, - {161446258, 132710739}, - {163306427, 130611648}, - {164845474, 128873855}, - {165270233, 128393600}, - {165281478, 128380706}, - {165300598, 128358673}, - {165303497, 128355194}, - {166411590, 122772674}, - {166423767, 122708648}, - {164745605, 66237312}, - {164740341, 66193061}, - {164721755, 66082092}, - {164721160, 66078750}, - {164688476, 65914146}, - {164668426, 65859436}, - {164563110, 65765937}, - {164431152, 65715034}, - {163997619, 65550788}, - {163946426, 65531440}, - {162998107, 65173629}, - {162664978, 65049140}, - {162482696, 64991668}, - {162464660, 64989639}, - {148029083, 66896141}, - {147862396, 66932853}, - {130087829, 73341102}, - {129791564, 73469726}, - {100590927, 90307685}, - {100483535, 90373847}, - {100364990, 90458930}, - {96447448, 93276664}, - {95179656, 94189010}, - {93692718, 95260208}, - {87904327, 99430885}, - {87663711, 99606147}, - {87576202, 99683990}, - {87498199, 99801719}, - {85740264, 104173728}, - {85538925, 104710494}, - {84786132, 107265830}, - {84635955, 107801383}, - {84619506, 107868064}, - {84518463, 108287200}, - {84456848, 108613471}, - {84419158, 108826194}, - {84375244, 109093818}, - {84329818, 109435180}, - {84249862, 110179664}, - {84218429, 110572166}, - {83630020, 117995208}, - {83595535, 118787673}, - {83576217, 119290679}, - {83617141, 120264900}, - }, - { - {91735549, 117640846}, - {91748252, 117958145}, - {91823547, 118515449}, - {92088752, 119477249}, - {97995346, 140538452}, - {98031051, 140660446}, - {98154449, 141060241}, - {98179855, 141133758}, - {98217056, 141232849}, - {98217147, 141233047}, - {98269256, 141337051}, - {98298950, 141387954}, - {98337753, 141445755}, - {99455047, 142984451}, - {99656250, 143247344}, - {102567855, 146783752}, - {102685150, 146906845}, - {102828948, 147031250}, - {102972457, 147120452}, - {103676147, 147539642}, - {103758956, 147586151}, - {103956756, 147682144}, - {104479949, 147931457}, - {104744453, 148044143}, - {104994750, 148123443}, - {105375648, 148158645}, - {109266250, 148178253}, - {109447753, 148169052}, - {109693649, 148129150}, - {113729949, 147337448}, - {113884552, 147303054}, - {115155349, 146956146}, - {117637145, 146174346}, - {154694046, 134048049}, - {156979949, 133128555}, - {157076843, 133059356}, - {157125045, 133001449}, - {157561340, 132300750}, - {157865753, 131795959}, - {157923156, 131667358}, - {158007049, 131297653}, - {158112747, 130777053}, - {158116653, 130640853}, - {158268951, 119981643}, - {158260040, 119824752}, - {158229949, 119563751}, - {149914047, 73458648}, - {149877548, 73331748}, - {144460754, 66413558}, - {144230545, 66153152}, - {144128051, 66075057}, - {143974853, 65973152}, - {142812744, 65353149}, - {141810943, 64837249}, - {141683349, 64805152}, - {141505157, 64784652}, - {108214355, 61896251}, - {107826354, 61866352}, - {107072151, 61821750}, - {106938850, 61873550}, - {106584251, 62055152}, - {106419952, 62147548}, - {100459152, 65546951}, - {100343849, 65615150}, - {100198852, 65716949}, - {99825149, 65979751}, - {94619247, 70330352}, - {94492355, 70480850}, - {94445846, 70547355}, - {94425354, 70588752}, - {94379753, 70687652}, - {94110252, 71443450}, - {94095252, 71569053}, - {91737251, 117308746}, - {91731048, 117430946}, - {91735549, 117640846}, - }, - { - {108231399, 111763748}, - {108335403, 111927955}, - {108865203, 112754745}, - {109206703, 113283851}, - {127117500, 125545951}, - {127212097, 125560951}, - {127358497, 125563652}, - {131348007, 125551147}, - {131412002, 125550849}, - {131509506, 125535446}, - {131579391, 125431343}, - {132041000, 124735656}, - {132104690, 124637847}, - {144108505, 100950546}, - {144120605, 100853042}, - {144123291, 100764648}, - {144122695, 100475143}, - {144086898, 85637748}, - {144083602, 85549346}, - {144071105, 85451843}, - {144007003, 85354545}, - {143679595, 84864547}, - {143468597, 84551048}, - {143367889, 84539146}, - {109847702, 84436347}, - {109684700, 84458953}, - {105946502, 89406143}, - {105915901, 91160446}, - {105880905, 93187744}, - {105876701, 93441345}, - {108231399, 111763748}, - }, - { - {102614700, 117684249}, - {102675102, 118074157}, - {102888999, 118743148}, - {103199707, 119517555}, - {103446800, 120099655}, - {103488204, 120193450}, - {104063903, 121373947}, - {104535499, 122192245}, - {104595802, 122295249}, - {104663002, 122402854}, - {104945701, 122854858}, - {105740501, 124038848}, - {106809700, 125479354}, - {107564399, 126380050}, - {108116203, 126975646}, - {123724700, 142516540}, - {124938400, 143705444}, - {127919601, 146599243}, - {128150894, 146821456}, - {128251602, 146917251}, - {128383605, 147041839}, - {128527709, 147176147}, - {128685699, 147321456}, - {128861007, 147481246}, - {132825103, 151046661}, - {133005493, 151205657}, - {133389007, 151488143}, - {133896499, 151858062}, - {134172302, 151991546}, - {134375000, 152063140}, - {135316101, 152300949}, - {136056304, 152220947}, - {136242706, 152186843}, - {136622207, 152016448}, - {136805404, 151908355}, - {147099594, 145766845}, - {147246704, 144900756}, - {147387603, 144048461}, - {144353698, 99345855}, - {144333801, 99232254}, - {144244598, 98812850}, - {144228698, 98757858}, - {144174606, 98616455}, - {133010101, 72396743}, - {132018905, 70280853}, - {130667404, 67536949}, - {129167297, 64854446}, - {128569198, 64098350}, - {124458503, 59135948}, - {124260597, 58946949}, - {123908706, 58658851}, - {123460098, 58327850}, - {122674499, 57840648}, - {122041801, 57712150}, - {121613403, 57699047}, - {121359901, 57749351}, - {121123199, 57826450}, - {120953498, 57882247}, - {120431701, 58198547}, - {120099205, 58599349}, - {119892303, 58903049}, - {102835296, 115179351}, - {102686599, 115817245}, - {102612396, 116540557}, - {102614700, 117684249}, - }, - { - {98163757, 71203430}, - {98212463, 73314544}, - {98326538, 74432693}, - {98402908, 75169799}, - {98524154, 76328353}, - {99088806, 79911361}, - {99304885, 80947769}, - {100106689, 84244186}, - {100358123, 85080337}, - {101715545, 89252807}, - {101969528, 89987213}, - {107989440, 106391418}, - {126299575, 140277343}, - {127061813, 141486663}, - {127405746, 141872253}, - {127846908, 142318450}, - {130818496, 145301574}, - {134366424, 148100921}, - {135308380, 148798828}, - {135745666, 149117523}, - {136033020, 149251800}, - {136500579, 149387725}, - {136662719, 149418395}, - {136973922, 149474822}, - {137184890, 149484375}, - {137623748, 149434356}, - {137830810, 149355072}, - {138681732, 148971343}, - {139374465, 148463409}, - {139589187, 148264312}, - {139809707, 148010711}, - {139985610, 147685028}, - {140196029, 147284973}, - {140355834, 146978668}, - {142079666, 142575622}, - {146702194, 129469726}, - {151285888, 113275238}, - {151543731, 112046264}, - {151701629, 110884704}, - {151837020, 108986206}, - {151837097, 107724029}, - {151760101, 106529205}, - {151581970, 105441925}, - {151577301, 105413757}, - {151495269, 105014709}, - {151393142, 104551513}, - {151058502, 103296112}, - {150705520, 102477264}, - {150137725, 101686370}, - {149427032, 100938537}, - {102979965, 60772064}, - {101930953, 60515609}, - {101276748, 60634414}, - {100717803, 60918136}, - {100125732, 61584625}, - {99618148, 62413436}, - {99457214, 62709442}, - {99368347, 62914794}, - {99166992, 63728332}, - {98313827, 69634780}, - {98176910, 70615707}, - {98162902, 70798233}, - {98163757, 71203430}, - }, - { - {79090698, 116426399}, - {80959800, 137087692}, - {81030303, 137762298}, - {81190704, 138903503}, - {81253700, 139084197}, - {81479301, 139544998}, - {81952003, 140118896}, - {82319900, 140523895}, - {82967803, 140993896}, - {83022903, 141032104}, - {83777900, 141493606}, - {84722099, 141849899}, - {84944396, 141887207}, - {86144699, 141915893}, - {87643997, 141938095}, - {88277503, 141887695}, - {88582099, 141840606}, - {89395401, 141712203}, - {90531204, 141528396}, - {91014801, 141438400}, - {92097595, 141190093}, - {123348297, 132876998}, - {123399505, 132860000}, - {123452804, 132841506}, - {123515502, 132818908}, - {123543800, 132806198}, - {124299598, 132437393}, - {124975502, 132042098}, - {125047500, 131992202}, - {125119506, 131930603}, - {166848800, 86317703}, - {168976409, 83524902}, - {169359603, 82932701}, - {169852600, 81917800}, - {170686904, 79771202}, - {170829406, 79245597}, - {170885498, 78796295}, - {170909301, 78531898}, - {170899703, 78238700}, - {170842803, 77553199}, - {170701293, 76723495}, - {170302307, 75753898}, - {169924301, 75067398}, - {169359802, 74578796}, - {168148605, 73757499}, - {163261596, 71124702}, - {162986007, 70977798}, - {162248703, 70599098}, - {158193405, 68923995}, - {157514297, 68667495}, - {156892700, 68495201}, - {156607299, 68432998}, - {154301895, 68061904}, - {93440299, 68061904}, - {88732002, 68255996}, - {88627304, 68298500}, - {88111396, 68541900}, - {86393898, 69555404}, - {86138298, 69706695}, - {85871704, 69913200}, - {85387199, 70393402}, - {79854499, 76783203}, - {79209701, 77649398}, - {79108505, 78072502}, - {79090698, 78472198}, - {79090698, 116426399}, - }, - { - {90956314, 84639938}, - {91073814, 85141891}, - {91185752, 85505371}, - {109815368, 137196487}, - {110342590, 138349899}, - {110388549, 138447540}, - {110652862, 138971343}, - {110918045, 139341140}, - {114380859, 143159042}, - {114446723, 143220352}, - {114652198, 143392166}, - {114712196, 143437301}, - {114782165, 143476028}, - {114873054, 143514923}, - {115217086, 143660934}, - {115306060, 143695526}, - {115344009, 143707580}, - {115444541, 143737747}, - {115589378, 143779937}, - {115751358, 143823989}, - {115802780, 143825820}, - {116872810, 143753616}, - {116927055, 143744644}, - {154690734, 133504180}, - {155009704, 133371856}, - {155029907, 133360061}, - {155089141, 133323181}, - {155342315, 133163360}, - {155602294, 132941406}, - {155669158, 132880294}, - {155821624, 132737884}, - {155898986, 132656890}, - {155934936, 132608932}, - {155968627, 132562713}, - {156062896, 132431808}, - {156111694, 132363174}, - {156148147, 132297180}, - {158738342, 127281066}, - {159026672, 126378631}, - {159073699, 125806335}, - {159048522, 125299743}, - {159040313, 125192901}, - {158898300, 123934677}, - {149829376, 70241508}, - {149763031, 69910629}, - {149684692, 69628723}, - {149557800, 69206214}, - {149366485, 68864326}, - {149137390, 68578514}, - {148637466, 68048767}, - {147027725, 66632934}, - {146228607, 66257507}, - {146061309, 66184646}, - {146017929, 66174186}, - {145236465, 66269500}, - {144802490, 66345039}, - {144673995, 66376220}, - {93732284, 79649864}, - {93345336, 79785865}, - {93208084, 79840286}, - {92814521, 79997779}, - {92591087, 80098968}, - {92567016, 80110511}, - {92032684, 80860725}, - {91988853, 80930152}, - {91471725, 82210029}, - {91142349, 83076683}, - {90969284, 83653182}, - {90929664, 84043212}, - {90926315, 84325256}, - {90956314, 84639938}, - }, - { - {114758499, 88719909}, - {114771591, 88860549}, - {115515533, 94195907}, - {115559539, 94383651}, - {119882980, 109502059}, - {120660522, 111909683}, - {126147735, 124949630}, - {127127212, 127107215}, - {129976379, 132117279}, - {130754470, 133257080}, - {130820968, 133340835}, - {130889312, 133423858}, - {131094787, 133652832}, - {131257629, 133828247}, - {131678619, 134164276}, - {131791107, 134248901}, - {131969482, 134335189}, - {132054107, 134373718}, - {132927368, 134701141}, - {133077072, 134749313}, - {133196075, 134785705}, - {133345230, 134804351}, - {133498809, 134809051}, - {133611541, 134797607}, - {134621170, 134565322}, - {134741165, 134527511}, - {134892089, 134465240}, - {135071212, 134353820}, - {135252029, 134185821}, - {135384979, 134003631}, - {135615585, 133576675}, - {135793029, 132859008}, - {135890228, 131382904}, - {135880828, 131261657}, - {135837570, 130787963}, - {135380661, 127428909}, - {132830596, 109495368}, - {132815826, 109411666}, - {132765869, 109199302}, - {132724380, 109068161}, - {127490066, 93353515}, - {125330810, 87852828}, - {125248336, 87647026}, - {125002182, 87088424}, - {124894592, 86872482}, - {121007278, 80019584}, - {120962829, 79941261}, - {120886489, 79833923}, - {120154983, 78949615}, - {119366561, 78111709}, - {119014755, 77776794}, - {116728790, 75636238}, - {116660522, 75593933}, - {116428192, 75458541}, - {116355255, 75416870}, - {116264663, 75372528}, - {115952728, 75233367}, - {115865554, 75205482}, - {115756835, 75190956}, - {115564163, 75197830}, - {115481170, 75202087}, - {115417144, 75230400}, - {115226959, 75337806}, - {115203842, 75351448}, - {114722015, 75746932}, - {114672103, 75795661}, - {114594619, 75891891}, - {114565811, 75973831}, - {114478256, 76240814}, - {114178039, 77252197}, - {114137664, 77769668}, - {114109771, 78154464}, - {114758499, 88719909}, - }, - { - {108135070, 109828002}, - {108200347, 110091529}, - {108319419, 110298500}, - {108439025, 110488388}, - {108663574, 110766731}, - {108812957, 110935768}, - {109321914, 111398925}, - {109368087, 111430320}, - {109421295, 111466331}, - {110058998, 111849746}, - {127160308, 120588981}, - {127350692, 120683456}, - {128052749, 120997207}, - {128326919, 121113449}, - {131669586, 122213058}, - {131754745, 122240592}, - {131854583, 122264770}, - {132662048, 122449813}, - {132782669, 122449897}, - {132909118, 122443687}, - {133013442, 122436058}, - {140561035, 121609939}, - {140786346, 121583320}, - {140876144, 121570228}, - {140962356, 121547996}, - {141052612, 121517837}, - {141231292, 121442184}, - {141309371, 121390007}, - {141370132, 121327003}, - {141456008, 121219932}, - {141591598, 121045005}, - {141905761, 120634796}, - {141894607, 120305725}, - {141881881, 120110855}, - {141840881, 119885009}, - {141685043, 119238922}, - {141617416, 118962882}, - {141570434, 118858856}, - {131617462, 100598548}, - {131542846, 100487213}, - {131229385, 100089019}, - {131091476, 99928108}, - {119824127, 90297180}, - {119636337, 90142387}, - {119507492, 90037765}, - {119436744, 89983657}, - {119423942, 89974159}, - {119207366, 89822471}, - {119117149, 89767097}, - {119039489, 89726867}, - {116322929, 88522857}, - {114817031, 87882110}, - {114683975, 87826751}, - {114306411, 87728507}, - {113876434, 87646003}, - {113792106, 87629974}, - {113658988, 87615974}, - {113574333, 87609275}, - {112813575, 87550102}, - {112578567, 87560157}, - {112439880, 87571647}, - {112306922, 87599395}, - {112225082, 87622535}, - {112132568, 87667175}, - {112103477, 87682830}, - {110795242, 88511634}, - {110373565, 88847793}, - {110286537, 88934989}, - {109730873, 89531501}, - {109648735, 89628883}, - {109552581, 89768859}, - {109514228, 89838470}, - {109501640, 89877586}, - {109480964, 89941864}, - {109461761, 90032417}, - {109457778, 90055458}, - {108105194, 109452575}, - {108094238, 109620979}, - {108135070, 109828002}, - }, - { - {108764694, 108910400}, - {108965499, 112306495}, - {109598602, 120388298}, - {110573898, 128289596}, - {110597801, 128427795}, - {113786201, 137983795}, - {113840301, 138134704}, - {113937202, 138326904}, - {114046005, 138520401}, - {114150802, 138696792}, - {114164703, 138717895}, - {114381896, 139021194}, - {114701004, 139425292}, - {114997398, 139747497}, - {115065597, 139805191}, - {115134498, 139850891}, - {115167098, 139871704}, - {115473396, 139992797}, - {115537498, 139995101}, - {116762596, 139832000}, - {116897499, 139808593}, - {118401802, 139225585}, - {118437500, 139209594}, - {118488204, 139182189}, - {118740097, 139033996}, - {118815795, 138967285}, - {134401000, 116395492}, - {134451507, 116309997}, - {135488098, 113593597}, - {137738006, 106775695}, - {140936492, 97033889}, - {140960006, 96948997}, - {141026504, 96660995}, - {141067291, 96467094}, - {141124893, 95771896}, - {141511795, 90171600}, - {141499801, 90026000}, - {141479598, 89907798}, - {141276794, 88844596}, - {141243804, 88707397}, - {140778305, 87031593}, - {140733306, 86871696}, - {140697204, 86789993}, - {140619796, 86708190}, - {140398391, 86487396}, - {125798797, 72806198}, - {125415802, 72454498}, - {123150398, 70566093}, - {123038803, 70503997}, - {122681198, 70305397}, - {121919204, 70104797}, - {121533699, 70008094}, - {121273696, 70004898}, - {121130599, 70020797}, - {121045097, 70033294}, - {120847099, 70082298}, - {120481895, 70278999}, - {120367004, 70379692}, - {120272796, 70475097}, - {119862098, 71004791}, - {119745101, 71167297}, - {119447799, 71726997}, - {119396499, 71825798}, - {119348701, 71944496}, - {109508796, 98298797}, - {109368598, 98700897}, - {109298400, 98926391}, - {108506301, 102750991}, - {108488197, 102879898}, - {108764694, 108910400}, - }, - { - {106666252, 87231246}, - {106673248, 87358055}, - {107734146, 101975646}, - {107762649, 102357955}, - {108702445, 111208351}, - {108749450, 111345153}, - {108848350, 111542648}, - {110270645, 114264358}, - {110389648, 114445144}, - {138794845, 143461151}, - {139048355, 143648956}, - {139376144, 143885345}, - {139594451, 144022644}, - {139754043, 144110046}, - {139923950, 144185852}, - {140058242, 144234451}, - {140185653, 144259552}, - {140427551, 144292648}, - {141130950, 144281448}, - {141157653, 144278152}, - {141214355, 144266555}, - {141347457, 144223449}, - {141625350, 144098953}, - {141755142, 144040145}, - {141878143, 143971557}, - {142011444, 143858154}, - {142076843, 143796356}, - {142160644, 143691055}, - {142224456, 143560852}, - {142925842, 142090850}, - {142935653, 142065353}, - {142995956, 141899154}, - {143042556, 141719757}, - {143102951, 141436157}, - {143129257, 141230453}, - {143316055, 139447250}, - {143342544, 133704650}, - {143307556, 130890960}, - {142461257, 124025558}, - {141916046, 120671051}, - {141890457, 120526153}, - {140002349, 113455749}, - {139909149, 113144149}, - {139853454, 112974456}, - {137303756, 105228057}, - {134700546, 98161254}, - {134617950, 97961547}, - {133823547, 96118057}, - {133688751, 95837356}, - {133481353, 95448059}, - {133205444, 94948150}, - {131178955, 91529853}, - {131144744, 91482055}, - {113942047, 67481246}, - {113837051, 67360549}, - {113048950, 66601745}, - {112305549, 66002746}, - {112030853, 65790351}, - {111970649, 65767547}, - {111912445, 65755249}, - {111854248, 65743453}, - {111657447, 65716354}, - {111576950, 65707351}, - {111509750, 65708549}, - {111443550, 65718551}, - {111397247, 65737449}, - {111338546, 65764648}, - {111129547, 65863349}, - {111112449, 65871551}, - {110995254, 65927856}, - {110968849, 65946151}, - {110941444, 65966751}, - {110836448, 66057853}, - {110490447, 66445449}, - {110404144, 66576751}, - {106802055, 73202148}, - {106741950, 73384948}, - {106715454, 73469650}, - {106678054, 73627151}, - {106657455, 75433448}, - {106666252, 87231246}, - }, - { - {101852752, 106261352}, - {101868949, 106406051}, - {102347549, 108974250}, - {112286750, 152027954}, - {112305648, 152106536}, - {112325752, 152175857}, - {112391448, 152290863}, - {113558250, 154187454}, - {113592048, 154226745}, - {113694351, 154313156}, - {113736549, 154335647}, - {113818145, 154367462}, - {114284454, 154490951}, - {114415847, 154504547}, - {114520751, 154489151}, - {114571350, 154478057}, - {114594551, 154472854}, - {114630546, 154463958}, - {114715148, 154429443}, - {146873657, 136143051}, - {146941741, 136074249}, - {147190155, 135763549}, - {147262649, 135654937}, - {147309951, 135557159}, - {147702255, 133903945}, - {147934143, 131616348}, - {147967041, 131273864}, - {148185852, 127892250}, - {148195648, 127669754}, - {148179656, 126409851}, - {148119552, 126182151}, - {147874053, 125334152}, - {147818954, 125150352}, - {146958557, 122656646}, - {139070251, 101025955}, - {139002655, 100879051}, - {119028450, 63067649}, - {118846649, 62740753}, - {115676048, 57814651}, - {115550453, 57629852}, - {115330352, 57319751}, - {115094749, 56998352}, - {114978347, 56847454}, - {114853050, 56740550}, - {114695053, 56609550}, - {114582252, 56528148}, - {114210449, 56375953}, - {113636245, 56214950}, - {113470352, 56171649}, - {109580749, 55503551}, - {109491645, 55495452}, - {109238754, 55511550}, - {109080352, 55534049}, - {108027748, 55687351}, - {107839950, 55732349}, - {107614456, 55834953}, - {107488143, 55925952}, - {107302551, 56062553}, - {107218353, 56145751}, - {107199447, 56167251}, - {107052749, 56354850}, - {106978652, 56476348}, - {106869644, 56710754}, - {104541351, 62448753}, - {104454551, 62672554}, - {104441253, 62707351}, - {104231750, 63366348}, - {104222648, 63419952}, - {104155746, 63922649}, - {104127349, 64147552}, - {104110847, 64299957}, - {102235450, 92366752}, - {101804351, 102877655}, - {101852752, 106261352}, - }, - { - {106808700, 120885696}, - {106818695, 120923103}, - {106873901, 121057098}, - {115123603, 133614700}, - {115128799, 133619598}, - {115182197, 133661804}, - {115330101, 133740707}, - {115455398, 133799407}, - {115595001, 133836807}, - {115651000, 133851806}, - {116413604, 134055206}, - {116654495, 134097900}, - {116887603, 134075210}, - {117071098, 134040405}, - {117458801, 133904891}, - {118057998, 133572601}, - {118546997, 133261001}, - {118578498, 133239395}, - {118818603, 133011596}, - {121109695, 130501495}, - {122661598, 128760101}, - {142458190, 102765197}, - {142789001, 102099601}, - {143105010, 101386505}, - {143154800, 101239700}, - {143193908, 100825500}, - {143160507, 100282501}, - {143133499, 100083602}, - {143092697, 99880500}, - {143050689, 99766700}, - {142657501, 98974502}, - {142580307, 98855201}, - {122267196, 76269897}, - {122036399, 76105003}, - {121832000, 76028305}, - {121688796, 75983108}, - {121591598, 75955001}, - {121119697, 75902099}, - {120789596, 75953498}, - {120487495, 76041900}, - {120042701, 76365798}, - {119886695, 76507301}, - {119774200, 76635299}, - {119739097, 76686904}, - {119685195, 76798202}, - {119456199, 77320098}, - {106877601, 119561401}, - {106854797, 119645103}, - {106849098, 119668807}, - {106847099, 119699005}, - {106840400, 119801406}, - {106807800, 120719299}, - {106806098, 120862808}, - {106808700, 120885696}, - }, - { - {99663352, 105328948}, - {99690048, 105797050}, - {99714050, 105921447}, - {99867248, 106439949}, - {100111557, 107256546}, - {104924850, 120873649}, - {105106155, 121284049}, - {105519149, 122184753}, - {105586051, 122292655}, - {105665054, 122400154}, - {106064147, 122838455}, - {106755355, 123453453}, - {106929054, 123577651}, - {107230346, 123771949}, - {107760650, 123930648}, - {108875854, 124205154}, - {108978752, 124228050}, - {131962051, 123738754}, - {135636047, 123513954}, - {135837249, 123500747}, - {136357345, 123442749}, - {136577346, 123394454}, - {136686645, 123367752}, - {137399353, 123185050}, - {137733947, 123063156}, - {137895355, 122997154}, - {138275650, 122829154}, - {138394256, 122767753}, - {138516845, 122670150}, - {139987045, 121111251}, - {149171646, 108517349}, - {149274353, 108372848}, - {149314758, 108314247}, - {149428848, 108140846}, - {149648651, 107650550}, - {149779541, 107290252}, - {149833343, 107115249}, - {149891357, 106920051}, - {150246353, 105630249}, - {150285842, 105423454}, - {150320953, 105233749}, - {150336639, 104981552}, - {150298049, 104374053}, - {150287948, 104271850}, - {150026153, 103481147}, - {149945449, 103301651}, - {149888946, 103213455}, - {149800949, 103103851}, - {149781143, 103079650}, - {149714141, 103005447}, - {149589950, 102914146}, - {149206054, 102698951}, - {128843856, 91378150}, - {128641754, 91283050}, - {119699851, 87248046}, - {117503555, 86311950}, - {117145851, 86178054}, - {116323654, 85925048}, - {115982551, 85834045}, - {115853050, 85819252}, - {115222549, 85771949}, - {107169357, 85771949}, - {107122650, 85776451}, - {106637145, 85831550}, - {105095046, 86423950}, - {104507850, 86703750}, - {104384155, 86763153}, - {104332351, 86790145}, - {104198257, 86882644}, - {103913757, 87109451}, - {103592346, 87388450}, - {103272651, 87666748}, - {103198051, 87779052}, - {101698654, 90600952}, - {101523551, 90958450}, - {101360054, 91347450}, - {101295349, 91542144}, - {99774551, 98278152}, - {99746749, 98417755}, - {99704055, 98675453}, - {99663352, 99022949}, - {99663352, 105328948}, - }, - { - {95036499, 101778106}, - {95479103, 102521301}, - {95587295, 102700103}, - {98306503, 106984901}, - {98573303, 107377700}, - {100622406, 110221702}, - {101252304, 111089599}, - {104669502, 115750198}, - {121838500, 131804107}, - {122000503, 131943695}, - {122176803, 132023406}, - {122474105, 132025390}, - {122703804, 132023101}, - {123278808, 131878112}, - {124072998, 131509109}, - {124466506, 131102508}, - {152779296, 101350906}, - {153016510, 101090606}, - {153269699, 100809097}, - {153731994, 100214096}, - {153927902, 99939796}, - {154641098, 98858100}, - {154864303, 98517601}, - {155056594, 97816604}, - {155083511, 97645599}, - {155084899, 97462097}, - {154682601, 94386100}, - {154376007, 92992599}, - {154198593, 92432403}, - {153830505, 91861701}, - {153686904, 91678695}, - {151907104, 90314605}, - {151368896, 89957603}, - {146983306, 87632202}, - {139082397, 84273605}, - {128947692, 80411399}, - {121179000, 78631301}, - {120264701, 78458198}, - {119279510, 78304603}, - {116913101, 77994102}, - {116151504, 77974601}, - {115435104, 78171401}, - {113544105, 78709106}, - {113231002, 78879898}, - {112726303, 79163604}, - {112310501, 79411102}, - {96169998, 97040802}, - {95196304, 98364402}, - {95167800, 98409599}, - {95083503, 98570701}, - {94986999, 99022201}, - {94915100, 100413299}, - {95036499, 101778106}, - }, - { - {82601348, 96004745}, - {83443847, 128861953}, - {84173248, 136147354}, - {104268249, 141388839}, - {104373649, 141395355}, - {105686950, 141389541}, - {149002243, 140435653}, - {159095748, 133388244}, - {159488143, 133112655}, - {159661849, 132894653}, - {163034149, 128290847}, - {164801849, 124684249}, - {167405746, 72553245}, - {167330444, 71960746}, - {167255050, 71791847}, - {167147155, 71572044}, - {166999557, 71341545}, - {166723937, 70961448}, - {166238250, 70611541}, - {165782348, 70359649}, - {165649444, 70286849}, - {165332946, 70122344}, - {165164154, 70062248}, - {164879150, 69967544}, - {164744949, 69928947}, - {164691452, 69915245}, - {164669448, 69910247}, - {159249938, 68738952}, - {158528259, 68704742}, - {147564254, 68604644}, - {116196655, 68982742}, - {115364944, 69005050}, - {115193145, 69013549}, - {101701248, 70984146}, - {93918449, 72233047}, - {93789749, 72285247}, - {93777046, 72292648}, - {93586044, 72444046}, - {93366348, 72662345}, - {93301147, 72745452}, - {93260345, 72816345}, - {83523948, 92593849}, - {83430145, 92810241}, - {82815048, 94665542}, - {82755554, 94858551}, - {82722953, 95014350}, - {82594253, 95682350}, - {82601348, 96004745}, - }, - { - {110371345, 125796493}, - {110411544, 126159599}, - {110445251, 126362899}, - {111201950, 127863800}, - {112030052, 129270492}, - {112367050, 129799301}, - {113088348, 130525604}, - {113418144, 130853698}, - {117363449, 134705505}, - {118131149, 135444793}, - {118307449, 135607299}, - {119102546, 136297195}, - {119385047, 136531906}, - {120080848, 137094390}, - {120794845, 137645401}, - {121150344, 137896392}, - {121528945, 138162506}, - {121644546, 138242095}, - {122142349, 138506408}, - {127540847, 141363006}, - {127933448, 141516204}, - {128728256, 141766799}, - {129877151, 141989898}, - {130626052, 142113891}, - {130912246, 142135192}, - {131246841, 142109100}, - {131496047, 142027404}, - {131596252, 141957794}, - {131696350, 141873504}, - {131741043, 141803405}, - {138788452, 128037704}, - {139628646, 125946197}, - {138319351, 112395401}, - {130035354, 78066703}, - {124174049, 69908798}, - {123970649, 69676895}, - {123874252, 69571899}, - {123246643, 68961303}, - {123193954, 68924400}, - {121952049, 68110000}, - {121787345, 68021896}, - {121661544, 67970306}, - {121313446, 67877502}, - {121010650, 67864799}, - {120995346, 67869705}, - {120583747, 68122207}, - {120509750, 68170600}, - {120485847, 68189102}, - {112160148, 77252403}, - {111128646, 78690704}, - {110969650, 78939407}, - {110512550, 79663406}, - {110397247, 79958206}, - {110371345, 80038299}, - {110371345, 125796493}, - }, - { - {112163948, 137752700}, - {112171150, 137837997}, - {112203048, 137955993}, - {112240150, 138008209}, - {112343246, 138111099}, - {112556243, 138223205}, - {112937149, 138307998}, - {113318748, 138331909}, - {126076446, 138428298}, - {126165245, 138428695}, - {126312446, 138417907}, - {134075546, 136054504}, - {134322753, 135949401}, - {134649948, 135791198}, - {135234954, 135493408}, - {135290145, 135464691}, - {135326248, 135443695}, - {135920043, 135032592}, - {135993850, 134975799}, - {136244247, 134761199}, - {136649444, 134378692}, - {137067153, 133964294}, - {137188156, 133839096}, - {137298049, 133704498}, - {137318954, 133677795}, - {137413543, 133522201}, - {137687347, 133043792}, - {137816055, 132660705}, - {137836044, 131747695}, - {137807144, 131318603}, - {136279342, 119078704}, - {136249053, 118945800}, - {127306152, 81348602}, - {127114852, 81065505}, - {127034248, 80951400}, - {126971649, 80893707}, - {125093551, 79178001}, - {124935745, 79036003}, - {115573745, 71767601}, - {115411148, 71701805}, - {115191947, 71621002}, - {115017051, 71571304}, - {114870147, 71572898}, - {113869552, 71653900}, - {112863349, 72976104}, - {112756347, 73223899}, - {112498947, 73832206}, - {112429351, 73998504}, - {112366050, 74168098}, - {112273246, 74487098}, - {112239250, 74605400}, - {112195549, 74899902}, - {112163948, 75280700}, - {112163948, 137752700}, - }, - { - {78562347, 141451843}, - {79335624, 142828186}, - {79610343, 143188140}, - {79845077, 143445724}, - {81379173, 145126678}, - {81826751, 145577178}, - {82519126, 146209472}, - {83964973, 147280502}, - {85471343, 148377868}, - {86115539, 148760803}, - {88839988, 150281188}, - {89021247, 150382217}, - {90775917, 151320526}, - {91711380, 151767288}, - {92757591, 152134277}, - {93241058, 152201766}, - {113402145, 153091995}, - {122065994, 146802825}, - {164111053, 91685104}, - {164812759, 90470565}, - {165640182, 89037384}, - {171027435, 66211853}, - {171450805, 64406951}, - {171463150, 64349624}, - {171469787, 64317184}, - {171475585, 64282028}, - {171479812, 64253036}, - {171483596, 64210433}, - {171484405, 64153488}, - {171483001, 64140785}, - {171481719, 64132751}, - {171478668, 64115478}, - {171472702, 64092437}, - {171462768, 64075408}, - {171448089, 64061347}, - {171060333, 63854789}, - {169640502, 63197738}, - {169342147, 63086711}, - {166413101, 62215766}, - {151881774, 58826736}, - {146010574, 57613151}, - {141776962, 56908004}, - {140982940, 57030628}, - {139246154, 57540817}, - {139209609, 57566974}, - {127545310, 66015594}, - {127476654, 66104812}, - {105799087, 98784980}, - {85531921, 129338897}, - {79319717, 138704513}, - {78548156, 140188079}, - {78530448, 140530456}, - {78515594, 141299987}, - {78562347, 141451843}, - }, - { - {77755004, 128712387}, - {78073547, 130552612}, - {78433593, 132017822}, - {79752693, 136839645}, - {80479461, 138929260}, - {80903221, 140119674}, - {81789848, 141978454}, - {82447387, 143105575}, - {83288436, 144264328}, - {84593582, 145846542}, - {84971939, 146242813}, - {86905578, 147321304}, - {87874191, 147594131}, - {89249092, 147245132}, - {89541542, 147169052}, - {98759140, 144071609}, - {98894233, 144024261}, - {113607818, 137992843}, - {128324356, 131649307}, - {139610076, 126210189}, - {146999572, 122112884}, - {147119415, 122036041}, - {148717330, 120934616}, - {149114776, 120652725}, - {171640289, 92086624}, - {171677917, 92036224}, - {171721191, 91973869}, - {171851608, 91721557}, - {171927795, 91507644}, - {172398696, 89846351}, - {172436752, 89559959}, - {169361663, 64753852}, - {169349029, 64687164}, - {169115127, 63616458}, - {168965728, 63218254}, - {168911788, 63121219}, - {168901611, 63106807}, - {168896896, 63100486}, - {168890686, 63092460}, - {168876586, 63081058}, - {168855529, 63067909}, - {168808746, 63046024}, - {167251068, 62405864}, - {164291717, 63716899}, - {152661651, 69910156}, - {142312393, 75421356}, - {78778053, 111143295}, - {77887222, 113905914}, - {77591979, 124378433}, - {77563247, 126586669}, - {77755004, 128712387}, - }, - { - {105954101, 131182754}, - {105959197, 131275848}, - {105972801, 131473556}, - {105981498, 131571044}, - {106077903, 132298553}, - {106134094, 132715255}, - {106155700, 132832351}, - {106180099, 132942657}, - {106326797, 133590347}, - {106375099, 133719345}, - {106417602, 133829345}, - {106471000, 133930343}, - {106707901, 134308654}, - {106728401, 134340545}, - {106778198, 134417556}, - {106832397, 134491851}, - {106891296, 134562957}, - {106981300, 134667358}, - {107044204, 134736557}, - {107111000, 134802658}, - {107180999, 134865661}, - {107291099, 134961349}, - {107362998, 135020355}, - {107485397, 135112854}, - {107558998, 135166946}, - {107690399, 135256256}, - {107765098, 135305252}, - {107903594, 135390548}, - {108183898, 135561843}, - {108459503, 135727951}, - {108532501, 135771850}, - {108796096, 135920059}, - {108944099, 135972549}, - {109102401, 136010757}, - {109660598, 136071044}, - {109971595, 136100250}, - {110209594, 136116851}, - {110752799, 136122344}, - {111059906, 136105758}, - {111152900, 136100357}, - {111237197, 136091354}, - {111316101, 136075057}, - {111402000, 136050949}, - {111475296, 136026657}, - {143546600, 123535949}, - {143899002, 122454353}, - {143917404, 122394348}, - {143929199, 122354652}, - {143944793, 122295753}, - {143956207, 122250953}, - {143969497, 122192253}, - {143980102, 122143249}, - {143991302, 122083053}, - {144000396, 122031753}, - {144009796, 121970954}, - {144017303, 121917655}, - {144025405, 121850250}, - {144030609, 121801452}, - {144036804, 121727455}, - {144040008, 121683456}, - {144043502, 121600952}, - {144044708, 121565048}, - {144045700, 121470352}, - {144045898, 121446952}, - {144041503, 121108657}, - {144037506, 121023452}, - {143733795, 118731750}, - {140461395, 95238647}, - {140461105, 95236755}, - {140433807, 95115249}, - {140392608, 95011650}, - {134840805, 84668952}, - {134824996, 84642456}, - {134781494, 84572952}, - {134716796, 84480850}, - {127473899, 74425453}, - {127467002, 74417152}, - {127431701, 74381652}, - {127402603, 74357147}, - {127375503, 74334457}, - {127294906, 74276649}, - {127181900, 74207649}, - {127177597, 74205451}, - {127123901, 74178451}, - {127078903, 74155853}, - {127028999, 74133148}, - {126870803, 74070953}, - {126442901, 73917648}, - {126432403, 73914955}, - {126326004, 73889846}, - {126262405, 73880645}, - {126128097, 73878456}, - {125998199, 73877655}, - {108701095, 74516647}, - {108644599, 74519348}, - {108495201, 74528953}, - {108311302, 74556457}, - {108252799, 74569458}, - {108079002, 74612152}, - {107981399, 74638954}, - {107921295, 74657951}, - {107862197, 74685951}, - {107601303, 74828948}, - {107546997, 74863449}, - {107192794, 75098846}, - {107131202, 75151153}, - {106260002, 76066146}, - {106195098, 76221145}, - {106168502, 76328453}, - {106144699, 76437454}, - {106124496, 76538452}, - {106103698, 76649650}, - {106084197, 76761650}, - {106066299, 76874450}, - {106049903, 76987457}, - {106034797, 77101150}, - {106020904, 77214950}, - {106008201, 77328948}, - {105996902, 77443145}, - {105986099, 77565849}, - {105977005, 77679649}, - {105969299, 77793151}, - {105963096, 77906349}, - {105958297, 78019149}, - {105955299, 78131454}, - {105954101, 78242950}, - {105954101, 131182754}, - }, - { - {91355499, 77889205}, - {114834197, 120804504}, - {114840301, 120815200}, - {124701507, 132324798}, - {124798805, 132436706}, - {124901504, 132548309}, - {125126602, 132788909}, - {125235000, 132901901}, - {125337707, 133005401}, - {125546302, 133184707}, - {125751602, 133358703}, - {126133300, 133673004}, - {126263900, 133775604}, - {126367401, 133855499}, - {126471908, 133935104}, - {126596008, 134027496}, - {127119308, 134397094}, - {127135101, 134408203}, - {127433609, 134614303}, - {127554107, 134695709}, - {128155395, 135070907}, - {128274505, 135141799}, - {129132003, 135573211}, - {129438003, 135713195}, - {129556106, 135767196}, - {131512695, 136648498}, - {132294509, 136966598}, - {132798400, 137158798}, - {133203796, 137294494}, - {133377410, 137350799}, - {133522399, 137396606}, - {133804397, 137480697}, - {134017807, 137542205}, - {134288696, 137618408}, - {134564208, 137680099}, - {134844696, 137740097}, - {135202606, 137807098}, - {135489105, 137849807}, - {135626800, 137864898}, - {135766906, 137878692}, - {135972808, 137895797}, - {136110107, 137905502}, - {136235000, 137913101}, - {136485809, 137907196}, - {139194305, 136979202}, - {140318298, 136536209}, - {140380004, 136505004}, - {140668197, 136340499}, - {140724304, 136298904}, - {140808197, 136228210}, - {140861801, 136180603}, - {140917404, 136129104}, - {140979202, 136045104}, - {141022903, 135984207}, - {147591094, 126486999}, - {147661315, 126356101}, - {147706100, 126261901}, - {147749099, 126166000}, - {147817108, 126007507}, - {147859100, 125908599}, - {153693206, 111901100}, - {153731109, 111807800}, - {153760894, 111698806}, - {158641998, 92419303}, - {158644500, 92263702}, - {158539703, 92013504}, - {158499603, 91918899}, - {158335510, 91626800}, - {158264007, 91516304}, - {158216308, 91449203}, - {158178314, 91397506}, - {158094299, 91283203}, - {157396408, 90368202}, - {157285491, 90224700}, - {157169906, 90079200}, - {157050003, 89931304}, - {156290603, 89006805}, - {156221099, 88922897}, - {156087707, 88771003}, - {155947906, 88620498}, - {155348602, 88004203}, - {155113204, 87772796}, - {154947296, 87609703}, - {154776306, 87448204}, - {154588806, 87284301}, - {153886306, 86716400}, - {153682403, 86560501}, - {152966705, 86032402}, - {152687805, 85828704}, - {152484313, 85683204}, - {152278808, 85539001}, - {150878204, 84561401}, - {150683013, 84426498}, - {150599395, 84372703}, - {150395599, 84243202}, - {149988906, 83989395}, - {149782897, 83864501}, - {149568908, 83739799}, - {148872100, 83365303}, - {148625396, 83242202}, - {128079010, 73079605}, - {127980506, 73031005}, - {126701103, 72407104}, - {126501701, 72312202}, - {126431503, 72280601}, - {126311706, 72230606}, - {126260101, 72210899}, - {126191902, 72187599}, - {126140106, 72170303}, - {126088203, 72155303}, - {126036102, 72142700}, - {125965904, 72126899}, - {125913009, 72116600}, - {125859603, 72108505}, - {125788101, 72100296}, - {125733505, 72094398}, - {125678100, 72090400}, - {125621398, 72088302}, - {125548805, 72087303}, - {125490707, 72086898}, - {125430908, 72088203}, - {125369804, 72091094}, - {125306900, 72095306}, - {125233505, 72100997}, - {125168609, 72106506}, - {125102203, 72113601}, - {125034103, 72122207}, - {124964309, 72132095}, - {124890701, 72143707}, - {124819305, 72155105}, - {91355499, 77889099}, - {91355499, 77889205}, - }, - { - {84531845, 127391708}, - {84916946, 130417510}, - {86133247, 131166900}, - {86338447, 131292892}, - {86748847, 131544799}, - {102193946, 136599502}, - {103090942, 136796798}, - {103247146, 136822509}, - {104083549, 136911499}, - {106119346, 137109802}, - {106265853, 137122207}, - {106480247, 137139205}, - {110257850, 137133605}, - {116917747, 136131408}, - {117054946, 136106704}, - {119043945, 135244293}, - {119249046, 135154708}, - {136220947, 126833007}, - {165896347, 91517105}, - {166032546, 91314697}, - {166055435, 91204902}, - {166056152, 91176803}, - {166047256, 91100006}, - {166039733, 91063705}, - {165814849, 90080802}, - {165736450, 89837707}, - {165677246, 89732101}, - {165676956, 89731803}, - {165560241, 89629302}, - {154419952, 82608505}, - {153822143, 82239700}, - {137942749, 74046104}, - {137095245, 73845504}, - {135751342, 73537704}, - {134225952, 73208602}, - {132484344, 72860801}, - {124730346, 73902000}, - {120736549, 74464401}, - {100401245, 78685401}, - {90574645, 90625701}, - {90475944, 90748809}, - {90430747, 90808700}, - {90321548, 90958305}, - {90254852, 91077903}, - {90165641, 91244003}, - {90134941, 91302398}, - {84474647, 103745697}, - {84328048, 104137901}, - {84288543, 104327606}, - {84038047, 106164604}, - {84013351, 106368698}, - {83943847, 110643203}, - {84531845, 127391708}, - }, -}; - -const TestDataEx PRINTER_PART_POLYGONS_EX = -{ - { - { - {533726562, 142141690}, - {532359712, 143386134}, - {530141290, 142155145}, - {528649729, 160091460}, - {533659500, 157607547}, - {538669739, 160091454}, - {537178168, 142155145}, - {534959534, 143386102}, - {533726562, 142141690}, - }, - { - }, - }, - { - { - {118305840, 11603332}, - {118311095, 26616786}, - {113311095, 26611146}, - {109311095, 29604752}, - {109300760, 44608489}, - {109311095, 49631801}, - {113300790, 52636806}, - {118311095, 52636806}, - {118308782, 103636810}, - {223830940, 103636981}, - {236845321, 90642174}, - {236832882, 11630488}, - {232825251, 11616786}, - {210149075, 11616786}, - {211308596, 13625149}, - {209315325, 17080886}, - {205326885, 17080886}, - {203334352, 13629720}, - {204493136, 11616786}, - {118305840, 11603332}, - }, - { - }, - }, - { - { - {365619370, 111280336}, - {365609100, 198818091}, - {387109100, 198804367}, - {387109100, 203279701}, - {471129120, 203279688}, - {471128689, 111283937}, - {365619370, 111280336}, - }, - { - }, - }, - { - { - {479997525, 19177632}, - {477473010, 21975778}, - {475272613, 21969219}, - {475267479, 32995796}, - {477026388, 32995796}, - {483041428, 22582411}, - {482560272, 20318630}, - {479997525, 19177632}, - }, - { - }, - }, - { - { - {476809080, 4972372}, - {475267479, 4975778}, - {475272613, 16002357}, - {481018177, 18281994}, - {482638044, 15466085}, - {476809080, 4972372}, - }, - { - }, - }, - { - { - {424866064, 10276075}, - {415113411, 10277960}, - {411723180, 13685293}, - {410473354, 18784347}, - {382490868, 18784008}, - {380996185, 17286945}, - {380996185, 11278161}, - {375976165, 11284347}, - {375976165, 56389754}, - {375169018, 57784347}, - {371996185, 57784347}, - {371996185, 53779177}, - {364976165, 53784347}, - {364969637, 56791976}, - {369214608, 61054367}, - {371474507, 61054367}, - {371473155, 98298160}, - {378476349, 105317193}, - {407491306, 105307497}, - {413509785, 99284903}, - {413496185, 48304367}, - {419496173, 48315719}, - {422501887, 45292801}, - {422500504, 39363184}, - {420425079, 37284347}, - {419476165, 43284347}, - {413496185, 43284347}, - {413497261, 30797428}, - {418986175, 25308513}, - {424005230, 25315076}, - {428496185, 20815924}, - {428512720, 13948847}, - {424866064, 10276075}, - }, - { - }, - }, - { - { - {723893066, 37354349}, - {717673034, 37370791}, - {717673034, 44872138}, - {715673034, 44867768}, - {715673034, 46055353}, - {699219526, 40066777}, - {697880758, 37748547}, - {691985477, 37748293}, - {689014018, 42869257}, - {691985477, 48016003}, - {697575093, 48003007}, - {715671494, 54589493}, - {715656800, 87142158}, - {759954611, 87142158}, - {764193054, 82897328}, - {764193054, 79872138}, - {757173034, 79866968}, - {757173034, 83872138}, - {754419422, 83869509}, - {753193054, 81739327}, - {753193054, 37360571}, - {723893066, 37354349}, - }, - { - }, - }, - { - { - {85607478, 4227596}, - {61739211, 4230337}, - {61739211, 13231393}, - {58725066, 13231405}, - {58721589, 27731406}, - {58738375, 30262521}, - {61739211, 30251413}, - {61736212, 38251411}, - {70759231, 38254724}, - {70905600, 33317391}, - {73749222, 31251468}, - {76592843, 33317393}, - {76739211, 38254516}, - {86765007, 38251411}, - {86759599, 4231393}, - {85607478, 4227596}, - }, - { - }, - }, - { - { - {534839721, 53437770}, - {534839721, 60849059}, - {539898273, 63773857}, - {545461140, 63757881}, - {544859741, 53447836}, - {541839721, 53437862}, - {541710836, 56353878}, - {540193984, 57229659}, - {538859741, 53437862}, - {534839721, 53437770}, - }, - { - }, - }, - { - { - {756086230, 136598477}, - {732054387, 136605752}, - {732052489, 172629505}, - {756091994, 172627853}, - {756086230, 136598477}, - }, - { - }, - }, - { - { - {100337034, 79731391}, - {70296833, 79731391}, - {70311095, 92263567}, - {74329808, 96264260}, - {96344976, 96257215}, - {100344419, 92232243}, - {100337034, 79731391}, - }, - { - }, - }, - { - { - {102331115, 44216643}, - {67311095, 44217252}, - {67311095, 69250964}, - {74329808, 76264260}, - {96334594, 76251411}, - {103335261, 69241401}, - {103345839, 44231404}, - {102331115, 44216643}, - }, - { - }, - }, - { - { - {93849749, 109613798}, - {91771666, 111698636}, - {91772404, 174626800}, - {96782902, 179645338}, - {241790509, 179645349}, - {246800716, 174626800}, - {246802574, 111699755}, - {243934250, 109616385}, - {93849749, 109613798}, - }, - { - }, - }, - { - { - {15856630, 87966835}, - {8414359, 91273170}, - {5891847, 99010553}, - {8403012, 104668172}, - {13739106, 107763252}, - {13739106, 116209175}, - {17959116, 116219127}, - {17959127, 107763252}, - {23952579, 103855773}, - {25806388, 96944174}, - {22553953, 90543787}, - {15856630, 87966835}, - }, - { - }, - }, - { - { - {503922805, 110421794}, - {491110107, 123244292}, - {479598157, 123244304}, - {479601067, 149264312}, - {494260327, 149265241}, - {502929782, 157948320}, - {506490250, 155806171}, - {502950518, 155094962}, - {507193172, 150852294}, - {504364680, 148023895}, - {535816833, 116571757}, - {538656617, 119411542}, - {542887886, 115157558}, - {543594970, 118693080}, - {545330008, 116966050}, - {540309189, 110425901}, - {503922805, 110421794}, - }, - { - }, - }, - { - { - {519310433, 62560296}, - {515749982, 64702434}, - {519289696, 65413661}, - {515047062, 69656303}, - {517875553, 72484703}, - {486423431, 103936848}, - {483595031, 101108448}, - {479352325, 105351055}, - {478645233, 101815525}, - {476917724, 103520870}, - {481923478, 110077233}, - {518337308, 110084297}, - {531130127, 97264312}, - {542630127, 97281049}, - {542639167, 71244292}, - {527979906, 71243363}, - {519310433, 62560296}, - }, - { - }, - }, - { - { - {528658425, 14775300}, - {525975568, 24475413}, - {522556814, 29181341}, - {517517474, 32090757}, - {511736147, 32698600}, - {506200465, 30901018}, - {501879743, 27011092}, - {497782491, 14775300}, - {492372374, 15588397}, - {489384268, 20795320}, - {491253082, 28537271}, - {495185363, 34469052}, - {495178475, 43927542}, - {502032399, 55796416}, - {524402581, 55807400}, - {531706434, 44295318}, - {531205383, 34469052}, - {536679415, 23789946}, - {535868173, 17264403}, - {532873348, 15073849}, - {528658425, 14775300}, - }, - { - }, - }, - { - { - {481122222, 166062916}, - {478115710, 166824472}, - {477103577, 169063247}, - {477106058, 192070670}, - {478623652, 194687013}, - {525109130, 195083267}, - {525117792, 198086965}, - {535129140, 198091624}, - {535129150, 195083267}, - {539038502, 194940807}, - {540865280, 193308821}, - {541132038, 169100183}, - {539614599, 166459484}, - {481122222, 166062916}, - }, - { - }, - }, - { - { - {23771404, 13005453}, - {24774973, 19182457}, - {31971050, 18727127}, - {32556286, 58337520}, - {25390683, 58337566}, - {25063762, 54707065}, - {20168811, 54707252}, - {20171550, 62917175}, - {70810377, 202895528}, - {74314421, 205588631}, - {88674817, 205515176}, - {91837376, 203083756}, - {92280287, 199307207}, - {40674807, 15904975}, - {36849630, 13006690}, - {23771404, 13005453}, - }, - { - }, - }, - { - { - {336421201, 2986256}, - {331176570, 6498191}, - {327552287, 5825511}, - {324913825, 2988891}, - {316226154, 2989990}, - {313040282, 6275291}, - {313040282, 23489990}, - {307126391, 23490002}, - {307140289, 25510010}, - {313040282, 25510010}, - {313040282, 28989990}, - {307126391, 28990002}, - {307140289, 31015515}, - {313040282, 31010010}, - {313040282, 35989990}, - {304534809, 37529785}, - {304524991, 73488855}, - {308554680, 77518546}, - {324040282, 77510010}, - {324040295, 93025333}, - {334574441, 93010010}, - {334574441, 90989990}, - {332560302, 90989990}, - {332560302, 85010010}, - {334560302, 85010010}, - {334561237, 82010010}, - {338540282, 82010010}, - {339540282, 83760010}, - {338540293, 93020012}, - {348060655, 93014679}, - {356564448, 84500000}, - {356560555, 28989990}, - {347334198, 29039989}, - {347334198, 25510010}, - {356510304, 25521084}, - {356510315, 23478922}, - {347560302, 23489990}, - {347560302, 5775291}, - {344874443, 2989990}, - {336421201, 2986256}, - }, - { - }, - }, - { - { - {465152221, 31684687}, - {457606880, 31688302}, - {452659362, 35508617}, - {449044605, 34734089}, - {446478972, 31692751}, - {437784814, 31692957}, - {435521210, 33956565}, - {435532195, 65697616}, - {426028494, 65691361}, - {426025938, 85049712}, - {435532195, 95717636}, - {435524445, 103754026}, - {436995898, 105225463}, - {447552204, 105226323}, - {447552215, 103197497}, - {444552215, 103197616}, - {444552215, 99217636}, - {452032195, 99217636}, - {452032195, 105221758}, - {465588513, 105225463}, - {467059965, 103754026}, - {467052215, 95717636}, - {478053039, 84511285}, - {478056214, 65697616}, - {468552215, 65697616}, - {468563959, 33957323}, - {465152221, 31684687}, - }, - { - }, - }, - { - { - {764927063, 92658416}, - {762115426, 94171595}, - {762122741, 131696443}, - {786415417, 132779578}, - {793690904, 129904572}, - {797383202, 124822853}, - {798269157, 120142660}, - {796710161, 114090278}, - {793387498, 110215980}, - {796094093, 103892242}, - {794107594, 96994001}, - {787445494, 92840355}, - {764927063, 92658416}, - }, - { - }, - }, - { - { - {27496331, 123147467}, - {3202195, 124246400}, - {3203433, 205768600}, - {20223453, 205775606}, - {20223644, 163243606}, - {31297341, 162189074}, - {36789517, 155659691}, - {36967183, 150566416}, - {34468182, 145711036}, - {38465496, 140400171}, - {38952460, 132613091}, - {34771593, 126022444}, - {27496331, 123147467}, - }, - { - }, - }, - { - { - {797556553, 39197820}, - {791313598, 39199767}, - {789506233, 39864015}, - {789522521, 48199767}, - {775974570, 48195721}, - {774022521, 50129235}, - {774008720, 76258022}, - {775974570, 78223833}, - {789522521, 78219787}, - {789522521, 86576919}, - {797556547, 87221747}, - {797556553, 39197820}, - }, - { - }, - }, - { - { - {676593113, 129820144}, - {676565322, 164844636}, - {701599609, 164858650}, - {701599609, 129823260}, - {676593113, 129820144}, - }, - { - }, - }, - { - { - {727646871, 93121321}, - {709122741, 93122138}, - {709122741, 125656310}, - {718769809, 135145243}, - {721622937, 135156111}, - {724152429, 132626619}, - {723734126, 112688301}, - {725837154, 107378546}, - {728976138, 104430846}, - {735847924, 102664848}, - {741289364, 104430846}, - {745202882, 108599767}, - {746590596, 114642158}, - {751137173, 114644887}, - {756151199, 109641674}, - {756149037, 94634278}, - {754642761, 93122138}, - {727646871, 93121321}, - }, - { - }, - }, - { - { - {135915724, 185598906}, - {131396265, 193419009}, - {131399444, 197643260}, - {140399444, 197636810}, - {140399444, 199138818}, - {157419464, 197643916}, - {157422805, 193210743}, - {153046747, 185604789}, - {149044579, 185614655}, - {147324399, 189850396}, - {144168954, 191108901}, - {141187892, 189479768}, - {139917659, 185615382}, - {135915724, 185598906}, - }, - { - }, - }, - { - { - {312619110, 154485844}, - {309601817, 157488332}, - {309599764, 203494810}, - {313109244, 207010010}, - {352900849, 207019221}, - {359629120, 200302405}, - {359638705, 159501827}, - {354621096, 154487830}, - {312619110, 154485844}, - }, - { - }, - }, - { - { - {313120315, 98984639}, - {309609100, 102486971}, - {309596977, 148492024}, - {312591195, 151510010}, - {354608772, 151524494}, - {359629120, 146515788}, - {359638123, 105715491}, - {352907860, 98987790}, - {313120315, 98984639}, - }, - { - }, - }, - { - { - {657746643, 86246732}, - {651722477, 92270881}, - {651720052, 131280884}, - {653947196, 131280884}, - {659746643, 125487816}, - {659746643, 119273826}, - {663742413, 112352691}, - {671726623, 112352691}, - {675733721, 119283349}, - {684745297, 119298573}, - {689758503, 114263168}, - {689752066, 91272158}, - {684746643, 86260871}, - {657746643, 86246732}, - }, - { - }, - }, - { - { - {653940791, 39260871}, - {651720052, 39260871}, - {651726623, 78280611}, - {657746631, 84295035}, - {684746643, 84280891}, - {689752066, 79269604}, - {689746643, 56247942}, - {684745283, 51243184}, - {675733721, 51258413}, - {671726623, 58189071}, - {663742413, 58189071}, - {659746643, 51267936}, - {659746643, 45053950}, - {653940791, 39260871}, - }, - { - }, - }, - { - { - {442365208, 3053303}, - {436408500, 5694021}, - {434342552, 11072741}, - {436986326, 17009033}, - {442365367, 19073360}, - {448299202, 16431441}, - {450365150, 11052721}, - {448299202, 5694021}, - {442365208, 3053303}, - }, - { - }, - }, -}; diff --git a/tests/libnest2d/printer_parts.hpp b/tests/libnest2d/printer_parts.hpp deleted file mode 100644 index 075e32a837..0000000000 --- a/tests/libnest2d/printer_parts.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef PRINTER_PARTS_H -#define PRINTER_PARTS_H - -#include -#include - -using TestData = std::vector; -using TestDataEx = std::vector; - -extern const TestData PRINTER_PART_POLYGONS; -extern const TestData STEGOSAUR_POLYGONS; -extern const TestDataEx PRINTER_PART_POLYGONS_EX; - -#endif // PRINTER_PARTS_H diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 971db528cd..69ae90178a 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -31,13 +31,15 @@ add_executable(${_TEST_NAME}_tests test_png_io.cpp test_surface_mesh.cpp test_timeutils.cpp - test_quadric_edge_collapse.cpp + test_quadric_edge_collapse.cpp test_triangulation.cpp test_emboss.cpp test_indexed_triangle_set.cpp test_astar.cpp - test_jump_point_search.cpp - ../libnest2d/printer_parts.cpp + test_anyptr.cpp + test_jump_point_search.cpp + ../data/prusaparts.cpp + ../data/prusaparts.hpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_anyptr.cpp b/tests/libslic3r/test_anyptr.cpp new file mode 100644 index 0000000000..d7b00a021c --- /dev/null +++ b/tests/libslic3r/test_anyptr.cpp @@ -0,0 +1,198 @@ +#include + +#include +#include +#include + +class Foo +{ +public: + virtual ~Foo() = default; + + virtual void set_foo(int) = 0; + virtual int get_foo() const = 0; +}; + +class Bar: public Foo +{ + int m_i = 0; + +public: + virtual void set_foo(int i) { m_i = i; } + virtual int get_foo() const { return m_i; }; +}; + +class BarPlus: public Foo { + int m_i = 0; + +public: + virtual void set_foo(int i) { m_i = i + 1; } + virtual int get_foo() const { return m_i; }; +}; + +TEST_CASE("Testing AnyPtr", "[anyptr]") { + using Slic3r::AnyPtr; + + SECTION("Construction with various valid arguments using operator=") + { + auto args = std::make_tuple(nullptr, + AnyPtr{nullptr}, + AnyPtr{static_cast(nullptr)}, + AnyPtr{static_cast(nullptr)}, + AnyPtr{static_cast(nullptr)}, + AnyPtr{}, + AnyPtr{}, + AnyPtr{}, + static_cast(nullptr), + static_cast(nullptr), + static_cast(nullptr)); + + auto check_ptr = [](auto &ptr) { + REQUIRE(!ptr); + REQUIRE(!ptr.is_owned()); + + auto shp = ptr.get_shared_cpy(); + REQUIRE(!shp); + }; + + SECTION("operator =") { + Slic3r::for_each_in_tuple([&check_ptr](auto &arg){ + AnyPtr ptr = std::move(arg); + + check_ptr(ptr); + }, args); + } + + SECTION("move construction") + { + Slic3r::for_each_in_tuple([&check_ptr](auto &arg){ + AnyPtr ptr{std::move(arg)}; + + check_ptr(ptr); + }, args); + } + } + + GIVEN("A polymorphic base class type Foo") { + WHEN("Creating a subclass on the stack") { + Bar bar; + auto val = random_value(-100, 100); + bar.set_foo(val); + + THEN("Storing a raw pointer in an AnyPtr should be valid " + "until the object is not destroyed") + { + AnyPtr ptr = &bar; + auto val2 = random_value(-100, 100); + ptr->set_foo(val2); + + REQUIRE(ptr->get_foo() == val2); + } + + THEN("Storing a raw pointer in an AnyPtr should be " + "valid until the object is not destroyed") + { + AnyPtr ptr{&bar}; + + REQUIRE(ptr->get_foo() == val); + } + } + } + + GIVEN("An empty AnyPtr of type Foo") + { + AnyPtr ptr; + + WHEN("Re-assigning a new unique_ptr of object of type Bar to ptr") + { + auto bar = std::make_unique(); + auto val = random_value(-100, 100); + + bar->set_foo(val); + + ptr = std::move(bar); + + THEN("the ptr should contain the new object and should own it") + { + REQUIRE(ptr->get_foo() == val); + REQUIRE(ptr.is_owned()); + } + } + + WHEN("Re-assigning a new unique_ptr of object of type BarPlus to ptr") + { + auto barplus = std::make_unique(); + auto val = random_value(-100, 100); + + barplus->set_foo(val); + + ptr = std::move(barplus); + + THEN("the ptr should contain the new object and should own it") + { + REQUIRE(ptr->get_foo() == val + 1); + REQUIRE(ptr.is_owned()); + } + + THEN("copying the stored object into a shared_ptr should be invalid") + { + std::shared_ptr shptr = ptr.get_shared_cpy(); + + REQUIRE(!shptr); + } + + THEN("copying the stored object into a shared_ptr after calling " + "convert_unique_to_shared should be valid") + { + ptr.convert_unique_to_shared(); + std::shared_ptr shptr = ptr.get_shared_cpy(); + + REQUIRE(shptr); + REQUIRE(shptr->get_foo() == val + 1); + } + } + } + + GIVEN("A vector of AnyPtr pointer to random Bar or BarPlus objects") + { + std::vector> ptrs; + + auto N = random_value(size_t(1), size_t(10)); + INFO("N = " << N); + + std::generate_n(std::back_inserter(ptrs), N, []{ + auto v = random_value(0, 1); + + std::unique_ptr ret; + + if (v) + ret = std::make_unique(); + else + ret = std::make_unique(); + + return ret; + }); + + WHEN("moving the whole array into a vector of AnyPtr") + { + THEN("the move should be valid") + { + std::vector> constptrs; + std::vector vals; + std::transform(ptrs.begin(), ptrs.end(), + std::back_inserter(vals), + [](auto &p) { return p->get_foo(); }); + + std::move(ptrs.begin(), ptrs.end(), std::back_inserter(constptrs)); + + REQUIRE(constptrs.size() == N); + REQUIRE(ptrs.size() == N); + REQUIRE(std::all_of(ptrs.begin(), ptrs.end(), [](auto &p) { return !p; })); + + for (size_t i = 0; i < N; ++i) { + REQUIRE(vals[i] == constptrs[i]->get_foo()); + } + } + } + } +} diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 16a27665e8..d96a2b3e2a 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -15,7 +15,7 @@ //#include "libnest2d/tools/benchmark.h" #include "libslic3r/SVG.hpp" -#include "../libnest2d/printer_parts.hpp" +#include "../data/prusaparts.hpp" #include @@ -683,15 +683,15 @@ struct Pair template<> struct std::hash { size_t operator()(const Pair &c) const { - return c.first * PRINTER_PART_POLYGONS.size() + c.second; + return c.first * PRUSA_PART_POLYGONS.size() + c.second; } }; TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") { // Overlap of the same polygon should always be an intersection - for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) { - Polygon P = PRINTER_PART_POLYGONS[i]; + for (size_t i = 0; i < PRUSA_PART_POLYGONS.size(); ++i) { + Polygon P = PRUSA_PART_POLYGONS[i]; P = Geometry::convex_hull(P.points); bool res = Geometry::convex_polygons_intersect(P, P); if (!res) { @@ -703,8 +703,8 @@ TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcali } std::unordered_set combos; - for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) { - for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) { + for (size_t i = 0; i < PRUSA_PART_POLYGONS.size(); ++i) { + for (size_t j = 0; j < PRUSA_PART_POLYGONS.size(); ++j) { if (i != j) { size_t a = std::min(i, j), b = std::max(i, j); combos.insert(Pair{a, b}); @@ -714,7 +714,7 @@ TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcali // All disjoint for (const auto &combo : combos) { - Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second]; + Polygon A = PRUSA_PART_POLYGONS[combo.first], B = PRUSA_PART_POLYGONS[combo.second]; A = Geometry::convex_hull(A.points); B = Geometry::convex_hull(B.points); @@ -741,7 +741,7 @@ TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcali // All intersecting for (const auto &combo : combos) { - Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second]; + Polygon A = PRUSA_PART_POLYGONS[combo.first], B = PRUSA_PART_POLYGONS[combo.second]; A = Geometry::convex_hull(A.points); B = Geometry::convex_hull(B.points); diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 0fbe6a5e34..89a86f1eeb 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -1,5 +1,3 @@ -#define NOMINMAX - #include #include diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index 7c83fb8d75..ac506e9756 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp slic3r_jobs_tests.cpp slic3r_version_tests.cpp + slic3r_arrangejob_tests.cpp ) # mold linker for successful linking needs also to link TBB library and link it before libslic3r. diff --git a/tests/slic3rutils/slic3r_arrangejob_tests.cpp b/tests/slic3rutils/slic3r_arrangejob_tests.cpp new file mode 100644 index 0000000000..b76861af16 --- /dev/null +++ b/tests/slic3rutils/slic3r_arrangejob_tests.cpp @@ -0,0 +1,351 @@ +#include "catch2/catch.hpp" +#include "test_utils.hpp" + +#include + +#include "slic3r/GUI/Jobs/UIThreadWorker.hpp" +#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp" + +#include "slic3r/GUI/Jobs/ArrangeJob2.hpp" + +#include "libslic3r/Model.hpp" +#include "libslic3r/SLAPrint.hpp" + +#include "libslic3r/Format/3mf.hpp" + +class RandomArrangeSettings: public Slic3r::arr2::ArrangeSettingsView { + Slic3r::arr2::ArrangeSettingsDb::Values m_v; + + std::mt19937 m_rng; +public: + explicit RandomArrangeSettings(int seed) : m_rng(seed) + { + std::uniform_real_distribution fdist(0., 100.f); + std::uniform_int_distribution<> bdist(0, 1); + std::uniform_int_distribution<> dist; + m_v.d_obj = fdist(m_rng); + m_v.d_bed = fdist(m_rng); + m_v.rotations = bdist(m_rng); + m_v.geom_handling = static_cast(dist(m_rng) % ghCount); + m_v.arr_strategy = static_cast(dist(m_rng) % asCount); + m_v.xl_align = static_cast(dist(m_rng) % xlpCount); + } + explicit RandomArrangeSettings() : m_rng(std::random_device{} ()) {} + + float get_distance_from_objects() const override { return m_v.d_obj; } + float get_distance_from_bed() const override { return m_v.d_bed; } + bool is_rotation_enabled() const override { return m_v.rotations; } + XLPivots get_xl_alignment() const override { return m_v.xl_align; } + GeometryHandling get_geometry_handling() const override { return m_v.geom_handling; } + ArrangeStrategy get_arrange_strategy() const override { return m_v.arr_strategy; } +}; + +TEMPLATE_TEST_CASE("Arranging empty bed should do nothing", + "[arrangejob][fillbedjob]", + Slic3r::GUI::ArrangeJob2, + Slic3r::GUI::FillBedJob2) +{ + using namespace Slic3r; + using namespace Slic3r::GUI; + + using JobType = TestType; + + Model m; + + UIThreadWorker w; + RandomArrangeSettings settings; + + w.push(std::make_unique(arr2::Scene{ + arr2::SceneBuilder{}.set_model(m).set_arrange_settings(&settings)})); + + w.process_events(); + + REQUIRE(m.objects.empty()); +} + +static void center_first_instance(Slic3r::ModelObject *mo, + const Slic3r::BoundingBox &bedbb) +{ + using namespace Slic3r; + + Vec2d d = unscaled(bedbb).center() - + to_2d(mo->instance_bounding_box(0).center()); + auto tr = mo->instances.front()->get_transformation().get_matrix(); + tr.translate(to_3d(d, 0.)); + mo->instances.front()->set_transformation(Geometry::Transformation(tr)); +} + +TEST_CASE("Basic arrange with cube", "[arrangejob]") { + using namespace Slic3r; + using namespace Slic3r::GUI; + + std::string basepath = TEST_DATA_DIR PATH_SEPARATOR; + + DynamicPrintConfig cfg; + cfg.load_from_ini(basepath + "default_fff.ini", + ForwardCompatibilitySubstitutionRule::Enable); + Model m = Model::read_from_file(basepath + "20mm_cube.obj", &cfg); + + UIThreadWorker w; + arr2::ArrangeSettings settings; + + Points bedpts = get_bed_shape(cfg); + arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); + + SECTION("Single cube needs to be centered") { + w.push(std::make_unique(arr2::Scene{ + arr2::SceneBuilder{} + .set_model(m) + .set_arrange_settings(&settings) + .set_bed(cfg)})); + + w.process_events(); + + REQUIRE(m.objects.size() == 1); + REQUIRE(m.objects.front()->instances.size() == 1); + + Vec3d c3 = m.objects.front()->bounding_box_exact().center(); + Point c{scaled(c3.x()), scaled(c3.y())}; + + REQUIRE(c == bounding_box(bed).center()); + } + + SECTION("Selected cube needs to go beside existing") { + REQUIRE(m.objects.size() == 1); + + ModelObject *mo = m.objects.front(); + + // Center the first instance within the bed + center_first_instance(mo, bounding_box(bed)); + + m.objects.front()->add_instance(); + + REQUIRE(m.objects.front()->instances.size() == 2); + + arr2::FixedSelection sel({ {false, true} }); + arr2::Scene scene{arr2::SceneBuilder{} + .set_model(m) + .set_arrange_settings(&settings) + .set_bed(cfg) + .set_selection(&sel)}; + + w.push(std::make_unique(std::move(scene))); + w.process_events(); + + auto bb0 = m.objects.front()->instance_bounding_box(0); + auto bb1 = m.objects.front()->instance_bounding_box(1); + + REQUIRE(!bb0.contains(bb1)); + + bb0.merge(bb1); + Vec2d sz = to_2d(bb0.size()); + if (sz.x() > sz.y()) + std::swap(sz.x(), sz.y()); + + double d_obj = settings.get_distance_from_objects(); + REQUIRE(sz.y() == Approx(2. * bb1.size().y() + d_obj)); + } + + SECTION("Selected cube (different object), needs to go beside existing") { + REQUIRE(m.objects.size() == 1); + + ModelObject *mo = m.objects.front(); + + // Center the first instance within the bed + center_first_instance(mo, bounding_box(bed)); + + ModelObject *mosel = m.add_object(*m.objects.front()); + + arr2::FixedSelection sel({ {false}, {true} }); + arr2::Scene scene{arr2::SceneBuilder{} + .set_model(m) + .set_arrange_settings(&settings) + .set_bed(cfg) + .set_selection(&sel)}; + + w.push(std::make_unique(std::move(scene))); + w.process_events(); + + auto bb0 = mo->instance_bounding_box(0); + auto bb1 = mosel->instance_bounding_box(0); + + REQUIRE(!bb0.contains(bb1)); + + bb0.merge(bb1); + Vec2d sz = to_2d(bb0.size()); + if (sz.x() > sz.y()) + std::swap(sz.x(), sz.y()); + + double d_obj = settings.get_distance_from_objects(); + REQUIRE(sz.y() == Approx(2. * bb1.size().y() + d_obj)); + } + + SECTION("Four cubes needs to touch each other after arrange") { + ModelObject *mo = m.objects.front(); + mo->add_instance(); + mo->add_instance(); + mo->add_instance(); + + auto bedbb = unscaled(bounding_box(bed)); + ModelInstance *mi = mo->instances[0]; + + Vec2d d = bedbb.min - to_2d(mo->instance_bounding_box(0).center()); + auto tr = mi->get_transformation().get_matrix(); + tr.translate(to_3d(d, 0.)); + mi->set_transformation(Geometry::Transformation(tr)); + + mi = mo->instances[1]; + d = Vec2d(bedbb.min.x(), bedbb.max.y()) - + to_2d(mo->instance_bounding_box(1).center()); + tr = mi->get_transformation().get_matrix(); + tr.translate(to_3d(d, 0.)); + mi->set_transformation(Geometry::Transformation(tr)); + + mi = mo->instances[2]; + d = bedbb.max - to_2d(mo->instance_bounding_box(2).center()); + tr = mi->get_transformation().get_matrix(); + tr.translate(to_3d(d, 0.)); + mi->set_transformation(Geometry::Transformation(tr)); + + mi = mo->instances[3]; + d = Vec2d(bedbb.max.x(), bedbb.min.y()) - + to_2d(mo->instance_bounding_box(3).center()); + tr = mi->get_transformation().get_matrix(); + tr.translate(to_3d(d, 0.)); + mi->set_transformation(Geometry::Transformation(tr)); + + arr2::Scene scene{arr2::SceneBuilder{} + .set_model(m) + .set_arrange_settings(&settings) + .set_bed(cfg)}; + + w.push(std::make_unique(std::move(scene))); + w.process_events(); + + auto pilebb = m.objects.front()->bounding_box_exact(); + Vec3d c3 = pilebb.center(); + Point c{scaled(c3.x()), scaled(c3.y())}; + + REQUIRE(c == bounding_box(bed).center()); + + float d_obj = settings.get_distance_from_objects(); + REQUIRE(pilebb.size().x() == Approx(2. * 20. + d_obj)); + REQUIRE(pilebb.size().y() == Approx(2. * 20. + d_obj)); + } +} + +struct DummyProgress: Slic3r::ProgressIndicator { + int range = 100; + int pr = 0; + std::string statustxt; + void set_range(int r) override { range = r; } + void set_cancel_callback(CancelFn = CancelFn()) override {} + void set_progress(int p) override { pr = p; } + void set_status_text(const char *txt) override { statustxt = txt; } + int get_range() const override { return range; } +}; + +TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjob]") +{ + using namespace Slic3r; + using namespace Slic3r::GUI; + + std::string basepath = TEST_DATA_DIR PATH_SEPARATOR; + + DynamicPrintConfig cfg; + cfg.load_from_ini(basepath + "default_fff.ini", + ForwardCompatibilitySubstitutionRule::Enable); + + Model m; + + ModelObject* new_object = m.add_object(); + new_object->name = "20mm_cyl"; + new_object->add_instance(); + TriangleMesh mesh = make_cylinder(10., 10.); + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + Points bedpts = get_bed_shape(cfg); + arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); + + BoostThreadWorker w(std::make_unique()); + RandomArrangeSettings settings; + + SECTION("Remove 10 cylinder instances during arrange") { + for (size_t i = 1; i < 10; ++i) + new_object->add_instance(); + + arr2::Scene scene{arr2::SceneBuilder{} + .set_model(m) + .set_arrange_settings(&settings) + .set_bed(cfg)}; + + ArrangeJob2::Callbacks cbs; + cbs.on_prepared = [&m] (auto &) { + m.clear_objects(); + }; + + w.push(std::make_unique(std::move(scene), cbs)); + w.wait_for_current_job(); + + REQUIRE(m.objects.empty()); + } +} + +//TEST_CASE("Logical bed needs to be used when physical bed is full", +// "[arrangejob][fillbedjob]") +//{ +// using namespace Slic3r; +// using namespace Slic3r::GUI; + +// std::string basepath = TEST_DATA_DIR PATH_SEPARATOR; + +// DynamicPrintConfig cfg; +// cfg.load_from_ini(basepath + "default_fff.ini", +// ForwardCompatibilitySubstitutionRule::Enable); + +// Model m; + +// ModelObject* new_object = m.add_object(); +// new_object->name = "bigbox"; +// new_object->add_instance(); +// TriangleMesh mesh = make_cube(200., 200., 10.); +// ModelVolume* new_volume = new_object->add_volume(mesh); +// new_volume->name = new_object->name; + +// Points bedpts = get_bed_shape(cfg); +// arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); +// auto bedbb = bounding_box(bed); + +// center_first_instance(new_object, bedbb); + +// new_object = m.add_object(); +// new_object->name = "40x20mm_box"; +// new_object->add_instance(); +// mesh = make_cube(50., 50., 50.); +// new_volume = new_object->add_volume(mesh); +// new_volume->name = new_object->name; + +// UIThreadWorker w(std::make_unique()); +// arr2::ArrangeSettings settings; + +// SECTION("Single cube needs to be on first logical bed") { +// { +// arr2::Scene scene{&m, &settings, &cfg}; + +// w.push(std::make_unique(std::move(scene))); +// w.process_events(); +// } + +// store_3mf("logicalbed_10mm.3mf", &m, &cfg, false); + +// REQUIRE(m.objects.size() == 2); + +// Vec3d c3 = m.objects[1]->bounding_box_exact().center(); +// Point result_center{scaled(c3.x()), scaled(c3.y())}; + +// auto bedidx_ojb1 = scene.virtual_bed_handler().get_bed_index(m.objects[1]->instances[0]); +// REQUIRE(bedidx_ojb1 == 1); +// } +//} + diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index b129cc79f1..842576a2c0 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -3,6 +3,7 @@ #include #include +#include #if defined(WIN32) || defined(_WIN32) #define PATH_SEPARATOR R"(\)" @@ -18,4 +19,22 @@ inline Slic3r::TriangleMesh load_model(const std::string &obj_filename) return mesh; } +template +Slic3r::FloatingOnly random_value(T minv, T maxv) +{ + static std::mt19937 rng(std::random_device{}()); + std::uniform_real_distribution dist(minv, maxv); + + return dist(rng); +} + +template +Slic3r::IntegerOnly random_value(T minv, T maxv) +{ + static std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(minv, maxv); + + return dist(rng); +} + #endif // SLIC3R_TEST_UTILS diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 2194089a54..9763c55904 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -79,8 +79,8 @@ ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; - bool arrange_objects(double dist) %code%{ ArrangeParams ap{scaled(dist)}; arrange_objects(*THIS, InfiniteBed{}, ap); %}; - void duplicate(unsigned int copies_num, double dist) %code%{ ArrangeParams ap{scaled(dist)}; duplicate(*THIS, copies_num, InfiniteBed{}, ap); %}; + bool arrange_objects(double dist) %code%{ arrange_objects(*THIS, arr2::InfiniteBed{}, arr2::ArrangeSettings{}.set_distance_from_objects(dist) ); %}; + void duplicate(unsigned int copies_num, double dist) %code%{ duplicate(*THIS, copies_num, arr2::InfiniteBed{}, arr2::ArrangeSettings{}.set_distance_from_objects(dist) ); %}; bool looks_like_multipart_object() const; void convert_multipart_object(unsigned int max_extruders); From c360f4c70d1ddafaedcdab68d72c2ed17dbe39a5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Jul 2023 15:35:05 +0200 Subject: [PATCH 048/120] Fix libnest2d cmake script on older cmake versions --- src/libnest2d/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index ccfdfb5512..e5cc40f089 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -20,7 +20,7 @@ set(LIBNEST2D_SRCFILES include/libnest2d/optimizers/nlopt/genetic.hpp ) -add_library(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) +add_library(libnest2d INTERFACE) target_include_directories(libnest2d INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) target_link_libraries(libnest2d INTERFACE NLopt::nlopt TBB::tbb TBB::tbbmalloc Boost::boost libslic3r) From 8db0f205164d7f402d81230658eb97e0123eb899 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Jul 2023 21:44:17 +0200 Subject: [PATCH 049/120] WIP on outline caching --- src/libslic3r/Arrange/OutlineCaching.cpp | 21 +++++ src/libslic3r/Arrange/OutlineCaching.hpp | 37 ++++++++ src/libslic3r/Arrange/SceneBuilder.cpp | 36 ++++++-- src/libslic3r/Arrange/SceneBuilder.hpp | 103 ++++++++++++++++++++++- src/libslic3r/CMakeLists.txt | 2 + 5 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 src/libslic3r/Arrange/OutlineCaching.cpp create mode 100644 src/libslic3r/Arrange/OutlineCaching.hpp diff --git a/src/libslic3r/Arrange/OutlineCaching.cpp b/src/libslic3r/Arrange/OutlineCaching.cpp new file mode 100644 index 0000000000..cb94d1c615 --- /dev/null +++ b/src/libslic3r/Arrange/OutlineCaching.cpp @@ -0,0 +1,21 @@ +#include "OutlineCaching.hpp" + +namespace Slic3r { namespace arr2 { + +const CacheEntryFull *OutlineCache::full_outline(const ObjectID &id) +{ + const CacheEntryFull *ret = nullptr; + + auto it = m_cache_full.find(id.id); + if (it != m_cache_full.end()) + ret = &(it->second); + + return ret; +} + +void OutlineCache::set_full_outline(const ObjectID &id, ExPolygons outline, std::any ctx) +{ + m_cache_full[id.id] = CacheEntryFull{std::move(outline), std::move(ctx)}; +} + +}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/OutlineCaching.hpp b/src/libslic3r/Arrange/OutlineCaching.hpp new file mode 100644 index 0000000000..e0c0e6b1f4 --- /dev/null +++ b/src/libslic3r/Arrange/OutlineCaching.hpp @@ -0,0 +1,37 @@ +#ifndef OUTLINECACHING_HPP +#define OUTLINECACHING_HPP + +#include +#include + +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { namespace arr2 { + +struct CacheEntryConvex { Polygon outline; std::any context; }; +struct CacheEntryFull +{ + ExPolygons outline; + std::any context; + + CacheEntryFull() = default; + CacheEntryFull(ExPolygons outl, std::any ctx) + : outline{std::move(outl)}, context{std::move(ctx)} + {} +}; + +class OutlineCache +{ + std::unordered_map m_cache_full; + std::unordered_map m_cache_convex; + +public: + const CacheEntryFull * full_outline(const ObjectID &id); + + void set_full_outline(const ObjectID &id, ExPolygons outline, std::any ctx); +}; + +}} // namespace Slic3r::arr2 + +#endif // OUTLINECACHING_HPP diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index ecebb5a8f7..ba3279a225 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -435,10 +435,8 @@ void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn) InstPos pos; for (auto *obj : self.m_model->objects) { for (auto *inst : obj->instances) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), - self.m_selmask.get(), pos}; + ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; fn(ainst); - ++pos.inst_idx; } pos.inst_idx = 0; @@ -458,9 +456,7 @@ void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, auto [inst, pos] = find_instance_by_id(*self.m_model, id); if (inst) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), - self.m_selmask.get(), pos}; - + ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; fn(ainst); } } @@ -854,6 +850,34 @@ std::unique_ptr VirtualBedHandler::create(const ExtendedBed & return ret; } +template +ExPolygons OutlineCachingArrangeable::full_outline() const +{ + auto *entry = m_cache->full_outline(m_arrbl.id()); + auto [inst, pos] = find_instance_by_id(m_mdl, m_arrbl.id()); + + ExPolygons outline; + + if (inst) { + Transform3d trafo = inst->get_matrix_no_offset(); + + if (!entry) { + outline = m_arrbl.full_outline(); + m_cache->set_full_outline(this->id().id, outline, + std::any{inst->get_matrix_no_offset()}); + } else { + auto *ctxtrafo = std::any_cast(&(entry->context)); + if (ctxtrafo && ctxtrafo->isApprox(trafo)) + outline = entry->outline; + } + } + + return outline; +} + +template class OutlineCachingArrangeable; +template class OutlineCachingArrangeable; + }} // namespace Slic3r::arr2 #endif // SCENEBUILDER_CPP diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 7939ba83d3..1589062836 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -2,6 +2,8 @@ #define SCENEBUILDER_HPP #include "Scene.hpp" +#include "OutlineCaching.hpp" + #include "Core/ArrangeItemTraits.hpp" namespace Slic3r { @@ -168,6 +170,8 @@ struct ArrangeableWipeTowerBase: public Arrangeable class SceneBuilder; +struct InstPos { size_t obj_idx = 0, inst_idx = 0; }; + class ArrangeableSlicerModel: public ArrangeableModel { protected: @@ -196,6 +200,9 @@ public: void visit_arrangeable(const ObjectID &id, std::function) override; ObjectID add_arrangeable(const ObjectID &prototype_id) override; + + Model & get_model() { return *m_model; } + const Model &get_model() const { return *m_model; } }; class SceneBuilder: public SceneBuilderBase @@ -208,6 +215,7 @@ protected: AnyPtr m_sla_print; AnyPtr m_fff_print; + AnyPtr m_outline_cache; void set_brim_and_skirt(); @@ -374,8 +382,6 @@ public: } }; -struct InstPos { size_t obj_idx = 0, inst_idx = 0; }; - template class ArrangeableModelInstance : public Arrangeable, VBedPlaceable { @@ -639,6 +645,99 @@ public: void apply_duplicates(); }; +template +class OutlineCachingArrangeable: public Arrangeable +{ + ArrblSubclass &m_arrbl; + Model &m_mdl; + mutable OutlineCache *m_cache; + +public: + OutlineCachingArrangeable(ArrblSubclass &arrbl, + Model &mdl, + OutlineCache *cache) + : m_arrbl{arrbl}, m_mdl{mdl}, m_cache{cache} + {} + + ObjectID id() const override { return m_arrbl.id(); } + ObjectID geometry_id() const override { return m_arrbl.geometry_id(); } + + ExPolygons full_outline() const override; + Polygon convex_outline() const override { return m_arrbl.convex_outline(); } + + ExPolygons full_envelope() const override { return m_arrbl.full_envelope(); } + Polygon convex_envelope() const override { return m_arrbl.convex_envelope(); } + + void transform(const Vec2d &transl, double rot) override + { + if constexpr (!std::is_const_v) + m_arrbl.transform(transl, rot); + } + + bool is_printable() const override { return m_arrbl.is_printable(); } + bool is_selected() const override { return m_arrbl.is_selected(); } + int priority() const override { return m_arrbl.priority(); } + void imbue_data(AnyWritable &datastore) const override { m_arrbl.imbue_data(datastore); } + int get_bed_index() const override { return m_arrbl.get_bed_index(); } + bool assign_bed(int bed_idx) override + { + bool ret = false; + + if constexpr (!std::is_const_v) + ret = m_arrbl.assign_bed(bed_idx); + + return ret; + } +}; + +extern template class OutlineCachingArrangeable; +extern template class OutlineCachingArrangeable; + +class OutlineCachingArrangeableModel: public ArrangeableModel { + ArrangeableSlicerModel &m_amodel; + mutable AnyPtr m_cache; + +public: + OutlineCachingArrangeableModel(ArrangeableSlicerModel &amodel, + AnyPtr cache) + : m_amodel{amodel}, m_cache{std::move(cache)} + {} + + void for_each_arrangeable(std::function fn) override + { + m_amodel.for_each_arrangeable([this, &fn](Arrangeable &arrbl){ + OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; + fn (oc_arrbl); + }); + } + void for_each_arrangeable(std::function fn) const override + { + m_amodel.for_each_arrangeable([this, &fn](Arrangeable &arrbl){ + OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; + fn (oc_arrbl); + }); + } + void visit_arrangeable(const ObjectID &id, std::function fn) const override + { + m_amodel.visit_arrangeable(id, [this, &fn](Arrangeable &arrbl){ + OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; + fn (oc_arrbl); + }); + } + void visit_arrangeable(const ObjectID &id, std::function fn) override + { + m_amodel.visit_arrangeable(id, [this, &fn](Arrangeable &arrbl) { + OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; + fn (oc_arrbl); + }); + } + + ObjectID add_arrangeable(const ObjectID &prototype_id) override + { + return m_amodel.add_arrangeable(prototype_id); + } +}; + } // namespace arr2 } // namespace Slic3r diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 75cef23e6d..97d753c939 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -232,6 +232,8 @@ set(SLIC3R_SOURCES Arrange/Scene.cpp Arrange/SceneBuilder.hpp Arrange/SceneBuilder.cpp + Arrange/OutlineCaching.hpp + Arrange/OutlineCaching.cpp Arrange/Tasks/ArrangeTask.hpp Arrange/Tasks/ArrangeTaskImpl.hpp Arrange/Tasks/FillBedTask.hpp From ea62d7b8fb3f6cca53f6d7e95dd78b822c92f85c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jul 2023 15:04:33 +0200 Subject: [PATCH 050/120] Fix SLA crash --- src/libslic3r/Arrange/ArrangeImpl.hpp | 2 +- src/libslic3r/Arrange/SceneBuilder.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 0b0949de28..cebc31ac4a 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -399,7 +399,7 @@ ArrItem AdvancedItemConverter::get_arritem(const Arrangeable &arrbl, if (infl != 0) { outline = offset_ex(outline, infl); if (! envelope.empty()) - envelope = offset_ex(outline, infl); + envelope = offset_ex(envelope, infl); } ArrItem ret; diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index ba3279a225..40208d0a30 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -632,6 +632,8 @@ ExPolygons ArrangeableSLAPrintObject::full_outline() const Polygons ptmp = project_mesh(smesh.its, trafo_instance, [] {}); std::move(ptmp.begin(), ptmp.end(), std::back_inserter(polys)); ret = union_ex(polys); + } else { + ret = m_arrbl->full_outline(); } return ret; From ccc70097196884ad897723fa1092884b02eed3ce Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jul 2023 19:49:33 +0200 Subject: [PATCH 051/120] Add more comments to Scene.hpp --- src/libslic3r/Arrange/Scene.hpp | 95 ++++++++++++++++++++++++++++----- src/libslic3r/ModelArrange.cpp | 2 +- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index 07069f0bf8..c5626bb9ac 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -11,6 +11,14 @@ namespace Slic3r { namespace arr2 { +// This module contains all the necessary high level interfaces for +// arrangement. No dependency on the rest of libslic3r is intoduced here. (No +// Model, ModelObject, etc...) except for ObjectID. + + +// An interface that allows to store arbitrary data (std::any) under a specific +// key in an object implementing the interface. This is later used to pass +// arbitrary parameters from any arranged object down to the arrangement core. class AnyWritable { public: @@ -19,48 +27,99 @@ public: virtual void write(std::string_view key, std::any d) = 0; }; +// The interface that captures the objects which are actually moved around. +// Implementations must provide means to extract the 2D outline that is used +// by the arrangement core. class Arrangeable { public: virtual ~Arrangeable() = default; - virtual ObjectID id() const = 0; + // ID is implementation specific, must uniquely identify an Arrangeable + // object. + virtual ObjectID id() const = 0; + + // This is different than id(), and identifies an underlying group into + // which the Arrangeable belongs. Can be used to group arrangeables sharing + // the same outline. virtual ObjectID geometry_id() const = 0; + + // Outline extraction can be a demanding operation, so there is a separate + // method the extract the full outline of an object and the convex hull only + // It will depend on the arrangement config to choose which one is called. + // convex_outline might be considerably faster than calling full_outline() + // and then calculating the convex hull from that. virtual ExPolygons full_outline() const = 0; virtual Polygon convex_outline() const = 0; + // Envelope is the boundary that an arrangeble object might have which + // is used when the object is being placed or moved around. Once it is + // placed, the outline (convex or full) will be used to determine the + // boundaries instead of the envelope. This concept can be used to + // implement arranging objects with support structures that can overlap, + // but never touch the actual object. In this case, full envelope would + // return the silhouette of the object with supports (pad, brim, etc...) and + // outline would be the actual object boundary. virtual ExPolygons full_envelope() const { return {}; } virtual Polygon convex_envelope() const { return {}; } + // Write the transformations determined by the arrangement into the object virtual void transform(const Vec2d &transl, double rot) = 0; + // An arrangeable can be printable or unprintable, they should not be on + // the same bed. (See arrange tasks) virtual bool is_printable() const { return true; } + + // An arrangeable can be selected or not, this will determine if treated + // as static objects or movable ones. virtual bool is_selected() const { return true; } + + // Determines the order in which the objects are arranged. Higher priority + // objects are arranged first. virtual int priority() const { return 0; } + // Any implementation specific properties can be passed to the arrangement + // core by overriding this method. This implies that the specific Arranger + // will be able to interpret these properties. An example usage is to mark + // special objects (like a wipe tower) virtual void imbue_data(AnyWritable &datastore) const {} + + // for convinience to pass an AnyWritable created in the same expression + // as the method call void imbue_data(AnyWritable &&datastore) const { imbue_data(datastore); } - // Returns the bed index on which the given ModelInstance is sitting. + // An Arrangeable might reside on a logical bed instead of the real one + // in case that the arrangement can not fit it onto the real bed. Handling + // of logical beds is also implementation specific and are specified with + // the next two methods: + + // Returns the bed index on which the given Arrangeable is sitting. virtual int get_bed_index() const = 0; - // Assign the ModelInstance to the given bed index. Note that this + // Assign the Arrangeable to the given bed index. Note that this // method can return false, indicating that the given bed is not available - // to be occupied (e.g. the handler has a limited amount of logical bed) + // to be occupied. virtual bool assign_bed(int bed_idx) = 0; }; +// Arrangeable objects are provided by an ArrangeableModel which is also able to +// create new arrangeables given a prototype id to copy. class ArrangeableModel { public: virtual ~ArrangeableModel() = default; + // Visit all arrangeable in this model and call the provided visitor virtual void for_each_arrangeable(std::function) = 0; virtual void for_each_arrangeable(std::function) const = 0; + // Visit a specific arrangeable identified by it's id virtual void visit_arrangeable(const ObjectID &id, std::function) const = 0; virtual void visit_arrangeable(const ObjectID &id, std::function) = 0; + // Add a new arrangeable which is a copy of the one matching prototype_id + // Return the new object id or an invalid id if the new object was not + // created. virtual ObjectID add_arrangeable(const ObjectID &prototype_id) = 0; size_t arrangeable_count() const @@ -72,9 +131,13 @@ public: } }; +// The special bed type used by XL printers using XLBed = SegmentedRectangleBed, std::integral_constant>; +// ExtendedBed is a variant type holding all bed types supported by the +// arrange core and the additional XLBed + template struct ExtendedBed_ { using Type = @@ -98,17 +161,16 @@ template void visit_bed(BedFn &&fn, ExtendedBed &bed) boost::apply_visitor(fn, bed); } -inline ExtendedBed to_extended_bed(const ArrangeBed &bed) -{ - ExtendedBed ret; - boost::apply_visitor([&ret](auto &rawbed) { ret = rawbed; }, bed); - - return ret; -} - class Scene; -// A little CRTP to implement fluent interface returning Subclass references +// SceneBuilderBase is intended for Scene construction. A simple constructor +// is not enough here to capture all the possible ways of constructing a Scene. +// Subclasses of SceneBuilderBase can add more domain specific methods and +// overloads. An rvalue object of this class is handed over to the Scene +// constructor which can then establish itself using the provided builder. + +// A little CRTP is used to implement fluent interface returning Subclass +// references. template class SceneBuilderBase { @@ -178,10 +240,13 @@ public: class BasicSceneBuilder: public SceneBuilderBase {}; +// The Scene class captures all data needed to do an arrangement. class Scene { template friend class SceneBuilderBase; + // These fields always need to be initialized to valid objects after + // construction of Scene which is ensured by the SceneBuilder AnyPtr m_amodel; AnyPtr m_settings; ExtendedBed m_bed; @@ -244,6 +309,10 @@ void SceneBuilderBase::build_scene(Scene &sc) && sc.m_bed = std::move(m_bed); } +// Arrange tasks produce an object implementing this interface. The arrange +// result can be applied to an ArrangeableModel which may or may not succeed. +// The ArrangeableModel could be in a different state (it's objects may have +// changed or removed) than it was at the time of arranging. class ArrangeResult { public: diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 2755102b5a..f0294e8a05 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -51,7 +51,7 @@ void duplicate(Model &model, const arr2::ArrangeBed &bed, const arr2::ArrangeSettingsView &settings) { - auto vbh = arr2::VirtualBedHandler::create(arr2::to_extended_bed(bed)); + auto vbh = arr2::VirtualBedHandler::create(bed); arr2::DuplicableModel dup_model{&model, std::move(vbh), bounding_box(bed)}; arr2::Scene scene{arr2::BasicSceneBuilder{} From 9c7fd4f692ba8a2e8a1da8eaab8dce59e7162832 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 21 Jul 2023 17:11:49 +0200 Subject: [PATCH 052/120] Implement correct centroid in DecomposedShape --- .../Core/NFP/Kernels/GravityKernel.hpp | 2 +- .../Core/NFP/Kernels/TMArrangeKernel.hpp | 10 ++++--- .../Arrange/Core/NFP/NFPArrangeItemTraits.hpp | 20 ++++++++++++++ src/libslic3r/Arrange/Items/ArrangeItem.cpp | 26 ++++++++++++++++++- src/libslic3r/Arrange/Items/ArrangeItem.hpp | 19 ++++++++++++++ .../Arrange/Items/SimpleArrangeItem.hpp | 10 +++++++ 6 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp index ba4f503c76..df3572deaf 100644 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp @@ -19,7 +19,7 @@ struct GravityKernel { template double placement_fitness(const ArrItem &itm, const Vec2crd &transl) const { - Vec2d center = unscaled(envelope_bounding_box(itm).center()); + Vec2d center = unscaled(envelope_centroid(itm)); center += unscaled(transl); diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp index f5dba769d5..dd7742bff5 100644 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp @@ -65,6 +65,8 @@ public: // Candidate item bounding box auto ibb = envelope_bounding_box(item); ibb.translate(transl); + auto itmcntr = envelope_centroid(item); + itmcntr += transl; // Calculate the full bounding box of the pile with the candidate item auto fullbb = m_pilebb; @@ -115,7 +117,7 @@ public: switch (compute_case) { case WIPE_TOWER: { - score = (unscaled(ibb.center()) - unscaled(active_sink)).squaredNorm(); + score = (unscaled(itmcntr) - unscaled(active_sink)).squaredNorm(); break; } case BIG_ITEM: { @@ -132,7 +134,7 @@ public: auto cc = fullbb.center(); // The gravity center dists[0] = (minc - cc).cast().norm(); dists[1] = (maxc - cc).cast().norm(); - dists[2] = (ibb.center() - cc).template cast().norm(); + dists[2] = (itmcntr - cc).template cast().norm(); dists[3] = (top_left - cc).cast().norm(); dists[4] = (bottom_right - cc).cast().norm(); @@ -191,7 +193,7 @@ public: break; } case LAST_BIG_ITEM: { - score = norm((ibb.center() - m_pilebb.center()).template cast().norm()); + score = norm((itmcntr - m_pilebb.center()).template cast().norm()); break; } case SMALL_ITEM: { @@ -199,7 +201,7 @@ public: // already processed bigger items. // No need to play around with the anchor points, the center will be // just fine for small items - score = norm((ibb.center() - bigbb.center()).template cast().norm()); + score = norm((itmcntr - bigbb.center()).template cast().norm()); break; } } diff --git a/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp b/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp index 009162661c..002d3df6fe 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp +++ b/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp @@ -74,6 +74,16 @@ template struct NFPArrangeItemTraits_ { return std::array{0.}; } + + static Vec2crd fixed_centroid(const ArrItem &itm) + { + return fixed_bounding_box(itm).center(); + } + + static Vec2crd envelope_centroid(const ArrItem &itm) + { + return envelope_bounding_box(itm).center(); + } }; template @@ -137,6 +147,16 @@ template double fixed_area(const ArrItem &itm) return NFPArrangeItemTraits::fixed_area(itm); } +template Vec2crd fixed_centroid(const ArrItem &itm) +{ + return NFPArrangeItemTraits::fixed_centroid(itm); +} + +template Vec2crd envelope_centroid(const ArrItem &itm) +{ + return NFPArrangeItemTraits::envelope_centroid(itm); +} + template auto allowed_rotations(const ArrItem &itm) { diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.cpp b/src/libslic3r/Arrange/Items/ArrangeItem.cpp index ff8c761147..63d15e75a6 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.cpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.cpp @@ -13,6 +13,8 @@ namespace Slic3r { namespace arr2 { const Polygons &DecomposedShape::transformed_outline() const { + constexpr auto sc = scaled(1.) * scaled(1.); + if (!m_transformed_outline_valid) { m_transformed_outline = contours(); for (Polygon &poly : m_transformed_outline) { @@ -20,7 +22,6 @@ const Polygons &DecomposedShape::transformed_outline() const poly.translate(translation()); } - auto sc = scaled(1.) * scaled(1.); m_area = std::accumulate(m_transformed_outline.begin(), m_transformed_outline.end(), 0., [sc](double s, const auto &p) { @@ -88,6 +89,29 @@ const Vec2crd &DecomposedShape::min_vertex(size_t idx) const return m_mins[idx]; } +Vec2crd DecomposedShape::centroid() const +{ + constexpr double area_sc = scaled(1.) * scaled(1.); + + if (!m_centroid_valid) { + double total_area = 0.0; + Vec2d cntr = Vec2d::Zero(); + + for (const Polygon& poly : transformed_outline()) { + double parea = poly.area() / area_sc; + Vec2d pcntr = unscaled(poly.centroid()); + total_area += parea; + cntr += pcntr * parea; + } + + cntr /= total_area; + m_centroid = scaled(cntr); + m_centroid_valid = true; + } + + return m_centroid; +} + DecomposedShape decompose(const ExPolygons &shape) { ExPolygons shape_s = expolygons_simplify(shape, scaled(.2)); diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.hpp b/src/libslic3r/Arrange/Items/ArrangeItem.hpp index 1fb276a6be..5362431e49 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.hpp @@ -55,6 +55,9 @@ class DecomposedShape mutable std::vector m_mins; mutable bool m_reference_vertex_valid = false; + mutable Point m_centroid; + mutable bool m_centroid_valid = false; + mutable Polygon m_convex_hull; mutable BoundingBox m_bounding_box; mutable double m_area = 0; @@ -87,6 +90,7 @@ public: m_translation = v; m_transformed_outline_valid = false; m_reference_vertex_valid = false; + m_centroid_valid = false; } void rotation(double v) @@ -94,6 +98,7 @@ public: m_rotation = v; m_transformed_outline_valid = false; m_reference_vertex_valid = false; + m_centroid_valid = false; } const Polygons &transformed_outline() const; @@ -115,6 +120,8 @@ public: return m_area; } + + Vec2crd centroid() const; }; DecomposedShape decompose(const ExPolygons &polys); @@ -188,6 +195,8 @@ public: { m_shape.reference_vertex(); m_envelope->reference_vertex(); + m_shape.centroid(); + m_envelope->centroid(); } }; @@ -412,6 +421,16 @@ template<> struct NFPArrangeItemTraits_ { return *ret_ptr; } + + static Vec2crd fixed_centroid(const ArrangeItem &itm) + { + return itm.shape().centroid(); + } + + static Vec2crd envelope_centroid(const ArrangeItem &itm) + { + return itm.envelope().centroid(); + } }; template<> struct IsWritableItem_: public std::true_type {}; diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp index 4f321c93b0..1d924c1861 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp @@ -138,6 +138,16 @@ template<> struct NFPArrangeItemTraits_ { return itm.allowed_rotations(); } + + static Vec2crd fixed_centroid(const SimpleArrangeItem &itm) noexcept + { + return itm.shape().centroid(); + } + + static Vec2crd envelope_centroid(const SimpleArrangeItem &itm) noexcept + { + return itm.shape().centroid(); + } }; template<> struct IsWritableItem_: public std::true_type {}; From e90f9e7088062ad3013aaa9762e50ace51ebe7a3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 21 Jul 2023 21:21:07 +0200 Subject: [PATCH 053/120] Usable workaround for missing items when filling the bed --- .../Arrange/Items/WritableItemTraits.hpp | 24 +++++++++++++++++++ .../Arrange/Tasks/FillBedTaskImpl.hpp | 23 +++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Arrange/Items/WritableItemTraits.hpp b/src/libslic3r/Arrange/Items/WritableItemTraits.hpp index 04281faa52..f80f449730 100644 --- a/src/libslic3r/Arrange/Items/WritableItemTraits.hpp +++ b/src/libslic3r/Arrange/Items/WritableItemTraits.hpp @@ -107,6 +107,30 @@ template int raise_priority(ArrItem &itm) return ret; } +template int reduce_priority(ArrItem &itm) +{ + int ret = get_priority(itm) - 1; + set_priority(itm, ret); + + return ret; +} + +template int lowest_priority(const Range &item_range) +{ + auto minp_it = std::min_element(item_range.begin(), + item_range.end(), + [](auto &itm1, auto &itm2) { + return get_priority(itm1) < + get_priority(itm2); + }); + + int min_priority = 0; + if (minp_it != item_range.end()) + min_priority = get_priority(*minp_it); + + return min_priority; +} + }} // namespace Slic3r::arr2 #endif // WRITABLEITEMTRAITS_HPP diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp index 9c76a35c81..62b0d78270 100644 --- a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp @@ -66,6 +66,17 @@ void extract(FillBedTask &task, if (!task.prototype_item) return; + // Workaround for missing items when arranging the same geometry only: + // Injecting a number of items but with slightly shrinked shape, so that + // they can fill the emerging holes. Priority is set to lowest so that + // these filler items will only be inserted as the last ones. + ArrItem prototype_item_shrinked; + scene.model().visit_arrangeable(selected_ids.front(), + [&prototype_item_shrinked, &itm_conv](const Arrangeable &arrbl) { + if (arrbl.is_printable()) + prototype_item_shrinked = itm_conv.convert(arrbl, -SCALED_EPSILON); + }); + set_bed_index(*task.prototype_item, Unarranged); auto collect_task_items = [&prototype_geometry_id, &task, @@ -82,6 +93,10 @@ void extract(FillBedTask &task, } }; + // Set the lowest priority to the shrinked prototype (hole filler) item + set_priority(prototype_item_shrinked, + lowest_priority(range(task.selected)) - 1); + scene.model().for_each_arrangeable(collect_task_items); int needed_items = calculate_items_needed_to_fill_bed(task.bed, @@ -93,6 +108,11 @@ void extract(FillBedTask &task, task.selected.reserve(task.selected.size() + needed_items); std::fill_n(std::back_inserter(task.selected), needed_items, *task.prototype_item); + + // Add as many filler items as there are needed items. Most of them will + // be discarded anyways. + std::fill_n(std::back_inserter(task.selected), needed_items, + prototype_item_shrinked); } @@ -143,7 +163,8 @@ std::unique_ptr FillBedTask::process_native( void on_packed(ArrItem &itm) override { - do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) == 0; + // Stop at the first filler that is not on the physical bed + do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) < 0; } } subctl(ctl, *this); From 2730d2e14f650a2baed19fe650bdabaa1a0b5101 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Jul 2023 10:54:22 +0200 Subject: [PATCH 054/120] Remove unneeded check from fill bed test --- src/libslic3r/Arrange/Scene.hpp | 2 +- tests/arrange/test_arrange_integration.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index c5626bb9ac..f917ed3c2f 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -18,7 +18,7 @@ namespace Slic3r { namespace arr2 { // An interface that allows to store arbitrary data (std::any) under a specific // key in an object implementing the interface. This is later used to pass -// arbitrary parameters from any arranged object down to the arrangement core. +// arbitrary parameters from any arrangeable object down to the arrangement core. class AnyWritable { public: diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index a7c9275600..26ff19c329 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -659,10 +659,12 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes", REQUIRE(task->unselected.empty()); REQUIRE(result->to_add.size() + result->arranged_items.size() == arr2::model_instance_count(m)); - REQUIRE( - std::all_of(task->selected.begin(), task->selected.end(), [](auto &itm) { - return arr2::get_bed_index(itm) == 0; - })); + // All the existing items should be on the physical bed + REQUIRE(std::all_of(result->arranged_items.begin(), + result->arranged_items.end(), [](auto &itm) { + return arr2::get_bed_index(itm) == 0; + })); + REQUIRE( std::all_of(result->to_add.begin(), result->to_add.end(), [](auto &itm) { return arr2::get_bed_index(itm) == 0; From e4a1df0add0291a8f8ca279f98166384e1e81b6b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Jul 2023 11:14:27 +0200 Subject: [PATCH 055/120] Fix failing tests --- src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp | 4 ++-- tests/arrange/test_arrange.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp index 1d924c1861..54d7866ba1 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp @@ -141,12 +141,12 @@ template<> struct NFPArrangeItemTraits_ static Vec2crd fixed_centroid(const SimpleArrangeItem &itm) noexcept { - return itm.shape().centroid(); + return itm.outline().centroid(); } static Vec2crd envelope_centroid(const SimpleArrangeItem &itm) noexcept { - return itm.shape().centroid(); + return itm.outline().centroid(); } }; diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp index 578fc573b6..0cb2c21511 100644 --- a/tests/arrange/test_arrange.cpp +++ b/tests/arrange/test_arrange.cpp @@ -937,7 +937,8 @@ TEST_CASE("Optimal nfp position search with GravityKernel using RectangleItem an } } -TEST_CASE("RectangleOverfitPackingStrategy test", "[arrange2]") +TEMPLATE_TEST_CASE("RectangleOverfitPackingStrategy test", "[arrange2]", + Slic3r::arr2::SimpleArrangeItem, Slic3r::arr2::ArrangeItem) { using Slic3r::arr2::RectangleOverfitPackingStrategy; using Slic3r::arr2::PackStrategyNFP; @@ -946,7 +947,7 @@ TEST_CASE("RectangleOverfitPackingStrategy test", "[arrange2]") namespace firstfit = Slic3r::arr2::firstfit; - using ArrItem = Slic3r::arr2::SimpleArrangeItem; + using ArrItem = TestType; auto frontleft_align_fn = [](const Slic3r::BoundingBox &bedbb, const Slic3r::BoundingBox &pilebb) { From dc8008bcb80c0b77e4339833693535ec09aa1b08 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Jul 2023 13:32:16 +0200 Subject: [PATCH 056/120] Fix failing back-fitting of single selected items when rotations enabled --- src/libslic3r/Arrange/ArrangeImpl.hpp | 52 +++++++++++++------ .../Arrange/Core/NFP/PackStrategyNFP.hpp | 4 +- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index cebc31ac4a..57e9e1f358 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -216,6 +216,40 @@ inline RectPivots xlpivots_to_rect_pivots(ArrangeSettingsView::XLPivots xlpivot) return rectpivot; } +template +void fill_rotations(const Range &items, + const Bed &bed, + const ArrangeSettingsView &settings) +{ + if (!settings.is_rotation_enabled()) + return; + + for (auto &itm : items) { + // Use the minimum bounding box rotation as a starting point. + auto minbbr = get_min_area_bounding_box_rotation(itm); + std::vector rotations = + {minbbr, + minbbr + PI / 4., minbbr + PI / 2., + minbbr + PI, minbbr + 3 * PI / 4.}; + + // Add the original rotation of the item if minbbr + // is not already the original rotation (zero) + if (std::abs(minbbr) > 0.) + rotations.emplace_back(0.); + + // Also try to find the rotation that fits the item + // into a rectangular bed, given that it cannot fit, + // and there exists a rotation which can fit. + if constexpr (std::is_convertible_v) { + double fitbrot = get_fit_into_bed_rotation(itm, bed); + if (std::abs(fitbrot) > 0.) + rotations.emplace_back(fitbrot); + } + + set_allowed_rotations(itm, rotations); + } +} + // An arranger put together to fulfill all the requirements of PrusaSlicer based // on the supplied ArrangeSettings template @@ -271,23 +305,7 @@ class DefaultArranger: public Arranger { auto & kernel = basekernel; #endif - // Use the minimum bounding box rotation as a starting point. - if (m_settings.is_rotation_enabled()) { - for (auto &itm : items) { - double fit_bed_rot = 0.; - - if constexpr (std::is_convertible_v) - fit_bed_rot = get_fit_into_bed_rotation(itm, bed); - - auto minbbr = get_min_area_bounding_box_rotation(itm); - std::vector rotations = - {minbbr, fit_bed_rot, - minbbr + PI / 4., minbbr + PI / 2., - minbbr + PI, minbbr + 3 * PI / 4.}; - - set_allowed_rotations(itm, rotations); - } - } + fill_rotations(items, bed, m_settings); bool with_wipe_tower = std::any_of(items.begin(), items.end(), [](auto &itm) { diff --git a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp index c042829dfb..838530505b 100644 --- a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp +++ b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp @@ -252,14 +252,12 @@ bool pack(Strategy &strategy, if (!nfp.empty()) { score = pick_best_spot_on_nfp(item, nfp, bed, strategy); - cancelled = std::isnan(score) || strategy.stop_condition(); + cancelled = strategy.stop_condition(); if (score > final_score) { final_score = score; final_rot = rot; final_tr = get_translation(item); } - } else { - cancelled = true; } } From 08e10ec98f56384a58b8fadaf0acc34853bdcb14 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Jul 2023 16:59:26 +0200 Subject: [PATCH 057/120] Add test involving simple arrange on vbeds --- tests/arrange/test_arrange_integration.cpp | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index 26ff19c329..383349c4a5 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -780,7 +780,43 @@ TEST_CASE("Testing a simple arrange on cubes", "[arrange2][integration]") [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); })); REQUIRE(is_collision_free(range(task->printable.selected))); +} +TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration]") +{ + using namespace Slic3r; + + Model model = get_example_model_with_arranged_primitives(); + + arr2::ArrangeSettings settings; + auto bed = arr2::RectangleBed{scaled(250.), scaled(210.)}; + + arr2::Scene scene{arr2::SceneBuilder{} + .set_model(model) + .set_arrange_settings(settings) + .set_bed(bed)}; + + auto task = arr2::ArrangeTask::create(scene); + task->printable.selected.emplace_back(arr2::ArrangeItem{arr2::to_rectangle(offset(bed, -scaled(1.)))}); + + REQUIRE(task->printable.selected.size() == arr2::model_instance_count(model) + 1); + + auto result = task->process_native(arr2::DummyCtl{}); + + REQUIRE(result); + + REQUIRE(result->items.size() == task->printable.selected.size()); + + REQUIRE(std::all_of(result->items.begin(), + std::prev(result->items.end()), + [](auto &item) { return arr2::get_bed_index(item) == 1; })); + + REQUIRE(arr2::get_bed_index(result->items.back()) == arr2::PhysicalBedId); + + REQUIRE(std::all_of(task->printable.selected.begin(), task->printable.selected.end(), + [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); })); + + REQUIRE(is_collision_free(Range{task->printable.selected.begin(), std::prev(task->printable.selected.end())})); } bool settings_eq(const Slic3r::arr2::ArrangeSettingsView &v1, From eb08a842904f0fe8db9f1c52e813802f79bebf9b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Jul 2023 17:11:47 +0200 Subject: [PATCH 058/120] Fix for broken arrange introduced in b7d79b1b9 --- src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp index 838530505b..733694b28e 100644 --- a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp +++ b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp @@ -261,6 +261,7 @@ bool pack(Strategy &strategy, } } + cancelled = !packed && (cancelled || std::isnan(final_score) || std::isinf(final_score)); packed = !cancelled; if (packed) { From ace6cb1f78a65556d5b28973704a2c3efd519200 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jul 2023 12:30:22 +0200 Subject: [PATCH 059/120] Complete test for vbed involvement --- src/libslic3r/Arrange/ArrangeSettingsView.hpp | 12 ++--- .../Arrange/Core/NFP/PackStrategyNFP.hpp | 13 +++-- tests/arrange/test_arrange_integration.cpp | 47 ++++++++++++++++--- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeSettingsView.hpp b/src/libslic3r/Arrange/ArrangeSettingsView.hpp index 5bd63aeea0..582e6bd5b7 100644 --- a/src/libslic3r/Arrange/ArrangeSettingsView.hpp +++ b/src/libslic3r/Arrange/ArrangeSettingsView.hpp @@ -102,12 +102,12 @@ public: void distance_from_obj_range(float &min, float &max) const override { min = 0.f; max = 100.f; } void distance_from_bed_range(float &min, float &max) const override { min = 0.f; max = 100.f; } - ArrangeSettingsDb& set_distance_from_objects(float v) override { m_v.d_obj = v; return *this; } - ArrangeSettingsDb& set_distance_from_bed(float v) override { m_v.d_bed = v; return *this; } - ArrangeSettingsDb& set_rotation_enabled(bool v) override { m_v.rotations = v; return *this; } - ArrangeSettingsDb& set_xl_alignment(XLPivots v) override { m_v.xl_align = v; return *this; } - ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) override { m_v.geom_handling = v; return *this; } - ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) override { m_v.arr_strategy = v; return *this; } + ArrangeSettings& set_distance_from_objects(float v) override { m_v.d_obj = v; return *this; } + ArrangeSettings& set_distance_from_bed(float v) override { m_v.d_bed = v; return *this; } + ArrangeSettings& set_rotation_enabled(bool v) override { m_v.rotations = v; return *this; } + ArrangeSettings& set_xl_alignment(XLPivots v) override { m_v.xl_align = v; return *this; } + ArrangeSettings& set_geometry_handling(GeometryHandling v) override { m_v.geom_handling = v; return *this; } + ArrangeSettings& set_arrange_strategy(ArrangeStrategy v) override { m_v.arr_strategy = v; return *this; } auto & values() const { return m_v; } auto & values() { return m_v; } diff --git a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp index 733694b28e..ea2f2a5100 100644 --- a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp +++ b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp @@ -226,6 +226,7 @@ bool pack(Strategy &strategy, { using KernelT = KernelTraits; + // The kernel might pack the item immediately bool packed = KernelT::on_start_packing(strategy.kernel, item, bed, packing_context, remaining_items); @@ -235,9 +236,10 @@ bool pack(Strategy &strategy, Vec2crd orig_tr = get_translation(item); Vec2crd final_tr = orig_tr; - bool cancelled = false; + bool cancelled = strategy.stop_condition(); const auto & rotations = allowed_rotations(item); + // Check all rotations but only if item is not already packed for (auto rot_it = rotations.begin(); !cancelled && !packed && rot_it != rotations.end(); ++rot_it) { @@ -261,12 +263,17 @@ bool pack(Strategy &strategy, } } - cancelled = !packed && (cancelled || std::isnan(final_score) || std::isinf(final_score)); - packed = !cancelled; + // If the score is not valid, and the item is not already packed, or + // the packing was cancelled asynchronously by stop condition, then + // discard the packing + bool is_score_valid = !std::isnan(final_score) && !std::isinf(final_score); + packed = !cancelled && (packed || is_score_valid); if (packed) { set_translation(item, final_tr); set_rotation(item, orig_rot + final_rot); + + // Finally, consult the kernel if the packing is sane packed = KernelT::on_item_packed(strategy.kernel, item); } diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index 383349c4a5..39bd70afa1 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -787,19 +787,50 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration] using namespace Slic3r; Model model = get_example_model_with_arranged_primitives(); + DynamicPrintConfig cfg; + cfg.load_from_ini(std::string(TEST_DATA_DIR PATH_SEPARATOR) + "default_fff.ini", + ForwardCompatibilitySubstitutionRule::Enable); + auto bed = arr2::to_arrange_bed(get_bed_shape(cfg)); + auto bedbb = bounding_box(bed); + auto bedsz = unscaled(bedbb.size()); - arr2::ArrangeSettings settings; - auto bed = arr2::RectangleBed{scaled(250.), scaled(210.)}; + auto strategy = GENERATE(arr2::ArrangeSettingsView::asAuto, + arr2::ArrangeSettingsView::asPullToCenter); + + INFO ("Strategy = " << strategy); + + auto settings = arr2::ArrangeSettings{} + .set_distance_from_objects(0.) + .set_arrange_strategy(strategy); arr2::Scene scene{arr2::SceneBuilder{} .set_model(model) .set_arrange_settings(settings) - .set_bed(bed)}; + .set_bed(cfg)}; - auto task = arr2::ArrangeTask::create(scene); - task->printable.selected.emplace_back(arr2::ArrangeItem{arr2::to_rectangle(offset(bed, -scaled(1.)))}); + auto itm_conv = arr2::ArrangeableToItemConverter::create(scene); - REQUIRE(task->printable.selected.size() == arr2::model_instance_count(model) + 1); + auto task = arr2::ArrangeTask::create(scene, *itm_conv); + + ModelObject* new_object = model.add_object(); + new_object->name = "big_cube"; + ModelInstance *bigcube_inst = new_object->add_instance(); + TriangleMesh mesh = make_cube(bedsz.x() - 5., bedsz.y() - 5., 20.); + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = new_object->name; + + { + arr2::ArrangeItem bigitm; + scene.model().visit_arrangeable(bigcube_inst->id(), + [&bigitm, &itm_conv]( + const arr2::Arrangeable &arrbl) { + bigitm = itm_conv->convert(arrbl); + }); + + task->printable.selected.emplace_back(std::move(bigitm)); + } + + REQUIRE(task->printable.selected.size() == arr2::model_instance_count(model)); auto result = task->process_native(arr2::DummyCtl{}); @@ -813,6 +844,10 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration] REQUIRE(arr2::get_bed_index(result->items.back()) == arr2::PhysicalBedId); + bool applied = result->apply_on(scene.model()); + REQUIRE(applied); + store_3mf("vbed_test_result.3mf", &model, &cfg, false); + REQUIRE(std::all_of(task->printable.selected.begin(), task->printable.selected.end(), [&bed](auto &item) { return bounding_box(bed).contains(arr2::envelope_bounding_box(item)); })); From 355b869e0d8a325ecedaa133c41b533931c9dd7c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jul 2023 15:35:57 +0200 Subject: [PATCH 060/120] Remove unfinished outline caching code --- src/libslic3r/Arrange/OutlineCaching.cpp | 21 ------ src/libslic3r/Arrange/OutlineCaching.hpp | 37 --------- src/libslic3r/Arrange/SceneBuilder.cpp | 28 ------- src/libslic3r/Arrange/SceneBuilder.hpp | 95 ------------------------ src/libslic3r/CMakeLists.txt | 2 - 5 files changed, 183 deletions(-) delete mode 100644 src/libslic3r/Arrange/OutlineCaching.cpp delete mode 100644 src/libslic3r/Arrange/OutlineCaching.hpp diff --git a/src/libslic3r/Arrange/OutlineCaching.cpp b/src/libslic3r/Arrange/OutlineCaching.cpp deleted file mode 100644 index cb94d1c615..0000000000 --- a/src/libslic3r/Arrange/OutlineCaching.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "OutlineCaching.hpp" - -namespace Slic3r { namespace arr2 { - -const CacheEntryFull *OutlineCache::full_outline(const ObjectID &id) -{ - const CacheEntryFull *ret = nullptr; - - auto it = m_cache_full.find(id.id); - if (it != m_cache_full.end()) - ret = &(it->second); - - return ret; -} - -void OutlineCache::set_full_outline(const ObjectID &id, ExPolygons outline, std::any ctx) -{ - m_cache_full[id.id] = CacheEntryFull{std::move(outline), std::move(ctx)}; -} - -}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/OutlineCaching.hpp b/src/libslic3r/Arrange/OutlineCaching.hpp deleted file mode 100644 index e0c0e6b1f4..0000000000 --- a/src/libslic3r/Arrange/OutlineCaching.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef OUTLINECACHING_HPP -#define OUTLINECACHING_HPP - -#include -#include - -#include "libslic3r/ObjectID.hpp" -#include "libslic3r/ExPolygon.hpp" - -namespace Slic3r { namespace arr2 { - -struct CacheEntryConvex { Polygon outline; std::any context; }; -struct CacheEntryFull -{ - ExPolygons outline; - std::any context; - - CacheEntryFull() = default; - CacheEntryFull(ExPolygons outl, std::any ctx) - : outline{std::move(outl)}, context{std::move(ctx)} - {} -}; - -class OutlineCache -{ - std::unordered_map m_cache_full; - std::unordered_map m_cache_convex; - -public: - const CacheEntryFull * full_outline(const ObjectID &id); - - void set_full_outline(const ObjectID &id, ExPolygons outline, std::any ctx); -}; - -}} // namespace Slic3r::arr2 - -#endif // OUTLINECACHING_HPP diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index 40208d0a30..b80f48a436 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -852,34 +852,6 @@ std::unique_ptr VirtualBedHandler::create(const ExtendedBed & return ret; } -template -ExPolygons OutlineCachingArrangeable::full_outline() const -{ - auto *entry = m_cache->full_outline(m_arrbl.id()); - auto [inst, pos] = find_instance_by_id(m_mdl, m_arrbl.id()); - - ExPolygons outline; - - if (inst) { - Transform3d trafo = inst->get_matrix_no_offset(); - - if (!entry) { - outline = m_arrbl.full_outline(); - m_cache->set_full_outline(this->id().id, outline, - std::any{inst->get_matrix_no_offset()}); - } else { - auto *ctxtrafo = std::any_cast(&(entry->context)); - if (ctxtrafo && ctxtrafo->isApprox(trafo)) - outline = entry->outline; - } - } - - return outline; -} - -template class OutlineCachingArrangeable; -template class OutlineCachingArrangeable; - }} // namespace Slic3r::arr2 #endif // SCENEBUILDER_CPP diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 1589062836..226934245d 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -2,7 +2,6 @@ #define SCENEBUILDER_HPP #include "Scene.hpp" -#include "OutlineCaching.hpp" #include "Core/ArrangeItemTraits.hpp" @@ -215,7 +214,6 @@ protected: AnyPtr m_sla_print; AnyPtr m_fff_print; - AnyPtr m_outline_cache; void set_brim_and_skirt(); @@ -645,99 +643,6 @@ public: void apply_duplicates(); }; -template -class OutlineCachingArrangeable: public Arrangeable -{ - ArrblSubclass &m_arrbl; - Model &m_mdl; - mutable OutlineCache *m_cache; - -public: - OutlineCachingArrangeable(ArrblSubclass &arrbl, - Model &mdl, - OutlineCache *cache) - : m_arrbl{arrbl}, m_mdl{mdl}, m_cache{cache} - {} - - ObjectID id() const override { return m_arrbl.id(); } - ObjectID geometry_id() const override { return m_arrbl.geometry_id(); } - - ExPolygons full_outline() const override; - Polygon convex_outline() const override { return m_arrbl.convex_outline(); } - - ExPolygons full_envelope() const override { return m_arrbl.full_envelope(); } - Polygon convex_envelope() const override { return m_arrbl.convex_envelope(); } - - void transform(const Vec2d &transl, double rot) override - { - if constexpr (!std::is_const_v) - m_arrbl.transform(transl, rot); - } - - bool is_printable() const override { return m_arrbl.is_printable(); } - bool is_selected() const override { return m_arrbl.is_selected(); } - int priority() const override { return m_arrbl.priority(); } - void imbue_data(AnyWritable &datastore) const override { m_arrbl.imbue_data(datastore); } - int get_bed_index() const override { return m_arrbl.get_bed_index(); } - bool assign_bed(int bed_idx) override - { - bool ret = false; - - if constexpr (!std::is_const_v) - ret = m_arrbl.assign_bed(bed_idx); - - return ret; - } -}; - -extern template class OutlineCachingArrangeable; -extern template class OutlineCachingArrangeable; - -class OutlineCachingArrangeableModel: public ArrangeableModel { - ArrangeableSlicerModel &m_amodel; - mutable AnyPtr m_cache; - -public: - OutlineCachingArrangeableModel(ArrangeableSlicerModel &amodel, - AnyPtr cache) - : m_amodel{amodel}, m_cache{std::move(cache)} - {} - - void for_each_arrangeable(std::function fn) override - { - m_amodel.for_each_arrangeable([this, &fn](Arrangeable &arrbl){ - OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; - fn (oc_arrbl); - }); - } - void for_each_arrangeable(std::function fn) const override - { - m_amodel.for_each_arrangeable([this, &fn](Arrangeable &arrbl){ - OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; - fn (oc_arrbl); - }); - } - void visit_arrangeable(const ObjectID &id, std::function fn) const override - { - m_amodel.visit_arrangeable(id, [this, &fn](Arrangeable &arrbl){ - OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; - fn (oc_arrbl); - }); - } - void visit_arrangeable(const ObjectID &id, std::function fn) override - { - m_amodel.visit_arrangeable(id, [this, &fn](Arrangeable &arrbl) { - OutlineCachingArrangeable oc_arrbl{arrbl, m_amodel.get_model(), m_cache.get()}; - fn (oc_arrbl); - }); - } - - ObjectID add_arrangeable(const ObjectID &prototype_id) override - { - return m_amodel.add_arrangeable(prototype_id); - } -}; - } // namespace arr2 } // namespace Slic3r diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 97d753c939..75cef23e6d 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -232,8 +232,6 @@ set(SLIC3R_SOURCES Arrange/Scene.cpp Arrange/SceneBuilder.hpp Arrange/SceneBuilder.cpp - Arrange/OutlineCaching.hpp - Arrange/OutlineCaching.cpp Arrange/Tasks/ArrangeTask.hpp Arrange/Tasks/ArrangeTaskImpl.hpp Arrange/Tasks/FillBedTask.hpp From 183afab8e72d4fa48d84fc174cf3d80bac232738 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jul 2023 16:15:14 +0200 Subject: [PATCH 061/120] SimpleArrangeItem now usable with arrange tasks For future comparisons and measurements --- src/libslic3r/Arrange/ArrangeImpl.hpp | 8 +++-- .../Arrange/Items/SimpleArrangeItem.cpp | 9 ++++++ .../Arrange/Items/SimpleArrangeItem.hpp | 30 +++++++++++++++++++ .../Arrange/Items/WritableItemTraits.hpp | 2 +- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 57e9e1f358..0e7b30f6e5 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -123,7 +123,7 @@ void arrange(SelectionStrategy &&selstrategy, d += corr; for (auto &itm : items) - if (itm.bed_idx() == static_cast(bedidx) && !is_wipe_tower(itm)) + if (get_bed_index(itm) == static_cast(bedidx) && !is_wipe_tower(itm)) translate(itm, d); } } @@ -386,7 +386,8 @@ ArrItem ConvexItemConverter::convert(const Arrangeable &arrbl, set_priority(ret, arrbl.priority()); imbue_id(ret, arrbl.id()); - arrbl.imbue_data(AnyWritableDataStore{ret}); + if constexpr (IsWritableDataStore) + arrbl.imbue_data(AnyWritableDataStore{ret}); return ret; } @@ -401,7 +402,8 @@ ArrItem AdvancedItemConverter::convert(const Arrangeable &arrbl, set_bed_index(ret, bed_index); set_priority(ret, arrbl.priority()); imbue_id(ret, arrbl.id()); - arrbl.imbue_data(AnyWritableDataStore{ret}); + if constexpr (IsWritableDataStore) + arrbl.imbue_data(AnyWritableDataStore{ret}); return ret; } diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp index 75ab1cf423..2e96290a4c 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp @@ -1,5 +1,8 @@ #include "SimpleArrangeItem.hpp" #include "libslic3r/Arrange/ArrangeImpl.hpp" +#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" +#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" +#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" namespace Slic3r { namespace arr2 { @@ -12,4 +15,10 @@ Polygon SimpleArrangeItem::outline() const return ret; } +template class ArrangeableToItemConverter; +template struct ArrangeTask; +template struct FillBedTask; +template struct MultiplySelectionTask; +template class Arranger; + }} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp index 54d7866ba1..f097ca7619 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp @@ -7,6 +7,9 @@ #include "libslic3r/Arrange/Core/NFP/NFP.hpp" #include "libslic3r/Arrange/Arrange.hpp" +#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" +#include "libslic3r/Arrange/Tasks/FillBedTask.hpp" +#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" @@ -24,6 +27,7 @@ class SimpleArrangeItem { int m_bed_idx = Unarranged; std::vector m_allowed_rotations = {0.}; + ObjectID m_obj_id; public: explicit SimpleArrangeItem(Polygon chull = {}): m_shape{std::move(chull)} {} @@ -52,6 +56,9 @@ public: { m_allowed_rotations = std::move(rots); } + + void set_object_id(const ObjectID &id) noexcept { m_obj_id = id; } + const ObjectID & get_object_id() const noexcept { return m_obj_id; } }; template<> struct NFPArrangeItemTraits_ @@ -183,6 +190,29 @@ struct WritableItemTraits_ { } }; +template<> struct ImbueableItemTraits_ +{ + static void imbue_id(SimpleArrangeItem &itm, const ObjectID &id) + { + itm.set_object_id(id); + } + + static std::optional retrieve_id(const SimpleArrangeItem &itm) + { + std::optional ret; + if (itm.get_object_id().valid()) + ret = itm.get_object_id(); + + return ret; + } +}; + +extern template class ArrangeableToItemConverter; +extern template struct ArrangeTask; +extern template struct FillBedTask; +extern template struct MultiplySelectionTask; +extern template class Arranger; + }} // namespace Slic3r::arr2 #endif // SIMPLEARRANGEITEM_HPP diff --git a/src/libslic3r/Arrange/Items/WritableItemTraits.hpp b/src/libslic3r/Arrange/Items/WritableItemTraits.hpp index f80f449730..552564dcfe 100644 --- a/src/libslic3r/Arrange/Items/WritableItemTraits.hpp +++ b/src/libslic3r/Arrange/Items/WritableItemTraits.hpp @@ -96,7 +96,7 @@ void set_arbitrary_data(Itm &itm, const std::string &key, T &&data) template void set_allowed_rotations(Itm &itm, const std::vector &rotations) { - WritableItemTraits::set_arbitrary_data(itm, "rotations", rotations); + WritableItemTraits::set_allowed_rotations(itm, rotations); } template int raise_priority(ArrItem &itm) From 2224ce0ca7acc06117551482e4deed18e1f84506 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jul 2023 17:27:27 +0200 Subject: [PATCH 062/120] Rename WritableItemTraits to MutableItemTraits makes more sense, avoids confusion with WritableDataStore --- src/libslic3r/Arrange/Arrange.hpp | 2 +- src/libslic3r/Arrange/ArrangeImpl.hpp | 2 +- src/libslic3r/Arrange/Items/ArrangeItem.hpp | 6 ++-- ...leItemTraits.hpp => MutableItemTraits.hpp} | 34 +++++++++---------- .../Arrange/Items/SimpleArrangeItem.hpp | 6 ++-- .../Arrange/Items/TrafoOnlyArrangeItem.hpp | 4 +-- src/libslic3r/CMakeLists.txt | 2 +- tests/arrange/test_arrange.cpp | 6 ++-- 8 files changed, 31 insertions(+), 31 deletions(-) rename src/libslic3r/Arrange/Items/{WritableItemTraits.hpp => MutableItemTraits.hpp} (73%) diff --git a/src/libslic3r/Arrange/Arrange.hpp b/src/libslic3r/Arrange/Arrange.hpp index 1578fed1c6..96bc007782 100644 --- a/src/libslic3r/Arrange/Arrange.hpp +++ b/src/libslic3r/Arrange/Arrange.hpp @@ -2,7 +2,7 @@ #define ARRANGE2_HPP #include "Scene.hpp" -#include "Items/WritableItemTraits.hpp" +#include "Items/MutableItemTraits.hpp" #include "Core/NFP/NFPArrangeItemTraits.hpp" #include "libslic3r/MinAreaBoundingBox.hpp" diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 0e7b30f6e5..3cec1afee9 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -13,7 +13,7 @@ #include "Core/NFP/RectangleOverfitPackingStrategy.hpp" #include "Core/Beds.hpp" -#include "Items/WritableItemTraits.hpp" +#include "Items/MutableItemTraits.hpp" #include "SegmentedRectangleBed.hpp" diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.hpp b/src/libslic3r/Arrange/Items/ArrangeItem.hpp index 5362431e49..963bc899f7 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.hpp @@ -12,7 +12,7 @@ #include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" #include "libslic3r/Arrange/Core/NFP/NFP.hpp" -#include "libslic3r/Arrange/Items/WritableItemTraits.hpp" +#include "libslic3r/Arrange/Items/MutableItemTraits.hpp" #include "libslic3r/Arrange/Arrange.hpp" #include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" @@ -433,10 +433,10 @@ template<> struct NFPArrangeItemTraits_ { } }; -template<> struct IsWritableItem_: public std::true_type {}; +template<> struct IsMutableItem_: public std::true_type {}; template<> -struct WritableItemTraits_ { +struct MutableItemTraits_ { static void set_priority(ArrangeItem &itm, int p) { itm.priority(p); } static void set_convex_shape(ArrangeItem &itm, const Polygon &shape) diff --git a/src/libslic3r/Arrange/Items/WritableItemTraits.hpp b/src/libslic3r/Arrange/Items/MutableItemTraits.hpp similarity index 73% rename from src/libslic3r/Arrange/Items/WritableItemTraits.hpp rename to src/libslic3r/Arrange/Items/MutableItemTraits.hpp index 552564dcfe..7668d08438 100644 --- a/src/libslic3r/Arrange/Items/WritableItemTraits.hpp +++ b/src/libslic3r/Arrange/Items/MutableItemTraits.hpp @@ -1,5 +1,5 @@ -#ifndef WRITABLEITEMTRAITS_HPP -#define WRITABLEITEMTRAITS_HPP +#ifndef MutableItemTraits_HPP +#define MutableItemTraits_HPP #include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" #include "libslic3r/Arrange/Core/DataStoreTraits.hpp" @@ -8,15 +8,15 @@ namespace Slic3r { namespace arr2 { -template struct IsWritableItem_ : public std::false_type +template struct IsMutableItem_ : public std::false_type {}; // Using this interface to set up any arrange item. Provides default // implementation but it needs to be explicitly switched on with -// IsWritableItem_ or completely reimplement a specialization. -template struct WritableItemTraits_ +// IsMutableItem_ or completely reimplement a specialization. +template struct MutableItemTraits_ { - static_assert(IsWritableItem_::value, "Not a Writable item type!"); + static_assert(IsMutableItem_::value, "Not a Writable item type!"); static void set_priority(Itm &itm, int p) { itm.set_priority(p); } @@ -55,48 +55,48 @@ template struct WritableItemTraits_ }; template -using WritableItemTraits = WritableItemTraits_>; +using MutableItemTraits = MutableItemTraits_>; -template constexpr bool IsWritableItem = IsWritableItem_::value; +template constexpr bool IsMutableItem = IsMutableItem_::value; template -using WritableItemOnly = std::enable_if_t, TT>; +using MutableItemOnly = std::enable_if_t, TT>; template void set_priority(Itm &itm, int p) { - WritableItemTraits::set_priority(itm, p); + MutableItemTraits::set_priority(itm, p); } template void set_convex_shape(Itm &itm, const Polygon &shape) { - WritableItemTraits::set_convex_shape(itm, shape); + MutableItemTraits::set_convex_shape(itm, shape); } template void set_shape(Itm &itm, const ExPolygons &shape) { - WritableItemTraits::set_shape(itm, shape); + MutableItemTraits::set_shape(itm, shape); } template void set_convex_envelope(Itm &itm, const Polygon &envelope) { - WritableItemTraits::set_convex_envelope(itm, envelope); + MutableItemTraits::set_convex_envelope(itm, envelope); } template void set_envelope(Itm &itm, const ExPolygons &envelope) { - WritableItemTraits::set_envelope(itm, envelope); + MutableItemTraits::set_envelope(itm, envelope); } template void set_arbitrary_data(Itm &itm, const std::string &key, T &&data) { - WritableItemTraits::set_arbitrary_data(itm, key, std::forward(data)); + MutableItemTraits::set_arbitrary_data(itm, key, std::forward(data)); } template void set_allowed_rotations(Itm &itm, const std::vector &rotations) { - WritableItemTraits::set_allowed_rotations(itm, rotations); + MutableItemTraits::set_allowed_rotations(itm, rotations); } template int raise_priority(ArrItem &itm) @@ -133,4 +133,4 @@ template int lowest_priority(const Range &item_range) }} // namespace Slic3r::arr2 -#endif // WRITABLEITEMTRAITS_HPP +#endif // MutableItemTraits_HPP diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp index f097ca7619..a18d85e8cd 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp @@ -14,7 +14,7 @@ #include "libslic3r/Polygon.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" -#include "WritableItemTraits.hpp" +#include "MutableItemTraits.hpp" namespace Slic3r { namespace arr2 { @@ -157,10 +157,10 @@ template<> struct NFPArrangeItemTraits_ } }; -template<> struct IsWritableItem_: public std::true_type {}; +template<> struct IsMutableItem_: public std::true_type {}; template<> -struct WritableItemTraits_ { +struct MutableItemTraits_ { static void set_priority(SimpleArrangeItem &itm, int p) { itm.set_priority(p); } static void set_convex_shape(SimpleArrangeItem &itm, const Polygon &shape) diff --git a/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp b/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp index 90b6651563..37a48b231c 100644 --- a/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp @@ -4,7 +4,7 @@ #include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" #include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp" -#include "libslic3r/Arrange/Items/WritableItemTraits.hpp" +#include "libslic3r/Arrange/Items/MutableItemTraits.hpp" namespace Slic3r { namespace arr2 { @@ -58,7 +58,7 @@ template<> struct DataStoreTraits_ } }; -template<> struct IsWritableItem_: public std::true_type {}; +template<> struct IsMutableItem_: public std::true_type {}; template<> struct WritableDataStoreTraits_ { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 75cef23e6d..6f8a759f3f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -223,7 +223,7 @@ set(SLIC3R_SOURCES Arrange/Items/SimpleArrangeItem.hpp Arrange/Items/SimpleArrangeItem.cpp Arrange/Items/TrafoOnlyArrangeItem.hpp - Arrange/Items/WritableItemTraits.hpp + Arrange/Items/MutableItemTraits.hpp Arrange/Items/ArbitraryDataStore.hpp Arrange/ArrangeSettingsView.hpp Arrange/ArrangeSettingsDb_AppCfg.hpp diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp index 0cb2c21511..0d2e1152e4 100644 --- a/tests/arrange/test_arrange.cpp +++ b/tests/arrange/test_arrange.cpp @@ -364,13 +364,13 @@ TEST_CASE("Static type tests for arrange items", "[arrange2]") using namespace Slic3r; REQUIRE(arr2::IsDataStore); - REQUIRE(arr2::IsWritableItem); + REQUIRE(arr2::IsMutableItem); REQUIRE(! arr2::IsDataStore); - REQUIRE(arr2::IsWritableItem); + REQUIRE(arr2::IsMutableItem); REQUIRE(arr2::IsDataStore); - REQUIRE(arr2::IsWritableItem); + REQUIRE(arr2::IsMutableItem); } template Bed init_bed() { return {}; } From c545400b0a208644a0fbe7d1b93d787fef309c11 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Sat, 29 Jul 2023 17:32:54 +0200 Subject: [PATCH 063/120] fixing sequential print settings not being loaded correctly SPE-1827 --- .../Arrange/ArrangeSettingsDb_AppCfg.cpp | 47 ++------------ .../Arrange/ArrangeSettingsDb_AppCfg.hpp | 65 +++++++++++-------- src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp | 19 ++++-- src/slic3r/GUI/GLCanvas3D.cpp | 35 +++++++--- src/slic3r/GUI/GLCanvas3D.hpp | 3 + 5 files changed, 86 insertions(+), 83 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp index 9353d4aac2..66ef53ec29 100644 --- a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp +++ b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp @@ -2,31 +2,7 @@ namespace Slic3r { -PrinterTechnology ArrangeSettingsDb_AppCfg::current_printer_technology() const -{ - PrinterTechnology pt = ptFFF; - - if (m_printtech_getter) - pt = m_printtech_getter(); - - return pt; -} - -const DynamicPrintConfig *ArrangeSettingsDb_AppCfg::config() const -{ - const DynamicPrintConfig *ret = nullptr; - - if (m_config_getter) - ret = m_config_getter(); - - return ret; -} - -ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg( - AppConfig *appcfg, - std::function cfgfn, - std::function printtech_fn) - : m_appcfg{appcfg}, m_config_getter{cfgfn}, m_printtech_getter{printtech_fn} +ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg(AppConfig *appcfg) : m_appcfg{appcfg} { m_settings_fff.postfix = "_fff"; m_settings_fff_seq.postfix = "_fff_seq_print"; @@ -149,33 +125,20 @@ ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg( m_settings_sla.vals.arr_strategy = arr_strategy; m_settings_fff.vals.arr_strategy = arr_strategy; m_settings_fff_seq.vals.arr_strategy = arr_strategy; - - if (config()) { - // Set default obj distance for fff sequential print mode - m_settings_fff_seq.defaults.d_obj = - std::max(m_settings_fff_seq.defaults.d_obj, - float(min_object_distance(*config()))); - } } void ArrangeSettingsDb_AppCfg::distance_from_obj_range(float &min, float &max) const { - min = 0.f; - if (config() && current_printer_technology() == ptFFF) { - auto co_opt = config()->option("complete_objects"); - if (co_opt && co_opt->value) { - min = float(min_object_distance(*config())); - } - } - max = 100.f; + min = get_slot(this).dobj_range.minval; + max = get_slot(this).dobj_range.maxval; } void ArrangeSettingsDb_AppCfg::distance_from_bed_range(float &min, float &max) const { - min = 0.f; - max = 100.f; + min = get_slot(this).dbed_range.minval; + max = get_slot(this).dbed_range.maxval; } arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_distance_from_objects(float v) diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp index 908deb73bc..1815fffbe6 100644 --- a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp +++ b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp @@ -9,47 +9,47 @@ namespace Slic3r { class ArrangeSettingsDb_AppCfg: public arr2::ArrangeSettingsDb { - AppConfig *m_appcfg; - std::function m_config_getter; - std::function m_printtech_getter; +public: + enum Slots { slotFFF, slotFFFSeqPrint, slotSLA }; - struct Slot { Values vals; Values defaults; std::string postfix; }; +private: + AppConfig *m_appcfg; + Slots m_current_slot = slotFFF; + + struct FloatRange { float minval = 0.f, maxval = 100.f; }; + struct Slot + { + Values vals; + Values defaults; + FloatRange dobj_range, dbed_range; + std::string postfix; + }; // Settings and their defaults are stored separately for fff, // sla and fff sequential mode Slot m_settings_fff, m_settings_fff_seq, m_settings_sla; - PrinterTechnology current_printer_technology() const; - const DynamicPrintConfig *config() const; - template - static auto & get_slot(Self *self) { - PrinterTechnology ptech = self->current_printer_technology(); - - auto *ptr = &self->m_settings_fff; - - if (ptech == ptSLA) { - ptr = &self->m_settings_sla; - } else if (ptech == ptFFF && self->config()) { - auto co_opt = self->config()->template option( - "complete_objects"); - if (co_opt && co_opt->value) - ptr = &self->m_settings_fff_seq; - else - ptr = &self->m_settings_fff; + static auto & get_slot(Self *self, Slots slot) { + switch(slot) { + case slotFFF: return self->m_settings_fff; + case slotFFFSeqPrint: return self->m_settings_fff_seq; + case slotSLA: return self->m_settings_sla; } - return *ptr; + return self->m_settings_fff; + } + + template static auto &get_slot(Self *self) + { + return get_slot(self, self->m_current_slot); } template static auto& get_ref(Self *self) { return get_slot(self).vals; } public: - explicit ArrangeSettingsDb_AppCfg( - AppConfig *appcfg, - std::function cfgfn, - std::function printtech_getter); + explicit ArrangeSettingsDb_AppCfg(AppConfig *appcfg); float get_distance_from_objects() const override { return get_ref(this).d_obj; } float get_distance_from_bed() const override { return get_ref(this).d_bed; } @@ -71,6 +71,19 @@ public: ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) override; Values get_defaults() const override { return get_slot(this).defaults; } + + void set_active_slot(Slots slot) noexcept { m_current_slot = slot; } + void set_distance_from_obj_range(Slots slot, float min, float max) + { + get_slot(this, slot).dobj_range = FloatRange{min, max}; + } + + void set_distance_from_bed_range(Slots slot, float min, float max) + { + get_slot(this, slot).dbed_range = FloatRange{min, max}; + } + + Values &get_defaults(Slots slot) { return get_slot(this, slot).defaults; } }; } // namespace Slic3r diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp index 9c75ce1cfa..b03ef6efd4 100644 --- a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp @@ -53,16 +53,24 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) m_db->distance_from_obj_range(dobj_min, dobj_max); m_db->distance_from_bed_range(dbed_min, dbed_max); + if(dobj_min > settings.d_obj) { + settings.d_obj = std::max(dobj_min, settings.d_obj); + m_db->set_distance_from_objects(settings.d_obj); + } + + if (dbed_min > settings.d_bed) { + settings.d_bed = std::max(dbed_min, settings.d_bed); + m_db->set_distance_from_bed(settings.d_bed); + } + if (m_imgui->slider_float(_L("Spacing"), &settings.d_obj, dobj_min, - dobj_max, "%5.2f") || - dobj_min > settings.d_obj) { + dobj_max, "%5.2f")) { settings.d_obj = std::max(dobj_min, settings.d_obj); m_db->set_distance_from_objects(settings.d_obj); } if (m_imgui->slider_float(_L("Spacing from bed"), &settings.d_bed, - dbed_min, dbed_max, "%5.2f") || - dbed_min > settings.d_bed) { + dbed_min, dbed_max, "%5.2f")) { settings.d_bed = std::max(dbed_min, settings.d_bed); m_db->set_distance_from_bed(settings.d_bed); } @@ -71,8 +79,7 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) m_db->set_rotation_enabled(settings.rotations); } -// Points bed = m_config ? get_bed_shape(*m_config) : Points{}; - if (/*arrangement::is_box(bed) */ m_show_xl_combo_predicate() && + if (m_show_xl_combo_predicate() && settings.xl_align >= 0 && m_imgui->combo(_L("Alignment"), {_u8L("Center"), _u8L("Rear left"), _u8L("Front left"), diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fb2be672df..d9afc10346 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -35,7 +35,6 @@ #include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" #include "slic3r/Utils/UndoRedo.hpp" -#include "libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp" #if ENABLE_RETINA_GL #include "slic3r/Utils/RetinaHelper.hpp" @@ -1297,7 +1296,7 @@ void GLCanvas3D::SLAView::select_full_instance(const GLVolume::CompositeID& id) PrinterTechnology GLCanvas3D::current_printer_technology() const { - return m_process->current_printer_technology(); + return m_process ? m_process->current_printer_technology() : ptFFF; } bool GLCanvas3D::is_arrange_alignment_enabled() const @@ -1339,13 +1338,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed) m_labels(*this), m_slope(m_volumes), m_sla_view(*this), - m_arrange_settings_dialog{wxGetApp().imgui(), - std::make_unique( - wxGetApp().app_config, - [this]() { return m_config; }, - [this] { - return current_printer_technology(); - })} + m_arrange_settings_db{wxGetApp().app_config}, + m_arrange_settings_dialog{wxGetApp().imgui(), &m_arrange_settings_db} { if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); @@ -1628,6 +1622,29 @@ void GLCanvas3D::set_config(const DynamicPrintConfig* config) { m_config = config; m_layers_editing.set_config(config); + + + if (config) { + PrinterTechnology ptech = current_printer_technology(); + + auto slot = ArrangeSettingsDb_AppCfg::slotFFF; + + if (ptech == ptSLA) { + slot = ArrangeSettingsDb_AppCfg::slotSLA; + } else if (ptech == ptFFF) { + auto co_opt = config->option("complete_objects"); + if (co_opt && co_opt->value) + slot = ArrangeSettingsDb_AppCfg::slotFFFSeqPrint; + else + slot = ArrangeSettingsDb_AppCfg::slotFFF; + } + + m_arrange_settings_db.set_active_slot(slot); + + double objdst = min_object_distance(*config); + m_arrange_settings_db.set_distance_from_obj_range(slot, min_object_distance(*config), 100.); + m_arrange_settings_db.get_defaults(slot).d_obj = objdst; + } } void GLCanvas3D::set_process(BackgroundSlicingProcess *process) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a25fddaa67..8ec2260467 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -18,6 +18,8 @@ #include "Camera.hpp" #include "SceneRaycaster.hpp" #include "GUI_Utils.hpp" + +#include "libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp" #include "ArrangeSettingsDialogImgui.hpp" #include "libslic3r/Slicing.hpp" @@ -586,6 +588,7 @@ private: bool is_arrange_alignment_enabled() const; + ArrangeSettingsDb_AppCfg m_arrange_settings_db; ArrangeSettingsDialogImgui m_arrange_settings_dialog; public: From 708f58949d0214cb7b7f4ae8b68372b76259fdb5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 31 Jul 2023 15:01:46 +0200 Subject: [PATCH 064/120] Fix newly introduced problem with unsettable zero minimum object dist --- src/slic3r/GUI/GLCanvas3D.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d9afc10346..d3a7a5b406 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1642,7 +1642,8 @@ void GLCanvas3D::set_config(const DynamicPrintConfig* config) m_arrange_settings_db.set_active_slot(slot); double objdst = min_object_distance(*config); - m_arrange_settings_db.set_distance_from_obj_range(slot, min_object_distance(*config), 100.); + double min_obj_dst = slot == ArrangeSettingsDb_AppCfg::slotFFFSeqPrint ? objdst : 0.; + m_arrange_settings_db.set_distance_from_obj_range(slot, min_obj_dst, 100.); m_arrange_settings_db.get_defaults(slot).d_obj = objdst; } } From b2a27101285a9e937128fe8b79b63de4b4291dba Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 31 Jul 2023 15:02:41 +0200 Subject: [PATCH 065/120] Fix arranging unprintable objects fixes #10911 fixes SPE-1830 --- .../Arrange/Core/ArrangeItemTraits.hpp | 5 ++ .../NFP/RectangleOverfitPackingStrategy.hpp | 69 ++++++++++++------- .../Arrange/Tasks/ArrangeTaskImpl.hpp | 29 +++++++- 3 files changed, 76 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp b/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp index 5806627eb1..81acc863cf 100644 --- a/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp +++ b/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp @@ -93,6 +93,11 @@ template bool is_fixed(const ArrItem &ap) return get_bed_index(ap) >= PhysicalBedId; } +template bool is_on_physical_bed(const ArrItem &ap) +{ + return get_bed_index(ap) == PhysicalBedId; +} + template void translate(ArrItem &ap, const Vec2crd &t) { set_translation(ap, get_translation(ap) + t); diff --git a/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp b/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp index eb41094df6..8efb0ac27e 100644 --- a/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp +++ b/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp @@ -19,6 +19,36 @@ struct CenterAlignmentFn { } }; +template +struct RectangleOverfitPackingContext : public DefaultPackingContext +{ + BoundingBox limits; + int bed_index; + PostAlignmentFn post_alignment_fn; + + explicit RectangleOverfitPackingContext(const BoundingBox limits, + int bedidx, + PostAlignmentFn alignfn = CenterAlignmentFn{}) + : limits{limits}, bed_index{bedidx}, post_alignment_fn{alignfn} + {} + + void align_pile() + { + // Here, the post alignment can be safely done. No throwing + // functions are called! + if (fixed_items_range(*this).empty()) { + auto itms = packed_items_range(*this); + auto pilebb = bounding_box(itms); + + for (auto &itm : itms) { + translate(itm, post_alignment_fn(limits, pilebb)); + } + } + } + + ~RectangleOverfitPackingContext() { align_pile(); } +}; + // With rectange bed, and no fixed items, an infinite bed with // RectangleOverfitKernelWrapper can produce better results than a pure // RectangleBed with inner-fit polygon calculation. @@ -29,31 +59,7 @@ struct RectangleOverfitPackingStrategy { PostAlignmentFn post_alignment_fn = CenterAlignmentFn{}; template - struct Context: public DefaultPackingContext { - BoundingBox limits; - int bed_index; - PostAlignmentFn post_alignment_fn; - - explicit Context(const BoundingBox limits, - int bedidx, - PostAlignmentFn alignfn = CenterAlignmentFn{}) - : limits{limits}, bed_index{bedidx}, post_alignment_fn{alignfn} - {} - - ~Context() - { - // Here, the post alignment can be safely done. No throwing - // functions are called! - if (fixed_items_range(*this).empty()) { - auto itms = packed_items_range(*this); - auto pilebb = bounding_box(itms); - - for (auto &itm : itms) { - translate(itm, post_alignment_fn(limits, pilebb)); - } - } - } - }; + using Context = RectangleOverfitPackingContext; RectangleOverfitPackingStrategy(PackStrategyNFP s, PostAlignmentFn post_align_fn) @@ -89,6 +95,19 @@ struct PackStrategyTraits_> { } }; +template +struct PackingContextTraits_> + : public PackingContextTraits_> +{ + static void add_packed_item(RectangleOverfitPackingContext &ctx, ArrItem &itm) + { + ctx.add_packed_item(itm); + + // to prevent coords going out of range + ctx.align_pile(); + } +}; + template bool pack(Strategy &strategy, const Bed &bed, diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index d4ff0244d2..703e6866ff 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -54,6 +54,20 @@ std::unique_ptr> ArrangeTask::create( return task; } +// Remove all items on the physical bed (not occupyable for unprintable items) +// and shift all items to the next lower bed index, so that arrange will think +// that logical bed no. 1 is the physical one +template +void prepare_fixed_unselected(ItemCont &items, int shift) +{ + for (auto &itm : items) + set_bed_index(itm, get_bed_index(itm) - shift); + + items.erase(std::remove_if(items.begin(), items.end(), + [](auto &itm) { return !is_arranged(itm); }), + items.end()); +} + template std::unique_ptr ArrangeTask::process_native(Ctl &ctl) @@ -78,8 +92,15 @@ ArrangeTask::process_native(Ctl &ctl) } subctl{ctl, *this}; - arranger->arrange(printable.selected, printable.unselected, bed, subctl); - arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl); + auto fixed_items = printable.unselected; + + // static (unselected) unprintable objects should not be overlapped by + // movable and printable objects + std::copy(unprintable.unselected.begin(), + unprintable.unselected.end(), + std::back_inserter(fixed_items)); + + arranger->arrange(printable.selected, fixed_items, bed, subctl); // Unprintable items should go to the first bed not containing any printable // items @@ -89,6 +110,10 @@ ArrangeTask::process_native(Ctl &ctl) // If there are no printables, leave the physical bed empty beds = std::max(beds, size_t{1}); + prepare_fixed_unselected(unprintable.unselected, beds); + + arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl); + result->add_items(crange(printable.selected)); for (auto &itm : unprintable.selected) { From eae9a76249f8a6fcd2d8c8425aa2e0c3e3d258a7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 9 Aug 2023 17:27:43 +0200 Subject: [PATCH 066/120] Addressing problem with items slightly out of bed due to simplification solves SPE-1824 --- src/libslic3r/Arrange/Arrange.hpp | 26 ++++++++++++++++++- src/libslic3r/Arrange/ArrangeImpl.hpp | 16 ++++++++++-- src/libslic3r/Arrange/Items/ArrangeItem.cpp | 7 ++--- .../Arrange/Tasks/ArrangeTaskImpl.hpp | 2 +- .../Arrange/Tasks/FillBedTaskImpl.hpp | 2 +- .../Tasks/MultiplySelectionTaskImpl.hpp | 2 +- 6 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Arrange/Arrange.hpp b/src/libslic3r/Arrange/Arrange.hpp index 96bc007782..36ab838739 100644 --- a/src/libslic3r/Arrange/Arrange.hpp +++ b/src/libslic3r/Arrange/Arrange.hpp @@ -90,6 +90,10 @@ public: virtual ArrItem convert(const Arrangeable &arrbl, coord_t offs = 0) const = 0; + // Returns the extent of simplification that the converter utilizes when + // creating arrange items. Zero shall mean no simplification at all. + virtual coord_t simplification_tolerance() const { return 0; } + static std::unique_ptr create( ArrangeSettingsView::GeometryHandling geometry_handling, coord_t safety_d); @@ -120,11 +124,19 @@ template class BasicItemConverter : public ArrangeableToItemConverter { coord_t m_safety_d; + coord_t m_simplify_tol; public: - BasicItemConverter(coord_t safety_d = 0) : m_safety_d{safety_d} {} + BasicItemConverter(coord_t safety_d = 0, coord_t simpl_tol = 0) + : m_safety_d{safety_d}, m_simplify_tol{simpl_tol} + {} coord_t safety_dist() const noexcept { return m_safety_d; } + + coord_t simplification_tolerance() const override + { + return m_simplify_tol; + } }; template @@ -231,6 +243,18 @@ double get_fit_into_bed_rotation(const ArrItem &itm, const RectangleBed &bed) return ret; } +template +auto get_corrected_bed(const ExtendedBed &bed, + const ArrangeableToItemConverter &converter) +{ + auto bedcpy = bed; + visit_bed([tol = -converter.simplification_tolerance()](auto &rawbed) { + rawbed = offset(rawbed, tol); + }, bedcpy); + + return bedcpy; +} + }} // namespace Slic3r::arr2 #endif // ARRANGE2_HPP diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 3cec1afee9..ce4ee03ebe 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -416,12 +416,22 @@ ArrItem AdvancedItemConverter::get_arritem(const Arrangeable &arrbl, auto outline = arrbl.full_outline(); auto envelope = arrbl.full_envelope(); + if (infl != 0) { outline = offset_ex(outline, infl); if (! envelope.empty()) envelope = offset_ex(envelope, infl); } + auto simpl_tol = static_cast(this->simplification_tolerance()); + + if (simpl_tol > 0) + { + outline = expolygons_simplify(outline, simpl_tol); + if (!envelope.empty()) + envelope = expolygons_simplify(envelope, simpl_tol); + } + ArrItem ret; set_shape(ret, outline); if (! envelope.empty()) @@ -448,15 +458,17 @@ ArrangeableToItemConverter::create( { std::unique_ptr> ret; + constexpr coord_t SimplifyTol = scaled(.2); + switch(gh) { case arr2::ArrangeSettingsView::ghConvex: ret = std::make_unique>(safety_d); break; case arr2::ArrangeSettingsView::ghBalanced: - ret = std::make_unique>(safety_d); + ret = std::make_unique>(safety_d, SimplifyTol); break; case arr2::ArrangeSettingsView::ghAdvanced: - ret = std::make_unique>(safety_d); + ret = std::make_unique>(safety_d, SimplifyTol); break; default: ; diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.cpp b/src/libslic3r/Arrange/Items/ArrangeItem.cpp index 63d15e75a6..fb4c1845ee 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.cpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.cpp @@ -114,8 +114,7 @@ Vec2crd DecomposedShape::centroid() const DecomposedShape decompose(const ExPolygons &shape) { - ExPolygons shape_s = expolygons_simplify(shape, scaled(.2)); - return DecomposedShape{convex_decomposition_tess(shape_s)}; + return DecomposedShape{convex_decomposition_tess(shape)}; } DecomposedShape decompose(const Polygon &shape) @@ -126,9 +125,7 @@ DecomposedShape decompose(const Polygon &shape) if (is_convex) { convex_shapes.emplace_back(shape); } else { - Polygon shape_s = shape; - shape_s.simplify(scaled(.2)); - convex_shapes = convex_decomposition_tess(shape_s); + convex_shapes = convex_decomposition_tess(shape); } return DecomposedShape{std::move(convex_shapes)}; diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index 703e6866ff..c9f48f93e4 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -47,7 +47,7 @@ std::unique_ptr> ArrangeTask::create( task->settings.set_from(sc.settings()); - task->bed = sc.bed(); + task->bed = get_corrected_bed(sc.bed(), converter); extract_selected(*task, sc.model(), converter); diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp index 62b0d78270..a99102418f 100644 --- a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp @@ -124,7 +124,7 @@ std::unique_ptr> FillBedTask::create( task->settings.set_from(sc.settings()); - task->bed = sc.bed(); + task->bed = get_corrected_bed(sc.bed(), converter); extract(*task, sc, converter); diff --git a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp index 0abf31376d..d4c925c3bf 100644 --- a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp @@ -15,7 +15,7 @@ std::unique_ptr> MultiplySelectionTask:: task.settings.set_from(scene.settings()); - task.bed = scene.bed(); + task.bed = get_corrected_bed(scene.bed(), itm_conv); task.prototype_item = {}; From e5a067f9aed23adc40e14d890965b2436f7607d8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 9 Aug 2023 17:57:04 +0200 Subject: [PATCH 067/120] hide "strategy" combo box Only "auto" will be used --- src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp | 10 ---------- src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp index b03ef6efd4..1dc442906e 100644 --- a/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp @@ -102,16 +102,6 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) settings.geom_handling)); } - if (m_imgui->combo(_L("Strategy"), - {_u8L("Automatic"), _u8L("Pull to center")}, - settings.arr_strategy)) { - if (settings.arr_strategy >= 0 && - settings.arr_strategy < ArrangeSettingsView::asCount) - m_db->set_arrange_strategy( - static_cast( - settings.arr_strategy)); - } - ImGui::Separator(); if (m_imgui->button(_L("Reset defaults"))) { diff --git a/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp b/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp index 2c46a0732b..d05a35f348 100644 --- a/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp +++ b/src/slic3r/GUI/ArrangeSettingsDialogImgui.hpp @@ -45,7 +45,7 @@ public: XLPivots get_xl_alignment() const override { return m_db->get_xl_alignment(); } GeometryHandling get_geometry_handling() const override { return m_db->get_geometry_handling(); } - ArrangeStrategy get_arrange_strategy() const override { return m_db->get_arrange_strategy(); } + ArrangeStrategy get_arrange_strategy() const override { return arr2::ArrangeSettingsView::asAuto; } }; }} // namespace Slic3r::GUI From 3455c8cc1a7a567672d903357cf8001ab5cd5173 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 11 Aug 2023 16:06:54 +0200 Subject: [PATCH 068/120] Hide alignment combo on arrange dialog if XL has wipe tower fixes SPE-1787 --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d3a7a5b406..f0b0ec88b2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1301,7 +1301,7 @@ PrinterTechnology GLCanvas3D::current_printer_technology() const bool GLCanvas3D::is_arrange_alignment_enabled() const { - return m_config ? is_XL_printer(*m_config) : false; + return m_config ? is_XL_printer(*m_config) && !this->get_wipe_tower_info() : false; } GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed) From 5b9d5b55b6f7853962ddabc57d8d25d7a588a8ad Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 14 Aug 2023 19:14:49 +0200 Subject: [PATCH 069/120] Disable "brim" offset heuristic for sla objects Fixes bug 1 in SPE-1824 --- src/libslic3r/Arrange/SceneBuilder.cpp | 32 -------------------------- 1 file changed, 32 deletions(-) diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index b80f48a436..f2bbc38876 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -653,22 +653,6 @@ ExPolygons ArrangeableSLAPrintObject::full_envelope() const Polygons ptmp = project_mesh(pmesh.its, trafo_instance, [] {}); ret = union_ex(ret, ptmp); } - } else { - // The 1.1 multiplier is a safety gap, as the offset might be bigger - // in sharp edges of a polygon, depending on clipper's offset algorithm - coord_t pad_infl = 0; - { - double infl = m_po->config().pad_enable.getBool() * - (m_po->config().pad_brim_size.getFloat() + - m_po->config().pad_around_object.getBool() * - m_po->config().pad_object_gap.getFloat()); - - pad_infl = scaled(1.1 * infl); - } - - if (pad_infl > 0) { - ret = offset_ex(ret, pad_infl); - } } return ret; @@ -722,22 +706,6 @@ Polygon ArrangeableSLAPrintObject::convex_envelope() const polys.emplace_back( its_convex_hull_2d_above(pmesh.its, trafo_instance, zlvl)); } - } else { - // The 1.1 multiplier is a safety gap, as the offset might be bigger - // in sharp edges of a polygon, depending on clipper's offset algorithm - coord_t pad_infl = 0; - { - double infl = m_po->config().pad_enable.getBool() * - (m_po->config().pad_brim_size.getFloat() + - m_po->config().pad_around_object.getBool() * - m_po->config().pad_object_gap.getFloat()); - - pad_infl = scaled(1.1 * infl); - } - - if (pad_infl > 0) { - polys = offset(polys, pad_infl); - } } return Geometry::convex_hull(polys); From 0c0a1af1e98941982d60f42841666311cbaea87b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 14 Aug 2023 19:54:16 +0200 Subject: [PATCH 070/120] Temporarily disable rotations for wipe tower when arranging It is problematic due to varying interpretation of rotation in arrange and slicer's wipe tower handling fixes SPE-1787 --- src/libslic3r/Arrange/ArrangeImpl.hpp | 3 ++ src/libslic3r/Arrange/SceneBuilder.hpp | 13 +------ src/slic3r/GUI/Jobs/ArrangeJob2.cpp | 43 ++++++++++------------ tests/arrange/test_arrange_integration.cpp | 4 +- 4 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index ce4ee03ebe..2b7f62e87f 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -225,6 +225,9 @@ void fill_rotations(const Range &items, return; for (auto &itm : items) { + if (is_wipe_tower(itm)) // Rotating the wipe tower is currently problematic + continue; + // Use the minimum bounding box rotation as a starting point. auto minbbr = get_min_area_bounding_box_rotation(itm); std::vector rotations = diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 226934245d..4fdd74eb19 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -115,20 +115,14 @@ struct ArrangeableWipeTowerBase: public Arrangeable ObjectID oid; Polygon poly; - Point pos = Point::Zero(); - double rot = 0.; SelectionPredicate selection_pred; ArrangeableWipeTowerBase( const ObjectID &objid, Polygon shape, - const Point &p, - double r, SelectionPredicate selection_predicate = [] { return false; }) : oid{objid}, poly{std::move(shape)}, - pos{p}, - rot{r}, selection_pred{std::move(selection_predicate)} {} @@ -138,15 +132,12 @@ struct ArrangeableWipeTowerBase: public Arrangeable ExPolygons full_outline() const override { auto cpy = poly; - cpy.translate(pos); - return {ExPolygon{cpy}}; + return {ExPolygon{std::move(cpy)}}; } Polygon convex_outline() const override { - auto cpy = poly; - cpy.translate(pos); - return cpy; + return poly; } bool is_selected() const override diff --git a/src/slic3r/GUI/Jobs/ArrangeJob2.cpp b/src/slic3r/GUI/Jobs/ArrangeJob2.cpp index 91830c3fb3..9aea0ced7d 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob2.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob2.cpp @@ -65,29 +65,21 @@ public: } }; -struct WipeTowerGeometry +static Polygon get_wtpoly(const GLCanvas3D::WipeTowerInfo &wti) { - Polygon poly; - Point pos = Point::Zero(); - double rot = 0.; -}; - -static WipeTowerGeometry get_wtg(const GLCanvas3D::WipeTowerInfo &wti) -{ - WipeTowerGeometry ret; auto bb = scaled(wti.bounding_box()); - ret.poly = Polygon({ + Polygon poly = Polygon({ {bb.min}, {bb.max.x(), bb.min.y()}, {bb.max}, {bb.min.x(), bb.max.y()} }); - ret.pos = scaled(wti.pos()); - ret.rot = wti.rotation(); + poly.rotate(wti.rotation()); + poly.translate(scaled(wti.pos())); - return ret; + return poly; } // Wipe tower logic based on GLCanvas3D::WipeTowerInfo implements the Arrangeable @@ -95,18 +87,24 @@ static WipeTowerGeometry get_wtg(const GLCanvas3D::WipeTowerInfo &wti) class ArrangeableWT: public arr2::ArrangeableWipeTowerBase { BoundingBox m_xl_bb; + Vec2d m_orig_tr; + double m_orig_rot; + public: - explicit ArrangeableWT(const ObjectID &oid, - const WipeTowerGeometry &wtg, - std::function sel_pred, - const BoundingBox xl_bb = {}) - : arr2::ArrangeableWipeTowerBase{oid, wtg.poly, wtg.pos, wtg.rot, - std::move(sel_pred)}, m_xl_bb{xl_bb} + explicit ArrangeableWT(const ObjectID &oid, + const GLCanvas3D::WipeTowerInfo &wti, + std::function sel_pred, + const BoundingBox xl_bb = {}) + : arr2::ArrangeableWipeTowerBase{oid, get_wtpoly(wti), std::move(sel_pred)} + , m_orig_tr{wti.pos()} + , m_orig_rot{wti.rotation()} + , m_xl_bb{xl_bb} {} - void transform(const Vec2d &transl, double rot) override + // Rotation is disabled for wipe tower in arrangement + void transform(const Vec2d &transl, double /*rot*/) override { - GLCanvas3D::WipeTowerInfo::apply_wipe_tower(unscaled(pos) + transl, rot); + GLCanvas3D::WipeTowerInfo::apply_wipe_tower(m_orig_tr + transl, m_orig_rot); } void imbue_data(arr2::AnyWritable &datastore) const override @@ -142,8 +140,7 @@ struct WTH : public arr2::WipeTowerHandler template static void visit_(Self &&self, Fn &&fn) { - auto wtg = get_wtg(self.wti); - ArrangeableWT wta{self.oid, wtg, self.sel_pred, self.xl_bb}; + ArrangeableWT wta{self.oid, self.wti, self.sel_pred, self.xl_bb}; fn(wta); } diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index 39bd70afa1..f5dc761f00 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -882,12 +882,12 @@ public: void visit(std::function fn) override { - MocWT wt{m_id, Polygon{}, Point::Zero(), 0., m_sel_pred}; + MocWT wt{m_id, Polygon{}, m_sel_pred}; fn(wt); } void visit(std::function fn) const override { - MocWT wt{m_id, Polygon{}, Point::Zero(), 0., m_sel_pred}; + MocWT wt{m_id, Polygon{}, m_sel_pred}; fn(wt); } void set_selection_predicate(std::function pred) override From 62c64fce2de0f3530ccc4f2d73e723fc42b6d191 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Aug 2023 13:09:47 +0200 Subject: [PATCH 071/120] Fix problem with incorrect number of objects in object list after bedfil fixes for problem reported in SPE-1824 (bug 2.) --- src/slic3r/GUI/Jobs/ArrangeJob2.hpp | 6 ++++++ src/slic3r/GUI/Plater.cpp | 14 ++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob2.hpp b/src/slic3r/GUI/Jobs/ArrangeJob2.hpp index 31d51e9124..a4130ee4d6 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob2.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob2.hpp @@ -58,9 +58,15 @@ public: typename decltype(std::declval().process_native( std::declval()))::element_type; + // All callbacks are called in the main thread. struct Callbacks { + // Task is prepared but not no processing has been initiated std::function on_prepared; + + // Task has been completed but the result is not yet written (inside finalize) std::function on_processed; + + // Task result has been written std::function on_finished; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6799b9c062..7d137698cf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6279,7 +6279,7 @@ void Plater::fill_bed_with_instances() ModelObject *model_object = prototype_mi->get_object(); assert(model_object); -// model_object->ensure_on_bed(); + model_object->ensure_on_bed(); size_t inst_cnt = model_object->instances.size(); if (inst_cnt == 0) @@ -6290,19 +6290,17 @@ void Plater::fill_bed_with_instances() if (object_idx < 0 || object_idx >= int(model().objects.size())) return; - int added_cnt = result.to_add.size(); + update(static_cast(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); - if (added_cnt > 0) { - update(static_cast(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); + if (!result.to_add.empty()) { + auto added_cnt = result.to_add.size(); // FIXME: somebody explain why this is needed for // increase_object_instances - if (inst_cnt == 1) + if (result.arranged_items.size() == 1) added_cnt++; - sidebar() - .obj_list() - ->increase_object_instances(object_idx, size_t(added_cnt)); + sidebar().obj_list()->increase_object_instances(object_idx, added_cnt); } }; From 9bb5bdb80f498315da1824cde0d979ae7c2e5f13 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 15 Aug 2023 17:03:41 +0200 Subject: [PATCH 072/120] Fixing crashes with objects residing at large world coordinate values fixes issue SPE-1844 --- src/libslic3r/Arrange/SceneBuilder.cpp | 45 +++++++++++++++++++++- src/libslic3r/Arrange/SceneBuilder.hpp | 29 ++++++++++++++ tests/arrange/test_arrange_integration.cpp | 18 ++++++--- 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index f2bbc38876..e661eee30f 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -262,6 +262,47 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const return tr; } +Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const +{ + Vec2i ret{bed_idx % ColsOutside, bed_idx / ColsOutside}; + + return ret; +} + +int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const +{ + assert(crd.x() < ColsOutside - 1 && crd.y() < ColsOutside - 1); + + return crd.y() * ColsOutside + crd.x(); +} + +int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const +{ + Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)}; + + return grid2raw(crd); +} + +bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) +{ + Vec2i crd = raw2grid(bed_idx); + + bool retx = m_xstrider.assign_bed(inst, crd.x()); + bool rety = m_ystrider.assign_bed(inst, crd.y()); + + return retx && rety; +} + +Transform3d GridStriderVBedHandler::get_physical_bed_trafo(int bed_idx) const +{ + Vec2i crd = raw2grid(bed_idx); + + Transform3d ret = m_xstrider.get_physical_bed_trafo(crd.x()) * + m_ystrider.get_physical_bed_trafo(crd.y()); + + return ret; +} + FixedSelection::FixedSelection(const Model &m) : m_wp{true} { m_seldata.resize(m.objects.size()); @@ -805,7 +846,7 @@ std::unique_ptr VirtualBedHandler::create(const ExtendedBed & if (is_infinite_bed(bed)) { ret = std::make_unique(); } else { - // The gap between logical beds in the x axis expressed in ratio of + // The gap between logical beds expressed in ratio of // the current bed width. constexpr double LogicalBedGap = 1. / 10.; @@ -814,7 +855,7 @@ std::unique_ptr VirtualBedHandler::create(const ExtendedBed & auto bedwidth = bedbb.size().x(); coord_t xgap = LogicalBedGap * bedwidth; - ret = std::make_unique(bedbb, xgap); + ret = std::make_unique(bedbb, xgap); } return ret; diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 4fdd74eb19..8315424ba6 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -330,6 +330,35 @@ public: Transform3d get_physical_bed_trafo(int bed_index) const override; }; +class GridStriderVBedHandler: public VirtualBedHandler +{ + // This vbed handler defines a grid of virtual beds with a large number + // of columns so that it behaves as XStrider for regular cases. + // The goal is to handle objects residing at world coordinates + // not representable with scaled coordinates. Combining XStrider with + // YStrider takes care of the X and Y axis to be mapped into the physical + // bed's coordinate region (which is representable in scaled coords) + static const int ColsOutside = std::sqrt(std::numeric_limits::max()); + + XStriderVBedHandler m_xstrider; + YStriderVBedHandler m_ystrider; + +public: + GridStriderVBedHandler(const BoundingBox &bedbb, + coord_t gap) + : m_xstrider{bedbb, gap} + , m_ystrider{bedbb, gap} + {} + + Vec2i raw2grid(int bedidx) const; + int grid2raw(const Vec2i &crd) const; + + int get_bed_index(const VBedPlaceable &obj) const override; + bool assign_bed(VBedPlaceable &inst, int bed_idx) override; + + Transform3d get_physical_bed_trafo(int bed_index) const override; +}; + std::vector selected_object_indices(const SelectionMask &sm); std::vector selected_instance_indices(int obj_idx, const SelectionMask &sm); diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index f5dc761f00..b60b87fce9 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -326,10 +326,18 @@ auto create_vbed_handler(const Slic3r::Boundi return Slic3r::arr2::YStriderVBedHandler{bedbb, gap}; } -TEMPLATE_TEST_CASE("Common virtual bed handlers", "[arrange2][integration][vbeds]", - Slic3r::arr2::PhysicalOnlyVBedHandler, - Slic3r::arr2::XStriderVBedHandler, - Slic3r::arr2::YStriderVBedHandler) +template<> +auto create_vbed_handler(const Slic3r::BoundingBox &bedbb, coord_t gap) +{ + return Slic3r::arr2::GridStriderVBedHandler{bedbb, gap}; +} + +TEMPLATE_TEST_CASE("Common virtual bed handlers", + "[arrange2][integration][vbeds]", + Slic3r::arr2::PhysicalOnlyVBedHandler, + Slic3r::arr2::XStriderVBedHandler, + Slic3r::arr2::YStriderVBedHandler, + Slic3r::arr2::GridStriderVBedHandler) { using namespace Slic3r; using VBP = arr2::VBedPlaceableMI; @@ -413,7 +421,7 @@ TEMPLATE_TEST_CASE("Common virtual bed handlers", "[arrange2][integration][vbeds WHEN ("moving back to the physical bed") { auto &mi_back_to_phys = *model.objects.front()->add_instance(mi_to_move); - bool moved_back_to_physical = vbedh->assign_bed(VBP{mi_back_to_phys}, 0); + bool moved_back_to_physical = vbedh->assign_bed(VBP{mi_back_to_phys}, arr2::PhysicalBedId); THEN("model instance should actually move back to the physical bed") { From 9bce0fcf9bb479c289c396e7e23ce95841a61628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 27 Jun 2023 11:31:18 +0200 Subject: [PATCH 073/120] SPE-1796: Added option for enabling interlocking between segmented regions by the multimaterial painting. --- src/libslic3r/MultiMaterialSegmentation.cpp | 22 ++++++++++++--------- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 9 +++++++++ src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 661c2d0fd1..f2c79fcc21 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1271,18 +1271,22 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto static void cut_segmented_layers(const std::vector &input_expolygons, std::vector> &segmented_regions, const float cut_width, + const float interlocking_depth, const std::function &throw_on_cancel_callback) { BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) { + tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_depth, &throw_on_cancel_callback](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); - const size_t num_extruders_plus_one = segmented_regions[layer_idx].size(); - std::vector segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id - for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx) - if (const ExPolygons &ex_polygons = segmented_regions[layer_idx][extruder_idx]; !ex_polygons.empty()) - segmented_regions_cuts[extruder_idx] = diff_ex(ex_polygons, offset_ex(input_expolygons[layer_idx], cut_width)); - segmented_regions[layer_idx] = std::move(segmented_regions_cuts); + const float region_cut_width = (layer_idx % 2 == 0 && interlocking_depth != 0.f) ? interlocking_depth : cut_width; + const size_t num_extruders_plus_one = segmented_regions[layer_idx].size(); + if (region_cut_width > 0.f) { + std::vector segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id + for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx) + if (const ExPolygons &ex_polygons = segmented_regions[layer_idx][extruder_idx]; !ex_polygons.empty()) + segmented_regions_cuts[extruder_idx] = diff_ex(ex_polygons, offset_ex(input_expolygons[layer_idx], -region_cut_width)); + segmented_regions[layer_idx] = std::move(segmented_regions_cuts); + } } }); // end of parallel_for BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end"; @@ -1891,8 +1895,8 @@ std::vector> multi_material_segmentation_by_painting(con BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end"; throw_on_cancel_callback(); - if (auto w = print_object.config().mmu_segmented_region_max_width; w > 0.f) { - cut_segmented_layers(input_expolygons, segmented_regions, float(-scale_(w)), throw_on_cancel_callback); + if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f || interlocking_depth > 0.f) { + cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback); throw_on_cancel_callback(); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f42b3f7708..6eeb0cfca9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -456,7 +456,7 @@ static std::vector s_Preset_print_options { "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", - "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", + "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width" }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f09a4493d6..2ebe5d4e08 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1621,6 +1621,15 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("mmu_segmented_region_interlocking_depth", coFloat); + def->label = L("Maximum interlocking depth of a segmented region"); + def->tooltip = L("Maximum interlocking depth of a segmented region. Zero disables this feature."); + def->sidetext = L("mm (zero to disable)"); + def->min = 0; + def->category = L("Advanced"); + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("ironing", coBool); def->label = L("Enable ironing"); def->tooltip = L("Enable ironing of the top layers with the hot print head for smooth surface"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 78c0ca03b4..6439d21e9d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -499,6 +499,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, interface_shells)) ((ConfigOptionFloat, layer_height)) ((ConfigOptionFloat, mmu_segmented_region_max_width)) + ((ConfigOptionFloat, mmu_segmented_region_interlocking_depth)) ((ConfigOptionFloat, raft_contact_distance)) ((ConfigOptionFloat, raft_expansion)) ((ConfigOptionPercent, raft_first_layer_density)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 49573a041a..71b222f86b 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -670,6 +670,7 @@ bool PrintObject::invalidate_state_by_config_options( } else if ( opt_key == "layer_height" || opt_key == "mmu_segmented_region_max_width" + || opt_key == "mmu_segmented_region_interlocking_depth" || opt_key == "raft_layers" || opt_key == "raft_contact_distance" || opt_key == "slice_closing_radius" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1a479e566b..0fd5e75ad1 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1625,6 +1625,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Advanced")); optgroup->append_single_option_line("interface_shells"); optgroup->append_single_option_line("mmu_segmented_region_max_width"); + optgroup->append_single_option_line("mmu_segmented_region_interlocking_depth"); page = add_options_page(L("Advanced"), "wrench"); optgroup = page->new_optgroup(L("Extrusion width")); From a469f6aaa1a1e408d110f7b05d3bd84f7faefc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 10 Aug 2023 11:20:51 +0200 Subject: [PATCH 074/120] SPE-1796: Made interlocking to be applied as offset computed from mmu_segmented_region_max_width. Also, interlocking input is disabled when mmu_segmented_region_max_width is equal to zero. --- src/libslic3r/MultiMaterialSegmentation.cpp | 7 ++++--- src/libslic3r/PrintConfig.cpp | 6 ++++-- src/slic3r/GUI/ConfigManipulation.cpp | 3 +++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index f2c79fcc21..7b986d6afc 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1275,10 +1275,11 @@ static void cut_segmented_layers(const std::vector &input_exp const std::function &throw_on_cancel_callback) { BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_depth, &throw_on_cancel_callback](const tbb::blocked_range& range) { + const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f; + tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); - const float region_cut_width = (layer_idx % 2 == 0 && interlocking_depth != 0.f) ? interlocking_depth : cut_width; + const float region_cut_width = (layer_idx % 2 == 0 && interlocking_cut_width > 0.f) ? interlocking_cut_width : cut_width; const size_t num_extruders_plus_one = segmented_regions[layer_idx].size(); if (region_cut_width > 0.f) { std::vector segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id @@ -1895,7 +1896,7 @@ std::vector> multi_material_segmentation_by_painting(con BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end"; throw_on_cancel_callback(); - if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f || interlocking_depth > 0.f) { + if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) { cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback); throw_on_cancel_callback(); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2ebe5d4e08..4b17e2c3d5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1622,8 +1622,10 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(0.)); def = this->add("mmu_segmented_region_interlocking_depth", coFloat); - def->label = L("Maximum interlocking depth of a segmented region"); - def->tooltip = L("Maximum interlocking depth of a segmented region. Zero disables this feature."); + def->label = L("Interlocking depth of a segmented region"); + def->tooltip = L("Interlocking depth of a segmented region. It will be ignored if " + "\"mmu_segmented_region_max_width\" is zero or if \"mmu_segmented_region_interlocking_depth\"" + "is bigger then \"mmu_segmented_region_max_width\". Zero disables this feature."); def->sidetext = L("mm (zero to disable)"); def->min = 0; def->category = L("Advanced"); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index f645e8a0dd..8126576b79 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -324,6 +324,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) "wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); + bool have_non_zero_mmu_segmented_region_max_width = config->opt_float("mmu_segmented_region_max_width") > 0.; + toggle_field("mmu_segmented_region_interlocking_depth", have_non_zero_mmu_segmented_region_max_width); + toggle_field("avoid_crossing_curled_overhangs", !config->opt_bool("avoid_crossing_perimeters")); toggle_field("avoid_crossing_perimeters", !config->opt_bool("avoid_crossing_curled_overhangs")); From 9728bc4d6f929b49aa1f18a25b218257a6bf0450 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 10:58:49 +0200 Subject: [PATCH 075/120] Fix build on MSVC --- src/libslic3r/Arrange/SceneBuilder.cpp | 3 +++ src/libslic3r/Arrange/SceneBuilder.hpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index e661eee30f..a6e37c0b74 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -262,6 +262,9 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const return tr; } +const int GridStriderVBedHandler::ColsOutside = + static_cast(std::sqrt(std::numeric_limits::max())); + Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const { Vec2i ret{bed_idx % ColsOutside, bed_idx / ColsOutside}; diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 8315424ba6..c19bd6ed69 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -338,7 +338,7 @@ class GridStriderVBedHandler: public VirtualBedHandler // not representable with scaled coordinates. Combining XStrider with // YStrider takes care of the X and Y axis to be mapped into the physical // bed's coordinate region (which is representable in scaled coords) - static const int ColsOutside = std::sqrt(std::numeric_limits::max()); + static const int ColsOutside; XStriderVBedHandler m_xstrider; YStriderVBedHandler m_ystrider; From 40c3d8b8a0fd0727225136430adec9842aca1307 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 11:17:59 +0200 Subject: [PATCH 076/120] Another fix for compilation on MSVC --- src/libslic3r/Arrange/Core/NFP/NFP.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.cpp b/src/libslic3r/Arrange/Core/NFP/NFP.cpp index 5dfe559f73..e96581b80d 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFP.cpp +++ b/src/libslic3r/Arrange/Core/NFP/NFP.cpp @@ -279,6 +279,12 @@ void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &po // Output iterator adapter for std::merge struct OutItAdaptor { + using value_type = Line; + using difference_type = std::ptrdiff_t; + using pointer = Line*; + using reference = Line&; + using iterator_category = std::output_iterator_tag; + Polygon *outpoly; OutItAdaptor(Polygon &out) : outpoly{&out} {} From bd31722bacb5989dc6ead45264fafbc5b59b8042 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 11:18:11 +0200 Subject: [PATCH 077/120] Prevent NOMINMAX warnings --- src/libslic3r/OpenVDBUtils.cpp | 1 - src/libslic3r/SLA/SupportTreeBuilder.cpp | 3 --- src/slic3r/GUI/GUI.cpp | 4 +++- src/slic3r/GUI/SysInfoDialog.cpp | 2 ++ 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 21409445fc..8821b9e0b9 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -1,4 +1,3 @@ -#define NOMINMAX #include "OpenVDBUtils.hpp" #ifdef _MSC_VER diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 1786adb837..c017837d80 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -1,9 +1,6 @@ -#define NOMINMAX - #include #include #include -//#include namespace Slic3r { namespace sla { diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index beee256004..c263370b6f 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -16,7 +16,9 @@ #import #elif _WIN32 #define WIN32_LEAN_AND_MEAN -#define NOMINMAX +#ifndef NOMINMAX + #define NOMINMAX +#endif #include #include "boost/nowide/convert.hpp" #endif diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index a29c3e9773..c66ba5cf3f 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -23,7 +23,9 @@ #ifdef _WIN32 // The standard Windows includes. #define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX #define NOMINMAX +#endif #include #include #endif /* _WIN32 */ From c61366971899ddbbf5c6d14223abe934ceafbede Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 14:15:38 +0200 Subject: [PATCH 078/120] Follow-up, get rid of even more NOMINMAX warnings --- src/libslic3r/GCode/PostProcessor.cpp | 2 ++ src/libslic3r/Preset.cpp | 2 ++ tests/libslic3r/test_png_io.cpp | 3 +++ 3 files changed, 7 insertions(+) diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index c434aa5603..0e5929205b 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -16,7 +16,9 @@ // The standard Windows includes. #define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX #define NOMINMAX +#endif #include #include diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6eeb0cfca9..906b4c76b6 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -7,7 +7,9 @@ #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX #define NOMINMAX +#endif #include #endif /* _MSC_VER */ diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp index e8229b7163..97fa064254 100644 --- a/tests/libslic3r/test_png_io.cpp +++ b/tests/libslic3r/test_png_io.cpp @@ -1,4 +1,7 @@ +#ifndef NOMINMAX #define NOMINMAX +#endif + #include #include From 8ab29e3080f7923cc291726467b7f41bfd9702f8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 16:07:09 +0200 Subject: [PATCH 079/120] Fix warnings with gcc like compilers --- src/libslic3r/Arrange/Core/NFP/NFP.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.cpp b/src/libslic3r/Arrange/Core/NFP/NFP.cpp index e96581b80d..99a25edfdb 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFP.cpp +++ b/src/libslic3r/Arrange/Core/NFP/NFP.cpp @@ -279,11 +279,11 @@ void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &po // Output iterator adapter for std::merge struct OutItAdaptor { - using value_type = Line; - using difference_type = std::ptrdiff_t; - using pointer = Line*; - using reference = Line&; - using iterator_category = std::output_iterator_tag; + using value_type [[maybe_unused]] = Line; + using difference_type [[maybe_unused]] = std::ptrdiff_t; + using pointer [[maybe_unused]] = Line*; + using reference [[maybe_unused]] = Line& ; + using iterator_category [[maybe_unused]] = std::output_iterator_tag; Polygon *outpoly; OutItAdaptor(Polygon &out) : outpoly{&out} {} From 90a7bc929c3b96d89150a5f12c47c8317374c57f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 16:47:47 +0200 Subject: [PATCH 080/120] Exclude tests marked as Slow from ctests --- tests/arrange/CMakeLists.txt | 2 +- tests/arrange/test_arrange.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/arrange/CMakeLists.txt b/tests/arrange/CMakeLists.txt index 26204e82b2..c09607a70d 100644 --- a/tests/arrange/CMakeLists.txt +++ b/tests/arrange/CMakeLists.txt @@ -12,6 +12,6 @@ if (WIN32) prusaslicer_copy_dlls(${_TEST_NAME}_tests) endif() -set(_catch_args "exclude:[NotWorking]") +set(_catch_args "exclude:[NotWorking][Slow]") list(APPEND _catch_args "${CATCH_EXTRA_ARGS}") add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args}) diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp index 0d2e1152e4..0304a29db9 100644 --- a/tests/arrange/test_arrange.cpp +++ b/tests/arrange/test_arrange.cpp @@ -466,8 +466,7 @@ TEST_CASE("NFP should be empty if item cannot fit into bed", "[arrange2]") { #include #include -TEMPLATE_TEST_CASE("NFP algorithm test", - "[arrange2]", +TEMPLATE_TEST_CASE("NFP algorithm test", "[arrange2][Slow]", Slic3r::arr2::InfiniteBed, Slic3r::arr2::RectangleBed, Slic3r::arr2::CircleBed, From be9bbb755291e73d3b0e4080d861deec26ce162b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 16:53:42 +0200 Subject: [PATCH 081/120] Follow up fix for ctest slow test filter expression --- tests/arrange/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/arrange/CMakeLists.txt b/tests/arrange/CMakeLists.txt index c09607a70d..7efbbbd325 100644 --- a/tests/arrange/CMakeLists.txt +++ b/tests/arrange/CMakeLists.txt @@ -12,6 +12,6 @@ if (WIN32) prusaslicer_copy_dlls(${_TEST_NAME}_tests) endif() -set(_catch_args "exclude:[NotWorking][Slow]") +set(_catch_args "exclude:[NotWorking] exclude:[Slow]") list(APPEND _catch_args "${CATCH_EXTRA_ARGS}") add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args}) From cb3596e90b20798c3a5488bb3250bc4adf4998e0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 17:34:42 +0200 Subject: [PATCH 082/120] Complete arrangement prevention of integer coordinate overflows --- src/libslic3r/Arrange/Arrange.hpp | 8 +++ src/libslic3r/Arrange/ArrangeImpl.hpp | 8 +++ src/libslic3r/Arrange/SceneBuilder.cpp | 59 ++++++++++++++++--- src/libslic3r/Arrange/SceneBuilder.hpp | 3 + .../Arrange/Tasks/ArrangeTaskImpl.hpp | 15 +++-- .../Arrange/Tasks/FillBedTaskImpl.hpp | 23 +++++--- .../Tasks/MultiplySelectionTaskImpl.hpp | 23 +++++--- 7 files changed, 112 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/Arrange/Arrange.hpp b/src/libslic3r/Arrange/Arrange.hpp index 36ab838739..75e632ad95 100644 --- a/src/libslic3r/Arrange/Arrange.hpp +++ b/src/libslic3r/Arrange/Arrange.hpp @@ -83,11 +83,19 @@ void Arranger::arrange(std::vector &items, arrange(items, fixed, bed, DefaultArrangerCtl{ctl}); } +class EmptyItemOutlineError: public std::exception { + static constexpr const char *Msg = "No outline can be derived for object"; + +public: + const char* what() const noexcept override { return Msg; } +}; + template class ArrangeableToItemConverter { public: virtual ~ArrangeableToItemConverter() = default; + // May throw EmptyItemOutlineError virtual ArrItem convert(const Arrangeable &arrbl, coord_t offs = 0) const = 0; // Returns the extent of simplification that the converter utilizes when diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 2b7f62e87f..d827be3df6 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -370,6 +370,10 @@ ArrItem ConvexItemConverter::convert(const Arrangeable &arrbl, { auto bed_index = arrbl.get_bed_index(); Polygon outline = arrbl.convex_outline(); + + if (outline.empty()) + throw EmptyItemOutlineError{}; + Polygon envelope = arrbl.convex_envelope(); coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); @@ -418,6 +422,10 @@ ArrItem AdvancedItemConverter::get_arritem(const Arrangeable &arrbl, coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); auto outline = arrbl.full_outline(); + + if (outline.empty()) + throw EmptyItemOutlineError{}; + auto envelope = arrbl.full_envelope(); if (infl != 0) { diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index a6e37c0b74..bf77dcd948 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -64,16 +64,18 @@ void transform_instance(ModelInstance &mi, mi.invalidate_object_bounding_box(); } -BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate) +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, + const Transform3d &tr, + bool dont_translate) { BoundingBoxf3 bb; const Transform3d inst_matrix = dont_translate ? mi.get_transformation().get_matrix_no_offset() - : mi.get_transformation().get_matrix(); + : mi.get_transformation().get_matrix(); for (ModelVolume *v : mi.get_object()->volumes) { if (v->is_model_part()) { - bb.merge(v->mesh().transformed_bounding_box(inst_matrix + bb.merge(v->mesh().transformed_bounding_box(tr * inst_matrix * v->get_matrix())); } } @@ -81,11 +83,32 @@ BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate return bb; } +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate) +{ + return instance_bounding_box(mi, Transform3d::Identity(), dont_translate); +} + +bool check_coord_bounds(const BoundingBoxf &bb) +{ + constexpr double hi = 1000.; + + return std::abs(bb.min.x()) < hi && + std::abs(bb.min.y()) < hi && + std::abs(bb.max.x()) < hi && + std::abs(bb.max.y()) < hi; +} + ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) { ExPolygons outline; for (const ModelVolume *v : inst.get_object()->volumes) { Polygons vol_outline; + + if (!check_coord_bounds(to_2d(v->mesh().transformed_bounding_box(tr)))) { + outline.clear(); + break; + } + vol_outline = project_mesh(v->mesh().its, tr * inst.get_matrix() * v->get_matrix(), [] {}); @@ -105,7 +128,14 @@ ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr Polygon extract_convex_outline(const ModelInstance &inst, const Transform3d &tr) { - return inst.get_object()->convex_hull_2d(tr * inst.get_matrix()); + auto bb = to_2d(instance_bounding_box(inst, tr)); + Polygon ret; + + if (check_coord_bounds(bb)) { + ret = inst.get_object()->convex_hull_2d(tr * inst.get_matrix()); + } + + return ret; } inline static bool is_infinite_bed(const ExtendedBed &ebed) noexcept @@ -190,7 +220,14 @@ int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const auto reference_pos_x = (instance_bb.min.x() - bedx); auto stride = unscaled(stride_s); - bedidx = static_cast(std::floor(reference_pos_x / stride)); + auto bedidx_d = std::floor(reference_pos_x / stride); + + if (bedidx_d < std::numeric_limits::min()) + bedidx = std::numeric_limits::min(); + else if (bedidx_d > std::numeric_limits::max()) + bedidx = std::numeric_limits::max(); + else + bedidx = static_cast(bedidx_d); } return bedidx; @@ -231,7 +268,14 @@ int YStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const auto reference_pos_y = (instance_bb.min.y() - ystart); auto stride = unscaled(stride_s); - bedidx = static_cast(std::floor(reference_pos_y / stride)); + auto bedidx_d = std::floor(reference_pos_y / stride); + + if (bedidx_d < std::numeric_limits::min()) + bedidx = std::numeric_limits::min(); + else if (bedidx_d > std::numeric_limits::max()) + bedidx = std::numeric_limits::max(); + else + bedidx = static_cast(bedidx_d); } return bedidx; @@ -274,7 +318,8 @@ Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const { - assert(crd.x() < ColsOutside - 1 && crd.y() < ColsOutside - 1); + assert(std::abs(crd.x()) < ColsOutside - 1 && + std::abs(crd.y()) < ColsOutside - 1); return crd.y() * ColsOutside + crd.x(); } diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index c19bd6ed69..f056ed3ff2 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -377,6 +377,9 @@ void transform_instance(ModelInstance &mi, BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate = false); +BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, + const Transform3d &tr, + bool dont_translate = false); ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr = Transform3d::Identity()); diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index c9f48f93e4..8925ea4b95 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -3,6 +3,8 @@ #include +#include + #include "ArrangeTask.hpp" namespace Slic3r { namespace arr2 { @@ -20,16 +22,21 @@ void extract_selected(ArrangeTask &task, bool selected = arrbl.is_selected(); bool printable = arrbl.is_printable(); - auto itm = itm_conv.convert(arrbl, selected ? 0 : -SCALED_EPSILON); + try { + auto itm = itm_conv.convert(arrbl, selected ? 0 : -SCALED_EPSILON); - auto &container_parent = printable ? task.printable : + auto &container_parent = printable ? task.printable : task.unprintable; - auto &container = selected ? + auto &container = selected ? container_parent.selected : container_parent.unselected; - container.emplace_back(std::move(itm)); + container.emplace_back(std::move(itm)); + } catch (const EmptyItemOutlineError &ex) { + BOOST_LOG_TRIVIAL(error) + << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); + } }); // If the selection was empty arrange everything diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp index a99102418f..e874be4575 100644 --- a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp @@ -5,6 +5,8 @@ #include "Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include + namespace Slic3r { namespace arr2 { template @@ -81,15 +83,20 @@ void extract(FillBedTask &task, auto collect_task_items = [&prototype_geometry_id, &task, &itm_conv](const Arrangeable &arrbl) { - if (arrbl.geometry_id() == prototype_geometry_id) { - if (arrbl.is_printable()) { - auto itm = itm_conv.convert(arrbl); - raise_priority(itm); - task.selected.emplace_back(std::move(itm)); + try { + if (arrbl.geometry_id() == prototype_geometry_id) { + if (arrbl.is_printable()) { + auto itm = itm_conv.convert(arrbl); + raise_priority(itm); + task.selected.emplace_back(std::move(itm)); + } + } else { + auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); + task.unselected.emplace_back(std::move(itm)); } - } else { - auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); - task.unselected.emplace_back(std::move(itm)); + } catch (const EmptyItemOutlineError &ex) { + BOOST_LOG_TRIVIAL(error) + << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); } }; diff --git a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp index d4c925c3bf..422c3da243 100644 --- a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp @@ -3,6 +3,8 @@ #include "MultiplySelectionTask.hpp" +#include + namespace Slic3r { namespace arr2 { template @@ -45,15 +47,20 @@ std::unique_ptr> MultiplySelectionTask:: auto collect_task_items = [&prototype_geometry_id, &task, &itm_conv](const Arrangeable &arrbl) { - if (arrbl.geometry_id() == prototype_geometry_id) { - if (arrbl.is_printable()) { - auto itm = itm_conv.convert(arrbl); - raise_priority(itm); - task.selected.emplace_back(std::move(itm)); + try { + if (arrbl.geometry_id() == prototype_geometry_id) { + if (arrbl.is_printable()) { + auto itm = itm_conv.convert(arrbl); + raise_priority(itm); + task.selected.emplace_back(std::move(itm)); + } + } else { + auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); + task.unselected.emplace_back(std::move(itm)); } - } else { - auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); - task.unselected.emplace_back(std::move(itm)); + } catch (const EmptyItemOutlineError &ex) { + BOOST_LOG_TRIVIAL(error) + << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); } }; From aea278ab5587fdda72e20d6408a2756af567d7c6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Aug 2023 18:52:05 +0200 Subject: [PATCH 083/120] Fix failing tests still the arrange is broken --- src/libslic3r/Arrange/SceneBuilder.cpp | 10 ++++------ src/libslic3r/Arrange/SceneBuilder.hpp | 2 ++ tests/arrange/test_arrange_integration.cpp | 7 ++++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index bf77dcd948..75990e2f84 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -90,12 +90,10 @@ BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate bool check_coord_bounds(const BoundingBoxf &bb) { - constexpr double hi = 1000.; - - return std::abs(bb.min.x()) < hi && - std::abs(bb.min.y()) < hi && - std::abs(bb.max.x()) < hi && - std::abs(bb.max.y()) < hi; + return std::abs(bb.min.x()) < UnscaledCoordLimit && + std::abs(bb.min.y()) < UnscaledCoordLimit && + std::abs(bb.max.x()) < UnscaledCoordLimit && + std::abs(bb.max.y()) < UnscaledCoordLimit; } ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index f056ed3ff2..d159669f51 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -381,6 +381,8 @@ BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, const Transform3d &tr, bool dont_translate = false); +constexpr double UnscaledCoordLimit = 1000.; + ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr = Transform3d::Identity()); diff --git a/tests/arrange/test_arrange_integration.cpp b/tests/arrange/test_arrange_integration.cpp index b60b87fce9..6d8fda6bff 100644 --- a/tests/arrange/test_arrange_integration.cpp +++ b/tests/arrange/test_arrange_integration.cpp @@ -49,8 +49,8 @@ static Slic3r::Model get_example_model_with_random_cube_objects(size_t N = 0) for (size_t i = 0; i < cube_count; ++i) { ModelInstance *inst = new_object->add_instance(); arr2::transform_instance(*inst, - Vec2d{random_value(-200., 200.), - random_value(-200., 200.)}, + Vec2d{random_value(-arr2::UnscaledCoordLimit / 10., arr2::UnscaledCoordLimit / 10.), + random_value(-arr2::UnscaledCoordLimit / 10., arr2::UnscaledCoordLimit / 10.)}, random_value(0., 2 * PI)); } @@ -168,7 +168,8 @@ TEMPLATE_TEST_CASE("Writing arrange transformations into ModelInstance should be { auto [tx, ty, rot] = GENERATE(map( [](int i) { - return std::make_tuple(-500. + i * 20., -500. + i * 20., + return std::make_tuple(-Slic3r::arr2::UnscaledCoordLimit / 2. + i * Slic3r::arr2::UnscaledCoordLimit / 100., + -Slic3r::arr2::UnscaledCoordLimit / 2. + i * Slic3r::arr2::UnscaledCoordLimit / 100., -PI + i * (2 * PI / 100.)); }, range(0, 100))); From 0c82f38950001b338d023d3e73f593add1e0a12a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 16 Aug 2023 11:26:53 +0200 Subject: [PATCH 084/120] ConfigWizard: Downloads page: Info line is changed + Linux and OSX specific: Fixed BG color for the HTML_widget on Materials pages and InfoDialogs --- src/slic3r/GUI/ConfigWizard.cpp | 61 ++++++++++++++++++++++++--------- src/slic3r/GUI/GUI_App.cpp | 18 ++++++++++ src/slic3r/GUI/GUI_App.hpp | 2 ++ src/slic3r/GUI/MsgDialog.cpp | 19 +++------- 4 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4f211323aa..6c9988d71f 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -707,6 +707,10 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin list_vendor->SetMinSize(wxSize(13*em, list_h)); list_profile->SetMinSize(wxSize(23*em, list_h)); +#ifdef __APPLE__ + for (wxWindow* win : std::initializer_list{ list_printer, list_type, list_vendor, list_profile }) + win->SetBackgroundColour(wxGetApp().get_window_default_clr()); +#endif grid = new wxFlexGridSizer(4, em/2, em); @@ -817,19 +821,9 @@ void PageMaterials::reload_presets() void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) { - const auto bgr_clr = -#if defined(__APPLE__) - html_window->GetParent()->GetBackgroundColour(); -#else -#if defined(_WIN32) - wxGetApp().get_window_default_clr(); -#else - wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); -#endif -#endif const auto text_clr = wxGetApp().get_label_clr_default(); - const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + const auto bgr_clr_str = wxGetApp().get_html_bg_color(parent); wxString text; if (materials->technology == T_FFF && template_shown) { // TRN ConfigWizard: Materials : "%1%" = "Filaments"/"SLA materials" @@ -1468,11 +1462,43 @@ PageDownloader::PageDownloader(ConfigWizard* parent) box_allow_downloads->SetValue(box_allow_value); append(box_allow_downloads); - // TRN ConfigWizard : Downloader : %1% = "PrusaSlicer" - append_text(format_wxstr(_L("If enabled, %1% registers to start on custom URL on www.printables.com." - " You will be able to use button with %1% logo to open models in this %1%." - " The model will be downloaded into folder you choose bellow." - ), SLIC3R_APP_NAME)); + // append info line with link on printables.com + { + const int em = parent->em_unit(); + wxHtmlWindow* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(60 * em, 5 * em), wxHW_SCROLLBAR_NEVER); + + html_window->Bind(wxEVT_HTML_LINK_CLICKED, [](wxHtmlLinkEvent& event) { + wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref()); + event.Skip(false); + }); + + append(html_window); + + const auto text_clr = wxGetApp().get_label_clr_default(); + const auto bgr_clr_str = wxGetApp().get_html_bg_color(parent); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + + const wxString link = format_wxstr("%1%", "printables.com"); + + // TRN ConfigWizard : Downloader : %1% = "printables.com", %2% = "PrusaSlicer" + const wxString main_text = format_wxstr(_L("If enabled, you will be able to open models from the %1% " + "online database with a single click (using a %2% logo button)." + ), link, SLIC3R_APP_NAME); + + const wxFont& font = this->GetFont(); + const int fs = font.GetPointSize(); + int size[] = { fs,fs,fs,fs,fs,fs,fs }; + html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + + html_window->SetPage(format_wxstr( + "" + "%3%" + "" + , bgr_clr_str + , text_clr_str + , main_text + )); + } #ifdef __linux__ append_text(wxString::Format(_L( @@ -3351,6 +3377,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent) : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , p(new priv(this)) { +#ifdef __APPLE__ + this->SetBackgroundColour(wxGetApp().get_window_default_clr()); +#endif wxBusyCursor wait; this->SetFont(wxGetApp().normal_font()); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e855b6290b..e8c7605473 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1732,6 +1732,24 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) app_config->set("label_clr_sys", str); } +const std::string GUI_App::get_html_bg_color(wxWindow* html_parent) +{ + wxColour bgr_clr = html_parent->GetBackgroundColour(); +#ifdef __APPLE__ + // On macOS 10.13 and older the background color returned by wxWidgets + // is wrong, which leads to https://github.com/prusa3d/PrusaSlicer/issues/7603 + // and https://github.com/prusa3d/PrusaSlicer/issues/3775. wxSYS_COLOUR_WINDOW + // may not match the window background exactly, but it seems to never end up + // as black on black. + + if (wxPlatformInfo::Get().GetOSMajorVersion() == 10 + && wxPlatformInfo::Get().GetOSMinorVersion() < 14) + bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); +#endif + + return encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); +} + const std::string& GUI_App::get_mode_btn_color(int mode_id) { assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size()); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6773806df2..eb81243284 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -222,6 +222,8 @@ public: const wxColour& get_label_clr_default() { return m_color_label_default; } const wxColour& get_window_default_clr(){ return m_color_window_default; } + const std::string get_html_bg_color(wxWindow* html_parent); + const std::string& get_mode_btn_color(int mode_id); std::vector get_mode_palette(); void set_mode_palette(const std::vector &palette); diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index cce159903e..aa318085d6 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -32,6 +32,9 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he , content_sizer(new wxBoxSizer(wxVERTICAL)) , btn_sizer(new wxBoxSizer(wxHORIZONTAL)) { +#ifdef __APPLE__ + this->SetBackgroundColour(wxGetApp().get_window_default_clr()); +#endif boldfont.SetWeight(wxFONTWEIGHT_BOLD); this->SetFont(wxGetApp().normal_font()); @@ -139,22 +142,8 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin wxFont font = wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); wxFont monospace = wxGetApp().code_font(); wxColour text_clr = wxGetApp().get_label_clr_default(); - wxColour bgr_clr = parent->GetBackgroundColour(); - -#ifdef __APPLE__ - // On macOS 10.13 and older the background color returned by wxWidgets - // is wrong, which leads to https://github.com/prusa3d/PrusaSlicer/issues/7603 - // and https://github.com/prusa3d/PrusaSlicer/issues/3775. wxSYS_COLOUR_WINDOW - // may not match the window background exactly, but it seems to never end up - // as black on black. - - if (wxPlatformInfo::Get().GetOSMajorVersion() == 10 - && wxPlatformInfo::Get().GetOSMinorVersion() < 14) - bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); -#endif - auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); - auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); + auto bgr_clr_str = wxGetApp().get_html_bg_color(parent); const int font_size = font.GetPointSize(); int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size }; html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size); From 8e719782701822ba6e5d178d33c273db1ad85090 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 17 Aug 2023 17:26:27 +0200 Subject: [PATCH 085/120] CutGizmo: Fixed some terminology --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index b8e64fafa6..7e00356217 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -186,7 +186,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_style (int(CutConnectorStyle::Prism)) , m_connector_shape_id (int(CutConnectorShape::Circle)) { - m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") + m_modes = { _u8L("Planar"), _u8L("Dovetail")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") }; @@ -228,7 +228,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, {"Size" , _u8L("Size")}, {"Groove" , _u8L("Groove")}, {"Width" , _u8L("Width")}, - {"Flaps Angle" , _u8L("Flaps Angle")}, + {"Flap Angle" , _u8L("Flap Angle")}, {"Groove Angle" , _u8L("Groove Angle")}, }; From 45a05b57942129a4b2246a8b6786ef154ba1bd5e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 17 Aug 2023 17:27:41 +0200 Subject: [PATCH 086/120] Follow up 75eccfd6507e4cb446569e2d4a53bb64158c5521 - Removed mistakenly added lines of code --- src/slic3r/GUI/GLCanvas3D.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 28c5866142..a5be3675d9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2629,8 +2629,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (curr_gizmo != nullptr) curr_gizmo->unregister_raycasters_for_picking(); m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo); - if (curr_gizmo != nullptr && !m_selection.is_empty()) - curr_gizmo->register_raycasters_for_picking(); m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo); if (curr_gizmo != nullptr && !m_selection.is_empty()) curr_gizmo->register_raycasters_for_picking(); From 1d4594ad6645b5051c34a0dc949a18cd187b25b5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Aug 2023 19:27:13 +0200 Subject: [PATCH 087/120] Fix faulty virtual bed handling Changes to firstfit selection to use a map of bed contexts to avoid memory overuse when a fixed item has a large bed index number. fixes SPE-1844 --- src/libslic3r/Arrange/ArrangeImpl.hpp | 26 +++++++--- src/libslic3r/Arrange/Core/ArrangeBase.hpp | 42 ++++++++++++++-- .../Arrange/Core/ArrangeFirstFit.hpp | 48 ++++++++++--------- src/libslic3r/Arrange/SceneBuilder.cpp | 18 ++++--- src/libslic3r/Arrange/SceneBuilder.hpp | 4 +- .../Arrange/Tasks/ArrangeTaskImpl.hpp | 31 +++++++++--- 6 files changed, 123 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index d827be3df6..2bbe9ab29c 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -2,6 +2,7 @@ #define ARRANGEIMPL_HPP #include +#include #include "Arrange.hpp" @@ -43,25 +44,36 @@ void arrange(SelectionStrategy &&selstrategy, std::forward(packingstrategy), items, fixed, RectangleBed{bed.bb}, SelStrategyTag{}); - size_t beds = get_bed_count(crange(items)); - size_t fixed_beds = std::max(beds, get_bed_count(fixed)); - std::vector fixed_is_empty(fixed_beds, true); + std::vector bed_indices = get_bed_indices(items, fixed); - std::vector pilebb(beds); + size_t beds = bed_indices.size(); + + auto fixed_is_empty = [&bed_indices](int bidx) { + auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); + return it == bed_indices.end() || *it != bidx; + }; + + auto set_bed_as_empty = [&bed_indices](int bidx) { + auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); + if (it != bed_indices.end()) + bed_indices.erase(it); + }; + + std::vector pilebb(bed_indices.size()); for (auto &itm : items) { auto bedidx = get_bed_index(itm); if (bedidx >= 0) { pilebb[bedidx].merge(fixed_bounding_box(itm)); if (is_wipe_tower(itm)) - fixed_is_empty[bedidx] = false; + set_bed_as_empty(bedidx); } } for (auto &fxitm : fixed) { auto bedidx = get_bed_index(fxitm); if (bedidx >= 0 || is_wipe_tower(fxitm)) - fixed_is_empty[bedidx] = false; + set_bed_as_empty(bedidx); } auto bedbb = bounding_box(bed); @@ -74,7 +86,7 @@ void arrange(SelectionStrategy &&selstrategy, Pivots pivot = bed.alignment(); for (size_t bedidx = 0; bedidx < beds; ++bedidx) { - if (! fixed_is_empty[bedidx]) + if (! fixed_is_empty(bedidx)) continue; BoundingBox bb; diff --git a/src/libslic3r/Arrange/Core/ArrangeBase.hpp b/src/libslic3r/Arrange/Core/ArrangeBase.hpp index 97177d4b3c..d341903d72 100644 --- a/src/libslic3r/Arrange/Core/ArrangeBase.hpp +++ b/src/libslic3r/Arrange/Core/ArrangeBase.hpp @@ -233,8 +233,44 @@ void arrange(SelectionStrategy &&selstrategy, "Arrange unimplemented for this selection strategy"); } +template +std::vector get_bed_indices(const Range &items) +{ + auto bed_indices = reserve_vector(items.size()); + + for (auto &itm : items) + bed_indices.emplace_back(get_bed_index(itm)); + + std::sort(bed_indices.begin(), bed_indices.end()); + auto endit = std::unique(bed_indices.begin(), bed_indices.end()); + + bed_indices.erase(endit, bed_indices.end()); + + return bed_indices; +} + +template +std::vector get_bed_indices(const Range &items, const Range &fixed) +{ + std::vector ret; + + auto iitems = get_bed_indices(items); + auto ifixed = get_bed_indices(fixed); + ret.reserve(std::max(iitems.size(), ifixed.size())); + std::set_union(iitems.begin(), iitems.end(), + ifixed.begin(), ifixed.end(), + std::back_inserter(ret)); + + return ret; +} + template size_t get_bed_count(const Range &items) +{ + return get_bed_indices(items).size(); +} + +template int get_max_bed_index(const Range &items) { auto it = std::max_element(items.begin(), items.end(), @@ -242,11 +278,11 @@ size_t get_bed_count(const Range &items) return get_bed_index(i1) < get_bed_index(i2); }); - size_t beds = 0; + int ret = Unarranged; if (it != items.end()) - beds = get_bed_index(*it) + 1; + ret = get_bed_index(*it); - return beds; + return ret; } struct DefaultStopCondition { diff --git a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp index ee6940b770..cf8d3e457a 100644 --- a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp +++ b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp @@ -2,6 +2,7 @@ #define ARRANGEFIRSTFIT_HPP #include +#include #include @@ -88,21 +89,28 @@ void arrange( sorted_items.emplace_back(itm); } - int max_bed_idx = get_bed_count(fixed); - using Context = PackStrategyContext; - auto bed_contexts = reserve_vector(max_bed_idx + 1); + std::map bed_contexts; + auto get_or_init_context = [&ps, &bed, &bed_contexts](int bedidx) -> Context& { + auto ctx_it = bed_contexts.find(bedidx); + if (ctx_it == bed_contexts.end()) { + auto res = bed_contexts.emplace( + bedidx, create_context(ps, bed, bedidx)); + + assert(res.second); + + ctx_it = res.first; + } + + return ctx_it->second; + }; for (auto &itm : fixed) { - if (get_bed_index(itm) >= 0) { - auto bedidx = static_cast(get_bed_index(itm)); - - while (bed_contexts.size() <= bedidx) - bed_contexts.emplace_back( - create_context(ps, bed, bedidx)); - - add_fixed_item(bed_contexts[bedidx], itm); + auto bedidx = get_bed_index(itm); + if (bedidx >= 0) { + Context &ctx = get_or_init_context(bedidx); + add_fixed_item(ctx, itm); } } @@ -124,18 +132,20 @@ void arrange( while (it != sorted_items.end() && !is_cancelled()) { bool was_packed = false; - size_t j = 0; + int bedidx = 0; while (!was_packed && !is_cancelled()) { - for (; j < bed_contexts.size() && !was_packed && !is_cancelled(); j++) { - set_bed_index(*it, int(j)); + for (; !was_packed && !is_cancelled(); bedidx++) { + set_bed_index(*it, bedidx); auto remaining = Range{std::next(static_cast(it)), sorted_items.cend()}; - was_packed = pack(ps, bed, *it, bed_contexts[j], remaining); + Context &ctx = get_or_init_context(bedidx); + + was_packed = pack(ps, bed, *it, ctx, remaining); if(was_packed) { - add_packed_item(bed_contexts[j], *it); + add_packed_item(ctx, *it); auto packed_range = Range{sorted_items.cbegin(), static_cast(it)}; @@ -145,12 +155,6 @@ void arrange( set_bed_index(*it, Unarranged); } } - - if (!was_packed) { - bed_contexts.emplace_back( - create_context(ps, bed, bed_contexts.size())); - j = bed_contexts.size() - 1; - } } ++it; } diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index 75990e2f84..12c4556670 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -304,22 +304,28 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const return tr; } -const int GridStriderVBedHandler::ColsOutside = - static_cast(std::sqrt(std::numeric_limits::max())); +const int GridStriderVBedHandler::Cols = + 2 * static_cast(std::sqrt(std::numeric_limits::max()) / 2); + +const int GridStriderVBedHandler::HalfCols = Cols / 2; +const int GridStriderVBedHandler::Offset = HalfCols + Cols * HalfCols; Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const { - Vec2i ret{bed_idx % ColsOutside, bed_idx / ColsOutside}; + bed_idx += Offset; + + Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols}; return ret; } int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const { - assert(std::abs(crd.x()) < ColsOutside - 1 && - std::abs(crd.y()) < ColsOutside - 1); + // Overlapping virtual beds will happen if the crd values exceed limits + assert((crd.x() < HalfCols - 1 && crd.x() >= -HalfCols) && + (crd.y() < HalfCols - 1 && crd.y() >= -HalfCols)); - return crd.y() * ColsOutside + crd.x(); + return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset; } int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index d159669f51..54146cd3cc 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -338,7 +338,9 @@ class GridStriderVBedHandler: public VirtualBedHandler // not representable with scaled coordinates. Combining XStrider with // YStrider takes care of the X and Y axis to be mapped into the physical // bed's coordinate region (which is representable in scaled coords) - static const int ColsOutside; + static const int Cols; + static const int HalfCols; + static const int Offset; XStriderVBedHandler m_xstrider; YStriderVBedHandler m_ystrider; diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index 8925ea4b95..a46152ed98 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -75,6 +75,21 @@ void prepare_fixed_unselected(ItemCont &items, int shift) items.end()); } +inline int find_first_empty_bed(const std::vector& bed_indices, + int starting_from = 0) { + int ret = starting_from; + + for (int idx : bed_indices) { + if (idx == ret) { + ret++; + } else if (idx > ret) { + break; + } + } + + return ret; +} + template std::unique_ptr ArrangeTask::process_native(Ctl &ctl) @@ -109,15 +124,17 @@ ArrangeTask::process_native(Ctl &ctl) arranger->arrange(printable.selected, fixed_items, bed, subctl); - // Unprintable items should go to the first bed not containing any printable - // items - auto beds = std::max(get_bed_count(crange(printable.selected)), - get_bed_count(crange(printable.unselected))); + std::vector printable_bed_indices = + get_bed_indices(crange(printable.selected), crange(printable.unselected)); // If there are no printables, leave the physical bed empty - beds = std::max(beds, size_t{1}); + constexpr int SearchFrom = 1; - prepare_fixed_unselected(unprintable.unselected, beds); + // Unprintable items should go to the first logical (!) bed not containing + // any printable items + int first_empty_bed = find_first_empty_bed(printable_bed_indices, SearchFrom); + + prepare_fixed_unselected(unprintable.unselected, first_empty_bed); arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl); @@ -125,7 +142,7 @@ ArrangeTask::process_native(Ctl &ctl) for (auto &itm : unprintable.selected) { if (is_arranged(itm)) { - int bedidx = get_bed_index(itm) + beds; + int bedidx = get_bed_index(itm) + first_empty_bed; arr2::set_bed_index(itm, bedidx); } From 3277b5ee061ffa2a0117d18da446ab43d0722179 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Aug 2023 19:29:16 +0200 Subject: [PATCH 088/120] Fix compilation on in VS 2019 in certain environments MSVC crashes when compiling Thanks to @enricoturri1966 for reporting and fixing --- tests/arrange/test_arrange.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp index 0304a29db9..56bba72a53 100644 --- a/tests/arrange/test_arrange.cpp +++ b/tests/arrange/test_arrange.cpp @@ -760,7 +760,7 @@ TEST_CASE("First fit selection strategy", "[arrange2]") THEN("The item should be left unpacked") { - REQUIRE(std::all_of(items.begin(), items.end(), [](auto &itm) { + REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) { return !Slic3r::arr2::is_arranged(itm); })); } @@ -878,7 +878,7 @@ TEST_CASE("First fit selection strategy", "[arrange2]") THEN("all items should fit onto the beds from index 0 to 4") { - REQUIRE(std::all_of(items.begin(), items.end(), [](auto &itm) { + REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) { auto bed_idx = Slic3r::arr2::get_bed_index(itm); return bed_idx >= 0 && bed_idx < Count / Capacity; })); From d69d477480b4ae51661a5084c100d80e1ecb6010 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 13:07:44 +0200 Subject: [PATCH 089/120] Fix wipe tower arrangement --- src/libslic3r/Arrange/ArrangeImpl.hpp | 28 ++++++++------------------ src/libslic3r/Arrange/Scene.hpp | 8 ++++++++ src/libslic3r/Arrange/SceneBuilder.cpp | 17 +++++++++++++--- src/libslic3r/Arrange/SceneBuilder.hpp | 7 ++++--- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 2bbe9ab29c..afe919ccff 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -45,35 +45,22 @@ void arrange(SelectionStrategy &&selstrategy, RectangleBed{bed.bb}, SelStrategyTag{}); std::vector bed_indices = get_bed_indices(items, fixed); - - size_t beds = bed_indices.size(); - - auto fixed_is_empty = [&bed_indices](int bidx) { - auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); - return it == bed_indices.end() || *it != bidx; - }; - - auto set_bed_as_empty = [&bed_indices](int bidx) { - auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx); - if (it != bed_indices.end()) - bed_indices.erase(it); - }; - - std::vector pilebb(bed_indices.size()); + std::map pilebb; + std::map bed_occupied; for (auto &itm : items) { auto bedidx = get_bed_index(itm); if (bedidx >= 0) { pilebb[bedidx].merge(fixed_bounding_box(itm)); if (is_wipe_tower(itm)) - set_bed_as_empty(bedidx); + bed_occupied[bedidx] = true; } } for (auto &fxitm : fixed) { auto bedidx = get_bed_index(fxitm); - if (bedidx >= 0 || is_wipe_tower(fxitm)) - set_bed_as_empty(bedidx); + if (bedidx >= 0) + bed_occupied[bedidx] = true; } auto bedbb = bounding_box(bed); @@ -85,8 +72,9 @@ void arrange(SelectionStrategy &&selstrategy, Pivots pivot = bed.alignment(); - for (size_t bedidx = 0; bedidx < beds; ++bedidx) { - if (! fixed_is_empty(bedidx)) + for (int bedidx : bed_indices) { + if (auto occup_it = bed_occupied.find(bedidx); + occup_it != bed_occupied.end() && occup_it->second) continue; BoundingBox bb; diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index f917ed3c2f..e7f6c2a7c0 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -161,6 +161,14 @@ template void visit_bed(BedFn &&fn, ExtendedBed &bed) boost::apply_visitor(fn, bed); } +inline BoundingBox bounding_box(const ExtendedBed &bed) +{ + BoundingBox bedbb; + visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed); + + return bedbb; +} + class Scene; // SceneBuilderBase is intended for Scene construction. A simple constructor diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index 12c4556670..f9453c9721 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -199,6 +199,17 @@ void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel m_wipetower_handler = std::make_unique(); } + if (m_fff_print && !m_xl_printer) + m_xl_printer = is_XL_printer(m_fff_print->config()); + + bool has_wipe_tower = false; + m_wipetower_handler->visit( + [&has_wipe_tower](const Arrangeable &arrbl) { has_wipe_tower = true; }); + + if (m_xl_printer && !has_wipe_tower) { + m_bed = XLBed{bounding_box(m_bed)}; + } + amodel.m_vbed_handler = std::move(m_vbed_handler); amodel.m_model = std::move(m_model); amodel.m_selmask = std::move(m_selection); @@ -443,11 +454,11 @@ SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg) Points bedpts = get_bed_shape(cfg); if (is_XL_printer(cfg)) { - m_bed = XLBed{get_extents(bedpts)}; - } else { - m_bed = arr2::to_arrange_bed(bedpts); + m_xl_printer = true; } + m_bed = arr2::to_arrange_bed(bedpts); + return std::move(*this); } diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 54146cd3cc..8f4f8a7797 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -205,6 +205,7 @@ protected: AnyPtr m_sla_print; AnyPtr m_fff_print; + bool m_xl_printer = false; void set_brim_and_skirt(); @@ -498,9 +499,9 @@ class ArrangeableSLAPrint : public ArrangeableSlicerModel { static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn); public: - explicit ArrangeableSLAPrint(const SLAPrint *slaprint, - SceneBuilder &builder) - : m_slaprint{slaprint}, ArrangeableSlicerModel{builder} + explicit ArrangeableSLAPrint(const SLAPrint *slaprint, SceneBuilder &builder) + : m_slaprint{slaprint} + , ArrangeableSlicerModel{builder} { assert(slaprint != nullptr); } From 391bf14559088c7830e48322f695304188119d24 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 18 Aug 2023 15:02:49 +0200 Subject: [PATCH 090/120] CutGizmo: Added new icon for "Snap" connectors --- resources/icons/snap.svg | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/resources/icons/snap.svg b/resources/icons/snap.svg index 0242c8e3bc..b1080a1847 100644 --- a/resources/icons/snap.svg +++ b/resources/icons/snap.svg @@ -1,13 +1,7 @@ - - - - - - - - - - - + + + + + + + \ No newline at end of file From 44d91ec3538fd8dbbc16fee485cfd9f5d6729866 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 16:49:01 +0200 Subject: [PATCH 091/120] Use gravity kernel for non rectangular beds Also change sorting of outlines based on convex hull area and --- src/libslic3r/Arrange/ArrangeImpl.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index afe919ccff..5c567d646f 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -272,7 +272,7 @@ class DefaultArranger: public Arranger { int pa = get_priority(itm1); int pb = get_priority(itm2); - return pa == pb ? envelope_area(itm1) > envelope_area(itm2) : + return pa == pb ? area(envelope_convex_hull(itm1)) > area(envelope_convex_hull(itm2)) : pa > pb; }; @@ -295,7 +295,11 @@ class DefaultArranger: public Arranger { default: [[fallthrough]]; case ArrangeSettingsView::asAuto: - basekernel = TMArrangeKernel{items.size(), area(bed)}; + if constexpr (std::is_convertible_v){ + basekernel = TMArrangeKernel{items.size(), area(bed)}; + } else { + basekernel = GravityKernel{}; + } break; case ArrangeSettingsView::asPullToCenter: basekernel = GravityKernel{}; From 01d5f04295fa553121dd0839b0f2448cc52203d2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 16:49:50 +0200 Subject: [PATCH 092/120] Fix outline bound check for full outlines --- src/libslic3r/Arrange/SceneBuilder.cpp | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index f9453c9721..53a785ea8d 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -99,25 +99,23 @@ bool check_coord_bounds(const BoundingBoxf &bb) ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) { ExPolygons outline; - for (const ModelVolume *v : inst.get_object()->volumes) { - Polygons vol_outline; - if (!check_coord_bounds(to_2d(v->mesh().transformed_bounding_box(tr)))) { - outline.clear(); - break; - } + if (check_coord_bounds(to_2d(instance_bounding_box(inst, tr)))) { + for (const ModelVolume *v : inst.get_object()->volumes) { + Polygons vol_outline; - vol_outline = project_mesh(v->mesh().its, - tr * inst.get_matrix() * v->get_matrix(), - [] {}); - switch (v->type()) { - case ModelVolumeType::MODEL_PART: - outline = union_ex(outline, vol_outline); - break; - case ModelVolumeType::NEGATIVE_VOLUME: - outline = diff_ex(outline, vol_outline); - break; - default:; + vol_outline = project_mesh(v->mesh().its, + tr * inst.get_matrix() * v->get_matrix(), + [] {}); + switch (v->type()) { + case ModelVolumeType::MODEL_PART: + outline = union_ex(outline, vol_outline); + break; + case ModelVolumeType::NEGATIVE_VOLUME: + outline = diff_ex(outline, vol_outline); + break; + default:; + } } } From b2d43c8719c5d141e20f11dba21736e25179bca9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 16:50:35 +0200 Subject: [PATCH 093/120] Fix arrange in fff tests --- src/libslic3r/Arrange/Scene.cpp | 4 ++-- src/libslic3r/Arrange/Scene.hpp | 22 +++++++++++++++++++--- src/libslic3r/ModelArrange.cpp | 19 ++++++------------- tests/fff_print/test_data.cpp | 2 +- tests/fff_print/test_model.cpp | 8 ++++++-- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Arrange/Scene.cpp b/src/libslic3r/Arrange/Scene.cpp index a59b3f9aec..ddcc5ead67 100644 --- a/src/libslic3r/Arrange/Scene.cpp +++ b/src/libslic3r/Arrange/Scene.cpp @@ -54,11 +54,11 @@ std::set selected_geometry_ids(const Scene &sc) return result; } -void arrange(Scene &scene, ArrangeTaskCtl &ctl) +bool arrange(Scene &scene, ArrangeTaskCtl &ctl) { auto task = ArrangeTaskBase::create(Tasks::Arrange, scene); auto result = task->process(ctl); - result->apply_on(scene.model()); + return result->apply_on(scene.model()); } }} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index e7f6c2a7c0..16622e464a 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -373,10 +373,26 @@ public: } }; -void arrange(Scene &scene, ArrangeTaskCtl &ctl); -inline void arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{}) +bool arrange(Scene &scene, ArrangeTaskCtl &ctl); +inline bool arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{}) { - arrange(scene, ctl); + return arrange(scene, ctl); +} + +inline bool arrange(Scene &&scene, ArrangeTaskCtl &ctl) +{ + return arrange(scene, ctl); +} + +inline bool arrange(Scene &&scene, ArrangeTaskCtl &&ctl = DummyCtl{}) +{ + return arrange(scene, ctl); +} + +template +bool arrange(SceneBuilderBase &&builder, Ctl &&ctl = {}) +{ + return arrange(Scene{std::move(builder)}, ctl); } } // namespace arr2 diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index f0294e8a05..b5f53dfed5 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,14 +1,11 @@ #include "ModelArrange.hpp" - #include +#include +#include #include #include -#include "Arrange/Core/ArrangeItemTraits.hpp" -#include "Arrange/Items/ArrangeItem.hpp" - -#include "MTUtils.hpp" namespace Slic3r { @@ -27,14 +24,10 @@ bool arrange_objects(Model &model, const arr2::ArrangeBed &bed, const arr2::ArrangeSettingsView &settings) { - arr2::Scene scene{arr2::SceneBuilder{} - .set_bed(bed) - .set_arrange_settings(settings) - .set_model(model)}; - - auto task = arr2::ArrangeTaskBase::create(arr2::Tasks::Arrange, scene); - auto result = task->process(); - return result->apply_on(scene.model()); + return arrange(arr2::SceneBuilder{} + .set_bed(bed) + .set_arrange_settings(settings) + .set_model(model)); } void duplicate_objects(Model &model, diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 8f9367ce51..fa41f88e2f 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -228,7 +228,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r object->add_volume(std::move(t)); object->add_instance(); } - arrange_objects(model, arr2::InfiniteBed{}, arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); + arrange_objects(model, arr2::to_arrange_bed(get_bed_shape(config)), arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); model.center_instances_around_point({100, 100}); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index d59a655663..243cb10d1d 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -42,8 +42,12 @@ SCENARIO("Model construction", "[Model]") { } } model_object->add_instance(); - arrange_objects(model, arr2::InfiniteBed{scaled(Vec2d(100, 100))}, arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); - model_object->ensure_on_bed(); + arrange_objects(model, + arr2::to_arrange_bed(get_bed_shape(config)), + arr2::ArrangeSettings{}.set_distance_from_objects( + min_object_distance(config))); + + model_object->ensure_on_bed(); print.auto_assign_extruders(model_object); THEN("Print works?") { print.set_status_silent(); From e6a722ab28ce5cda05694cf8f24489f8bbf7e225 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 17:42:47 +0200 Subject: [PATCH 094/120] Use gravity kernel only for circular beds --- src/libslic3r/Arrange/ArrangeImpl.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 5c567d646f..79f474bc2d 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -295,10 +295,10 @@ class DefaultArranger: public Arranger { default: [[fallthrough]]; case ArrangeSettingsView::asAuto: - if constexpr (std::is_convertible_v){ - basekernel = TMArrangeKernel{items.size(), area(bed)}; - } else { + if constexpr (std::is_convertible_v){ basekernel = GravityKernel{}; + } else { + basekernel = TMArrangeKernel{items.size(), area(bed)}; } break; case ArrangeSettingsView::asPullToCenter: From 08b0684bac2772d7b43d7c995a16586fa4333f2f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Aug 2023 17:43:06 +0200 Subject: [PATCH 095/120] Fix circular bed shape recognition --- src/libslic3r/Arrange/Core/Beds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Arrange/Core/Beds.cpp b/src/libslic3r/Arrange/Core/Beds.cpp index 90d5f4f77d..1c96723767 100644 --- a/src/libslic3r/Arrange/Core/Beds.cpp +++ b/src/libslic3r/Arrange/Core/Beds.cpp @@ -110,7 +110,7 @@ template auto call_with_bed(const Points &bed, Fn &&fn) if ((1.0 - parea / area(bb)) < 1e-3) { return fn(RectangleBed{bb}); - } else if (!std::isnan(circ.radius())) + } else if (!std::isnan(circ.radius()) && (1.0 - parea / area(circ)) < 1e-2) return fn(circ); else return fn(IrregularBed{{ExPolygon(bed)}}); From b809996d307ee1843ba137135792a34ccf1be18c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 7 Jun 2023 15:06:54 +0200 Subject: [PATCH 096/120] SPE-1761: Show part shells in pre-gcode preview --- src/slic3r/GUI/GCodeViewer.cpp | 12 ++++++------ src/slic3r/GUI/GCodeViewer.hpp | 6 +++++- src/slic3r/GUI/GLCanvas3D.cpp | 12 ++++++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/GUI_Preview.cpp | 5 +++++ src/slic3r/GUI/GUI_Preview.hpp | 1 + src/slic3r/GUI/Plater.cpp | 1 + 7 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ed4713737f..38582c7aa8 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -747,9 +747,7 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr m_filament_diameters = gcode_result.filament_diameters; m_filament_densities = gcode_result.filament_densities; - if (wxGetApp().is_editor()) - load_shells(print); - else { + if (!wxGetApp().is_editor()) { Pointfs bed_shape; std::string texture; std::string model; @@ -903,7 +901,6 @@ void GCodeViewer::reset() m_filament_diameters = std::vector(); m_filament_densities = std::vector(); m_extrusions.reset_ranges(); - m_shells.volumes.clear(); m_layers.reset(); m_layers_z_range = { 0, 0 }; m_roles = std::vector(); @@ -927,12 +924,13 @@ void GCodeViewer::render() m_statistics.total_instances_gpu_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS + glsafe(::glEnable(GL_DEPTH_TEST)); + render_shells(); + if (m_roles.empty()) return; - glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); - render_shells(); float legend_height = 0.0f; if (!m_layers.empty()) { render_legend(legend_height); @@ -2226,6 +2224,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) void GCodeViewer::load_shells(const Print& print) { + m_shells.volumes.clear(); + if (print.objects().empty()) // no shells, return return; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 32d0a91e34..7c387d59c4 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -838,6 +838,9 @@ public: bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } + bool are_shells_visible() const { return m_shells.visible; } + void set_shells_visible(bool visible) { m_shells.visible = visible; } + void export_toolpaths_to_obj(const char* filename) const; void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); } @@ -849,9 +852,10 @@ public: const ConflictResultOpt& get_conflict_result() const { return m_conflict_result; } + void load_shells(const Print& print); + private: void load_toolpaths(const GCodeProcessorResult& gcode_result); - void load_shells(const Print& print); void render_toolpaths(); void render_shells(); void render_legend(float& legend_height); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a5be3675d9..c161d67b90 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1913,7 +1913,7 @@ void GLCanvas3D::render() _render_bed_axes(); if (is_looking_downward) _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false); - if (!m_main_toolbar.is_enabled()) + if (!m_main_toolbar.is_enabled() && current_printer_technology() != ptSLA) _render_gcode(); _render_objects(GLVolumeCollection::ERenderType::Transparent); @@ -2637,12 +2637,19 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_dirty = true; } +void GLCanvas3D::load_gcode_shells() +{ + m_gcode_viewer_shells_visible = m_gcode_viewer.are_shells_visible(); + m_gcode_viewer.load_shells(*this->fff_print()); + m_gcode_viewer.update_shells_color_by_extruder(m_config); + m_gcode_viewer.set_shells_visible(true); +} + void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) { m_gcode_viewer.load(gcode_result, *this->fff_print()); if (wxGetApp().is_editor()) { - m_gcode_viewer.update_shells_color_by_extruder(m_config); _set_warning_notification_if_needed(EWarning::ToolpathOutside); _set_warning_notification_if_needed(EWarning::GCodeConflict); } @@ -2690,6 +2697,7 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c for (const PrintObject* object : print->objects()) _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); + m_gcode_viewer.set_shells_visible(m_gcode_viewer_shells_visible); _set_warning_notification_if_needed(EWarning::ToolpathOutside); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 8ec2260467..7890e96589 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -509,6 +509,7 @@ private: TriangleMesh m_wipe_tower_mesh; #endif // ENABLE_OPENGL_ES GCodeViewer m_gcode_viewer; + bool m_gcode_viewer_shells_visible{ true }; RenderTimer m_render_timer; @@ -829,6 +830,7 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); + void load_gcode_shells(); void load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); void refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last); void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 74a8719d8e..c4f942406d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -279,6 +279,11 @@ void Preview::set_drop_target(wxDropTarget* target) SetDropTarget(target); } +void Preview::load_gcode_shells() +{ + m_canvas->load_gcode_shells(); +} + void Preview::load_print(bool keep_z_range) { PrinterTechnology tech = m_process->current_printer_technology(); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index c7b7fdc61d..7c5b111227 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -132,6 +132,7 @@ public: void select_view(const std::string& direction); void set_drop_target(wxDropTarget* target); + void load_gcode_shells(); void load_print(bool keep_z_range = false); void reload_print(bool keep_volumes = false); void refresh_print(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7d137698cf..7d710849fc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3991,6 +3991,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; if (!model.objects.empty() && !export_in_progress && model_fits) { preview->get_canvas3d()->init_gcode_viewer(); + preview->load_gcode_shells(); q->reslice(); } // keeps current gcode preview, if any From d48b01d939a8d31d11a4c4370ff7d50da89a3c29 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Aug 2023 08:29:56 +0200 Subject: [PATCH 097/120] Fixed color of shells shown in preview --- src/slic3r/GUI/GCodeViewer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 38582c7aa8..a475ce880d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3209,8 +3209,10 @@ void GCodeViewer::render_shells() return; shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); const Camera& camera = wxGetApp().plater()->get_camera(); m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, camera.get_view_matrix(), camera.get_projection_matrix()); + shader->set_uniform("emission_factor", 0.0f); shader->stop_using(); } From 61c8940cf6007560d77843072d28846d3f583c17 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Aug 2023 09:29:08 +0200 Subject: [PATCH 098/120] Fixed camera frustum for preview to take in account for shells --- src/slic3r/GUI/GCodeViewer.cpp | 17 +++++++++++------ src/slic3r/GUI/GCodeViewer.hpp | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a475ce880d..12567c5c74 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -892,8 +892,8 @@ void GCodeViewer::reset() buffer.reset(); } - m_paths_bounding_box = BoundingBoxf3(); - m_max_bounding_box = BoundingBoxf3(); + m_paths_bounding_box.reset(); + m_max_bounding_box.reset(); m_max_print_height = 0.0f; m_tool_colors = std::vector(); m_extruders_count = 0; @@ -1541,6 +1541,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) m_statistics.results_time = gcode_result.time; #endif // ENABLE_GCODE_VIEWER_STATISTICS + m_max_bounding_box.reset(); + m_moves_count = gcode_result.moves.size(); if (m_moves_count == 0) return; @@ -1566,10 +1568,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) } } - // set approximate max bounding box (take in account also the tool marker) - m_max_bounding_box = m_paths_bounding_box; - m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); - if (wxGetApp().is_editor()) m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); @@ -2289,6 +2287,13 @@ void GCodeViewer::load_shells(const Print& print) volume->force_native_color = true; volume->set_render_color(true); } + + m_shells_bounding_box.reset(); + for (const GLVolume* volume : m_shells.volumes.volumes) { + m_shells_bounding_box.merge(volume->transformed_bounding_box()); + } + + m_max_bounding_box.reset(); } void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 7c387d59c4..50c27e5fa4 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -752,7 +752,9 @@ private: std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; - // bounding box of toolpaths + marker tools + // bounding box of shells + BoundingBoxf3 m_shells_bounding_box; + // bounding box of toolpaths + marker tools + shells BoundingBoxf3 m_max_bounding_box; float m_max_print_height{ 0.0f }; std::vector m_tool_colors; @@ -811,7 +813,16 @@ public: bool can_export_toolpaths() const; const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; } - const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; } + const BoundingBoxf3& get_shells_bounding_box() const { return m_shells_bounding_box; } + const BoundingBoxf3& get_max_bounding_box() const { + BoundingBoxf3& max_bounding_box = const_cast(m_max_bounding_box); + if (!max_bounding_box.defined) { + max_bounding_box = m_shells_bounding_box; + max_bounding_box.merge(m_paths_bounding_box); + max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); + } + return m_max_bounding_box; + } const std::vector& get_layers_zs() const { return m_layers.get_zs(); } const SequentialView& get_sequential_view() const { return m_sequential_view; } From fbd7d143151b5115771b4fd6ab37b15fd6d762f9 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Aug 2023 11:08:45 +0200 Subject: [PATCH 099/120] Fixed wipe tower shell visibility for preview --- src/slic3r/GUI/GCodeViewer.cpp | 43 +++++++++++++++++++++------------- src/slic3r/GUI/GCodeViewer.hpp | 1 + 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 12567c5c74..cb0775c626 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -739,6 +739,7 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr m_max_print_height = gcode_result.max_print_height; load_toolpaths(gcode_result); + load_wipetower_shell(print); if (m_layers.empty()) return; @@ -2254,22 +2255,6 @@ void GCodeViewer::load_shells(const Print& print) ++object_id; } - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { - // adds wipe tower's volume - const double max_z = print.objects()[0]->model_object()->get_model()->max_z(); - const PrintConfig& config = print.config(); - const size_t extruders_count = config.nozzle_diameter.size(); - if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { - const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count); - const float depth = wipe_tower_data.depth; - const std::vector> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs; - const float brim_width = wipe_tower_data.brim_width; - if (depth != 0.) - m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, z_and_depth_pairs, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle, - !print.is_step_done(psWipeTower), brim_width); - } - } - // remove modifiers while (true) { GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); @@ -2296,6 +2281,32 @@ void GCodeViewer::load_shells(const Print& print) m_max_bounding_box.reset(); } +void GCodeViewer::load_wipetower_shell(const Print& print) +{ + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF && print.is_step_done(psWipeTower)) { + // adds wipe tower's volume + const double max_z = print.objects()[0]->model_object()->get_model()->max_z(); + const PrintConfig& config = print.config(); + const size_t extruders_count = config.nozzle_diameter.size(); + if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { + const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count); + const float depth = wipe_tower_data.depth; + const std::vector> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs; + const float brim_width = wipe_tower_data.brim_width; + if (depth != 0.) { + m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, z_and_depth_pairs, + max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle, false, brim_width); + GLVolume* volume = m_shells.volumes.volumes.back(); + volume->color.a(0.25f); + volume->force_native_color = true; + volume->set_render_color(true); + m_shells_bounding_box.merge(volume->transformed_bounding_box()); + m_max_bounding_box.reset(); + } + } + } +} + void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const { #if ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 50c27e5fa4..aa021089d1 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -867,6 +867,7 @@ public: private: void load_toolpaths(const GCodeProcessorResult& gcode_result); + void load_wipetower_shell(const Print& print); void render_toolpaths(); void render_shells(); void render_legend(float& legend_height); From ded52197194b14decf23f70556af520023cd9afa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Aug 2023 13:01:47 +0200 Subject: [PATCH 100/120] Fixed shells of sinking volumes for preview --- src/slic3r/GUI/GCodeViewer.cpp | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index cb0775c626..93d80cc3c0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2259,12 +2259,38 @@ void GCodeViewer::load_shells(const Print& print) while (true) { GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); if (it != m_shells.volumes.volumes.end()) { - delete (*it); + delete *it; m_shells.volumes.volumes.erase(it); } else break; - } + } + + // removes volumes which are completely below bed + int i = 0; + while (i < m_shells.volumes.volumes.size()) { + GLVolume* v = m_shells.volumes.volumes[i]; + if (v->transformed_bounding_box().max.z() < SINKING_MIN_Z_THRESHOLD + EPSILON) { + delete v; + m_shells.volumes.volumes.erase(m_shells.volumes.volumes.begin() + i); + --i; + } + ++i; + } + + // search for sinking volumes and replace their mesh with the part of it with positive z + for (GLVolume* v : m_shells.volumes.volumes) { + if (v->is_sinking()) { + TriangleMesh mesh(wxGetApp().plater()->model().objects[v->object_idx()]->volumes[v->volume_idx()]->mesh()); + mesh.transform(v->world_matrix(), true); + indexed_triangle_set upper_its; + cut_mesh(mesh.its, 0.0f, &upper_its, nullptr); + v->model.reset(); + v->model.init_from(upper_its); + v->set_instance_transformation(Transform3d::Identity()); + v->set_volume_transformation(Transform3d::Identity()); + } + } for (GLVolume* volume : m_shells.volumes.volumes) { volume->zoom_to_volumes = false; From 200df9c02d91f517bfe4473fb3d1fb7be86425bb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 16 Aug 2023 16:04:15 +0200 Subject: [PATCH 101/120] Do not show shell for non-printable and out of printbed volumes in preview --- src/slic3r/GUI/GCodeViewer.cpp | 9 ++++-- src/slic3r/GUI/GCodeViewer.hpp | 4 +-- src/slic3r/GUI/GLCanvas3D.cpp | 51 +++++++++++++++++++--------------- src/slic3r/GUI/GLCanvas3D.hpp | 8 ++++-- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 93d80cc3c0..9301132c26 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2255,9 +2255,12 @@ void GCodeViewer::load_shells(const Print& print) ++object_id; } - // remove modifiers + wxGetApp().plater()->get_current_canvas3D()->check_volumes_outside_state(m_shells.volumes); + + // remove modifiers, non-printable and out-of-bed volumes while (true) { - GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); + GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), + [](GLVolume* volume) { return volume->is_modifier || !volume->printable || volume->is_outside; }); if (it != m_shells.volumes.volumes.end()) { delete *it; m_shells.volumes.volumes.erase(it); @@ -3243,7 +3246,7 @@ void GCodeViewer::render_toolpaths() void GCodeViewer::render_shells() { - if (!m_shells.visible || m_shells.volumes.empty()) + if (m_shells.volumes.empty() || (!m_shells.visible && !m_shells.force_visible)) return; GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index aa021089d1..85a252881c 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -378,6 +378,7 @@ class GCodeViewer { GLVolumeCollection volumes; bool visible{ false }; + bool force_visible{ false }; }; // helper to render center of gravity @@ -849,8 +850,7 @@ public: bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } - bool are_shells_visible() const { return m_shells.visible; } - void set_shells_visible(bool visible) { m_shells.visible = visible; } + void set_force_shells_visible(bool visible) { m_shells.force_visible = visible; } void export_toolpaths_to_obj(const char* filename) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c161d67b90..b4c36dd635 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1442,11 +1442,16 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(bool sele { ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; if (m_initialized && !m_volumes.empty()) - check_volumes_outside_state(m_bed.build_volume(), &state, selection_only); + check_volumes_outside_state(const_cast(m_volumes), &state, selection_only); return state; } -bool GLCanvas3D::check_volumes_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state, bool selection_only) const +void GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes) const +{ + check_volumes_outside_state(volumes, nullptr, false); +} + +bool GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes, ModelInstanceEPrintVolumeState* out_state, bool selection_only) const { auto volume_below = [](GLVolume& volume) -> bool { return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_below_printbed(); }; @@ -1460,26 +1465,27 @@ bool GLCanvas3D::check_volumes_outside_state(const Slic3r::BuildVolume& build_vo auto volume_convex_mesh = [this, volume_sinking](GLVolume& volume) -> const TriangleMesh& { return volume_sinking(volume) ? m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : *volume.convex_hull(); }; - auto volumes_to_process_idxs = [this, selection_only]() { - std::vector ret; - if (!selection_only || m_selection.is_empty()) { - ret = std::vector(m_volumes.volumes.size()); - std::iota(ret.begin(), ret.end(), 0); - } - else { - const GUI::Selection::IndicesList& selected_volume_idxs = m_selection.get_volume_idxs(); - ret.assign(selected_volume_idxs.begin(), selected_volume_idxs.end()); - } - return ret; + auto volumes_to_process_idxs = [this, &volumes, selection_only]() { + std::vector ret; + if (!selection_only || m_selection.is_empty()) { + ret = std::vector(volumes.volumes.size()); + std::iota(ret.begin(), ret.end(), 0); + } + else { + const GUI::Selection::IndicesList& selected_volume_idxs = m_selection.get_volume_idxs(); + ret.assign(selected_volume_idxs.begin(), selected_volume_idxs.end()); + } + return ret; }; ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside; bool contained_min_one = false; - const std::vector volumes_idxs = volumes_to_process_idxs(); + const Slic3r::BuildVolume& build_volume = m_bed.build_volume(); + const std::vector volumes_idxs = volumes_to_process_idxs(); for (unsigned int vol_idx : volumes_idxs) { - GLVolume* volume = m_volumes.volumes[vol_idx]; + GLVolume* volume = volumes.volumes[vol_idx]; if (!volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (!volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) { BuildVolume::ObjectState state; if (volume_below(*volume)) @@ -1492,7 +1498,7 @@ bool GLCanvas3D::check_volumes_outside_state(const Slic3r::BuildVolume& build_vo break; case BuildVolume::Type::Circle: case BuildVolume::Type::Convex: - //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. case BuildVolume::Type::Custom: state = build_volume.object_state(volume_convex_mesh(*volume).its, volume->world_matrix().cast(), volume_sinking(*volume)); break; @@ -1514,9 +1520,9 @@ bool GLCanvas3D::check_volumes_outside_state(const Slic3r::BuildVolume& build_vo } } - for (unsigned int vol_idx = 0; vol_idx < m_volumes.volumes.size(); ++vol_idx) { + for (unsigned int vol_idx = 0; vol_idx < volumes.volumes.size(); ++vol_idx) { if (std::find(volumes_idxs.begin(), volumes_idxs.end(), vol_idx) == volumes_idxs.end()) { - if (!m_volumes.volumes[vol_idx]->is_outside) { + if (!volumes.volumes[vol_idx]->is_outside) { contained_min_one = true; break; } @@ -2562,7 +2568,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { ModelInstanceEPrintVolumeState state; - const bool contained_min_one = check_volumes_outside_state(m_bed.build_volume(), &state, !force_full_scene_refresh); + const bool contained_min_one = check_volumes_outside_state(m_volumes, &state, !force_full_scene_refresh); const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); @@ -2639,10 +2645,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re void GLCanvas3D::load_gcode_shells() { - m_gcode_viewer_shells_visible = m_gcode_viewer.are_shells_visible(); m_gcode_viewer.load_shells(*this->fff_print()); m_gcode_viewer.update_shells_color_by_extruder(m_config); - m_gcode_viewer.set_shells_visible(true); + m_gcode_viewer.set_force_shells_visible(true); } void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) @@ -2697,7 +2702,7 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c for (const PrintObject* object : print->objects()) _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); - m_gcode_viewer.set_shells_visible(m_gcode_viewer_shells_visible); + m_gcode_viewer.set_force_shells_visible(false); _set_warning_notification_if_needed(EWarning::ToolpathOutside); } @@ -6036,7 +6041,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) } } if (m_requires_check_outside_state) { - check_volumes_outside_state(build_volume, nullptr); + check_volumes_outside_state(m_volumes, nullptr); m_requires_check_outside_state = false; } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7890e96589..3c91ab1b41 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -509,7 +509,6 @@ private: TriangleMesh m_wipe_tower_mesh; #endif // ENABLE_OPENGL_ES GCodeViewer m_gcode_viewer; - bool m_gcode_viewer_shells_visible{ true }; RenderTimer m_render_timer; @@ -712,10 +711,15 @@ public: const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); ModelInstanceEPrintVolumeState check_volumes_outside_state(bool selection_only = true) const; + // update the is_outside state of all the volumes contained in the given collection + void check_volumes_outside_state(GLVolumeCollection& volumes) const; + +private: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null - bool check_volumes_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state, bool selection_only = true) const; + bool check_volumes_outside_state(GLVolumeCollection& volumes, ModelInstanceEPrintVolumeState* out_state, bool selection_only = true) const; +public: void init_gcode_viewer() { m_gcode_viewer.init(); } void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } From b66402c62d9e92beaf57609b2e715f762a409806 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 21 Aug 2023 10:18:28 +0200 Subject: [PATCH 102/120] Fixed warning --- src/slic3r/GUI/GCodeViewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 9301132c26..b23fd76b70 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2271,7 +2271,7 @@ void GCodeViewer::load_shells(const Print& print) // removes volumes which are completely below bed int i = 0; - while (i < m_shells.volumes.volumes.size()) { + while (i < (int)m_shells.volumes.volumes.size()) { GLVolume* v = m_shells.volumes.volumes[i]; if (v->transformed_bounding_box().max.z() < SINKING_MIN_Z_THRESHOLD + EPSILON) { delete v; From c6a8b01073b03d230d3fc6707e6bbab8f9b10a72 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 28 Jun 2023 13:31:37 +0200 Subject: [PATCH 103/120] Make 'retraction when tool is disabled' parameters overriddable in Filament Overrides --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 5 ++++- src/slic3r/GUI/Tab.cpp | 19 ++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 906b4c76b6..376529a14e 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -474,7 +474,7 @@ static std::vector s_Preset_filament_options { "overhang_fan_speed_0", "overhang_fan_speed_1", "overhang_fan_speed_2", "overhang_fan_speed_3", // Retract overrides "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", - "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", + "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", "filament_retract_length_toolchange", "filament_retract_restart_extra_toolchange", // Profile compatibility "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4b17e2c3d5..83dd450c23 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3376,7 +3376,8 @@ void PrintConfigDef::init_fff_params() // Declare retract values for filament profile, overriding the printer's extruder profile. for (const char *opt_key : { // floats - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", + "deretract_speed", "retract_restart_extra", "retract_before_travel", "retract_length_toolchange", "retract_restart_extra_toolchange", // bools "retract_layer_change", "wipe", // percents @@ -3415,10 +3416,12 @@ void PrintConfigDef::init_extruder_option_keys() "retract_before_wipe", "retract_layer_change", "retract_length", + "retract_length_toolchange", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_restart_extra", + "retract_restart_extra_toolchange", "retract_speed", "wipe" }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 0fd5e75ad1..f3dc02e46f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1903,6 +1903,13 @@ void TabFilament::add_filament_overrides_page() "filament_retract_before_wipe" }) create_line_with_near_label_widget(optgroup, opt_key, extruder_idx); + + optgroup = page->new_optgroup(L("Retraction when tool is disabled")); + for (const std::string opt_key : { "filament_retract_length_toolchange", + "filament_retract_restart_extra_toolchange" + }) + create_line_with_near_label_widget(optgroup, opt_key, extruder_idx); + } void TabFilament::update_filament_overrides_page() @@ -1911,7 +1918,7 @@ void TabFilament::update_filament_overrides_page() return; Page* page = m_active_page; - const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Retraction"; }); + auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Retraction"; }); if (og_it == page->m_optgroups.end()) return; ConfigOptionsGroupShp optgroup = *og_it; @@ -1939,6 +1946,16 @@ void TabFilament::update_filament_overrides_page() bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; update_line_with_near_label_widget(optgroup, opt_key, extruder_idx, is_checked); } + + og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Retraction when tool is disabled"; }); + if (og_it == page->m_optgroups.end()) + return; + optgroup = *og_it; + + for (const std::string& opt_key : {"filament_retract_length_toolchange", "filament_retract_restart_extra_toolchange"}) + update_line_with_near_label_widget(optgroup, opt_key, extruder_idx); + + } void TabFilament::create_extruder_combobox() From 4421e584383313ebcae6b4140013ba6dbde1c1c5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 13 Jun 2023 08:36:42 +0200 Subject: [PATCH 104/120] Perform ramming for non-single-extruder-MM --- src/libslic3r/GCode/WipeTower.cpp | 15 +++++++++------ src/libslic3r/GCode/WipeTower.hpp | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 490df98068..6d04d88c48 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -518,6 +518,7 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default_region_config, const std::vector>& wiping_matrix, size_t initial_tool) : m_semm(config.single_extruder_multi_material.value), + m_ramming(true), // TESTING ONLY m_wipe_tower_pos(config.wipe_tower_x, config.wipe_tower_y), m_wipe_tower_width(float(config.wipe_tower_width)), m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)), @@ -557,8 +558,10 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default m_cooling_tube_length = float(config.cooling_tube_length); m_parking_pos_retraction = float(config.parking_pos_retraction); m_extra_loading_move = float(config.extra_loading_move); - m_set_extruder_trimpot = config.high_current_on_filament_swap; } + if (m_ramming) + m_set_extruder_trimpot = config.high_current_on_filament_swap; + // Calculate where the priming lines should be - very naive test not detecting parallelograms etc. const std::vector& bed_points = config.bed_shape.values; BoundingBoxf bb(bed_points); @@ -617,7 +620,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - if (m_semm) { + if (m_ramming) { std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; float speed = 0.f; stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; @@ -839,12 +842,12 @@ void WipeTower::toolchange_Unload( float remaining = xr - xl ; // keeps track of distance to the next turnaround float e_done = 0; // measures E move done from each segment - if (m_semm) + if (m_ramming) writer.travel(ramming_start_pos); // move to starting position else writer.set_position(ramming_start_pos); // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: - if (m_semm && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { + if (m_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { // this is y of the center of previous sparse infill border float sparse_beginning_y = 0.f; @@ -876,7 +879,7 @@ void WipeTower::toolchange_Unload( writer.disable_linear_advance(); // now the ramming itself: - while (m_semm && i < m_filpar[m_current_tool].ramming_speed.size()) + while (m_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) { const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move; @@ -953,7 +956,7 @@ void WipeTower::toolchange_Unload( // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width); - if (m_semm) + if (m_ramming) writer.travel(pos, 2400.f); else writer.set_position(pos); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 301cd3bec0..66a5b348eb 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -258,6 +258,7 @@ private: bool m_semm = true; // Are we using a single extruder multimaterial printer? + bool m_ramming = true; // Do we want to perform ramming? Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower From 4f66559b1e7a2d04291eb2f5dfb195cbc45a2105 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 16 Jun 2023 17:48:46 +0200 Subject: [PATCH 105/120] Polishing the multiextruder ramming --- src/libslic3r/GCode.cpp | 14 ++++++++++---- src/libslic3r/GCode/WipeTower.cpp | 30 ++++++++++++++++++++---------- src/libslic3r/GCode/WipeTower.hpp | 2 +- src/libslic3r/Preset.cpp | 1 + src/libslic3r/Print.cpp | 3 +++ src/libslic3r/PrintConfig.cpp | 22 ++++++++++++++++++++++ src/libslic3r/PrintConfig.hpp | 3 +++ src/slic3r/GUI/Tab.cpp | 13 +++++++++++++ 8 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1f306c83c6..444fe95e25 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -239,9 +239,12 @@ namespace Slic3r { const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id); const bool will_go_down = ! is_approx(z, current_z); - if (tcr.force_travel || ! needs_toolchange || (gcodegen.config().single_extruder_multi_material && ! tcr.priming)) { - // Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the - // toolchange will travel there anyway (if there is a toolchange). + const bool is_ramming = (gcodegen.config().single_extruder_multi_material && ! tcr.priming) + || (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool)); + const bool should_travel_to_tower = tcr.force_travel // wipe tower says so + || ! needs_toolchange // this is just finishing the tower with no toolchange + || is_ramming; + if (should_travel_to_tower) { // FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges, // then we could simplify the condition and make it more readable. gcode += gcodegen.retract(); @@ -251,6 +254,9 @@ namespace Slic3r { ExtrusionRole::Mixed, "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); + } else { + // When this is multiextruder printer without any ramming, we can just change + // the tool without travelling to the tower. } if (will_go_down) { @@ -262,7 +268,7 @@ namespace Slic3r { std::string toolchange_gcode_str; std::string deretraction_str; if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { - if (gcodegen.config().single_extruder_multi_material) + if (is_ramming) gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z if (gcodegen.config().wipe_tower) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 6d04d88c48..00ac858173 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -518,7 +518,6 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default_region_config, const std::vector>& wiping_matrix, size_t initial_tool) : m_semm(config.single_extruder_multi_material.value), - m_ramming(true), // TESTING ONLY m_wipe_tower_pos(config.wipe_tower_x, config.wipe_tower_y), m_wipe_tower_width(float(config.wipe_tower_width)), m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)), @@ -558,9 +557,8 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default m_cooling_tube_length = float(config.cooling_tube_length); m_parking_pos_retraction = float(config.parking_pos_retraction); m_extra_loading_move = float(config.extra_loading_move); - } - if (m_ramming) m_set_extruder_trimpot = config.high_current_on_filament_swap; + } // Calculate where the priming lines should be - very naive test not detecting parallelograms etc. const std::vector& bed_points = config.bed_shape.values; @@ -620,7 +618,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - if (m_ramming) { + if (m_semm) { std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; float speed = 0.f; stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; @@ -628,6 +626,14 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].ramming_step_multiplicator /= 100; while (stream >> speed) m_filpar[idx].ramming_speed.push_back(speed); + } else { + // We will use the same variables internally, but the correspondence to the configuration options will be different. + float vol = config.filament_multitool_ramming_volume.get_at(idx); + float flow = config.filament_multitool_ramming_flow.get_at(idx); + m_filpar[idx].multitool_ramming = config.filament_multitool_ramming.get_at(idx); + m_filpar[idx].ramming_line_width_multiplicator = 2.; + m_filpar[idx].ramming_step_multiplicator = 1.; + m_filpar[idx].ramming_speed.resize(4 *size_t(vol/flow + 0.5f), flow); } m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later @@ -842,12 +848,17 @@ void WipeTower::toolchange_Unload( float remaining = xr - xl ; // keeps track of distance to the next turnaround float e_done = 0; // measures E move done from each segment - if (m_ramming) + const bool do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming; + + if (do_ramming) { writer.travel(ramming_start_pos); // move to starting position + writer.disable_linear_advance(); + } else writer.set_position(ramming_start_pos); + // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: - if (m_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { + if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) { // this is y of the center of previous sparse infill border float sparse_beginning_y = 0.f; @@ -875,11 +886,10 @@ void WipeTower::toolchange_Unload( sum_of_depths += tch.required_depth; } } - - writer.disable_linear_advance(); + // now the ramming itself: - while (m_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) + while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) { const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move; @@ -956,7 +966,7 @@ void WipeTower::toolchange_Unload( // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width); - if (m_ramming) + if (do_ramming) writer.travel(pos, 2400.f); else writer.set_position(pos); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 66a5b348eb..666a3e0075 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -241,6 +241,7 @@ public: std::vector ramming_speed; float nozzle_diameter; float filament_area; + bool multitool_ramming; }; private: @@ -258,7 +259,6 @@ private: bool m_semm = true; // Are we using a single extruder multimaterial printer? - bool m_ramming = true; // Do we want to perform ramming? Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 376529a14e..d6ecb782e1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -468,6 +468,7 @@ static std::vector s_Preset_filament_options { "extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", + "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode", "enable_dynamic_fan_speeds", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 94b08b8857..9ed95dddf3 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -194,6 +194,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_cooling_initial_speed" || opt_key == "filament_cooling_final_speed" || opt_key == "filament_ramming_parameters" + || opt_key == "filament_multitool_ramming" + || opt_key == "filament_multitool_ramming_volume" + || opt_key == "filament_multitool_ramming_flow" || opt_key == "filament_max_volumetric_speed" || opt_key == "gcode_flavor" || opt_key == "high_current_on_filament_swap" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 83dd450c23..fdc3fb70ea 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1103,6 +1103,28 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloats { 0. }); + def = this->add("filament_multitool_ramming", coBools); + def->label = L("Enable ramming for multitool setups"); + def->tooltip = L("Perform ramming when using multitool printer (i.e. when the 'Single Extruder Multimaterial' in Printer Settings is unchecked)."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBools { false }); + + def = this->add("filament_multitool_ramming_volume", coFloats); + def->label = L("Multitool ramming volume"); + def->tooltip = L(""); + def->sidetext = L("mm³"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("filament_multitool_ramming_flow", coFloats); + def->label = L("Multitool ramming flow"); + def->tooltip = L(""); + def->sidetext = L("mm³/s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloats { 0. }); + def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); def->tooltip = L("Enter your filament diameter here. Good precision is required, so use a caliper " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 6439d21e9d..e79e520648 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -689,6 +689,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) ((ConfigOptionFloats, filament_cooling_final_speed)) ((ConfigOptionStrings, filament_ramming_parameters)) + ((ConfigOptionBools, filament_multitool_ramming)) + ((ConfigOptionFloats, filament_multitool_ramming_volume)) + ((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionBool, gcode_comments)) ((ConfigOptionEnum, gcode_flavor)) ((ConfigOptionBool, gcode_label_objects)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index f3dc02e46f..dc922f971b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2145,6 +2145,12 @@ void TabFilament::build() }); + optgroup = page->new_optgroup(L("Toolchange parameters with multi extruder MM printers")); + optgroup->append_single_option_line("filament_multitool_ramming"); + optgroup->append_single_option_line("filament_multitool_ramming_volume"); + optgroup->append_single_option_line("filament_multitool_ramming_flow"); + + add_filament_overrides_page(); @@ -2247,6 +2253,13 @@ void TabFilament::toggle_options() } } + if (m_active_page->title() == "Advanced") + { + bool multitool_ramming = m_config->opt_bool("filament_multitool_ramming", 0); + toggle_option("filament_multitool_ramming_volume", multitool_ramming); + toggle_option("filament_multitool_ramming_flow", multitool_ramming); + } + if (m_active_page->title() == "Filament Overrides") update_filament_overrides_page(); From b5093b0fe3f4ecd5f093af87e3ed00194c19866c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 31 Jul 2023 14:02:45 +0200 Subject: [PATCH 106/120] Wipe tower: do not apply extra spacing on the first layer purge lines --- src/libslic3r/GCode/WipeTower.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 00ac858173..b29381350b 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1053,8 +1053,9 @@ void WipeTower::toolchange_Wipe( // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. - float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height); - float dy = m_extra_spacing*m_perimeter_width; + float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) * (is_first_layer() ? m_extra_spacing : 1.f); + float dy = (is_first_layer() ? 1.f : m_extra_spacing) * m_perimeter_width; // Don't use the extra spacing for the first layer. + // All the calculations in all other places take the spacing into account for all the layers. const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : m_infill_speed * 60.f; float wipe_speed = 0.33f * target_speed; From 8c413cb4ae83d576220e00520a706e6795beb4da Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 3 Aug 2023 12:54:05 +0200 Subject: [PATCH 107/120] Wipe tower: multiextruder ramming is now not limited by the 0.25s granularity the semm ramming uses --- src/libslic3r/GCode/WipeTower.cpp | 23 ++++++++++++++++++----- src/libslic3r/GCode/WipeTower.hpp | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index b29381350b..74170d6348 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -626,6 +626,9 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].ramming_step_multiplicator /= 100; while (stream >> speed) m_filpar[idx].ramming_speed.push_back(speed); + // ramming_speed now contains speeds to be used for every 0.25s piece of the ramming line. + // This allows to have the ramming flow variable. The 0.25s value is how it is saved in config + // and the same time step has to be used when the ramming is performed. } else { // We will use the same variables internally, but the correspondence to the configuration options will be different. float vol = config.filament_multitool_ramming_volume.get_at(idx); @@ -633,7 +636,14 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].multitool_ramming = config.filament_multitool_ramming.get_at(idx); m_filpar[idx].ramming_line_width_multiplicator = 2.; m_filpar[idx].ramming_step_multiplicator = 1.; - m_filpar[idx].ramming_speed.resize(4 *size_t(vol/flow + 0.5f), flow); + + // Now the ramming speed vector. In this case it contains just one value (flow). + // The time is calculated and saved separately. This is here so that the MM ramming + // is not limited by the 0.25s granularity - it is not possible to create a SEMM-style + // ramming_speed vector that would respect both the volume and flow (because of + // rounding issues with small volumes and high flow). + m_filpar[idx].ramming_speed.push_back(flow); + m_filpar[idx].multitool_ramming_time = vol/flow; } m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later @@ -891,10 +901,13 @@ void WipeTower::toolchange_Unload( // now the ramming itself: while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) { - const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); - const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move; - const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround - const float actual_time = dist/x * 0.25f; + // The time step is different for SEMM ramming and the MM ramming. See comments in set_extruder() for details. + const float time_step = m_semm ? 0.25f : m_filpar[m_current_tool].multitool_ramming_time; + + const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * time_step, line_width, m_layer_height); + const float e = m_filpar[m_current_tool].ramming_speed[i] * time_step / filament_area(); // transform volume per sec to E move; + const float dist = std::min(x - e_done, remaining); // distance to travel for either the next time_step, or to the next turnaround + const float actual_time = dist/x * time_step; writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0.f, 0.f, e * (dist / x), dist / (actual_time / 60.f)); remaining -= dist; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 666a3e0075..38242125fa 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -242,6 +242,7 @@ public: float nozzle_diameter; float filament_area; bool multitool_ramming; + float multitool_ramming_time = 0.f; }; private: From a60be9e6d15681c5d62f196d62436a0375eb6d1d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 3 Aug 2023 13:44:34 +0200 Subject: [PATCH 108/120] Wipe tower: added comments on multitool ramming config options --- src/libslic3r/PrintConfig.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fdc3fb70ea..3001e2924b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1105,25 +1105,27 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_multitool_ramming", coBools); def->label = L("Enable ramming for multitool setups"); - def->tooltip = L("Perform ramming when using multitool printer (i.e. when the 'Single Extruder Multimaterial' in Printer Settings is unchecked)."); + def->tooltip = L("Perform ramming when using multitool printer (i.e. when the 'Single Extruder Multimaterial' in Printer Settings is unchecked). " + "When checked, a small amount of filament is rapidly extruded on the wipe tower just before the toolchange. " + "This option is only used when the wipe tower is enabled."); def->mode = comExpert; def->set_default_value(new ConfigOptionBools { false }); def = this->add("filament_multitool_ramming_volume", coFloats); def->label = L("Multitool ramming volume"); - def->tooltip = L(""); + def->tooltip = L("The volume to be rammed before the toolchange."); def->sidetext = L("mm³"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloats { 0. }); + def->set_default_value(new ConfigOptionFloats { 10. }); def = this->add("filament_multitool_ramming_flow", coFloats); def->label = L("Multitool ramming flow"); - def->tooltip = L(""); + def->tooltip = L("Flow used for ramming the filament before the toolchange."); def->sidetext = L("mm³/s"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloats { 0. }); + def->set_default_value(new ConfigOptionFloats { 10. }); def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); From 86c9ea0eb04d4d25ddd2d9c73a8cdbd9fdfe0116 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:20:32 +0200 Subject: [PATCH 109/120] MK4IS thumbnail --- .../PrusaResearch/MK4IS_thumbnail_v2.png | Bin 0 -> 66178 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/profiles/PrusaResearch/MK4IS_thumbnail_v2.png diff --git a/resources/profiles/PrusaResearch/MK4IS_thumbnail_v2.png b/resources/profiles/PrusaResearch/MK4IS_thumbnail_v2.png new file mode 100644 index 0000000000000000000000000000000000000000..8ef29303823628bc024b02239afdac6672d7cbb8 GIT binary patch literal 66178 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 xKBf?YR Date: Mon, 21 Aug 2023 15:21:35 +0200 Subject: [PATCH 110/120] Sync with PrusaSlicer-settings. --- resources/profiles/PrusaResearch.idx | 7 + resources/profiles/PrusaResearch.ini | 1946 ++++++++++++++++++++++---- 2 files changed, 1698 insertions(+), 255 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index c20e8e8e16..09bd30991a 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,11 @@ min_slic3r_version = 2.6.0-beta2 +1.9.8 FW version notification (MK2.5/3 family). Minor update of MK4IS profiles. Updated MK4IS thumbnail. +1.9.7 MK4 Input Shaper RC firmware notification. +1.9.6 Added filament profile for Prusament PETG V0. +1.9.5 Added new profiles for MK4 Input Shaper (Alpha). +1.9.4 Added profiles for MMU3. Updated PA values for MK4IS. Increased minimal purge on wipe tower for XL Multi-Tool. +1.9.3 Added material profiles for Prusament Resin Flex80 White. +1.9.2 Added profiles for Prusa XL Multi-Tool. 1.9.1 Added new Prusament Resin material profiles. 1.9.0 Bumped up configuration version. 1.9.0-beta3 Updated start g-code for MK4 (modified purge line and nozzle cleaning). diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index c068d9e2f8..93d05a216d 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.9.1 +config_version = 1.9.8 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -24,14 +24,14 @@ bed_texture = mk4.svg default_materials = Generic PLA @PG; Generic ABS @MK4; Generic PETG @PG; Prusament PLA @PG; Prusament PETG @PG; Prusament ASA @MK4; Prusament PC Blend @MK4; Prusament PC Blend Carbon Fiber @MK4; Prusament PVB @PG; Prusament PA11 Carbon Fiber @PG [printer_model:MK4IS] -name = Original Prusa MK4 Input Shaper (Alpha) -variants = 0.4 +name = Original Prusa MK4 Input Shaper +variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8 technology = FFF family = MK4 bed_model = mk4_bed.stl -bed_texture = mk4is.svg -thumbnail = MK4IS_thumbnail.png -default_materials = Prusament PLA @MK4IS; Prusament PLA Blend @MK4IS; Prusament PETG @MK4IS; Generic PLA @MK4IS; Prusa PLA @MK4IS; Prusa PETG @MK4IS; Generic ASA @MK4; Generic PETG @MK4IS; Prusa PLA @MK4IS; Generic PLA Silk @MK4IS +bed_texture = mk4.svg +thumbnail = MK4IS_thumbnail_v2.png +default_materials = Prusament PLA @PGIS; Prusament PLA Blend @PGIS; Prusament PETG @PGIS; Generic PLA @PGIS; Prusa PLA @PGIS; Prusa PETG @PGIS; Generic ASA @MK4; Generic PETG @PGIS; Prusa PLA @PGIS; Generic PLA Silk @PGIS [printer_model:MINI] name = Original Prusa MINI && MINI+ @@ -60,6 +60,15 @@ bed_model = mk3_bed.stl bed_texture = mk3.svg default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA; Prusament PETG; Prusament ASA; Prusament PC Blend; Prusament PC Blend Carbon Fiber; Prusament PVB; Prusament PA11 Carbon Fiber +[printer_model:MK3SMMU3] +name = Original Prusa i3 MK3S && MK3S+ MMU3 +variants = 0.4; 0.25; 0.6; 0.8 +technology = FFF +family = MK3 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU; Prusament PETG @MMU; Prusament ASA @MMU; Verbatim BVOH @MMU; Prusament PC Blend @MMU; Prusament PC Blend Carbon Fiber @MMU; Prusament PVB @MMU + [printer_model:MK3SMMU2S] name = Original Prusa i3 MK3S && MK3S+ MMU2S variants = 0.4; 0.25; 0.6; 0.8 @@ -67,7 +76,7 @@ technology = FFF family = MK3 bed_model = mk3_bed.stl bed_texture = mk3.svg -default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU2; Prusament PETG @MMU2; Prusament ASA @MMU2; Verbatim BVOH @MMU2; Prusament PC Blend @MMU2; Prusament PC Blend Carbon Fiber @MMU2; Prusament PVB @MMU2 +default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU; Prusament PETG @MMU; Prusament ASA @MMU; Verbatim BVOH @MMU; Prusament PC Blend @MMU; Prusament PC Blend Carbon Fiber @MMU; Prusament PVB @MMU [printer_model:MK3MMU2] name = Original Prusa i3 MK3 MMU2 @@ -76,17 +85,35 @@ technology = FFF family = MK3 bed_model = mk3_bed.stl bed_texture = mk3.svg -default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU2; Prusament PETG @MMU2; Prusament ASA @MMU2; Verbatim BVOH @MMU2; Prusament PC Blend @MMU2; Prusament PC Blend Carbon Fiber @MMU2; Prusament PVB @MMU2 +default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU; Prusament PETG @MMU; Prusament ASA @MMU; Verbatim BVOH @MMU; Prusament PC Blend @MMU; Prusament PC Blend Carbon Fiber @MMU; Prusament PVB @MMU [printer_model:XL] name = Original Prusa XL variants = 0.6; 0.25; 0.3; 0.4; 0.5; 0.8 technology = FFF -family = XL +family = XL Single-Tool bed_model = xl_bed.stl bed_texture = xl.svg default_materials = Generic PLA @PG 0.6; Generic ABS @PG 0.6; Generic PETG @PG 0.6; Prusament PLA @PG 0.6; Prusament PETG @PG 0.6; Prusament ASA @PG 0.6; Prusament PC Blend @PG 0.6; Prusament PC Blend Carbon Fiber @PG 0.6; Prusament PVB @PG 0.6; Prusament PA11 Carbon Fiber @PG 0.6 +[printer_model:XL2] +name = Original Prusa XL - 2T +variants = 0.6; 0.25; 0.3; 0.4; 0.5; 0.8 +technology = FFF +family = XL Multi-Tool +bed_model = xl_bed.stl +bed_texture = xl.svg +default_materials = Generic PLA @PG 0.6; Generic ABS @PG 0.6; Generic PETG @PG 0.6; Prusament PLA @PG 0.6; Prusament PETG @PG 0.6; Prusament ASA @PG 0.6; Prusament PC Blend @PG 0.6; Prusament PC Blend Carbon Fiber @PG 0.6; Prusament PVB @PG 0.6; Prusament PA11 Carbon Fiber @PG 0.6; Verbatim BVOH @PG 0.6 + +[printer_model:XL5] +name = Original Prusa XL - 5T +variants = 0.6; 0.25; 0.3; 0.4; 0.5; 0.8 +technology = FFF +family = XL Multi-Tool +bed_model = xl_bed.stl +bed_texture = xl.svg +default_materials = Generic PLA @PG 0.6; Generic ABS @PG 0.6; Generic PETG @PG 0.6; Prusament PLA @PG 0.6; Prusament PETG @PG 0.6; Prusament ASA @PG 0.6; Prusament PC Blend @PG 0.6; Prusament PC Blend Carbon Fiber @PG 0.6; Prusament PVB @PG 0.6; Prusament PA11 Carbon Fiber @PG 0.6; Verbatim BVOH @PG 0.6 + [printer_model:MK2.5S] name = Original Prusa i3 MK2.5S variants = 0.4; 0.25; 0.6; 0.8 @@ -112,7 +139,7 @@ technology = FFF family = MK2.5 bed_model = mk3_bed.stl bed_texture = mk3.svg -default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU2; Prusament PETG @MMU2; Prusament ASA @MMU2; Verbatim BVOH @MMU2; Prusament PC Blend @MMU2; Prusament PC Blend Carbon Fiber @MMU2; Prusament PVB @MMU2 +default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU; Prusament PETG @MMU; Prusament ASA @MMU; Verbatim BVOH @MMU; Prusament PC Blend @MMU; Prusament PC Blend Carbon Fiber @MMU; Prusament PVB @MMU [printer_model:MK2.5MMU2] name = Original Prusa i3 MK2.5 MMU2 @@ -121,7 +148,7 @@ technology = FFF family = MK2.5 bed_model = mk3_bed.stl bed_texture = mk3.svg -default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU2; Prusament PETG @MMU2; Prusament ASA @MMU2; Verbatim BVOH @MMU2; Prusament PC Blend @MMU2; Prusament PC Blend Carbon Fiber @MMU2; Prusament PVB @MMU2 +default_materials = Generic PLA; Generic ABS; Generic PETG; Prusament PLA @MMU; Prusament PETG @MMU; Prusament ASA @MMU; Verbatim BVOH @MMU; Prusament PC Blend @MMU; Prusament PC Blend Carbon Fiber @MMU; Prusament PVB @MMU [printer_model:MK2S] name = Original Prusa i3 MK2S @@ -211,7 +238,7 @@ notes = overhangs = 1 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{nozzle_diameter[initial_tool]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -272,6 +299,10 @@ gcode_label_objects = 1 infill_anchor = 2.5 infill_anchor_max = 12 enable_dynamic_overhang_speeds = 1 +support_tree_angle_slow = 30 +support_tree_branch_diameter_angle = 3 +support_tree_tip_diameter = 0.6 +support_tree_top_rate = 30% [print:*MK3*] fill_pattern = grid @@ -301,7 +332,14 @@ first_layer_speed = 25 support_material_threshold = 45 raft_first_layer_density = 80% ## gcode_substitutions = "; stop printing object\\s(.*)\\s+id:(\\d+)\\s+.*";"$0\\nM486 S-1\\n";r;;"; printing object\\s(.*)\\s+id:(\\d+)\\s+.*";"$0\\nM486 S$2\\nM486 N$1\\n";r; -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{nozzle_diameter[initial_tool]}n_{layer_height}mm_{printing_filament_types}_XL_{print_time}.gcode +wipe_tower_cone_angle = 25 +wipe_tower = 1 +wipe_tower_bridging = 8 +wipe_tower_extra_spacing = 100 +wipe_tower_brim_width = 3 +ooze_prevention = 1 +standby_temperature_delta = -110 [print:*MK4*] inherits = *common* @@ -325,7 +363,6 @@ first_layer_speed = 20 support_material_threshold = 45 raft_first_layer_density = 80% ## gcode_substitutions = "; stop printing object\\s(.*)\\s+id:(\\d+)\\s+.*";"$0\\nM486 S-1\\n";r;;"; printing object\\s(.*)\\s+id:(\\d+)\\s+.*";"$0\\nM486 S$2\\nM486 N$1\\n";r; -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode [print:*MK306*] inherits = *MK3* @@ -362,7 +399,6 @@ support_material_interface_spacing = 0.15 support_material_spacing = 1 support_material_xy_spacing = 150% support_material_contact_distance = 0.1 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode thick_bridges = 0 bridge_flow_ratio = 1 bridge_speed = 20 @@ -436,7 +472,6 @@ support_material_xy_spacing = 80% support_material_interface_spacing = 0.2 support_material_spacing = 2.2 raft_first_layer_expansion = 2 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode infill_anchor = 2 infill_anchor_max = 15 thick_bridges = 0 @@ -454,7 +489,6 @@ support_material_extrusion_width = 0.55 support_material_contact_distance = 0.15 support_material_xy_spacing = 80% support_material_interface_spacing = 0.3 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode infill_anchor_max = 15 top_solid_min_thickness = 0.9 bottom_solid_min_thickness = 0.6 @@ -516,7 +550,6 @@ support_material_interface_speed = 100% support_material_spacing = 2 support_material_xy_spacing = 80% support_material_threshold = 50 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode fill_pattern = gyroid fill_density = 15% infill_anchor_max = 20 @@ -577,6 +610,17 @@ wipe_tower_bridging = 6 support_material_interface_speed = 80% support_material_bottom_interface_layers = -1 thick_bridges = 1 +support_material_interface_pattern = auto + +[print:*soluble_support_XL*] +inherits = *soluble_support* +support_material_extruder = 2 +support_material_interface_extruder = 2 +support_material_threshold = 50 +support_material_with_sheath = 0 +support_material_bottom_interface_layers = 2 +raft_first_layer_density = 90% +support_material_interface_pattern = auto [print:*0.05mm*] inherits = *common* @@ -1790,7 +1834,7 @@ infill_acceleration = 800 bridge_acceleration = 300 first_layer_acceleration = 600 default_acceleration = 800 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.25 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.25 [print:0.07mm ULTRADETAIL @XL 0.25] inherits = *0.07mm*; *XL*; *0.25nozzleXL* @@ -1815,7 +1859,7 @@ first_layer_acceleration = 600 default_acceleration = 800 max_print_speed = 200 gcode_resolution = 0.006 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.25 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.25 [print:0.10mm QUALITY @XL 0.25] inherits = *0.10mm*; *XL*; *0.25nozzleXL* @@ -1840,7 +1884,7 @@ first_layer_acceleration = 600 default_acceleration = 1000 max_print_speed = 200 gcode_resolution = 0.008 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.25 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.25 [print:0.12mm QUALITY @XL 0.25] inherits = *0.12mm*; *XL*; *0.25nozzleXL* @@ -1870,7 +1914,7 @@ external_perimeter_extrusion_width = 0.27 infill_extrusion_width = 0.27 solid_infill_extrusion_width = 0.27 top_infill_extrusion_width = 0.25 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.25 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.25 [print:0.15mm SPEED @XL 0.25] inherits = *0.15mm*; *XL*; *0.25nozzleXL* @@ -1900,7 +1944,7 @@ external_perimeter_extrusion_width = 0.27 infill_extrusion_width = 0.27 solid_infill_extrusion_width = 0.27 top_infill_extrusion_width = 0.25 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.25 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.25 ## XL - 0.3mm nozzle @@ -1928,7 +1972,7 @@ infill_acceleration = 800 bridge_acceleration = 500 first_layer_acceleration = 600 default_acceleration = 800 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.3 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.3 [print:0.08mm DETAIL @XL 0.3] inherits = *0.07mm*; *XL*; *0.3nozzle* @@ -1956,7 +2000,7 @@ default_acceleration = 1000 max_print_speed = 200 perimeters = 3 gcode_resolution = 0.006 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.3 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.3 [print:0.12mm QUALITY @XL 0.3] inherits = *0.12mm*; *XL*; *0.3nozzle* @@ -1982,7 +2026,7 @@ first_layer_acceleration = 600 default_acceleration = 1000 max_print_speed = 200 gcode_resolution = 0.008 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.3 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.3 [print:0.16mm SPEED @XL 0.3] inherits = *0.16mm*; *XL*; *0.3nozzle* @@ -2008,7 +2052,7 @@ first_layer_acceleration = 600 default_acceleration = 1250 max_print_speed = 200 gcode_resolution = 0.008 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.3 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.3 [print:0.20mm DRAFT @XL 0.3] inherits = *0.20mm*; *XL*; *0.3nozzle* @@ -2039,7 +2083,7 @@ external_perimeter_extrusion_width = 0.35 infill_extrusion_width = 0.35 solid_infill_extrusion_width = 0.35 top_infill_extrusion_width = 0.3 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.3 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.3 ## XL - 0.4mm nozzle @@ -2076,7 +2120,7 @@ infill_acceleration = 800 bridge_acceleration = 300 first_layer_acceleration = 600 default_acceleration = 800 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.4 [print:0.10mm DETAIL @XL 0.4] inherits = *0.10mm*; *XL* @@ -2112,7 +2156,7 @@ solid_infill_extrusion_width = 0.4 top_infill_extrusion_width = 0.4 perimeters = 3 gcode_resolution = 0.006 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.4 [print:0.15mm QUALITY @XL 0.4] inherits = *0.15mm*; *XL* @@ -2144,7 +2188,7 @@ first_layer_extrusion_width = 0.5 support_material_extrusion_width = 0.37 top_infill_extrusion_width = 0.4 gcode_resolution = 0.008 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.4 [print:0.20mm QUALITY @XL 0.4] inherits = *0.20mm*; *XL* @@ -2176,7 +2220,24 @@ first_layer_extrusion_width = 0.5 gcode_resolution = 0.008 support_material_extrusion_width = 0.37 top_infill_extrusion_width = 0.4 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.4 + +[print:0.20mm SOLUBLE INTERFACE @XL 0.4] +inherits = 0.20mm QUALITY @XL 0.4; *soluble_support_XL* +support_material_extruder = 0 +perimeter_speed = 50 +external_perimeter_speed = 35 +small_perimeter_speed = 30 +solid_infill_speed = 55 +solid_infill_acceleration = 1200 +support_material_interface_layers = 3 +support_material_extrusion_width = 0.4 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 + +[print:0.20mm SOLUBLE FULL @XL 0.4] +inherits = 0.20mm SOLUBLE INTERFACE @XL 0.4 +support_material_extruder = 2 +support_material_with_sheath = 1 [print:0.20mm SPEED @XL 0.4] inherits = *0.20mm*; *XL* @@ -2207,7 +2268,7 @@ max_print_speed = 200 first_layer_extrusion_width = 0.5 support_material_extrusion_width = 0.37 top_infill_extrusion_width = 0.42 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.4 [print:0.30mm DRAFT @XL 0.4] inherits = *0.30mm*; *XL* @@ -2244,7 +2305,7 @@ perimeter_extrusion_width = 0.5 solid_infill_extrusion_width = 0.5 top_infill_extrusion_width = 0.45 support_material_extrusion_width = 0.38 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.4 ## XL - 0.5mm nozzle @@ -2277,7 +2338,7 @@ solid_infill_extrusion_width = 0.5 top_infill_extrusion_width = 0.45 perimeters = 2 gcode_resolution = 0.008 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.5 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.5 [print:0.15mm QUALITY @XL 0.5] inherits = *0.15mm*; *XL*; *0.5nozzle* @@ -2301,7 +2362,7 @@ first_layer_acceleration = 600 default_acceleration = 1250 max_print_speed = 200 gcode_resolution = 0.008 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.5 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.5 [print:0.20mm QUALITY @XL 0.5] inherits = 0.15mm QUALITY @XL 0.5; *0.20mm*; *XL*; *0.5nozzle* @@ -2317,6 +2378,23 @@ infill_acceleration = 2500 default_acceleration = 1250 max_print_speed = 200 +[print:0.20mm SOLUBLE INTERFACE @XL 0.5] +inherits = 0.20mm QUALITY @XL 0.5; *soluble_support_XL* +support_material_extruder = 0 +perimeter_speed = 50 +external_perimeter_speed = 35 +small_perimeter_speed = 30 +solid_infill_speed = 55 +solid_infill_acceleration = 1200 +support_material_interface_layers = 3 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.5 and num_extruders>1 + +[print:0.20mm SOLUBLE FULL @XL 0.5] +inherits = 0.20mm SOLUBLE INTERFACE @XL 0.5 +support_material_extruder = 2 +support_material_with_sheath = 1 +support_material_extrusion_width = 0.47 + [print:0.25mm SPEED @XL 0.5] inherits = *0.25mm*; *XL*; *0.5nozzle* bottom_solid_layers = 3 @@ -2341,7 +2419,7 @@ bridge_acceleration = 800 first_layer_acceleration = 600 default_acceleration = 1250 max_print_speed = 200 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.5 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.5 [print:0.32mm DRAFT @XL 0.5] inherits = *0.32mm*; *XL*; *0.5nozzle* @@ -2368,7 +2446,7 @@ bridge_acceleration = 1000 first_layer_acceleration = 600 default_acceleration = 1250 max_print_speed = 200 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.5 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.5 ## XL - 0.6mm nozzle @@ -2405,7 +2483,7 @@ first_layer_acceleration = 600 default_acceleration = 1250 bridge_flow_ratio = 1 max_print_speed = 200 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [print:0.20mm DETAIL @XL 0.6] inherits = *0.20mm*; *XL*; *0.6nozzleXL* @@ -2439,7 +2517,7 @@ first_layer_acceleration = 600 default_acceleration = 1250 bridge_flow_ratio = 1 max_print_speed = 200 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [print:0.25mm QUALITY @XL 0.6] inherits = *0.25mm*; *XL*; *0.6nozzleXL* @@ -2470,7 +2548,24 @@ bridge_flow_ratio = 1 top_solid_layers = 5 bottom_solid_layers = 4 max_print_speed = 200 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 + +[print:0.25mm SOLUBLE INTERFACE @XL 0.6] +inherits = 0.25mm QUALITY @XL 0.6; *soluble_support_XL* +support_material_extruder = 0 +perimeter_speed = 50 +external_perimeter_speed = 35 +small_perimeter_speed = 30 +solid_infill_speed = 55 +solid_infill_acceleration = 1200 +support_material_interface_layers = 3 +support_material_extrusion_width = 0.5 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 + +[print:0.25mm SOLUBLE FULL @XL 0.6] +inherits = 0.25mm SOLUBLE INTERFACE @XL 0.6 +support_material_extruder = 2 +support_material_with_sheath = 1 [print:0.25mm SPEED @XL 0.6] inherits = 0.25mm QUALITY @XL 0.6 @@ -2528,7 +2623,7 @@ default_acceleration = 1250 bridge_flow_ratio = 0.95 max_print_speed = 200 bottom_solid_layers = 3 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [print:0.32mm SPEED @XL 0.6] inherits = *0.32mm*; *XL*; *0.6nozzleXL* @@ -2563,7 +2658,7 @@ default_acceleration = 1250 bridge_flow_ratio = 0.95 max_print_speed = 200 bottom_solid_layers = 3 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [print:0.40mm DRAFT @XL 0.6] inherits = *0.40mm*; *XL*; *0.6nozzleXL* @@ -2596,7 +2691,7 @@ infill_acceleration = 3000 default_acceleration = 1500 bridge_flow_ratio = 0.95 dynamic_overhang_speeds = 30,20,15,15 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 ## XL - 0.8mm nozzle @@ -2619,7 +2714,7 @@ perimeter_acceleration = 1000 bridge_acceleration = 1000 top_solid_infill_acceleration = 800 solid_infill_acceleration = 1500 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 [print:0.40mm QUALITY @XL 0.8] inherits = *0.40mm*; *XL*; *0.8nozzleXL* @@ -2640,7 +2735,7 @@ perimeter_acceleration = 1000 bridge_acceleration = 1000 top_solid_infill_acceleration = 800 solid_infill_acceleration = 2000 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 [print:0.55mm DRAFT @XL 0.8] inherits = *XL*; *0.8nozzleXL* @@ -2666,7 +2761,7 @@ perimeter_acceleration = 1000 bridge_acceleration = 1000 top_solid_infill_acceleration = 800 solid_infill_acceleration = 2000 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 ## MK4 ## @@ -3094,6 +3189,7 @@ support_material_interface_speed = 80% support_material_xy_spacing = 80% gap_fill_speed = 40 bridge_speed = 25 +overhang_speed_3 = 80% external_perimeter_acceleration = 800 perimeter_acceleration = 1000 top_solid_infill_acceleration = 800 @@ -3156,6 +3252,7 @@ support_material_interface_speed = 70% support_material_xy_spacing = 80% gap_fill_speed = 45 bridge_speed = 25 +overhang_speed_3 = 80% external_perimeter_acceleration = 800 perimeter_acceleration = 1000 top_solid_infill_acceleration = 800 @@ -3576,9 +3673,9 @@ solid_infill_acceleration = 1500 infill_acceleration = 2000 compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.8 -## MK4 - Input Shaper 0.4mm nozzle +## MK4 - Input Shaper -[print:0.20mm Input Shaper @MK4IS 0.4] +[print:*MK4IS_common*] bottom_solid_layers = 4 bottom_solid_min_thickness = 0 bridge_acceleration = 1500 @@ -3609,7 +3706,7 @@ first_layer_speed_over_raft = 30 gap_fill_enabled = 1 gap_fill_speed = 120 gcode_comments = 0 -gcode_label_objects = 0 +gcode_label_objects = 1 gcode_resolution = 0.008 infill_acceleration = 4000 infill_anchor = 2 @@ -3637,7 +3734,7 @@ output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_heig overhang_speed_0 = 15 overhang_speed_1 = 25 overhang_speed_2 = 30 -overhang_speed_3 = 120 +overhang_speed_3 = 80% overhangs = 1 perimeter_acceleration = 4000 perimeter_extruder = 1 @@ -3672,14 +3769,14 @@ support_material_extruder = 0 support_material_extrusion_width = 0.36 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 -support_material_interface_layers = 0 +support_material_interface_layers = 5 support_material_interface_pattern = auto support_material_interface_spacing = 0.2 support_material_interface_speed = 50% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 120 -support_material_style = organic +support_material_style = snug support_material_synchronize_layers = 0 support_material_threshold = 40 support_material_with_sheath = 0 @@ -3705,6 +3802,782 @@ thick_bridges = 0 thin_walls = 0 compatible_printers_condition = printer_model=="MK4IS" and nozzle_diameter[0]==0.4 +[print:*MK4IS_common025*] +inherits = *MK4IS_common* +extrusion_width = 0.27 +first_layer_extrusion_width = 0.32 +perimeter_extrusion_width = 0.27 +external_perimeter_extrusion_width = 0.27 +infill_extrusion_width = 0.27 +solid_infill_extrusion_width = 0.27 +top_infill_extrusion_width = 0.27 +support_material_extrusion_width = 0.25 +first_layer_speed = 20 +gcode_resolution = 0.008 +elefant_foot_compensation = 0 +brim_separation = 0 +bottom_solid_layers = 6 +top_solid_layers = 9 +support_material_speed = 70 +support_material_interface_speed = 75% +infill_overlap = 10% +default_acceleration = 2000 +travel_acceleration = 3000 +infill_acceleration = 3000 +solid_infill_acceleration = 2500 +bridge_speed = 30 +bridge_flow_ratio = 1 +infill_speed = 100 +solid_infill_speed = 160 +top_solid_infill_speed = 60 +perimeters = 3 +infill_anchor = 1 +support_material_contact_distance = 0.15 +support_material_spacing = 1 +support_material_xy_spacing = 150% +raft_contact_distance = 0.15 +raft_first_layer_density = 95% +gap_fill_speed = 50 +single_extruder_multi_material_priming = 0 +wipe_tower = 1 +compatible_printers_condition = printer_model=="MK4IS" and nozzle_diameter[0]==0.25 + +[print:*MK4IS_common03*] +inherits = *MK4IS_common* +extrusion_width = 0.34 +first_layer_extrusion_width = 0.4 +perimeter_extrusion_width = 0.34 +external_perimeter_extrusion_width = 0.34 +infill_extrusion_width = 0.34 +solid_infill_extrusion_width = 0.34 +top_infill_extrusion_width = 0.3 +support_material_extrusion_width = 0.3 +first_layer_speed = 30 +gcode_resolution = 0.008 +elefant_foot_compensation = 0 +brim_separation = 0 +bottom_solid_layers = 5 +top_solid_layers = 6 +support_material_speed = 70 +support_material_interface_speed = 75% +infill_overlap = 10% +default_acceleration = 2000 +infill_acceleration = 4000 +bridge_speed = 30 +bridge_flow_ratio = 1 +perimeters = 3 +infill_anchor = 1 +support_material_contact_distance = 0.15 +support_material_spacing = 1 +support_material_xy_spacing = 100% +raft_contact_distance = 0.15 +raft_first_layer_density = 90% +gap_fill_speed = 50 +top_solid_min_thickness = 0.7 +bottom_solid_min_thickness = 0.5 +compatible_printers_condition = printer_model=="MK4IS" and nozzle_diameter[0]==0.3 + +[print:*MK4IS_common05*] +inherits = *MK4IS_common* +extrusion_width = 0.55 +first_layer_extrusion_width = 0.55 +perimeter_extrusion_width = 0.55 +external_perimeter_extrusion_width = 0.55 +infill_extrusion_width = 0.55 +solid_infill_extrusion_width = 0.55 +top_infill_extrusion_width = 0.5 +support_material_extrusion_width = 0.4 +gcode_resolution = 0.008 +bottom_solid_layers = 3 +top_solid_layers = 4 +support_material_contact_distance = 0.25 +support_material_xy_spacing = 80% +support_material_interface_spacing = 0.22 +support_material_interface_speed = 75% +raft_contact_distance = 0.25 +gap_fill_speed = 70 +top_solid_min_thickness = 0.7 +bottom_solid_min_thickness = 0.5 +infill_acceleration = 4000 +default_acceleration = 2500 +infill_anchor = 2 +infill_anchor_max = 15 +compatible_printers_condition = printer_model=="MK4IS" and nozzle_diameter[0]==0.5 + +[print:*MK4IS_common06*] +inherits = *MK4IS_common* +extrusion_width = 0.68 +first_layer_extrusion_width = 0.68 +perimeter_extrusion_width = 0.68 +external_perimeter_extrusion_width = 0.68 +infill_extrusion_width = 0.68 +solid_infill_extrusion_width = 0.68 +top_infill_extrusion_width = 0.55 +support_material_extrusion_width = 0.5 +gcode_resolution = 0.0125 +bottom_solid_layers = 3 +top_solid_layers = 4 +support_material_contact_distance = 0.15 +support_material_xy_spacing = 80% +support_material_interface_spacing = 0.25 +support_material_interface_speed = 75% +raft_contact_distance = 0.25 +gap_fill_speed = 70 +compatible_printers_condition = printer_model=="MK4IS" and nozzle_diameter[0]==0.6 +overhang_speed_0 = 15 +overhang_speed_1 = 20 +overhang_speed_2 = 25 +overhang_speed_3 = 50 +top_solid_min_thickness = 0.9 +bottom_solid_min_thickness = 0.6 +infill_anchor = 2.5 +infill_anchor_max = 20 + +[print:*MK4IS_common08*] +inherits = *MK4IS_common* +fill_pattern = rectilinear +thick_bridges = 1 +extrusion_width = 0.9 +first_layer_extrusion_width = 1 +perimeter_extrusion_width = 0.9 +external_perimeter_extrusion_width = 0.9 +infill_extrusion_width = 0.9 +solid_infill_extrusion_width = 0.9 +top_infill_extrusion_width = 0.8 +support_material_extrusion_width = 0.65 +gcode_resolution = 0.0125 +bottom_solid_layers = 3 +top_solid_layers = 4 +support_material_contact_distance = 0.25 +support_material_xy_spacing = 80% +support_material_interface_spacing = 0.35 +support_material_interface_speed = 85% +raft_contact_distance = 0.2 +gap_fill_speed = 40 +top_solid_min_thickness = 1.2 +bottom_solid_min_thickness = 0.8 +compatible_printers_condition = printer_model=="MK4IS" and nozzle_diameter[0]==0.8 +seam_position = nearest +infill_anchor = 2.5 +infill_anchor_max = 20 +overhang_speed_1 = 20 +overhang_speed_2 = 25 +overhang_speed_3 = 80% + +# 0.4mm nozzle IS + +[print:0.20mm SPEED @MK4IS 0.4] +inherits = *MK4IS_common* +renamed_from = "0.20mm Input Shaper @MK4IS 0.4" + +[print:0.20mm STRUCTURAL @MK4IS 0.4] +inherits = *MK4IS_common* +perimeter_speed = 80 +external_perimeter_speed = 45 +small_perimeter_speed = 45 +gap_fill_speed = 60 +top_solid_infill_speed = 80 +support_material_speed = 120 +support_material_interface_speed = 50 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2500 +solid_infill_acceleration = 3000 +default_acceleration = 2500 +overhang_speed_3 = 90% + +[print:0.15mm SPEED @MK4IS 0.4] +inherits = *MK4IS_common* +layer_height = 0.15 +top_solid_layers = 6 +bottom_solid_layers = 5 +support_material_contact_distance = 0.17 +raft_contact_distance = 0.15 +perimeter_speed = 170 +external_perimeter_speed = 170 +small_perimeter_speed = 140 +gap_fill_speed = 120 +support_material_speed = 120 +support_material_interface_speed = 50 +external_perimeter_acceleration = 2500 +perimeter_acceleration = 3500 +solid_infill_acceleration = 3500 +default_acceleration = 3000 +overhang_speed_3 = 80% + +[print:0.15mm STRUCTURAL @MK4IS 0.4] +inherits = 0.20mm STRUCTURAL @MK4IS 0.4 +layer_height = 0.15 +top_solid_layers = 6 +bottom_solid_layers = 5 +support_material_contact_distance = 0.17 +raft_contact_distance = 0.15 + +[print:0.10mm FAST DETAIL @MK4IS 0.4] +inherits = 0.15mm SPEED @MK4IS 0.4 +layer_height = 0.1 +top_solid_layers = 8 +bottom_solid_layers = 7 +support_material_contact_distance = 0.17 +raft_contact_distance = 0.15 + +# 0.25mm nozzle IS + +; [print:0.05mm @MK4IS 0.25] +; inherits = *MK4IS_common025* +; layer_height = 0.05 +; bottom_solid_layers = 10 +; top_solid_layers = 13 +; support_material_contact_distance = 0.1 +; raft_contact_distance = 0.1 +; perimeter_speed = 60 +; small_perimeter_speed = 40 +; external_perimeter_speed = 40 +; solid_infill_speed = 100 +; top_solid_infill_speed = 60 +; bridge_speed = 25 +; gap_fill_speed = 40 +; support_material_speed = 60 +; external_perimeter_acceleration = 800 +; perimeter_acceleration = 1200 +; top_solid_infill_acceleration = 1000 +; solid_infill_acceleration = 2000 +; infill_acceleration = 2500 +; bridge_acceleration = 1000 +; default_acceleration = 1500 +; perimeter_extrusion_width = 0.25 +; external_perimeter_extrusion_width = 0.25 +; infill_extrusion_width = 0.25 +; solid_infill_extrusion_width = 0.25 + +[print:0.07mm DETAIL @MK4IS 0.25] +inherits = *MK4IS_common025* +layer_height = 0.07 +bottom_solid_layers = 9 +top_solid_layers = 11 +support_material_contact_distance = 0.1 +raft_contact_distance = 0.1 +perimeter_speed = 60 +small_perimeter_speed = 40 +external_perimeter_speed = 40 +top_solid_infill_speed = 60 +gap_fill_speed = 40 +support_material_speed = 60 +external_perimeter_acceleration = 800 +perimeter_acceleration = 1200 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 2000 +infill_acceleration = 2500 +bridge_acceleration = 1000 +default_acceleration = 1500 +perimeter_extrusion_width = 0.25 +external_perimeter_extrusion_width = 0.25 +infill_extrusion_width = 0.25 +solid_infill_extrusion_width = 0.25 +solid_infill_speed = 140 +bridge_speed = 30 + +; [print:0.10mm @MK4IS 0.25] +; inherits = 0.07mm @MK4IS 0.25 +; layer_height = 0.1 +; bottom_solid_layers = 8 +; top_solid_layers = 10 +; perimeter_speed = 70 +; external_perimeter_acceleration = 1000 +; perimeter_acceleration = 1500 +; default_acceleration = 2000 +; perimeter_extrusion_width = 0.27 +; external_perimeter_extrusion_width = 0.27 +; infill_extrusion_width = 0.27 +; solid_infill_extrusion_width = 0.27 +; support_material_contact_distance = 0.08 +; raft_contact_distance = 0.07 + +[print:0.12mm SPEED @MK4IS 0.25] +inherits = *MK4IS_common025* +layer_height = 0.12 +perimeter_speed = 120 +external_perimeter_speed = 120 +small_perimeter_speed = 120 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +support_material_contact_distance = 0.09 +raft_contact_distance = 0.08 + +[print:0.12mm STRUCTURAL @MK4IS 0.25] +inherits = 0.12mm SPEED @MK4IS 0.25 +perimeter_speed = 70 +external_perimeter_speed = 40 +small_perimeter_speed = 40 +external_perimeter_acceleration = 1000 +perimeter_acceleration = 1500 +infill_acceleration = 3000 +solid_infill_acceleration = 3000 + +[print:0.15mm SPEED @MK4IS 0.25] +inherits = 0.12mm SPEED @MK4IS 0.25 +layer_height = 0.15 +bottom_solid_layers = 7 +top_solid_layers = 6 + +[print:0.15mm STRUCTURAL @MK4IS 0.25] +inherits = 0.15mm SPEED @MK4IS 0.25 +perimeter_speed = 70 +external_perimeter_speed = 40 +small_perimeter_speed = 40 +external_perimeter_acceleration = 1000 +perimeter_acceleration = 1500 + +# 0.3mm nozzle IS + +; [print:0.08mm STRUCTURAL @MK4IS 0.3] +; inherits = *MK4IS_common03* +; layer_height = 0.08 +; bottom_solid_layers = 8 +; top_solid_layers = 10 +; support_material_contact_distance = 0.1 +; raft_contact_distance = 0.1 +; perimeter_speed = 70 +; small_perimeter_speed = 40 +; external_perimeter_speed = 40 +; infill_speed = 100 +; solid_infill_speed = 160 +; top_solid_infill_speed = 40 +; support_material_speed = 70 +; bridge_speed = 25 +; gap_fill_speed = 40 +; external_perimeter_acceleration = 1000 +; perimeter_acceleration = 1500 +; top_solid_infill_acceleration = 1000 +; solid_infill_acceleration = 2500 +; infill_acceleration = 2500 +; bridge_acceleration = 800 +; default_acceleration = 1500 +; perimeter_extrusion_width = 0.3 +; external_perimeter_extrusion_width = 0.3 +; infill_extrusion_width = 0.3 +; solid_infill_extrusion_width = 0.3 + +[print:0.12mm STRUCTURAL @MK4IS 0.3] +inherits = *MK4IS_common03* +layer_height = 0.12 +bottom_solid_layers = 6 +top_solid_layers = 7 +perimeter_speed = 80 +small_perimeter_speed = 40 +external_perimeter_speed = 40 +infill_speed = 100 +solid_infill_speed = 200 +top_solid_infill_speed = 40 +support_material_speed = 70 +external_perimeter_acceleration = 1200 +perimeter_acceleration = 1500 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 2500 +infill_acceleration = 3000 +bridge_acceleration = 1000 +default_acceleration = 1500 +support_material_contact_distance = 0.12 +raft_contact_distance = 0.12 + +[print:0.16mm STRUCTURAL @MK4IS 0.3] +inherits = 0.12mm STRUCTURAL @MK4IS 0.3 +layer_height = 0.16 +bottom_solid_layers = 5 +top_solid_layers = 6 +small_perimeter_speed = 45 +external_perimeter_speed = 45 +infill_speed = 120 +top_solid_infill_speed = 50 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 2500 +infill_acceleration = 4000 +bridge_acceleration = 1000 +default_acceleration = 2000 + +[print:0.16mm SPEED @MK4IS 0.3] +inherits = 0.16mm STRUCTURAL @MK4IS 0.3 +perimeter_speed = 140 +small_perimeter_speed = 120 +external_perimeter_speed = 120 +support_material_speed = 100 +support_material_interface_speed = 45% +external_perimeter_acceleration = 2500 +perimeter_acceleration = 2500 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1000 +default_acceleration = 2000 + +[print:0.20mm SPEED @MK4IS 0.3] +inherits = 0.16mm SPEED @MK4IS 0.3 +layer_height = 0.2 +bottom_solid_layers = 4 +top_solid_layers = 5 + +# 0.5mm nozzle IS + +[print:0.10mm STRUCTURAL @MK4IS 0.5] +inherits = *MK4IS_common05* +layer_height = 0.1 +top_solid_layers = 8 +bottom_solid_layers = 7 +perimeter_speed = 70 +small_perimeter_speed = 40 +external_perimeter_speed = 40 +infill_speed = 200 +solid_infill_speed = 200 +top_solid_infill_speed = 70 +bridge_speed = 30 +gap_fill_speed = 40 +support_material_speed = 80 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 2500 +infill_acceleration = 3000 +bridge_acceleration = 1000 +default_acceleration = 2000 +extrusion_width = 0.55 +perimeter_extrusion_width = 0.5 +external_perimeter_extrusion_width = 0.5 +infill_extrusion_width = 0.5 +solid_infill_extrusion_width = 0.5 +top_infill_extrusion_width = 0.45 +support_material_contact_distance = 0.2 + +[print:0.15mm STRUCTURAL @MK4IS 0.5] +inherits = *MK4IS_common05* +layer_height = 0.15 +top_solid_layers = 6 +bottom_solid_layers = 5 +perimeter_speed = 80 +small_perimeter_speed = 45 +external_perimeter_speed = 45 +infill_speed = 200 +solid_infill_speed = 180 +top_solid_infill_speed = 70 +bridge_speed = 40 +gap_fill_speed = 50 +support_material_speed = 80 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 2500 +infill_acceleration = 3000 +bridge_acceleration = 1000 +default_acceleration = 2000 +support_material_contact_distance = 0.2 + +[print:0.20mm SPEED @MK4IS 0.5] +inherits = *MK4IS_common05* +layer_height = 0.20 +top_solid_layers = 5 +bottom_solid_layers = 4 +perimeter_speed = 140 +small_perimeter_speed = 140 +external_perimeter_speed = 140 +infill_speed = 200 +solid_infill_speed = 135 +top_solid_infill_speed = 70 +bridge_speed = 40 +gap_fill_speed = 70 +support_material_speed = 120 +external_perimeter_acceleration = 2500 +perimeter_acceleration = 3500 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1000 +default_acceleration = 2500 +support_material_contact_distance = 0.2 + +[print:0.20mm STRUCTURAL @MK4IS 0.5] +inherits = 0.20mm SPEED @MK4IS 0.5 +perimeter_speed = 80 +small_perimeter_speed = 45 +external_perimeter_speed = 45 +infill_speed = 200 +solid_infill_speed = 120 +top_solid_infill_speed = 70 +bridge_speed = 40 +gap_fill_speed = 70 +support_material_speed = 80 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 2500 + +[print:0.25mm SPEED @MK4IS 0.5] +inherits = 0.20mm SPEED @MK4IS 0.5 +layer_height = 0.25 +solid_infill_speed = 110 +top_solid_layers = 4 +bottom_solid_layers = 3 +support_material_contact_distance = 0.25 +raft_contact_distance = 0.25 + +[print:0.25mm STRUCTURAL @MK4IS 0.5] +inherits = 0.20mm STRUCTURAL @MK4IS 0.5 +layer_height = 0.25 +top_solid_layers = 4 +bottom_solid_layers = 3 +support_material_contact_distance = 0.25 +solid_infill_speed = 110 +raft_contact_distance = 0.25 + +; [print:0.30mm SPEED @MK4IS 0.5] +; inherits = *MK4IS_common05* +; layer_height = 0.3 +; top_solid_layers = 4 +; bottom_solid_layers = 3 +; perimeter_speed = 90 +; small_perimeter_speed = 75 +; external_perimeter_speed = 75 +; infill_speed = 200 +; solid_infill_speed = 90 +; top_solid_infill_speed = 70 +; bridge_speed = 40 +; gap_fill_speed = 60 +; support_material_speed = 60 +; external_perimeter_acceleration = 2000 +; perimeter_acceleration = 2500 +; top_solid_infill_acceleration = 1000 +; solid_infill_acceleration = 3000 +; infill_acceleration = 4000 +; bridge_acceleration = 1000 +; default_acceleration = 2500 +; support_material_contact_distance = 0.3 +; raft_contact_distance = 0.3 + +# 0.6mm nozzle IS + +[print:0.15mm STRUCTURAL @MK4IS 0.6] +inherits = *MK4IS_common06* +layer_height = 0.15 +bottom_solid_layers = 6 +top_solid_layers = 7 +perimeter_speed = 70 +external_perimeter_speed = 45 +small_perimeter_speed = 45 +solid_infill_speed = 165 +infill_speed = 200 +top_solid_infill_speed = 70 +bridge_speed = 40 +gap_fill_speed = 80 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2500 +top_solid_infill_acceleration = 1500 +solid_infill_acceleration = 2500 +infill_acceleration = 4000 +bridge_acceleration = 1500 +default_acceleration = 2500 +support_material_contact_distance = 0.22 +extrusion_width = 0.68 +perimeter_extrusion_width = 0.6 +external_perimeter_extrusion_width = 0.6 +infill_extrusion_width = 0.6 +solid_infill_extrusion_width = 0.6 +top_infill_extrusion_width = 0.5 + +[print:0.20mm SPEED @MK4IS 0.6] +inherits = *MK4IS_common06* +layer_height = 0.2 +bottom_solid_layers = 4 +top_solid_layers = 5 +perimeter_speed = 125 +external_perimeter_speed = 125 +small_perimeter_speed = 125 +solid_infill_speed = 125 +infill_speed = 200 +top_solid_infill_speed = 70 +bridge_speed = 40 +gap_fill_speed = 80 +external_perimeter_acceleration = 2500 +perimeter_acceleration = 3000 +top_solid_infill_acceleration = 1500 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1500 +default_acceleration = 2500 +support_material_contact_distance = 0.22 +extrusion_width = 0.68 +perimeter_extrusion_width = 0.62 +external_perimeter_extrusion_width = 0.62 +infill_extrusion_width = 0.62 +solid_infill_extrusion_width = 0.62 +top_infill_extrusion_width = 0.5 + +; [print:0.20mm STRUCTURAL @MK4IS 0.6] +; inherits = 0.20mm SPEED @MK4IS 0.6 +; bottom_solid_layers = 5 +; top_solid_layers = 6 +; perimeter_speed = 80 +; external_perimeter_speed = 45 +; small_perimeter_speed = 45 +; infill_speed = 200 +; solid_infill_speed = 120 +; top_solid_infill_speed = 70 +; bridge_speed = 30 +; gap_fill_speed = 80 +; external_perimeter_acceleration = 1500 +; perimeter_acceleration = 2500 +; top_solid_infill_acceleration = 1500 +; solid_infill_acceleration = 2500 + +[print:0.25mm SPEED @MK4IS 0.6] +inherits = *MK4IS_common06* +layer_height = 0.25 +perimeter_speed = 90 +external_perimeter_speed = 80 +small_perimeter_speed = 80 +infill_speed = 200 +solid_infill_speed = 90 +top_solid_infill_speed = 60 +support_material_speed = 90 +bridge_speed = 30 +gap_fill_speed = 70 +external_perimeter_acceleration = 2000 +perimeter_acceleration = 3000 +top_solid_infill_acceleration = 1500 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1500 +default_acceleration = 2500 +support_material_contact_distance = 0.25 + +[print:0.25mm STRUCTURAL @MK4IS 0.6] +inherits = 0.25mm SPEED @MK4IS 0.6 +perimeter_speed = 80 +external_perimeter_speed = 45 +small_perimeter_speed = 45 +top_solid_infill_speed = 60 +bridge_speed = 30 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2500 +top_solid_infill_acceleration = 1500 +solid_infill_acceleration = 3000 + +[print:0.32mm SPEED @MK4IS 0.6] +inherits = *MK4IS_common06* +layer_height = 0.32 +bottom_solid_layers = 3 +top_solid_layers = 4 +perimeter_speed = 70 +external_perimeter_speed = 70 +small_perimeter_speed = 70 +infill_speed = 200 +solid_infill_speed = 70 +top_solid_infill_speed = 60 +bridge_speed = 30 +gap_fill_speed = 60 +support_material_speed = 70 +external_perimeter_acceleration = 2000 +perimeter_acceleration = 2500 +top_solid_infill_acceleration = 1500 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1500 +default_acceleration = 2500 +support_material_contact_distance = 0.25 + +; [print:0.40mm SPEED @MK4IS 0.6] +; inherits = *MK4IS_common06* +; layer_height = 0.4 +; bottom_solid_layers = 3 +; top_solid_layers = 4 +; perimeter_speed = 60 +; external_perimeter_speed = 60 +; small_perimeter_speed = 60 +; infill_speed = 200 +; solid_infill_speed = 60 +; top_solid_infill_speed = 60 +; bridge_speed = 30 +; gap_fill_speed = 60 +; external_perimeter_acceleration = 2000 +; perimeter_acceleration = 2500 +; top_solid_infill_acceleration = 1500 +; solid_infill_acceleration = 3000 +; infill_acceleration = 4000 +; bridge_acceleration = 1500 +; default_acceleration = 2000 +; support_material_contact_distance = 0.25 + +# 0.8mm nozzle IS + +[print:0.30mm DETAIL @MK4IS 0.8] +inherits = *MK4IS_common08* +layer_height = 0.30 +bottom_solid_layers = 3 +top_solid_layers = 4 +perimeter_speed = 70 +external_perimeter_speed = 45 +small_perimeter_speed = 45 +infill_speed = 100 +solid_infill_speed = 50 +top_solid_infill_speed = 35 +support_material_speed = 60 +bridge_speed = 22 +gap_fill_speed = 40 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1000 +default_acceleration = 2000 +top_infill_extrusion_width = 0.7 + +[print:0.40mm QUALITY @MK4IS 0.8] +inherits = *MK4IS_common08* +layer_height = 0.4 +bottom_solid_layers = 3 +top_solid_layers = 4 +perimeter_speed = 50 +external_perimeter_speed = 45 +small_perimeter_speed = 45 +infill_speed = 90 +solid_infill_speed = 45 +top_solid_infill_speed = 35 +support_material_speed = 50 +bridge_speed = 22 +gap_fill_speed = 35 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1000 +default_acceleration = 2000 +top_infill_extrusion_width = 0.75 + +[print:0.55mm DRAFT @MK4IS 0.8] +inherits = *MK4IS_common08* +layer_height = 0.55 +bottom_solid_layers = 3 +top_solid_layers = 4 +perimeter_speed = 40 +external_perimeter_speed = 35 +small_perimeter_speed = 35 +infill_speed = 60 +solid_infill_speed = 35 +top_solid_infill_speed = 30 +support_material_speed = 35 +bridge_speed = 22 +gap_fill_speed = 30 +external_perimeter_acceleration = 1500 +perimeter_acceleration = 2000 +top_solid_infill_acceleration = 1000 +solid_infill_acceleration = 3000 +infill_acceleration = 4000 +bridge_acceleration = 1000 +default_acceleration = 2000 +top_infill_extrusion_width = 0.75 +perimeter_extrusion_width = 1 +external_perimeter_extrusion_width = 1 + # XXXXXXxxXXXXXXXXXXXXXX # XXX--- filament ---XXX # XXXXXXXXxxXXXXXXXXXXXX @@ -3759,7 +4632,7 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and no compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) and printer_notes!~/.*PG.*/ [filament:*PLAPG*] -start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.04{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp" +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.038{elsif nozzle_diameter[0]==0.5}0.025{elsif nozzle_diameter[0]==0.6}0.02{elsif nozzle_diameter[0]==0.8}0.014{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp" compatible_printers_condition = printer_notes=~/.*PG.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 slowdown_below_layer_time = 8 filament_cooling_final_speed = 2 @@ -3780,7 +4653,7 @@ full_fan_speed_layer = 3 inherits = *PLAPG* compatible_printers_condition = printer_notes=~/.*PG.*/ and nozzle_diameter[0]==0.6 filament_max_volumetric_speed = 15.5 -slowdown_below_layer_time = 10 +slowdown_below_layer_time = 14 [filament:*PLA08PG*] inherits = *PLAPG* @@ -3817,7 +4690,7 @@ filament_max_volumetric_speed = 15 [filament:*PETPG*] compatible_printers_condition = printer_notes=~/.*PG.*/ and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 filament_max_volumetric_speed = 10 -start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.055{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp" +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.055{elsif nozzle_diameter[0]==0.5}0.042{elsif nozzle_diameter[0]==0.6}0.025{elsif nozzle_diameter[0]==0.8}0.018{elsif nozzle_diameter[0]==0.25}0.18{elsif nozzle_diameter[0]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp" filament_cooling_final_speed = 1 filament_cooling_initial_speed = 2 filament_cooling_moves = 1 @@ -3832,13 +4705,14 @@ idle_temperature = 70 filament_retract_length = 1 filament_retract_lift = 0.15 filament_retract_before_wipe = 0 +filament_minimal_purge_on_wipe_tower = 35 slowdown_below_layer_time = 9 [filament:*PET06PG*] inherits = *PETPG* compatible_printers_condition = printer_notes=~/.*PG.*/ and nozzle_diameter[0]==0.6 filament_max_volumetric_speed = 17 -slowdown_below_layer_time = 12 +slowdown_below_layer_time = 14 filament_retract_length = 0.9 [filament:*PET08PG*] @@ -3963,9 +4837,9 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and no compatible_printers_condition = printer_model!="MINI" and printer_notes!~/.*PG.*/ and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:*ABSPG*] -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 filament_max_volumetric_speed = 12 -start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.04{elsif nozzle_diameter[0]==0.25}0.1{elsif nozzle_diameter[0]==0.3}0.06{elsif nozzle_diameter[0]==0.35}0.05{elsif nozzle_diameter[0]==0.5}0.03{elsif nozzle_diameter[0]==0.6}0.02{elsif nozzle_diameter[0]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.02{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp" +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.04{elsif nozzle_diameter[0]==0.25}0.1{elsif nozzle_diameter[0]==0.3}0.06{elsif nozzle_diameter[0]==0.35}0.05{elsif nozzle_diameter[0]==0.5}0.03{elsif nozzle_diameter[0]==0.6}0.02{elsif nozzle_diameter[0]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.02{elsif nozzle_diameter[0]==0.5}0.018{elsif nozzle_diameter[0]==0.6}0.012{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.25}0.09{elsif nozzle_diameter[0]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp" filament_cooling_final_speed = 50 filament_cooling_initial_speed = 10 filament_cooling_moves = 5 @@ -3975,17 +4849,18 @@ filament_load_time = 15 filament_unload_time = 12 filament_loading_speed = 14 filament_unloading_speed = 20 +filament_minimal_purge_on_wipe_tower = 35 idle_temperature = 100 [filament:*ABS06PG*] inherits = *ABSPG* filament_max_volumetric_speed = 15 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [filament:*ABS08PG*] inherits = *ABSPG* filament_max_volumetric_speed = 18 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 slowdown_below_layer_time = 25 [filament:*ABSMK4*] @@ -3995,80 +4870,82 @@ compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter [filament:*ABS06MK4*] inherits = *ABSMK4* filament_max_volumetric_speed = 15 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.6 [filament:*ABS08MK4*] inherits = *ABSMK4* filament_max_volumetric_speed = 18 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.8 slowdown_below_layer_time = 25 [filament:*PCPG*] inherits = *ABSPG* filament_max_volumetric_speed = 8 -start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp\n" +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.6}0.022{elsif nozzle_diameter[0]==0.8}0.016{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp" first_layer_bed_temperature = 100 bed_temperature = 105 idle_temperature = 150 +filament_minimal_purge_on_wipe_tower = 35 [filament:*PC06PG*] inherits = *PCPG* filament_max_volumetric_speed = 14 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [filament:*PC08PG*] inherits = *PCPG* filament_max_volumetric_speed = 18 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 [filament:*PCMK4*] inherits = *ABSMK4* filament_max_volumetric_speed = 8 -start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp\n" +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.6}0.022{elsif nozzle_diameter[0]==0.8}0.016{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp" idle_temperature = 150 [filament:*PC06MK4*] inherits = *PCMK4* filament_max_volumetric_speed = 14 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.6 [filament:*PC08MK4*] inherits = *PCMK4* filament_max_volumetric_speed = 18 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.8 [filament:*PAPG*] inherits = *ABSPG* filament_max_volumetric_speed = 5 -start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp\n" +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.6}0.022{elsif nozzle_diameter[0]==0.8}0.016{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp" bed_temperature = 105 idle_temperature = 150 +filament_minimal_purge_on_wipe_tower = 35 [filament:*PA06PG*] inherits = *PAPG* filament_max_volumetric_speed = 7 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [filament:*PA08PG*] inherits = *PAPG* filament_max_volumetric_speed = 10 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 [filament:*PAMK4*] inherits = *ABSMK4* filament_max_volumetric_speed = 5 -start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp\n" +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.6}0.022{elsif nozzle_diameter[0]==0.8}0.016{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp" idle_temperature = 70 [filament:*PA06MK4*] inherits = *PAMK4* filament_max_volumetric_speed = 7 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.6 [filament:*PA08MK4*] inherits = *PAMK4* filament_max_volumetric_speed = 10 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.8 [filament:*FLEX*] inherits = *common* @@ -4100,38 +4977,39 @@ filament_max_volumetric_speed = 3.5 filament_retract_speed = 60 filament_deretract_speed = 20 filament_retract_before_travel = 2 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]>=0.3 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]>=0.3 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 idle_temperature = 70 start_filament_gcode = "M900 K0 ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" +filament_minimal_purge_on_wipe_tower = 35 [filament:*FLEX06PG*] inherits = *FLEXPG* filament_max_volumetric_speed = 4.5 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [filament:*FLEX08PG*] inherits = *FLEXPG* filament_max_volumetric_speed = 8 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 [filament:*FLEXMK4*] filament_max_volumetric_speed = 4 filament_retract_speed = 60 filament_deretract_speed = 20 filament_retract_before_travel = 2 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]>=0.3 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]>=0.3 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 idle_temperature = 70 start_filament_gcode = "M900 K0 ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" [filament:*FLEX06MK4*] inherits = *FLEXMK4* filament_max_volumetric_speed = 6.5 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.6 [filament:*FLEX08MK4*] inherits = *FLEXMK4* filament_max_volumetric_speed = 9 -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.8 [filament:ColorFabb bronzeFill] inherits = *PLA*; *04PLUS* @@ -4219,27 +5097,27 @@ temperature = 270 inherits = ColorFabb HT; *PETPG* first_layer_bed_temperature = 100 bed_temperature = 105 -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 [filament:ColorFabb HT @PG 0.6] inherits = ColorFabb HT @PG; *PET06PG* -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.6 [filament:ColorFabb HT @PG 0.8] inherits = ColorFabb HT @PG; *PET08PG* -compatible_printers_condition = printer_model=="XL" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/.*XL.*/ and nozzle_diameter[0]==0.8 [filament:ColorFabb HT @MK4] inherits = ColorFabb HT; *PETPG* -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 [filament:ColorFabb HT @MK4 0.6] inherits = ColorFabb HT @MK4; *PET06PG* -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.6 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.6 [filament:ColorFabb HT @MK4 0.8] inherits = ColorFabb HT @MK4; *PET08PG* -compatible_printers_condition = printer_model=="MK4" and nozzle_diameter[0]==0.8 +compatible_printers_condition = printer_model=~/(MK4|MK4IS)/ and nozzle_diameter[0]==0.8 [filament:ColorFabb PLA-PHA] inherits = *PLA* @@ -4472,7 +5350,7 @@ compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model!="MINI inherits = Kimya ABS Carbon; *ABSPG*; *04PLUSPG* bed_temperature = 105 filament_max_volumetric_speed = 6 -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="XL" +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/.*XL.*/ [filament:Kimya ABS Carbon @PG 0.6] inherits = Kimya ABS Carbon @PG; *ABS06PG* @@ -4485,7 +5363,7 @@ filament_max_volumetric_speed = 14 [filament:Kimya ABS Carbon @MK4] inherits = Kimya ABS Carbon; *ABSMK4* filament_max_volumetric_speed = 6 -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="MK4" +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/(MK4|MK4IS)/ [filament:Kimya ABS Carbon @MK4 0.6] inherits = Kimya ABS Carbon @MK4; *ABS06MK4* @@ -4503,7 +5381,7 @@ filament_density = 1.037 [filament:Kimya ABS Kevlar @PG] inherits = Kimya ABS Kevlar; *ABSPG*; *04PLUSPG* bed_temperature = 105 -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="XL" +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/.*XL.*/ [filament:Kimya ABS Kevlar @PG 0.6] inherits = Kimya ABS Kevlar @PG; *ABS06PG* @@ -4515,7 +5393,7 @@ filament_max_volumetric_speed = 14 [filament:Kimya ABS Kevlar @MK4] inherits = Kimya ABS Kevlar; *ABSMK4*; *04PLUSPG* -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="MK4" +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/(MK4|MK4IS)/ [filament:Kimya ABS Kevlar @MK4 0.6] inherits = Kimya ABS Kevlar @MK4; *ABS06MK4* @@ -5097,14 +5975,17 @@ compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4I [filament:Generic PETG @PG 0.6] inherits = Generic PETG; *PET06PG* filament_max_volumetric_speed = 17 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 [filament:Generic PETG @PG 0.8] inherits = Generic PETG; *PET08PG* first_layer_temperature = 240 temperature = 250 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 -[filament:Generic PETG @MK4IS] +[filament:Generic PETG @PGIS] inherits = Generic PETG @PG +renamed_from = "Generic PETG @MK4IS" filament_max_volumetric_speed = 13 min_fan_speed = 35 max_fan_speed = 60 @@ -5116,6 +5997,26 @@ filament_retract_before_wipe = 20 filament_retract_lift = nil compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Generic PETG @PGIS 0.6] +inherits = Generic PETG @PG 0.6 +first_layer_temperature = 240 +temperature = 245 +filament_retract_length = 0.8 +filament_wipe = 1 +filament_retract_before_wipe = 20 +filament_retract_lift = nil +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Generic PETG @PGIS 0.8] +inherits = Generic PETG @PG 0.8 +first_layer_temperature = 245 +temperature = 250 +filament_retract_length = 0.8 +filament_wipe = 1 +filament_retract_before_wipe = 20 +filament_retract_lift = nil +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.8 + [filament:Extrudr DuraPro ASA] inherits = Fillamentum ASA filament_vendor = Extrudr @@ -5579,32 +6480,32 @@ inherits = addnorth Adura X; *PETPG* first_layer_bed_temperature = 100 bed_temperature = 105 filament_max_volumetric_speed = 4 -compatible_printers_condition = nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="XL" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/.*XL.*/ and ! single_extruder_multi_material [filament:addnorth Adura X @PG 0.6] inherits = addnorth Adura X @PG; *PET06PG* filament_max_volumetric_speed = 6 -compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model=="XL" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model=~/.*XL.*/ and ! single_extruder_multi_material [filament:addnorth Adura X @PG 0.8] inherits = addnorth Adura X @PG; *PET08PG* filament_max_volumetric_speed = 8 -compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_model=="XL" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_model=~/.*XL.*/ and ! single_extruder_multi_material [filament:addnorth Adura X @MK4] inherits = addnorth Adura X; *PETPG* filament_max_volumetric_speed = 4 -compatible_printers_condition = nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="MK4" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/(MK4|MK4IS)/ and ! single_extruder_multi_material [filament:addnorth Adura X @MK4 0.6] inherits = addnorth Adura X @MK4; *PET06PG* filament_max_volumetric_speed = 6 -compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model=="MK4" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model=~/(MK4|MK4IS)/ and ! single_extruder_multi_material [filament:addnorth Adura X @MK4 0.8] inherits = addnorth Adura X @MK4; *PET08PG* filament_max_volumetric_speed = 8 -compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_model=="MK4" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_model=~/(MK4|MK4IS)/ and ! single_extruder_multi_material [filament:addnorth Adura X @MINI] inherits = addnorth Adura X @@ -6031,27 +6932,56 @@ compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4I [filament:Generic PLA @PG 0.6] inherits = Generic PLA; *PLA06PG* filament_max_volumetric_speed = 15 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 [filament:Generic PLA @PG 0.8] inherits = Generic PLA; *PLA08PG* first_layer_temperature = 220 temperature = 220 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 -[filament:Generic PLA @MK4IS] +[filament:Generic PLA @PGIS] inherits = Generic PLA @PG +renamed_from = "Generic PLA @MK4IS" first_layer_temperature = 230 temperature = 220 -slowdown_below_layer_time = 5 +slowdown_below_layer_time = 6 compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 -[filament:Generic PLA Silk @MK4IS] -inherits = Generic PLA @PG +[filament:Generic PLA @PGIS 0.6] +inherits = Generic PLA @PG 0.6 first_layer_temperature = 230 temperature = 220 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Generic PLA @PGIS 0.8] +inherits = Generic PLA @PG 0.8 +first_layer_temperature = 230 +temperature = 225 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.8 + +[filament:Generic PLA Silk @PGIS] +inherits = Generic PLA @PG +renamed_from = "Generic PLA Silk @MK4IS" +first_layer_temperature = 230 +temperature = 225 slowdown_below_layer_time = 8 filament_max_volumetric_speed = 7 +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.035{elsif nozzle_diameter[0]==0.5}0.022{elsif nozzle_diameter[0]==0.6}0.018{elsif nozzle_diameter[0]==0.8}0.012{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.075{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp" compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Generic PLA Silk @PGIS 0.6] +inherits = Generic PLA Silk @PGIS +slowdown_below_layer_time = 15 +filament_max_volumetric_speed = 9 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Generic PLA Silk @PGIS 0.8] +inherits = Generic PLA Silk @PGIS +slowdown_below_layer_time = 20 +filament_max_volumetric_speed = 12 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.8 + [filament:3D-Fuel Standard PLA] inherits = *PLA* filament_vendor = 3D-Fuel @@ -6423,7 +7353,7 @@ compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model!="MINI [filament:Spectrum ASA Kevlar @PG] inherits = Spectrum ASA Kevlar; *ABSPG* -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="XL" +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/.*XL.*/ [filament:Spectrum ASA Kevlar @PG 0.6] inherits = Spectrum ASA Kevlar @PG; *ABS06PG* @@ -6433,7 +7363,7 @@ inherits = Spectrum ASA Kevlar @PG; *ABS08PG* [filament:Spectrum ASA Kevlar @MK4] inherits = Spectrum ASA Kevlar; *ABSMK4* -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="MK4" +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/(MK4|MK4IS)/ [filament:Spectrum ASA Kevlar @MK4 0.6] inherits = Spectrum ASA Kevlar @MK4; *ABS06MK4* @@ -7704,11 +8634,11 @@ filament_notes = "Material Description\nPET CF15 is a Carbon Fiber reinforced PE compatible_printers_condition = nozzle_diameter[0]>=0.6 and printer_model!="MINI" and printer_model!="MK2SMM" and printer_notes!~/.*PG.*/ and ! single_extruder_multi_material [filament:Ultrafuse PET-CF15 @PG 0.6] -inherits = Ultrafuse PET; *PET06PG* +inherits = Ultrafuse PET-CF15; *PET06PG* filament_max_volumetric_speed = 10 [filament:Ultrafuse PET-CF15 @PG 0.8] -inherits = Ultrafuse PET; *PET08PG* +inherits = Ultrafuse PET-CF15; *PET08PG* filament_max_volumetric_speed = 13 first_layer_temperature = 270 temperature = 275 @@ -8065,17 +8995,20 @@ temperature = 195 [filament:PrimaSelect PVA+ @PG] inherits = PrimaSelect PVA+; *PLAPG* +first_layer_temperature = 210 +temperature = 200 filament_max_volumetric_speed = 4 +filament_minimal_purge_on_wipe_tower = 35 [filament:PrimaSelect PVA+ @PG 0.6] inherits = PrimaSelect PVA+ @PG; *PLA06PG* -filament_max_volumetric_speed = 6 +filament_max_volumetric_speed = 5 [filament:PrimaSelect PVA+ @PG 0.8] inherits = PrimaSelect PVA+ @PG; *PLA08PG* -first_layer_temperature = 205 -temperature = 205 -filament_max_volumetric_speed = 9 +first_layer_temperature = 210 +temperature = 210 +filament_max_volumetric_speed = 8 [filament:Prusa ABS] inherits = *ABSC* @@ -8121,98 +9054,113 @@ filament_unload_time = 12 filament_loading_speed = 14 filament_unloading_speed = 20 -[filament:Generic ABS @MMU2] +[filament:Generic ABS @MMU] inherits = *ABS MMU2* +renamed_from = "Generic ABS @MMU2" filament_vendor = Generic -[filament:Generic ABS @MMU2 0.25] -inherits = Generic ABS @MMU2 +[filament:Generic ABS @MMU 0.25] +inherits = Generic ABS @MMU +renamed_from = "Generic ABS @MMU2 0.25" filament_max_volumetric_speed = 1.7 filament_ramming_parameters = "200 110 3.41935 3.6129 3.93548 4.35484 4.87097 5.51613 6.25806 7.06452 7.80645 8.35484 8.70968 8.83871| 0.05 3.37419 0.45 3.70322 0.95 4.5742 1.45 5.78387 1.95 7.44194 2.45 8.58065 2.95 8.89045 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Generic PLA @MMU2 0.25] -inherits = Generic PLA @MMU2 +[filament:Generic PLA @MMU 0.25] +inherits = Generic PLA @MMU +renamed_from = "Generic PLA @MMU2 0.25" filament_max_volumetric_speed = 3 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 120 2.70968 2.93548 3.29032 3.80645 4.58065 5.54839 6.54839 7.3871 7.93548 8.19355 8.16129 8.03226| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 7.98716 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusa PLA @MMU2 0.25] -inherits = Generic PLA @MMU2 0.25 +[filament:Prusa PLA @MMU 0.25] +inherits = Generic PLA @MMU 0.25 +renamed_from = "Prusa PLA @MMU2 0.25" -[filament:Prusament PLA @MMU2 0.25] -inherits = Prusament PLA @MMU2 +[filament:Prusament PLA @MMU 0.25] +inherits = Prusament PLA @MMU +renamed_from = "Prusament PLA @MMU2 0.25" filament_max_volumetric_speed = 3 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 120 2.70968 2.93548 3.29032 3.80645 4.58065 5.54839 6.54839 7.3871 7.93548 8.19355 8.16129 8.03226| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 7.98716 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusament PVB @MMU2 0.25] -inherits = Prusament PVB @MMU2 +[filament:Prusament PVB @MMU 0.25] +inherits = Prusament PVB @MMU +renamed_from = "Prusament PVB @MMU2 0.25" filament_max_volumetric_speed = 2 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusament ASA @MMU2 0.25] -inherits = Prusament ASA @MMU2 +[filament:Prusament ASA @MMU 0.25] +inherits = Prusament ASA @MMU +renamed_from = "Prusament ASA @MMU2 0.25" filament_max_volumetric_speed = 1.7 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 110 3.41935 3.6129 3.93548 4.35484 4.87097 5.51613 6.25806 7.06452 7.80645 8.35484 8.70968 8.83871| 0.05 3.37419 0.45 3.70322 0.95 4.5742 1.45 5.78387 1.95 7.44194 2.45 8.58065 2.95 8.89045 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusament PC Blend @MMU2 0.25] -inherits = Prusament PC Blend @MMU2 +[filament:Prusament PC Blend @MMU 0.25] +inherits = Prusament PC Blend @MMU +renamed_from = "Prusament PC Blend @MMU2 0.25" filament_max_volumetric_speed = 2 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 120 2.70968 2.93548 3.32258 3.83871 4.58065 5.54839 6.51613 7.35484 7.93548 8.16129| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 5.97742 1.95 7.69999 2.45 8.1936 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusa ABS @MMU2 0.25] -inherits = Prusa ABS @MMU2 +[filament:Prusa ABS @MMU 0.25] +inherits = Prusa ABS @MMU +renamed_from = "Prusa ABS @MMU2 0.25" filament_max_volumetric_speed = 1.7 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 110 3.41935 3.6129 3.93548 4.35484 4.87097 5.51613 6.25806 7.06452 7.80645 8.35484 8.70968 8.83871| 0.05 3.37419 0.45 3.70322 0.95 4.5742 1.45 5.78387 1.95 7.44194 2.45 8.58065 2.95 8.89045 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Generic PETG @MMU2 0.25] -inherits = Generic PETG @MMU2 +[filament:Generic PETG @MMU 0.25] +inherits = Generic PETG @MMU +renamed_from = "Generic PETG @MMU2 0.25" filament_max_volumetric_speed = 2 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 140 4.70968 4.74194 4.77419 4.80645 4.83871 4.87097 4.90323 5 5.25806 5.67742 6.29032 7.06452 7.83871 8.3871| 0.05 4.72901 0.45 4.73545 0.95 4.83226 1.45 4.88067 1.95 5.05483 2.45 5.93553 2.95 7.53556 3.45 8.6323 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusa PETG @MMU2 0.25] -inherits = Prusa PETG @MMU2 +[filament:Prusa PETG @MMU 0.25] +inherits = Prusa PETG @MMU +renamed_from = "Prusa PETG @MMU2 0.25" filament_max_volumetric_speed = 2 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 140 4.70968 4.74194 4.77419 4.80645 4.83871 4.87097 4.90323 5 5.25806 5.67742 6.29032 7.06452 7.83871 8.3871| 0.05 4.72901 0.45 4.73545 0.95 4.83226 1.45 4.88067 1.95 5.05483 2.45 5.93553 2.95 7.53556 3.45 8.6323 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusament PETG @MMU2 0.25] -inherits = Prusament PETG @MMU2 +[filament:Prusament PETG @MMU 0.25] +inherits = Prusament PETG @MMU +renamed_from = "Prusament PETG @MMU2 0.25" filament_max_volumetric_speed = 2 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 140 4.70968 4.74194 4.77419 4.80645 4.83871 4.87097 4.90323 5 5.25806 5.67742 6.29032 7.06452 7.83871 8.3871| 0.05 4.72901 0.45 4.73545 0.95 4.83226 1.45 4.88067 1.95 5.05483 2.45 5.93553 2.95 7.53556 3.45 8.6323 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Verbatim BVOH @MMU2 0.25] -inherits = Verbatim BVOH @MMU2 +[filament:Verbatim BVOH @MMU 0.25] +inherits = Verbatim BVOH @MMU +renamed_from = "Verbatim BVOH @MMU2 0.25" filament_max_volumetric_speed = 2 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:PrimaSelect PVA+ @MMU2 0.25] -inherits = PrimaSelect PVA+ @MMU2 +[filament:PrimaSelect PVA+ @MMU 0.25] +inherits = PrimaSelect PVA+ @MMU +renamed_from = "PrimaSelect PVA+ @MMU2 0.25" filament_max_volumetric_speed = 2 filament_minimal_purge_on_wipe_tower = 10 filament_ramming_parameters = "200 110 2.32258 2.48387 2.67742 2.87097 3.03226 3.22581 3.48387 3.80645 4.29032 5 5.93548 7 7.90323 8.48387 8.80645 8.90323| 0.05 2.27741 0.45 2.54192 0.95 2.9613 1.45 3.33225 1.95 4.02257 2.45 5.48393 2.95 7.72915 3.45 8.76139 3.95 8.95485 4.45 7.6 4.95 7.6" compatible_printers_condition = nozzle_diameter[0]==0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Generic HIPS @MMU2] +[filament:Generic HIPS @MMU] inherits = *ABS MMU2* +renamed_from = "Generic HIPS @MMU2" filament_vendor = Generic filament_cost = 27.3 filament_density = 1.04 @@ -8237,8 +9185,9 @@ filament_colour = #FFFFD7 filament_ramming_parameters = "130 120 2.74194 2.96774 3.25806 3.77419 4.83871 6.3871 8.09677 9.64516 10.7419 11.2903| 0.05 2.66451 0.45 3.05805 0.95 4.05807 1.45 7.13871 1.95 10.2806 2.45 11.4194 2.95 11.342 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.03{else}0.04{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K12{elsif nozzle_diameter[0]==0.8};{else}M900 K20{endif} ; Filament gcode LA 1.0" -[filament:Prusament ASA @MMU2] +[filament:Prusament ASA @MMU] inherits = *ABS MMU2* +renamed_from = "Prusament ASA @MMU2" filament_vendor = Prusa Polymers filament_cost = 42.69 filament_density = 1.07 @@ -8262,8 +9211,9 @@ filament_type = ASA filament_colour = #FFF2EC start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.01{elsif nozzle_diameter[0]==0.6}0.02{else}0.04{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K12{elsif nozzle_diameter[0]==0.8};{else}M900 K20{endif} ; Filament gcode LA 1.0" -[filament:Prusament PC Blend @MMU2] +[filament:Prusament PC Blend @MMU] inherits = *ABS MMU2* +renamed_from = "Prusament PC Blend @MMU2" filament_vendor = Prusa Polymers filament_cost = 62.36 filament_density = 1.22 @@ -8291,8 +9241,9 @@ filament_type = PC filament_colour = #DEE0E6 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.04{else}0.07{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K22{elsif nozzle_diameter[0]==0.8};{else}M900 K40{endif} ; Filament gcode LA 1.0" -[filament:Prusament PC Blend Carbon Fiber @MMU2] -inherits = Prusament PC Blend @MMU2 +[filament:Prusament PC Blend Carbon Fiber @MMU] +inherits = Prusament PC Blend @MMU +renamed_from = "Prusament PC Blend Carbon Fiber @MMU2" filament_cost = 90.73 filament_density = 1.22 extrusion_multiplier = 1.04 @@ -8304,15 +9255,16 @@ filament_retract_lift = nil filament_colour = #BBBBBB compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.8 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusa ABS @MMU2] +[filament:Prusa ABS @MMU] inherits = *ABS MMU2* +renamed_from = "Prusa ABS @MMU2" filament_vendor = Made for Prusa filament_cost = 27.82 filament_spool_weight = 230 -[filament:Filament PM ABS @MMU2] +[filament:Filament PM ABS @MMU] inherits = *ABS MMU2* -renamed_from = "Plasty Mladec ABS @MMU2" +renamed_from = "Plasty Mladec ABS @MMU2"; "Filament PM ABS @MMU2" filament_vendor = Filament PM filament_density = 1.08 filament_cost = 27.82 @@ -8394,23 +9346,47 @@ compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4I [filament:Prusa PETG @PG 0.6] inherits = Prusa PETG; *PET06PG* +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 [filament:Prusa PETG @PG 0.8] inherits = Prusa PETG; *PET08PG* +temperature = 250 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 -[filament:Prusa PETG @MK4IS] +[filament:Prusa PETG @PGIS] inherits = Generic PETG @PG +renamed_from = "Prusa PETG @MK4IS" filament_max_volumetric_speed = 15 min_fan_speed = 35 max_fan_speed = 60 first_layer_temperature = 240 -temperature = 250 +temperature = 245 filament_retract_length = 0.8 filament_wipe = 1 filament_retract_before_wipe = 20 filament_retract_lift = nil compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Prusa PETG @PGIS 0.6] +inherits = Generic PETG @PG 0.6 +first_layer_temperature = 240 +temperature = 245 +filament_retract_length = 0.8 +filament_wipe = 1 +filament_retract_before_wipe = 20 +filament_retract_lift = nil +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Prusa PETG @PGIS 0.8] +inherits = Generic PETG @PG 0.8 +first_layer_temperature = 245 +temperature = 250 +filament_retract_length = 0.8 +filament_wipe = 1 +filament_retract_before_wipe = 20 +filament_retract_lift = nil +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.8 + [filament:Verbatim PETG] inherits = *PET* filament_vendor = Verbatim @@ -8445,14 +9421,17 @@ compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4I [filament:Prusament PETG @PG 0.6] inherits = Prusament PETG; *PET06PG* +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 [filament:Prusament PETG @PG 0.8] inherits = Prusament PETG; *PET08PG* first_layer_temperature = 250 temperature = 260 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 -[filament:Prusament PETG @MK4IS] +[filament:Prusament PETG @PGIS] inherits = Prusament PETG @PG +renamed_from = "Prusament PETG @MK4IS" filament_max_volumetric_speed = 15 min_fan_speed = 35 max_fan_speed = 60 @@ -8462,6 +9441,52 @@ filament_retract_before_wipe = 20 filament_retract_lift = nil compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Prusament PETG @PGIS 0.6] +inherits = Prusament PETG @PG 0.6 +filament_retract_length = 0.8 +filament_wipe = 1 +filament_retract_before_wipe = 20 +filament_retract_lift = nil +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Prusament PETG @PGIS 0.8] +inherits = Prusament PETG @PG 0.8 +filament_retract_length = 0.8 +filament_wipe = 1 +filament_retract_before_wipe = 20 +filament_retract_lift = nil +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.8 + +[filament:Prusament PETG V0 certified] +inherits = Prusament PETG +first_layer_temperature = 230 +temperature = 230 +filament_cost = 54.99 +filament_density = 1.27 +extrusion_multiplier = 1.04 +filament_colour = #BBBBBB +compatible_printers_condition = printer_model=~/.*MK3S.*/ and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) and nozzle_diameter[0]==0.4 +filament_notes = "The following profile is the UL certified setting - Original Prusa i3 MK3S+, 0.4 mm nozzle, 0.2 mm layer height." + +[filament:Prusament PETG V0] +inherits = Prusament PETG V0 certified +compatible_printers_condition = printer_model=~/.*MK3.*/ and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) and ! (printer_model=~/.*MK3S.*/ and nozzle_diameter[0]==0.4) +filament_notes = "" + +[filament:Prusament PETG V0 @PG] +inherits = Prusament PETG V0 certified; *PETPG* +filament_notes = "" + +[filament:Prusament PETG V0 @PG 0.6] +inherits = Prusament PETG V0 @PG; *PET06PG* + +[filament:Prusament PETG V0 @PG 0.8] +inherits = Prusament PETG V0 @PG; *PET08PG* + +[filament:Prusament PETG V0 @MINI] +inherits = Prusament PETG V0 certified; *PETMINI* +filament_notes = "" + [filament:Prusament PETG Carbon Fiber] inherits = Prusament PETG filament_vendor = Prusa Polymers @@ -8604,70 +9629,74 @@ inherits = *PET MMU2* compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 13 -[filament:Generic PETG @MMU2] +[filament:Generic PETG @MMU] inherits = *PET MMU2* -renamed_from = "Generic PET MMU2"; "Generic PETG MMU2" +renamed_from = "Generic PET MMU2"; "Generic PETG MMU2"; "Generic PETG @MMU2" filament_vendor = Generic -[filament:Filament PM PETG @MMU2] +[filament:Filament PM PETG @MMU] inherits = *PET MMU2* -renamed_from = "Plasty Mladec PETG @MMU2" +renamed_from = "Plasty Mladec PETG @MMU2"; "Filament PM PETG @MMU2" filament_vendor = Filament PM filament_spool_weight = 230 -[filament:Prusa PETG @MMU2] +[filament:Prusa PETG @MMU] inherits = *PET MMU2* -renamed_from = "Prusa PET MMU2"; "Prusa PETG MMU2" +renamed_from = "Prusa PET MMU2"; "Prusa PETG MMU2"; "Prusa PETG @MMU2" filament_vendor = Made for Prusa filament_cost = 27.82 filament_spool_weight = 230 -[filament:Prusament PETG @MMU2] +[filament:Prusament PETG @MMU] inherits = *PET MMU2* +renamed_from = "Prusament PETG @MMU2" filament_type = PETG filament_vendor = Prusa Polymers filament_cost = 36.29 filament_density = 1.27 filament_spool_weight = 201 -[filament:Prusament PETG Carbon Fiber @MMU2] -inherits = Prusament PETG @MMU2 +[filament:Prusament PETG Carbon Fiber @MMU] +inherits = Prusament PETG @MMU +renamed_from = "Prusament PETG Carbon Fiber @MMU2" first_layer_temperature = 260 temperature = 260 extrusion_multiplier = 1.03 filament_cost = 54.99 filament_colour = #BBBBBB -[filament:Generic PETG @MMU2 0.6 nozzle] +[filament:Generic PETG @MMU 0.6 nozzle] inherits = *PET MMU2 06* -renamed_from = "Generic PET MMU2 0.6 nozzle"; "Generic PETG MMU2 0.6 nozzle" +renamed_from = "Generic PET MMU2 0.6 nozzle"; "Generic PETG MMU2 0.6 nozzle"; "Generic PETG @MMU2 0.6 nozzle" filament_vendor = Generic -[filament:Prusa PETG @MMU2 0.6 nozzle] +[filament:Prusa PETG @MMU 0.6 nozzle] inherits = *PET MMU2 06* -renamed_from = "Prusa PET MMU2 0.6 nozzle"; "Prusa PETG MMU2 0.6 nozzle" +renamed_from = "Prusa PET MMU2 0.6 nozzle"; "Prusa PETG MMU2 0.6 nozzle"; "Prusa PETG @MMU2 0.6 nozzle" filament_vendor = Made for Prusa filament_spool_weight = 230 -[filament:Prusament PETG @MMU2 0.6 nozzle] +[filament:Prusament PETG @MMU 0.6 nozzle] inherits = *PET MMU2 06* +renamed_from = "Prusament PETG @MMU2 0.6 nozzle" filament_type = PETG filament_vendor = Prusa Polymers filament_cost = 36.29 filament_density = 1.27 filament_spool_weight = 201 -[filament:Prusament PETG Carbon Fiber @MMU2 0.6 nozzle] -inherits = Prusament PETG @MMU2 0.6 nozzle +[filament:Prusament PETG Carbon Fiber @MMU 0.6 nozzle] +inherits = Prusament PETG @MMU 0.6 nozzle +renamed_from = "Prusament PETG Carbon Fiber @MMU2 0.6 nozzle" first_layer_temperature = 260 temperature = 260 extrusion_multiplier = 1.03 filament_cost = 54.99 filament_colour = #BBBBBB -[filament:Filament PM PETG @MMU2 0.6 nozzle] +[filament:Filament PM PETG @MMU 0.6 nozzle] inherits = *PET MMU2 06* -renamed_from = "Plasty Mladec PETG @MMU2 0.6 nozzle" +renamed_from = "Plasty Mladec PETG @MMU2 0.6 nozzle"; "Filament PM PETG @MMU2 0.6 nozzle" filament_type = PETG filament_vendor = Filament PM filament_spool_weight = 230 @@ -8687,17 +9716,32 @@ compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4I [filament:Prusa PLA @PG 0.6] inherits = Prusa PLA; *PLA06PG* filament_max_volumetric_speed = 15.5 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 [filament:Prusa PLA @PG 0.8] inherits = Prusa PLA; *PLA08PG* +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 -[filament:Prusa PLA @MK4IS] +[filament:Prusa PLA @PGIS] inherits = Prusa PLA @PG +renamed_from = "Prusa PLA @MK4IS" first_layer_temperature = 230 temperature = 220 -slowdown_below_layer_time = 5 +slowdown_below_layer_time = 6 compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Prusa PLA @PGIS 0.6] +inherits = Prusa PLA @PG 0.6 +first_layer_temperature = 230 +temperature = 220 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Prusa PLA @PGIS 0.8] +inherits = Prusa PLA @PG 0.8 +first_layer_temperature = 230 +temperature = 230 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.8 + [filament:Eolas Prints PLA] inherits = *PLA* @@ -8905,6 +9949,7 @@ bed_temperature = 110 filament_type = ASA max_fan_speed = 40 bridge_fan_speed = 70 +filament_notes = filament_max_volumetric_speed = 11 [filament:Print With Smile ASA @MINI] @@ -9556,7 +10601,7 @@ inherits = Fiberlogy Nylon PA12+CF15; *ABSPG*; *04PLUSPG* first_layer_bed_temperature = 100 bed_temperature = 105 filament_max_volumetric_speed = 8 -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="XL" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/.*XL.*/ and ! single_extruder_multi_material [filament:Fiberlogy Nylon PA12+CF15 @PG 0.6] inherits = Fiberlogy Nylon PA12+CF15 @PG; *ABS06PG* @@ -9569,7 +10614,7 @@ filament_max_volumetric_speed = 12 [filament:Fiberlogy Nylon PA12+CF15 @MK4] inherits = Fiberlogy Nylon PA12+CF15; *ABSMK4*; *04PLUSPG* filament_max_volumetric_speed = 8 -compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=="MK4" and ! single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_model=~/(MK4|MK4IS)/ and ! single_extruder_multi_material [filament:Fiberlogy Nylon PA12+CF15 @MK4 0.6] inherits = Fiberlogy Nylon PA12+CF15 @MK4; *ABS06MK4* @@ -9709,12 +10754,16 @@ filament_vendor = Hatchbox filament_cost = 25.4 filament_density = 1.27 filament_spool_weight = 245 +filament_max_volumetric_speed = 13 [filament:Hatchbox PLA @PG] inherits = Hatchbox PLA; *PLAPG* +filament_max_volumetric_speed = 13 +first_layer_temperature = 215 +temperature = 215 [filament:Hatchbox PLA @PG 0.6] -inherits = Hatchbox PLA; *PLA06PG* +inherits = Hatchbox PLA @PG; *PLA06PG* [filament:Hatchbox PLA @PG 0.8] inherits = Hatchbox PLA; *PLA08PG* @@ -9727,9 +10776,11 @@ filament_vendor = Esun filament_cost = 25.4 filament_density = 1.24 filament_spool_weight = 265 +filament_max_volumetric_speed = 13 [filament:Esun PLA @PG] inherits = Esun PLA; *PLAPG* +filament_max_volumetric_speed = 13 [filament:Esun PLA @PG 0.6] inherits = Esun PLA; *PLA06PG* @@ -9764,6 +10815,7 @@ filament_density = 1.24 [filament:EUMAKERS PLA @PG] inherits = EUMAKERS PLA; *PLAPG* +filament_max_volumetric_speed = 13 [filament:EUMAKERS PLA @PG 0.6] inherits = EUMAKERS PLA; *PLA06PG* @@ -9807,27 +10859,50 @@ compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4I [filament:Prusament PLA @PG 0.6] inherits = Prusament PLA; *PLA06PG* filament_max_volumetric_speed = 16 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 [filament:Prusament PLA @PG 0.8] inherits = Prusament PLA; *PLA08PG* -first_layer_temperature = 225 -temperature = 225 - -[filament:Prusament PLA @MK4IS] -inherits = Prusament PLA @PG first_layer_temperature = 230 temperature = 225 -slowdown_below_layer_time = 5 + +[filament:Prusament PLA @PGIS] +inherits = Prusament PLA @PG +renamed_from = "Prusament PLA @MK4IS" +first_layer_temperature = 230 +temperature = 225 +slowdown_below_layer_time = 6 compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 -[filament:Prusament PLA Blend @MK4IS] -inherits = Prusament PLA @PG +[filament:Prusament PLA @PGIS 0.6] +inherits = Prusament PLA @PG 0.6 first_layer_temperature = 230 -temperature = 220 +temperature = 225 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Prusament PLA Blend @PGIS] +inherits = Prusament PLA @PG +renamed_from = "Prusament PLA Blend @MK4IS" +first_layer_temperature = 230 +temperature = 225 filament_max_volumetric_speed = 7 slowdown_below_layer_time = 7 +start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*PRINTER_MODEL_MK4IS.*/}\nM572 S{if nozzle_diameter[0]==0.4}0.035{elsif nozzle_diameter[0]==0.5}0.022{elsif nozzle_diameter[0]==0.6}0.018{elsif nozzle_diameter[0]==0.8}0.012{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.075{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp" compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 +[filament:Prusament PLA Blend @PGIS 0.6] +inherits = Prusament PLA Blend @PGIS +filament_max_volumetric_speed = 9 +slowdown_below_layer_time = 14 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.6 + +[filament:Prusament PLA Blend @PGIS 0.8] +inherits = Prusament PLA Blend @PGIS +filament_max_volumetric_speed = 12 +slowdown_below_layer_time = 20 +temperature = 230 +compatible_printers_condition = printer_notes=~/.*PG.*/ and printer_model=="MK4IS" and nozzle_diameter[0]==0.8 + [filament:Prusament PVB] inherits = *PLA* filament_vendor = Prusa Polymers @@ -9847,12 +10922,13 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and no [filament:Prusament PVB @PG] inherits = Prusament PVB; *PLAPG* +filament_minimal_purge_on_wipe_tower = 35 [filament:Prusament PVB @PG 0.6] -inherits = Prusament PVB; *PLA06PG* +inherits = Prusament PVB @PG; *PLA06PG* [filament:Prusament PVB @PG 0.8] -inherits = Prusament PVB; *PLA08PG* +inherits = Prusament PVB @PG; *PLA08PG* first_layer_temperature = 225 temperature = 225 @@ -9874,32 +10950,37 @@ filament_unloading_speed_start = 100 full_fan_speed_layer = 4 filament_max_volumetric_speed = 12 -[filament:Generic PLA @MMU2] +[filament:Generic PLA @MMU] inherits = *PLA MMU2* +renamed_from = "Generic PLA @MMU2" filament_vendor = Generic -[filament:Prusa PLA @MMU2] +[filament:Prusa PLA @MMU] inherits = *PLA MMU2* +renamed_from = "Prusa PLA @MMU2" filament_vendor = Made for Prusa filament_cost = 27.82 filament_spool_weight = 230 -[filament:Prusament PLA @MMU2] +[filament:Prusament PLA @MMU] inherits = *PLA MMU2* +renamed_from = "Prusament PLA @MMU2" filament_vendor = Prusa Polymers filament_cost = 36.29 filament_density = 1.24 filament_spool_weight = 201 -[filament:Jessie PLA @MMU2] +[filament:Jessie PLA @MMU] inherits = *PLA MMU2* +renamed_from = "Jessie PLA @MMU2" filament_vendor = Printed Solid filament_cost = 21 filament_density = 1.24 filament_max_volumetric_speed = 10 -[filament:Prusament PVB @MMU2] +[filament:Prusament PVB @MMU] inherits = *PLA MMU2* +renamed_from = "Prusament PVB @MMU2" filament_vendor = Prusa Polymers filament_cost = 60.48 filament_density = 1.09 @@ -9914,8 +10995,9 @@ slowdown_below_layer_time = 20 filament_ramming_parameters = "120 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6" start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.05{else}0.08{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K24{elsif nozzle_diameter[0]==0.8};{else}M900 K45{endif} ; Filament gcode LA 1.0" -[filament:Fillamentum PLA @MMU2] +[filament:Fillamentum PLA @MMU] inherits = *PLA MMU2* +renamed_from = "Fillamentum PLA @MMU2" filament_vendor = Fillamentum filament_cost = 35.48 filament_density = 1.24 @@ -10340,21 +11422,26 @@ temperature = 210 [filament:Verbatim BVOH @PG] inherits = Verbatim BVOH; *ABSPG* +first_layer_temperature = 215 +temperature = 210 +idle_temperature = 70 filament_max_volumetric_speed = 4 compatible_printers_condition = nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8 and printer_notes=~/.*PG.*/ and ! single_extruder_multi_material [filament:Verbatim BVOH @PG 0.6] inherits = Verbatim BVOH @PG; *ABS06PG* -filament_max_volumetric_speed = 6 +filament_max_volumetric_speed = 5 compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_notes=~/.*PG.*/ and ! single_extruder_multi_material [filament:Verbatim BVOH @PG 0.8] inherits = Verbatim BVOH @PG; *ABS08PG* -filament_max_volumetric_speed = 9 +temperature = 215 +filament_max_volumetric_speed = 8 compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes=~/.*PG.*/ and ! single_extruder_multi_material -[filament:Verbatim BVOH @MMU2] +[filament:Verbatim BVOH @MMU] inherits = Verbatim BVOH +renamed_from = "Verbatim BVOH @MMU2" filament_vendor = Verbatim compatible_printers_condition = nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material temperature = 195 @@ -10373,9 +11460,10 @@ filament_unloading_speed = 20 filament_unloading_speed_start = 100 filament_loading_speed_start = 19 -[filament:PrimaSelect PVA+ @MMU2] +[filament:PrimaSelect PVA+ @MMU] inherits = *common* filament_vendor = PrimaSelect +renamed_from = "PrimaSelect PVA+ @MMU2" compatible_printers_condition = nozzle_diameter[0]!=0.25 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material bed_temperature = 60 bridge_fan_speed = 100 @@ -11113,23 +12201,27 @@ slowdown_below_layer_time = 20 ## Filaments 0.8 nozzle MMU2 -[filament:Generic HIPS @MMU2 0.8 nozzle] -inherits = Generic HIPS @MMU2 +[filament:Generic HIPS @MMU 0.8 nozzle] +inherits = Generic HIPS @MMU +renamed_from = "Generic HIPS @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material -[filament:Prusament ASA @MMU2 0.8 nozzle] -inherits = Prusament ASA @MMU2 +[filament:Prusament ASA @MMU 0.8 nozzle] +inherits = Prusament ASA @MMU +renamed_from = "Prusament ASA @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material slowdown_below_layer_time = 20 filament_max_volumetric_speed = 14 -[filament:Prusament PC Blend @MMU2 0.8 nozzle] -inherits = Prusament PC Blend @MMU2 +[filament:Prusament PC Blend @MMU 0.8 nozzle] +inherits = Prusament PC Blend @MMU +renamed_from = "Prusament PC Blend @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 12 -[filament:Generic PETG @MMU2 0.8 nozzle] -inherits = Generic PETG @MMU2 +[filament:Generic PETG @MMU 0.8 nozzle] +inherits = Generic PETG @MMU +renamed_from = "Generic PETG @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 18 first_layer_temperature = 240 @@ -11137,8 +12229,9 @@ temperature = 240 slowdown_below_layer_time = 20 filament_ramming_parameters = "120 140 5.51613 5.6129 5.70968 5.77419 5.77419 5.74194 5.80645 5.93548 6.06452 6.19355 6.3871 6.74194 7.25806 7.87097 8.54839 9.22581 10 10.8387| 0.05 5.5032 0.45 5.63868 0.95 5.8 1.45 5.7839 1.95 6.02257 2.45 6.25811 2.95 7.08395 3.45 8.43875 3.95 9.92258 4.45 11.3419 4.95 7.6" -[filament:Prusament PETG @MMU2 0.8 nozzle] -inherits = Prusament PETG @MMU2 +[filament:Prusament PETG @MMU 0.8 nozzle] +inherits = Prusament PETG @MMU +renamed_from = "Prusament PETG @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 18 first_layer_temperature = 240 @@ -11146,35 +12239,40 @@ temperature = 240 slowdown_below_layer_time = 20 filament_ramming_parameters = "120 140 5.51613 5.6129 5.70968 5.77419 5.77419 5.74194 5.80645 5.93548 6.06452 6.19355 6.3871 6.74194 7.25806 7.87097 8.54839 9.22581 10 10.8387| 0.05 5.5032 0.45 5.63868 0.95 5.8 1.45 5.7839 1.95 6.02257 2.45 6.25811 2.95 7.08395 3.45 8.43875 3.95 9.92258 4.45 11.3419 4.95 7.6" -[filament:Prusament PETG Carbon Fiber @MMU2 0.8 nozzle] -inherits = Prusament PETG @MMU2 0.8 nozzle +[filament:Prusament PETG Carbon Fiber @MMU 0.8 nozzle] +inherits = Prusament PETG @MMU 0.8 nozzle +renamed_from = "Prusament PETG Carbon Fiber @MMU2 0.8 nozzle" first_layer_temperature = 265 temperature = 270 extrusion_multiplier = 1.03 filament_cost = 54.99 filament_colour = #BBBBBB -[filament:Generic PLA @MMU2 0.8 nozzle] -inherits = Generic PLA @MMU2 +[filament:Generic PLA @MMU 0.8 nozzle] +inherits = Generic PLA @MMU +renamed_from = "Generic PLA @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 14 first_layer_temperature = 215 temperature = 210 -[filament:Prusament PLA @MMU2 0.8 nozzle] -inherits = Prusament PLA @MMU2 +[filament:Prusament PLA @MMU 0.8 nozzle] +inherits = Prusament PLA @MMU +renamed_from = "Prusament PLA @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 14 first_layer_temperature = 215 temperature = 210 -[filament:Verbatim BVOH @MMU2 0.8 nozzle] -inherits = Verbatim BVOH @MMU2 +[filament:Verbatim BVOH @MMU 0.8 nozzle] +inherits = Verbatim BVOH @MMU +renamed_from = "Verbatim BVOH @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 8 -[filament:PrimaSelect PVA+ @MMU2 0.8 nozzle] -inherits = PrimaSelect PVA+ @MMU2 +[filament:PrimaSelect PVA+ @MMU 0.8 nozzle] +inherits = PrimaSelect PVA+ @MMU +renamed_from = "PrimaSelect PVA+ @MMU2 0.8 nozzle" compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes!~/.*PG.*/ and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_max_volumetric_speed = 8 @@ -11785,6 +12883,14 @@ material_type = Flexible material_vendor = Prusa Polymers material_colour = #595959 +[sla_material:Prusament Resin Flex80 White @0.025] +inherits = *common 0.025* +exposure_time = 5.5 +initial_exposure_time = 35 +material_type = Flexible +material_vendor = Prusa Polymers +material_colour = #E2D3DB + ## Prusa 0.025 [sla_material:Prusa Orange Tough @0.025] @@ -12838,6 +13944,14 @@ material_type = Flexible material_vendor = Prusa Polymers material_colour = #595959 +[sla_material:Prusament Resin Flex80 White @0.05] +inherits = *common 0.05* +exposure_time = 6.5 +initial_exposure_time = 35 +material_type = Flexible +material_vendor = Prusa Polymers +material_colour = #E2D3DB + ## Prusa 0.05 [sla_material:Prusa Beige Tough @0.05] @@ -13322,6 +14436,14 @@ material_type = Flexible material_vendor = Prusa Polymers material_colour = #595959 +[sla_material:Prusament Resin Flex80 White @0.1] +inherits = *common 0.1* +exposure_time = 7.5 +initial_exposure_time = 45 +material_type = Flexible +material_vendor = Prusa Polymers +material_colour = #E2D3DB + ## Prusa 0.1 [sla_material:Prusa Orange Tough @0.1] @@ -13629,6 +14751,14 @@ material_vendor = Prusa Polymers material_colour = #595959 material_print_speed = slow +[sla_material:Prusament Resin Flex80 White @0.025 SL1S] +inherits = *0.025_sl1s* +exposure_time = 1.8 +initial_exposure_time = 17 +material_type = Flexible +material_vendor = Prusa Polymers +material_colour = #E2D3DB + ## Made for Prusa 0.025 [sla_material:Prusa Orange Tough @0.025 SL1S] @@ -14147,6 +15277,14 @@ material_vendor = Prusa Polymers material_colour = #595959 material_print_speed = slow +[sla_material:Prusament Resin Flex80 White @0.05 SL1S] +inherits = *0.05_sl1s* +exposure_time = 3 +initial_exposure_time = 17 +material_type = Flexible +material_vendor = Prusa Polymers +material_colour = #E2D3DB + ## Made for Prusa 0.05 [sla_material:Prusa Orange Tough @0.05 SL1S] @@ -14993,6 +16131,14 @@ material_vendor = Prusa Polymers material_colour = #595959 material_print_speed = slow +[sla_material:Prusament Resin Flex80 White @0.1 SL1S] +inherits = *0.1_sl1s* +exposure_time = 3.5 +initial_exposure_time = 17 +material_type = Flexible +material_vendor = Prusa Polymers +material_colour = #E2D3DB + ## Made for Prusa 0.1 [sla_material:Prusa Orange Tough @0.1 SL1S] @@ -15505,7 +16651,7 @@ inherits = Original Prusa i3 MK2S printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0 end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y200 F3600 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM900 K0 ; reset LA\nM84 ; disable motors\n; max_layer_z = [max_layer_z] thumbnails = 160x120 @@ -15514,7 +16660,7 @@ inherits = Original Prusa i3 MK2S 0.25 nozzle printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\nG92 E0 end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y200 F3600 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM900 K0 ; reset LA\nM84 ; disable motors\n; max_layer_z = [max_layer_z] thumbnails = 160x120 @@ -15524,7 +16670,7 @@ printer_model = MK2.5 remaining_times = 1 machine_max_jerk_e = 4.5 deretract_speed = 25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0 end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y200 F3600 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM900 K0 ; reset LA\nM84 ; disable motors\n; max_layer_z = [max_layer_z] thumbnails = 160x120 @@ -15541,7 +16687,7 @@ deretract_speed = 20 retract_lift = 0.25 remaining_times = 1 machine_max_jerk_e = 4.5 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0 end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y200 F3600 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM900 K0 ; reset LA\nM84 ; disable motors\n; max_layer_z = [max_layer_z] default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle @@ -15556,7 +16702,7 @@ max_print_height = 200 default_print_profile = 0.15mm OPTIMAL @MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; load to nozzle\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.20 F1000\nG1 X5 E4 F1000\nG92 E0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; load to nozzle\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.20 F1000\nG1 X5 E4 F1000\nG92 E0\n end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors [printer:Original Prusa i3 MK2.5 MMU2 Single 0.8 nozzle] @@ -15580,7 +16726,7 @@ printer_notes = Don't remove the following keywords! These keywords are used in single_extruder_multi_material = 1 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#3EC0FF;#FF4F4F;#FBEB7D -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors\n; max_layer_z = [max_layer_z] [printer:Original Prusa i3 MK2.5S] @@ -15607,7 +16753,7 @@ max_print_height = 200 default_print_profile = 0.15mm OPTIMAL @MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors\n; max_layer_z = [max_layer_z] [printer:Original Prusa i3 MK2.5S MMU2S Single 0.8 nozzle] @@ -15621,7 +16767,7 @@ retract_length = 0.7 retract_speed = 35 deretract_speed = 20 retract_lift = 0.25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle color_change_gcode = M600\nG1 E0.6 F1500 ; prime after color change @@ -15646,7 +16792,7 @@ nozzle_diameter = 0.25 printer_variant = 0.25 retract_lift = 0.15 default_print_profile = 0.10mm DETAIL 0.25 nozzle -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change [printer:Original Prusa i3 MK2.5S MMU2S] @@ -15658,7 +16804,7 @@ printer_notes = Don't remove the following keywords! These keywords are used in single_extruder_multi_material = 1 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#3EC0FF;#FF4F4F;#FBEB7D -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\nG92 E0\n end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM900 K0 ; reset LA\nM84 ; disable motors\n; max_layer_z = [max_layer_z] [printer:Original Prusa i3 MK2.5S MMU2S 0.6 nozzle] @@ -15688,7 +16834,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL @0.25 nozzle -default_filament_profile = Prusament PLA @MMU2 0.25 +default_filament_profile = Prusament PLA @MMU 0.25 color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change [printer:Original Prusa i3 MK2.5 MMU2 0.25 nozzle] @@ -15698,7 +16844,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL @0.25 nozzle -default_filament_profile = Prusament PLA @MMU2 0.25 +default_filament_profile = Prusament PLA @MMU 0.25 color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change ## 0.8mm nozzle profiles are only available for MMU2 Single mode at the moment. @@ -15711,7 +16857,7 @@ color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change ## printer_variant = 0.8 ## retract_length = 1 ## default_print_profile = 0.40mm QUALITY @0.8 nozzle -## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n +## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n ## [printer:Original Prusa i3 MK2.5 MMU2 0.8 nozzle] ## inherits = Original Prusa i3 MK2.5 MMU2 @@ -15721,7 +16867,7 @@ color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change ## printer_variant = 0.8 ## retract_length = 1 ## default_print_profile = 0.40mm QUALITY @0.8 nozzle -## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n +## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n # XXXXXXXXXXXXXXXXX # XXX--- MK3 ---XXX @@ -15751,7 +16897,7 @@ remaining_times = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} printer_model = MK3 default_print_profile = 0.15mm QUALITY @MK3 thumbnails = 160x120 @@ -15763,7 +16909,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E8 F700 ; intro line\nG1 X100 E12.5 F700 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E8 F700 ; intro line\nG1 X100 E12.5 F700 ; intro line\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif}\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change @@ -15774,7 +16920,7 @@ max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 deretract_speed = 25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0\nM221 S{if layer_height<0.075}100{else}95{endif} default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 color_change_gcode = M600\nG1 E0.5 F1500 ; prime after color change @@ -15788,7 +16934,7 @@ retract_length = 0.7 retract_speed = 35 deretract_speed = 20 retract_lift = 0.25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0\nM221 S95 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n{if filament_settings_id[initial_tool]=~/.*Prusament PA11.*/}\nG1 Z0.3 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E9 F1000 ; intro line\n{else}\nG1 Z0.2 F720\nG1 Y-3 F1000 ; go outside print area\nG92 E0\nG1 X60 E9 F1000 ; intro line\nG1 X100 E12.5 F1000 ; intro line\n{endif}\nG92 E0\nM221 S95 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle color_change_gcode = M600\nG1 E0.6 F1500 ; prime after color change @@ -15822,7 +16968,7 @@ retract_length_toolchange = 0 extra_loading_move = -13 printer_model = MK3MMU2 default_print_profile = 0.15mm QUALITY @MK3 -default_filament_profile = Prusament PLA @MMU2 +default_filament_profile = Prusament PLA @MMU [printer:*mm2s*] inherits = Original Prusa i3 MK3 @@ -15834,7 +16980,7 @@ retract_length_toolchange = 0 extra_loading_move = -25 printer_model = MK3SMMU2S default_print_profile = 0.15mm QUALITY @MK3 -default_filament_profile = Prusament PLA @MMU2 +default_filament_profile = Prusament PLA @MMU [printer:*25mm2*] inherits = Original Prusa i3 MK2.5 @@ -15844,7 +16990,7 @@ cooling_tube_retraction = 30 parking_pos_retraction = 85 retract_length_toolchange = 0 extra_loading_move = -13 -default_filament_profile = Prusament PLA @MMU2 +default_filament_profile = Prusament PLA @MMU [printer:*25mm2s*] inherits = Original Prusa i3 MK2.5S @@ -15854,13 +17000,13 @@ cooling_tube_retraction = 40 parking_pos_retraction = 85 retract_length_toolchange = 0 extra_loading_move = -25 -default_filament_profile = Prusament PLA @MMU2 +default_filament_profile = Prusament PLA @MMU [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors\n; max_layer_z = [max_layer_z] [printer:Original Prusa i3 MK3 MMU2 Single 0.6 nozzle] @@ -15871,7 +17017,7 @@ max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 deretract_speed = 25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 color_change_gcode = M600\nG1 E0.5 F1500 ; prime after color change @@ -15886,7 +17032,7 @@ retract_length = 0.7 retract_speed = 35 deretract_speed = 20 retract_lift = 0.25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle color_change_gcode = M600\nG1 E0.6 F1500 ; prime after color change @@ -15899,7 +17045,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F1000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 E8 F1000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change @@ -15908,7 +17054,7 @@ inherits = *mm2* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#3EC0FF;#FF4F4F;#FBEB7D -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors\n; max_layer_z = [max_layer_z] [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single] @@ -15916,7 +17062,7 @@ inherits = *mm2s* renamed_from = "Original Prusa i3 MK3S MMU2S Single" single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +start_gcode = M862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM702 C\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors\n; max_layer_z = [max_layer_z] [printer:Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.6 nozzle] @@ -15928,7 +17074,7 @@ max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 deretract_speed = 25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 +start_gcode = M862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 color_change_gcode = M600\nG1 E0.5 F1500 ; prime after color change @@ -15943,7 +17089,7 @@ retract_length = 0.7 retract_speed = 35 deretract_speed = 20 retract_lift = 0.25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 +start_gcode = M862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.40mm QUALITY @0.8 nozzle default_filament_profile = Prusament PLA @0.8 nozzle color_change_gcode = M600\nG1 E0.6 F1500 ; prime after color change @@ -15957,17 +17103,47 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 retract_lift = 0.15 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} +start_gcode = M862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nTc\n; purge line\nG1 X55 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F1400\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E value below. Excessive value can damage the printer.\n{if print_settings_id=~/.*@0.25 nozzle MK3.*/}M907 E430 ; set extruder motor current{endif} default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change +## MMU3 + +[printer:Original Prusa i3 MK3S & MK3S+ MMU3 Single] +inherits = Original Prusa i3 MK3S & MK3S+ MMU2S Single +printer_model = MK3SMMU3 + +[printer:Original Prusa i3 MK3S & MK3S+ MMU3 Single 0.6 nozzle] +inherits = Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.6 nozzle +printer_model = MK3SMMU3 + +[printer:Original Prusa i3 MK3S & MK3S+ MMU3 Single 0.8 nozzle] +inherits = Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.8 nozzle +printer_model = MK3SMMU3 + +[printer:Original Prusa i3 MK3S & MK3S+ MMU3 Single 0.25 nozzle] +inherits = Original Prusa i3 MK3S & MK3S+ MMU2S Single 0.25 nozzle +printer_model = MK3SMMU3 + +[printer:Original Prusa i3 MK3S & MK3S+ MMU3] +inherits = Original Prusa i3 MK3S & MK3S+ MMU2S +printer_model = MK3SMMU3 + +[printer:Original Prusa i3 MK3S & MK3S+ MMU3 0.6 nozzle] +inherits = Original Prusa i3 MK3S & MK3S+ MMU2S 0.6 nozzle +printer_model = MK3SMMU3 + +[printer:Original Prusa i3 MK3S & MK3S+ MMU3 0.25 nozzle] +inherits = Original Prusa i3 MK3S & MK3S+ MMU2S 0.25 nozzle +printer_model = MK3SMMU3 + [printer:Original Prusa i3 MK3S & MK3S+ MMU2S] inherits = *mm2s* renamed_from = "Original Prusa i3 MK3S MMU2S" machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#3EC0FF;#FF4F4F;#FBEB7D -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} +start_gcode = M862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0\n\n; Don't change E values below. Excessive value can damage the printer.\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE).*/}M907 E430 ; set extruder motor current{endif}\n{if print_settings_id=~/.*(SPEED @MK3|DRAFT @MK3).*/}M907 E538 ; set extruder motor current{endif} end_gcode = {if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nG1 X0 Y210 F7200 ; park\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+49, max_print_height)} F720 ; Move print head further up{endif}\n{if has_wipe_tower}\nG1 E-15 F3000\n{else}\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15 F5800\nG1 E-20 F5500\nG1 E10 F3000\nG1 E-10 F3100\nG1 E10 F3150\nG1 E-10 F3250\nG1 E10 F3300\n{endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM221 S100 ; reset flow\nM900 K0 ; reset LA\n{if print_settings_id=~/.*(DETAIL @MK3|QUALITY @MK3|SOLUBLE|@0.25 nozzle MK3).*/}M907 E538 ; reset extruder motor current{endif}\nM104 S0 ; turn off temperature\nM84 ; disable motors\n; max_layer_z = [max_layer_z] ## 0.6mm nozzle MMU2/S printer profiles @@ -15980,7 +17156,7 @@ max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 deretract_speed = 25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 +start_gcode = M862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E29 F1073\nG1 X5 E29 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 color_change_gcode = M600\nG1 E0.5 F1500 ; prime after color change @@ -15991,7 +17167,7 @@ max_layer_height = 0.40 min_layer_height = 0.15 printer_variant = 0.6 deretract_speed = 25 -start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 +start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55 E32 F1073\nG1 X5 E32 F1800\nG1 X55 E8 F2000\nG1 Z0.3 F1000\nG92 E0\nG1 X240 E25 F2200\nG1 Y-2 F1000\nG1 X55 E25 F1400\nG1 Z0.2 F1000\nG1 X5 E4 F1000\nG92 E0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0 default_print_profile = 0.30mm QUALITY @0.6 nozzle MK3 color_change_gcode = M600\nG1 E0.5 F1500 ; prime after color change @@ -16002,7 +17178,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 -default_filament_profile = Prusament PLA @MMU2 0.25 +default_filament_profile = Prusament PLA @MMU 0.25 color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change [printer:Original Prusa i3 MK3 MMU2 0.25 nozzle] @@ -16012,7 +17188,7 @@ max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL @0.25 nozzle MK3 -default_filament_profile = Prusament PLA @MMU2 0.25 +default_filament_profile = Prusament PLA @MMU 0.25 color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change ## 0.8mm nozzle MMU2/S printer profiles @@ -16025,7 +17201,7 @@ color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change ## max_layer_height = 0.6 ## min_layer_height = 0.2 ## printer_variant = 0.8 -## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000\nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 ## default_print_profile = 0.40mm QUALITY @0.8 nozzle ## [printer:Original Prusa i3 MK3S & MK3S+ MMU2S 0.8 nozzle] @@ -16034,7 +17210,7 @@ color_change_gcode = M600\nG1 E0.3 F1500 ; prime after color change ## max_layer_height = 0.6 ## min_layer_height = 0.2 ## printer_variant = 0.8 -## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.12.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 +## start_gcode = M862.3 P \"[printer_model]\" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM115 U3.13.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\n; Send the filament type to the MMU unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0 ## default_print_profile = 0.40mm QUALITY @0.8 nozzle ## MINI @@ -16146,7 +17322,7 @@ machine_max_acceleration_z = 200 machine_max_feedrate_e = 100 machine_max_feedrate_x = 400 machine_max_feedrate_y = 400 -machine_max_feedrate_z = 20 +machine_max_feedrate_z = 12 machine_max_jerk_e = 10 machine_max_jerk_x = 8 machine_max_jerk_y = 8 @@ -16174,10 +17350,67 @@ gcode_flavor = marlin2 high_current_on_filament_swap = 0 retract_lift = 0.3 +[printer:*XLMULTI*] +inherits = *commonXL* +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +retract_speed = 35,35,35,35,35 +deretract_speed = 20,20,20,20,20 +retract_lift = 0.2,0.2,0.2,0.2,0.2 +extruder_offset = 0x0,0x0,0x0,0x0,0x0 +min_layer_height = 0.15,0.15,0.15,0.15,0.15 +max_layer_height = 0.4,0.4,0.4,0.4,0.4 +retract_before_travel = 1.5,1.5,1.5,1.5,1.5 +retract_before_wipe = 80%,80%,80%,80%,80% +retract_length = 0.7,0.7,0.7,0.7,0.7 +retract_length_toolchange = 1.2,1.2,1.2,1.2,1.2 +retract_lift_above = 0,0,0,0,0 +retract_lift_below = 359,359,359,359,359 +retract_restart_extra = 0,0,0,0,0 +retract_restart_extra_toolchange = 0,0,0,0,0 +wipe = 1,1,1,1,1 +extruder_colour = #FF8000;#DB5182;#3EC0FF;#FF4F4F;#FBEB7D +start_gcode = M17 ; enable steppers\nM862.3 P "XL" ; printer model check\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n; set print area\nM555 X{first_layer_print_min[0]} Y{first_layer_print_min[1]} W{(first_layer_print_max[0]) - (first_layer_print_min[0])} H{(first_layer_print_max[1]) - (first_layer_print_min[1])}\n; inform about nozzle diameter\n{if (is_extruder_used[0])}M862.1 T0 P{nozzle_diameter[0]}{endif}\n{if (is_extruder_used[1])}M862.1 T1 P{nozzle_diameter[1]}{endif}\n{if (is_extruder_used[2])}M862.1 T2 P{nozzle_diameter[2]}{endif}\n{if (is_extruder_used[3])}M862.1 T3 P{nozzle_diameter[3]}{endif}\n{if (is_extruder_used[4])}M862.1 T4 P{nozzle_diameter[4]}{endif}\n\n; turn off unused heaters\n{if ! is_extruder_used[0]} M104 T0 S0 {endif}\n{if ! is_extruder_used[1]} M104 T1 S0 {endif}\n{if ! is_extruder_used[2]} M104 T2 S0 {endif}\n{if ! is_extruder_used[3]} M104 T3 S0 {endif}\n{if ! is_extruder_used[4]} M104 T4 S0 {endif}\n\nM217 Z{max(zhop, 2.0)} ; set toolchange z hop to 2mm, or zhop variable from slicer if higher\n; set bed and extruder temp for MBL\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 T{initial_tool} S{((filament_type[initial_tool] == "PC" or filament_type[initial_tool] == "PA") ? (first_layer_temperature[initial_tool] - 25) : (filament_type[initial_tool] == "FLEX" ? 210 : 170))}\n; Home XY\nG28 XY\n; try picking tools used in print\n{if (is_extruder_used[0]) and (initial_tool != 0)}T0 S1 L0{endif}\n{if (is_extruder_used[1]) and (initial_tool != 1)}T1 S1 L0{endif}\n{if (is_extruder_used[2]) and (initial_tool != 2)}T2 S1 L0{endif}\n{if (is_extruder_used[3]) and (initial_tool != 3)}T3 S1 L0{endif}\n{if (is_extruder_used[4]) and (initial_tool != 4)}T4 S1 L0{endif}\n; select tool that will be used to home & MBL\nT{initial_tool} S1 L0\n; home Z with MBL tool\nM84 E ; turn off E motor\nG28 Z\nG0 Z10 ; add Z clearance\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG29 G ; absorb heat\n; move to the nozzle cleanup area\nG1 X{(min(((((first_layer_print_min[0] + first_layer_print_max[0]) / 2) < ((print_bed_min[0] + print_bed_max[0]) / 2)) ? (((first_layer_print_min[1] - 7) < -2) ? 70 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)) : (((first_layer_print_min[1] - 7) < -2) ? 260 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))), first_layer_print_min[0])) + 32} Y{(min((first_layer_print_min[1] - 7), first_layer_print_min[1]))} Z{5} F{(travel_speed * 60)}\nM302 S160 ; lower cold extrusion limit to 160C\nG1 E{-(filament_type[0] == "FLEX" ? 4 : 2)} F2400 ; retraction for nozzle cleanup\n; nozzle cleanup\nM84 E ; turn off E motor\nG29 P9 X{((((first_layer_print_min[0] + first_layer_print_max[0]) / 2) < ((print_bed_min[0] + print_bed_max[0]) / 2)) ? (((first_layer_print_min[1] - 7) < -2) ? 70 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)) : (((first_layer_print_min[1] - 7) < -2) ? 260 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)))} Y{(first_layer_print_min[1] - 7)} W{32} H{7}\nG0 Z10 F480 ; move away in Z\nM107 ; turn off the fan\n; MBL\nM84 E ; turn off E motor\nG29 P1 ; invalidate mbl & probe print area\nG29 P1 X30 Y0 W{(((is_extruder_used[4]) or ((is_extruder_used[3]) or (is_extruder_used[2]))) ? "300" : ((is_extruder_used[1]) ? "130" : "50"))} H20 C ; probe near purge place\nG29 P3.2 ; interpolate mbl probes\nG29 P3.13 ; extrapolate mbl outside probe area\nG29 A ; activate mbl\nG1 Z10 F720 ; move away in Z\nP0 S1 L1 D0; park the tool\n; set extruder temp\n{if first_layer_temperature[0] > 0 and (is_extruder_used[0])}M104 T0 S{first_layer_temperature[0]}{endif}\n{if first_layer_temperature[1] > 0 and (is_extruder_used[1])}M104 T1 S{first_layer_temperature[1]}{endif}\n{if first_layer_temperature[2] > 0 and (is_extruder_used[2])}M104 T2 S{first_layer_temperature[2]}{endif}\n{if first_layer_temperature[3] > 0 and (is_extruder_used[3])}M104 T3 S{first_layer_temperature[3]}{endif}\n{if first_layer_temperature[4] > 0 and (is_extruder_used[4])}M104 T4 S{first_layer_temperature[4]}{endif}\n{if (is_extruder_used[0]) and initial_tool != 0}\n;\n; purge first tool\n;\nP0 S1 L1 D0; park the tool\nM109 T0 S{first_layer_temperature[0]}\nT0 S1 L0 D0; pick the tool\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move to the Z ready for purge\nG0 X{(0 == 0 ? 30 : (0 == 1 ? 150 : (0 == 2 ? 210 : 330)))} Y{(0 < 4 ? -7 : -4.5)} F{(travel_speed * 60)} ; move close to the sheet's edge\nG0 E10 X40 Z0.2 F500 ; purge while moving towards the sheet\nG0 X70 E9 F800 ; continue purging and wipe the nozzle\nG0 X{70 + 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{70 + 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG1 E{- 1.5 * retract_length[0]} F2400 ; retract\n{e_retracted[0] = 1.5 * retract_length[0]} ; update slicer internal retract variable\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move away\nM104 S{(is_nil(idle_temperature[0]) ? (first_layer_temperature[0] + standby_temperature_delta) : (idle_temperature[0]))} T0\n{endif}\n{if (is_extruder_used[1]) and initial_tool != 1}\n;\n; purge second tool\n;\nP0 S1 L1 D0; park the tool\nM109 T1 S{first_layer_temperature[1]}\nT1 S1 L0 D0; pick the tool\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move to the Z ready for purge\nG0 X{(1 == 0 ? 30 : (1 == 1 ? 150 : (1 == 2 ? 210 : 330)))} Y{(1 < 4 ? -7 : -4.5)} F{(travel_speed * 60)} ; move close to the sheet's edge\nG0 E10 X140 Z0.2 F500 ; purge while moving towards the sheet\nG0 X110 E9 F800 ; continue purging and wipe the nozzle\nG0 X{110 - 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{110 - 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG1 E{- 1.5 * retract_length[1]} F2400 ; retract\n{e_retracted[1] = 1.5 * retract_length[1]} ; update slicer internal retract variable\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move away\nM104 S{(is_nil(idle_temperature[1]) ? (first_layer_temperature[1] + standby_temperature_delta) : (idle_temperature[1]))} T1\n{endif}\n{if (is_extruder_used[2]) and initial_tool != 2}\n;\n; purge third tool\n;\nP0 S1 L1 D0; park the tool\nM109 T2 S{first_layer_temperature[2]}\nT2 S1 L0 D0; pick the tool\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move to the Z ready for purge\nG0 X{(2 == 0 ? 30 : (2 == 1 ? 150 : (2 == 2 ? 210 : 330)))} Y{(2 < 4 ? -7 : -4.5)} F{(travel_speed * 60)} ; move close to the sheet's edge\nG0 E10 X220 Z0.2 F500 ; purge while moving towards the sheet\nG0 X250 E9 F800 ; continue purging and wipe the nozzle\nG0 X{250 + 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{250 + 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG1 E{- 1.5 * retract_length[2]} F2400 ; retract\n{e_retracted[2] = 1.5 * retract_length[2]} ; update slicer internal retract variable\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move away\nM104 S{(is_nil(idle_temperature[2]) ? (first_layer_temperature[2] + standby_temperature_delta) : (idle_temperature[2]))} T2\n{endif}\n{if (is_extruder_used[3]) and initial_tool != 3}\n;\n; purge fourth tool\n;\nP0 S1 L1 D0; park the tool\nM109 T3 S{first_layer_temperature[3]}\nT3 S1 L0 D0; pick the tool\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move to the Z ready for purge\nG0 X{(3 == 0 ? 30 : (3 == 1 ? 150 : (3 == 2 ? 210 : 330)))} Y{(3 < 4 ? -7 : -4.5)} F{(travel_speed * 60)} ; move close to the sheet's edge\nG0 E10 X320 Z0.2 F500 ; purge while moving towards the sheet\nG0 X290 E9 F800 ; continue purging and wipe the nozzle\nG0 X{290 - 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{290 - 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG1 E{- 1.5 * retract_length[3]} F2400 ; retract\n{e_retracted[3] = 1.5 * retract_length[3]} ; update slicer internal retract variable\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move away\nM104 S{(is_nil(idle_temperature[3]) ? (first_layer_temperature[3] + standby_temperature_delta) : (idle_temperature[3]))} T3\n{endif}\n{if (is_extruder_used[4]) and initial_tool != 4}\n;\n; purge fifth tool\n;\nP0 S1 L1 D0; park the tool\nM109 T4 S{first_layer_temperature[4]}\nT4 S1 L0 D0; pick the tool\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move to the Z ready for purge\nG0 X{(4 == 0 ? 30 : (4 == 1 ? 150 : (4 == 2 ? 210 : 330)))} Y{(4 < 4 ? -7 : -4.5)} F{(travel_speed * 60)} ; move close to the sheet's edge\nG0 E10 X320 Z0.2 F500 ; purge while moving towards the sheet\nG0 X290 E9 F800 ; continue purging and wipe the nozzle\nG0 X{290 - 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{290 - 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG1 E{- 1.5 * retract_length[4]} F2400 ; retract\n{e_retracted[4] = 1.5 * retract_length[4]} ; update slicer internal retract variable\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move away\nM104 S{(is_nil(idle_temperature[4]) ? (first_layer_temperature[4] + standby_temperature_delta) : (idle_temperature[4]))} T4\n{endif}\n;\n; purge initial tool\n;\nP0 S1 L1 D0; park the tool\nM109 T{initial_tool} S{first_layer_temperature[initial_tool]}\nT{initial_tool} S1 L0 D0; pick the tool\nG92 E0 ; reset extruder position\nG1 Z10 F720 ; move to the Z ready for purge\nG0 X{(initial_tool == 0 ? 30 : (initial_tool == 1 ? 150 : (initial_tool == 2 ? 210 : 330)))} Y{(initial_tool < 4 ? -7 : -4.5)} F{(travel_speed * 60)} ; move close to the sheet's edge\nG0 E10 X{(initial_tool == 0 ? 30 : (initial_tool == 1 ? 150 : (initial_tool == 2 ? 210 : 330))) + ((initial_tool == 0 or initial_tool == 2 ? 1 : -1) * 10)} Z0.2 F500 ; purge while moving towards the sheet\nG0 X{(initial_tool == 0 ? 30 : (initial_tool == 1 ? 150 : (initial_tool == 2 ? 210 : 330))) + ((initial_tool == 0 or initial_tool == 2 ? 1 : -1) * 40)} E9 F800 ; continue purging and wipe the nozzle\nG0 X{(initial_tool == 0 ? 30 : (initial_tool == 1 ? 150 : (initial_tool == 2 ? 210 : 330))) + ((initial_tool == 0 or initial_tool == 2 ? 1 : -1) * 40) + ((initial_tool == 0 or initial_tool == 2 ? 1 : -1) * 3)} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{(initial_tool == 0 ? 30 : (initial_tool == 1 ? 150 : (initial_tool == 2 ? 210 : 330))) + ((initial_tool == 0 or initial_tool == 2 ? 1 : -1) * 40) + ((initial_tool == 0 or initial_tool == 2 ? 1 : -1) * 3 * 2)} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG1 E{- 1.5 * retract_length[initial_tool]} F2400 ; retract\n{e_retracted[initial_tool] = 1.5 * retract_length[initial_tool]}\nG92 E0 ; reset extruder position\n +end_gcode = G4 ; wait\n\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+5, max_print_height)}{endif} ; Move print head up\nP0 S1 ; park tool\nM84 ; disable motors\n\n; turn off extruder heaters\n{if is_extruder_used[0]} M104 T0 S0 {endif}\n{if is_extruder_used[1]} M104 T1 S0 {endif}\n{if is_extruder_used[2]} M104 T2 S0 {endif}\n{if is_extruder_used[3]} M104 T3 S0 {endif}\n{if is_extruder_used[4]} M104 T4 S0 {endif}\n\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM221 S100 ; reset flow percentage\nM84 ; disable motors\nM77 ; stop print timer\n; max_layer_z = [max_layer_z] +toolchange_gcode = ; Change Tool[previous_extruder] -> Tool[next_extruder] (layer [layer_num])\n{\nlocal max_speed_toolchange = 350.0;\nlocal wait_for_extruder_temp = true;\nposition[2] = position[2] + 2.0;\n\nlocal speed_toolchange = max_speed_toolchange;\nif travel_speed < max_speed_toolchange then\n speed_toolchange = travel_speed;\nendif\n"G1 F" + (speed_toolchange * 60) + "\n";\nif wait_for_extruder_temp and not((layer_num < 0) and (next_extruder == initial_tool)) then\n "P0 S1 L2 D0\n";\n "; " + layer_num + "\n";\n if layer_num == 0 then\n "M109 S" + first_layer_temperature[next_extruder] + " T" + next_extruder + "\n";\n else\n "M109 S" + temperature[next_extruder] + " T" + next_extruder + "\n";\n endif\nendif\n"T" + next_extruder + " S1 L0 D0\n";\n} +color_change_gcode = M600 + +[printer:*XL2MULTI*] +inherits = *XLMULTI* +printer_model = XL2 + [printer:Original Prusa XL 0.4 nozzle] inherits = *commonXL* max_layer_height = 0.30 +[printer:Original Prusa XL - 2T 0.4 nozzle] +inherits = *XL2MULTI* +printer_variant = 0.4 +nozzle_diameter = 0.4,0.4 +retract_speed = 35,35 +deretract_speed = 25,25 +retract_lift = 0.3,0.3 +extruder_offset = 0x0,0x0 +min_layer_height = 0.07,0.07 +max_layer_height = 0.3,0.3 +retract_before_travel = 1.5,1.5 +retract_before_wipe = 80%,80% +retract_length = 0.8,0.8 +retract_length_toolchange = 1.2,1.2 +retract_lift_below = 359,359 +wipe = 1,1 +extruder_colour = #FF8000;#DB5182 + +[printer:Original Prusa XL - 5T 0.4 nozzle] +inherits = *XLMULTI* +printer_model = XL5 +printer_variant = 0.4 +nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 +retract_lift = 0.3,0.3,0.3,0.3,0.3 +min_layer_height = 0.07,0.07,0.07,0.07,0.07 +max_layer_height = 0.3,0.3,0.3,0.3,0.3 +retract_length = 0.8,0.8,0.8,0.8,0.8 +wipe = 1,1,1,1,1 + [printer:Original Prusa XL 0.6 nozzle] inherits = *commonXL* printer_variant = 0.6 @@ -16189,6 +17422,29 @@ min_layer_height = 0.15 default_print_profile = 0.25mm QUALITY @XL 0.6 default_filament_profile = "Prusament PLA @PG 0.6" +[printer:Original Prusa XL - 2T 0.6 nozzle] +inherits = *XL2MULTI* +printer_variant = 0.6 +nozzle_diameter = 0.6,0.6 +retract_speed = 35,35 +deretract_speed = 20,20 +retract_lift = 0.2,0.2 +extruder_offset = 0x0,0x0 +min_layer_height = 0.15,0.15 +max_layer_height = 0.4,0.4 +retract_before_travel = 1.5,1.5 +retract_before_wipe = 80%,80% +retract_length = 0.7,0.7 +retract_length_toolchange = 1.2,1.2 +retract_lift_below = 359,359 +wipe = 1,1 +extruder_colour = #FF8000;#DB5182 + +[printer:Original Prusa XL - 5T 0.6 nozzle] +inherits = *XLMULTI* +printer_model = XL5 +printer_variant = 0.6 + [printer:Original Prusa XL 0.5 nozzle] inherits = *commonXL* printer_variant = 0.5 @@ -16198,6 +17454,33 @@ max_layer_height = 0.32 min_layer_height = 0.07 default_print_profile = 0.20mm QUALITY @XL 0.5 +[printer:Original Prusa XL - 2T 0.5 nozzle] +inherits = *XL2MULTI* +printer_variant = 0.5 +nozzle_diameter = 0.5,0.5 +retract_speed = 35,35 +deretract_speed = 25,25 +retract_lift = 0.3,0.3 +extruder_offset = 0x0,0x0 +min_layer_height = 0.07,0.07 +max_layer_height = 0.32,0.32 +retract_before_travel = 1.5,1.5 +retract_before_wipe = 80%,80% +retract_length = 0.7,0.7 +retract_length_toolchange = 1.2,1.2 +retract_lift_below = 359,359 +wipe = 1,1 +extruder_colour = #FF8000;#DB5182 + +[printer:Original Prusa XL - 5T 0.5 nozzle] +inherits = *XLMULTI* +printer_model = XL5 +printer_variant = 0.5 +nozzle_diameter = 0.5,0.5,0.5,0.5,0.5 +retract_lift = 0.3,0.3,0.3,0.3,0.3 +min_layer_height = 0.07,0.07,0.07,0.07,0.07 +max_layer_height = 0.32,0.32,0.32,0.32,0.32 + [printer:Original Prusa XL 0.3 nozzle] inherits = *commonXL* printer_variant = 0.3 @@ -16208,6 +17491,35 @@ min_layer_height = 0.05 default_print_profile = 0.16mm QUALITY @XL 0.3 machine_max_acceleration_travel = 2500 +[printer:Original Prusa XL - 2T 0.3 nozzle] +inherits = *XL2MULTI* +printer_variant = 0.3 +nozzle_diameter = 0.3,0.3 +retract_speed = 35,35 +deretract_speed = 25,25 +retract_lift = 0.3,0.3 +extruder_offset = 0x0,0x0 +min_layer_height = 0.05,0.05 +max_layer_height = 0.22,0.22 +retract_before_travel = 1.5,1.5 +retract_before_wipe = 80%,80% +retract_length = 0.7,0.7 +retract_length_toolchange = 1.2,1.2 +retract_lift_below = 359,359 +wipe = 1,1 +extruder_colour = #FF8000;#DB5182 +machine_max_acceleration_travel = 2500 + +[printer:Original Prusa XL - 5T 0.3 nozzle] +inherits = *XLMULTI* +printer_model = XL5 +printer_variant = 0.3 +nozzle_diameter = 0.3,0.3,0.3,0.3,0.3 +retract_lift = 0.3,0.3,0.3,0.3,0.3 +min_layer_height = 0.05,0.05,0.05,0.05,0.05 +max_layer_height = 0.22,0.22,0.22,0.22,0.22 +machine_max_acceleration_travel = 2500 + [printer:Original Prusa XL 0.25 nozzle] inherits = *commonXL* printer_variant = 0.25 @@ -16219,6 +17531,36 @@ min_layer_height = 0.05 default_print_profile = 0.12mm QUALITY @XL 0.25 machine_max_acceleration_travel = 1500 +[printer:Original Prusa XL - 2T 0.25 nozzle] +inherits = *XL2MULTI* +printer_variant = 0.25 +nozzle_diameter = 0.25,0.25 +retract_speed = 35,35 +deretract_speed = 25,25 +retract_lift = 0.15,0.15 +extruder_offset = 0x0,0x0 +min_layer_height = 0.05,0.05 +max_layer_height = 0.15,0.15 +retract_before_travel = 1.5,1.5 +retract_before_wipe = 80%,80% +retract_length = 0.8,0.8 +retract_length_toolchange = 1.2,1.2 +retract_lift_below = 359,359 +wipe = 1,1 +extruder_colour = #FF8000;#DB5182 +machine_max_acceleration_travel = 1500 + +[printer:Original Prusa XL - 5T 0.25 nozzle] +inherits = *XLMULTI* +printer_model = XL5 +printer_variant = 0.25 +nozzle_diameter = 0.25,0.25,0.25,0.25,0.25 +retract_lift = 0.15,0.15,0.15,0.15,0.15 +min_layer_height = 0.05,0.05,0.05,0.05,0.05 +max_layer_height = 0.15,0.15,0.15,0.15,0.15 +retract_length = 0.8,0.8,0.8,0.8,0.8 +machine_max_acceleration_travel = 1500 + [printer:Original Prusa XL 0.8 nozzle] inherits = *commonXL* printer_variant = 0.8 @@ -16233,6 +17575,34 @@ min_layer_height = 0.2 default_print_profile = 0.40mm QUALITY @XL 0.8 default_filament_profile = "Prusament PLA @PG 0.8" +[printer:Original Prusa XL - 2T 0.8 nozzle] +inherits = *XL2MULTI* +printer_variant = 0.8 +nozzle_diameter = 0.8,0.8 +retract_speed = 25,25 +deretract_speed = 15,15 +retract_lift = 0.25,0.25 +extruder_offset = 0x0,0x0 +min_layer_height = 0.2,0.2 +max_layer_height = 0.6,0.6 +retract_before_travel = 1.5,1.5 +retract_before_wipe = 50%,50% +retract_length = 0.6,0.6 +retract_length_toolchange = 1.2,1.2 +retract_lift_below = 359,359 +wipe = 1,1 +extruder_colour = #FF8000;#DB5182 + +[printer:Original Prusa XL - 5T 0.8 nozzle] +inherits = *XLMULTI* +printer_model = XL5 +printer_variant = 0.8 +nozzle_diameter = 0.8,0.8,0.8,0.8,0.8 +retract_lift = 0.25,0.25,0.25,0.25,0.25 +min_layer_height = 0.2,0.2,0.2,0.2,0.2 +max_layer_height = 0.6,0.6,0.6,0.6,0.6 +retract_length = 0.6,0.6,0.6,0.6,0.6 + [printer:*commonMK4*] inherits = *common* bed_shape = 0x0,250x0,250x210,0x210 @@ -16337,8 +17707,9 @@ min_layer_height = 0.2 default_print_profile = 0.40mm QUALITY @MK4 0.8 default_filament_profile = "Prusament PLA @PG 0.8" -[printer:Original Prusa MK4 Input Shaper (Alpha)] +[printer:Original Prusa MK4 Input Shaper 0.4 nozzle] inherits = *commonMK4* +renamed_from = "Original Prusa MK4 Input Shaper (Alpha)" printer_model = MK4IS printer_variant = 0.4 max_layer_height = 0.30 @@ -16364,14 +17735,79 @@ max_print_height = 220 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_MODEL_MK4IS\nPG retract_length = 0.7 wipe = 0 +retract_before_wipe = 80 retract_speed = 35 deretract_speed = 0 -start_gcode = M17 ; enable steppers\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM862.3 P "MK4" ; printer model check\nM862.5 P2 ; g-code level check\nM862.6 P"Input shaper" ; firmware feature check\n\nM555 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} W{((min(print_bed_max[0], max(first_layer_print_min[0] + 32, first_layer_print_max[0])))) - ((min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))} H{((first_layer_print_max[1])) - ((max(0, first_layer_print_min[1]) - 4))}\n\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n\nM140 S[first_layer_bed_temperature] ; set bed temp\n{if filament_type[initial_tool]=="PC" or filament_type[initial_tool]=="PA"}\nM104 S{first_layer_temperature[initial_tool]-25} ; set extruder temp for bed leveling\nM109 R{first_layer_temperature[initial_tool]-25} ; wait for temp\n{elsif filament_type[initial_tool]=="FLEX"}\nM104 S210 ; set extruder temp for bed leveling\nM109 R210 ; wait for temp\n{else}\nM104 S170 ; set extruder temp for bed leveling\nM109 R170 ; wait for temp\n{endif}\n\nM84 E ; turn off E motor\n\nG28 ; home all without mesh bed level\n\nG1 X{10 + 32} Y-4 Z5 F4800\n\nM302 S160 ; lower cold extrusion limit to 160C\n\n{if filament_type[initial_tool]=="FLEX"}\nG1 E-4 F2400 ; retraction\n{else}\nG1 E-2 F2400 ; retraction\n{endif}\n\nM84 E ; turn off E motor\n\nG29 P9 X10 Y-4 W32 H4\n\n{if first_layer_bed_temperature[initial_tool]<=60}M106 S100{endif}\n\nG0 Z40 F10000\n\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nM107\n\n;\n; MBL\n;\nM84 E ; turn off E motor\nG29 P1 ; invalidate mbl & probe print area\nG29 P1 X0 Y0 W50 H20 C ; probe near purge place\nG29 P3.2 ; interpolate mbl probes\nG29 P3.13 ; extrapolate mbl outside probe area\nG29 A ; activate mbl\n\n; prepare for purge\nM104 S{first_layer_temperature[0]}\nG0 X0 Y-4 Z15 F4800 ; move away and ready for the purge\nM109 S{first_layer_temperature[0]}\n\nG92 E0\nM569 S0 E ; set spreadcycle mode for extruder\n\n;\n; Extrude purge line\n;\nG92 E0 ; reset extruder position\nG1 E{(filament_type[0] == "FLEX" ? 4 : 2)} F2400 ; deretraction after the initial one before nozzle cleaning\nG0 E7 X15 Z0.2 F500 ; purge\nG0 X25 E4 F500 ; purge\nG0 X35 E4 F650 ; purge\nG0 X45 E4 F800 ; purge\nG0 X{45 + 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{45 + 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\n\nG92 E0\nM221 S100 ; set flow to 100%\n\nM593 X T2 F50.7\nM593 Y T2 F40.6 +start_gcode = M17 ; enable steppers\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM862.3 P "MK4" ; printer model check\nM862.5 P2 ; g-code level check\nM862.6 P"Input shaper" ; FW feature check\nM115 U5.0.0-RC+11963\n\nM555 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} W{((min(print_bed_max[0], max(first_layer_print_min[0] + 32, first_layer_print_max[0])))) - ((min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))} H{((first_layer_print_max[1])) - ((max(0, first_layer_print_min[1]) - 4))}\n\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n\nM140 S[first_layer_bed_temperature] ; set bed temp\n{if filament_type[initial_tool]=="PC" or filament_type[initial_tool]=="PA"}\nM104 S{first_layer_temperature[initial_tool]-25} ; set extruder temp for bed leveling\nM109 R{first_layer_temperature[initial_tool]-25} ; wait for temp\n{elsif filament_type[initial_tool]=="FLEX"}\nM104 S210 ; set extruder temp for bed leveling\nM109 R210 ; wait for temp\n{else}\nM104 S170 ; set extruder temp for bed leveling\nM109 R170 ; wait for temp\n{endif}\n\nM84 E ; turn off E motor\n\nG28 ; home all without mesh bed level\n\nG1 X{10 + 32} Y-4 Z5 F4800\n\nM302 S160 ; lower cold extrusion limit to 160C\n\n{if filament_type[initial_tool]=="FLEX"}\nG1 E-4 F2400 ; retraction\n{else}\nG1 E-2 F2400 ; retraction\n{endif}\n\nM84 E ; turn off E motor\n\nG29 P9 X10 Y-4 W32 H4\n\n{if first_layer_bed_temperature[initial_tool]<=60}M106 S100{endif}\n\nG0 Z40 F10000\n\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nM107\n\n;\n; MBL\n;\nM84 E ; turn off E motor\nG29 P1 ; invalidate mbl & probe print area\nG29 P1 X0 Y0 W50 H20 C ; probe near purge place\nG29 P3.2 ; interpolate mbl probes\nG29 P3.13 ; extrapolate mbl outside probe area\nG29 A ; activate mbl\n\n; prepare for purge\nM104 S{first_layer_temperature[0]}\nG0 X0 Y-4 Z15 F4800 ; move away and ready for the purge\nM109 S{first_layer_temperature[0]}\n\nG92 E0\nM569 S0 E ; set spreadcycle mode for extruder\n\n;\n; Extrude purge line\n;\nG92 E0 ; reset extruder position\nG1 E{(filament_type[0] == "FLEX" ? 4 : 2)} F2400 ; deretraction after the initial one before nozzle cleaning\nG0 E7 X15 Z0.2 F500 ; purge\nG0 X25 E4 F500 ; purge\nG0 X35 E4 F650 ; purge\nG0 X45 E4 F800 ; purge\nG0 X{45 + 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{45 + 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\n\nG92 E0\nM221 S100 ; set flow to 100% end_gcode = {if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X241 Y170 F3600 ; park\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+23, max_print_height)} F300 ; Move print head up{endif}\nG4 ; wait\nM572 S0 ; reset PA\nM593 X T2 F0 ; disable IS\nM593 Y T2 F0 ; disable IS\nM84 X Y E ; disable motors\n; max_layer_z = [max_layer_z] -before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]\nM593 Y T2 F{interpolate_table(extruded_weight_total, (0,40), (300,30), (800,20), (10000,20)) }\nM201 X{interpolate_table(extruded_weight_total, (0,4000), (1400,2500), (10000,2500))} Y{interpolate_table(extruded_weight_total, (0,4000), (1400,2500), (10000,2500))}\nM74 W[extruded_weight_total] -default_print_profile = 0.20mm Input Shaper @MK4IS 0.4 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]\nM201 X{interpolate_table(extruded_weight_total, (0,4000), (1400,2500), (10000,2500))} Y{interpolate_table(extruded_weight_total, (0,4000), (1400,2500), (10000,2500))}\nM74 W[extruded_weight_total] +default_print_profile = 0.20mm SPEED @MK4IS 0.4 default_filament_profile = "Prusament PLA @MK4IS" +[printer:Original Prusa MK4 Input Shaper 0.25 nozzle] +inherits = Original Prusa MK4 Input Shaper 0.4 nozzle +renamed_from = "Original Prusa MK4 Input Shaper (Alpha) 0.25 nozzle" +printer_model = MK4IS +printer_variant = 0.25 +nozzle_diameter = 0.25 +retract_length = 0.8 +retract_lift = 0.15 +max_layer_height = 0.15 +min_layer_height = 0.05 +default_print_profile = 0.12mm STRUCTURAL @MK4IS 0.25 + +[printer:Original Prusa MK4 Input Shaper 0.3 nozzle] +inherits = Original Prusa MK4 Input Shaper 0.4 nozzle +renamed_from = "Original Prusa MK4 Input Shaper (Alpha) 0.3 nozzle" +printer_model = MK4IS +printer_variant = 0.3 +nozzle_diameter = 0.3 +retract_length = 0.7 +max_layer_height = 0.22 +min_layer_height = 0.05 +default_print_profile = 0.16mm STRUCTURAL @MK4IS 0.3 + +[printer:Original Prusa MK4 Input Shaper 0.5 nozzle] +inherits = Original Prusa MK4 Input Shaper 0.4 nozzle +renamed_from = "Original Prusa MK4 Input Shaper (Alpha) 0.5 nozzle" +printer_variant = 0.5 +nozzle_diameter = 0.5 +retract_length = 0.7 +max_layer_height = 0.32 +min_layer_height = 0.07 +deretract_speed = 25 +wipe = 1 +default_print_profile = 0.20mm SPEED @MK4IS 0.5 + +[printer:Original Prusa MK4 Input Shaper 0.6 nozzle] +inherits = Original Prusa MK4 Input Shaper 0.4 nozzle +renamed_from = "Original Prusa MK4 Input Shaper (Alpha) 0.6 nozzle" +printer_variant = 0.6 +nozzle_diameter = 0.6 +retract_length = 0.7 +retract_lift = 0.2 +max_layer_height = 0.40 +min_layer_height = 0.15 +deretract_speed = 25 +wipe = 1 +default_print_profile = 0.25mm SPEED @MK4IS 0.6 + +[printer:Original Prusa MK4 Input Shaper 0.8 nozzle] +inherits = Original Prusa MK4 Input Shaper 0.4 nozzle +renamed_from = "Original Prusa MK4 Input Shaper (Alpha) 0.8 nozzle" +printer_variant = 0.8 +nozzle_diameter = 0.8 +retract_length = 0.6 +wipe = 1 +retract_before_wipe = 50% +retract_lift = 0.25 +retract_speed = 25 +deretract_speed = 15 +max_layer_height = 0.6 +min_layer_height = 0.2 +default_print_profile = 0.40mm QUALITY @MK4IS 0.8 +default_filament_profile = "Prusament PLA @PG 0.8" + [printer:Original Prusa SL1] printer_technology = SLA printer_model = SL1 From a9fee21f2b30d067851490169120d4d668f8842d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 21 Aug 2023 15:25:12 +0200 Subject: [PATCH 111/120] SPE-1839: Change of MMU2 error reporting string if the MMU2/3 bootloader USB device cannot be found --- src/slic3r/GUI/FirmwareDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 47402644f4..272461d4b4 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -771,7 +771,7 @@ void FirmwareDialog::priv::ensure_joined() const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) { switch (usb_pid.boot) { case USB_PID_MMU_BOOT: - return "Original Prusa MMU 2.0 Control"; + return "Original Prusa Multi Material 2 & 3 Upgrade (bootloader)"; case USB_PID_CW1_BOOT: return "Original Prusa CW1"; case USB_PID_CW1S_BOOT: From a9ed8455f3ca8d6b549c4b69c7faae5c8cb757c0 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:31:51 +0200 Subject: [PATCH 112/120] Sync with PrusaSlicer-settings. Added PapapiuLab bundle. --- resources/profiles/PapapiuLab.idx | 2 + resources/profiles/PapapiuLab.ini | 505 ++++++++++++++++++ .../profiles/PapapiuLab/Papapiu_N1S_Bed.stl | Bin 0 -> 7884 bytes .../PapapiuLab/Papapiu_N1S_thumbnail.png | Bin 0 -> 52445 bytes .../PapapiuLab/Papapiu_N1_Texture.png | Bin 0 -> 29071 bytes 5 files changed, 507 insertions(+) create mode 100644 resources/profiles/PapapiuLab.idx create mode 100644 resources/profiles/PapapiuLab.ini create mode 100644 resources/profiles/PapapiuLab/Papapiu_N1S_Bed.stl create mode 100644 resources/profiles/PapapiuLab/Papapiu_N1S_thumbnail.png create mode 100644 resources/profiles/PapapiuLab/Papapiu_N1_Texture.png diff --git a/resources/profiles/PapapiuLab.idx b/resources/profiles/PapapiuLab.idx new file mode 100644 index 0000000000..f747a21eda --- /dev/null +++ b/resources/profiles/PapapiuLab.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.6.0-alpha1 +0.1.0 Initial version \ No newline at end of file diff --git a/resources/profiles/PapapiuLab.ini b/resources/profiles/PapapiuLab.ini new file mode 100644 index 0000000000..9a54f9e6d6 --- /dev/null +++ b/resources/profiles/PapapiuLab.ini @@ -0,0 +1,505 @@ +# Print profiles for Papapiu printers. +[vendor] +name = Papapiu +config_version = 0.1.0 +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PapapiuLab/ + + + +[printer_model:Papapiu_N1S] +name = Papapiu N1S +variants = 0.4 +technology = FFF +bed_model = Papapiu_N1S_Bed.stl +bed_texture = Papapiu_N1_Texture.png +thumbnail = Papapiu_N1S_thumbnail.png +default_materials = "Generic PLA @Papapiu"; Generic PETG HF @Papapiu; Generic PLA @Papapiu; Esun ePA-CF @Papapiu; Generic ABS @Papapiu; Generic TPU @Papapiu + +[print:*common*] +compatible_printers_condition = printer_model=~/(Papapiu_N1S).*/ +allow_empty_layers = 0 +avoid_crossing_perimeters = 0 +avoid_crossing_perimeters_max_detour = 0 +bottom_fill_pattern = monotonic +extra_perimeters = 0 +bottom_solid_layers = 8 +bottom_solid_min_thickness = 0 +bridge_acceleration = 0 +bridge_angle = 0 +bridge_speed = 60 +brim_offset = 0 +brim_width = 0 +clip_multipart_objects = 1 +complete_objects = 0 +complete_objects_one_skirt = 0 +complete_objects_sort = object +default_acceleration = 12000 +dont_support_bridges = 1 +draft_shield = 0 +ensure_vertical_shell_thickness = 1 +exact_last_layer_height = 0 +external_perimeter_extrusion_width = 0.4 +external_perimeter_speed = 90% +extruder_clearance_height = 25 +extruder_clearance_radius = 45 +extrusion_width = 0.4 +fill_angle = 45 +fill_density = 15% +fill_pattern = cubic +first_layer_acceleration = 3000 +first_layer_extrusion_width = 0.45 +first_layer_height = 0.2 +first_layer_speed = 100 +gap_fill = 1 +gap_fill_speed = 50 +gcode_comments = 0 +gcode_label_objects = 1 +infill_acceleration = 12000 +infill_anchor = 600% +infill_anchor_max = 5 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.4 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 18% +infill_speed = 150 +interface_shells = 0 +ironing = 0 +ironing_flowrate = 15% +ironing_spacing = 0.1 +ironing_speed = 15 +ironing_type = top +layer_height = 0.15 +max_print_speed = 300 +max_volumetric_speed = 12 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{print_time}.gcode +perimeter_acceleration = 12000 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.4 +perimeter_speed = 120 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = aligned +skirt_distance = 10 +skirt_height = 1 +skirts = 2 +slice_closing_radius = 0.049 +small_perimeter_speed = 25 +solid_fill_pattern = rectilinear +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.4 +solid_infill_speed = 80% +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_auto = 1 +support_material_buildplate_only = 1 +support_material_contact_distance_bottom = 0.2 +support_material_contact_distance_top = 0.2 +support_material_extruder = 0 +support_material_extrusion_width = 0.38 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_pattern = rectilinear +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_solid_first_layer = 0 +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 65 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +top_fill_pattern = monotonic +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 60% +top_solid_layers = 8 +top_solid_min_thickness = 0 +travel_speed = 250 +travel_speed_z = 0 +wipe_tower = 0 +wipe_tower_bridging = 10 +wipe_tower_no_sparse_layers = 0 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 170 +wipe_tower_y = 140 +elefant_foot_compensation = 0.1 + +[print:*0.15mm*] +bottom_solid_layers = 7 +top_solid_layers = 7 +layer_height = 0.15 +bridge_flow_ratio = 0.7 +max_print_speed = 250 +solid_infill_speed = 80% + +[print:*0.2mm*] +bottom_solid_layers = 5 +top_solid_layers = 5 +layer_height = 0.2 +bridge_flow_ratio = 0.8 + +[print:0.15mm NORMAL @Papapiu] +inherits = *common*; *0.15mm* +max_print_speed = 300 +bottom_solid_layers = 8 +top_solid_layers = 8 + +[print:0.15mm FAST @Papapiu] +inherits = *common*; *0.15mm* +compatible_printers_condition = printer_model=~/(Papapiu_N1S).*/ +external_perimeter_speed = 140 +first_layer_infill_speed = 60 +first_layer_speed = 100 +gap_fill_speed = 50 +infill_speed = 230 +ironing_speed = 15 +max_print_speed = 300 +max_volumetric_speed = 25 +perimeter_speed = 160 +small_perimeter_speed = 25 +solid_infill_speed = 80% +top_solid_infill_speed = 60% +fill_pattern = cubic + +[print:0.20mm NORMAL @Papapiu] +inherits = *common*; *0.2mm* +fill_pattern = cubic +external_perimeter_speed = 120 +infill_speed = 160 +compatible_printers_condition = printer_model=~/(Papapiu_N1S).*/ + +[print:0.20mm FAST @Papapiu] +inherits = 0.15mm FAST @Papapiu; *0.2mm* +compatible_printers_condition = printer_model=~/(Papapiu_N1S).*/ +external_perimeter_speed = 140 +first_layer_infill_speed = 60 +first_layer_speed = 100 +gap_fill_speed = 50 +infill_speed = 230 +ironing_speed = 15 +max_print_speed = 300 +max_volumetric_speed = 25 +perimeter_speed = 160 +small_perimeter_speed = 25 +solid_infill_speed = 80% +top_solid_infill_speed = 60% + + +# Common printer preset +[printer:*common*] +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n;{if layer_num == 2 }SET_FILAMENT_SENSOR SENSOR=my_sensor ENABLE=1{endif}\n\n +between_objects_gcode = +color_change_gcode = M600 +cooling_tube_length = 5 +cooling_tube_retraction = 91.5 +default_filament_profile = "Generic PLA @Papapiu" +default_print_profile = "0.20mm NORMAL @Papapiu" +deretract_speed = 40 +end_gcode = PRINT_END +extra_loading_move = -2 +extruder_colour = "" +gcode_flavor = marlin +high_current_on_filament_swap = 0 +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z]\n +machine_limits_usage = time_estimate_only +machine_max_acceleration_e = 5000 +machine_max_acceleration_extruding = 9000 +machine_max_acceleration_retracting = 9000 +machine_max_acceleration_travel = 12000,12000 +machine_max_acceleration_x = 12000 +machine_max_acceleration_y = 12000 +machine_max_acceleration_z = 100 +machine_max_feedrate_e = 60 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 10 +machine_max_jerk_e = 5 +machine_max_jerk_x = 5 +machine_max_jerk_y = 5 +machine_max_jerk_z = 0.4 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +min_layer_height = 0.05 +nozzle_diameter = 0.4 +parking_pos_retraction = 92 +pause_print_gcode = M601 +print_host = +printer_notes = "" +remaining_times = 1 +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 1 +retract_length_toolchange = 1 +retract_lift = 0 +retract_lift_above = 0 +retract_lift_below = 0 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 40 +silent_mode = 0 +single_extruder_multi_material = 0 +start_gcode = M104 S0\nM140 S0\nPRINT_START BED={first_layer_bed_temperature[0]} EXTRUDER={first_layer_temperature[0]} +thumbnails = 64x64,400x300 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 0 +z_offset = 0 + + +[printer:*0.4nozzle*] +nozzle_diameter = 0.4 +max_layer_height = 0.3 +min_layer_height = 0.05 +retract_lift_above = 0.2 +printer_variant = 0.4 + +[printer:Papapiu N1S] +inherits = *common*; *0.4nozzle* +bed_shape = 0x0,299x0,299x300,0x300 +max_print_height = 360 +printer_model = Papapiu_N1S + + +[filament:Generic ABS @Papapiu] +filament_vendor = Generic +filament_type = ABS +bed_temperature = 105 +bridge_fan_speed = 30 +cooling = 0 +disable_fan_first_layers = 2 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +extrusion_multiplier = 0.98 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_colour = #3A80CA +filament_cooling_final_speed = 3.4 +filament_cooling_initial_speed = 2.2 +filament_cooling_moves = 4 +filament_cooling_zone_pause = 0 +filament_cost = 18 +filament_density = 1.04 +filament_deretract_speed = nil +filament_diameter = 1.75 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_speed = 0 +filament_max_volumetric_speed = 0 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "" +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_retract_layer_change = 0 +filament_retract_length = 0.7 +filament_soluble = 0 +filament_spool_weight = 0 +filament_toolchange_delay = 0 +filament_toolchange_part_fan_speed = 50 +filament_toolchange_temp = 200 +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_wipe = 1 +first_layer_bed_temperature = 108 +first_layer_temperature = 248 +full_fan_speed_layer = 0 +max_fan_speed = 30 +min_fan_speed = 20 +min_print_speed = 25 +slowdown_below_layer_time = 10 +start_filament_gcode = "" +temperature = 243 + +[filament:Generic PETG @Papapiu] +filament_vendor = Generic +filament_type = PETG +bed_temperature = 80 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +extrusion_multiplier = 0.94 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FF8000 +filament_cost = 20 +filament_density = 1.27 +filament_diameter = 1.75 +filament_enable_toolchange_part_fan = 0 +filament_enable_toolchange_temp = 0 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_speed = 0 +filament_max_volumetric_speed = 11 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "" +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_retract_layer_change = 1 +filament_retract_length = 1.2 +filament_settings_id = "" +filament_soluble = 0 +filament_spool_weight = 0 +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_wipe = 1 +first_layer_bed_temperature = 80 +first_layer_temperature = 240 +full_fan_speed_layer = 0 +max_fan_speed = 100 +min_fan_speed = 40 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "" +temperature = 235 + + +[filament:Generic PLA @Papapiu] +filament_vendor = Generic +filament_type = PLA +bed_temperature = 65 +bridge_fan_speed = 100 +cooling = 0 +disable_fan_first_layers = 2 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +extrusion_multiplier = 0.89 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_cooling_final_speed = 3.4 +filament_cooling_initial_speed = 2.2 +filament_cooling_moves = 4 +filament_cooling_zone_pause = 0 +filament_cost = 20 +filament_density = 1.24 +filament_deretract_speed = nil +filament_diameter = 1.75 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_speed = 0 +filament_max_volumetric_speed = 0 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "" +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_retract_length = 0.6 +filament_soluble = 0 +filament_spool_weight = 0 +filament_toolchange_delay = 0 +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_wipe = nil +first_layer_bed_temperature = 68 +first_layer_temperature = 205 +full_fan_speed_layer = 4 +inherits = +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 10 +slowdown_below_layer_time = 15 +start_filament_gcode = "" +temperature = 195 + +[filament:Generic TPU @Papapiu] +inherits = Generic PLA @Papapiu +bed_temperature = 30 +bridge_fan_speed = 100 +bridge_internal_fan_speed = -1 +chamber_temperature = 0 +compatible_printers = +compatible_printers_condition = +compatible_prints = +compatible_prints_condition = +cooling = 0 +disable_fan_first_layers = 2 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +external_perimeter_fan_speed = -1 +extrusion_multiplier = 0.89 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF00FF +filament_cooling_final_speed = 3.4 +filament_cooling_initial_speed = 2.2 +filament_cooling_moves = 4 +filament_cooling_zone_pause = 0 +filament_cost = 20 +filament_custom_variables = "" +filament_density = 1.24 +filament_deretract_speed = nil +filament_diameter = 1.75 +filament_dip_extraction_speed = 70 +filament_dip_insertion_speed = 33 +filament_enable_toolchange_part_fan = 0 +filament_enable_toolchange_temp = 0 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_overlap = 100% +filament_max_speed = 0 +filament_max_volumetric_speed = 5 +filament_max_wipe_tower_speed = 0 +filament_melt_zone_pause = 0 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "This is a profile for TPU95A. It was tested with Anycubic TPU95A, but most of the economy TPU filaments behave pretty similar.\n\nYou will want to optimize the temperature. Even different colors of the same brand can require 10° more or less.\n\nIf you experience clogs or grinding, reduce the maximum volumetric speed!" +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_retract_before_travel = nil +filament_retract_before_wipe = nil +filament_retract_layer_change = nil +filament_retract_length = 1 +filament_retract_lift = 0.1 +filament_retract_lift_above = nil +filament_retract_lift_below = nil +filament_retract_restart_extra = nil +filament_retract_speed = nil +filament_seam_gap = nil +filament_shrink = 100% +filament_skinnydip_distance = 31 +filament_soluble = 0 +filament_spool_weight = 0 +filament_toolchange_delay = 0 +filament_toolchange_part_fan_speed = 50 +filament_toolchange_temp = 200 +filament_type = FLEX +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_use_fast_skinnydip = 0 +filament_use_skinnydip = 0 +filament_vendor = Generic +filament_wipe = nil +filament_wipe_advanced_pigment = 0.5 +filament_wipe_extra_perimeter = nil +filament_wipe_only_crossing = nil +filament_wipe_speed = nil +first_layer_bed_temperature = 30 +first_layer_temperature = 220 +full_fan_speed_layer = 4 +max_fan_speed = 100 +max_speed_reduction = 95% +min_fan_speed = 100 +min_print_speed = 10 +slowdown_below_layer_time = 8 +start_filament_gcode = "" +temperature = 220 +top_fan_speed = -1 \ No newline at end of file diff --git a/resources/profiles/PapapiuLab/Papapiu_N1S_Bed.stl b/resources/profiles/PapapiuLab/Papapiu_N1S_Bed.stl new file mode 100644 index 0000000000000000000000000000000000000000..b1a0dd3b192b41ea0370acb308290fa112cfb4c1 GIT binary patch literal 7884 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 uQjBtr{B7TOUma1IN=<$NAeH9#2 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

KunRYs7ngcRBsK>bpKV0c_?{UoU=UH!5Jc zG=EV^hdAvUQeIYeHj(?wc4`8#65v75=yS&I>Qvvtorm|a$3`>gnXB7W)ympVSfMNyLLnij!kWLK)o@B0#-#{=Pg7KqG5?}otu6lp9pW75VqKrlL^)WF`E^rxM7%$qqkDY->6wQ#ghuY+Uyh}7Fa>lcQ| zr>r1`YI0WaqT8Ee2*HKl;e~af&s(;u!h#{eu#kFg;5pj<$NfB$*1p@L-#Enqz4rVA z*HkC$V&}T`Q!&pzqO@X~BBc*=&176s+77>(+t}L!7qLo`X`I^WjTJ9eS65ZW>yhmk z%*S#)2?+K2^y1eejY#{l2kxmLwJF9`_I~nuK!VnMc;1vjUBw^ z+f6|ku820Rp^Jc)c||qioDl_^RJWNVWFT`VRTI$n^APX9K2jNEXU?vGmrXCuNLqvd zLe_G_8>h!{q8IfoYdHU}06?!o@4`ag{P^T5t`P3^`_$K<)o^C|SI_vbyQBvcf1cu7 zA$cK_;)3H>S_}-=PcQHA0OH4>-U;BH$;zchokS6D&I_NcZVql1>8RiE=PeVrzWuqL zBWpj^(vGG^+U(>DRij@r-;RAWq`D{_`r{H&oC|x9ykzis*Fl4yFh>2n>bFIckjNBH zzbKj`4w}FTKWC2*c+cT1s5a(G-oInks~=tFbgWuCzw}r_Q)8?RTDaL|9sTvxvn=zhB|==z8D4i=R|W(i*sF=~ZhN>h*feY6B`7!#Qk3dbkwL2euEB2h<0>7>K_ z^yXlK%(X-4amJZ)Ip&_!UzdG|ly6M;Qn(2}?(cQMgaUt$`LKeSP~ zUl7zjf(O`j8p~mr>v~p^9o5S1il)W3J#QR$D z73K8&WtW&{%^S?2zoCvKV^Hh2vx-&j7}Z_`^!oxE@xI;pbRvJobsJ=W!EbB~ijQkb zSFg;%?bsd*eK>gCL8ubpd5!E`zxR0O3)^A+X9aD<9z@fZ5O;|+EYM&eUN4FxXpE$E zJR#1bQq!{X%~z=hK_RYbR?z{8B~N15Q7h@m$VfcRkJp_*MZtxh4P??X2a}Qtm-JbU zyyymSVP4$6k8i1*C-TWnvsOrtE?!X)Xs)#H(l{8(UKjNSJ~fb@iNh;vP;^!O1~j{B}DS9 z=IY(<c*0Arq$-5Mp~mmt-5x#eF<=odRj-bAvPC$xy@h2D@tFJoWC(t zq^~gY{bS-d^qXDm$>eGSw9$Ay*12xmnqTb$Swzx|%F0$v1tJqR_D`!;I}Pg|wb{|z z#>e}3yB+~sN)TvN|C+YP403RC!uC@+ z8c_CQo$LyYp3Z0kJ^naC^E2Ww49P0$oEe`iws+mFM{tj=0=9cZ~eMMzj-UW8KX=S|KBGXS5sQ5%0c@%#MOSKw0au2+1hGvwAY z8#7%)T=O^x9xsrJvhcNUoqsQ@GwmFHCRy6vyH2(9ERRq8cK>) z8KZ`!A@@I2+!L!>J? zcaIX-Zv-{XX|D|E0ED#K|F=Y(#}uJk@DJHUBj~KLR>pE#?D`V%0-7Gr{s&)r=i<`VD8J8JCgPorj%hj zL~0zhS&`AK+EEXEaLiMz&^Kn@t~u&E<|CG-8 z#KPl-4+$I7tzJRJ1h~kRwGgdH^dpdsym>$k#xJDt#vplpLz~n8_fuX>6fc>lIl?ej z%MWu*<>i`zYg+9h>v-j@Eqm0}f5RFpcn^EB%l4;k-Om~EyxN}mSb~5qsonYYYB@zT zH|$NZa(_;85+W$*ouA|_*9m8=ti~I<)VEid=#5i31^ZqSBBIjc|KuuH)jXE8^7eqI zD8kXZw=Bq1!W((+LA#cxonODc(xsku8qrCoZ65qCO-0|4{nn>U`RsdE1?UvC-g~xD z7d=57erkh6jgjK_CAxP4Dk=#-+&N}35`i$@!@G)nHd;fv$;}eDAl&T5?tmtIT&=q5li)Zz?l3k78T*8qJNgA1;_aOl=Yq&xjZhhFeRXmR6QN*M@mVzhP z0h=wG^Lu)DNdq+ZtQHRipP7!4t`9An%_q&!7qm_UeMCAO+<(Ca_Glu+yc!VZt|~7qKHNcT!tdfT02h- z8~ZT-lN0u3OX7Nx=ZjRMN|9gvm#H0`c#jdWKeWkgHU^X;Eut41ld#Rbj^LH#@Mn2* z)Mce~JtS$g))RI>w@K)4+Bd2ZkZhl=`3c05!!6&g3GS|OIpyL?|GErGMFw~TCTxKy zWY8@y9CLW>^k&(jW3Rsl>6|Y~aTm?<%;uLL=cpYQg1N?pSupN3=*&k^4|0vI_M{2o zZ%@6|jI!1fT%?K7XPwdUen(YZn*uYZgyH*+)&9O4&=~m&=WVR)j}PvNl7YeA_P3f} zK30L&t*C7Jk-wDyP;~5T#-QxmQMOIqK!#yd^y$df*`gIF<0mY7@AkTgK~UbCS~cdh zu{}^GGR20Q4S2kh==tm3O18YU16GJY>=~9f|cKH1wIRR(WqiCYrnJ44_x5wg!?+Ils7PM>pXd zJ^&1^H~E)!Gb^uTO@F%k%#=lA)-{zqZv!ta%h($IB2T+^yJwe0nCl6GzJ*2-=~?Ck zoy~>p)KY4L6VP`L(o`DVH)3F+T;eSoE*FQ{2L8>m@jYvzLLndQ&8@1AF-6HPGPKVX z>rlJ#eu|q>6XalsI5#B(IU$HybC+6n1@8&bWNBzYVl$9`J+vWaA@`=&RLf3%=z&Zr z?j@bcGqF$F!qgCkU6nqa-uL#BN3g5kbcc`o?^4cSO%s$;+3@!#?*_c94}ciJ!t@4n@(s{}9_yW)TE=oqVeWOy6vA-8CC)2ca;qE1T z{h5^k8-Yph+D7PfrlM!=$hY3{_vtBm(DhN~qF66$-an2lVg1~~6z^Zweka!w01r+> zdQ4ia1i*2bV7P7CW6e7(8cSLB30PkSU!-*l{l!1qV-cM_oru0+2`IY1ls=1iBZIvYdYg4+8-R*O+9J4aO&IqE77dL5=3FIVupb zFX9Qi&(^m=jj*3F?k!_dvwwu0Oy_+|vb$PO(m3;`B;*1#lBL5%#a(wn${Ys&5s&?$ zrdLu&R4-$@CswsSdbYg!7n5^dxGPJ^J2QWcKf^`l>|nz4tmG4aFttw$^z+PXd{;;a z8%N=o9BQNq%v8%x|FSi!c|wr7xL)rR2z;~b8+2TcM7mlfZX-RNZ2|*l6JEdZyCZx4 zk3&9oZCBZS+SfLMwxsk*EcU-L?@sFG|DWFfp8G5o5I- zCo^O8*Q2)fdn6-LEf@g35;*Xwl{#FAG~LD{6kYmEjubN%Dcb% zQD*U^S#=AZFf8y*>q4}w6Z)(M>70Qg6%AxzB%xu6)O*!y{;;Fh zQI_4h|I}aGGh2iA=mC$3XC&5Z61Wrt^U{P+H%v|CqrI(*UK+>*sKw$=cYiRr%U{95 zHoJC-jhM;e4Zll8LDw#vI%M=LI-<{Z-E;^8Zuw^#nPlTo6@hKTYfOko{}*U>nTEk? zjqT{Vks^8Ts@}{qh3-<-?kAgLp)5kAi3eC5E`6wUaTQJ!NF6+m6#tnLKdE#0@nnza NYJ;_^{(buT{{Vc+Ie!2E literal 0 HcmV?d00001 From ae8684a4a46bdaa8fb7dda03b38561ffe170cb47 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 21 Aug 2023 17:45:07 +0200 Subject: [PATCH 113/120] Added handle_legacy_composite() callback to configuration layer to support conversion of multiple configuration keys into one or multiple other configuration keys. --- src/libslic3r/Config.cpp | 3 +++ src/libslic3r/Config.hpp | 4 ++++ src/libslic3r/PrintConfig.cpp | 7 +++++++ src/libslic3r/PrintConfig.hpp | 9 +++++++++ 4 files changed, 23 insertions(+) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 51870e93d7..28224d9baf 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -797,6 +797,9 @@ ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, Fo // ignore } } + // Do legacy conversion on a completely loaded dictionary. + // Perform composite conversions, for example merging multiple keys into one key. + this->handle_legacy_composite(); return std::move(substitutions_ctxt.substitutions); } diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 28410b87dc..c0ab5266f7 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2160,6 +2160,10 @@ protected: // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). // handle_legacy() is called internally by set_deserialize(). virtual void handle_legacy(t_config_option_key &/*opt_key*/, std::string &/*value*/) const {} + // Called after a config is loaded as a whole. + // Perform composite conversions, for example merging multiple keys into one key. + // For conversion of single options, the handle_legacy() method above is called. + virtual void handle_legacy_composite() {} public: using ConfigOptionResolver::option; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3001e2924b..5ccfdf9981 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4323,6 +4323,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } } +// Called after a config is loaded as a whole. +// Perform composite conversions, for example merging multiple keys into one key. +// Don't convert single options here, implement such conversion in PrintConfigDef::handle_legacy() instead. +void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config) +{ +} + const PrintConfigDef print_config_def; DynamicPrintConfig DynamicPrintConfig::full_print_config() diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index e79e520648..ab6f1ea907 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -166,6 +166,8 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS +class DynamicPrintConfig; + // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. // Does not store the actual values, but defines default values. class PrintConfigDef : public ConfigDef @@ -174,6 +176,7 @@ public: PrintConfigDef(); static void handle_legacy(t_config_option_key &opt_key, std::string &value); + static void handle_legacy_composite(DynamicPrintConfig &config); // Array options growing with the number of extruders const std::vector& extruder_option_keys() const { return m_extruder_option_keys; } @@ -258,6 +261,12 @@ public: // handle_legacy() is called internally by set_deserialize(). void handle_legacy(t_config_option_key &opt_key, std::string &value) const override { PrintConfigDef::handle_legacy(opt_key, value); } + + // Called after a config is loaded as a whole. + // Perform composite conversions, for example merging multiple keys into one key. + // For conversion of single options, the handle_legacy() method above is called. + void handle_legacy_composite() override + { PrintConfigDef::handle_legacy_composite(*this); } }; void handle_legacy_sla(DynamicPrintConfig &config); From 2101f593dbb17b1562909b9457069e5ecf071087 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 19 Jun 2023 09:17:21 +0200 Subject: [PATCH 114/120] #10839 - Fixed volumes' translation in Object Coordinates (SPE-1789) --- src/slic3r/GUI/Selection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 9742a7466a..bc7227fa3b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -952,7 +952,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor } else { Vec3d relative_disp = displacement; - if (transformation_type.instance()) + if (transformation_type.world() && transformation_type.instance()) relative_disp = volume_data.get_instance_scale_matrix().inverse() * relative_disp; transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(relative_disp), m_cache.dragging_center); From 3c23e6d7c35acdd9b648bd749ea20a8c48669533 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 19 Jun 2023 11:57:54 +0200 Subject: [PATCH 115/120] Follow-up of 9e20d0ff173d7303bc02c4f39a025b29cb682b88 - Removal of obsolete struct VolumeCache::TransformCache --- src/slic3r/GUI/Selection.cpp | 15 +------------ src/slic3r/GUI/Selection.hpp | 41 +++++------------------------------- 2 files changed, 6 insertions(+), 50 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index bc7227fa3b..263477f20e 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -32,19 +32,6 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5 namespace Slic3r { namespace GUI { -Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) - , transform(transform) - , rotation_matrix(transform.get_rotation_matrix()) - , scale_matrix(transform.get_scaling_factor_matrix()) - , mirror_matrix(transform.get_mirror_matrix()) -{ -} - Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) : m_volume(volume_transform) , m_instance(instance_transform) @@ -953,7 +940,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor else { Vec3d relative_disp = displacement; if (transformation_type.world() && transformation_type.instance()) - relative_disp = volume_data.get_instance_scale_matrix().inverse() * relative_disp; + relative_disp = volume_data.get_instance_transform().get_scaling_factor_matrix().inverse() * relative_disp; transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(relative_disp), m_cache.dragging_center); } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 5a6fcb5e04..1e7bf113e2 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -57,46 +57,15 @@ public: private: struct VolumeCache { - private: - struct TransformCache - { - Vec3d position{ Vec3d::Zero() }; - Vec3d rotation{ Vec3d::Zero() }; - Vec3d scaling_factor{ Vec3d::Ones() }; - Vec3d mirror{ Vec3d::Ones() }; - Transform3d rotation_matrix{ Transform3d::Identity() }; - Transform3d scale_matrix{ Transform3d::Identity() }; - Transform3d mirror_matrix{ Transform3d::Identity() }; - Transform3d full_matrix{ Transform3d::Identity() }; - Geometry::Transformation transform; - - TransformCache() = default; - explicit TransformCache(const Geometry::Transformation& transform); - }; - - TransformCache m_volume; - TransformCache m_instance; - - public: VolumeCache() = default; VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); - const Vec3d& get_volume_position() const { return m_volume.position; } - const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } - const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } - const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } - const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; } - const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; } + const Geometry::Transformation& get_volume_transform() const { return m_volume; } + const Geometry::Transformation& get_instance_transform() const { return m_instance; } - const Vec3d& get_instance_position() const { return m_instance.position; } - const Vec3d& get_instance_rotation() const { return m_instance.rotation; } - const Vec3d& get_instance_scaling_factor() const { return m_instance.scaling_factor; } - const Vec3d& get_instance_mirror() const { return m_instance.mirror; } - const Transform3d& get_instance_rotation_matrix() const { return m_instance.rotation_matrix; } - const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } - const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } - const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } - const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; } + private: + Geometry::Transformation m_volume; + Geometry::Transformation m_instance; }; public: From 0f8208c83f72914324b066e75c9ee097a8a10156 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 16 Aug 2023 10:19:35 +0200 Subject: [PATCH 116/120] Fixed drop to bed command for volumes in part coordinates --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 24da753278..83dd781a41 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -356,7 +356,10 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const GLVolume* volume = selection.get_first_volume(); const double min_z = get_volume_min_z(*volume); if (!is_world_coordinates()) { - const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); + Vec3d diff = volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); + if (is_local_coordinates()) + diff = volume->get_volume_transformation().get_matrix_no_offset().inverse() * diff; + diff = m_cache.position - diff; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); @@ -942,7 +945,7 @@ void ObjectManipulation::change_position_value(int axis, double value) selection.setup_cache(); TransformationType trafo_type; trafo_type.set_relative(); - switch (get_coordinates_type()) + switch (m_coordinates_type) { case ECoordinatesType::Instance: { trafo_type.set_instance(); break; } case ECoordinatesType::Local: { trafo_type.set_local(); break; } @@ -952,7 +955,7 @@ void ObjectManipulation::change_position_value(int axis, double value) canvas->do_move(L("Set Position")); m_cache.position = position; - m_cache.position_rounded(axis) = DBL_MAX; + m_cache.position_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } From fa0abac9522f0a3efbb628ad34467b994fbfc409 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 21 Jun 2023 17:31:02 +0200 Subject: [PATCH 117/120] Optimization: measurement tries to save some reallocations --- src/libslic3r/Measure.cpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 08c17df47b..6e04acb967 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -190,6 +190,7 @@ void MeasuringImpl::update_planes() //const Halfedge_index he_orig = he; m_planes[plane_id].borders.emplace_back(); std::vector& last_border = m_planes[plane_id].borders.back(); + last_border.reserve(4); last_border.emplace_back(sm.point(sm.source(he)).cast()); //Vertex_index target = sm.target(he); const Halfedge_index he_start = he; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index c2ab108667..53cc85a4e3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -96,6 +96,8 @@ static GLModel::Geometry init_plane_data(const indexed_triangle_set& its, const { GLModel::Geometry init_data; init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_indices(3 * triangle_indices.size()); + init_data.reserve_vertices(3 * triangle_indices.size()); unsigned int i = 0; for (int idx : triangle_indices) { const Vec3f& v0 = its.vertices[its.indices[idx][0]]; @@ -1036,10 +1038,10 @@ void GLGizmoMeasure::update_if_needed() { auto update_plane_models_cache = [this](const indexed_triangle_set& its) { m_plane_models_cache.clear(); + m_plane_models_cache.resize(m_measuring->get_num_of_planes(), GLModel()); for (int idx = 0; idx < m_measuring->get_num_of_planes(); ++idx) { - m_plane_models_cache.emplace_back(GLModel()); GLModel::Geometry init_data = init_plane_data(its, m_measuring->get_plane_triangle_indices(idx)); - m_plane_models_cache.back().init_from(std::move(init_data)); + m_plane_models_cache[idx].init_from(std::move(init_data)); } }; From 3277bba8c1bf9ac14b7459693dedadd7f654acf1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 22 Jun 2023 15:33:48 +0200 Subject: [PATCH 118/120] Optimization: further improvements in measurement initialization --- src/libslic3r/Measure.cpp | 62 +++++++++++++----------- src/libslic3r/Measure.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 24 ++++++--- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 6e04acb967..5593362949 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -72,7 +72,7 @@ public: int get_num_of_planes() const; const std::vector& get_plane_triangle_indices(int idx) const; const std::vector& get_plane_features(unsigned int plane_id); - const TriangleMesh& get_mesh() const; + const indexed_triangle_set& get_its() const; private: void update_planes(); @@ -80,7 +80,7 @@ private: std::vector m_planes; std::vector m_face_to_plane; - TriangleMesh m_mesh; + indexed_triangle_set m_its; }; @@ -89,7 +89,7 @@ private: MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its) -: m_mesh(its) +: m_its(its) { update_planes(); @@ -104,14 +104,12 @@ MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its) void MeasuringImpl::update_planes() { - m_planes.clear(); - // Now we'll go through all the facets and append Points of facets sharing the same normal. // This part is still performed in mesh coordinate system. - const size_t num_of_facets = m_mesh.its.indices.size(); + const size_t num_of_facets = m_its.indices.size(); m_face_to_plane.resize(num_of_facets, size_t(-1)); - const std::vector face_normals = its_face_normals(m_mesh.its); - const std::vector face_neighbors = its_face_neighbors(m_mesh.its); + const std::vector face_normals = its_face_normals(m_its); + const std::vector face_neighbors = its_face_neighbors(m_its); std::vector facet_queue(num_of_facets, 0); int facet_queue_cnt = 0; const stl_normal* normal_ptr = nullptr; @@ -121,6 +119,10 @@ void MeasuringImpl::update_planes() return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); }; + m_planes.clear(); + m_planes.reserve(num_of_facets / 5); // empty plane data object is quite lightweight, let's save the initial reallocations + + // First go through all the triangles and fill in m_planes vector. For each "plane" // detected on the model, it will contain list of facets that are part of it. // We will also fill in m_face_to_plane, which contains index into m_planes @@ -132,7 +134,7 @@ void MeasuringImpl::update_planes() facet_queue[facet_queue_cnt ++] = seed_facet_idx; normal_ptr = &face_normals[seed_facet_idx]; m_face_to_plane[seed_facet_idx] = m_planes.size(); - m_planes.emplace_back(); + m_planes.emplace_back(); break; } if (seed_facet_idx == num_of_facets) @@ -160,16 +162,21 @@ void MeasuringImpl::update_planes() assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); // Now we will walk around each of the planes and save vertices which form the border. - SurfaceMesh sm(m_mesh.its); - for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) { - const auto& facets = m_planes[plane_id].facets; - m_planes[plane_id].borders.clear(); + const SurfaceMesh sm(m_its); + + const auto& face_to_plane = m_face_to_plane; + auto& planes = m_planes; + + tbb::parallel_for(tbb::blocked_range(0, m_planes.size()), + [&planes, &face_to_plane, &face_neighbors, &sm](const tbb::blocked_range& range) { + for (size_t plane_id = range.begin(); plane_id != range.end(); ++plane_id) { + + const auto& facets = planes[plane_id].facets; + planes[plane_id].borders.clear(); std::vector> visited(facets.size(), {false, false, false}); - - for (int face_id=0; face_id& last_border = m_planes[plane_id].borders.back(); + planes[plane_id].borders.emplace_back(); + std::vector& last_border = planes[plane_id].borders.back(); last_border.reserve(4); last_border.emplace_back(sm.point(sm.source(he)).cast()); //Vertex_index target = sm.target(he); @@ -211,7 +218,7 @@ void MeasuringImpl::update_planes() // Remember all halfedges we saw to break out of such infinite loops. boost::container::small_vector he_seen; - while ( (int)m_face_to_plane[sm.face(he)] == plane_id && he != he_orig) { + while ( (int)face_to_plane[sm.face(he)] == plane_id && he != he_orig) { he_seen.emplace_back(he); he = sm.next_around_target(he); if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end()) @@ -242,7 +249,7 @@ void MeasuringImpl::update_planes() } while (he != he_start); if (last_border.size() == 1) - m_planes[plane_id].borders.pop_back(); + planes[plane_id].borders.pop_back(); else { assert(last_border.front() == last_border.back()); last_border.pop_back(); @@ -252,8 +259,9 @@ void MeasuringImpl::update_planes() continue; // There was no failure. PLANE_FAILURE: - m_planes[plane_id].borders.clear(); - } + planes[plane_id].borders.clear(); + }}); + m_planes.shrink_to_fit(); } @@ -582,9 +590,9 @@ const std::vector& MeasuringImpl::get_plane_features(unsigned in return m_planes[plane_id].surface_features; } -const TriangleMesh& MeasuringImpl::get_mesh() const +const indexed_triangle_set& MeasuringImpl::get_its() const { - return this->m_mesh; + return this->m_its; } @@ -627,9 +635,9 @@ const std::vector& Measuring::get_plane_features(unsigned int pl return priv->get_plane_features(plane_id); } -const TriangleMesh& Measuring::get_mesh() const +const indexed_triangle_set& Measuring::get_its() const { - return priv->get_mesh(); + return priv->get_its(); } const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true }; diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index a273135ca8..70f446afd9 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -109,7 +109,7 @@ public: const std::vector& get_plane_features(unsigned int plane_id) const; // Returns the mesh used for measuring - const TriangleMesh& get_mesh() const; + const indexed_triangle_set& get_its() const; private: std::unique_ptr priv; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 53cc85a4e3..4632f2490a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -14,6 +14,8 @@ #include +#include + #include namespace Slic3r { @@ -650,7 +652,7 @@ void GLGizmoMeasure::on_render() const auto [idx, normal, point] = m_curr_feature->get_plane(); if (m_last_plane_idx != idx) { m_last_plane_idx = idx; - const indexed_triangle_set& its = m_measuring->get_mesh().its; + const indexed_triangle_set& its = m_measuring->get_its(); const std::vector& plane_triangles = m_measuring->get_plane_triangle_indices(idx); GLModel::Geometry init_data = init_plane_data(its, plane_triangles); m_plane.reset(); @@ -1039,10 +1041,18 @@ void GLGizmoMeasure::update_if_needed() auto update_plane_models_cache = [this](const indexed_triangle_set& its) { m_plane_models_cache.clear(); m_plane_models_cache.resize(m_measuring->get_num_of_planes(), GLModel()); - for (int idx = 0; idx < m_measuring->get_num_of_planes(); ++idx) { - GLModel::Geometry init_data = init_plane_data(its, m_measuring->get_plane_triangle_indices(idx)); - m_plane_models_cache[idx].init_from(std::move(init_data)); - } + + auto& plane_models_cache = m_plane_models_cache; + const auto& measuring = m_measuring; + + //for (int idx = 0; idx < m_measuring->get_num_of_planes(); ++idx) { + tbb::parallel_for(tbb::blocked_range(0, m_measuring->get_num_of_planes()), + [&plane_models_cache, &measuring, &its](const tbb::blocked_range& range) { + for (size_t idx = range.begin(); idx != range.end(); ++idx) { + GLModel::Geometry init_data = init_plane_data(its, measuring->get_plane_triangle_indices(idx)); + plane_models_cache[idx].init_from(std::move(init_data)); + } + }); }; auto do_update = [this, update_plane_models_cache](const std::vector& volumes_cache, const Selection& selection) { @@ -1061,8 +1071,8 @@ void GLGizmoMeasure::update_if_needed() } m_measuring.reset(new Measure::Measuring(composite_mesh.its)); - update_plane_models_cache(m_measuring->get_mesh().its); - m_raycaster.reset(new MeshRaycaster(std::make_shared(m_measuring->get_mesh()))); + update_plane_models_cache(m_measuring->get_its()); + m_raycaster.reset(new MeshRaycaster(std::make_shared(composite_mesh))); m_volumes_cache = volumes_cache; }; From 37a088abacb1eeea89350e044840af109e782527 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 23 Jun 2023 12:46:30 +0200 Subject: [PATCH 119/120] Optimization: Parallelize the for loops over individual PrintObjects --- src/libslic3r/Print.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9ed95dddf3..8f6c65f2f8 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -903,20 +903,29 @@ void Print::process() name_tbb_thread_pool_threads_set_locale(); BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); - for (PrintObject *obj : m_objects) - obj->make_perimeters(); - for (PrintObject *obj : m_objects) - obj->infill(); - for (PrintObject *obj : m_objects) - obj->ironing(); + + tbb::parallel_for(tbb::blocked_range(0, m_objects.size(), 1), [this](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++idx) { + m_objects[idx]->make_perimeters(); + m_objects[idx]->infill(); + m_objects[idx]->ironing(); + } + }, tbb::simple_partitioner()); + + // The following step writes to m_shared_regions, it should not run in parallel. for (PrintObject *obj : m_objects) obj->generate_support_spots(); // check data from previous step, format the error message(s) and send alert to ui + // this also has to be done sequentially. alert_when_supports_needed(); - for (PrintObject *obj : m_objects) - obj->generate_support_material(); - for (PrintObject *obj : m_objects) - obj->estimate_curled_extrusions(); + + tbb::parallel_for(tbb::blocked_range(0, m_objects.size(), 1), [this](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++idx) { + m_objects[idx]->generate_support_material(); + m_objects[idx]->estimate_curled_extrusions(); + } + }, tbb::simple_partitioner()); + if (this->set_started(psWipeTower)) { m_wipe_tower_data.clear(); m_tool_ordering.clear(); From 163f81f1d492d6a9b0870af5cb3f2818cd6e3cec Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 22 Aug 2023 15:31:26 +0200 Subject: [PATCH 120/120] Fix for checkbox tooltip --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 6576cb273b..701e584be3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -2863,7 +2863,6 @@ bool GLGizmoEmboss::revertible(const std::string &name, else ImGuiWrapper::text(name); - bool result = draw(); // render revert changes button if (changed) { ImGuiWindow *window = ImGui::GetCurrentWindow(); @@ -2876,7 +2875,7 @@ bool GLGizmoEmboss::revertible(const std::string &name, ImGui::SetTooltip("%s", undo_tooltip.c_str()); window->DC.CursorPosPrevLine.x = prev_x; // set back previous position } - return result; + return draw(); }