From fb540f50937f994d20804a79de24855faed99697 Mon Sep 17 00:00:00 2001 From: "zhou.xu" Date: Mon, 18 Mar 2024 11:20:59 +0800 Subject: [PATCH 01/70] NEW:add move and rotate gizmo in assemble view Jira: STUDIO-6545 Change-Id: I30ab8155f5288953b36cd9a301ce3596d6edc0c6 --- src/libslic3r/Model.hpp | 3 +- src/slic3r/GUI/3DScene.cpp | 16 +++++---- src/slic3r/GUI/3DScene.hpp | 12 ++++--- src/slic3r/GUI/GLCanvas3D.cpp | 33 +++++++++++++++---- src/slic3r/GUI/GLCanvas3D.hpp | 8 ++--- src/slic3r/GUI/GUI_Preview.cpp | 6 ++-- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 9 ++--- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 22 ++++++++++--- .../GUI/Gizmos/GizmoObjectManipulation.cpp | 16 +++++---- src/slic3r/GUI/Plater.cpp | 12 ++++--- src/slic3r/GUI/Selection.cpp | 17 +++++++--- 11 files changed, 105 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 62696f8c76..e942faa1c9 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1276,13 +1276,14 @@ class ModelInstance final : public ObjectBase m_assemble_transformation.set_from_transform(transform); } void set_assemble_offset(const Vec3d& offset) { m_assemble_transformation.set_offset(offset); } + void set_assemble_rotation(const Vec3d &rotation) { m_assemble_transformation.set_rotation(rotation); } void rotate_assemble(double angle, const Vec3d& axis) { m_assemble_transformation.set_rotation(m_assemble_transformation.get_rotation() + Geometry::extract_euler_angles(Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)).toRotationMatrix())); } // BBS void set_offset_to_assembly(const Vec3d& offset) { m_offset_to_assembly = offset; } - Vec3d get_offset_to_assembly() const { return m_offset_to_assembly; } + const Vec3d& get_offset_to_assembly() const { return m_offset_to_assembly; } const Vec3d& get_offset() const { return m_transformation.get_offset(); } double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index bf36690ddc..97486424d4 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -678,7 +678,7 @@ void GLVolume::set_range(double min_z, double max_z) //BBS: add outline related logic //static unsigned char stencil_data[1284][2944]; -void GLVolume::render(bool with_outline) const +void GLVolume::render(bool with_outline, const std::array& body_color) const { if (!is_active) return; @@ -880,7 +880,6 @@ void GLVolume::render(bool with_outline) const glStencilFunc(GL_NOTEQUAL, 0xff, 0xFF); glStencilMask(0x00); float scale = 1.02f; - std::array body_color = { 1.0f, 1.0f, 1.0f, 1.0f }; //red shader->set_uniform("uniform_color", body_color); shader->set_uniform("is_outline", true); @@ -1336,8 +1335,13 @@ int GLVolumeCollection::get_selection_support_threshold_angle(bool &enable_suppo } //BBS: add outline drawing logic -void GLVolumeCollection::render( - GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d &view_matrix, std::function filter_func, bool with_outline) const +void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, + bool disable_cullface, + const Transform3d & view_matrix, + std::function filter_func, + bool with_outline, + const std::array & body_color, + bool partly_inside_enable) const { GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func); if (to_render.empty()) @@ -1396,7 +1400,7 @@ void GLVolumeCollection::render( //shader->set_uniform("print_volume.xy_data", m_render_volume.data); //shader->set_uniform("print_volume.z_data", m_render_volume.zs); - if (volume.first->partly_inside) { + if (volume.first->partly_inside && partly_inside_enable) { //only partly inside volume need to be painted with boundary check shader->set_uniform("print_volume.type", static_cast(m_print_volume.type)); shader->set_uniform("print_volume.xy_data", m_print_volume.data); @@ -1427,7 +1431,7 @@ void GLVolumeCollection::render( glcheck(); //BBS: add outline related logic - volume.first->render(with_outline && volume.first->selected); + volume.first->render(with_outline && volume.first->selected, body_color); #if ENABLE_ENVIRONMENT_MAP if (use_environment_texture) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index e1f5c31650..a4bce52325 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -497,7 +497,7 @@ class GLVolume { void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } void set_offset_to_assembly(const Vec3d& offset) { m_offset_to_assembly = offset; set_bounding_boxes_as_dirty(); } - Vec3d get_offset_to_assembly() { return m_offset_to_assembly; } + const Vec3d& get_offset_to_assembly() { return m_offset_to_assembly; } int object_idx() const { return this->composite_id.object_id; } int volume_idx() const { return this->composite_id.volume_id; } @@ -523,7 +523,8 @@ class GLVolume { void set_range(double low, double high); //BBS: add outline related logic and add virtual specifier - virtual void render(bool with_outline = false) const; + virtual void render(bool with_outline = false, + const std::array &body_color = {1.0f, 1.0f, 1.0f, 1.0f} ) const; //BBS: add simple render function for thumbnail void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector>& extruder_colors) const; @@ -667,8 +668,11 @@ class GLVolumeCollection void render(ERenderType type, bool disable_cullface, const Transform3d & view_matrix, - std::function filter_func = std::function(), - bool with_outline = true) const; + std::function filter_func = std::function(), + bool with_outline = true, + const std::array& body_color = {1.0f, 1.0f, 1.0f, 1.0f}, + bool partly_inside_enable =true + ) const; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e946c8f6f9..36007d250a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -14,7 +14,7 @@ #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PresetBundle.hpp" -#include "slic3r/GUI/3DBed.hpp" + #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/BackgroundSlicingProcess.hpp" #include "slic3r/GUI/GLShader.hpp" @@ -1946,6 +1946,9 @@ void GLCanvas3D::render(bool only_init) /* assemble render*/ else if (m_canvas_type == ECanvasType::CanvasAssembleView) { //BBS: add outline logic + if (m_show_world_axes) { + m_axes.render(); + } _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); //_render_bed(!camera.is_looking_downward(), show_axes); _render_plane(); @@ -4569,8 +4572,13 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) // Move instances/volumes ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { - if (selection_mode == Selection::Instance) - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + if (selection_mode == Selection::Instance) { + if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { + model_object->instances[instance_idx]->set_assemble_offset(v->get_instance_offset()); + } else { + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + } + } else if (selection_mode == Selection::Volume) { if (model_object->volumes[volume_idx]->get_offset() != v->get_volume_offset()) { model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); @@ -4677,8 +4685,13 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { - model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); - model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { + model_object->instances[instance_idx]->set_assemble_rotation(v->get_instance_rotation()); + model_object->instances[instance_idx]->set_assemble_offset(v->get_instance_offset()); + } else { + model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + } } else if (selection_mode == Selection::Volume) { if (model_object->volumes[volume_idx]->get_rotation() != v->get_volume_rotation()) { @@ -6974,6 +6987,9 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); ECanvasType canvas_type = this->m_canvas_type; + std::array body_color = canvas_type == ECanvasType::CanvasAssembleView ? std::array({1.0f, 1.0f, 0.0f, 1.0f}) ://yellow + std::array({1.0f, 1.0f, 1.0f, 1.0f});//white + bool partly_inside_enable = canvas_type == ECanvasType::CanvasAssembleView ? false : true; if (shader != nullptr) { shader->start_using(); @@ -7009,7 +7025,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with else { return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); } - }, with_outline); + }, + with_outline, body_color, partly_inside_enable); } } else { @@ -7042,7 +7059,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with else { return true; } - }, with_outline); + }, + with_outline, body_color, partly_inside_enable); if (m_canvas_type == CanvasAssembleView && m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position() > 0) { const GLGizmosManager& gm = get_gizmos_manager(); shader->stop_using(); @@ -7908,6 +7926,7 @@ void GLCanvas3D::_render_return_toolbar() wxPostEvent(m_canvas, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); const_cast(&m_gizmos)->reset_all_states(); wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().reset_all_states(); + wxGetApp().plater()->get_view3D_canvas3D()->reload_scene(true); { GLCanvas3D * view_3d = wxGetApp().plater()->get_view3D_canvas3D(); GLToolbarItem * assembly_item = view_3d->m_assemble_view_toolbar.get_item("assembly_view"); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d85cec8012..94ca1a34fa 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -17,7 +17,7 @@ #include "GCodeViewer.hpp" #include "Camera.hpp" #include "IMToolbar.hpp" - +#include "slic3r/GUI/3DBed.hpp" #include "libslic3r/Slicing.hpp" #include @@ -53,7 +53,6 @@ namespace CustomGCode { struct Item; } namespace GUI { -class Bed3D; class PartPlateList; #if ENABLE_RETINA_GL @@ -609,8 +608,8 @@ class GLCanvas3D PrinterTechnology current_printer_technology() const; - - + bool m_show_world_axes{false}; + Bed3D::Axes m_axes; //BBS:record key botton frequency int auto_orient_count = 0; int auto_arrange_count = 0; @@ -773,6 +772,7 @@ class GLCanvas3D void set_color_by(const std::string& value); + void set_show_world_axes(bool flag) { m_show_world_axes = flag; } void refresh_camera_scene_box(); BoundingBoxf3 volumes_bounding_box() const; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index c17dcea7c7..534e14fb2d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -811,10 +811,10 @@ bool AssembleView::init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrint m_canvas->enable_assemble_view_toolbar(false); m_canvas->enable_return_toolbar(true); m_canvas->enable_separator_toolbar(false); - + m_canvas->set_show_world_axes(true); // BBS: set volume_selection_mode to Volume - m_canvas->get_selection().set_volume_selection_mode(Selection::Volume); - m_canvas->get_selection().lock_volume_selection_mode(); + //same to 3d //m_canvas->get_selection().set_volume_selection_mode(Selection::Instance); + //m_canvas->get_selection().lock_volume_selection_mode(); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index aa93d49817..22b13ef05b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -3,7 +3,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" - +#include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/MeasureUtils.hpp" @@ -1322,7 +1322,8 @@ void GLGizmoMeasure::render_dimensioning() m_imgui->text(txt); if (m_hit_different_volumes.size() < 2) { ImGui::SameLine(); - if (m_imgui->image_button(ImGui::SliderFloatEditBtnIcon, _L("Edit to scale"))) { + if (m_imgui->image_button(ImGui::SliderFloatEditBtnIcon, _L("Edit to scale")) && + wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D) { m_editing_distance = true; edit_value = curr_value; m_imgui->requires_extra_frame(); @@ -1345,7 +1346,7 @@ void GLGizmoMeasure::render_dimensioning() return; const double ratio = new_value / old_value; - wxGetApp().plater()->take_snapshot(_u8L("Scale")); + wxGetApp().plater()->take_snapshot(_u8L("Scale"), UndoRedo::SnapshotType::GizmoAction); // apply scale TransformationType type; type.set_world(); @@ -2024,7 +2025,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit distance[2] = m_buffered_distance[2]; } if (displacement.norm() > 0.0f) { - wxGetApp().plater()->take_snapshot(_u8L("MoveInMeasure"));// avoid storing another snapshot + wxGetApp().plater()->take_snapshot(_u8L("MoveInMeasure"), UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); auto llo = selection->get_mode(); if (same_model_object == false) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 8e300c4abd..d5889665c2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -59,7 +59,9 @@ std::vector GLGizmosManager::get_selectable_idxs() const std::vector out; if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) { for (size_t i = 0; i < m_gizmos.size(); ++i) - if (m_gizmos[i]->get_sprite_id() == (unsigned int)MmuSegmentation) + if (m_gizmos[i]->get_sprite_id() == (unsigned int) Move || + m_gizmos[i]->get_sprite_id() == (unsigned int) Rotate || + m_gizmos[i]->get_sprite_id() == (unsigned int) MmuSegmentation) out.push_back(i); } else { @@ -877,9 +879,21 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) } else if (is_dragging()) { switch (m_current) { - case Move: { m_parent.do_move(("Tool-Move")); break; } - case Scale: { m_parent.do_scale(("Tool-Scale")); break; } - case Rotate: { m_parent.do_rotate(("Tool-Rotate")); break; } + case Move: { + wxGetApp().plater()->take_snapshot(_u8L("Tool-Move"), UndoRedo::SnapshotType::GizmoAction); + m_parent.do_move(""); + break; + } + case Scale: { + wxGetApp().plater()->take_snapshot(_u8L("Tool-Scale"), UndoRedo::SnapshotType::GizmoAction); + m_parent.do_scale(""); + break; + } + case Rotate: { + wxGetApp().plater()->take_snapshot(_u8L("Tool-Rotate"), UndoRedo::SnapshotType::GizmoAction); + m_parent.do_rotate(""); + break; + } default: break; } diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index 7d58d5d737..577e933991 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -6,8 +6,8 @@ //#include "I18N.hpp" #include "GLGizmosManager.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" - #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" @@ -275,7 +275,8 @@ void GizmoObjectManipulation::change_position_value(int axis, double value) Selection& selection = m_glcanvas.get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); - m_glcanvas.do_move(L("Set Position")); + wxGetApp().plater()->take_snapshot(_u8L("Set Position"), UndoRedo::SnapshotType::GizmoAction); + m_glcanvas.do_move(""); m_cache.position = position; m_cache.position_rounded(axis) = DBL_MAX; @@ -303,9 +304,10 @@ void GizmoObjectManipulation::change_rotation_value(int axis, double value) selection.start_dragging(); selection.rotate( - (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), + (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); - m_glcanvas.do_rotate(L("Set Orientation")); + wxGetApp().plater()->take_snapshot(_u8L("Set Orientation"), UndoRedo::SnapshotType::GizmoAction); + m_glcanvas.do_rotate(""); m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; @@ -422,7 +424,8 @@ void GizmoObjectManipulation::reset_position_value() return; // Copy position values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - m_glcanvas.do_move(L("Reset Position")); + wxGetApp().plater()->take_snapshot(_u8L("Reset Position"), UndoRedo::SnapshotType::GizmoAction); + m_glcanvas.do_move(""); UpdateAndShow(true); } @@ -448,7 +451,8 @@ void GizmoObjectManipulation::reset_rotation_value() selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - m_glcanvas.do_rotate(L("Reset Rotation")); + wxGetApp().plater()->take_snapshot(_u8L("Reset Rotation"), UndoRedo::SnapshotType::GizmoAction); + m_glcanvas.do_rotate(""); UpdateAndShow(true); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3ae47d5e70..bcd815a7e0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3137,11 +3137,13 @@ void Plater::priv::update(unsigned int flags) // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. update_status = this->update_background_process(false, flags & (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE); - //BBS TODO reload_scene - this->view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH); - this->preview->reload_print(); - //BBS assemble view - this->assemble_view->reload_scene(false, flags); + //BBS reload_scene + if (wxGetApp().plater() && wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) { // BBS assemble view + this->assemble_view->reload_scene(false, flags); + } else { + this->view3D->reload_scene(false, flags & (unsigned int) UpdateParams::FORCE_FULL_SCREEN_REFRESH); + this->preview->reload_print(); + } if (current_panel && q->is_preview_shown()) { q->force_update_all_plate_thumbnails(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 454d5390c7..6977fa79a9 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1071,10 +1071,13 @@ void Selection::translate(const Vec3d &displacement, bool local) else if (translation_type == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH - - ensure_not_below_bed(); + if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { + ensure_not_below_bed(); + } set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); + if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); + } } // Rotate an object around one of the axes. Only one rotation component is expected to be changing. @@ -1188,7 +1191,9 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); + if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); + } } void Selection::flattening_rotate(const Vec3d& normal) @@ -1303,7 +1308,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type ensure_on_bed(); set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); + if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); + } } #if ENABLE_ENHANCED_PRINT_VOLUME_FIT From 041c90b9e3880d717ada7f38dcf52369a994cfd6 Mon Sep 17 00:00:00 2001 From: "zorro.zhang" Date: Tue, 26 Mar 2024 17:02:01 +0800 Subject: [PATCH 02/70] ENH: Optimize Some Feature 1, Show/Hide OnlineModels When Quick Switch in Perference 2, Fix New Tag show of Left Menu JIRA: none Change-Id: Ie52c5b4a178d049259f9762c05dfb3decc5b5ca9 --- resources/web/homepage2/js/home.js | 18 +++++++++--------- src/slic3r/GUI/WebViewDialog.cpp | 29 ++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/resources/web/homepage2/js/home.js b/resources/web/homepage2/js/home.js index 770562612d..8ca7ae1098 100644 --- a/resources/web/homepage2/js/home.js +++ b/resources/web/homepage2/js/home.js @@ -135,7 +135,7 @@ function HandleStudio( pVal ) else if( strCmd=="modelmall_model_advise_get") { //alert('hot'); - if( m_HotModelList!=null ) + if( m_HotModelList!=null && pVal['hits'].length>0 ) { let SS1=JSON.stringify(pVal['hits']); let SS2=JSON.stringify(m_HotModelList); @@ -143,17 +143,14 @@ function HandleStudio( pVal ) if( SS1==SS2 ) return; } - - $("#Online_Models_Bar").css('display','flex'); - $("#ForU_Models_Bar").css('display','none'); m_HotModelList=pVal['hits']; ShowStaffPick( m_HotModelList ); } else if( strCmd=="modelmall_model_customized_get") { - //alert('hot'); - if( m_ForUModelList!=null ) + //alert('For U'); + if( m_ForUModelList!=null && pVal['hits'].length>0 ) { let SS1=JSON.stringify(pVal['hits']); let SS2=JSON.stringify(m_ForUModelList); @@ -161,9 +158,6 @@ function HandleStudio( pVal ) if( SS1==SS2 ) return; } - - $("#Online_Models_Bar").css('display','none'); - $("#ForU_Models_Bar").css('display','flex'); m_ForUModelList=pVal['hits']; Show4UPick( m_ForUModelList ); @@ -574,6 +568,9 @@ function ShowStaffPick( ModelList ) return; } + $("#Online_Models_Bar").css('display','flex'); + $("#ForU_Models_Bar").css('display','none'); + let strPickHtml=''; for(let a=0;aset_str("homepage", "makerlab_clicked", "1"); wxGetApp().app_config->save(); - + wxGetApp().CallAfter([this] { ShowMenuNewTag("makerlab", "0"); }); + return; } else if (modelname.compare("online") == 0) { @@ -1329,6 +1332,7 @@ void WebViewPanel::SwitchWebContent(std::string modelname,int refresh) // conf save wxGetApp().app_config->set_str("homepage", "online_clicked", "1"); wxGetApp().app_config->save(); + wxGetApp().CallAfter([this] { ShowMenuNewTag("online", "0"); }); } else if (modelname.compare("home") == 0 || modelname.compare("recent") == 0 || modelname.compare("manual") == 0 ) { @@ -1377,10 +1381,17 @@ void WebViewPanel::OpenOneMakerlab(std::string url) { void WebViewPanel::CheckMenuNewTag() { std::string sClick = wxGetApp().app_config->get("homepage", "online_clicked"); - ShowMenuNewTag("online", sClick); + if (sClick.compare("1")==0) + ShowMenuNewTag("online", "0"); + else + ShowMenuNewTag("online", "1"); + sClick = wxGetApp().app_config->get("homepage", "makerlab_clicked"); - ShowMenuNewTag("makerlab", sClick); + if (sClick.compare("1") == 0) + ShowMenuNewTag("makerlab", "0"); + else + ShowMenuNewTag("makerlab", "1"); } void WebViewPanel::ShowMenuNewTag(std::string menuname, std::string show) @@ -1395,7 +1406,7 @@ void WebViewPanel::ShowMenuNewTag(std::string menuname, std::string show) m_Res["menu"] = menuname; - if (show != "1") + if (show.compare("1") == 0) m_Res["show"] = 1; else m_Res["show"] = 0; From e38612eba9dd3c3c8255e944fb36537074101286 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Mar 2024 14:09:45 +0800 Subject: [PATCH 03/70] ENH: Rotate around the center of the bounding box jira:none code is from PrusaSlicer,thanks for enricoturri1966 and PrusaSlicer commit dcec7a8ad40eaad72789f6dba15cafc94664119f Author: enricoturri1966 Date: Tue Feb 28 08:08:56 2023 +0100 Fixed Rotate Gizmo orientation for mirrored objects + ensure that instances and volumes always rotate as rigid body Change-Id: I359d15814a6411bbd6bcb753661388bb5e6fb513 --- src/libslic3r/Geometry.cpp | 9 ++ src/libslic3r/Geometry.hpp | 2 + src/slic3r/GUI/3DScene.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 21 ++- src/slic3r/GUI/Selection.cpp | 149 ++++++++++++++++++---- src/slic3r/GUI/Selection.hpp | 96 +++++++++----- 6 files changed, 227 insertions(+), 53 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 27b9616235..c18cd3d49e 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -500,6 +500,15 @@ void Transformation::set_rotation(Axis axis, double rotation) } } +Transform3d Transformation::get_scaling_factor_matrix() const +{ + Transform3d scale = extract_scale(m_matrix); + scale(0, 0) = std::abs(scale(0, 0)); + scale(1, 1) = std::abs(scale(1, 1)); + scale(2, 2) = std::abs(scale(2, 2)); + return scale; +} + void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { set_scaling_factor(X, scaling_factor(0)); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 8202de271a..f28eba2006 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -406,6 +406,7 @@ class Transformation void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); + Transform3d get_scaling_factor_matrix() const; const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } @@ -421,6 +422,7 @@ class Transformation void set_mirror(Axis axis, double mirror); void set_from_transform(const Transform3d& transform); + void set_matrix(const Transform3d &transform) { set_from_transform(transform); } void reset(); void reset_offset() { set_offset(Vec3d::Zero()); } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index a4bce52325..4a06a72134 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -437,7 +437,7 @@ class GLVolume { const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } - + void set_instance_transformation(const Transform3d &transform){ m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } @@ -464,6 +464,7 @@ class GLVolume { const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } + void set_volume_transformation(const Transform3d &transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index d5889665c2..0ddfc6c7e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -973,7 +973,26 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) case Rotate: { // Apply new temporary rotations - TransformationType transformation_type(TransformationType::World_Relative_Joint); + TransformationType transformation_type; + if (m_parent.get_selection().is_wipe_tower()) + transformation_type = TransformationType::World_Relative_Joint; + else { + switch (wxGetApp().obj_manipul()->get_coordinates_type()) { + default: + case ECoordinatesType::World: { + transformation_type = TransformationType::World_Relative_Joint; + break; + } + case ECoordinatesType::Instance: { + transformation_type = TransformationType::Instance_Relative_Joint; + break; + } + case ECoordinatesType::Local: { + transformation_type = TransformationType::Local_Relative_Joint; + break; + } + } + } if (evt.AltDown()) transformation_type.set_independent(); selection.rotate(get_rotation(), transformation_type); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 6977fa79a9..d26a842e74 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -23,6 +23,9 @@ #include #include +#include +#include +#include static const std::array UNIFORM_SCALE_COLOR = { 0.923f, 0.504f, 0.264f, 1.0f }; namespace Slic3r { @@ -36,7 +39,7 @@ Selection::VolumeCache::TransformCache::TransformCache() , rotation_matrix(Transform3d::Identity()) , scale_matrix(Transform3d::Identity()) , mirror_matrix(Transform3d::Identity()) - , full_matrix(Transform3d::Identity()) + , full_tran(Transform3d::Identity()) { } @@ -45,7 +48,7 @@ Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transform , rotation(transform.get_rotation()) , scaling_factor(transform.get_scaling_factor()) , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) + , full_tran(transform) { rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); @@ -1002,6 +1005,40 @@ void Selection::move_to_center(const Vec3d& displacement, bool local) this->set_bounding_boxes_dirty(); } +const std::pair Selection::get_bounding_sphere() const +{ + if (!m_bounding_sphere.has_value()) { + std::optional> *sphere = const_cast> *>(&m_bounding_sphere); + *sphere = {Vec3d::Zero(), 0.0}; + + using K = CGAL::Simple_cartesian; + using Traits = CGAL::Min_sphere_of_points_d_traits_3; + using Min_sphere = CGAL::Min_sphere_of_spheres_d; + using Point = K::Point_3; + + std::vector points; + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume & volume = *(*m_volumes)[i]; + const TriangleMesh * hull = volume.convex_hull(); + const indexed_triangle_set &its = (hull != nullptr) ? hull->its : m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh().its; + const Transform3d & matrix = volume.world_matrix(); + for (const Vec3f &v : its.vertices) { + const Vec3d vv = matrix * v.cast(); + points.push_back(Point(vv.x(), vv.y(), vv.z())); + } + } + + Min_sphere ms(points.begin(), points.end()); + const float *center_x = ms.center_cartesian_begin(); + (*sphere)->first = {*center_x, *(center_x + 1), *(center_x + 2)}; + (*sphere)->second = ms.radius(); + } + } + + return *m_bounding_sphere; +} + void Selection::setup_cache() { if (!m_valid) @@ -1141,33 +1178,60 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ }; for (unsigned int i : m_list) { + Transform3d rotation_matrix = Geometry::rotation_transform(rotation); GLVolume &v = *(*m_volumes)[i]; - if (is_single_full_instance()) - rotate_instance(v, i); - else if (is_single_volume() || is_single_modifier()) { - if (transformation_type.independent()) - v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); - else { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - v.set_volume_rotation(new_rotation); + const VolumeCache &volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform(); + if (m_mode == Instance ||is_single_full_instance()) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.instance()) { + // ensure that the instance rotates as a rigid body + Transform3d inst_rotation_matrix = inst_trafo.get_rotation_matrix(); + if (inst_trafo.is_left_handed()) { + Geometry::TransformationSVD inst_svd(inst_trafo); + inst_rotation_matrix = inst_svd.u * inst_svd.v.transpose(); + // ensure the rotation has the proper direction + if (!rotation.normalized().cwiseAbs().isApprox(Vec3d::UnitX())) rotation_matrix = rotation_matrix.inverse(); + } + + const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset(); + rotation_matrix = inst_matrix_no_offset.inverse() * inst_rotation_matrix * rotation_matrix * inst_rotation_matrix.inverse() * inst_matrix_no_offset; + + // rotate around selection center + const Vec3d inst_pivot = inst_trafo.get_matrix_no_offset().inverse() * (m_cache.rotation_pivot - inst_trafo.get_offset()); + rotation_matrix = Geometry::translation_transform(inst_pivot) * rotation_matrix * Geometry::translation_transform(-inst_pivot); } + transform_instance_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.rotation_pivot); + } + else if (!is_single_volume_or_modifier()) { + assert(transformation_type.world()); + transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.rotation_pivot); } + else { - if (m_mode == Instance) - rotate_instance(v, i); - else if (m_mode == Volume) { - // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) { - const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; - const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); - v.set_volume_offset(local_pivot + offset); + if (transformation_type.instance()) {//in object Coordinate System + // ensure that the volume rotates as a rigid body + const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix(); + rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix; + } else { + if (transformation_type.local()) { + // ensure that the volume rotates as a rigid body + const Geometry::Transformation &vol_trafo = volume_data.get_volume_transform(); + const Transform3d vol_matrix_no_offset = vol_trafo.get_matrix_no_offset(); + const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix(); + Transform3d vol_rotation_matrix = vol_trafo.get_rotation_matrix(); + if (vol_trafo.is_left_handed()) { + Geometry::TransformationSVD vol_svd(vol_trafo); + vol_rotation_matrix = vol_svd.u * vol_svd.v.transpose(); + // ensure the rotation has the proper direction + if (!rotation.normalized().cwiseAbs().isApprox(Vec3d::UnitX())) rotation_matrix = rotation_matrix.inverse(); + } + rotation_matrix = vol_matrix_no_offset.inverse() * inst_scale_matrix.inverse() * vol_rotation_matrix * rotation_matrix * + vol_rotation_matrix.inverse() * inst_scale_matrix * vol_matrix_no_offset; } - v.set_volume_rotation(new_rotation); } + transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.rotation_pivot); } } } @@ -2264,6 +2328,7 @@ void Selection::set_caches() m_cache.sinking_volumes.push_back(i); } m_cache.dragging_center = get_bounding_box().center(); + m_cache.rotation_pivot = get_bounding_sphere().first; } void Selection::do_add_volume(unsigned int volume_idx) @@ -2915,5 +2980,45 @@ void Selection::paste_objects_from_clipboard() #endif /* _DEBUG */ } +void Selection::transform_instance_relative( + GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot) +{ + assert(transformation_type.relative()); + + const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform(); + if (transformation_type.world()) { + const Vec3d inst_pivot = transformation_type.independent() && !is_from_single_instance() ? inst_trafo.get_offset() : world_pivot; + const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot); + volume.set_instance_transformation(trafo * inst_trafo.get_matrix()); + } else if (transformation_type.instance()) + volume.set_instance_transformation(inst_trafo.get_matrix() * transform); + else + assert(false); +} + +void Selection::transform_volume_relative( + GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot) +{ + assert(transformation_type.relative()); + + const Geometry::Transformation &vol_trafo = volume_data.get_volume_transform(); + const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform(); + + if (transformation_type.world()) { + const Vec3d inst_pivot = transformation_type.independent() ? vol_trafo.get_offset() : (Vec3d) (inst_trafo.get_matrix().inverse() * world_pivot); + const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset(); + const Transform3d trafo = Geometry::translation_transform(inst_pivot) * inst_matrix_no_offset.inverse() * transform * inst_matrix_no_offset * + Geometry::translation_transform(-inst_pivot); + volume.set_volume_transformation(trafo * vol_trafo.get_matrix()); + } else if (transformation_type.instance()) { + const Vec3d inst_pivot = transformation_type.independent() ? vol_trafo.get_offset() : (Vec3d) (inst_trafo.get_matrix().inverse() * world_pivot); + const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot); + volume.set_volume_transformation(trafo * vol_trafo.get_matrix()); + } else if (transformation_type.local()) + volume.set_volume_transformation(vol_trafo.get_matrix() * transform); + else + assert(false); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 2f58920c26..90cb94ae6d 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -38,45 +38,69 @@ class TransformationType enum Enum { // Transforming in a world coordinate system World = 0, + // Transforming in a instance coordinate system + Instance = 1, // Transforming in a local coordinate system - Local = 1, + Local = 2, // Absolute transformations, allowed in local coordinate system only. Absolute = 0, // Relative transformations, allowed in both local and world coordinate system. - Relative = 2, + Relative = 4, // For group selection, the transformation is performed as if the group made a single solid body. Joint = 0, // For group selection, the transformation is performed on each object independently. - Independent = 4, - - World_Relative_Joint = World | Relative | Joint, - World_Relative_Independent = World | Relative | Independent, - Local_Absolute_Joint = Local | Absolute | Joint, - Local_Absolute_Independent = Local | Absolute | Independent, - Local_Relative_Joint = Local | Relative | Joint, - Local_Relative_Independent = Local | Relative | Independent, + Independent = 8, + + World_Relative_Joint = World | Relative | Joint, + World_Relative_Independent = World | Relative | Independent, + Instance_Absolute_Joint = Instance | Absolute | Joint, + Instance_Absolute_Independent = Instance | Absolute | Independent, + Instance_Relative_Joint = Instance | Relative | Joint, + Instance_Relative_Independent = Instance | Relative | Independent, + Local_Absolute_Joint = Local | Absolute | Joint, + Local_Absolute_Independent = Local | Absolute | Independent, + Local_Relative_Joint = Local | Relative | Joint, + Local_Relative_Independent = Local | Relative | Independent, }; TransformationType() : m_value(World) {} TransformationType(Enum value) : m_value(value) {} - TransformationType& operator=(Enum value) { m_value = value; return *this; } + TransformationType &operator=(Enum value) + { + m_value = value; + return *this; + } Enum operator()() const { return m_value; } - bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } - - void set_world() { this->remove(Local); } - void set_local() { this->add(Local); } - void set_absolute() { this->remove(Relative); } - void set_relative() { this->add(Relative); } - void set_joint() { this->remove(Independent); } - void set_independent() { this->add(Independent); } - - bool world() const { return !this->has(Local); } - bool local() const { return this->has(Local); } - bool absolute() const { return !this->has(Relative); } - bool relative() const { return this->has(Relative); } - bool joint() const { return !this->has(Independent); } - bool independent() const { return this->has(Independent); } + bool has(Enum v) const { return ((unsigned int) m_value & (unsigned int) v) != 0; } + + void set_world() + { + this->remove(Instance); + this->remove(Local); + } + void set_instance() + { + this->remove(Local); + this->add(Instance); + } + void set_local() + { + this->remove(Instance); + this->add(Local); + } + void set_absolute() { this->remove(Relative); } + void set_relative() { this->add(Relative); } + void set_joint() { this->remove(Independent); } + void set_independent() { this->add(Independent); } + + bool world() const { return !this->has(Instance) && !this->has(Local); } + bool instance() const { return this->has(Instance); } + bool local() const { return this->has(Local); } + bool absolute() const { return !this->has(Relative); } + bool relative() const { return this->has(Relative); } + bool joint() const { return !this->has(Independent); } + bool independent() const { return this->has(Independent); } private: void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } @@ -125,7 +149,7 @@ class Selection Transform3d rotation_matrix; Transform3d scale_matrix; Transform3d mirror_matrix; - Transform3d full_matrix; + Geometry::Transformation full_tran; TransformCache(); explicit TransformCache(const Geometry::Transformation& transform); @@ -145,7 +169,7 @@ class Selection 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 Transform3d &get_volume_full_matrix() const { return m_volume.full_tran.get_matrix(); } const Vec3d& get_instance_position() const { return m_instance.position; } const Vec3d& get_instance_rotation() const { return m_instance.rotation; } @@ -154,7 +178,10 @@ class Selection 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 Transform3d &get_instance_full_matrix() const { return m_instance.full_tran.get_matrix(); } + + const Geometry::Transformation &get_volume_transform() const { return m_volume.full_tran; } + const Geometry::Transformation &get_instance_transform() const { return m_instance.full_tran; } }; public: @@ -199,6 +226,7 @@ class Selection ObjectIdxsToInstanceIdxsMap content; // List of ids of the volumes which are sinking when starting dragging std::vector sinking_volumes; + Vec3d rotation_pivot; }; // Volumes owned by GLCanvas3D. @@ -223,6 +251,8 @@ class Selection // Bounding box aligned to the axis of the currently selected reference system (World/Object/Part) // and transform to place and orient it in world coordinates std::optional> m_bounding_box_in_current_reference_system; + + std::optional> m_bounding_sphere; #if ENABLE_RENDER_SELECTION_CENTER GLModel m_vbo_sphere; #endif // ENABLE_RENDER_SELECTION_CENTER @@ -350,6 +380,8 @@ class Selection void start_dragging(); void stop_dragging() { m_dragging = false; } bool is_dragging() const { return m_dragging; } + // Returns the bounding sphere: first = center, second = radius + const std::pair get_bounding_sphere() const; void setup_cache(); void translate(const Vec3d& displacement, bool local = false); @@ -425,6 +457,7 @@ class Selection m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); m_bounding_box_in_current_reference_system.reset(); + m_bounding_sphere.reset(); } void render_selected_volumes() const; void render_synchronized_volumes() const; @@ -452,6 +485,11 @@ class Selection void paste_volumes_from_clipboard(); void paste_objects_from_clipboard(); + + void transform_instance_relative( + GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot); + void transform_volume_relative( + GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot); }; } // namespace GUI From afa621dcc7e20d00c006063ad7d5cfd8bac6f125 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Mar 2024 20:29:50 +0800 Subject: [PATCH 04/70] FIX: layer Times in preview showed wrong color Jira: STUDIO-6612 code is from PrusaSlicer,thanks for PrusaSlicer and enricoturri1966 commit 3ce2d3a700ef215b37faef273f54be5619b9d642 Author: enricoturri1966 Date: Wed Apr 13 15:27:46 2022 +0200 #8176 - Tech ENABLE_USED_FILAMENT_POST_PROCESS - Fixes used filament data exported to gcode file not taking in account custom gcode Change-Id: Iafceb6c88f2a8b7ce1f2a34d2b392bf7a390d52f --- src/slic3r/GUI/GCodeViewer.cpp | 45 ++++++++++++++++++++++------------ src/slic3r/GUI/GCodeViewer.hpp | 8 ++++-- src/slic3r/GUI/GUI_App.hpp | 1 + 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6f93a13895..da919eaff0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -262,31 +262,46 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move move.volumetric_rate(), move.layer_duration, move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); } -GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const +GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const { - // Input value scaled to the colors range - const float step = step_size(); - const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f - + float global_t = 0.0f; + const float step = step_size(type); + if (step > 0.0f) { + switch (type) { + default: + case EType::Linear: { + global_t = (value > min) ? (value - min) / step : 0.0f; + break; + } + case EType::Logarithmic: { + global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; + break; + } + } + } const size_t color_max_idx = Range_Colors.size() - 1; // Compute the two colors just below (low) and above (high) the input value - const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); + const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); // Compute how far the value is between the low and high colors so that they can be interpolated const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); - // Interpolate between the low and high colors to find exactly which color the input value should get - Color ret = { 0.0f, 0.0f, 0.0f, 1.0f }; - for (unsigned int i = 0; i < 3; ++i) { - ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); - } - return ret; + auto color = lerp(ColorRGBA(Range_Colors[color_low_idx]), ColorRGBA(Range_Colors[color_high_idx]), local_t); + return color.get_data(); } -float GCodeViewer::Extrusions::Range::step_size() const { - return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); +float GCodeViewer::Extrusions::Range::step_size(EType type) const { + switch (type) { + default: + case EType::Linear: { + return (max > min) ? (max - min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; + } + case EType::Logarithmic: { + return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast(Range_Colors.size()) - 1.0f) : 0.0f; + } + } } float GCodeViewer::Extrusions::Range::get_value_at_step(int step) const { @@ -3260,7 +3275,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } - case EViewType::LayerTime: { color = m_extrusions.ranges.layer_duration.get_color_at(path.layer_time); break; } + case EViewType::LayerTime: { color = m_extrusions.ranges.layer_duration.get_color_at(path.layer_time, Extrusions::Range::EType::Logarithmic); break; } case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tools.m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 7343f0c714..2a1f6aba40 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -396,6 +396,10 @@ class GCodeViewer { struct Range { + enum class EType : unsigned char { + Linear, + Logarithmic + }; float min; float max; unsigned int count; @@ -410,8 +414,8 @@ class GCodeViewer } void reset(bool log = false) { min = FLT_MAX; max = -FLT_MAX; count = 0; log_scale = log; } - float step_size() const; - Color get_color_at(float value) const; + float step_size(EType type = EType::Linear) const; + Color get_color_at(float value, EType type = EType::Linear) const; float get_value_at_step(int step) const; }; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index aa99cae9f6..4f1ec9c04e 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -8,6 +8,7 @@ #include "OpenGLManager.hpp" #include "libslic3r/Preset.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" #include "slic3r/GUI/DeviceManager.hpp" #include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/GUI/WebViewDialog.hpp" From a94dcb55ab9091b8b3de0a765b98a01c6f826797 Mon Sep 17 00:00:00 2001 From: "xun.zhang" Date: Mon, 25 Mar 2024 14:31:34 +0800 Subject: [PATCH 05/70] ENH: add some new filaments 1.Add Bambu ABS-GF,Bambu ASA-Aero,Bambu Support for PLA/PETG jira:NEW Signed-off-by: xun.zhang Change-Id: I347c953b7bf2b0be79ca3f4b6eeaf7fa9cc31b62 --- resources/info/filament_info.json | 4 +- resources/profiles/BBL.json | 70 ++++++++++++++++++- .../BBL/filament/Bambu ABS-GF @BBL A1.json | 13 ++++ .../BBL/filament/Bambu ABS-GF @BBL P1P.json | 13 ++++ .../BBL/filament/Bambu ABS-GF @BBL X1C.json | 22 ++++++ .../BBL/filament/Bambu ABS-GF @base.json | 41 +++++++++++ .../BBL/filament/Bambu ASA-Aero @BBL A1.json | 13 ++++ .../BBL/filament/Bambu ASA-Aero @BBL P1P.json | 13 ++++ .../BBL/filament/Bambu ASA-Aero @BBL X1C.json | 22 ++++++ .../BBL/filament/Bambu ASA-Aero @base.json | 62 ++++++++++++++++ ...pport For PLA-PETG @BBL A1 0.2 nozzle.json | 26 +++++++ .../Bambu Support For PLA-PETG @BBL A1.json | 25 +++++++ ...port For PLA-PETG @BBL A1M 0.2 nozzle.json | 26 +++++++ .../Bambu Support For PLA-PETG @BBL A1M.json | 25 +++++++ ...port For PLA-PETG @BBL P1P 0.2 nozzle.json | 26 +++++++ .../Bambu Support For PLA-PETG @BBL P1P.json | 25 +++++++ ...port For PLA-PETG @BBL X1C 0.2 nozzle.json | 17 +++++ .../Bambu Support For PLA-PETG @BBL X1C.json | 22 ++++++ .../Bambu Support For PLA-PETG @base.json | 53 ++++++++++++++ src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 2 + 21 files changed, 519 insertions(+), 3 deletions(-) create mode 100644 resources/profiles/BBL/filament/Bambu ABS-GF @BBL A1.json create mode 100644 resources/profiles/BBL/filament/Bambu ABS-GF @BBL P1P.json create mode 100644 resources/profiles/BBL/filament/Bambu ABS-GF @BBL X1C.json create mode 100644 resources/profiles/BBL/filament/Bambu ABS-GF @base.json create mode 100644 resources/profiles/BBL/filament/Bambu ASA-Aero @BBL A1.json create mode 100644 resources/profiles/BBL/filament/Bambu ASA-Aero @BBL P1P.json create mode 100644 resources/profiles/BBL/filament/Bambu ASA-Aero @BBL X1C.json create mode 100644 resources/profiles/BBL/filament/Bambu ASA-Aero @base.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1 0.2 nozzle.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M 0.2 nozzle.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P 0.2 nozzle.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C 0.2 nozzle.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C.json create mode 100644 resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json diff --git a/resources/info/filament_info.json b/resources/info/filament_info.json index 086080645b..8472e66462 100644 --- a/resources/info/filament_info.json +++ b/resources/info/filament_info.json @@ -12,7 +12,9 @@ "PPS", "PPS-CF", "PPA-CF", - "PPA-GF" + "PPA-GF", + "ABS-GF", + "ASA-Aero" ], "low_temp_filament": [ "PLA", diff --git a/resources/profiles/BBL.json b/resources/profiles/BBL.json index c4bd3d62ca..5341640ecc 100644 --- a/resources/profiles/BBL.json +++ b/resources/profiles/BBL.json @@ -1,7 +1,7 @@ { "name": "Bambulab", "url": "http://www.bambulab.com/Parameters/vendor/BBL.json", - "version": "01.09.00.02", + "version": "01.09.00.03", "force_update": "0", "description": "the initial version of BBL configurations", "machine_model_list": [ @@ -765,6 +765,10 @@ "name": "Bambu PLA Galaxy @base", "sub_path": "filament/Bambu PLA Galaxy @base.json" }, + { + "name": "Bambu Support For PLA/PETG @base", + "sub_path": "filament/Bambu Support For PLA-PETG @base.json" + }, { "name": "Bambu TPU 95A @base", "sub_path": "filament/Bambu TPU 95A @base.json" @@ -825,6 +829,10 @@ "name": "PolyLite ABS @base", "sub_path": "filament/PolyLite ABS @base.json" }, + { + "name": "Bambu ABS-GF @base", + "sub_path": "filament/Bambu ABS-GF @base.json" + }, { "name": "Bambu PC @base", "sub_path": "filament/Bambu PC @base.json" @@ -845,6 +853,10 @@ "name": "PolyLite ASA @base", "sub_path": "filament/PolyLite ASA @base.json" }, + { + "name": "Bambu ASA-Aero @base", + "sub_path": "filament/Bambu ASA-Aero @base.json" + }, { "name": "Generic PVA @base", "sub_path": "filament/Generic PVA @base.json" @@ -1613,6 +1625,38 @@ "name": "Bambu PLA Galaxy @BBL A1M 0.2 nozzle", "sub_path": "filament/Bambu PLA Galaxy @BBL A1M 0.2 nozzle.json" }, + { + "name": "Bambu Support For PLA/PETG @BBL X1C", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL X1C.json" + }, + { + "name": "Bambu Support For PLA/PETG @BBL X1C 0.2 nozzle", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL X1C 0.2 nozzle.json" + }, + { + "name": "Bambu Support For PLA/PETG @BBL P1P", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL P1P.json" + }, + { + "name": "Bambu Support For PLA/PETG @BBL P1P 0.2 nozzle", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL P1P 0.2 nozzle.json" + }, + { + "name": "Bambu Support For PLA/PETG @BBL A1M", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL A1M.json" + }, + { + "name": "Bambu Support For PLA/PETG @BBL A1M 0.2 nozzle", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL A1M 0.2 nozzle.json" + }, + { + "name": "Bambu Support For PLA/PETG @BBL A1", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL A1.json" + }, + { + "name": "Bambu Support For PLA/PETG @BBL A1 0.2 nozzle", + "sub_path": "filament/Bambu Support For PLA-PETG @BBL A1 0.2 nozzle.json" + }, { "name": "Bambu TPU 95A @BBL X1C", "sub_path": "filament/Bambu TPU 95A @BBL X1C.json" @@ -1901,6 +1945,18 @@ "name": "PolyLite ABS @BBL A1 0.2 nozzle", "sub_path": "filament/PolyLite ABS @BBL A1 0.2 nozzle.json" }, + { + "name": "Bambu ABS-GF @BBL X1C", + "sub_path": "filament/Bambu ABS-GF @BBL X1C.json" + }, + { + "name": "Bambu ABS-GF @BBL P1P", + "sub_path": "filament/Bambu ABS-GF @BBL P1P.json" + }, + { + "name": "Bambu ABS-GF @BBL A1", + "sub_path": "filament/Bambu ABS-GF @BBL A1.json" + }, { "name": "Bambu PC @BBL X1C", "sub_path": "filament/Bambu PC @BBL X1C.json" @@ -2029,6 +2085,18 @@ "name": "PolyLite ASA @BBL A1", "sub_path": "filament/PolyLite ASA @BBL A1.json" }, + { + "name": "Bambu ASA-Aero @BBL X1C", + "sub_path": "filament/Bambu ASA-Aero @BBL X1C.json" + }, + { + "name": "Bambu ASA-Aero @BBL P1P", + "sub_path": "filament/Bambu ASA-Aero @BBL P1P.json" + }, + { + "name": "Bambu ASA-Aero @BBL A1", + "sub_path": "filament/Bambu ASA-Aero @BBL A1.json" + }, { "name": "Generic PVA @0.2 nozzle", "sub_path": "filament/Generic PVA @0.2 nozzle.json" diff --git a/resources/profiles/BBL/filament/Bambu ABS-GF @BBL A1.json b/resources/profiles/BBL/filament/Bambu ABS-GF @BBL A1.json new file mode 100644 index 0000000000..ff5f41cae7 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ABS-GF @BBL A1.json @@ -0,0 +1,13 @@ +{ + "type": "filament", + "name": "Bambu ABS-GF @BBL A1", + "inherits": "Bambu ABS-GF @base", + "from": "system", + "setting_id": "GFSB50_02", + "instantiation": "true", + "compatible_printers": [ + "Bambu Lab A1 0.4 nozzle", + "Bambu Lab A1 0.6 nozzle", + "Bambu Lab A1 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu ABS-GF @BBL P1P.json b/resources/profiles/BBL/filament/Bambu ABS-GF @BBL P1P.json new file mode 100644 index 0000000000..37acd12522 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ABS-GF @BBL P1P.json @@ -0,0 +1,13 @@ +{ + "type": "filament", + "name": "Bambu ABS-GF @BBL P1P", + "inherits": "Bambu ABS-GF @base", + "from": "system", + "setting_id": "GFSB50_01", + "instantiation": "true", + "compatible_printers": [ + "Bambu Lab P1P 0.8 nozzle", + "Bambu Lab P1P 0.6 nozzle", + "Bambu Lab P1P 0.4 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu ABS-GF @BBL X1C.json b/resources/profiles/BBL/filament/Bambu ABS-GF @BBL X1C.json new file mode 100644 index 0000000000..89916b17b3 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ABS-GF @BBL X1C.json @@ -0,0 +1,22 @@ +{ + "type": "filament", + "name": "Bambu ABS-GF @BBL X1C", + "inherits": "Bambu ABS-GF @base", + "from": "system", + "setting_id": "GFSB50_00", + "instantiation": "true", + "compatible_printers": [ + "Bambu Lab P1S 0.4 nozzle", + "Bambu Lab P1S 0.6 nozzle", + "Bambu Lab P1S 0.8 nozzle", + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 0.6 nozzle", + "Bambu Lab X1 0.8 nozzle", + "Bambu Lab X1 Carbon 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle", + "Bambu Lab X1E 0.4 nozzle", + "Bambu Lab X1E 0.6 nozzle", + "Bambu Lab X1E 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu ABS-GF @base.json b/resources/profiles/BBL/filament/Bambu ABS-GF @base.json new file mode 100644 index 0000000000..777cf85080 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ABS-GF @base.json @@ -0,0 +1,41 @@ +{ + "type": "filament", + "name": "Bambu ABS-GF @base", + "inherits": "fdm_filament_abs", + "from": "system", + "filament_id": "GFB50", + "instantiation": "false", + "fan_cooling_layer_time": [ + "12" + ], + "fan_max_speed": [ + "30" + ], + "filament_cost": [ + "29.99" + ], + "filament_density": [ + "1.08" + ], + "filament_type": [ + "ABS-GF" + ], + "filament_flow_ratio": [ + "0.95" + ], + "filament_max_volumetric_speed": [ + "12" + ], + "filament_vendor": [ + "Bambu Lab" + ], + "overhang_fan_speed": [ + "30" + ], + "overhang_fan_threshold": [ + "10%" + ], + "slow_down_layer_time": [ + "4" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL A1.json b/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL A1.json new file mode 100644 index 0000000000..ceca584ce1 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL A1.json @@ -0,0 +1,13 @@ +{ + "type": "filament", + "name": "Bambu ASA-Aero @BBL A1", + "inherits": "Bambu ASA-Aero @base", + "from": "system", + "setting_id": "GFSB02_02", + "instantiation": "true", + "compatible_printers": [ + "Bambu Lab A1 0.4 nozzle", + "Bambu Lab A1 0.6 nozzle", + "Bambu Lab A1 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL P1P.json b/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL P1P.json new file mode 100644 index 0000000000..03be549c97 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL P1P.json @@ -0,0 +1,13 @@ +{ + "type": "filament", + "name": "Bambu ASA-Aero @BBL P1P", + "inherits": "Bambu ASA-Aero @base", + "from": "system", + "setting_id": "GFSB02_01", + "instantiation": "true", + "compatible_printers": [ + "Bambu Lab P1P 0.4 nozzle", + "Bambu Lab P1P 0.6 nozzle", + "Bambu Lab P1P 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL X1C.json b/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL X1C.json new file mode 100644 index 0000000000..2a0eaaa792 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ASA-Aero @BBL X1C.json @@ -0,0 +1,22 @@ +{ + "type": "filament", + "name": "Bambu ASA-Aero @BBL X1C", + "inherits": "Bambu ASA-Aero @base", + "from": "system", + "setting_id": "GFSB02_00", + "instantiation": "true", + "compatible_printers": [ + "Bambu Lab P1S 0.4 nozzle", + "Bambu Lab P1S 0.6 nozzle", + "Bambu Lab P1S 0.8 nozzle", + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 0.6 nozzle", + "Bambu Lab X1 0.8 nozzle", + "Bambu Lab X1 Carbon 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle", + "Bambu Lab X1E 0.4 nozzle", + "Bambu Lab X1E 0.6 nozzle", + "Bambu Lab X1E 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu ASA-Aero @base.json b/resources/profiles/BBL/filament/Bambu ASA-Aero @base.json new file mode 100644 index 0000000000..f469aeebc6 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu ASA-Aero @base.json @@ -0,0 +1,62 @@ +{ + "type": "filament", + "name": "Bambu ASA-Aero @base", + "inherits": "fdm_filament_asa", + "from": "system", + "filament_id": "GFB02", + "instantiation": "false", + "fan_cooling_layer_time": [ + "30" + ], + "fan_max_speed": [ + "50" + ], + "fan_min_speed": [ + "30" + ], + "filament_cost": [ + "49.99" + ], + "filament_density": [ + "0.99" + ], + "filament_flow_ratio": [ + "0.52" + ], + "filament_max_volumetric_speed": [ + "12" + ], + "filament_retract_before_wipe": [ + "nil" + ], + "filament_retraction_length": [ + "1.5" + ], + "filament_type": [ + "ASA-Aero" + ], + "filament_vendor": [ + "Bambu Lab" + ], + "filament_wipe_distance": [ + "5" + ], + "filament_z_hop_types": [ + "Normal Lift" + ], + "nozzle_temperature": [ + "270" + ], + "nozzle_temperature_initial_layer": [ + "270" + ], + "overhang_fan_speed": [ + "50" + ], + "reduce_fan_stop_start_freq": [ + "0" + ], + "slow_down_layer_time": [ + "5" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1 0.2 nozzle.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1 0.2 nozzle.json new file mode 100644 index 0000000000..457adaa094 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1 0.2 nozzle.json @@ -0,0 +1,26 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL A1 0.2 nozzle", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_07", + "instantiation": "true", + "filament_max_volumetric_speed": [ + "0.5" + ], + "hot_plate_temp": [ + "65" + ], + "hot_plate_temp_initial_layer": [ + "65" + ], + "textured_plate_temp": [ + "65" + ], + "textured_plate_temp_initial_layer": [ + "65" + ], + "compatible_printers": [ + "Bambu Lab A1 0.2 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1.json new file mode 100644 index 0000000000..f8c479a3fc --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1.json @@ -0,0 +1,25 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL A1", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_06", + "instantiation": "true", + "hot_plate_temp": [ + "65" + ], + "hot_plate_temp_initial_layer": [ + "65" + ], + "textured_plate_temp": [ + "65" + ], + "textured_plate_temp_initial_layer": [ + "65" + ], + "compatible_printers": [ + "Bambu Lab A1 0.4 nozzle", + "Bambu Lab A1 0.6 nozzle", + "Bambu Lab A1 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M 0.2 nozzle.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M 0.2 nozzle.json new file mode 100644 index 0000000000..29a1eaa108 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M 0.2 nozzle.json @@ -0,0 +1,26 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL A1M 0.2 nozzle", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_05", + "instantiation": "true", + "filament_max_volumetric_speed": [ + "0.5" + ], + "hot_plate_temp": [ + "65" + ], + "hot_plate_temp_initial_layer": [ + "65" + ], + "textured_plate_temp": [ + "65" + ], + "textured_plate_temp_initial_layer": [ + "65" + ], + "compatible_printers": [ + "Bambu Lab A1 mini 0.2 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M.json new file mode 100644 index 0000000000..39ddf691d2 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL A1M.json @@ -0,0 +1,25 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL A1M", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_04", + "instantiation": "true", + "hot_plate_temp": [ + "65" + ], + "hot_plate_temp_initial_layer": [ + "65" + ], + "textured_plate_temp": [ + "65" + ], + "textured_plate_temp_initial_layer": [ + "65" + ], + "compatible_printers": [ + "Bambu Lab A1 mini 0.4 nozzle", + "Bambu Lab A1 mini 0.6 nozzle", + "Bambu Lab A1 mini 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P 0.2 nozzle.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P 0.2 nozzle.json new file mode 100644 index 0000000000..d330490aa5 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P 0.2 nozzle.json @@ -0,0 +1,26 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL P1P 0.2 nozzle", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_03", + "instantiation": "true", + "filament_max_volumetric_speed": [ + "0.5" + ], + "hot_plate_temp": [ + "65" + ], + "hot_plate_temp_initial_layer": [ + "65" + ], + "textured_plate_temp": [ + "65" + ], + "textured_plate_temp_initial_layer": [ + "65" + ], + "compatible_printers": [ + "Bambu Lab P1P 0.2 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P.json new file mode 100644 index 0000000000..7352c4842a --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL P1P.json @@ -0,0 +1,25 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL P1P", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_02", + "instantiation": "true", + "hot_plate_temp": [ + "65" + ], + "hot_plate_temp_initial_layer": [ + "65" + ], + "textured_plate_temp": [ + "65" + ], + "textured_plate_temp_initial_layer": [ + "65" + ], + "compatible_printers": [ + "Bambu Lab P1P 0.4 nozzle", + "Bambu Lab P1P 0.6 nozzle", + "Bambu Lab P1P 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C 0.2 nozzle.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C 0.2 nozzle.json new file mode 100644 index 0000000000..1b6525ed85 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C 0.2 nozzle.json @@ -0,0 +1,17 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL X1C 0.2 nozzle", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_01", + "instantiation": "true", + "filament_max_volumetric_speed": [ + "0.5" + ], + "compatible_printers": [ + "Bambu Lab P1S 0.2 nozzle", + "Bambu Lab X1 0.2 nozzle", + "Bambu Lab X1 Carbon 0.2 nozzle", + "Bambu Lab X1E 0.2 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C.json new file mode 100644 index 0000000000..2b23eec51e --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @BBL X1C.json @@ -0,0 +1,22 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @BBL X1C", + "inherits": "Bambu Support For PLA/PETG @base", + "from": "system", + "setting_id": "GFSS05_00", + "instantiation": "true", + "compatible_printers": [ + "Bambu Lab P1S 0.4 nozzle", + "Bambu Lab P1S 0.6 nozzle", + "Bambu Lab P1S 0.8 nozzle", + "Bambu Lab X1 0.4 nozzle", + "Bambu Lab X1 0.6 nozzle", + "Bambu Lab X1 0.8 nozzle", + "Bambu Lab X1 Carbon 0.4 nozzle", + "Bambu Lab X1 Carbon 0.6 nozzle", + "Bambu Lab X1 Carbon 0.8 nozzle", + "Bambu Lab X1E 0.4 nozzle", + "Bambu Lab X1E 0.6 nozzle", + "Bambu Lab X1E 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json new file mode 100644 index 0000000000..4e62835862 --- /dev/null +++ b/resources/profiles/BBL/filament/Bambu Support For PLA-PETG @base.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "name": "Bambu Support For PLA/PETG @base", + "inherits": "fdm_filament_pla", + "from": "system", + "filament_id": "GFS05", + "instantiation": "false", + "fan_cooling_layer_time": [ + "80" + ], + "fan_max_speed": [ + "30" + ], + "fan_min_speed": [ + "20" + ], + "filament_cost": [ + "69.98" + ], + "filament_density": [ + "1.19" + ], + "filament_is_support": [ + "1" + ], + "filament_max_volumetric_speed": [ + "6" + ], + "filament_vendor": [ + "Bambu Lab" + ], + "hot_plate_temp": [ + "60" + ], + "hot_plate_temp_initial_layer": [ + "60" + ], + "nozzle_temperature": [ + "210" + ], + "nozzle_temperature_initial_layer": [ + "210" + ], + "slow_down_layer_time": [ + "8" + ], + "textured_plate_temp": [ + "60" + ], + "textured_plate_temp_initial_layer": [ + "60" + ] +} \ No newline at end of file diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e93d1c227d..5011765ead 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2194,7 +2194,7 @@ FilamentTempType Print::get_filament_temp_type(const std::string& filament_type) catch (const json::parse_error& err){ in.close(); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); - filament_temp_type_map[HighTempFilamentStr] = {"ABS","ASA","PC","PA","PA-CF","PA-GF","PA6-CF","PET-CF","PPS","PPS-CF","PPA-GF","PPA-CF"}; + filament_temp_type_map[HighTempFilamentStr] = {"ABS","ASA","PC","PA","PA-CF","PA-GF","PA6-CF","PET-CF","PPS","PPS-CF","PPA-GF","PPA-CF","ABS-Aero","ABS-GF"}; filament_temp_type_map[LowTempFilamentStr] = {"PLA","TPU","PLA-CF","PLA-AERO","PVA","BVOH"}; filament_temp_type_map[HighLowCompatibleFilamentStr] = { "HIPS","PETG","PE","PP","EVA","PE-CF","PP-CF","PP-GF","PHA"}; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8dcf47460b..a07f2b20e3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1504,6 +1504,8 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("PPS-CF"); def->enum_values.push_back("PPA-CF"); def->enum_values.push_back("PPA-GF"); + def->enum_values.push_back("ABS-GF"); + def->enum_values.push_back("ASA-Aero"); def->enum_values.push_back("PE"); def->enum_values.push_back("PP"); def->enum_values.push_back("EVA"); From 5938103a7d1b32fcfc370f45089319c63e23e8f0 Mon Sep 17 00:00:00 2001 From: "zhou.xu" Date: Wed, 27 Mar 2024 11:56:56 +0800 Subject: [PATCH 06/70] NEW:add "face and face assembly" function Jira: STUDIO-6545 Change-Id: I1091b8a4f27a54b26761cd369462813fb0055572 --- src/libslic3r/Geometry.cpp | 28 ++ src/libslic3r/Geometry.hpp | 1 + src/libslic3r/Measure.cpp | 54 +++- src/libslic3r/Measure.hpp | 21 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 351 ++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 14 +- src/slic3r/GUI/Selection.cpp | 48 ++++ src/slic3r/GUI/Selection.hpp | 3 + 8 files changed, 474 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index c18cd3d49e..47915817ac 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -804,5 +804,33 @@ Geometry::TransformationSVD::TransformationSVD(const Transform3d &trafo) } else skew = false; } + + Transformation mat_around_a_point_rotate(const Transformation &InMat, const Vec3d &pt, const Vec3d &axis, float rotate_theta_radian) +{ + auto xyz = InMat.get_offset(); + Transformation left; + left.set_offset(-xyz); // at world origin + auto curMat = left * InMat; + + auto qua = Eigen::Quaterniond(Eigen::AngleAxisd(rotate_theta_radian, axis)); + qua.normalize(); + Transform3d cur_matrix; + Transformation rotateMat4; + rotateMat4.set_from_transform(cur_matrix.fromPositionOrientationScale(Vec3d(0., 0., 0.), qua, Vec3d(1., 1., 1.))); + + curMat = rotateMat4 * curMat; // along_fix_axis + // rotate mat4 along fix pt + Transformation temp_world; + auto qua_world = Eigen::Quaterniond(Eigen::AngleAxisd(0, axis)); + qua_world.normalize(); + Transform3d cur_matrix_world; + temp_world.set_from_transform(cur_matrix_world.fromPositionOrientationScale(pt, qua_world, Vec3d(1., 1., 1.))); + auto temp_xyz = temp_world.get_matrix().inverse() * xyz; + auto new_pos = temp_world.get_matrix() * (rotateMat4.get_matrix() * temp_xyz); + curMat.set_offset(new_pos); + + return curMat; +} + } // namespace Geometry } // namespace Slic3r diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index f28eba2006..12e6142766 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -503,6 +503,7 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation) return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z()); } +Transformation mat_around_a_point_rotate(const Transformation& innMat, const Vec3d &pt, const Vec3d &axis, float rotate_theta_radian); } } // namespace Slicer::Geometry #endif diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 2bb775b647..2be0532da2 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -17,6 +17,14 @@ namespace Slic3r { namespace Measure { +bool get_point_projection_to_plane(const Vec3d &pt, const Vec3d &plane_origin, const Vec3d &plane_normal, Vec3d &intersection_pt) +{ + auto normal = plane_normal.normalized(); + auto BA = plane_origin - pt; + auto length = BA.dot(normal); + intersection_pt = pt + length * normal; + return true; +} constexpr double feature_hover_limit = 0.5; // how close to a feature the mouse must be to highlight it @@ -1273,6 +1281,42 @@ MeasurementResult get_measurement(const SurfaceFeature &a, const SurfaceFeature return result; } +AssemblyAction get_assembly_action(const SurfaceFeature& a, const SurfaceFeature& b) +{ + AssemblyAction action; + const SurfaceFeature &f1 = a; + const SurfaceFeature &f2 = b; + if (f1.get_type() == SurfaceFeatureType::Plane) { + action.can_set_feature_1_reverse_rotation = true; + if (f2.get_type() == SurfaceFeatureType::Plane) { + const auto [idx1, normal1, pt1] = f1.get_plane(); + const auto [idx2, normal2, pt2] = f2.get_plane(); + action.can_set_to_center_coincidence = true; + action.can_set_feature_2_reverse_rotation = true; + if (are_parallel(normal1, normal2)) { + action.can_set_to_parallel = false; + action.has_parallel_distance = true; + action.can_around_center_of_faces = true; + Vec3d proj_pt2; + Measure::get_point_projection_to_plane(pt2, pt1, normal1, proj_pt2); + action.parallel_distance = (pt2 - proj_pt2).norm(); + if ((pt2 - proj_pt2).dot(normal1) < 0) { + action.parallel_distance = -action.parallel_distance; + } + action.angle_radian = 0; + + } else { + action.can_set_to_parallel = true; + action.has_parallel_distance = false; + action.can_around_center_of_faces = false; + action.parallel_distance = 0; + action.angle_radian = std::acos(std::clamp(normal2.dot(-normal1), -1.0, 1.0)); + } + } + } + return action; +} + void SurfaceFeature::translate(const Vec3d& displacement) { switch (get_type()) { case Measure::SurfaceFeatureType::Point: { @@ -1334,14 +1378,8 @@ void SurfaceFeature::translate(const Transform3d &tran) m_pt1 = tran * m_pt1; auto world_center = m_pt1; m_pt2 = (temp_pt2 - m_pt1).normalized(); - auto get_point_projection_to_plane = [](const Vec3d& pt, const Vec3d& plane_origin, const Vec3d& plane_normal,Vec3d& intersection_pt )->bool { - auto normal = plane_normal.normalized(); - auto BA=plane_origin-pt; - auto length = BA.dot(normal); - intersection_pt = pt + length * normal; - return true; - }; - auto calc_world_radius = [&local_center, &local_normal, &tran, &world_center, &get_point_projection_to_plane](const Vec3d &pt, double &value) { + + auto calc_world_radius = [&local_center, &local_normal, &tran, &world_center](const Vec3d &pt, double &value) { Vec3d intersection_pt; get_point_projection_to_plane(pt, local_center, local_normal, intersection_pt); auto local_radius_pt = (intersection_pt - local_center).normalized() * value + local_center; diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index e749f4dd5c..80c9463640 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -25,7 +25,10 @@ enum class SurfaceFeatureType : int { Plane = 1 << 3 }; -class SurfaceFeature { +bool get_point_projection_to_plane(const Vec3d &pt, const Vec3d &plane_origin, const Vec3d &plane_normal, Vec3d &intersection_pt); + +class SurfaceFeature +{ public: SurfaceFeature(SurfaceFeatureType type, const Vec3d& pt1, const Vec3d& pt2, std::optional pt3 = std::nullopt, double value = 0.0) : m_type(type), m_pt1(pt1), m_pt2(pt2), m_pt3(pt3), m_value(value) {} @@ -182,6 +185,22 @@ struct MeasurementResult { // Returns distance/angle between two SurfaceFeatures. MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b,bool deal_circle_result =false); +struct AssemblyAction +{ + bool can_set_to_parallel{false}; + bool can_set_to_center_coincidence{false}; + bool can_set_feature_1_reverse_rotation{false}; + bool can_set_feature_2_reverse_rotation{false}; + bool can_around_center_of_faces{false}; + bool has_parallel_distance{false}; + float parallel_distance; + float angle_radian{0}; + Transform3d tran_for_parallel; + Transform3d tran_for_center_coincidence; + Transform3d tran_for_reverse_rotation; +}; +AssemblyAction get_assembly_action(const SurfaceFeature &a, const SurfaceFeature &b); + inline Vec3d edge_direction(const Vec3d& from, const Vec3d& to) { return (to - from).normalized(); } inline Vec3d edge_direction(const std::pair& e) { return edge_direction(e.first, e.second); } inline Vec3d edge_direction(const SurfaceFeature& edge) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 22b13ef05b..5b37db21b4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -445,20 +445,6 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) update_measurement_result(); m_imgui->set_requires_extra_frame(); - - const Measure::MeasurementResult &measure = m_measurement_result; - m_distance = Vec3d::Zero(); - if (measure.distance_xyz.has_value() && measure.distance_xyz->norm() > EPSILON) { - m_distance = *measure.distance_xyz; - } - else if (measure.distance_infinite.has_value()) { - m_distance = measure.distance_infinite->to - measure.distance_infinite->from; - } else if (measure.distance_strict.has_value()) { - m_distance = measure.distance_strict->to - measure.distance_strict->from; - } - if (wxGetApp().app_config->get("use_inches") == "1") - m_distance = GizmoObjectManipulation::mm_to_in * m_distance; - m_buffered_distance = m_distance; return true; } else @@ -497,11 +483,11 @@ void GLGizmoMeasure::data_changed(bool is_serializing) { wxBusyCursor wait; - if (m_pending_scale) { + if (m_pending_scale > 0) { update_if_needed(); register_single_mesh_pick(); update_measurement_result(); - m_pending_scale = false; + m_pending_scale --; } else { m_parent.toggle_selected_volume_visibility(true); @@ -1369,7 +1355,7 @@ void GLGizmoMeasure::render_dimensioning() if (m_selected_features.second.feature.has_value()) update_feature_by_tran(*m_selected_features.second.feature); // update measure on next call to data_changed() - m_pending_scale = true; + m_pending_scale = 1; }; auto action_exit = [this]() { @@ -2010,10 +1996,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit } m_imgui->disabled_end(); if (m_last_active_item_imgui != current_active_id && m_hit_different_volumes.size() == 2) { - auto selection = const_cast(&m_parent.get_selection()); - selection->setup_cache(); Vec3d displacement = Vec3d::Zero(); - auto v = m_hit_different_volumes[1]; if (std::abs(m_buffered_distance[0] - distance[0]) > EPSILON) { displacement[0] = m_buffered_distance[0] - distance[0]; distance[0] = m_buffered_distance[0]; @@ -2025,18 +2008,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit distance[2] = m_buffered_distance[2]; } if (displacement.norm() > 0.0f) { - wxGetApp().plater()->take_snapshot(_u8L("MoveInMeasure"), UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot - selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); - auto llo = selection->get_mode(); - if (same_model_object == false) { - selection->translate(v->object_idx(), v->instance_idx(), displacement); - } else { - selection->translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement); - } - wxGetApp().plater()->canvas3D()->do_move(""); - update_single_mesh_pick(v); - update_feature_by_tran(*m_selected_features.second.feature); - m_pending_scale = true; + set_distance(same_model_object, displacement); } } }; @@ -2090,6 +2062,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit ImGui::PopID(); } } + if (m_distance.norm() >0.01) { add_edit_distance_xyz_box(m_distance); } @@ -2099,7 +2072,84 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit add_strings_row_to_table(*m_imgui, " ", ImGuiWrapper::COL_BAMBU, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); }*/ ImGui::EndTable(); + if (m_hit_different_volumes.size() == 2) { + ImGui::Separator(); + auto &action = m_assembly_action; + auto set_to_parallel_size = m_imgui->calc_button_size(_L("Parallel")).x; + auto set_to_center_coincidence_size = m_imgui->calc_button_size(_L("Center coincidence")).x; + auto feature_text_size = m_imgui->calc_button_size(_L("Featue 1")).x + m_imgui->calc_button_size(":").x; + auto set_to_reverse_rotation_size = m_imgui->calc_button_size(_L("Reverse rotation")).x; + auto rotate_around_center_size = m_imgui->calc_button_size(_L("Rotate around center:")).x; + auto parallel_distance_size = m_imgui->calc_button_size(_L("Parallel_distance:")).x; + //set_feature_1 + if (action.can_set_feature_1_reverse_rotation) { + m_imgui->text(_L("Featue 1") + ":"); + { + ImGui::SameLine(feature_text_size + space_size); + ImGui::PushItemWidth(set_to_reverse_rotation_size); + if (m_imgui->button(_L("Reverse rotation"))) { + set_to_reverse_rotation(same_model_object, 0); + } + //ImGui::SameLine(set_to_reverse1_rotation_size + 2 * space_size); + } + ImGui::Separator(); + } + + m_imgui->text(_L("Featue 2") + ":"); + ImGui::SameLine(feature_text_size + space_size); + m_imgui->disabled_begin(!action.can_set_feature_2_reverse_rotation); + { + ImGui::PushItemWidth(set_to_reverse_rotation_size); + ImGui::PushID("Featue2"); + if (m_imgui->button(_L("Reverse rotation"))) { + set_to_reverse_rotation(same_model_object, 1); + } + ImGui::PopID(); + } + m_imgui->disabled_end(); + + m_imgui->disabled_begin(!action.can_set_to_parallel); + { + if (m_imgui->button(_L("Parallel"))) { + set_to_parallel(same_model_object); + } + ImGui::SameLine(set_to_parallel_size + space_size *2); + } + m_imgui->disabled_end(); + + m_imgui->disabled_begin(!(action.can_set_to_center_coincidence)); + { + ImGui::PushItemWidth(set_to_center_coincidence_size); + if (m_imgui->button(_L("Center coincidence"))) { + set_to_center_coincidence(same_model_object); + } + } + m_imgui->disabled_end(); + + if (action.has_parallel_distance) { + m_imgui->text(_u8L("Parallel_distance:")); + ImGui::SameLine(parallel_distance_size + space_size); + ImGui::PushItemWidth(input_size_max); + ImGui::BBLInputDouble("##parallel_distance_z", &m_buffered_parallel_distance, 0.0f, 0.0f, "%.2f"); + if (m_last_active_item_imgui != current_active_id && std::abs(m_buffered_parallel_distance - action.parallel_distance) > EPSILON) { + set_parallel_distance(same_model_object, m_buffered_parallel_distance); + } + } + if (action.can_around_center_of_faces) { + m_imgui->text(_u8L("Rotate around center:")); + ImGui::SameLine(rotate_around_center_size + space_size); + ImGui::PushItemWidth(input_size_max); + ImGui::BBLInputDouble("##rotate_around_center", &m_buffered_around_center, 0.0f, 0.0f, "%.2f"); + if (m_last_active_item_imgui != current_active_id && std::abs(m_buffered_around_center) > EPSILON) { + set_to_around_center_of_faces(same_model_object, m_buffered_around_center); + m_buffered_around_center = 0; + } + ImGui::SameLine(rotate_around_center_size + space_size + input_size_max+ space_size / 2.0f); + m_imgui->text(_L("°")); + } + } } + render_input_window_warning(same_model_object); ImGui::Separator(); @@ -2126,6 +2176,17 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit ImGuiWrapper::pop_toolbar_style(); } +void GLGizmoMeasure::render_input_window_warning(bool same_model_object) +{ + if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D) { + if (m_hit_different_volumes.size() == 2) { + if (same_model_object == false) { + m_imgui->text(_L("Warning") + ": " + _L("Due to ensuer_on_bed, assembly between \ndifferent objects may not be correct in 3D view.\n It is recommended to assemble them together.")); + } + } + } +} + void GLGizmoMeasure::remove_selected_sphere_raycaster(int id) { reset_gripper_pick(id == SEL_SPHERE_1_ID ? GripperType::SPHERE_1 : GripperType::SPHERE_2); @@ -2133,10 +2194,29 @@ void GLGizmoMeasure::remove_selected_sphere_raycaster(int id) void GLGizmoMeasure::update_measurement_result() { - if (!m_selected_features.first.feature.has_value()) + if (!m_selected_features.first.feature.has_value()) { m_measurement_result = Measure::MeasurementResult(); - else if (m_selected_features.second.feature.has_value()) - m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature,true); + m_assembly_action = Measure::AssemblyAction(); + } + else if (m_selected_features.second.feature.has_value()) { + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature, true); + m_assembly_action = Measure::get_assembly_action(*m_selected_features.first.feature, *m_selected_features.second.feature); + //update buffer + const Measure::MeasurementResult &measure = m_measurement_result; + m_distance = Vec3d::Zero(); + if (measure.distance_xyz.has_value() && measure.distance_xyz->norm() > EPSILON) { + m_distance = *measure.distance_xyz; + } else if (measure.distance_infinite.has_value()) { + m_distance = measure.distance_infinite->to - measure.distance_infinite->from; + } else if (measure.distance_strict.has_value()) { + m_distance = measure.distance_strict->to - measure.distance_strict->from; + } + if (wxGetApp().app_config->get("use_inches") == "1") m_distance = GizmoObjectManipulation::mm_to_in * m_distance; + m_buffered_distance = m_distance; + if (m_assembly_action.has_parallel_distance) { + m_buffered_parallel_distance = m_assembly_action.parallel_distance; + } + } else if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle) m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, Measure::SurfaceFeature(std::get<0>(m_selected_features.first.feature->get_circle()))); } @@ -2366,5 +2446,206 @@ void GLGizmoMeasure::update_feature_by_tran(Measure::SurfaceFeature &feature) } } +void GLGizmoMeasure::set_distance(bool same_model_object, const Vec3d &displacement, bool take_shot) +{ + if (m_hit_different_volumes.size() == 2 && displacement.norm() > 0.0f) { + auto v = m_hit_different_volumes[1]; + auto selection = const_cast(&m_parent.get_selection()); + selection->setup_cache(); + if (take_shot) { + wxGetApp().plater()->take_snapshot("MoveInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot + } + selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); + m_pending_scale ++; + if (same_model_object == false) { + selection->translate(v->object_idx(), v->instance_idx(), displacement); + wxGetApp().plater()->canvas3D()->do_move(""); + register_single_mesh_pick(); + } else { + selection->translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement); + wxGetApp().plater()->canvas3D()->do_move(""); + update_single_mesh_pick(v); + } + update_feature_by_tran(*m_selected_features.second.feature); + } +} + +void GLGizmoMeasure::set_to_parallel(bool same_model_object, bool take_shot) +{ + if (m_hit_different_volumes.size() == 2) { + auto &action = m_assembly_action; + auto v = m_hit_different_volumes[1]; + auto selection = const_cast(&m_parent.get_selection()); + selection->setup_cache(); + if (take_shot) { + wxGetApp().plater()->take_snapshot("RotateInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot + } + selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); + const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane(); + const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane(); + if (abs(normal1.dot(normal2) < 1 - 1e-3)) { + m_pending_scale ++; + Vec3d axis; + double angle; + Matrix3d rotation_matrix; + Geometry::rotation_from_two_vectors(normal2, -normal1, axis, angle, &rotation_matrix); + Transform3d r_m = (Transform3d) rotation_matrix; + if (same_model_object == false) { + auto new_rotation_tran = r_m * v->get_instance_transformation().get_rotation_matrix(); + Vec3d rotation = Geometry::extract_euler_angles(new_rotation_tran); + v->set_instance_rotation(rotation); + selection->rotate(v->object_idx(), v->instance_idx(), v->get_instance_transformation().get_matrix()); + wxGetApp().plater()->canvas3D()->do_rotate(""); + register_single_mesh_pick(); + } else { + Geometry::Transformation world_tran(v->world_matrix()); + auto new_tran = r_m * world_tran.get_rotation_matrix(); + Transform3d volume_rotation_tran = v->get_instance_transformation().get_rotation_matrix().inverse() * new_tran; + Vec3d rotation = Geometry::extract_euler_angles(volume_rotation_tran); + v->set_volume_rotation(rotation); + selection->rotate(v->object_idx(), v->instance_idx(), v->volume_idx(), v->get_volume_transformation().get_matrix()); + wxGetApp().plater()->canvas3D()->do_rotate(""); + update_single_mesh_pick(v); + } + update_feature_by_tran(*m_selected_features.second.feature); + } + } +} + +void GLGizmoMeasure::set_to_reverse_rotation(bool same_model_object, int feature_index) +{ + if (m_hit_different_volumes.size() == 2 && feature_index < 2) { + auto &action = m_assembly_action; + auto v = m_hit_different_volumes[feature_index]; + auto selection = const_cast(&m_parent.get_selection()); + selection->setup_cache(); + wxGetApp().plater()->take_snapshot("ReverseRotateInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot + + selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); + m_pending_scale = 1; + Vec3d plane_normal,plane_center; + if (feature_index ==0) {//feature 1 + const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane(); + plane_normal = normal1; + plane_center = pt1; + } + else { // feature 2 + const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane(); + plane_normal = normal2; + plane_center = pt2; + } + + Vec3d dir(1,0,0); + float eps = 1e-2; + if ((plane_normal - dir).norm() < 1e-2) { + dir = Vec3d(0, 1, 0); + } + Vec3d new_pt = plane_center + dir; + Vec3d intersection_pt; + Measure::get_point_projection_to_plane(new_pt, plane_center, plane_normal, intersection_pt); + Vec3d axis = (intersection_pt - plane_center).normalized(); + if (same_model_object == false) { + Geometry::Transformation inMat(v->get_instance_transformation()); + Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, axis, PI); + selection->rotate(v->object_idx(), v->instance_idx(), outMat.get_matrix()); + wxGetApp().plater()->canvas3D()->do_rotate(""); + register_single_mesh_pick(); + } else { + Geometry::Transformation inMat(v->world_matrix()); + Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, axis, PI); + Transform3d volume_tran = v->get_instance_transformation().get_matrix().inverse() * outMat.get_matrix(); + selection->rotate(v->object_idx(), v->instance_idx(), v->volume_idx(), volume_tran); + wxGetApp().plater()->canvas3D()->do_rotate(""); + update_single_mesh_pick(v); + } + if (feature_index == 0) { // feature 1 + update_feature_by_tran(*m_selected_features.first.feature); + } + else {// feature 2 + update_feature_by_tran(*m_selected_features.second.feature); + } + } +} + +void GLGizmoMeasure::set_to_around_center_of_faces(bool same_model_object, float rotate_degree) +{ + if (m_hit_different_volumes.size() == 2 ) { + auto &action = m_assembly_action; + auto v = m_hit_different_volumes[1]; + auto selection = const_cast(&m_parent.get_selection()); + selection->setup_cache(); + wxGetApp().plater()->take_snapshot("ReverseRotateInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot + + selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); + m_pending_scale = 1; + + auto radian = Geometry::deg2rad(rotate_degree); + Vec3d plane_normal, plane_center; + const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane(); + plane_normal = normal2; + plane_center = pt2; + + if (same_model_object == false) { + Geometry::Transformation inMat(v->get_instance_transformation()); + Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, plane_normal, radian); + selection->rotate(v->object_idx(), v->instance_idx(), outMat.get_matrix()); + wxGetApp().plater()->canvas3D()->do_rotate(""); + register_single_mesh_pick(); + } else { + Geometry::Transformation inMat(v->world_matrix()); + Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, plane_normal, radian); + Transform3d volume_tran = v->get_instance_transformation().get_matrix().inverse() * outMat.get_matrix(); + selection->rotate(v->object_idx(), v->instance_idx(), v->volume_idx(), volume_tran); + wxGetApp().plater()->canvas3D()->do_rotate(""); + update_single_mesh_pick(v); + } + update_feature_by_tran(*m_selected_features.second.feature); + } +} + +void GLGizmoMeasure::set_to_center_coincidence(bool same_model_object) { + auto v = m_hit_different_volumes[1]; + wxGetApp().plater()->take_snapshot("RotateThenMoveInMeasure", UndoRedo::SnapshotType::GizmoAction); + set_to_parallel(same_model_object, false); + + const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane(); + const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane(); + set_distance(same_model_object, pt1 - pt2, false); + m_set_center_coincidence = true; +} + +void GLGizmoMeasure::set_parallel_distance(bool same_model_object, float dist) +{ + if (m_hit_different_volumes.size() == 2 && abs(dist) >= 0.0f) { + auto v = m_hit_different_volumes[1]; + auto selection = const_cast(&m_parent.get_selection()); + selection->setup_cache(); + + wxGetApp().plater()->take_snapshot("SetParallelDistanceInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot + + selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance); + m_pending_scale = 1; + + const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane(); + const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane(); + Vec3d proj_pt2; + Measure::get_point_projection_to_plane(pt2, pt1, normal1, proj_pt2); + auto new_pt2 = proj_pt2 + normal1 * dist; + + Vec3d displacement = new_pt2 - pt2; + + if (same_model_object == false) { + selection->translate(v->object_idx(), v->instance_idx(), displacement); + wxGetApp().plater()->canvas3D()->do_move(""); + register_single_mesh_pick(); + } else { + selection->translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement); + wxGetApp().plater()->canvas3D()->do_move(""); + update_single_mesh_pick(v); + } + update_feature_by_tran(*m_selected_features.second.feature); + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 2a991f00e9..123484f064 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -82,7 +82,7 @@ class GLGizmoMeasure : public GLGizmoBase EMode m_mode{ EMode::FeatureSelection }; Measure::MeasurementResult m_measurement_result; - + Measure::AssemblyAction m_assembly_action; std::map> m_mesh_measure_map; std::shared_ptr m_curr_measuring{nullptr}; @@ -127,6 +127,8 @@ class GLGizmoMeasure : public GLGizmoBase unsigned int m_last_active_item_imgui{0}; Vec3d m_buffered_distance; Vec3d m_distance; + double m_buffered_parallel_distance{0}; + double m_buffered_around_center{0}; // used to keep the raycasters for point/center spheres //std::vector> m_selected_sphere_raycasters; std::optional m_curr_feature; @@ -143,7 +145,8 @@ class GLGizmoMeasure : public GLGizmoBase KeyAutoRepeatFilter m_shift_kar_filter; SelectedFeatures m_selected_features; - bool m_pending_scale{ false }; + int m_pending_scale{ 0 }; + bool m_set_center_coincidence{false}; bool m_editing_distance{ false }; bool m_is_editing_distance_first_frame{ true }; @@ -186,6 +189,7 @@ class GLGizmoMeasure : public GLGizmoBase virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; + void render_input_window_warning(bool same_model_object); void remove_selected_sphere_raycaster(int id); void update_measurement_result(); @@ -202,6 +206,12 @@ class GLGizmoMeasure : public GLGizmoBase Measure::Measuring* get_measuring_of_mesh(GLVolume *v, Transform3d &tran); void update_world_plane_features(Measure::Measuring *cur_measuring, Measure::SurfaceFeature &feautre); void update_feature_by_tran(Measure::SurfaceFeature & feature); + void set_distance(bool same_model_object, const Vec3d &displacement, bool take_shot = true); + void set_to_parallel(bool same_model_object, bool take_shot = true); + void set_to_reverse_rotation(bool same_model_object,int feature_index); + void set_to_around_center_of_faces(bool same_model_object,float rotate_degree); + void set_to_center_coincidence(bool same_model_object); + void set_parallel_distance(bool same_model_object,float dist); private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index d26a842e74..9e12147b95 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1635,6 +1635,54 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, un this->set_bounding_boxes_dirty(); } +void Selection::rotate(unsigned int object_idx, unsigned int instance_idx, const Transform3d &overwrite_tran) +{ + if (!m_valid) return; + + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (v.object_idx() == (int) object_idx && v.instance_idx() == (int) instance_idx) { + v.set_instance_transformation(overwrite_tran); + } + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) break; + + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) continue; + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int) m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) break; + + if (done.find(j) != done.end()) continue; + + GLVolume &v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx || v.instance_idx() != (int) instance_idx) + continue; + + v.set_instance_transformation(overwrite_tran); + done.insert(j); + } + } + this->set_bounding_boxes_dirty(); +} +void Selection::rotate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Transform3d &overwrite_tran) +{ + if (!m_valid) return; + + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (v.object_idx() == (int) object_idx && v.instance_idx() == (int) instance_idx && v.volume_idx() == (int) volume_idx) { + v.set_volume_transformation(overwrite_tran); + } + } + this->set_bounding_boxes_dirty(); +} + //BBS: add partplate related logic void Selection::notify_instance_update(int object_idx, int instance_idx) { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 90cb94ae6d..09189e33a8 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -399,6 +399,9 @@ class Selection void translate(unsigned int object_idx, const Vec3d& displacement); void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); void translate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Vec3d &displacement); + + void rotate(unsigned int object_idx, unsigned int instance_idx, const Transform3d &overwrite_tran); + void rotate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Transform3d &overwrite_tran); //BBS: add partplate related logic void notify_instance_update(int object_idx, int instance_idx); // BBS From 771c7adbf7f2a0a3e29b62b9f1b8046a035f3fa9 Mon Sep 17 00:00:00 2001 From: "zhou.xu" Date: Wed, 27 Mar 2024 13:03:53 +0800 Subject: [PATCH 07/70] FIX:GLWipeTowerVolume's render should inherit parent class Jira: STUDIO-6545 Change-Id: Iee9a7e7cc93785e736e56760640c8315af472c6a --- src/slic3r/GUI/3DScene.cpp | 2 +- src/slic3r/GUI/3DScene.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 97486424d4..6415add99f 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1016,7 +1016,7 @@ GLWipeTowerVolume::GLWipeTowerVolume(const std::vector>& co m_colors = colors; } -void GLWipeTowerVolume::render(bool with_outline) const +void GLWipeTowerVolume::render(bool with_outline,const std::array &body_color) const { if (!is_active) return; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 4a06a72134..2f0dd47b87 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -560,7 +560,7 @@ class GLVolume { class GLWipeTowerVolume : public GLVolume { public: GLWipeTowerVolume(const std::vector>& colors); - virtual void render(bool with_outline = false) const; + virtual void render(bool with_outline = false, const std::array &body_color = {1.0f, 1.0f, 1.0f, 1.0f}) const; std::vector iva_per_colors; bool IsTransparent(); From f804a44489e9011154db08e0f286121c17928c78 Mon Sep 17 00:00:00 2001 From: "zhou.xu" Date: Wed, 27 Mar 2024 15:28:07 +0800 Subject: [PATCH 08/70] NEW:add measure gizmo in assemble view Jira: STUDIO-6545 Change-Id: I83b85f26305754c99088abb81fe568619151d32f --- src/libslic3r/Model.hpp | 1 + src/slic3r/GUI/GLCanvas3D.cpp | 4 +++- src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_Preview.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 16 ++++++++++++++-- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 1 + src/slic3r/GUI/Plater.cpp | 3 +++ src/slic3r/GUI/Selection.cpp | 3 +++ 8 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e942faa1c9..808e023fc0 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1275,6 +1275,7 @@ class ModelInstance final : public ObjectBase m_assemble_initialized = true; m_assemble_transformation.set_from_transform(transform); } + const Vec3d& get_assemble_offset() {return m_assemble_transformation.get_offset(); } void set_assemble_offset(const Vec3d& offset) { m_assemble_transformation.set_offset(offset); } void set_assemble_rotation(const Vec3d &rotation) { m_assemble_transformation.set_rotation(rotation); } void rotate_assemble(double angle, const Vec3d& axis) { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 36007d250a..b79ef70104 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4574,7 +4574,9 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) if (model_object != nullptr) { if (selection_mode == Selection::Instance) { if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { - model_object->instances[instance_idx]->set_assemble_offset(v->get_instance_offset()); + if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) { + model_object->instances[instance_idx]->set_assemble_offset(v->get_instance_offset()); + } } else { model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 94ca1a34fa..84d4170467 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -711,6 +711,7 @@ class GLCanvas3D bool init(); void post_event(wxEvent &&event); + float get_explosion_ratio() { return m_explosion_ratio; } void reset_explosion_ratio() { m_explosion_ratio = 1.0; } void on_change_color_mode(bool is_dark, bool reinit = true); const bool get_dark_mode_status() { return m_is_dark; } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 534e14fb2d..46b799ad56 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -811,7 +811,7 @@ bool AssembleView::init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrint m_canvas->enable_assemble_view_toolbar(false); m_canvas->enable_return_toolbar(true); m_canvas->enable_separator_toolbar(false); - m_canvas->set_show_world_axes(true); + //m_canvas->set_show_world_axes(true);//wait for GitHub users to see if they have this requirement // BBS: set volume_selection_mode to Volume //same to 3d //m_canvas->get_selection().set_volume_selection_mode(Selection::Instance); //m_canvas->get_selection().lock_volume_selection_mode(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 5b37db21b4..5dababa2e7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -575,7 +575,12 @@ void GLGizmoMeasure::on_set_state() std::string GLGizmoMeasure::on_get_name() const { if (!on_is_activable() && m_state == EState::Off) { - return _u8L("Measure") + ":\n" + _u8L("Please select at least one object."); + if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) { + return _u8L("Measure") + ":\n" + _u8L("Please confirm explosion ratio = 1,and please select at least one object"); + } + else { + return _u8L("Measure") + ":\n" + _u8L("Please select at least one object."); + } } else { return _u8L("Measure"); } @@ -584,7 +589,14 @@ std::string GLGizmoMeasure::on_get_name() const bool GLGizmoMeasure::on_is_activable() const { const Selection& selection = m_parent.get_selection(); - return selection.volumes_count()>0; + if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) { + if (abs(m_parent.get_explosion_ratio() - 1.0f) < 1e-2 && selection.volumes_count() > 0) { + return true; + } + return false; + } else { + return selection.volumes_count() > 0; + } } void GLGizmoMeasure::init_circle_glmodel(GripperType gripper_type, const Measure::SurfaceFeature &feature, CircleGLModel &circle_gl_model,float inv_zoom) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 0ddfc6c7e6..ce55c95125 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -61,6 +61,7 @@ std::vector GLGizmosManager::get_selectable_idxs() const for (size_t i = 0; i < m_gizmos.size(); ++i) if (m_gizmos[i]->get_sprite_id() == (unsigned int) Move || m_gizmos[i]->get_sprite_id() == (unsigned int) Rotate || + m_gizmos[i]->get_sprite_id() == (unsigned int) Measure || m_gizmos[i]->get_sprite_id() == (unsigned int) MmuSegmentation) out.push_back(i); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bcd815a7e0..a9f3521c6c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2943,6 +2943,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) //BBS wxGLCanvas* assemble_canvas = assemble_view->get_wxglcanvas(); if (wxGetApp().is_editor()) { + assemble_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent &) { update(); }); + assemble_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent &) { update(); }); + assemble_canvas->Bind(EVT_GLTOOLBAR_FILLCOLOR, [q](IntEvent& evt) { q->fill_color(evt.get_data()); }); assemble_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); assemble_canvas->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 9e12147b95..21b4689cfc 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -2866,6 +2866,9 @@ void Selection::ensure_not_below_bed() bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const { + if (m_mode == Instance && wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) { + return true; + } struct SameInstance { int obj_idx; From f1fe2f80cfd65ddef3ba5be4f871100f15702d14 Mon Sep 17 00:00:00 2001 From: "zhou.xu" Date: Thu, 28 Mar 2024 11:28:38 +0800 Subject: [PATCH 09/70] FIX: add query_real_volume_idx_from_other_view api Jira: STUDIO-6545 Change-Id: Ib8216981c5d2945a0221a5caa1fbc14ed74e930b --- src/slic3r/GUI/Plater.cpp | 18 +++++++++++++++--- src/slic3r/GUI/Selection.cpp | 11 +++++++++++ src/slic3r/GUI/Selection.hpp | 1 + 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a9f3521c6c..a5eea812cb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4575,7 +4575,11 @@ void Plater::priv::selection_changed() } // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) - view3D->render(); + if (get_current_canvas3D()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) { + assemble_view->render(); + } else { + view3D->render(); + } } void Plater::priv::object_list_changed() @@ -6037,7 +6041,11 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice) Selection& view3d_selection = view3D->get_canvas3d()->get_selection(); view3d_selection.clear(); for (unsigned int idx : select_idxs) { - view3d_selection.add(idx, false); + auto v = assemble_canvas->get_selection().get_volume(idx); + auto real_idx = view3d_selection.query_real_volume_idx_from_other_view(v->object_idx(), v->instance_idx(), v->volume_idx()); + if (real_idx >= 0) { + view3d_selection.add(real_idx, false); + } } } @@ -6125,7 +6133,11 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice) Selection& assemble_selection = assemble_view->get_canvas3d()->get_selection(); assemble_selection.clear(); for (unsigned int idx : select_idxs) { - assemble_selection.add(idx, false); + auto v = view3D_canvas->get_selection().get_volume(idx); + auto real_idx = assemble_selection.query_real_volume_idx_from_other_view(v->object_idx(), v->instance_idx(), v->volume_idx()); + if (real_idx >= 0) { + assemble_selection.add(real_idx, false); + } } } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 21b4689cfc..c0b9be2a78 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -149,6 +149,17 @@ void Selection::set_model(Model* model) update_valid(); } +int Selection::query_real_volume_idx_from_other_view(unsigned int object_idx, unsigned int instance_idx, unsigned int model_volume_idx) +{ + for (int i = 0; i < m_volumes->size(); i++) { + auto v = (*m_volumes)[i]; + if (v->object_idx() == object_idx && instance_idx == v->instance_idx() && model_volume_idx == v->volume_idx()) { + return i; + } + } + return -1; +} + void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained) { if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 09189e33a8..817a423549 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -282,6 +282,7 @@ class Selection EMode get_mode() const { return m_mode; } void set_mode(EMode mode) { m_mode = mode; } + int query_real_volume_idx_from_other_view(unsigned int object_idx, unsigned int instance_idx, unsigned int model_volume_idx); void add(unsigned int volume_idx, bool as_single_selection = true, bool check_for_already_contained = false); void remove(unsigned int volume_idx); From f80a7428234c682fa12325b3e6401e8f2695161a Mon Sep 17 00:00:00 2001 From: "zhimin.zeng" Date: Wed, 27 Mar 2024 12:02:11 +0800 Subject: [PATCH 10/70] FIX: Can't edit text github: 3750 Change-Id: I1caecaa968e60cadcdbe9f7aa67cba141bb88230 --- src/slic3r/GUI/Gizmos/GLGizmoText.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp index c42b38c81e..7fcc88433e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp @@ -1117,10 +1117,8 @@ bool GLGizmoText::update_text_positions(const std::vector& texts) Polygons polys = union_(temp_polys); auto point_in_line_rectange = [](const Line &line, const Point &point, double& distance) { - distance = abs((point.x() - line.a.x()) * (line.b.y() - line.a.y()) - (line.b.x() - line.a.x()) * (point.y() - line.a.y())); - bool in_rectange = (std::min(line.a.x(), line.b.x()) - 1000) <= point.x() && point.x() <= (std::max(line.a.x(), line.b.x()) + 1000) && - (std::min(line.a.y(), line.b.y()) - 1000) <= point.y() && point.y() <= (std::max(line.a.y(), line.b.y()) + 1000); - return in_rectange; + distance = line.distance_to(point); + return distance < line.length() / 2; }; int index = 0; From 471baf066c135ec9c3478b4132287f8f87c70824 Mon Sep 17 00:00:00 2001 From: "zhimin.zeng" Date: Thu, 28 Mar 2024 14:00:37 +0800 Subject: [PATCH 11/70] FIX: Slicer creates invalid color pattern github: 3749 Change-Id: I3fd74a9ca59b75873fcbca4437e4858c749ee853 --- src/libslic3r/MultiMaterialSegmentation.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index badfed1726..a8bf9e6ff6 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1980,7 +1980,9 @@ std::vector> multi_material_segmentation_by_painting(con // be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618). // To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases // when any of the endpoints of the line are outside the EdgeGrid's BoundingBox. - if (const BoundingBox &edge_grid_bbox = edge_grids[layer_idx].bbox(); !edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) { + BoundingBox edge_grid_bbox = edge_grids[layer_idx].bbox(); + edge_grid_bbox.offset(10 * scale_(EPSILON)); + if (!edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) { // If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line. if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) || !line_to_test.clip_with_bbox(edge_grid_bbox)) From a9ee53f4b4262690df782d627942d98fa601b508 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 26 Mar 2024 11:39:30 +0800 Subject: [PATCH 12/70] ENH: improve supporting sharp tails of tree support 1. sharp tails are supported by a sparse set of contact points which are easier to remove than previously dense surrounding support. Organic tree support also has this feature, including all other smart overhang detection techniques (small overhang and cantilever detection), with the cost of slightly longer time to detect overhangs. 2. improve supporting overhang contours by adding contact points along contours. jira: STUDIO-3876 2. remove some redundant data structure. Change-Id: If7f595348506a14aba2d0132d23f97d3539c1e1f (cherry picked from commit e3cce09b9db12ced2841045ffd337b1f35494e6c) --- src/libslic3r/Layer.hpp | 5 +- src/libslic3r/PrintObjectSlice.cpp | 9 +- src/libslic3r/Support/TreeSupport.cpp | 265 +++++++++++------------- src/libslic3r/Support/TreeSupport.hpp | 7 +- src/libslic3r/Support/TreeSupport3D.cpp | 20 +- 5 files changed, 149 insertions(+), 157 deletions(-) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 59a099651f..c0763f00fd 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -269,11 +269,10 @@ class SupportLayer : public Layer ExPolygons support_islands; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; - SupportInnerType support_type = stInnerNormal; + SupportInnerType support_type = stInnerNormal; // for tree supports ExPolygons base_areas; - ExPolygons overhang_areas; // Is there any valid extrusion assigned to this LayerRegion? @@ -312,9 +311,7 @@ class SupportLayer : public Layer bool need_extra_wall = false; AreaGroup(ExPolygon *a, int t, coordf_t d) : area(a), type(t), dist_to_top(d) {} }; - enum OverhangType { Detected = 0, Enforced, SharpTail }; std::vector area_groups; - std::map overhang_types; }; template diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 11b9f870d7..f9622a6ed7 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -801,10 +801,11 @@ void PrintObject::slice() std::string warning = fix_slicing_errors(this, m_layers, [this](){ m_print->throw_if_canceled(); }, firstLayerReplacedBy); m_print->throw_if_canceled(); //BBS: send warning message to slicing callback - if (!warning.empty()) { - BOOST_LOG_TRIVIAL(info) << warning; - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingReplaceInitEmptyLayers); - } + // This warning is inaccurate, because the empty layers may have been replaced, or the model has supports. + //if (!warning.empty()) { + // BOOST_LOG_TRIVIAL(info) << warning; + // this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingReplaceInitEmptyLayers); + //} #endif // BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 3c40c6df32..d0aecc7f1f 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -12,6 +12,7 @@ #include "ShortestPath.hpp" #include "I18N.hpp" #include +#include #include "TreeModelVolumes.hpp" #include "TreeSupport3D.hpp" #include "SupportMaterial.hpp" @@ -653,18 +654,15 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) { - // overhangs are already detected - if (m_object->support_layer_count() >= m_object->layer_count()) - return; - bool tree_support_enable = m_object_config->enable_support.value && is_tree(m_object_config->support_type.value); + if (!tree_support_enable && !check_support_necessity) { + BOOST_LOG_TRIVIAL(info) << "Tree support is disabled."; + return; + } // Clear and create Tree Support Layers m_object->clear_support_layers(); m_object->clear_tree_support_preview_cache(); - create_tree_support_layers(); - if (!tree_support_enable && !check_support_necessity) - return; const PrintObjectConfig& config = m_object->config(); SupportType stype = support_type; @@ -787,6 +785,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) Layer* layer = m_object->get_layer(layer_nr); // Filter out areas whose diameter that is smaller than extrusion_width, but we don't want to lose any details. layer->lslices_extrudable = intersection_ex(layer->lslices, offset2_ex(layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled)); + layer->loverhangs.clear(); } }); @@ -794,6 +793,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) typedef std::chrono::duration > second_; std::chrono::time_point t0{ clock_::now() }; // main part of overhang detection can be parallel + tbb::concurrent_vector overhangs_all_layers(m_object->layer_count()); tbb::parallel_for(tbb::blocked_range(0, m_object->layer_count()), [&](const tbb::blocked_range& range) { for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { @@ -825,12 +825,11 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) ExPolygons& lower_polys = lower_layer->lslices_extrudable; // normal overhang - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - ts_layer->overhang_areas = std::move(diff_ex(curr_polys, lower_layer_offseted)); + overhangs_all_layers[layer_nr] = std::move(diff_ex(curr_polys, lower_layer_offseted)); double duration{ std::chrono::duration_cast(clock_::now() - t0).count() }; - if (duration > 30 || ts_layer->overhang_areas.size() > 100) { + if (duration > 30 || overhangs_all_layers[layer_nr].size() > 100) { BOOST_LOG_TRIVIAL(info) << "detect_overhangs takes more than 30 secs, skip cantilever and sharp tails detection: layer_nr=" << layer_nr << " duration=" << duration; config_detect_sharp_tails = false; config_remove_small_overhangs = false; @@ -848,18 +847,14 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } if (is_sharp_tail) { - ExPolygons overhang = diff_ex({ expoly }, lower_polys); layer->sharp_tails.push_back(expoly); - layer->sharp_tails_height.push_back(layer->height); - append(ts_layer->overhang_areas, overhang); + layer->sharp_tails_height.push_back(0); - if (!overhang.empty()) { - has_sharp_tails = true; + has_sharp_tails = true; #ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG::export_expolygons(debug_out_path("sharp_tail_orig_%.02f.svg", layer->print_z), { expoly }); + SVG::export_expolygons(debug_out_path("sharp_tail_orig_%.02f.svg", layer->print_z), { expoly }); #endif - } - } + } } } @@ -867,7 +862,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) // check cantilever // lower_layer_offset may be very small, so we need to do max and then add 0.1 lower_layer_offseted = offset_ex(lower_layer_offseted, scale_(std::max(extrusion_width - lower_layer_offset, 0.) + 0.1)); - for (ExPolygon& poly : ts_layer->overhang_areas) { + for (ExPolygon& poly : overhangs_all_layers[layer_nr]) { auto cluster_boundary_ex = intersection_ex(poly, lower_layer_offseted); Polygons cluster_boundary = to_polygons(cluster_boundary_ex); if (cluster_boundary.empty()) continue; @@ -902,7 +897,6 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) break; Layer* layer = m_object->get_layer(layer_nr); - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); Layer* lower_layer = layer->lower_layer; if (!lower_layer) continue; @@ -964,43 +958,32 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } while (0); if (is_sharp_tail) { - ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices); layer->sharp_tails.push_back(expoly); layer->sharp_tails_height.push_back( accum_height); - append(ts_layer->overhang_areas, overhang); - - if (!overhang.empty()) - has_sharp_tails = true; -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG::export_expolygons(debug_out_path("sharp_tail_%.02f.svg", layer->print_z), overhang); -#endif } } } } - + + // group overhang clusters + for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { + if (m_object->print()->canceled()) + break; + Layer* layer = m_object->get_layer(layer_nr); + for (auto& overhang : overhangs_all_layers[layer_nr]) { + OverhangCluster* cluster = find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled); + if (overlaps({ overhang }, layer->cantilevers)) + cluster->is_cantilever = true; + } + } + auto enforcers = m_object->slice_support_enforcers(); auto blockers = m_object->slice_support_blockers(); m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); + if (is_auto(stype) && config_remove_small_overhangs) { - if (blockers.size() < m_object->layer_count()) - blockers.resize(m_object->layer_count()); - // group overhang clusters - for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { - if (m_object->print()->canceled()) - break; - if (layer_nr + m_raft_layers >= m_object->support_layer_count()) - break; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - Layer* layer = m_object->get_layer(layer_nr); - for (auto& overhang : ts_layer->overhang_areas) { - OverhangCluster* cluster = find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled); - if (overlaps({ overhang }, layer->cantilevers)) - cluster->is_cantilever = true; - } - } // remove small overhangs for (auto& cluster : overhangClusters) { // 3. check whether the small overhang is sharp tail @@ -1033,51 +1016,66 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) { cluster.merged_poly,{"overhang", "blue", 0.5} }, { cluster.is_cantilever? layer1->cantilevers: offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled), {cluster.is_cantilever ? "cantilever":"erode1","green",0.5}} }); #endif + } + } - if (!cluster.is_small_overhang) - continue; - - for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { - int layer_nr = it->first; - auto p_overhang = it->second; - blockers[layer_nr].push_back(p_overhang->contour); - } + for (auto& cluster : overhangClusters) { + if (cluster.is_small_overhang) continue; + // collect overhangs that's not small overhangs + for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { + int layer_nr = it->first; + auto p_overhang = it->second; + m_object->get_layer(layer_nr)->loverhangs.emplace_back(*p_overhang); } } - has_overhangs = false; + int layers_with_overhangs = 0; for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { if (m_object->print()->canceled()) break; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); auto layer = m_object->get_layer(layer_nr); auto lower_layer = layer->lower_layer; - if (support_critical_regions_only && is_auto(stype)) { - ts_layer->overhang_areas.clear(); - if (lower_layer == nullptr) - ts_layer->overhang_areas = layer->sharp_tails; - else - ts_layer->overhang_areas = diff_ex(layer->sharp_tails, lower_layer->lslices); - append(ts_layer->overhang_areas, layer->cantilevers); + // add support for every 1mm height for sharp tails + ExPolygons sharp_tail_overhangs; + if (lower_layer == nullptr) + sharp_tail_overhangs = layer->sharp_tails; + else { + ExPolygons lower_layer_expanded = offset_ex(lower_layer->lslices_extrudable, SCALED_RESOLUTION); + for (size_t i = 0; i < layer->sharp_tails_height.size();i++) { + ExPolygons areas = diff_clipped({ layer->sharp_tails[i]}, lower_layer_expanded); + float accum_height = layer->sharp_tails_height[i]; + if (!areas.empty() && int(accum_height * 10) % 5 == 0) { + append(sharp_tail_overhangs, areas); + has_sharp_tails = true; +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + SVG::export_expolygons(debug_out_path("sharp_tail_%.02f.svg", layer->print_z), areas); +#endif + } + } + } + + if (support_critical_regions_only && is_auto(stype)) { + layer->loverhangs.clear(); // remove oridinary overhangs, only keep cantilevers and sharp tails (added later) + append(layer->loverhangs, layer->cantilevers); } if (layer_nr < blockers.size()) { Polygons& blocker = blockers[layer_nr]; // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. - ts_layer->overhang_areas = diff_ex(ts_layer->overhang_areas, offset_ex(union_(blocker), scale_(radius_sample_resolution))); + layer->loverhangs = diff_ex(layer->loverhangs, offset_ex(union_(blocker), scale_(radius_sample_resolution))); } - if (max_bridge_length > 0 && ts_layer->overhang_areas.size() > 0 && lower_layer) { + if (max_bridge_length > 0 && layer->loverhangs.size() > 0 && lower_layer) { // do not break bridge for normal part in TreeHybrid, nor Tree Strong - bool break_bridge = !(support_style == smsTreeHybrid && area(ts_layer->overhang_areas) > m_support_params.thresh_big_overhang) + bool break_bridge = !(support_style == smsTreeHybrid && area(layer->loverhangs) > m_support_params.thresh_big_overhang) && !(support_style==smsTreeStrong); - m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &ts_layer->overhang_areas, max_bridge_length, break_bridge); + m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &layer->loverhangs, max_bridge_length, break_bridge); } - int nDetected = ts_layer->overhang_areas.size(); + int nDetected = layer->loverhangs.size(); // enforcers now follow same logic as normal support. See STUDIO-3692 if (layer_nr < enforcers.size() && lower_layer) { float no_interface_offset = std::accumulate(layer->regions().begin(), layer->regions().end(), FLT_MAX, @@ -1088,31 +1086,36 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) ExPolygons enforcer_polygons = diff_ex(intersection_ex(layer->lslices, enforcer), // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - append(ts_layer->overhang_areas, enforcer_polygons); + append(layer->loverhangs, enforcer_polygons); } } - int nEnforced = ts_layer->overhang_areas.size(); + int nEnforced = layer->loverhangs.size(); + // add sharp tail overhangs + append(layer->loverhangs, sharp_tail_overhangs); + // fill overhang_types - for (size_t i = 0; i < ts_layer->overhang_areas.size(); i++) - ts_layer->overhang_types.emplace(&ts_layer->overhang_areas[i], i < nDetected ? SupportLayer::Detected : - i < nEnforced ? SupportLayer::Enforced : SupportLayer::SharpTail); + for (size_t i = 0; i < layer->loverhangs.size(); i++) + overhang_types.emplace(&layer->loverhangs[i], i < nDetected ? OverhangType::Detected : + i < nEnforced ? OverhangType::Enforced : OverhangType::SharpTail); - if (!ts_layer->overhang_areas.empty()) { - has_overhangs = true; + if (!layer->loverhangs.empty()) { + layers_with_overhangs++; m_highest_overhang_layer = std::max(m_highest_overhang_layer, size_t(layer_nr)); } if (!layer->cantilevers.empty()) has_cantilever = true; } + BOOST_LOG_TRIVIAL(info) << "Tree support overhang detection done. " << layers_with_overhangs << " layers with overhangs."; + #ifdef SUPPORT_TREE_DEBUG_TO_SVG - for (const SupportLayer* layer : m_object->support_layers()) { - if (layer->overhang_areas.empty() && (blockers.size()<=layer->id() || blockers[layer->id()].empty())) + for (const Layer* layer : m_object->layers()) { + if (layer->loverhangs.empty() && (blockers.size()<=layer->id() || blockers[layer->id()].empty())) continue; - SVG::export_expolygons(debug_out_path("overhang_areas_%.2f.svg", layer->print_z), { + SVG::export_expolygons(debug_out_path("overhang_areas_%d_%.2f.svg",layer->id(), layer->print_z), { { m_object->get_layer(layer->id())->lslices, {"lslices","yellow",0.5} }, - { layer->overhang_areas, {"overhang","red",0.5} } + { layer->loverhangs, {"overhang","red",0.5} } }); if (enforcers.size() > layer->id()) { @@ -1628,19 +1631,20 @@ void TreeSupport::generate() // Generate overhang areas profiler.stage_start(STAGE_DETECT_OVERHANGS); - m_object->print()->set_status(55, _u8L("Generating supports")); + m_object->print()->set_status(55, _u8L("Generating support")); detect_overhangs(); profiler.stage_finish(STAGE_DETECT_OVERHANGS); + create_tree_support_layers(); m_ts_data = m_object->alloc_tree_support_preview_cache(); m_ts_data->is_slim = is_slim; // Generate contact points of tree support std::vector> contact_nodes(m_object->layers().size()); +#if USE_SUPPORT_3D std::vector move_bounds(m_highest_overhang_layer + 1); profiler.stage_start(STAGE_GENERATE_CONTACT_NODES); -#if USE_SUPPORT_3D //m_object->print()->set_status(56, _u8L("Support: precalculate avoidance")); Points bedpts = m_machine_border.contour.points; @@ -1666,19 +1670,19 @@ void TreeSupport::generate() if (support_style != smsTreeHybrid) { overhangs.resize(m_object->support_layer_count()); for (size_t i = 0; i < overhangs.size(); i++) { - overhangs[i] = to_polygons(m_object->get_support_layer(i)->overhang_areas); + overhangs[i] = to_polygons(m_object->get_layer(i)->loverhangs); } //m_object->clear_support_layers(); TreeSupport3D::generate_initial_areas(*m_object, *m_model_volumes.get(), tree_support_3d_config, overhangs, move_bounds, top_contacts, layer_storage, throw_on_cancel); } #endif - generate_contact_points(contact_nodes, move_bounds); + generate_contact_points(contact_nodes); profiler.stage_finish(STAGE_GENERATE_CONTACT_NODES); //Drop nodes to lower layers. profiler.stage_start(STAGE_DROP_DOWN_NODES); - m_object->print()->set_status(60, _u8L("Generating supports")); + m_object->print()->set_status(60, _u8L("Generating support")); drop_nodes(contact_nodes); profiler.stage_finish(STAGE_DROP_DOWN_NODES); @@ -1686,14 +1690,14 @@ void TreeSupport::generate() //Generate support areas. profiler.stage_start(STAGE_DRAW_CIRCLES); - m_object->print()->set_status(65, _u8L("Generating supports")); + m_object->print()->set_status(65, _u8L("Generating support")); draw_circles(contact_nodes); profiler.stage_finish(STAGE_DRAW_CIRCLES); profiler.stage_start(STAGE_GENERATE_TOOLPATHS); - m_object->print()->set_status(69, _u8L("Generating supports")); + m_object->print()->set_status(70, _u8L("Generating support")); generate_toolpaths(); profiler.stage_finish(STAGE_GENERATE_TOOLPATHS); @@ -1988,7 +1992,6 @@ void TreeSupport::draw_circles(const std::vector>& con coordf_t max_layers_above_roof = 0; coordf_t max_layers_above_roof1 = 0; int interface_id = 0; - bool has_polygon_node = false; bool has_circle_node = false; bool need_extra_wall = false; ExPolygons collision_sharp_tails; @@ -2009,6 +2012,7 @@ void TreeSupport::draw_circles(const std::vector>& con BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << contact_nodes[layer_nr].size(); //Draw the support areas and add the roofs appropriately to the support roof instead of normal areas. ts_layer->lslices.reserve(contact_nodes[layer_nr].size()); + ExPolygons area_poly; // the polygon node area which will be printed as normal support for (const SupportNode* p_node : contact_nodes[layer_nr]) { if (print->canceled()) @@ -2025,9 +2029,9 @@ void TreeSupport::draw_circles(const std::vector>& con else { area = offset_ex({ node.overhang }, scale_(m_ts_data->m_xy_distance)); } - if (node.type == ePolygon) - has_polygon_node = true; area = diff_clipped(area, get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); + if (node.type == ePolygon) + area_poly = area; } else { Polygon circle(branch_circle); @@ -2154,8 +2158,8 @@ void TreeSupport::draw_circles(const std::vector>& con auto &area_groups = ts_layer->area_groups; for (auto& area : ts_layer->base_areas) { area_groups.emplace_back(&area, SupportLayer::BaseType, max_layers_above_base); - area_groups.back().need_infill = has_polygon_node; - area_groups.back().need_extra_wall = need_extra_wall; + area_groups.back().need_infill = overlaps({ area }, area_poly); + area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill; } for (auto& area : ts_layer->roof_areas) { area_groups.emplace_back(&area, SupportLayer::RoofType, max_layers_above_roof); @@ -2491,13 +2495,14 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod continue; int layer_nr_next = layer_heights[layer_nr].next_layer_nr; + coordf_t print_z = layer_heights[layer_nr].print_z; coordf_t print_z_next = layer_heights[layer_nr_next].print_z; coordf_t height_next = layer_heights[layer_nr_next].height; std::deque> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches. const Layer* ts_layer = m_object->get_support_layer(layer_nr); - m_object->print()->set_status(60 + int(10 * (1 - float(layer_nr) / contact_nodes.size())), _u8L("Generating supports"));// (boost::format(_u8L("Support: propagate branches at layer %d")) % layer_nr).str()); + m_object->print()->set_status(60 + int(10 * (1 - float(layer_nr) / contact_nodes.size())), _u8L("Generating support"));// (boost::format(_u8L("Support: propagate branches at layer %d")) % layer_nr).str()); Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(layer_nr)); //std::unordered_map& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; @@ -3226,7 +3231,7 @@ std::vector TreeSupport::plan_layer_heights(std::vector>& contact_nodes, const std::vector& move_bounds) +void TreeSupport::generate_contact_points(std::vector>& contact_nodes) { const PrintObjectConfig &config = m_object->config(); const coordf_t point_spread = scale_(config.tree_support_branch_distance.value); @@ -3284,8 +3289,8 @@ void TreeSupport::generate_contact_points(std::vector> // fix bug of generating support for very thin objects if (m_object->layers().size() <= z_distance_top_layers + 1) return; - if (m_object->support_layer_count() <= m_raft_layers) - return; + //if (m_object->support_layer_count() <= m_raft_layers) + // return; int nonempty_layers = 0; tbb::concurrent_vector all_nodes; @@ -3293,10 +3298,9 @@ void TreeSupport::generate_contact_points(std::vector> for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { if (m_object->print()->canceled()) break; - auto ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); Layer* layer = m_object->get_layer(layer_nr); auto& curr_nodes = contact_nodes[layer_nr - 1]; - if (ts_layer->overhang_areas.empty()) continue; + if (layer->loverhangs.empty()) continue; std::unordered_set already_inserted; auto print_z = m_object->get_layer(layer_nr)->print_z; @@ -3332,9 +3336,9 @@ void TreeSupport::generate_contact_points(std::vector> return contact_node; }; - for (const auto& area_type : ts_layer->overhang_types) { - const auto& overhang_part = *area_type.first; - is_sharp_tail = area_type.second == SupportLayer::SharpTail; + for (const auto& overhang_part : layer->loverhangs) { + const auto& overhang_type = this->overhang_types[&overhang_part]; + is_sharp_tail = overhang_type == OverhangType::SharpTail; BoundingBox overhang_bounds = get_extents(overhang_part); if (support_style == smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang && !is_sharp_tail) { if (!overlaps({ overhang_part }, m_ts_data->m_layer_outlines_below[layer_nr - 1])) { @@ -3364,58 +3368,41 @@ void TreeSupport::generate_contact_points(std::vector> } } - auto& move_bounds_layer = move_bounds[layer_nr]; - if (move_bounds_layer.empty()) { - overhang_bounds.inflated(half_overhang_distance); - bool added = false; //Did we add a point this way? + // add supports along contours + libnest2d::placers::EdgeCache edge_cache(overhang_part); + for (size_t i = 0; i < edge_cache.holeCount() + 1; i++) { + double step = point_spread / (i == 0 ? edge_cache.circumference() : edge_cache.circumference(i - 1)); + double distance = 0; + while (distance < 1) { + auto pt = i == 0 ? edge_cache.coords(distance) : edge_cache.coords(i - 1, distance); + SupportNode* contact_node = insert_point(pt, overhang_part); + distance += step; + } + } + + // don't add inner supports for sharp tails + if (is_sharp_tail) + continue; + + // add inner supports + overhang_bounds.inflated(-radius); + ExPolygons overhang_inner = offset_ex(overhang_part, -radius); for (Point candidate : grid_points) { if (overhang_bounds.contains(candidate)) { // BBS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported! - bool is_inside = is_inside_ex(overhang_part, candidate); - if (!is_inside) { - constexpr coordf_t distance_inside = 0; // Move point towards the border of the polygon if it is closer than half the overhang distance: Catch points that - // fall between overhang areas on constant surfaces. - is_inside = move_inside_expoly(overhang_part, candidate, distance_inside, half_overhang_distance); - } + bool is_inside = is_inside_ex(overhang_inner, candidate); if (is_inside) { SupportNode* contact_node = insert_point(candidate, overhang_part); - if (contact_node) - added = true; } } } - - if (!added) //If we didn't add any points due to bad luck, we want to add one anyway such that loose parts are also supported. - { - auto bbox = overhang_part.contour.bounding_box(); - Points candidates; - if (ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected) - candidates = { bbox.min, bounding_box_middle(bbox), bbox.max }; - else - candidates = { bounding_box_middle(bbox) }; - - for (Point candidate : candidates) { - if (!overhang_part.contains(candidate)) - move_inside_expoly(overhang_part, candidate); - insert_point(candidate, overhang_part); - } - } - } - else { - for (auto& elem : move_bounds_layer) { - SupportNode* contact_node = m_ts_data->create_node(elem.state.result_on_layer, -gap_layers, layer_nr-1, support_roof_layers + 1, elem.state.to_buildplate, - SupportNode::NO_PARENT, print_z, height, z_distance_top); - contact_node->overhang = ExPolygon(elem.influence_area.front()); - curr_nodes.emplace_back(contact_node); - - } - } + } if (!curr_nodes.empty()) nonempty_layers++; for (auto node : curr_nodes) { all_nodes.emplace_back(node->position(0), node->position(1), scale_(node->print_z)); } #ifdef SUPPORT_TREE_DEBUG_TO_SVG - draw_contours_and_nodes_to_svg(debug_out_path("init_contact_points_%.2f.svg", print_z), ts_layer->overhang_areas, m_ts_data->m_layer_outlines_below[layer_nr], {}, - contact_nodes[layer_nr], contact_nodes[layer_nr - 1], { "overhang","outlines","" }); + draw_contours_and_nodes_to_svg(debug_out_path("init_contact_points_%.2f.svg", print_z), layer->loverhangs,layer->lslices, m_ts_data->m_layer_outlines_below[layer_nr], + contact_nodes[layer_nr], contact_nodes[layer_nr - 1], { "overhang","lslices","outlines_below"}); #endif }} ); // end tbb::parallel_for diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index 3328efd0e0..09ecd1337d 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -379,7 +379,6 @@ class TreeSupport int avg_node_per_layer = 0; float nodes_angle = 0; - bool has_overhangs = false; bool has_sharp_tails = false; bool has_cantilever = false; double max_cantilever_dist = 0; @@ -396,6 +395,9 @@ class TreeSupport * machine */ ExPolygon m_machine_border; + + enum OverhangType { Detected = 0, Enforced, SharpTail }; + std::map overhang_types; private: /*! * \brief Generator for model collision, avoidance and internal guide volumes @@ -414,6 +416,7 @@ class TreeSupport size_t m_highest_overhang_layer = 0; std::vector> m_spanning_trees; std::vector< std::unordered_map> m_mst_line_x_layer_contour_caches; + float DO_NOT_MOVER_UNDER_MM = 0.0; coordf_t MAX_BRANCH_RADIUS = 10.0; coordf_t MIN_BRANCH_RADIUS = 0.5; @@ -476,7 +479,7 @@ class TreeSupport * \return For each layer, a list of points where the tree should connect * with the model. */ - void generate_contact_points(std::vector>& contact_nodes, const std::vector& move_bounds); + void generate_contact_points(std::vector>& contact_nodes); /*! * \brief Add a node to the next layer. diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index 6434e29dc3..36983cff29 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -4180,18 +4180,22 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons //FIXME generating overhangs just for the first mesh of the group. assert(processing.second.size() == 1); - //print.set_status(55, _L("Support: detect overhangs")); -#if 0 +#if 1 + // use smart overhang detection std::vector overhangs; tree_support->detect_overhangs(); const int num_raft_layers = int(config.raft_layers.size()); const int num_layers = int(print_object.layer_count()) + num_raft_layers; overhangs.resize(num_layers); - for (size_t i = 0; i < print_object.layer_count(); i++) - { - overhangs[i + num_raft_layers] = to_polygons(print_object.get_support_layer(i)->overhang_areas); + for (size_t i = 0; i < print_object.layer_count(); i++) { + for (ExPolygon& expoly : print_object.get_layer(i)->loverhangs) { + Polygons polys = to_polygons(expoly); + if (tree_support->overhang_types[&expoly] == TreeSupport::SharpTail) { + polys = offset(to_polygons(expoly), scale_(0.2)); + } + append(overhangs[i + num_raft_layers], polys); + } } - print_object.clear_support_layers(); #else std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); #endif @@ -4274,7 +4278,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons #endif // TREESUPPORT_DEBUG_SVG // ### Propagate the influence areas downwards. This is an inherently serial operation. - //print.set_status(60, _L("Support: propagate branches")); + print.set_status(60, _L("Generating support")); create_layer_pathing(volumes, config, move_bounds, throw_on_cancel); auto t_path = std::chrono::high_resolution_clock::now(); @@ -4331,7 +4335,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons SupportGeneratorLayersPtr layers_sorted = generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); // Don't fill in the tree supports, make them hollow with just a single sheath line. - //print.set_status(69, _L("Support: generate toolpath")); + print.set_status(69, _L("Generating support")); generate_support_toolpaths(print_object, print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); From 9f5cd7b3cc48990435e7db5024f555d697663548 Mon Sep 17 00:00:00 2001 From: "chunmao.guo" Date: Tue, 6 Feb 2024 13:24:37 +0800 Subject: [PATCH 13/70] ENH: hide tuck did Change-Id: I9021d3f51c9a73bc9208b479f96b1ddbe7a2f8f8 Jira: none --- src/slic3r/GUI/CameraPopup.cpp | 5 ++-- src/slic3r/GUI/MediaPlayCtrl.cpp | 40 +++++++++++++++----------------- src/slic3r/GUI/MediaPlayCtrl.h | 3 --- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/CameraPopup.cpp b/src/slic3r/GUI/CameraPopup.cpp index 9639bb8d30..d484675168 100644 --- a/src/slic3r/GUI/CameraPopup.cpp +++ b/src/slic3r/GUI/CameraPopup.cpp @@ -71,13 +71,14 @@ CameraPopup::CameraPopup(wxWindow *parent) m_text_liveview_retry->SetFont(Label::Head_14); m_text_liveview_retry->SetForegroundColour(TEXT_COL); m_switch_liveview_retry = new SwitchButton(m_panel); - m_switch_liveview_retry->SetValue(true); + bool auto_retry = wxGetApp().app_config->get("liveview", "auto_retry") != "false"; + m_switch_liveview_retry->SetValue(auto_retry); top_sizer->Add(m_text_liveview_retry, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5)); top_sizer->Add(m_switch_liveview_retry, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5)); m_switch_liveview_retry->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &e) { - dynamic_cast(GetParent()->FindWindowByLabel("MediaPlayCtrl"))->SetAutoRetry(e.IsChecked()); + wxGetApp().app_config->set("liveview", "auto_retry", e.IsChecked()); e.Skip(); }); #endif diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 176ad67a25..e6712125fe 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -143,19 +143,6 @@ MediaPlayCtrl::~MediaPlayCtrl() } } -wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1) -{ -#if BBL_RELEASE_TO_PUBLIC - if (length == size_t(-1)) - length = str.Length() - offset; - if (length <= 8) - return str; - return str.Left(offset + 4) + wxString(length - 8, '*') + str.Mid(offset + length - 4); -#else - return str; -#endif -} - void MediaPlayCtrl::SetMachineObject(MachineObject* obj) { std::string machine = obj ? obj->dev_id : ""; @@ -213,9 +200,15 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj) SetStatus("", false); } -void MediaPlayCtrl::SetAutoRetry(bool b) +wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1) { - m_auto_retry = b; +#if BBL_RELEASE_TO_PUBLIC + if (length == size_t(-1)) length = str.Length() - offset; + if (length <= 8) return str; + return str.Left(offset + 4) + wxString(length - 8, '*') + str.Mid(offset + length - 4); +#else + return str; +#endif } wxString hide_passwd(wxString url, std::vector const &passwords) @@ -232,7 +225,9 @@ wxString hide_passwd(wxString url, std::vector const &passwords) if (j == wxString::npos) j = url.length(); } auto l = size_t(j - i); - if (j == url.length() || url[j] == '@' || url[j] == '&') + if (p[0] == '?' || p[0] == '&') + url = hide_id_middle_string(url, i, l); + else if (j == url.length() || url[j] == '@' || url[j] == '&') url.replace(i, l, l, wxUniChar('*')); } #endif @@ -328,7 +323,8 @@ void MediaPlayCtrl::Play() url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); url += "&cli_ver=" + std::string(SLIC3R_VERSION); } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_id_middle_string(hide_passwd(url, {"authkey=", "passwd="}), 9, 20) << "tutk_state: " << m_tutk_state; + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url, + {"?uid=", "authkey=", "passwd=", "license=", "token="}); CallAfter([this, m, url] { if (m != m_machine) { BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl drop late ttcode for machine: " << m; @@ -383,7 +379,8 @@ void MediaPlayCtrl::Stop(wxString const &msg) } else SetStatus(_L("Stopped."), false); m_last_state = MEDIASTATE_IDLE; - if (!m_auto_retry || m_failed_code >= 100 || m_failed_code == 1) // not keep retry on local error or EOS + bool auto_retry = wxGetApp().app_config->get("liveview", "auto_retry") != "false"; + if (!auto_retry || m_failed_code >= 100 || m_failed_code == 1) // not keep retry on local error or EOS m_next_retry = wxDateTime(); } else if (!msg.IsEmpty()) { SetStatus(msg, false); @@ -561,7 +558,8 @@ void MediaPlayCtrl::ToggleStream() url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); url += "&cli_ver=" + std::string(SLIC3R_VERSION); } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_id_middle_string(hide_passwd(url, {"authkey=", "passwd="}), 9, 20); + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url, + {"?uid=", "authkey=", "passwd=", "license=", "token="}); CallAfter([this, m, url] { if (m != m_machine) return; if (url.empty() || !boost::algorithm::starts_with(url, "bambu:///")) { @@ -655,14 +653,14 @@ void MediaPlayCtrl::SetStatus(wxString const &msg2, bool hyperlink) m_last_state + MEDIASTATE_BUFFERING - MEDIASTATE_IDLE; msg += wxString::Format(" [%d:%d]", state2, m_failed_code); } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetStatus: " << msg.ToUTF8().data() << m_tutk_state; + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetStatus: " << msg.ToUTF8().data() << " tutk_state: " << m_tutk_state; #ifdef __WXMSW__ OutputDebugStringA("MediaPlayCtrl::SetStatus: "); OutputDebugStringA(msg.ToUTF8().data()); OutputDebugStringA("\n"); #endif // __WXMSW__ m_label_status->SetLabel(msg); - m_label_status->Wrap(GetSize().GetWidth() - 120); + m_label_status->Wrap(GetSize().GetWidth() - 120 - m_label_stat->GetSize().GetWidth()); long style = m_label_status->GetWindowStyle() & ~LB_HYPERLINK; if (hyperlink) { style |= LB_HYPERLINK; diff --git a/src/slic3r/GUI/MediaPlayCtrl.h b/src/slic3r/GUI/MediaPlayCtrl.h index b1fa5c800c..c4e088876c 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.h +++ b/src/slic3r/GUI/MediaPlayCtrl.h @@ -36,8 +36,6 @@ class MediaPlayCtrl : public wxPanel void SetMachineObject(MachineObject * obj); - void SetAutoRetry(bool b); - bool IsStreaming() const; void ToggleStream(); @@ -86,7 +84,6 @@ class MediaPlayCtrl : public wxPanel bool m_remote_support = false; bool m_device_busy = false; bool m_disable_lan = false; - bool m_auto_retry = true; wxString m_url; std::deque m_tasks; From 870a10592f16b7e8195516bc0e9dc2b0c50dd274 Mon Sep 17 00:00:00 2001 From: "chunmao.guo" Date: Thu, 29 Feb 2024 10:46:36 +0800 Subject: [PATCH 14/70] FIX: PrinterFileSystem: retry connect on user action Change-Id: I3e8902298385ed2e5906fd15d1817b6e33522a76 Jira: STUDIO-6354 --- src/slic3r/GUI/MediaFilePanel.cpp | 14 +++++++++----- src/slic3r/GUI/MediaPlayCtrl.cpp | 3 ++- src/slic3r/GUI/Printer/PrinterFileSystem.cpp | 14 +++++++++----- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/MediaFilePanel.cpp b/src/slic3r/GUI/MediaFilePanel.cpp index da3dbb85ce..c39c544732 100644 --- a/src/slic3r/GUI/MediaFilePanel.cpp +++ b/src/slic3r/GUI/MediaFilePanel.cpp @@ -337,7 +337,9 @@ void MediaFilePanel::SetMachineObject(MachineObject* obj) int result = e.GetExtraLong(); if (result > 1 && !e.GetString().IsEmpty()) - MessageDialog(this, e.GetString(), _L("Download failed"), wxOK | wxICON_ERROR).ShowModal(); + CallAfter([this, m = e.GetString()] { + MessageDialog(this, m, _L("Download failed"), wxOK | wxICON_ERROR).ShowModal(); + }); NetworkAgent* agent = wxGetApp().getAgent(); if (result > 1 || result == 0) { @@ -558,9 +560,11 @@ void MediaFilePanel::doAction(size_t index, int action) if (result == PrinterFileSystem::ERROR_CANCEL) return; if (result != 0) { - MessageDialog(this, - _L("Failed to fetch model information from printer."), - _L("Error"), wxOK).ShowModal(); + wxString msg = data.empty() ? _L("Failed to fetch model information from printer.") : + from_u8(data); + CallAfter([this, msg] { + MessageDialog(this, msg, _L("Print"), wxOK).ShowModal(); + }); return; } Slic3r::DynamicPrintConfig config; @@ -572,7 +576,7 @@ void MediaFilePanel::doAction(size_t index, int action) || plate_data_list.empty()) { MessageDialog(this, _L("Failed to parse model information."), - _L("Error"), wxOK).ShowModal(); + _L("Print"), wxOK).ShowModal(); return; } diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index e6712125fe..9fd174e0a0 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -315,7 +315,8 @@ void MediaPlayCtrl::Play() SetStatus(_L("Initializing...")); if (agent) { - agent->get_camera_url(m_machine, [this, m = m_machine, v = agent_version, dv = m_dev_ver](std::string url) { + agent->get_camera_url(m_machine, + [this, m = m_machine, v = agent_version, dv = m_dev_ver](std::string url) { if (boost::algorithm::starts_with(url, "bambu:///")) { url += "&device=" + into_u8(m); url += "&net_ver=" + v; diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp index 70251a4207..f2bb0842b5 100644 --- a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp +++ b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp @@ -45,7 +45,7 @@ wxDEFINE_EVENT(EVT_FILE_CALLBACK, wxCommandEvent); static wxBitmap default_thumbnail; static std::map error_messages = { - {PrinterFileSystem::ERROR_PIPE, L("The printer has been logged out and cannot connect.")}, + {PrinterFileSystem::ERROR_PIPE, L("Reconnecting the printer, the operation cannot be completed immediately, please try again later.")}, {PrinterFileSystem::ERROR_RES_BUSY, L("Over 4 studio/handy are using remote access, you can close some and try again.")}, {PrinterFileSystem::FILE_NO_EXIST, L("File does not exist.")}, {PrinterFileSystem::FILE_CHECK_ERR, L("File checksum error. Please retry.")}, @@ -358,6 +358,13 @@ void PrinterFileSystem::FetchModel(size_t index, std::functionsecond.c_str()); + else + file_data->clear(); + } callback(result, *file_data); }); } @@ -1026,10 +1033,7 @@ void PrinterFileSystem::DumpLog(void * thiz, int, tchar const *msg) boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback) { if (m_session.tunnel == nullptr) { - { - boost::unique_lock l(m_mutex); - m_cond.notify_all(); - } + Retry(); callback(ERROR_PIPE, json(), nullptr); return 0; } From 8ae0c4d89d6927ed84ef8d5d0094ef03afea400c Mon Sep 17 00:00:00 2001 From: Kunlong Ma Date: Thu, 21 Mar 2024 19:10:18 +0800 Subject: [PATCH 15/70] ENH: copy pt-BR/BamabuStudio.mo to pt_br JIRA: NONE Signed-off-by: Kunlong Ma Change-Id: Ib974d498a6a38402a8d5c3252741d4d7d2850af3 --- CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9b17ee559..7633676ef7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -538,6 +538,14 @@ foreach(po_file ${BBL_L10N_PO_FILES}) ) endforeach() +# copy pt-BR/BamabuStudio.mo to pt_br/ +SET(PT_BR "${L10N_DIR}/pt-BR/BambuStudio.mo") +SET(PT_BR_DST "${L10N_DIR}/pt_br/") +add_custom_command( + TARGET gettext_po_to_mo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PT_BR} ${PT_BR_DST} +) + find_package(NLopt 1.4 REQUIRED) From 30eb2bad5bc0b833666e1e6b1e00c28f6940b679 Mon Sep 17 00:00:00 2001 From: "maosheng.wei" Date: Fri, 29 Mar 2024 10:40:14 +0800 Subject: [PATCH 16/70] FIX: Remove user ID and other information Jira: XXXX Change-Id: Ia63ec88a335d88fd40a29952abe6d40d8991efee --- src/slic3r/GUI/CreatePresetsDialog.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/slic3r/GUI/CreatePresetsDialog.cpp b/src/slic3r/GUI/CreatePresetsDialog.cpp index b0333b9291..6c5f5e624d 100644 --- a/src/slic3r/GUI/CreatePresetsDialog.cpp +++ b/src/slic3r/GUI/CreatePresetsDialog.cpp @@ -3785,13 +3785,9 @@ ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_preset_bundle_to_fi NetworkAgent *agent = wxGetApp().getAgent(); std::string clock = get_curr_timestmp(); if (agent) { - bundle_structure["user_name"] = agent->get_user_name(); - bundle_structure["user_id"] = agent->get_user_id(); bundle_structure["version"] = agent->get_version(); bundle_structure["bundle_id"] = agent->get_user_id() + "_" + printer_preset_name_ + "_" + clock; } else { - bundle_structure["user_name"] = ""; - bundle_structure["user_id"] = ""; bundle_structure["version"] = ""; bundle_structure["bundle_id"] = "offline_" + printer_preset_name_ + "_" + clock; } @@ -3908,13 +3904,9 @@ ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_filament_bundle_to_ NetworkAgent *agent = wxGetApp().getAgent(); std::string clock = get_curr_timestmp(); if (agent) { - bundle_structure["user_name"] = agent->get_user_name(); - bundle_structure["user_id"] = agent->get_user_id(); bundle_structure["version"] = agent->get_version(); bundle_structure["bundle_id"] = agent->get_user_id() + "_" + filament_name + "_" + clock; } else { - bundle_structure["user_name"] = ""; - bundle_structure["user_id"] = ""; bundle_structure["version"] = ""; bundle_structure["bundle_id"] = "offline_" + filament_name + "_" + clock; } From 279700ecaf94528710d8c7644f4591b238f47df5 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 29 Mar 2024 20:31:14 +0800 Subject: [PATCH 17/70] FIX: tree supports may generate flying nodes Previous parallelization has a bug where two adjacent nodes may be deleted at the same time. jira: none Change-Id: I99a29dae9f72aa74ed2721eea4421b15eec10732 (cherry picked from commit 91efe67d723652d3f7e4484dd3cdf31638f769a4) --- src/libslic3r/Support/TreeSupport.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index d0aecc7f1f..3e0d5fa315 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -2617,7 +2617,7 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod // Remove all circle neighbours that are completely inside the polygon and merge them into this node. for (const Point &neighbour : neighbours) { SupportNode * neighbour_node = nodes_this_part[neighbour]; - if(neighbour_node->type==ePolygon) return; + if(neighbour_node->type==ePolygon) continue; coord_t neighbour_radius = scale_(neighbour_node->radius); Point pt_north = neighbour + Point(0, neighbour_radius), pt_south = neighbour - Point(0, neighbour_radius), pt_west = neighbour - Point(neighbour_radius, 0), pt_east = neighbour + Point(neighbour_radius, 0); @@ -2676,10 +2676,15 @@ void TreeSupport::drop_nodes(std::vector>& contact_nod // only allow bigger node to merge smaller nodes. See STUDIO-6326 if(node.dist_mm_to_top < neighbour_node->dist_mm_to_top) continue; - node.merged_neighbours.push_front(neighbour_node); - node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); - to_delete.insert(neighbour_node); - neighbour_node->valid = false; + m_ts_data->m_mutex.lock(); + if (to_delete.find(p_node) == to_delete.end()) + { // since we are processing all nodes in parallel, p_node may have been deleted by another thread. In this case, we should not delete neighbour_node. + node.merged_neighbours.push_front(neighbour_node); + node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); + to_delete.insert(neighbour_node); + neighbour_node->valid = false; + } + m_ts_data->m_mutex.unlock(); } } } From 7bfd8af3db2b8492440e005b411ea7525d489c9d Mon Sep 17 00:00:00 2001 From: "xun.zhang" Date: Fri, 29 Mar 2024 15:37:11 +0800 Subject: [PATCH 18/70] ENH: port of new infill algorithm from prusa 1. initial port of new ensure vertical thickness algorithm from prusa 2. initial port of new internal bridge algorithm from prusa 3. readd interface shell Based on prusa commit 11c0e567a68979e96085b3763a76464cb793ea12 and commit f8e7d1b01c114b4d45f9e221c6b5bb935065d650 Thanks prusa. original author: PavelMikus Vojtech Bubnik jira:NEW Signed-off-by: xun.zhang Change-Id: I0d36be065a29cf87315918d720f726975a43ef9f --- src/libslic3r/BoundingBox.hpp | 44 +- src/libslic3r/BridgeDetector.hpp | 46 + src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/ClipperUtils.cpp | 10 +- src/libslic3r/ClipperUtils.hpp | 7 +- src/libslic3r/ClipperZUtils.hpp | 147 +++ src/libslic3r/Fill/Fill.cpp | 94 ++ src/libslic3r/Layer.hpp | 4 + src/libslic3r/LayerRegion.cpp | 348 +++++++ src/libslic3r/Point.cpp | 16 +- src/libslic3r/Point.hpp | 15 +- src/libslic3r/Polyline.hpp | 19 + src/libslic3r/Print.hpp | 9 +- src/libslic3r/PrintConfig.cpp | 6 +- src/libslic3r/PrintObject.cpp | 1435 ++++++++++++++++++++------- src/libslic3r/RegionExpansion.cpp | 548 ++++++++++ src/libslic3r/RegionExpansion.hpp | 122 +++ src/libslic3r/SurfaceCollection.cpp | 41 +- src/libslic3r/SurfaceCollection.hpp | 6 +- src/libslic3r/Utils.hpp | 10 + src/slic3r/GUI/Tab.cpp | 3 +- 21 files changed, 2495 insertions(+), 438 deletions(-) create mode 100644 src/libslic3r/ClipperZUtils.hpp create mode 100644 src/libslic3r/RegionExpansion.cpp create mode 100644 src/libslic3r/RegionExpansion.hpp diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 283ba3de1a..58cbdd6889 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -23,23 +23,11 @@ class BoundingBoxBase BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : min(p1), max(p1), defined(false) { merge(p2); merge(p3); } - template > - BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) + + template> + BoundingBoxBase(It from, It to) { - if (from == to) { - this->defined = false; - // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); - } else { - auto it = from; - this->min = it->template cast(); - this->max = this->min; - for (++ it; it != to; ++ it) { - auto vec = it->template cast(); - this->min = this->min.cwiseMin(vec); - this->max = this->max.cwiseMax(vec); - } - this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); - } + construct(*this, from, to); } BoundingBoxBase(const std::vector &points) @@ -88,6 +76,30 @@ class BoundingBoxBase os << "[" << bbox.max(0) - bbox.min(0) << " x " << bbox.max(1) - bbox.min(1) << "] from (" << bbox.min(0) << ", " << bbox.min(1) << ")"; return os; } + +private: + // to access construct() + friend BoundingBox get_extents(const Points &pts); + friend BoundingBox get_extents(const Points &pts); + + // if IncludeBoundary, then a bounding box is defined even for a single point. + // otherwise a bounding box is only defined if it has a positive area. + // The output bounding box is expected to be set to "undefined" initially. + template> + static void construct(BoundingBoxType &out, It from, It to) + { + if (from != to) { + auto it = from; + out.min = it->template cast(); + out.max = out.min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + out.min = out.min.cwiseMin(vec); + out.max = out.max.cwiseMax(vec); + } + out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y()); + } + } }; template diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index 064e40dd51..bec5fae866 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -71,6 +71,52 @@ class BridgeDetector { ExPolygons _anchor_regions; }; +//return ideal bridge direction and unsupported bridge endpoints distance. +inline std::tuple detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area) +{ + if (floating_edges.empty()) { + // consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges + auto [pc1, pc2] = compute_principal_components(overhang_area); + if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok + return {Vec2d{1.0,0.0}, 0.0}; + } else { + return {pc2.normalized().cast(), 0.0}; + } + } + + // Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air + std::unordered_map directions{}; + for (const Line &l : floating_edges) { + Vec2d normal = l.normal().cast().normalized(); + double quantized_angle = std::ceil(std::atan2(normal.y(),normal.x()) * 1000.0); + directions.emplace(quantized_angle, normal); + } + std::vector> direction_costs{}; + // it is acutally cost of a perpendicular bridge direction - we find the minimal cost and then return the perpendicular dir + for (const auto& d : directions) { + direction_costs.emplace_back(d.second, 0.0); + } + + for (const Line &l : floating_edges) { + Vec2d line = (l.b - l.a).cast(); + for (auto &dir_cost : direction_costs) { + // the dot product already contains the length of the line. dir_cost.first is normalized. + dir_cost.second += std::abs(line.dot(dir_cost.first)); + } + } + + Vec2d result_dir = Vec2d::Ones(); + double min_cost = std::numeric_limits::max(); + for (const auto &cost : direction_costs) { + if (cost.second < min_cost) { + // now flip the orientation back and return the direction of the bridge extrusions + result_dir = Vec2d{cost.first.y(), -cost.first.x()}; + min_cost = cost.second; + } + } + + return {result_dir, min_cost}; +}; //return ideal bridge direction and unsupported bridge endpoints distance. inline std::tuple detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 239c38b59c..108b24f30c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -403,6 +403,9 @@ set(lisbslic3r_sources Arachne/WallToolPaths.cpp Shape/TextShape.hpp Shape/TextShape.cpp + RegionExpansion.hpp + RegionExpansion.cpp + ClipperZUtils.hpp ) if (APPLE) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index ea8bd00977..590ab388a9 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -280,7 +280,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.ShortestEdgeLength = double(std::abs(offset * ClipperOffsetShortestEdgeFactor)); for (const ClipperLib::Path &path : paths) { co.Clear(); // Execute reorients the contours so that the outer most contour has a positive area. Thus the output @@ -431,7 +431,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.ShortestEdgeLength = double(std::abs(delta * ClipperOffsetShortestEdgeFactor)); co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); co.Execute(contours, delta); } @@ -452,7 +452,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.ShortestEdgeLength = double(std::abs(delta * ClipperOffsetShortestEdgeFactor)); co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out2; // Execute reorients the contours so that the outer most contour has a positive area. Thus the output @@ -751,6 +751,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr& subject, const Slic3r::ExPolygons& clip, ApplySafetyOffset do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset);} // BBS inline Slic3r::ExPolygons diff_ex(const Slic3r::Polygon& subject, const Slic3r::Polygons& clip, ApplySafetyOffset do_safety_offset) { @@ -1125,7 +1127,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v }; // Minimum edge length, squared. - double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR; + double lmin = *std::max_element(deltas.begin(), deltas.end()) * ClipperOffsetShortestEdgeFactor; double l2min = lmin * lmin; // Minimum angle to consider two edges to be parallel. // Vojtech's estimate. diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index f35fd29ba5..e150c6b6fb 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl // Miter limit is ignored for jtSquare. static constexpr const double DefaultLineMiterLimit = 0.; +// Decimation factor applied on input contour when doing offset, multiplied by the offset distance. +static constexpr const double ClipperOffsetShortestEdgeFactor = 0.005; + enum class ApplySafetyOffset { No, Yes @@ -381,7 +384,8 @@ inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float d { assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); } inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } - +inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) +{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } // Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better. // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); @@ -448,6 +452,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines diff_pl(const Slic3r::Polyline& subject, const Slic3r::Polygons& clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp new file mode 100644 index 0000000000..f6b249b47f --- /dev/null +++ b/src/libslic3r/ClipperZUtils.hpp @@ -0,0 +1,147 @@ +///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef slic3r_ClipperZUtils_hpp_ +#define slic3r_ClipperZUtils_hpp_ + +#include +#include + +#include +#include + +namespace Slic3r { + +namespace ClipperZUtils { + +using ZPoint = ClipperLib_Z::IntPoint; +using ZPoints = ClipperLib_Z::Path; +using ZPath = ClipperLib_Z::Path; +using ZPaths = ClipperLib_Z::Paths; + +inline bool zpoint_lower(const ZPoint &l, const ZPoint &r) +{ + return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z()))); +} + +// Convert a single path to path with a given Z coordinate. +// If Open, then duplicate the first point at the end. +template +inline ZPath to_zpath(const Points &path, coord_t z) +{ + ZPath out; + if (! path.empty()) { + out.reserve((path.size() + Open) ? 1 : 0); + for (const Point &p : path) + out.emplace_back(p.x(), p.y(), z); + if (Open) + out.emplace_back(out.front()); + } + return out; +} + +// Convert multiple paths to paths with a given Z coordinate. +// If Open, then duplicate the first point of each path at its end. +template +inline ZPaths to_zpaths(const VecOfPoints &paths, coord_t z) +{ + ZPaths out; + out.reserve(paths.size()); + for (const Points &path : paths) + out.emplace_back(to_zpath(path, z)); + return out; +} + +// Convert multiple expolygons into z-paths with Z specified by an index of the source expolygon +// offsetted by base_index. +// If Open, then duplicate the first point of each path at its end. +template +inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx) +{ + ZPaths out; + out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath(expoly.contour.points, base_idx)); + for (const Polygon &hole : expoly.holes) + out.emplace_back(to_zpath(hole.points, base_idx)); + ++ base_idx; + } + return out; +} + +// Convert a single path to path with a given Z coordinate. +// If Open, then duplicate the first point at the end. +template +inline Points from_zpath(const ZPoints &path) +{ + Points out; + if (! path.empty()) { + out.reserve((path.size() + Open) ? 1 : 0); + for (const ZPoint &p : path) + out.emplace_back(p.x(), p.y()); + if (Open) + out.emplace_back(out.front()); + } + return out; +} + +// Convert multiple paths to paths with a given Z coordinate. +// If Open, then duplicate the first point of each path at its end. +template +inline void from_zpaths(const ZPaths &paths, VecOfPoints &out) +{ + out.reserve(out.size() + paths.size()); + for (const ZPoints &path : paths) + out.emplace_back(from_zpath(path)); +} +template +inline VecOfPoints from_zpaths(const ZPaths &paths) +{ + VecOfPoints out; + from_zpaths(paths, out); + return out; +} + +class ClipperZIntersectionVisitor { +public: + using Intersection = std::pair; + using Intersections = std::vector; + ClipperZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {} + void reset() { m_intersections.clear(); } + void operator()(const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) { + coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() }; + coord_t *begin = srcs; + coord_t *end = srcs + 4; + //FIXME bubble sort manually? + std::sort(begin, end); + end = std::unique(begin, end); + if (begin + 1 == end) { + // Self intersection may happen on source contour. Just copy the Z value. + pt.z() = *begin; + } else { + assert(begin + 2 == end); + if (begin + 2 <= end) { + // store a -1 based negative index into the "intersections" vector here. + m_intersections.emplace_back(srcs[0], srcs[1]); + pt.z() = -coord_t(m_intersections.size()); + } + } + } + ClipperLib_Z::ZFillCallback clipper_callback() { + return [this](const ZPoint &e1bot, const ZPoint &e1top, + const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) + { return (*this)(e1bot, e1top, e2bot, e2top, pt); }; + } + + const std::vector>& intersections() const { return m_intersections; } + +private: + std::vector> &m_intersections; +}; + +} // namespace ClipperZUtils +} // namespace Slic3r + +#endif // slic3r_ClipperZUtils_hpp_ diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index d206d41c13..da876374ec 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -513,6 +513,100 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif } +Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const +{ + std::vector surface_fills = group_fills(*this); + const Slic3r::BoundingBox bbox = this->object()->bounding_box(); + const auto resolution = this->object()->print()->config().resolution.value; + + Polylines sparse_infill_polylines{}; + + for (SurfaceFill& surface_fill : surface_fills) { + if (surface_fill.surface.surface_type != stInternal) { + continue; + } + + switch (surface_fill.params.pattern) { + case ipCount: continue; break; + case ipSupportBase: continue; break; + //case ipEnsuring: continue; break; + case ipLightning: + case ipAdaptiveCubic: + case ipSupportCubic: + case ipRectilinear: + case ipMonotonic: + case ipAlignedRectilinear: + case ipGrid: + case ipTriangles: + case ipStars: + case ipCubic: + case ipLine: + case ipConcentric: + case ipHoneycomb: + case ip3DHoneycomb: + case ipGyroid: + case ipHilbertCurve: + case ipArchimedeanChords: + case ipOctagramSpiral: break; + } + + // Create the filler object. + std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); + f->set_bounding_box(bbox); + f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers. + f->z = this->print_z; + f->angle = surface_fill.params.angle; + f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + + + if (surface_fill.params.pattern == ipLightning) + dynamic_cast(f.get())->generator = lightning_generator; + + // calculate flow spacing for infill pattern generation + double link_max_length = 0.; + if (!surface_fill.params.bridge) { +#if 0 + link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); + // printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); +#else + if (surface_fill.params.density > 80.) // 80% + link_max_length = 3. * f->spacing; +#endif + } + + // Maximum length of the perimeter segment linking two infill lines. + f->link_max_length = (coord_t)scale_(link_max_length); + // Used by the concentric infill pattern to clip the loops to create extrusion paths. + f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + + LayerRegion& layerm = *m_regions[surface_fill.region_id]; + + // apply half spacing using this flow's own spacing and generate infill + FillParams params; + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = false; // surface_fill.params.dont_adjust; + params.anchor_length = surface_fill.params.anchor_length; + params.anchor_length_max = surface_fill.params.anchor_length_max; + params.resolution = resolution; + params.use_arachne = false; + params.layer_height = layerm.layer()->height; + + for (ExPolygon& expoly : surface_fill.expolygons) { + // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. + f->spacing = surface_fill.params.spacing; + surface_fill.surface.expolygon = std::move(expoly); + try { + Polylines polylines = f->fill_surface(&surface_fill.surface, params); + sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end()); + } + catch (InfillFailedException&) {} + } + } + + return sparse_infill_polylines; +} + + // Create ironing extrusions over top surfaces. void Layer::make_ironing() { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index c0763f00fd..b71f7c98a2 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -181,6 +181,10 @@ class Layer // Phony version of make_fills() without parameters for Perl integration only. void make_fills() { this->make_fills(nullptr, nullptr); } void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr); + Polylines generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree *adaptive_fill_octree, + FillAdaptive::Octree *support_fill_octree, + FillLightning::Generator* lightning_generator) const; + void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 9414dcf170..e364abefb2 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -7,6 +7,7 @@ #include "Surface.hpp" #include "BoundingBox.hpp" #include "SVG.hpp" +#include "RegionExpansion.hpp" #include #include @@ -112,6 +113,352 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec g.process_classic(); } + +#if 1 + +// Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces. +static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::initializer_list surface_types, double &thickness) +{ + size_t cnt = 0; + for (const Surface &surface : surfaces) + if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) { + ++cnt; + thickness = surface.thickness; + } + if (cnt == 0) + return {}; + + ExPolygons out; + out.reserve(cnt); + for (Surface &surface : surfaces) + if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) + out.emplace_back(std::move(surface.expolygon)); + return out; +} + +// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, +// detect bridges. +// Trim "shells" by the expanded bridges. +Surfaces expand_bridges_detect_orientations( + Surfaces &surfaces, + ExPolygons &shells, + const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill, + ExPolygons &sparse, + const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, + const float closing_radius) +{ + using namespace Slic3r::Algorithm; + + double thickness; + ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness); + if (bridges_ex.empty()) + return {}; + + // Calculate bridge anchors and their expansions in their respective shell region. + WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params_into_solid_infill.tiny_expansion, true); + std::vector bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params_into_solid_infill); + bool expanded_into_shells = ! bridge_expansions.empty(); + bool expanded_into_sparse = false; + { + WaveSeeds bridge_anchors_sparse = wave_seeds(bridges_ex, sparse, expansion_params_into_sparse_infill.tiny_expansion, true); + std::vector bridge_expansions_sparse = propagate_waves_ex(bridge_anchors_sparse, sparse, expansion_params_into_sparse_infill); + if (! bridge_expansions_sparse.empty()) { + expanded_into_sparse = true; + for (WaveSeed &seed : bridge_anchors_sparse) + seed.boundary += uint32_t(shells.size()); + for (RegionExpansionEx &expansion : bridge_expansions_sparse) + expansion.boundary_id += uint32_t(shells.size()); + append(bridge_anchors, std::move(bridge_anchors_sparse)); + append(bridge_expansions, std::move(bridge_expansions_sparse)); + } + } + + // Cache for detecting bridge orientation and merging regions with overlapping expansions. + struct Bridge { + ExPolygon expolygon; + uint32_t group_id; + std::vector::const_iterator bridge_expansion_begin; + double angle = -1; + }; + std::vector bridges; + { + bridges.reserve(bridges_ex.size()); + uint32_t group_id = 0; + for (ExPolygon &ex : bridges_ex) + bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() }); + bridges_ex.clear(); + } + + // Group the bridge surfaces by overlaps. + auto group_id = [&bridges](uint32_t src_id) { + uint32_t group_id = bridges[src_id].group_id; + while (group_id != src_id) { + src_id = group_id; + group_id = bridges[src_id].group_id; + } + bridges[src_id].group_id = group_id; + return group_id; + }; + + { + // Cache of bboxes per expansion boundary. + std::vector bboxes; + // Detect overlaps of bridge anchors inside their respective shell regions. + // bridge_expansions are sorted by boundary id and source id. + for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) { + // For each boundary region: + auto it_begin = it; + auto it_end = std::next(it_begin); + for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ; + bboxes.clear(); + bboxes.reserve(it_end - it_begin); + for (auto it2 = it_begin; it2 != it_end; ++ it2) + bboxes.emplace_back(get_extents(it2->expolygon.contour)); + // For each bridge anchor of the current source: + for (; it != it_end; ++ it) { + // A grup id for this bridge. + for (auto it2 = std::next(it); it2 != it_end; ++ it2) + if (it->src_id != it2->src_id && + bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) && + // One may ignore holes, they are irrelevant for intersection test. + ! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) { + // The two bridge regions intersect. Give them the same (lower) group id. + uint32_t id = group_id(it->src_id); + uint32_t id2 = group_id(it2->src_id); + if (id < id2) + bridges[id2].group_id = id; + else + bridges[id].group_id = id2; + } + } + } + } + + // Detect bridge directions. + { + std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary); + auto it_bridge_anchor = bridge_anchors.begin(); + Lines lines; + Polygons anchor_areas; + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { + Bridge &bridge = bridges[bridge_id]; +// lines.clear(); + anchor_areas.clear(); + int32_t last_anchor_id = -1; + for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) { + if (last_anchor_id != int(it_bridge_anchor->boundary)) { + last_anchor_id = int(it_bridge_anchor->boundary); + append(anchor_areas, to_polygons(last_anchor_id < int32_t(shells.size()) ? shells[last_anchor_id] : sparse[last_anchor_id - int32_t(shells.size())])); + } +// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) { +// reserve_more_power_of_2(lines, polyline.size() - 1); +// for (size_t i = 1; i < polyline.size(); ++ i) +// lines.push_back({ polyline[i - 1], polyline[1] }); +// } + } + lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON)))); + auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); + bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); +#if 0 + coordf_t stroke_width = scale_(0.06); + BoundingBox bbox = get_extents(anchor_areas); + bbox.merge(get_extents(bridge.expolygon)); + bbox.offset(scale_(1.)); + ::Slic3r::SVG + svg(debug_out_path(("bridge" + std::to_string(bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()), + bbox); + svg.draw(bridge.expolygon, "cyan"); + svg.draw(lines, "green", stroke_width); + svg.draw(anchor_areas, "red"); +#endif + } + } + + // Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors. + Surfaces out; + { + Polygons acc; + Surface templ{ stBottomBridge, {} }; + std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) { + return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id); + }); + for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) { + bridges[it->src_id].bridge_expansion_begin = it; + uint32_t src_id = it->src_id; + for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ; + } + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) + if (group_id(bridge_id) == bridge_id) { + // Head of the group. + acc.clear(); + for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2) + if (group_id(bridge_id2) == bridge_id) { + append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon))); + auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin; + assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2); + for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion) + append(acc, to_polygons(std::move(it_bridge_expansion->expolygon))); + } + //FIXME try to be smart and pick the best bridging angle for all? + templ.bridge_angle = bridges[bridge_id].angle; + //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) + // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. + // look for narrow_ensure_vertical_wall_thickness_region_radius filter. + ExPolygons final = closing_ex(acc, closing_radius); + // without safety offset, artifacts are generated (GH #2494) + // union_safety_offset_ex(acc) + for (ExPolygon &ex : final) + out.emplace_back(templ, std::move(ex)); + } + } + + // Clip by the expanded bridges. + if (expanded_into_shells) + shells = diff_ex(shells, out); + if (expanded_into_sparse) + sparse = diff_ex(sparse, out); + return out; +} + +// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params. +// Trim "shells" by the expanded bridges. +static Surfaces expand_merge_surfaces( + Surfaces &surfaces, + SurfaceType surface_type, + ExPolygons &shells, + const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill, + ExPolygons &sparse, + const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, + const float closing_radius, + const double bridge_angle = -1.) +{ + using namespace Slic3r::Algorithm; + + double thickness; + ExPolygons src = fill_surfaces_extract_expolygons(surfaces, {surface_type}, thickness); + if (src.empty()) + return {}; + + std::vector expansions = propagate_waves(src, shells, expansion_params_into_solid_infill); + bool expanded_into_shells = !expansions.empty(); + bool expanded_into_sparse = false; + { + std::vector expansions2 = propagate_waves(src, sparse, expansion_params_into_sparse_infill); + if (! expansions2.empty()) { + expanded_into_sparse = true; + for (RegionExpansion &expansion : expansions2) + expansion.boundary_id += uint32_t(shells.size()); + append(expansions, std::move(expansions2)); + } + } + + std::vector expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions)); + //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) + // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. + // look for narrow_ensure_vertical_wall_thickness_region_radius filter. + expanded = closing_ex(expanded, closing_radius); + // Trim the shells by the expanded expolygons. + if (expanded_into_shells) + shells = diff_ex(shells, expanded); + if (expanded_into_sparse) + sparse = diff_ex(sparse, expanded); + + Surface templ{ surface_type, {} }; + templ.bridge_angle = bridge_angle; + Surfaces out; + out.reserve(expanded.size()); + for (auto &expoly : expanded) + out.emplace_back(templ, std::move(expoly)); + return out; +} + +void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) +{ + using namespace Slic3r::Algorithm; + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + // Width of the perimeters. + float shell_width = 0; + float expansion_min = 0; + if (int num_perimeters = this->region().config().wall_loops; num_perimeters > 0) { + Flow external_perimeter_flow = this->flow(frExternalPerimeter); + Flow perimeter_flow = this->flow(frPerimeter); + shell_width = 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing(); + shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1); + expansion_min = perimeter_flow.scaled_spacing(); + } else { + // TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation + shell_width = float(SCALED_EPSILON); + expansion_min = float(SCALED_EPSILON);; + } + + // Scaled expansions of the respective external surfaces. + float expansion_top = shell_width * sqrt(2.); + float expansion_bottom = expansion_top; + float expansion_bottom_bridge = expansion_top; + // Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps. + static constexpr const float expansion_step = scaled(0.1); + // Don't take more than max_nr_steps for small expansion_step. + static constexpr const size_t max_nr_expansion_steps = 5; + // Radius (with added epsilon) to absorb empty regions emering from regularization of ensuring, viz const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; + const float closing_radius = 0.55f * 0.65f * 1.05f * this->flow(frSolidInfill).scaled_spacing(); + + // Expand the top / bottom / bridge surfaces into the shell thickness solid infills. + double layer_thickness; + ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stInternalSolid }, layer_thickness)); + ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stInternal }, layer_thickness)); + + SurfaceCollection bridges; + const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); + { + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; + const double custom_angle = this->region().config().bridge_angle.value; + const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); + bridges.surfaces = custom_angle > 0 ? + expand_merge_surfaces(fill_surfaces.surfaces, stBottomBridge, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, Geometry::deg2rad(custom_angle)) : + expand_bridges_detect_orientations(fill_surfaces.surfaces, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius); + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; +#if 0 + { + static int iRun = 0; + bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true); + } +#endif + } + + Surfaces bottoms = expand_merge_surfaces(fill_surfaces.surfaces, stBottom, shells, + RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius); + Surfaces tops = expand_merge_surfaces(fill_surfaces.surfaces, stTop, shells, + RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius); + +// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); + fill_surfaces.clear(); + reserve_more(fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size()); + { + Surface solid_templ(stInternalSolid, {}); + solid_templ.thickness = layer_thickness; + fill_surfaces.append(std::move(shells), solid_templ); + } + { + Surface sparse_templ(stInternal, {}); + sparse_templ.thickness = layer_thickness; + fill_surfaces.append(std::move(sparse), sparse_templ); + } + fill_surfaces.append(std::move(bridges.surfaces)); + fill_surfaces.append(std::move(bottoms)); + fill_surfaces.append(std::move(tops)); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +} +#else + //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. @@ -404,6 +751,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } +#endif void LayerRegion::prepare_fill_surfaces() { diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index b2427d46db..8799ad078f 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -188,18 +188,28 @@ bool has_duplicate_points(std::vector &&pts) return false; } +template BoundingBox get_extents(const Points &pts) { - return BoundingBox(pts); + BoundingBox out; + BoundingBox::construct(out, pts.begin(), pts.end()); + return out; } +template BoundingBox get_extents(const Points &pts); +template BoundingBox get_extents(const Points &pts); -BoundingBox get_extents(const std::vector &pts) +// if IncludeBoundary, then a bounding box is defined even for a single point. +// otherwise a bounding box is only defined if it has a positive area. +template +BoundingBox get_extents(const VecOfPoints &pts) { BoundingBox bbox; for (const Points &p : pts) - bbox.merge(get_extents(p)); + bbox.merge(get_extents(p)); return bbox; } +template BoundingBox get_extents(const VecOfPoints &pts); +template BoundingBox get_extents(const VecOfPoints &pts); BoundingBoxf get_extents(const std::vector &pts) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 4a3e5deb9d..92ac8f6520 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -55,6 +55,7 @@ using Points3 = std::vector; using Pointfs = std::vector; using Vec2ds = std::vector; using Pointf3s = std::vector; +using VecOfPoints = std::vector; using Matrix2f = Eigen::Matrix; using Matrix2d = Eigen::Matrix; @@ -268,8 +269,20 @@ inline Point lerp(const Point &a, const Point &b, double t) return ((1. - t) * a.cast() + t * b.cast()).cast(); } +// if IncludeBoundary, then a bounding box is defined even for a single point. +// otherwise a bounding box is only defined if it has a positive area. +template BoundingBox get_extents(const Points &pts); -BoundingBox get_extents(const std::vector &pts); +extern template BoundingBox get_extents(const Points &pts); +extern template BoundingBox get_extents(const Points &pts); + +// if IncludeBoundary, then a bounding box is defined even for a single point. +// otherwise a bounding box is only defined if it has a positive area. +template +BoundingBox get_extents(const VecOfPoints &pts); +extern template BoundingBox get_extents(const VecOfPoints &pts); +extern template BoundingBox get_extents(const VecOfPoints &pts); + BoundingBoxf get_extents(const std::vector &pts); // Test for duplicate points in a vector of points. diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 320a98fef7..074db1a1cc 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -221,6 +221,25 @@ inline void polylines_append(Polylines &dst, Polylines &&src) } } +// Merge polylines at their respective end points. +// dst_first: the merge point is at dst.begin() or dst.end()? +// src_first: the merge point is at src.begin() or src.end()? +// The orientation of the resulting polyline is unknown, the output polyline may start +// either with src piece or dst piece. +template +inline void polylines_merge(PointsType &dst, bool dst_first, PointsType &&src, bool src_first) +{ + if (dst_first) { + if (src_first) + std::reverse(dst.begin(), dst.end()); + else + std::swap(dst, src); + } else if (! src_first) + std::reverse(src.begin(), src.end()); + // Merge src into dst. + append(dst, std::move(src)); +} + const Point& leftmost_point(const Polylines &polylines); bool remove_degenerate(Polylines &polylines); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index f2123ebf53..f375e72887 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -2,6 +2,8 @@ #define slic3r_Print_hpp_ #include "PrintBase.hpp" +#include "Fill/FillAdaptive.hpp" +#include "Fill/FillLightning.hpp" #include "BoundingBox.hpp" #include "ExtrusionEntityCollection.hpp" @@ -491,7 +493,8 @@ class PrintObject : public PrintObjectBaseWithState prepare_adaptive_infill_data(); + std::pair prepare_adaptive_infill_data( + const std::vector>& surfaces_w_bottom_z) const; FillLightning::GeneratorPtr prepare_lightning_infill_data(); // BBS @@ -522,6 +525,10 @@ class PrintObject : public PrintObjectBaseWithStateslices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; + + std::pair m_adaptive_fill_octrees; + FillLightning::GeneratorPtr m_lightning_generator; + std::vector < VolumeSlices > firstLayerObjSliceByVolume; std::vector firstLayerObjSliceByGroups; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a07f2b20e3..0d7f99097c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2100,9 +2100,9 @@ void PrintConfigDef::init_fff_params() def = this->add("interface_shells", coBool); //def->label = L("Interface shells"); def->label = "Interface shells"; - //def->tooltip = L("Force the generation of solid shells between adjacent materials/volumes. " - // "Useful for multi-extruder prints with translucent materials or manual soluble " - // "support material"); + def->tooltip = L("Force the generation of solid shells between adjacent materials/volumes. " + "Useful for multi-extruder prints with translucent materials or manual soluble " + "support material"); def->category = L("Quality"); def->mode = comDevelop; def->set_default_value(new ConfigOptionBool(false)); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8e19621cac..2de1855e59 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -18,6 +18,7 @@ #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" #include "InternalBridgeDetector.hpp" +#include "AABBTreeLines.hpp" #include #include @@ -26,6 +27,7 @@ #include #include +#include #include @@ -268,6 +270,10 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); } + // Add solid fills to ensure the shell vertical thickness. + this->discover_vertical_shells(); + m_print->throw_if_canceled(); + // this will detect bridges and reverse bridges // and rearrange top/bottom/internal surfaces // It produces enlarged overlapping bridging areas. @@ -280,9 +286,6 @@ void PrintObject::prepare_infill() this->process_external_surfaces(); m_print->throw_if_canceled(); - // Add solid fills to ensure the shell vertical thickness. - this->discover_vertical_shells(); - m_print->throw_if_canceled(); // Debugging output. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -368,17 +371,16 @@ void PrintObject::infill() if (this->set_started(posInfill)) { m_print->set_status(35, L("Generating infill toolpath")); - auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data(); - auto lightning_generator = this->prepare_lightning_infill_data(); - + const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first; + const auto& support_fill_octree = this->m_adaptive_fill_octrees.second; BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &lightning_generator](const tbb::blocked_range& range) { + [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), lightning_generator.get()); + m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get()); } } ); @@ -554,7 +556,8 @@ void PrintObject::simplify_extrusion_path() } } -std::pair PrintObject::prepare_adaptive_infill_data() +std::pair PrintObject::prepare_adaptive_infill_data( + const std::vector> &surfaces_w_bottom_z) const { using namespace FillAdaptive; @@ -568,21 +571,18 @@ std::pair PrintObject::prepare its_transform(mesh, to_octree * this->trafo_centered(), true); // Triangulate internal bridging surfaces. - std::vector> overhangs(this->layers().size()); - tbb::parallel_for( - tbb::blocked_range(0, int(m_layers.size()) - 1), - [this, &to_octree, &overhangs](const tbb::blocked_range &range) { - std::vector &out = overhangs[range.begin()]; - for (int idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + std::vector> overhangs(std::max(surfaces_w_bottom_z.size(), size_t(1))); + // ^ make sure vector is not empty, even with no briding surfaces we still want to build the adaptive trees later, some continue normally + tbb::parallel_for(tbb::blocked_range(0, surfaces_w_bottom_z.size()), + [this, &to_octree, &overhangs, &surfaces_w_bottom_z](const tbb::blocked_range &range) { + for (int surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) { + std::vector &out = overhangs[surface_idx]; m_print->throw_if_canceled(); - const Layer *layer = this->layers()[idx_layer]; - for (const LayerRegion *layerm : layer->regions()) - for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternalBridge) - append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z())); + append(out, triangulate_expolygon_3d(surfaces_w_bottom_z[surface_idx].first->expolygon, + surfaces_w_bottom_z[surface_idx].second)); + for (Vec3d &p : out) + p = (to_octree * p).eval(); } - for (Vec3d &p : out) - p = (to_octree * p).eval(); }); // and gather them. for (size_t i = 1; i < overhangs.size(); ++ i) @@ -1229,7 +1229,7 @@ void PrintObject::process_external_surfaces() if (has_voids && m_layers.size() > 1) { // All but stInternal fill surfaces will get expanded and possibly trimmed. std::vector layer_expansions_and_voids(m_layers.size(), false); - for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { + for (size_t layer_idx = 1; layer_idx < m_layers.size(); ++ layer_idx) { const Layer *layer = m_layers[layer_idx]; bool expansions = false; bool voids = false; @@ -1305,74 +1305,52 @@ void PrintObject::discover_vertical_shells() }; bool spiral_mode = this->print()->config().spiral_mode.value; size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size(); - coordf_t min_layer_height = this->slicing_parameters().min_layer_height; - // Does this region possibly produce more than 1 top or bottom layer? - auto has_extra_layers_fn = [min_layer_height](const PrintRegionConfig &config) { - auto num_extra_layers = [min_layer_height](int num_solid_layers, coordf_t min_shell_thickness) { - if (num_solid_layers == 0) - return 0; - int n = num_solid_layers - 1; - int n2 = int(ceil(min_shell_thickness / min_layer_height)); - return std::max(n, n2 - 1); - }; - return num_extra_layers(config.top_shell_layers, config.top_shell_thickness) + - num_extra_layers(config.bottom_shell_layers, config.bottom_shell_thickness) > 0; - }; + std::vector cache_top_botom_regions(num_layers, DiscoverVerticalShellsCacheEntry()); bool top_bottom_surfaces_all_regions = this->num_printing_regions() > 1 && ! m_config.interface_shells.value; + + static constexpr float top_bottom_expansion_coeff = 0.05f; if (top_bottom_surfaces_all_regions) { // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness // is calculated over all materials. - // Is the "ensure vertical wall thickness" applicable to any region? - bool has_extra_layers = false; - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { - const PrintRegionConfig &config = this->printing_region(region_id).config(); - if (config.ensure_vertical_shell_thickness.value && has_extra_layers_fn(config)) { - has_extra_layers = true; - break; - } - } - if (! has_extra_layers) - // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit. - return; BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { - const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; + const std::initializer_list surfaces_bottom{ stBottom, stBottomBridge }; const size_t num_regions = this->num_printing_regions(); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { m_print->throw_if_canceled(); - const Layer &layer = *m_layers[idx_layer]; - DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer]; + const Layer& layer = *m_layers[idx_layer]; + DiscoverVerticalShellsCacheEntry& cache = cache_top_botom_regions[idx_layer]; // Simulate single set of perimeters over all merged regions. float perimeter_offset = 0.f; float perimeter_min_spacing = FLT_MAX; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; - ++ debug_idx; + ++debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - for (size_t region_id = 0; region_id < num_regions; ++ region_id) { - LayerRegion &layerm = *layer.m_regions[region_id]; - float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; + for (size_t region_id = 0; region_id < num_regions; ++region_id) { + LayerRegion& layerm = *layer.m_regions[region_id]; + float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; // Top surfaces. - append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing)); - append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); - // Bottom surfaces. - append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); - // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. - // First find the maxium number of perimeters per region slice. + append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion)); + // append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); + // Bottom surfaces. + append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion)); + // append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), top_bottom_expansion)); + // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. + // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; - for (Surface &s : layerm.slices.surfaces) + for (const Surface& s : layerm.slices.surfaces) perimeters = std::max(perimeters, s.extra_perimeters); perimeters += layerm.region().config().wall_loops.value; // Then calculate the infill offset. if (perimeters > 0) { Flow extflow = layerm.flow(frExternalPerimeter); - Flow flow = layerm.flow(frPerimeter); + Flow flow = layerm.flow(frPerimeter); perimeter_offset = std::max(perimeter_offset, 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing()); perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing()))); @@ -1380,12 +1358,12 @@ void PrintObject::discover_vertical_shells() polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); } // Save some computing time by reducing the number of polygons. - cache.top_surfaces = union_(cache.top_surfaces); + cache.top_surfaces = union_(cache.top_surfaces); cache.bottom_surfaces = union_(cache.bottom_surfaces); // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. - polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing)); + polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, -perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); @@ -1397,23 +1375,12 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } cache.holes = union_(cache.holes); - } - }); + }}); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - PROFILE_BLOCK(discover_vertical_shells_region); - - const PrintRegion ®ion = this->printing_region(region_id); - if (! region.config().ensure_vertical_shell_thickness.value) - // This region will be handled by discover_horizontal_shells(). - continue; - if (! has_extra_layers_fn(region.config())) - // Zero or 1 layer, there is no additional vertical wall thickness enforced. - continue; - //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -1424,19 +1391,19 @@ void PrintObject::discover_vertical_shells() tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, region_id, &cache_top_botom_regions](const tbb::blocked_range& range) { - const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; + const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); - Layer &layer = *m_layers[idx_layer]; - LayerRegion &layerm = *layer.m_regions[region_id]; - float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; + Layer &layer = *m_layers[idx_layer]; + LayerRegion &layerm = *layer.m_regions[region_id]; + float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; - cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing); - append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); + cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion); +// append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); // Bottom surfaces. - cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion); +// append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), top_bottom_expansion)); // Holes over all regions. Only collect them once, they are valid for all region_id iterations. if (cache.holes.empty()) { for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) @@ -1449,13 +1416,13 @@ void PrintObject::discover_vertical_shells() } BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness"; + grain_size = 1; tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, region_id, &cache_top_botom_regions] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - PROFILE_BLOCK(discover_vertical_shells_region_layer); m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; @@ -1467,12 +1434,12 @@ void PrintObject::discover_vertical_shells() const PrintRegionConfig ®ion_config = layerm->region().config(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial"); - layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-initial"); + layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial"); + layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ Flow solid_infill_flow = layerm->flow(frSolidInfill); - coord_t infill_line_spacing = solid_infill_flow.scaled_spacing(); + coord_t infill_line_spacing = solid_infill_flow.scaled_spacing(); // Find a union of perimeters below / above this surface to guarantee a minimum shell thickness. Polygons shell; Polygons holes; @@ -1480,88 +1447,117 @@ void PrintObject::discover_vertical_shells() ExPolygons shell_ex; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; - { - PROFILE_BLOCK(discover_vertical_shells_region_layer_collect); #if 0 // #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box()); - for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) { - if (n < 0 || n >= (int)m_layers.size()) - continue; - ExPolygons &expolys = m_layers[n]->perimeter_expolygons; - for (size_t i = 0; i < expolys.size(); ++ i) { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i])); - svg.draw(expolys[i]); - svg.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - svg.Close(); - - svg_cummulative.draw(expolys[i]); - svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - } + { + Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box()); + for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) { + if (n < 0 || n >= (int)m_layers.size()) + continue; + ExPolygons &expolys = m_layers[n]->perimeter_expolygons; + for (size_t i = 0; i < expolys.size(); ++ i) { + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i])); + svg.draw(expolys[i]); + svg.draw_outline(expolys[i].contour, "black", scale_(0.05)); + svg.draw_outline(expolys[i].holes, "blue", scale_(0.05)); + svg.Close(); + + svg_cummulative.draw(expolys[i]); + svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05)); + svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); } } + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - polygons_append(holes, cache_top_botom_regions[idx_layer].holes); - if (int n_top_layers = region_config.top_shell_layers.value; n_top_layers > 0) { - // Gather top regions projected to this layer. - coordf_t print_z = layer->print_z; - for (int i = int(idx_layer) + 1; - i < int(cache_top_botom_regions.size()) && - (i < int(idx_layer) + n_top_layers || - m_layers[i]->print_z - print_z < region_config.top_shell_thickness - EPSILON); - ++ i) { - const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - if (! holes.empty()) - holes = intersection(holes, cache.holes); - if (! cache.top_surfaces.empty()) { - polygons_append(shell, cache.top_surfaces); - // Running the union_ using the Clipper library piece by piece is cheaper - // than running the union_ all at once. - shell = union_(shell); - } - } + polygons_append(holes, cache_top_botom_regions[idx_layer].holes); + auto combine_holes = [&holes](const Polygons &holes2) { + if (holes.empty() || holes2.empty()) + holes.clear(); + else + holes = intersection(holes, holes2); + }; + auto combine_shells = [&shell](const Polygons &shells2) { + if (shell.empty()) + shell = std::move(shells2); + else if (! shells2.empty()) { + polygons_append(shell, shells2); + // Running the union_ using the Clipper library piece by piece is cheaper + // than running the union_ all at once. + shell = union_(shell); + } + }; + static constexpr const bool one_more_layer_below_top_bottom_surfaces = false; + if (int n_top_layers = region_config.top_shell_layers.value; n_top_layers > 0) { + // Gather top regions projected to this layer. + coordf_t print_z = layer->print_z; + int i = int(idx_layer) + 1; + int itop = int(idx_layer) + n_top_layers; + bool at_least_one_top_projected = false; + for (; i < int(cache_top_botom_regions.size()) && + (i < itop || m_layers[i]->print_z - print_z < region_config.top_shell_thickness - EPSILON); + ++ i) { + at_least_one_top_projected = true; + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + combine_holes(cache.holes); + combine_shells(cache.top_surfaces); } - if (int n_bottom_layers = region_config.bottom_shell_layers.value; n_bottom_layers > 0) { - // Gather bottom regions projected to this layer. - coordf_t bottom_z = layer->bottom_z(); - for (int i = int(idx_layer) - 1; - i >= 0 && - (i > int(idx_layer) - n_bottom_layers || - bottom_z - m_layers[i]->bottom_z() < region_config.bottom_shell_thickness - EPSILON); - -- i) { - const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - if (! holes.empty()) - holes = intersection(holes, cache.holes); - if (! cache.bottom_surfaces.empty()) { - polygons_append(shell, cache.bottom_surfaces); - // Running the union_ using the Clipper library piece by piece is cheaper - // than running the union_ all at once. - shell = union_(shell); - } - } + if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) { + // Lets consider this a special case - with only 1 top solid and minimal shell thickness settings, the + // boundaries of solid layers are not anchored over/under perimeters, so lets fix it by adding at least one + // perimeter width of area + Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].top_surfaces, + layerm->flow(frExternalPerimeter).scaled_spacing()), + to_polygons(m_layers[i]->lslices)); + combine_shells(anchor_area); + } + + if (one_more_layer_below_top_bottom_surfaces) + if (i < int(cache_top_botom_regions.size()) && + (i <= itop || m_layers[i]->bottom_z() - print_z < region_config.top_shell_thickness - EPSILON)) + combine_holes(cache_top_botom_regions[i].holes); + } + if (int n_bottom_layers = region_config.bottom_shell_layers.value; n_bottom_layers > 0) { + // Gather bottom regions projected to this layer. + coordf_t bottom_z = layer->bottom_z(); + int i = int(idx_layer) - 1; + int ibottom = int(idx_layer) - n_bottom_layers; + bool at_least_one_bottom_projected = false; + for (; i >= 0 && + (i > ibottom || bottom_z - m_layers[i]->bottom_z() < region_config.bottom_shell_thickness - EPSILON); + -- i) { + at_least_one_bottom_projected = true; + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + combine_holes(cache.holes); + combine_shells(cache.bottom_surfaces); } -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); - svg.draw(shell); - svg.draw_outline(shell, "black", scale_(0.05)); - svg.Close(); + + if (!at_least_one_bottom_projected && i >= 0) { + Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].bottom_surfaces, + layerm->flow(frExternalPerimeter).scaled_spacing()), + to_polygons(m_layers[i]->lslices)); + combine_shells(anchor_area); } + + if (one_more_layer_below_top_bottom_surfaces) + if (i >= 0 && + (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) + combine_holes(cache_top_botom_regions[i].holes); + } +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); + svg.draw(shell); + svg.draw_outline(shell, "black", scale_(0.05)); + svg.Close(); + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #if 0 - { - PROFILE_BLOCK(discover_vertical_shells_region_layer_shell_); - // shell = union_(shell, true); - shell = union_(shell, false); - } +// shell = union_(shell, true); + shell = union_(shell, false); #endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - shell_ex = union_safety_offset_ex(shell); + shell_ex = union_safety_offset_ex(shell); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - } //if (shell.empty()) // continue; @@ -1571,40 +1567,39 @@ void PrintObject::discover_vertical_shells() Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-after-union-%d.svg", debug_idx), get_extents(shell)); svg.draw(shell_ex); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); + svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces.filter_by_type(stInternal), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternal), "black", "blue", scale_(0.05)); + svg.draw(layerm->fill_surfaces().filter_by_type(stInternal), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternal), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); - } + } { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); + svg.draw(layerm->fill_surfaces().filter_by_type(stInternalVoid), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); - } + } { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalsolid-wshell-%d.svg", debug_idx), get_extents(shell)); + svg.draw(layerm->fill_surfaces().filter_by_type(stInternalSolid), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalSolid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); - } + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the shells region by the internal & internal void surfaces. - const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; - const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3)); + const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types({ stInternal, stInternalVoid, stInternalSolid })); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) @@ -1619,31 +1614,59 @@ void PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING Polygons shell_before = shell; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#if 1 - // Intentionally inflate a bit more than how much the region has been shrunk, - // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); - if (shell.empty()) - continue; -#else - // Ensure each region is at least 3x infill line width wide, so it could be filled in. - // float margin = float(infill_line_spacing) * 3.f; - float margin = float(infill_line_spacing) * 1.5f; - // we use a higher miterLimit here to handle areas with acute angles - // in those cases, the default miterLimit would cut the corner and we'd - // get a triangle in $too_narrow; if we grow it below then the shell - // would have a different shape from the external surface and we'd still - // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true); - if (! too_narrow.empty()) { - // grow the collapsing parts and add the extra area to the neighbor layer - // as well as to our original surfaces so that we support this - // additional area in the next shell too - // make sure our grown surfaces don't exceed the fill area - polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal)); + ExPolygons regularized_shell; + { + // Open to remove (filter out) regions narrower than a bit less than an infill extrusion line width. + // Such narrow regions are difficult to fill in with a gap fill algorithm (or Arachne), however they are most likely + // not needed for print stability / quality. + const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; + // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill, + // thus they will be merged into the solid infill. + const float narrow_sparse_infill_region_radius = 0.5f * 1.2f * min_perimeter_infill_spacing; + // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. + const float tiny_overlap_radius = 0.2f * min_perimeter_infill_spacing; + regularized_shell = shrink_ex(offset2_ex(union_ex(shell), + // Open to remove (filter out) regions narrower than an infill extrusion line width. + -narrow_ensure_vertical_wall_thickness_region_radius, + // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill. + narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, ClipperLib::jtSquare), + // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. + narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); + + Polygons object_volume; + Polygons internal_volume; + { + Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; + Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ? + to_polygons(m_layers[idx_layer + 1]->lslices) : + Polygons{}; + object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); + internal_volume = closing(polygonsInternal, float(SCALED_EPSILON)); + } + + // The regularization operation may cause scattered tiny drops on the smooth parts of the model, filter them out + // If the region checks both following conditions, it is removed: + // 1. the area is very small, + // OR the area is quite small and it is fully wrapped in model (not visible) + // the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy + // 2. the area does not fully cover an internal polygon + // This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small + regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(), + [&internal_volume, &min_perimeter_infill_spacing, + &object_volume](const ExPolygon &p) { + return (p.area() < min_perimeter_infill_spacing * scaled(1.5) || + (p.area() < min_perimeter_infill_spacing * scaled(8.0) && + diff(to_polygons(p), object_volume).empty())) && + diff(internal_volume, + expand(to_polygons(p), min_perimeter_infill_spacing)) + .size() >= internal_volume.size(); + }), + regularized_shell.end()); } -#endif - ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell); + if (regularized_shell.empty()) + continue; + + ExPolygons new_internal_solid = intersection_ex(polygonsInternal, regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); @@ -1653,13 +1676,13 @@ void PrintObject::discover_vertical_shells() svg.draw_outline(union_safety_offset_ex(shell), "black", "blue", scale_(0.05)); // Regularized infill region. svg.draw_outline(new_internal_solid, "red", "magenta", scale_(0.05)); - svg.Close(); + svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), shell); - Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), shell); + Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell); + Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1670,8 +1693,7 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Assign resulting internal surfaces to layer. - const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge }; - layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); + layerm->fill_surfaces.keep_types({ stTop, stBottom, stBottomBridge }); layerm->fill_surfaces.append(new_internal, stInternal); layerm->fill_surfaces.append(new_internal_void, stInternalVoid); layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); @@ -1694,6 +1716,7 @@ void PrintObject::discover_vertical_shells() // PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str()); } +#if 0 /* This method applies bridge flow to the first internal solid layer above sparse infill */ void PrintObject::bridge_over_infill() @@ -1861,7 +1884,833 @@ void PrintObject::bridge_over_infill() } } } +#else +// This method applies bridge flow to the first internal solid layer above sparse infill. +// This method applies bridge flow to the first internal solid layer above sparse infill. +void PrintObject::bridge_over_infill() +{ + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); + + struct CandidateSurface + { + CandidateSurface(const Surface *original_surface, + int layer_index, + Polygons new_polys, + const LayerRegion *region, + double bridge_angle) + : original_surface(original_surface) + , layer_index(layer_index) + , new_polys(new_polys) + , region(region) + , bridge_angle(bridge_angle) + {} + const Surface *original_surface; + int layer_index; + Polygons new_polys; + const LayerRegion *region; + double bridge_angle; + }; + + std::map> surfaces_by_layer; + + // SECTION to gather and filter surfaces for expanding, and then cluster them by layer + { + tbb::concurrent_vector candidate_surfaces; + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = static_cast(this), + &candidate_surfaces](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); + if (layer->lower_layer == nullptr) { + continue; + } + double spacing = layer->regions().front()->flow(frSolidInfill).scaled_spacing(); + // unsupported area will serve as a filter for polygons worth bridging. + Polygons unsupported_area; + Polygons lower_layer_solids; + for (const LayerRegion *region : layer->lower_layer->regions()) { + Polygons fill_polys = to_polygons(region->fill_expolygons); + // initially consider the whole layer unsupported, but also gather solid layers to later cut off supported parts + unsupported_area.insert(unsupported_area.end(), fill_polys.begin(), fill_polys.end()); + for (const Surface &surface : region->fill_surfaces.surfaces) { + if (surface.surface_type != stInternal || region->region().config().sparse_infill_density.value == 100) { + Polygons p = to_polygons(surface.expolygon); + lower_layer_solids.insert(lower_layer_solids.end(), p.begin(), p.end()); + } + } + } + unsupported_area = closing(unsupported_area, float(SCALED_EPSILON)); + // By expanding the lower layer solids, we avoid making bridges from the tiny internal overhangs that are (very likely) supported by previous layer solids + // NOTE that we cannot filter out polygons worth bridging by their area, because sometimes there is a very small internal island that will grow into large hole + lower_layer_solids = shrink(lower_layer_solids, 1 * spacing); // first remove thin regions that will not support anything + lower_layer_solids = expand(lower_layer_solids, (1 + 3) * spacing); // then expand back (opening), and further for parts supported by internal solids + // By shrinking the unsupported area, we avoid making bridges from narrow ensuring region along perimeters. + unsupported_area = shrink(unsupported_area, 3 * spacing); + unsupported_area = diff(unsupported_area, lower_layer_solids); + + for (LayerRegion *region : layer->regions()) { + SurfacesPtr region_internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid); + for (const Surface *s : region_internal_solids) { + Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); + // The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported. + // These regions can be filtered by area, because they for sure are touching solids on lower layers, and it does not make sense to bridge their tiny overhangs + bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON; + if (!unsupported.empty() && (!partially_supported || area(unsupported) > 3 * 3 * spacing * spacing)) { + Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 4 * spacing)); + // after we extracted the part worth briding, we go over the leftovers and merge the tiny ones back, to not brake the surface too much + for (const Polygon& p : diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))) { + double area = p.area(); + if (area < spacing * scale_(12.0) && area > spacing * spacing) { + worth_bridging.push_back(p); + } + } + worth_bridging = intersection(closing(worth_bridging, float(SCALED_EPSILON)), s->expolygon); + candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0)); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_candidate_surface_" + std::to_string(area(s->expolygon)), + to_lines(region->layer()->lslices), to_lines(s->expolygon), to_lines(worth_bridging), + to_lines(unsupported_area)); +#endif +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_candidate_processing_" + std::to_string(area(unsupported)), + to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))), + to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))), + to_lines(unsupported_area)); +#endif + } + } + } + } + }); + + for (const CandidateSurface &c : candidate_surfaces) { + surfaces_by_layer[c.layer_index].push_back(c); + } + } + + // LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the + // lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends. + // It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure. + bool has_lightning_infill = false; + for (size_t i = 0; i < this->num_printing_regions(); i++) { + if (this->printing_region(i).config().sparse_infill_pattern == ipLightning) { + has_lightning_infill = true; + break; + } + } + if (has_lightning_infill) { + // Prepare backup data for the Layer Region infills. Before modfiyng the layer region, we backup its fill surfaces by moving! them into this map. + // then a copy is created, modifiyed and passed to lightning infill generator. After generator is created, we restore the original state of the fills + // again by moving the data from this map back to the layer regions. This ensures that pointers to surfaces stay valid. + std::map> backup_surfaces; + for (size_t lidx = 0; lidx < this->layer_count(); lidx++) { + backup_surfaces[lidx] = {}; + } + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &backup_surfaces, + &surfaces_by_layer](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end()) + continue; + + Layer *layer = po->get_layer(lidx); + const Layer *lower_layer = layer->lower_layer; + if (lower_layer == nullptr) + continue; + + Polygons lightning_fill; + for (LayerRegion *region : lower_layer->regions()) { + if (region->region().config().sparse_infill_pattern == ipLightning) { + Polygons lf = to_polygons(region->fill_surfaces.filter_by_type(stInternal)); + lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end()); + } + } + + if (lightning_fill.empty()) + continue; + + for (LayerRegion *region : layer->regions()) { + backup_surfaces[lidx][region] = std::move( + region->fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid + // Copy the surfaces back, this will make copy, but we will later discard it anyway + region->fill_surfaces = backup_surfaces[lidx][region]; + } + + for (LayerRegion *region : layer->regions()) { + ExPolygons sparse_infill = to_expolygons(region->fill_surfaces.filter_by_type(stInternal)); + ExPolygons solid_infill = to_expolygons(region->fill_surfaces.filter_by_type(stInternalSolid)); + + if (sparse_infill.empty()) { + break; + } + for (const auto &surface : surfaces_by_layer[lidx]) { + if (surface.region != region) + continue; + ExPolygons expansion = intersection_ex(sparse_infill, expand(surface.new_polys, scaled(3.0))); + solid_infill.insert(solid_infill.end(), expansion.begin(), expansion.end()); + } + + solid_infill = union_safety_offset_ex(solid_infill); + sparse_infill = diff_ex(sparse_infill, solid_infill); + + region->fill_surfaces.remove_types({stInternalSolid, stInternal}); + for (const ExPolygon &ep : solid_infill) { + region->fill_surfaces.surfaces.emplace_back(stInternalSolid, ep); + } + for (const ExPolygon &ep : sparse_infill) { + region->fill_surfaces.surfaces.emplace_back(stInternal, ep); + } + } + } + }); + + // Use the modified surfaces to generate expanded lightning anchors + this->m_lightning_generator = this->prepare_lightning_infill_data(); + + // And now restore carefully the original surfaces, again using move to avoid reallocation and preserving the validity of the + // pointers in surface candidates + for (size_t lidx = 0; lidx < this->layer_count(); lidx++) { + Layer *layer = this->get_layer(lidx); + for (LayerRegion *region : layer->regions()) { + if (backup_surfaces[lidx].find(region) != backup_surfaces[lidx].end()) { + region->fill_surfaces = std::move(backup_surfaces[lidx][region]); + } + } + } + } + + std::map infill_lines; + // SECTION to generate infill polylines + { + std::vector> surfaces_w_bottom_z; + for (const auto &pair : surfaces_by_layer) { + for (const CandidateSurface &c : pair.second) { + surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z()); + } + } + + this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); + + std::vector layers_to_generate_infill; + for (const auto &pair : surfaces_by_layer) { + assert(pair.first > 0); + infill_lines[pair.first - 1] = {}; + layers_to_generate_infill.push_back(pair.first - 1); + } + + tbb::parallel_for(tbb::blocked_range(0, layers_to_generate_infill.size()), [po = static_cast(this), + &layers_to_generate_infill, + &infill_lines](tbb::blocked_range r) { + for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { + size_t lidx = layers_to_generate_infill[job_idx]; + infill_lines.at( + lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(po->m_adaptive_fill_octrees.first.get(), + po->m_adaptive_fill_octrees.second.get(), + po->m_lightning_generator.get()); + } + }); +#ifdef DEBUG_BRIDGE_OVER_INFILL + for (const auto &il : infill_lines) { + debug_draw(std::to_string(il.first) + "_infill_lines", to_lines(get_layer(il.first)->lslices), to_lines(il.second), {}, {}); + } +#endif + } + + // cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another + std::vector> clustered_layers_for_threads; + float target_flow_height_factor = 0.9f; + { + std::vector layers_with_candidates; + std::map layer_area_covered_by_candidates; + for (const auto& pair : surfaces_by_layer) { + layers_with_candidates.push_back(pair.first); + layer_area_covered_by_candidates[pair.first] = {}; + } + + // prepare inflated filter for each candidate on each layer. layers will be put into single thread cluster if they are close to each other (z-axis-wise) + // and if the inflated AABB polygons overlap somewhere + tbb::parallel_for(tbb::blocked_range(0, layers_with_candidates.size()), [&layers_with_candidates, &surfaces_by_layer, + &layer_area_covered_by_candidates]( + tbb::blocked_range r) { + for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { + size_t lidx = layers_with_candidates[job_idx]; + for (const auto &candidate : surfaces_by_layer.at(lidx)) { + Polygon candiate_inflated_aabb = get_extents(candidate.new_polys).inflated(scale_(7)).polygon(); + layer_area_covered_by_candidates.at(lidx) = union_(layer_area_covered_by_candidates.at(lidx), + Polygons{candiate_inflated_aabb}); + } + } + }); + + // note: surfaces_by_layer is ordered map + for (auto pair : surfaces_by_layer) { + if (clustered_layers_for_threads.empty() || + this->get_layer(clustered_layers_for_threads.back().back())->print_z < + this->get_layer(pair.first)->print_z - + this->get_layer(pair.first)->regions()[0]->bridging_flow(frSolidInfill, true).height() * target_flow_height_factor - + EPSILON || + intersection(layer_area_covered_by_candidates[clustered_layers_for_threads.back().back()], + layer_area_covered_by_candidates[pair.first]) + .empty()) { + clustered_layers_for_threads.push_back({pair.first}); + } else { + clustered_layers_for_threads.back().push_back(pair.first); + } + } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + std::cout << "BRIDGE OVER INFILL CLUSTERED LAYERS FOR SINGLE THREAD" << std::endl; + for (auto cluster : clustered_layers_for_threads) { + std::cout << "CLUSTER: "; + for (auto l : cluster) { + std::cout << l << " "; + } + std::cout << std::endl; + } +#endif + } + + // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. + auto gather_areas_w_depth = [target_flow_height_factor](const PrintObject *po, int lidx, float target_flow_height) { + // Gather layers sparse infill areas, to depth defined by used bridge flow + ExPolygons layers_sparse_infill{}; + ExPolygons not_sparse_infill{}; + double bottom_z = po->get_layer(lidx)->print_z - target_flow_height * target_flow_height_factor - EPSILON; + for (int i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z and at least one iteration was made + const Layer *layer = po->get_layer(i); + if (layer->print_z < bottom_z && i < int(lidx) - 1) + break; + + for (const LayerRegion *region : layer->regions()) { + bool has_low_density = region->region().config().sparse_infill_density.value < 100; + for (const Surface &surface : region->fill_surfaces.surfaces) { + if ((surface.surface_type == stInternal && has_low_density) || surface.surface_type == stInternalVoid ) { + layers_sparse_infill.push_back(surface.expolygon); + } else { + not_sparse_infill.push_back(surface.expolygon); + } + } + } + } + layers_sparse_infill = union_ex(layers_sparse_infill); + layers_sparse_infill = closing_ex(layers_sparse_infill, float(SCALED_EPSILON)); + not_sparse_infill = union_ex(not_sparse_infill); + not_sparse_infill = closing_ex(not_sparse_infill, float(SCALED_EPSILON)); + return diff(layers_sparse_infill, not_sparse_infill); + }; + + // LAMBDA do determine optimal bridging angle + auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern) { + AABBTreeLines::LinesDistancer lines_tree(anchors); + + std::map counted_directions; + for (const Polygon &p : bridged_area) { + double acc_distance = 0; + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + acc_distance += dist_to_next; + if (acc_distance > scaled(2.0)) { + acc_distance = 0.0; + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(2.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a = (start + v * (i * step_size)).cast(); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + double angle = lines_tree.get_line(index).orientation(); + if (angle > PI) { + angle -= PI; + } + angle += PI * 0.5; + counted_directions[angle]++; + } + } + } + } + + std::pair best_dir{0, 0}; + // sliding window accumulation + for (const auto &dir : counted_directions) { + int score_acc = 0; + double dir_acc = 0; + double window_start_angle = dir.first - PI * 0.1; + double window_end_angle = dir.first + PI * 0.1; + for (auto dirs_window = counted_directions.lower_bound(window_start_angle); + dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + // current span of directions is 0.5 PI to 1.5 PI (due to the aproach.). Edge values should also account for the + // opposite direction. + if (window_start_angle < 0.5 * PI) { + for (auto dirs_window = counted_directions.lower_bound(1.5 * PI - (0.5 * PI - window_start_angle)); + dirs_window != counted_directions.end(); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } + if (window_start_angle > 1.5 * PI) { + for (auto dirs_window = counted_directions.begin(); + dirs_window != counted_directions.upper_bound(window_start_angle - 1.5 * PI); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } + + if (score_acc > best_dir.second) { + best_dir = {dir_acc / score_acc, score_acc}; + } + } + double bridging_angle = best_dir.first; + if (bridging_angle == 0) { + bridging_angle = 0.001; + } + switch (dominant_pattern) { + case ipHilbertCurve: bridging_angle += 0.25 * PI; break; + case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break; + default: break; + } + + return bridging_angle; + }; + + // LAMBDA that will fill given polygons with lines, exapand the lines to the nearest anchor, and reconstruct polygons from the newly + // generated lines + auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle) { + auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { + for (Line &l : lines) { + double ax = double(l.a.x()); + double ay = double(l.a.y()); + l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); + l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); + double bx = double(l.b.x()); + double by = double(l.b.y()); + l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); + l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); + } + }; + + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || + (bhigh >= alow && bhigh <= ahigh); + }; + + Polygons expanded_bridged_area{}; + double aligning_angle = -bridging_angle + PI * 0.5; + { + polygons_rotate(bridged_area, aligning_angle); + lines_rotate(anchors, cos(aligning_angle), sin(aligning_angle)); + BoundingBox bb_x = get_extents(bridged_area); + BoundingBox bb_y = get_extents(anchors); + + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + bridging_flow.scaled_spacing() - 1) / bridging_flow.scaled_spacing(); + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb_x.min.x() + i * bridging_flow.scaled_spacing(); + coord_t y_min = bb_y.min.y() - bridging_flow.scaled_spacing(); + coord_t y_max = bb_y.max.y() + bridging_flow.scaled_spacing(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors)}; + auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; + + std::vector> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); + for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { + if (bridged_area_tree.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2) < 0) { + polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, + area_intersections[intersection_idx + 1].first); + } + } + auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); + + for (Line §ion : polygon_sections[i]) { + auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a, + [](const Point &a, const std::pair &b) { + return a.y() > b.first.y(); + }); + if (maybe_below_anchor != anchors_intersections.rend()) { + section.a = maybe_below_anchor->first; + section.a.y() -= bridging_flow.scaled_width() * (0.5 + 0.5); + } + + auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_upper_anchor != anchors_intersections.end()) { + section.b = maybe_upper_anchor->first; + section.b.y() += bridging_flow.scaled_width() * (0.5 + 0.5); + } + } + + for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { + Line §ion_a = polygon_sections[i][section_idx]; + Line §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.a.y(), section_a.b.y(), section_b.a.y(), section_b.b.y())) { + section_b.a = section_a.a.y() < section_b.a.y() ? section_a.a : section_b.a; + section_b.b = section_a.b.y() < section_b.b.y() ? section_b.b : section_a.b; + section_a.a = section_a.b; + } + } + + polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[i].end()); + std::sort(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &a, const Line &b) { return a.a.y() < b.b.y(); }); + } + + // reconstruct polygon from polygon sections + struct TracedPoly + { + Points lows; + Points highs; + }; + + std::vector current_traced_polys; + for (const auto &polygon_slice : polygon_sections) { + std::unordered_set used_segments; + for (TracedPoly &traced_poly : current_traced_polys) { + auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(), + [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); + auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(), + [](const Point &high, const Line &seg) { return seg.a.y() > high.y(); }); + + bool segment_added = false; + for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) { + if (used_segments.find(&(*candidate)) != used_segments.end()) { + continue; + } + + if ((traced_poly.lows.back() - candidate->a).cast().squaredNorm() < + 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { + traced_poly.lows.push_back(candidate->a); + } else { + traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(candidate->a); + } + + if ((traced_poly.highs.back() - candidate->b).cast().squaredNorm() < + 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { + traced_poly.highs.push_back(candidate->b); + } else { + traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(candidate->b); + } + segment_added = true; + used_segments.insert(&(*candidate)); + } + + if (!segment_added) { + // Zero overlapping segments, we just close this polygon + traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + traced_poly.lows.clear(); + traced_poly.highs.clear(); + } + } + + current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }), + current_traced_polys.end()); + + for (const auto &segment : polygon_slice) { + if (used_segments.find(&segment) == used_segments.end()) { + TracedPoly &new_tp = current_traced_polys.emplace_back(); + new_tp.lows.push_back(segment.a - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a); + new_tp.highs.push_back(segment.b - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b); + } + } + } + + // add not closed polys + for (TracedPoly &traced_poly : current_traced_polys) { + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + } + expanded_bridged_area = union_safety_offset(expanded_bridged_area); + } + + polygons_rotate(expanded_bridged_area, -aligning_angle); + return expanded_bridged_area; + }; + + tbb::parallel_for(tbb::blocked_range(0, clustered_layers_for_threads.size()), [po = static_cast(this), + target_flow_height_factor, &surfaces_by_layer, + &clustered_layers_for_threads, + gather_areas_w_depth, &infill_lines, + determine_bridging_angle, + construct_anchored_polygon]( + tbb::blocked_range r) { + for (size_t cluster_idx = r.begin(); cluster_idx < r.end(); cluster_idx++) { + for (size_t job_idx = 0; job_idx < clustered_layers_for_threads[cluster_idx].size(); job_idx++) { + size_t lidx = clustered_layers_for_threads[cluster_idx][job_idx]; + const Layer *layer = po->get_layer(lidx); + // this thread has exclusive access to all surfaces in layers enumerated in + // clustered_layers_for_threads[cluster_idx] + + // Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that + // would otherwise compete over anchoring sparse infill lines, leaving one area unachored + std::sort(surfaces_by_layer[lidx].begin(), surfaces_by_layer[lidx].end(), + [](const CandidateSurface &left, const CandidateSurface &right) { + auto a = get_extents(left.new_polys); + auto b = get_extents(right.new_polys); + + if (a.min.x() == b.min.x()) { + return a.min.y() < b.min.y(); + }; + return a.min.x() < b.min.x(); + }); + if (surfaces_by_layer[lidx].size() > 2) { + Vec2d origin = get_extents(surfaces_by_layer[lidx].front().new_polys).max.cast(); + std::stable_sort(surfaces_by_layer[lidx].begin() + 1, surfaces_by_layer[lidx].end(), + [origin](const CandidateSurface &left, const CandidateSurface &right) { + auto a = get_extents(left.new_polys); + auto b = get_extents(right.new_polys); + + return (origin - a.min.cast()).squaredNorm() < + (origin - b.min.cast()).squaredNorm(); + }); + } + + // Gather deep infill areas, where thick bridges fit + coordf_t spacing = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).scaled_spacing(); + coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).height() * + target_flow_height_factor; + Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); + + { + // Now also remove area that has been already filled on lower layers by bridging expansion - For this + // reason we did the clustering of layers per thread. + Polygons filled_polyons_on_lower_layers; + double bottom_z = layer->print_z - target_flow_height - EPSILON; + if (job_idx > 0) { + for (int lower_job_idx = job_idx - 1; lower_job_idx >= 0; lower_job_idx--) { + size_t lower_layer_idx = clustered_layers_for_threads[cluster_idx][lower_job_idx]; + const Layer *lower_layer = po->get_layer(lower_layer_idx); + if (lower_layer->print_z >= bottom_z) { + for (const auto &c : surfaces_by_layer[lower_layer_idx]) { + filled_polyons_on_lower_layers.insert(filled_polyons_on_lower_layers.end(), c.new_polys.begin(), + c.new_polys.end()); + } + } else { + break; + } + } + } + deep_infill_area = diff(deep_infill_area, filled_polyons_on_lower_layers); + } + + deep_infill_area = expand(deep_infill_area, spacing * 1.5); + + // Now gather expansion polygons - internal infill on current layer, from which we can cut off anchors + Polygons lightning_area; + Polygons expansion_area; + Polygons total_fill_area; + for (LayerRegion *region : layer->regions()) { + Polygons internal_polys = to_polygons(region->fill_surfaces.filter_by_types({stInternal, stInternalSolid})); + expansion_area.insert(expansion_area.end(), internal_polys.begin(), internal_polys.end()); + Polygons fill_polys = to_polygons(region->fill_expolygons); + total_fill_area.insert(total_fill_area.end(), fill_polys.begin(), fill_polys.end()); + if (region->region().config().sparse_infill_pattern == ipLightning) { + Polygons l = to_polygons(region->fill_surfaces.filter_by_type(stInternal)); + lightning_area.insert(lightning_area.end(), l.begin(), l.end()); + } + } + total_fill_area = closing(total_fill_area, float(SCALED_EPSILON)); + expansion_area = closing(expansion_area, float(SCALED_EPSILON)); + expansion_area = intersection(expansion_area, deep_infill_area); + Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); + Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_total_area", + to_lines(total_fill_area), to_lines(expansion_area), to_lines(deep_infill_area), to_lines(anchors)); +#endif + + std::vector expanded_surfaces; + expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); + for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { + const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); + Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); + area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); + + area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), + [internal_unsupported_area](const Polygon &p) { + return intersection({p}, internal_unsupported_area).empty(); + }), + area_to_be_bridge.end()); + + Polygons limiting_area = union_(area_to_be_bridge, expansion_area); + if (area_to_be_bridge.empty()) + continue; + + Polylines boundary_plines = to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())); + { + Polylines limiting_plines = to_polylines(expand(limiting_area, 0.3*flow.spacing())); + boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end()); + } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + int r = rand(); + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + + "_anchors_" + std::to_string(r), + to_lines(area_to_be_bridge), to_lines(boundary_plines), to_lines(anchors), to_lines(expansion_area)); +#endif + + double bridging_angle = 0; + if (!anchors.empty()) { + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors), + candidate.region->region().config().sparse_infill_pattern.value); + } else { + // use expansion boundaries as anchors. + // Also, use Infill pattern that is neutral for angle determination, since there are no infill lines. + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine); + } + + boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end()); + if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) { + boundary_plines = intersection_pl(boundary_plines, expand(area_to_be_bridge, scale_(10))); + } + Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + + // Check collision with other expanded surfaces + { + bool reconstruct = false; + Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing()); + for (const CandidateSurface &s : expanded_surfaces) { + if (!intersection(s.new_polys, tmp_expanded_area).empty()) { + bridging_angle = s.bridge_angle; + reconstruct = true; + break; + } + } + if (reconstruct) { + bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + } + } + + bridging_area = opening(bridging_area, flow.scaled_spacing()); + bridging_area = closing(bridging_area, flow.scaled_spacing()); + bridging_area = intersection(bridging_area, limiting_area); + bridging_area = intersection(bridging_area, total_fill_area); + expansion_area = diff(expansion_area, bridging_area); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r), + to_lines(layer->lslices), to_lines(boundary_plines), to_lines(candidate.new_polys), to_lines(bridging_area)); +#endif + + expanded_surfaces.push_back(CandidateSurface(candidate.original_surface, candidate.layer_index, bridging_area, + candidate.region, bridging_angle)); + } + surfaces_by_layer[lidx].swap(expanded_surfaces); + expanded_surfaces.clear(); + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end() && surfaces_by_layer.find(lidx + 1) == surfaces_by_layer.end()) + continue; + Layer *layer = po->get_layer(lidx); + + Polygons cut_from_infill{}; + if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { + for (const auto &surface : surfaces_by_layer.at(lidx)) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } + } + + Polygons additional_ensuring_areas{}; + if (surfaces_by_layer.find(lidx + 1) != surfaces_by_layer.end()) { + for (const auto &surface : surfaces_by_layer.at(lidx + 1)) { + auto additional_area = diff(surface.new_polys, + shrink(surface.new_polys, surface.region->flow(frSolidInfill).scaled_spacing())); + additional_ensuring_areas.insert(additional_ensuring_areas.end(), additional_area.begin(), additional_area.end()); + } + } + + for (LayerRegion *region : layer->regions()) { + Surfaces new_surfaces; + + Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces.surfaces))); + near_perimeters = diff(near_perimeters, shrink(near_perimeters, region->flow(frSolidInfill).scaled_spacing())); + ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters); + + SurfacesPtr internal_infills = region->fill_surfaces.filter_by_type(stInternal); + ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); + new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); + for (const ExPolygon &ep : new_internal_infills) { + new_surfaces.emplace_back(stInternal, ep); + } + + SurfacesPtr internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid); + if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { + for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) { + for (const Surface *surface : internal_solids) { + if (cs.original_surface == surface) { + Surface tmp{*surface, {}}; + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = cs.bridge_angle; + for (const ExPolygon &ep : union_ex(cs.new_polys)) { + new_surfaces.emplace_back(tmp, ep); + } + break; + } + } + } + } + ExPolygons new_internal_solids = to_expolygons(internal_solids); + new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end()); + new_internal_solids = diff_ex(new_internal_solids, cut_from_infill); + new_internal_solids = union_safety_offset_ex(new_internal_solids); + for (const ExPolygon &ep : new_internal_solids) { + new_surfaces.emplace_back(stInternalSolid, ep); + } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("Aensuring_" + std::to_string(reinterpret_cast(®ion)), to_polylines(additional_ensuring), + to_polylines(near_perimeters), to_polylines(to_polygons(internal_infills)), + to_polylines(to_polygons(internal_solids))); + debug_draw("Aensuring_" + std::to_string(reinterpret_cast(®ion)) + "_new", to_polylines(additional_ensuring), + to_polylines(near_perimeters), to_polylines(to_polygons(new_internal_infills)), + to_polylines(to_polygons(new_internal_solids))); +#endif + + region->fill_surfaces.remove_types({stInternalSolid, stInternal}); + region->fill_surfaces.append(new_surfaces); + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + +} // void PrintObject::bridge_over_infill() + + + + +#endif static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { if (opt.value > (int)num_extruders) @@ -2160,12 +3009,11 @@ void PrintObject::clip_fill_surfaces() for (LayerRegion *layerm : lower_layer->m_regions) { if (layerm->region().config().sparse_infill_density.value == 0) continue; - SurfaceType internal_surface_types[] = { stInternal, stInternalVoid }; Polygons internal; for (Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(internal, std::move(surface.expolygon)); - layerm->fill_surfaces.remove_types(internal_surface_types, 2); + layerm->fill_surfaces.remove_types({ stInternal, stInternalVoid }); layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); layerm->fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid); // If there are voids it means that our internal infill is not adjacent to @@ -2183,190 +3031,29 @@ void PrintObject::discover_horizontal_shells() { BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - for (size_t i = 0; i < m_layers.size(); ++ i) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (size_t i = 0; i < m_layers.size(); ++i) { m_print->throw_if_canceled(); - Layer *layer = m_layers[i]; - LayerRegion *layerm = layer->regions()[region_id]; - const PrintRegionConfig ®ion_config = layerm->region().config(); + Layer* layer = m_layers[i]; + LayerRegion* layerm = layer->regions()[region_id]; + const PrintRegionConfig& region_config = layerm->region().config(); #if 0 if (region_config.solid_infill_every_layers.value > 0 && region_config.sparse_infill_density.value > 0 && (i % region_config.solid_infill_every_layers) == 0) { // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. SurfaceType type = (region_config.sparse_infill_density == 100 || region_config.solid_infill_every_layers == 1) ? stInternalSolid : stInternalBridge; - for (Surface &surface : layerm->fill_surfaces.surfaces) + for (Surface& surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal) surface.surface_type = type; } #endif - - // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). - if (region_config.ensure_vertical_shell_thickness.value) - continue; - - coordf_t print_z = layer->print_z; - coordf_t bottom_z = layer->bottom_z(); - for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { - m_print->throw_if_canceled(); - SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; - int num_solid_layers = (type == stTop) ? region_config.top_shell_layers.value : region_config.bottom_shell_layers.value; - if (num_solid_layers == 0) - continue; - // Find slices of current type for current layer. - // Use slices instead of fill_surfaces, because they also include the perimeter area, - // which needs to be propagated in shells; we need to grow slices like we did for - // fill_surfaces though. Using both ungrown slices and grown fill_surfaces will - // not work in some situations, as there won't be any grown region in the perimeter - // area (this was seen in a model where the top layer had one extra perimeter, thus - // its fill_surfaces were thinner than the lower layer's infill), however it's the best - // solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put - // too much solid infill inside nearly-vertical slopes. - - // Surfaces including the area of perimeters. Everything, that is visible from the top / bottom - // (not covered by a layer above / below). - // This does not contain the areas covered by perimeters! - Polygons solid; - for (const Surface &surface : layerm->slices.surfaces) - if (surface.surface_type == type) - polygons_append(solid, to_polygons(surface.expolygon)); - // Infill areas (slices without the perimeters). - for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == type) - polygons_append(solid, to_polygons(surface.expolygon)); - if (solid.empty()) - continue; -// Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == stTop) ? 'top' : 'bottom'; - - // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. - for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1; - (type == stTop) ? - (n >= 0 && (int(i) - n < num_solid_layers || - print_z - m_layers[n]->print_z < region_config.top_shell_thickness.value - EPSILON)) : - (n < int(m_layers.size()) && (n - int(i) < num_solid_layers || - m_layers[n]->bottom_z() - bottom_z < region_config.bottom_shell_thickness.value - EPSILON)); - (type == stTop) ? -- n : ++ n) - { -// Slic3r::debugf " looking for neighbors on layer %d...\n", $n; - // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. - LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id]; - - // find intersection between neighbor and current layer's surfaces - // intersections have contours and holes - // we update $solid so that we limit the next neighbor layer to the areas that were - // found on this one - in other words, solid shells on one layer (for a given external surface) - // are always a subset of the shells found on the previous shell layer - // this approach allows for DWIM in hollow sloping vases, where we want bottom - // shells to be generated in the base but not in the walls (where there are many - // narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the - // upper perimeter as an obstacle and shell will not be propagated to more upper layers - //FIXME How does it work for stInternalBRIDGE? This is set for sparse infill. Likely this does not work. - Polygons new_internal_solid; - { - Polygons internal; - for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid) - polygons_append(internal, to_polygons(surface.expolygon)); - new_internal_solid = intersection(solid, internal, ApplySafetyOffset::Yes); - } - if (new_internal_solid.empty()) { - // No internal solid needed on this layer. In order to decide whether to continue - // searching on the next neighbor (thus enforcing the configured number of solid - // layers, use different strategies according to configured infill density: - if (region_config.sparse_infill_density.value == 0) { - // If user expects the object to be void (for example a hollow sloping vase), - // don't continue the search. In this case, we only generate the external solid - // shell if the object would otherwise show a hole (gap between perimeters of - // the two layers), and internal solid shells are a subset of the shells found - // on each previous layer. - goto EXTERNAL; - } else { - // If we have internal infill, we can generate internal solid shells freely. - continue; - } - } - - if (region_config.sparse_infill_density.value == 0) { - // if we're printing a hollow object we discard any solid shell thinner - // than a perimeter width, since it's probably just crossing a sloping wall - // and it's not wanted in a hollow print even if it would make sense when - // obeying the solid shell count option strictly (DWIM!) - float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); - Polygons too_narrow = diff( - new_internal_solid, - opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5)); - // Trim the regularized region by the original region. - if (! too_narrow.empty()) - new_internal_solid = solid = diff(new_internal_solid, too_narrow); - } - - // make sure the new internal solid is wide enough, as it might get collapsed - // when spacing is added in Fill.pm - { - //FIXME Vojtech: Disable this and you will be sorry. - float margin = 3.f * layerm->flow(frSolidInfill).scaled_width(); // require at least this size - // we use a higher miterLimit here to handle areas with acute angles - // in those cases, the default miterLimit would cut the corner and we'd - // get a triangle in $too_narrow; if we grow it below then the shell - // would have a different shape from the external surface and we'd still - // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff( - new_internal_solid, - opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); - if (! too_narrow.empty()) { - // grow the collapsing parts and add the extra area to the neighbor layer - // as well as to our original surfaces so that we support this - // additional area in the next shell too - // make sure our grown surfaces don't exceed the fill area - Polygons internal; - for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) - if (surface.is_internal() && !surface.is_bridge()) - polygons_append(internal, to_polygons(surface.expolygon)); - polygons_append(new_internal_solid, - intersection( - expand(too_narrow, +margin), - // Discard bridges as they are grown for anchoring and we can't - // remove such anchors. (This may happen when a bridge is being - // anchored onto a wall where little space remains after the bridge - // is grown, and that little space is an internal solid shell so - // it triggers this too_narrow logic.) - internal)); - // solid = new_internal_solid; - } - } - - // internal-solid are the union of the existing internal-solid surfaces - // and new ones - SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces); - polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid))); - ExPolygons internal_solid = union_ex(new_internal_solid); - // assign new internal-solid surfaces to layer - neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid); - // subtract intersections from layer surfaces to get resulting internal surfaces - Polygons polygons_internal = to_polygons(std::move(internal_solid)); - ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, ApplySafetyOffset::Yes); - // assign resulting internal surfaces to layer - neighbor_layerm->fill_surfaces.append(internal, stInternal); - polygons_append(polygons_internal, to_polygons(std::move(internal))); - // assign top and bottom surfaces to layer - SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge }; - backup.keep_types(surface_types_solid, 3); - std::vector top_bottom_groups; - backup.group(&top_bottom_groups); - for (SurfacesPtr &group : top_bottom_groups) - neighbor_layerm->fill_surfaces.append( - diff_ex(group, polygons_internal), - // Use an existing surface as a template, it carries the bridge angle etc. - *group.front()); - } - EXTERNAL:; - } // foreach type (stTop, stBottom, stBottomBridge) } // for each layer } // for each region #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - for (const Layer *layer : m_layers) { - const LayerRegion *layerm = layer->m_regions[region_id]; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer* layer : m_layers) { + const LayerRegion* layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells"); layerm->export_region_fill_surfaces_to_svg_debug("5_discover_horizontal_shells"); } // for each layer diff --git a/src/libslic3r/RegionExpansion.cpp b/src/libslic3r/RegionExpansion.cpp new file mode 100644 index 0000000000..17e8bd9a72 --- /dev/null +++ b/src/libslic3r/RegionExpansion.cpp @@ -0,0 +1,548 @@ +///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "RegionExpansion.hpp" + +#include +#include +#include +#include + +#include + +namespace Slic3r { +namespace Algorithm { + +// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta) +inline double clipper_round_offset_error(double offset, double arc_tolerance) +{ + static constexpr const double def_arc_tolerance = 0.25; + const double y = + arc_tolerance <= 0 ? + def_arc_tolerance : + arc_tolerance > offset * def_arc_tolerance ? + offset * def_arc_tolerance : + arc_tolerance; + double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI); + return offset * (1. - cos(M_PI / steps)); +} + +RegionExpansionParameters RegionExpansionParameters::build( + // Scaled expansion value + float full_expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_expansion_steps) +{ + assert(full_expansion > 0); + assert(expansion_step > 0); + assert(max_nr_expansion_steps > 0); + + RegionExpansionParameters out; + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + // The expansion should not be too tiny, but also small enough, so the following expansion will + // compensate for tiny_expansion and bring the wave back to the boundary without producing + // ugly cusps where it touches the boundary. + out.tiny_expansion = std::min(0.25f * full_expansion, scaled(0.05f)); + size_t nsteps = size_t(ceil((full_expansion - out.tiny_expansion) / expansion_step)); + if (max_nr_expansion_steps > 0) + nsteps = std::min(nsteps, max_nr_expansion_steps); + assert(nsteps > 0); + out.initial_step = (full_expansion - out.tiny_expansion) / nsteps; + if (nsteps > 1 && 0.25 * out.initial_step < out.tiny_expansion) { + // Decrease the step size by lowering number of steps. + nsteps = std::max(1, (floor((full_expansion - out.tiny_expansion) / (4. * out.tiny_expansion)))); + out.initial_step = (full_expansion - out.tiny_expansion) / nsteps; + } + if (0.25 * out.initial_step < out.tiny_expansion || nsteps == 1) { + out.tiny_expansion = 0.2f * full_expansion; + out.initial_step = 0.8f * full_expansion; + } + out.other_step = out.initial_step; + out.num_other_steps = nsteps - 1; + + // Accuracy of the offsetter for wave propagation. + out.arc_tolerance = scaled(0.1); + out.shortest_edge_length = out.initial_step * ClipperOffsetShortestEdgeFactor; + + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. Needs to be in sync with the offsetter accuracy. + // Clipper positive round offset should rather offset less than more. + // Still a little bit of additional offset was added. + out.max_inflation = (out.tiny_expansion + nsteps * out.initial_step) * 1.1; +// (clipper_round_offset_error(out.tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(out.initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty + + return out; +} + +// similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath. +// The expanded contours are then opened (the first point is repeated at the end). +static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened( + const ExPolygons &src, const float expansion, coord_t &base_idx) +{ + ClipperLib_Z::Paths out; + out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + ClipperLib::ClipperOffset offsetter; + offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor; + ClipperLib::Paths expansion_cache; + for (const ExPolygon &expoly : src) { + for (size_t icontour = 0; icontour < expoly.num_contours(); ++ icontour) { + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + offsetter.Clear(); + offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon); + expansion_cache.clear(); + offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion); + append(out, ClipperZUtils::to_zpaths(expansion_cache, base_idx)); + } + ++ base_idx; + } + return out; +} + +// Paths were created by splitting closed polygons into open paths and then by clipping them. +// Thus some pieces of the clipped polygons may now become split at the ends of the source polygons. +// Those ends are sorted lexicographically in "splits". +// Reconnect those split pieces. +static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector> &splits) +{ + for (auto it_path = paths.begin(); it_path != paths.end(); ) { + ClipperLib_Z::Path &path = *it_path; + assert(path.size() >= 2); + bool merged = false; + if (path.size() >= 2) { + const ClipperLib_Z::IntPoint &front = path.front(); + const ClipperLib_Z::IntPoint &back = path.back(); + // The path before clipping was supposed to cross the clipping boundary or be fully out of it. + // Thus the clipped contour is supposed to become open, with one exception: The anchor expands into a closed hole. + if (front.x() != back.x() || front.y() != back.y()) { + // Look up the ends in "splits", possibly join the contours. + // "splits" maps into the other piece connected to the same end point. + auto find_end = [&splits](const ClipperLib_Z::IntPoint &pt) -> std::pair* { + auto it = std::lower_bound(splits.begin(), splits.end(), pt, + [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r); }); + return it != splits.end() && it->first == pt ? &(*it) : nullptr; + }; + auto *end = find_end(front); + bool end_front = true; + if (! end) { + end_front = false; + end = find_end(back); + } + if (end) { + // This segment ends at a split point of the source closed contour before clipping. + if (end->second == -1) { + // Open end was found, not matched yet. + end->second = int(it_path - paths.begin()); + } else { + // Open end was found and matched with end->second + ClipperLib_Z::Path &other_path = paths[end->second]; + polylines_merge(other_path, other_path.front() == end->first, std::move(path), end_front); + if (std::next(it_path) == paths.end()) { + paths.pop_back(); + break; + } + path = std::move(paths.back()); + paths.pop_back(); + merged = true; + } + } + } + } + if (! merged) + ++ it_path; + } +} + +using AABBTreeBBoxes = AABBTreeIndirect::Tree<2, coord_t>; + +static AABBTreeBBoxes build_aabb_tree_over_expolygons(const ExPolygons &expolygons) +{ + // Calculate bounding boxes of internal slices. + std::vector bboxes; + bboxes.reserve(expolygons.size()); + for (size_t i = 0; i < expolygons.size(); ++ i) + bboxes.emplace_back(i, get_extents(expolygons[i].contour)); + // Build AABB tree over bounding boxes of boundary expolygons. + AABBTreeBBoxes out; + out.build_modify_input(bboxes); + return out; +} + +static int sample_in_expolygons( + // AABB tree over boundary expolygons + const AABBTreeBBoxes &aabb_tree, + const ExPolygons &expolygons, + const Point &sample) +{ + int out = -1; + AABBTreeIndirect::traverse(aabb_tree, + [&sample](const AABBTreeBBoxes::Node &node) { + return node.bbox.contains(sample); + }, + [&expolygons, &sample, &out](const AABBTreeBBoxes::Node &node) { + assert(node.is_leaf()); + assert(node.is_valid()); + if (expolygons[node.idx].contains(sample)) { + out = int(node.idx); + // Stop traversal. + return false; + } + // Continue traversal. + return true; + }); + return out; +} + +std::vector wave_seeds( + // Source regions that are supposed to touch the boundary. + const ExPolygons &src, + // Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region. + const ExPolygons &boundary, + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + float tiny_expansion, + // Sort output by boundary ID and source ID. + bool sorted) +{ + assert(tiny_expansion > 0); + + if (src.empty() || boundary.empty()) + return {}; + + using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection; + using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections; + + ClipperLib_Z::Paths segments; + Intersections intersections; + + coord_t idx_boundary_begin = 1; + coord_t idx_boundary_end = idx_boundary_begin; + coord_t idx_src_end; + + { + ClipperLib_Z::Clipper zclipper; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + zclipper.ZFillFunction(visitor.clipper_callback()); + // as closed contours + zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true); + // as open contours + std::vector> zsrc_splits; + { + idx_src_end = idx_boundary_end; + ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end); + zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false); + zsrc_splits.reserve(zsrc.size()); + for (const ClipperLib_Z::Path &path : zsrc) { + assert(path.size() >= 2); + assert(path.front() == path.back()); + zsrc_splits.emplace_back(path.front(), -1); + } + std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); }); + } + ClipperLib_Z::PolyTree polytree; + zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), segments); + merge_splits(segments, zsrc_splits); + } + + // AABBTree over bounding boxes of boundaries. + // Only built if necessary, that is if any of the seed contours is closed, thus there is no intersection point + // with the boundary and all Z coordinates of the closed contour point to the source contour. + AABBTreeBBoxes aabb_tree; + + // Sort paths into their respective islands. + // Each src x boundary will be processed (wave expanded) independently. + // Multiple pieces of a single src may intersect the same boundary. + WaveSeeds out; + out.reserve(segments.size()); + int iseed = 0; + for (const ClipperLib_Z::Path &path : segments) { + assert(path.size() >= 2); + const ClipperLib_Z::IntPoint &front = path.front(); + const ClipperLib_Z::IntPoint &back = path.back(); + // Both ends of a seed segment are supposed to be inside a single boundary expolygon. + // Thus as long as the seed contour is not closed, it should be open at a boundary point. + assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) || + //(front.z() < 0 && back.z() < 0)); + // Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created. + (front.z() < 0 || back.z() < 0)); + const Intersection *intersection = nullptr; + auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) { + return is.first >= 1 && is.first < idx_boundary_end && + is.second >= idx_boundary_end && is.second < idx_src_end; + }; + if (front.z() < 0) { + const Intersection &is = intersections[- front.z() - 1]; + assert(intersection_point_valid(is)); + if (intersection_point_valid(is)) + intersection = &is; + } + if (! intersection && back.z() < 0) { + const Intersection &is = intersections[- back.z() - 1]; + assert(intersection_point_valid(is)); + if (intersection_point_valid(is)) + intersection = &is; + } + if (intersection) { + // The path intersects the boundary contour at least at one side. + out.push_back({ uint32_t(intersection->second - idx_boundary_end), uint32_t(intersection->first - 1), ClipperZUtils::from_zpath(path) }); + } else { + // This should be a closed contour. + assert(front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end); + // Find a source boundary expolygon of one sample of this closed path. + if (aabb_tree.empty()) + aabb_tree = build_aabb_tree_over_expolygons(boundary); + int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y())); + // Boundary that contains the sample point was found. + assert(boundary_id >= 0); + if (boundary_id >= 0) + out.push_back({ uint32_t(front.z() - idx_boundary_end), uint32_t(boundary_id), ClipperZUtils::from_zpath(path) }); + } + ++ iseed; + } + + if (sorted) + // Sort the seeds by their intersection boundary and source contour. + std::sort(out.begin(), out.end(), lower_by_boundary_and_src); + return out; +} + +static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset) +{ + ClipperLib::Paths out; + out.reserve(polylines.size()); + ClipperLib::Paths out_this; + for (const ClipperLib::Path &path : polylines) { + assert(path.size() >= 2); + co.Clear(); + co.AddPath(path, jtRound, path.front() == path.back() ? ClipperLib::etClosedLine : ClipperLib::etOpenRound); + co.Execute(out_this, offset); + append(out, std::move(out_this)); + } + return out; +} + +// Input polygons may consist of multiple expolygons, even nested expolygons. +// After inflation some polygons may thus overlap, however the overlap is being resolved during the successive +// clipping operation, thus it is not being done here. +static ClipperLib::Paths wavefront_step(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polygons, float offset) +{ + ClipperLib::Paths out; + out.reserve(polygons.size()); + ClipperLib::Paths out_this; + for (const ClipperLib::Path &polygon : polygons) { + co.Clear(); + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + co.AddPath(polygon, jtRound, ClipperLib::etClosedPolygon); + bool ccw = ClipperLib::Orientation(polygon); + co.Execute(out_this, ccw ? offset : - offset); + if (! ccw) { + // Reverse the resulting contours. + for (ClipperLib::Path &path : out_this) + std::reverse(path.begin(), path.end()); + } + append(out, std::move(out_this)); + } + return out; +} + +static ClipperLib::Paths wavefront_clip(const ClipperLib::Paths &wavefront, const Polygons &clipping) +{ + ClipperLib::Clipper clipper; + clipper.AddPaths(wavefront, ClipperLib::ptSubject, true); + clipper.AddPaths(ClipperUtils::PolygonsProvider(clipping), ClipperLib::ptClip, true); + ClipperLib::Paths out; + clipper.Execute(ClipperLib::ctIntersection, out, ClipperLib::pftPositive, ClipperLib::pftPositive); + return out; +} + +static Polygons propagate_wave_from_boundary( + ClipperLib::ClipperOffset &co, + // Seed of the wave: Open polylines very close to the boundary. + const ClipperLib::Paths &seed, + // Boundary inside which the waveform will propagate. + const ExPolygon &boundary, + // How much to inflate the seed lines to produce the first wave area. + const float initial_step, + // How much to inflate the first wave area and the successive wave areas in each step. + const float other_step, + // Number of inflate steps after the initial step. + const size_t num_other_steps, + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. + const float max_inflation) +{ + assert(! seed.empty() && seed.front().size() >= 2); + Polygons clipping = ClipperUtils::clip_clipper_polygons_with_subject_bbox(boundary, get_extents(seed).inflated(max_inflation)); + ClipperLib::Paths polygons = wavefront_clip(wavefront_initial(co, seed, initial_step), clipping); + // Now offset the remaining + for (size_t ioffset = 0; ioffset < num_other_steps; ++ ioffset) + polygons = wavefront_clip(wavefront_step(co, polygons, other_step), clipping); + return to_polygons(polygons); +} + +// Resulting regions are sorted by boundary id and source id. +std::vector propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + std::vector out; + ClipperLib::Paths paths; + ClipperLib::ClipperOffset co; + co.ArcTolerance = params.arc_tolerance; + co.ShortestEdgeLength = params.shortest_edge_length; + for (auto it_seed = seeds.begin(); it_seed != seeds.end();) { + auto it = it_seed; + paths.clear(); + for (; it != seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it) + paths.emplace_back(it->path); + // Propagate the wavefront while clipping it with the trimmed boundary. + // Collect the expanded polygons, merge them with the source polygons. + RegionExpansion re; + for (Polygon &polygon : propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation)) + out.push_back({ std::move(polygon), it_seed->src, it_seed->boundary }); + it_seed = it; + } + + return out; +} + +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + return propagate_waves(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params); +} + +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps) +{ + return propagate_waves(src, boundary, RegionExpansionParameters::build(expansion, expansion_step, max_nr_steps)); +} + +// Returns regions per source ExPolygon expanded into boundary. +std::vector propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + std::vector expanded = propagate_waves(seeds, boundary, params); + assert(std::is_sorted(seeds.begin(), seeds.end(), [](const auto &l, const auto &r){ return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); })); + Polygons acc; + std::vector out; + for (auto it = expanded.begin(); it != expanded.end(); ) { + auto it2 = it; + acc.clear(); + for (; it2 != expanded.end() && it2->boundary_id == it->boundary_id && it2->src_id == it->src_id; ++ it2) + acc.emplace_back(std::move(it2->polygon)); + size_t size = it2 - it; + if (size == 1) + out.push_back({ ExPolygon{std::move(acc.front())}, it->src_id, it->boundary_id }); + else { + ExPolygons expolys = union_ex(acc); + reserve_more_power_of_2(out, expolys.size()); + for (ExPolygon &ex : expolys) + out.push_back({ std::move(ex), it->src_id, it->boundary_id }); + } + it = it2; + } + return out; +} + +// Returns regions per source ExPolygon expanded into boundary. +std::vector propagate_waves_ex( + // Source regions that are supposed to touch the boundary. + // Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region. + const ExPolygons &src, + const ExPolygons &boundary, + // Scaled expansion value + float full_expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_expansion_steps) +{ + auto params = RegionExpansionParameters::build(full_expansion, expansion_step, max_nr_expansion_steps); + return propagate_waves_ex(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params); +} + +std::vector expand_expolygons(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps) +{ + std::vector out(src.size(), Polygons{}); + for (RegionExpansion &r : propagate_waves(src, boundary, expansion, expansion_step, max_nr_steps)) + out[r.src_id].emplace_back(std::move(r.polygon)); + return out; +} + +std::vector merge_expansions_into_expolygons(ExPolygons &&src, std::vector &&expanded) +{ + // expanded regions will be merged into source regions, thus they will be re-sorted by source id. + std::sort(expanded.begin(), expanded.end(), [](const auto &l, const auto &r) { return l.src_id < r.src_id; }); + uint32_t last = 0; + Polygons acc; + ExPolygons out; + out.reserve(src.size()); + for (auto it = expanded.begin(); it != expanded.end();) { + for (; last < it->src_id; ++ last) + out.emplace_back(std::move(src[last])); + acc.clear(); + assert(it->src_id == last); + for (; it != expanded.end() && it->src_id == last; ++ it) + acc.emplace_back(std::move(it->polygon)); + //FIXME offset & merging could be more efficient, for example one does not need to copy the source expolygon + ExPolygon &src_ex = src[last ++]; + assert(! src_ex.contour.empty()); +#if 0 + { + static int iRun = 0; + BoundingBox bbox = get_extents(acc); + bbox.merge(get_extents(src_ex)); + SVG svg(debug_out_path("expand_merge_expolygons-failed-union=%d.svg", iRun ++).c_str(), bbox); + svg.draw(acc); + svg.draw_outline(acc, "black", scale_(0.05)); + svg.draw(src_ex, "red"); + svg.Close(); + } +#endif + Point sample = src_ex.contour.front(); + append(acc, to_polygons(std::move(src_ex))); + ExPolygons merged = union_safety_offset_ex(acc); + // Expanding one expolygon by waves should not change connectivity of the source expolygon: + // Single expolygon should be produced possibly with increased number of holes. + if (merged.size() > 1) { + // assert(merged.size() == 1); + // There is something wrong with the initial waves. Most likely the bridge was not valid at all + // or the boundary region was very close to some bridge edge, but not really touching. + // Pick only a single merged expolygon, which contains one sample point of the source expolygon. + auto aabb_tree = build_aabb_tree_over_expolygons(merged); + int id = sample_in_expolygons(aabb_tree, merged, sample); + assert(id != -1); + if (id != -1) + out.emplace_back(std::move(merged[id])); + } else if (merged.size() == 1) + out.emplace_back(std::move(merged.front())); + } + for (; last < uint32_t(src.size()); ++ last) + out.emplace_back(std::move(src[last])); + return out; +} + +std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + // expanded regions are sorted by boundary id and source id + std::vector expanded = propagate_waves(src, boundary, params); + return merge_expansions_into_expolygons(std::move(src), std::move(expanded)); +} + +} // Algorithm +} // Slic3r diff --git a/src/libslic3r/RegionExpansion.hpp b/src/libslic3r/RegionExpansion.hpp new file mode 100644 index 0000000000..62e32526aa --- /dev/null +++ b/src/libslic3r/RegionExpansion.hpp @@ -0,0 +1,122 @@ +///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ +#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ + +#include +#include +#include +#include + +namespace Slic3r { +namespace Algorithm { + +struct RegionExpansionParameters +{ + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + float tiny_expansion; + // How much to inflate the seed lines to produce the first wave area. + float initial_step; + // How much to inflate the first wave area and the successive wave areas in each step. + float other_step; + // Number of inflate steps after the initial step. + size_t num_other_steps; + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. + float max_inflation; + + // Accuracy of the offsetter for wave propagation. + double arc_tolerance; + double shortest_edge_length; + + static RegionExpansionParameters build( + // Scaled expansion value + float full_expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_expansion_steps); +}; + +struct WaveSeed { + uint32_t src; + uint32_t boundary; + Points path; +}; +using WaveSeeds = std::vector; + +inline bool lower_by_boundary_and_src(const WaveSeed &l, const WaveSeed &r) +{ + return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); +} + +inline bool lower_by_src_and_boundary(const WaveSeed &l, const WaveSeed &r) +{ + return l.src < r.src || (l.src == r.src && l.boundary < r.boundary); +} + +// Expand src slightly outwards to intersect boundaries, trim the offsetted src polylines by the boundaries. +// Return the trimmed paths annotated with their origin (source of the path, index of the boundary region). +WaveSeeds wave_seeds( + // Source regions that are supposed to touch the boundary. + const ExPolygons &src, + // Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region. + const ExPolygons &boundary, + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + float tiny_expansion, + bool sorted); + +struct RegionExpansion +{ + Polygon polygon; + uint32_t src_id; + uint32_t boundary_id; +}; + +std::vector propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); + +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps); + +struct RegionExpansionEx +{ + ExPolygon expolygon; + uint32_t src_id; + uint32_t boundary_id; +}; + +std::vector propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); + +std::vector propagate_waves_ex(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps); + +std::vector expand_expolygons(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps); + +// Merge src with expansions, return the merged expolygons. +std::vector merge_expansions_into_expolygons(ExPolygons &&src, std::vector &&expanded); + +std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); + +} // Algorithm +} // Slic3r + +#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */ diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index f2f0b3f97e..7d0b10b3fa 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -51,17 +51,12 @@ SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) return ss; } -SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) +SurfacesPtr SurfaceCollection::filter_by_types(std::initializer_list types) { SurfacesPtr ss; - for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - for (int i = 0; i < ntypes; ++ i) { - if (surface->surface_type == types[i]) { - ss.push_back(&*surface); - break; - } - } - } + for (Surface& surface : this->surfaces) + if (std::find(types.begin(), types.end(), surface.surface_type) != types.end()) + ss.push_back(&surface); return ss; } @@ -86,23 +81,15 @@ void SurfaceCollection::keep_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::keep_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool keep = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - keep = true; - break; - } - } - if (keep) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) != types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } @@ -121,23 +108,15 @@ void SurfaceCollection::remove_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::remove_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool remove = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - remove = true; - break; - } - } - if (! remove) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) == types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 50be3e97da..18708b2144 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -27,11 +27,11 @@ class SurfaceCollection return false; } SurfacesPtr filter_by_type(const SurfaceType type); - SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes); + SurfacesPtr filter_by_types(std::initializer_list types); void keep_type(const SurfaceType type); - void keep_types(const SurfaceType *types, int ntypes); + void keep_types(std::initializer_list types); void remove_type(const SurfaceType type); - void remove_types(const SurfaceType *types, int ntypes); + void remove_types(std::initializer_list type); void filter_by_type(SurfaceType type, Polygons* polygons); void set_type(SurfaceType type) { for (Surface &surface : this->surfaces) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 463e84659c..45bc607342 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -291,6 +291,16 @@ template size_t next_highest_power_of_2(T v, return next_highest_power_of_2(uint32_t(v)); } +template void reserve_more(VectorType &vector, size_t n) +{ + vector.reserve(vector.size() + n); +} + +template void reserve_more_power_of_2(VectorType &vector, size_t n) +{ + vector.reserve(next_highest_power_of_2(vector.size() + n)); +} + template inline INDEX_TYPE prev_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1d6b22cb25..68c4b16ab6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1980,6 +1980,7 @@ void TabPrint::build() optgroup->append_single_option_line("detect_thin_wall"); optgroup = page->new_optgroup(L("Top/bottom shells"), L"param_shell"); + optgroup->append_single_option_line("interface_shells"); optgroup->append_single_option_line("top_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); optgroup->append_single_option_line("top_shell_layers"); optgroup->append_single_option_line("top_shell_thickness"); @@ -2002,7 +2003,7 @@ void TabPrint::build() optgroup->append_single_option_line("minimum_sparse_infill_area","parameter/strength-advance-settings"); optgroup->append_single_option_line("infill_combination","parameter/strength-advance-settings"); optgroup->append_single_option_line("detect_narrow_internal_solid_infill","parameter/strength-advance-settings"); - optgroup->append_single_option_line("ensure_vertical_shell_thickness","parameter/strength-advance-settings"); + //optgroup->append_single_option_line("ensure_vertical_shell_thickness","parameter/strength-advance-settings"); optgroup->append_single_option_line("internal_bridge_support_thickness","parameter/strength-advance-settings"); page = add_options_page(L("Speed"), "empty"); From dbcfa3733cc4adcf74eefaa36743ea10569660db Mon Sep 17 00:00:00 2001 From: gerrit Date: Sat, 30 Mar 2024 09:47:33 +0800 Subject: [PATCH 19/70] ci: update build version to 01.09.00.61 Change-Id: I2dad9c713dd2bd4362391f1358aa477bf69de4c6 --- version.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.inc b/version.inc index bf7d21ab93..df0206dafa 100644 --- a/version.inc +++ b/version.inc @@ -15,4 +15,4 @@ endif() # The build_version should start from 50 in master branch -set(SLIC3R_VERSION "01.09.00.60") +set(SLIC3R_VERSION "01.09.00.61") From 2fbbb969bfac7c750153cc78ebe0e067f5994099 Mon Sep 17 00:00:00 2001 From: "xun.zhang" Date: Fri, 29 Mar 2024 20:08:43 +0800 Subject: [PATCH 20/70] ENH: refine retraction before cut 1. Add filament retraction before cut control jira:NEW Signed-off-by: xun.zhang Change-Id: Ifcb087c9791c0461b793ef811b21ebd4c007d880 --- .../BBL/filament/fdm_filament_common.json | 6 ++ src/BambuStudio.cpp | 21 ++--- src/libslic3r/FlushVolCalc.cpp | 6 +- src/libslic3r/GCode.cpp | 21 ++++- src/libslic3r/GCode/GCodeProcessor.hpp | 2 +- src/libslic3r/Preset.cpp | 7 +- src/libslic3r/PrintConfig.cpp | 29 ++++--- src/libslic3r/PrintConfig.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 71 +++++++++++------ src/slic3r/GUI/Plater.hpp | 1 + src/slic3r/GUI/Tab.cpp | 77 ++++++++++++++----- src/slic3r/GUI/WipeTowerDialog.cpp | 14 ++-- src/slic3r/GUI/WipeTowerDialog.hpp | 8 +- 13 files changed, 182 insertions(+), 85 deletions(-) diff --git a/resources/profiles/BBL/filament/fdm_filament_common.json b/resources/profiles/BBL/filament/fdm_filament_common.json index 6f1291c6ec..6ec95db4a8 100644 --- a/resources/profiles/BBL/filament/fdm_filament_common.json +++ b/resources/profiles/BBL/filament/fdm_filament_common.json @@ -57,6 +57,9 @@ "filament_is_support": [ "0" ], + "filament_long_retractions_when_cut": [ + "nil" + ], "filament_max_volumetric_speed": [ "0" ], @@ -72,6 +75,9 @@ "filament_retract_when_changing_layer": [ "nil" ], + "filament_retraction_distances_when_cut": [ + "nil" + ], "filament_retraction_length": [ "nil" ], diff --git a/src/BambuStudio.cpp b/src/BambuStudio.cpp index eec13042a6..5b9d17bda0 100644 --- a/src/BambuStudio.cpp +++ b/src/BambuStudio.cpp @@ -80,6 +80,7 @@ using namespace nlohmann; #include "slic3r/GUI/OpenGLManager.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" #include #ifdef __WXGTK__ @@ -2646,12 +2647,8 @@ int CLI::run(int argc, char **argv) //flush_vol_vector.resize(project_filament_count); //set multiplier to 1? m_print_config.option("flush_multiplier", true)->set(new ConfigOptionFloat(1.f)); - ConfigOption* extra_flush_volume_opt = m_print_config.option("nozzle_volume"); - int extra_flush_volume = extra_flush_volume_opt ? (int)extra_flush_volume_opt->getFloat() : 0; - bool activate = m_print_config.option("enable_long_retraction_when_cut")->value && m_print_config.option("long_retraction_when_cut")->value; - float extra_retract_length = activate && m_print_config.option("long_retraction_when_cut")->value ? m_print_config.option("retraction_distance_when_cut")->value : 0; - float extra_retract_volume = PI * 1.75 * 1.75 / 4 * extra_retract_length; - extra_flush_volume = (int)std::max(0.f, extra_flush_volume - extra_retract_volume); + + const std::vector& min_flush_volumes = Slic3r::GUI::get_min_flush_volumes(); if (filament_is_support->size() != project_filament_count) { @@ -2660,9 +2657,13 @@ int CLI::run(int argc, char **argv) flush_and_exit(CLI_CONFIG_FILE_ERROR); } - BOOST_LOG_TRIVIAL(info) << boost::format("extra_flush_volume: %1%")%extra_flush_volume; - BOOST_LOG_TRIVIAL(info) << boost::format("filament_is_support: %1%")%filament_is_support->serialize(); - BOOST_LOG_TRIVIAL(info) << boost::format("flush_volumes_matrix before computing: %1%")%m_print_config.option("flush_volumes_matrix")->serialize(); + { + std::ostringstream volumes_str; + std::copy(min_flush_volumes.begin(), min_flush_volumes.end(), std::ostream_iterator(volumes_str, ",")); + BOOST_LOG_TRIVIAL(info) << boost::format("extra_flush_volume: %1%") % volumes_str.str(); + BOOST_LOG_TRIVIAL(info) << boost::format("filament_is_support: %1%") % filament_is_support->serialize(); + BOOST_LOG_TRIVIAL(info) << boost::format("flush_volumes_matrix before computing: %1%") % m_print_config.option("flush_volumes_matrix")->serialize(); + } for (int from_idx = 0; from_idx < project_filament_count; from_idx++) { const std::string& from_color = project_filament_colors[from_idx]; unsigned char from_rgb[4] = {}; @@ -2686,7 +2687,7 @@ int CLI::run(int argc, char **argv) //BOOST_LOG_TRIVIAL(info) << boost::format("src_rgba {%1%,%2%,%3%,%4%} dst_rgba {%5%,%6%,%7%,%8%}")%(unsigned int)(from_rgb[0]) %(unsigned int)(from_rgb[1]) %(unsigned int)(from_rgb[2]) %(unsigned int)(from_rgb[3]) // %(unsigned int)(to_rgb[0]) %(unsigned int)(to_rgb[1]) %(unsigned int)(to_rgb[2]) %(unsigned int)(to_rgb[3]); - Slic3r::FlushVolCalculator calculator(extra_flush_volume, Slic3r::g_max_flush_volume); + Slic3r::FlushVolCalculator calculator(min_flush_volumes[from_idx], Slic3r::g_max_flush_volume); flushing_volume = calculator.calc_flush_vol(from_rgb[3], from_rgb[0], from_rgb[1], from_rgb[2], to_rgb[3], to_rgb[0], to_rgb[1], to_rgb[2]); if (is_from_support) { diff --git a/src/libslic3r/FlushVolCalc.cpp b/src/libslic3r/FlushVolCalc.cpp index 2606ec4f11..29cbcbe401 100644 --- a/src/libslic3r/FlushVolCalc.cpp +++ b/src/libslic3r/FlushVolCalc.cpp @@ -88,11 +88,7 @@ int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, float hs_flush = 230.f * hs_dist; float flush_volume = calc_triangle_3rd_edge(hs_flush, lumi_flush, 120.f); - constexpr int standard_nozzle_volume = 107; - auto formula = [](float rate) { - return 1.13 / (1 + 5.24*exp(-4 * rate)) - 0.031; - }; - flush_volume = std::max(flush_volume, 60.f) *formula(float( m_min_flush_vol) / standard_nozzle_volume); + flush_volume = std::max(flush_volume, 60.f); //float flush_multiplier = std::atof(m_flush_multiplier_ebox->GetValue().c_str()); flush_volume += m_min_flush_vol; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d96a8010c1..5fda1f34d1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -561,6 +561,8 @@ static std::vector get_path_of_change_filament(const Print& print) } gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); + gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(new_extruder_id)); + gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_extruder_id)); // Process the start filament gcode. std::string start_filament_gcode_str; @@ -1262,8 +1264,15 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_processor.result().timelapse_warning_code = m_timelapse_warning_code; m_processor.result().support_traditional_timelapse = m_support_traditional_timelapse; - m_processor.result().long_retraction_when_cut = m_config.long_retraction_when_cut; + bool activate_long_retraction_when_cut = false; + for (const auto& extruder : m_writer.extruders()) + activate_long_retraction_when_cut |= ( + m_config.long_retractions_when_cut.get_at(extruder.id()) + && m_config.retraction_distances_when_cut.get_at(extruder.id()) > 0 + ); + m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; + { //BBS:check bed and filament compatible const ConfigOptionDef *bed_type_def = print_config_def.get("curr_bed_type"); assert(bed_type_def != nullptr); @@ -1918,6 +1927,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_placeholder_parser.set("initial_no_support_tool", initial_non_support_extruder_id); m_placeholder_parser.set("initial_no_support_extruder", initial_non_support_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id); + //set the key for compatibilty + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); + + m_placeholder_parser.set("retraction_distances_when_cut", new ConfigOptionFloats(m_config.retraction_distances_when_cut)); + m_placeholder_parser.set("long_retractions_when_cut",new ConfigOptionBools(m_config.long_retractions_when_cut)); //Set variable for total layer count so it can be used in custom gcode. m_placeholder_parser.set("total_layer_count", m_layer_count); // Useful for sequential prints. @@ -5055,6 +5070,8 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { m_placeholder_parser.set("current_extruder", extruder_id); + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(extruder_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(extruder_id)); std::string gcode; // Append the filament start G-code. @@ -5241,6 +5258,8 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool b } m_placeholder_parser.set("current_extruder", extruder_id); + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(extruder_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(extruder_id)); // Append the filament start G-code. const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 70cbfdb390..ade7bc359d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -22,7 +22,7 @@ namespace Slic3r { #define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament" #define NOT_SUPPORT_TRADITIONAL_TIMELAPSE "not_support_traditional_timelapse" #define NOT_GENERATE_TIMELAPSE "not_generate_timelapse" -#define LONG_RETRACTION_WHEN_CUT "activate_long_retration_when_cut" +#define LONG_RETRACTION_WHEN_CUT "activate_long_retraction_when_cut" enum class EMoveType : unsigned char { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d6dd621db1..97d46a166f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -871,7 +871,8 @@ static std::vector s_Preset_filament_options { "filament_wipe_distance", "additional_cooling_fan_speed", "nozzle_temperature_range_low", "nozzle_temperature_range_high", //OrcaSlicer - "enable_pressure_advance", "pressure_advance", "chamber_temperatures","filament_notes" + "enable_pressure_advance", "pressure_advance", "chamber_temperatures","filament_notes", + "filament_long_retractions_when_cut","filament_retraction_distances_when_cut" }; static std::vector s_Preset_machine_limits_options { @@ -894,13 +895,13 @@ static std::vector s_Preset_printer_options { "scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode", "nozzle_type","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types","support_chamber_temp_control","support_air_filtration","printer_structure","thumbnail_size", "best_object_pos","head_wrap_detect_zone","printer_notes", + "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", //OrcaSlicer "host_type", "print_host", "printhost_apikey", "print_host_webui", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", - "use_relative_e_distances", "extruder_type","use_firmware_retraction", - "long_retraction_when_cut","retraction_distance_when_cut","enable_long_retraction_when_cut" + "use_relative_e_distances", "extruder_type","use_firmware_retraction" }; static std::vector s_Preset_sla_print_options { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 0d7f99097c..a3aa709660 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2695,21 +2695,20 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionBool {false}); - - def = this->add("long_retraction_when_cut", coBool); + def = this->add("long_retractions_when_cut", coBools); def->label = L("Long retraction when cut(experimental)"); def->tooltip = L("Experimental feature.Retracting and cutting off the filament at a longer distance during changes to minimize purge." "While this reduces flush significantly, it may also raise the risk of nozzle clogs or other printing problems."); def->mode = comDevelop; - def->set_default_value(new ConfigOptionBool {false}); + def->set_default_value(new ConfigOptionBools {false}); - def = this->add("retraction_distance_when_cut",coFloat); + def = this->add("retraction_distances_when_cut",coFloats); def->label = L("Retraction distance when cut"); def->tooltip = L("Experimental feature.Retraction length before cutting off during filament change"); def->mode = comDevelop; def->min = 10; - def->max = 20; - def->set_default_value(new ConfigOptionFloat {20}); + def->max = 18; + def->set_default_value(new ConfigOptionFloats {18}); def = this->add("retract_length_toolchange", coFloats); def->label = L("Length"); @@ -3847,7 +3846,10 @@ void PrintConfigDef::init_fff_params() // bools "retract_when_changing_layer", "wipe", // percents - "retract_before_wipe"}) { + "retract_before_wipe", + "long_retractions_when_cut", + "retraction_distances_when_cut" + }) { auto it_opt = options.find(opt_key); assert(it_opt != options.end()); def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); @@ -3858,6 +3860,8 @@ void PrintConfigDef::init_fff_params() def->enum_keys_map = it_opt->second.enum_keys_map; def->enum_labels = it_opt->second.enum_labels; def->enum_values = it_opt->second.enum_values; + def->min = it_opt->second.min; + def->max = it_opt->second.max; //BBS: shown specific filament retract config because we hide the machine retract into comDevelop mode if ((strcmp(opt_key, "retraction_length") == 0) || (strcmp(opt_key, "z_hop") == 0)) @@ -3891,16 +3895,18 @@ void PrintConfigDef::init_extruder_option_keys() "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "retract_lift_above", "retract_lift_below","deretraction_speed", "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", - "default_filament_profile" + "default_filament_profile","retraction_distances_when_cut","long_retractions_when_cut" }; m_extruder_retract_keys = { "deretraction_speed", + "long_retractions_when_cut", "retract_before_wipe", "retract_lift_above", "retract_lift_below", "retract_restart_extra", "retract_when_changing_layer", + "retraction_distances_when_cut", "retraction_length", "retraction_minimum_travel", "retraction_speed", @@ -3919,14 +3925,16 @@ void PrintConfigDef::init_filament_option_keys() "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "deretraction_speed", "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "filament_colour", - "default_filament_profile" + "default_filament_profile","retraction_distances_when_cut","long_retractions_when_cut" }; m_filament_retract_keys = { "deretraction_speed", + "long_retractions_when_cut", "retract_before_wipe", "retract_restart_extra", "retract_when_changing_layer", + "retraction_distances_when_cut", "retraction_length", "retraction_minimum_travel", "retraction_speed", @@ -4681,7 +4689,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "remove_freq_sweep", "remove_bed_leveling", "remove_extrusion_calibration", "support_transition_line_width", "support_transition_speed", "bed_temperature", "bed_temperature_initial_layer", "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height", - "z_hop_type","nozzle_hrc","chamber_temperature","only_one_wall_top","bed_temperature_difference" + "z_hop_type","nozzle_hrc","chamber_temperature","only_one_wall_top","bed_temperature_difference","long_retraction_when_cut", + "retraction_distance_when_cut" }; if (ignore.find(opt_key) != ignore.end()) { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 901cc3c95b..c22b093d28 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -916,8 +916,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, retraction_length)) ((ConfigOptionFloats, retract_length_toolchange)) ((ConfigOptionBool, enable_long_retraction_when_cut)) - ((ConfigOptionBool, long_retraction_when_cut)) - ((ConfigOptionFloat, retraction_distance_when_cut)) + ((ConfigOptionFloats, retraction_distances_when_cut)) + ((ConfigOptionBools, long_retractions_when_cut)) ((ConfigOptionFloats, z_hop)) // BBS ((ConfigOptionEnumsGeneric, z_hop_types)) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a5eea812cb..93114a3bba 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -463,6 +463,45 @@ void Sidebar::priv::hide_rich_tip(wxButton* btn) } #endif +std::vector get_min_flush_volumes() +{ + std::vectorextra_flush_volumes; + const auto& full_config = wxGetApp().preset_bundle->full_config(); + auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + + ConfigOption* nozzle_volume_opt = printer_config.option("nozzle_volume"); + int nozzle_volume_val = nozzle_volume_opt ? (int)nozzle_volume_opt->getFloat() : 0; + + bool machine_enabled = printer_config.option("enable_long_retraction_when_cut")->value; + bool machine_activated = printer_config.option("long_retractions_when_cut")->values[0] == 1; + + auto filament_retraction_distance_when_cut = full_config.option("filament_retraction_distances_when_cut"); + auto printer_retraction_distance_when_cut = full_config.option("retraction_distances_when_cut"); + auto filament_long_retractions_when_cut = full_config.option("filament_long_retractions_when_cut"); + + size_t filament_size = filament_retraction_distance_when_cut->values.size(); + for (size_t idx = 0; idx < filament_size; ++idx) { + int extra_flush_volume = nozzle_volume_val; + int retract_length = machine_enabled && machine_activated ? printer_retraction_distance_when_cut->values[0] : 0; + + char filament_activated = filament_long_retractions_when_cut->values[idx]; + double filament_retract_length = filament_retraction_distance_when_cut->values[idx]; + + if(filament_activated == 0) + retract_length = 0; + else if (filament_activated == 1 && machine_enabled) { + if (!std::isnan(filament_retract_length)) + retract_length = (int)filament_retraction_distance_when_cut->values[idx]; + else + retract_length = printer_retraction_distance_when_cut->values[0]; + } + + extra_flush_volume -= PI * 1.75 * 1.75 / 4 * retract_length; + extra_flush_volumes.emplace_back(extra_flush_volume); + } + return extra_flush_volumes; +} + // Sidebar / public static struct DynamicFilamentList : DynamicList @@ -778,22 +817,14 @@ Sidebar::Sidebar(Plater *parent) p->m_flushing_volume_btn->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent &e) { auto& project_config = wxGetApp().preset_bundle->project_config; - auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; const std::vector& init_matrix = (project_config.option("flush_volumes_matrix"))->values; const std::vector& init_extruders = (project_config.option("flush_volumes_vector"))->values; - ConfigOption* extra_flush_volume_opt = printer_config.option("nozzle_volume"); - int extra_flush_volume = extra_flush_volume_opt ? (int)extra_flush_volume_opt->getFloat() : 0; - bool activate = printer_config.option("enable_long_retraction_when_cut")->value && printer_config.option("long_retraction_when_cut")->value; - float extra_retract_length = activate && printer_config.option("long_retraction_when_cut")->value ? printer_config.option("retraction_distance_when_cut")->value : 0; - float extra_retract_volume = PI * 1.75 * 1.75 / 4 * extra_retract_length; - extra_flush_volume = (int)std::max(0.f, extra_flush_volume - extra_retract_volume); ConfigOptionFloat* flush_multi_opt = project_config.option("flush_multiplier"); float flush_multiplier = flush_multi_opt ? flush_multi_opt->getFloat() : 1.f; const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); - - WipingDialog dlg(parent, cast(init_matrix), cast(init_extruders), extruder_colours, extra_flush_volume, flush_multiplier); - + const auto& extra_flush_volumes = get_min_flush_volumes(); + WipingDialog dlg(parent, cast(init_matrix), cast(init_extruders), extruder_colours, extra_flush_volumes, flush_multiplier); if (dlg.ShowModal() == wxID_OK) { std::vector matrix = dlg.get_matrix(); std::vector extruders = dlg.get_extruders(); @@ -1937,17 +1968,12 @@ void Sidebar::auto_calc_flushing_volumes(const int modify_id) const std::vector& init_matrix = (project_config.option("flush_volumes_matrix"))->values; const std::vector& init_extruders = (project_config.option("flush_volumes_vector"))->values; - ConfigOption* extra_flush_volume_opt = printer_config.option("nozzle_volume"); - int extra_flush_volume = extra_flush_volume_opt ? (int)extra_flush_volume_opt->getFloat() : 0; - bool activate = printer_config.option("enable_long_retraction_when_cut")->value && printer_config.option("long_retraction_when_cut")->value; - float extra_retract_length = activate && printer_config.option("long_retraction_when_cut")->value ? printer_config.option("retraction_distance_when_cut")->value : 0; - float extra_retract_volume = PI * 1.75 * 1.75 / 4 * extra_retract_length; - extra_flush_volume = (int)std::max(0.f, extra_flush_volume - extra_retract_volume); + + const std::vector& min_flush_volumes= get_min_flush_volumes(); ConfigOptionFloat* flush_multi_opt = project_config.option("flush_multiplier"); float flush_multiplier = flush_multi_opt ? flush_multi_opt->getFloat() : 1.f; std::vector matrix = init_matrix; - int m_min_flush_volume = extra_flush_volume; int m_max_flush_volume = Slic3r::g_max_flush_volume; unsigned int m_number_of_extruders = (int)(sqrt(init_matrix.size()) + 0.001); @@ -1974,12 +2000,10 @@ void Sidebar::auto_calc_flushing_volumes(const int modify_id) if (modify_id >= 0 && modify_id < multi_colours.size()) { for (int i = 0; i < multi_colours.size(); ++i) { - - Slic3r::FlushVolCalculator calculator(m_min_flush_volume, m_max_flush_volume); - // from to modify int from_idx = i; if (from_idx != modify_id) { + Slic3r::FlushVolCalculator calculator(min_flush_volumes[from_idx], m_max_flush_volume); int flushing_volume = 0; bool is_from_support = is_support_filament(from_idx); bool is_to_support = is_support_filament(modify_id); @@ -2004,6 +2028,7 @@ void Sidebar::auto_calc_flushing_volumes(const int modify_id) // modify to to int to_idx = i; if (to_idx != modify_id) { + Slic3r::FlushVolCalculator calculator(min_flush_volumes[modify_id], m_max_flush_volume); bool is_from_support = is_support_filament(modify_id); bool is_to_support = is_support_filament(to_idx); int flushing_volume = 0; @@ -6330,11 +6355,9 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) view3D->deselect_all(); } // update flush matrix - auto& project_config = wxGetApp().preset_bundle->project_config; - const std::vector& init_matrix = (project_config.option("flush_volumes_matrix"))->values; - for (size_t idx = 0; idx < init_matrix.size(); ++idx) + size_t filament_size = wxGetApp().plater()->get_extruder_colors_from_plater_config().size(); + for (size_t idx = 0; idx < filament_size; ++idx) wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(idx); - } #ifdef __WXMSW__ diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 58d96a0809..84069d0929 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -776,6 +776,7 @@ class SuppressBackgroundProcessingUpdate bool m_was_scheduled; }; +std::vector get_min_flush_volumes(); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 68c4b16ab6..87708b8798 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1537,16 +1537,40 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } } - auto update_flush_volume = []() { - auto& project_config = wxGetApp().preset_bundle->project_config; - const std::vector& init_matrix = (project_config.option("flush_volumes_matrix"))->values; - for (size_t idx = 0; idx < init_matrix.size(); ++idx) + + // -1 means caculate all + auto update_flush_volume = [](int idx = -1) { + if (idx < 0) { + size_t filament_size = wxGetApp().plater()->get_extruder_colors_from_plater_config().size(); + for (size_t i = 0; i < filament_size; ++i) + wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(i); + } + else wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(idx); }; - if(opt_key == "long_retraction_when_cut"){ - bool activate = boost::any_cast(value); - if (activate) { + + string opt_key_without_idx = opt_key.substr(0, opt_key.find('#')); + + if (opt_key_without_idx == "long_retractions_when_cut") { + unsigned char activate = boost::any_cast(value); + if (activate == 1) { + MessageDialog dialog(wxGetApp().plater(), + _L("Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush." + "Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications."), "", wxICON_WARNING | wxOK); + dialog.ShowModal(); + } + update_flush_volume(); + } + + if (opt_key_without_idx == "retraction_distances_when_cut") + update_flush_volume(); + + + + if (opt_key == "filament_long_retractions_when_cut"){ + unsigned char activate = boost::any_cast(value); + if (activate == 1) { MessageDialog dialog(wxGetApp().plater(), _L("Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush." "Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications."), "", wxICON_WARNING | wxOK); @@ -1555,9 +1579,10 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) update_flush_volume(); } - if (opt_key == "retraction_distance_when_cut") { + if (opt_key == "filament_retraction_distances_when_cut") update_flush_volume(); - } + + // BBS #if 0 if (opt_key == "extruders_count") @@ -2895,7 +2920,9 @@ void TabFilament::add_filament_overrides_page() "filament_wipe", //BBS "filament_wipe_distance", - "filament_retract_before_wipe" + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut" }) append_single_option_line(opt_key, extruder_idx); } @@ -2927,7 +2954,9 @@ void TabFilament::update_filament_overrides_page() "filament_wipe", //BBS "filament_wipe_distance", - "filament_retract_before_wipe" + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut" }; const int extruder_idx = 0; // #ys_FIXME @@ -2944,8 +2973,19 @@ void TabFilament::update_filament_overrides_page() m_overrides_options[opt_key]->SetValue(is_checked); Field* field = optgroup->get_fieldc(opt_key, extruder_idx); - if (field != nullptr) - field->toggle(is_checked); + if (field != nullptr) { + if (opt_key == "filament_long_retractions_when_cut") { + bool machine_enabled = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; + field->toggle(is_checked&&machine_enabled); + } + else if (opt_key == "filament_retraction_distances_when_cut") { + bool machine_enabled = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; + bool filament_enabled = m_config->option("filament_long_retractions_when_cut")->values[extruder_idx]==1; + field->toggle(is_checked && filament_enabled && machine_enabled); + } + else + field->toggle(is_checked); + } } } @@ -3907,8 +3947,9 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction", -1, true); optgroup->append_single_option_line("retract_length_toolchange", "", extruder_idx); optgroup->append_single_option_line("retract_restart_extra_toolchange", "", extruder_idx); - optgroup->append_single_option_line("long_retraction_when_cut", ""); - optgroup->append_single_option_line("retraction_distance_when_cut", ""); + // do not display this params now + optgroup->append_single_option_line("long_retractions_when_cut", "", extruder_idx); + optgroup->append_single_option_line("retraction_distances_when_cut", "", extruder_idx); #if 0 //optgroup = page->new_optgroup(L("Preview"), -1, true); @@ -4148,9 +4189,9 @@ void TabPrinter::toggle_options() bool toolchange_retraction = m_config->opt_float("retract_length_toolchange", i) > 0; toggle_option("retract_restart_extra_toolchange", have_multiple_extruders && toolchange_retraction, i); - toggle_option("long_retraction_when_cut", !use_firmware_retraction && m_config->opt_bool("enable_long_retraction_when_cut")); - toggle_line("retraction_distance_when_cut", m_config->opt_bool("long_retraction_when_cut")); - + // do not display this extruder param now + toggle_option("long_retractions_when_cut", !use_firmware_retraction && m_config->opt_bool("enable_long_retraction_when_cut"),i); + toggle_option("retraction_distances_when_cut", m_config->opt_bool("long_retractions_when_cut",i),i); } if (m_active_page->title() == "Motion ability") { diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index ec4fb83008..3055c52a58 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -194,7 +194,7 @@ void WipingDialog::on_dpi_changed(const wxRect &suggested_rect) // Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode: WipingDialog::WipingDialog(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours, - int extra_flush_volume, float flush_multiplier) + const std::vector&extra_flush_volume, float flush_multiplier) : DPIDialog(parent ? parent : static_cast(wxGetApp().mainframe), wxID_ANY, _(L("Flushing volumes for filament change")), @@ -294,7 +294,7 @@ void WipingPanel::create_panels(wxWindow* parent, const int num) { // This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers) WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours, Button* calc_button, - int extra_flush_volume, float flush_multiplier) + const std::vector& extra_flush_volume, float flush_multiplier) : wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/) ,m_matrix(matrix), m_min_flush_volume(extra_flush_volume), m_max_flush_volume(Slic3r::g_max_flush_volume) { @@ -423,7 +423,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con multi_desc_label->SetForegroundColour(g_text_color); m_sizer_advanced->Add(multi_desc_label, 0, wxEXPAND | wxLEFT, TEXT_BEG_PADDING); - wxString min_flush_str = wxString::Format(_L("Suggestion: Flushing Volume in range [%d, %d]"), m_min_flush_volume, m_max_flush_volume); + wxString min_flush_str = wxString::Format(_L("Suggestion: Flushing Volume in range [%d, %d]"),*std::min_element(m_min_flush_volume.begin(), m_min_flush_volume.end()), m_max_flush_volume); m_min_flush_label = new wxStaticText(m_page_advanced, wxID_ANY, min_flush_str, wxDefaultPosition, wxDefaultSize, 0); m_min_flush_label->SetForegroundColour(g_text_color); m_sizer_advanced->Add(m_min_flush_label, 0, wxEXPAND | wxLEFT, TEXT_BEG_PADDING); @@ -598,9 +598,9 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector& matrix, con }); } -int WipingPanel::calc_flushing_volume(const wxColour& from_, const wxColour& to_) +int WipingPanel::calc_flushing_volume(const wxColour& from_, const wxColour& to_ ,int min_flush_volume) { - Slic3r::FlushVolCalculator calculator(m_min_flush_volume, m_max_flush_volume); + Slic3r::FlushVolCalculator calculator(min_flush_volume, m_max_flush_volume); return calculator.calc_flush_vol(from_.Alpha(), from_.Red(), from_.Green(), from_.Blue(), to_.Alpha(), to_.Red(), to_.Green(), to_.Blue()); } @@ -623,7 +623,7 @@ void WipingPanel::update_warning_texts() auto text_box = box_vec[j]; wxString str = text_box->GetValue(); int actual_volume = wxAtoi(str); - if (actual_volume < m_min_flush_volume || actual_volume > m_max_flush_volume) { + if (actual_volume < m_min_flush_volume[i] || actual_volume > m_max_flush_volume) { if (text_box->GetForegroundColour() != g_warning_color) { text_box->SetForegroundColour(g_warning_color); text_box->Refresh(); @@ -688,7 +688,7 @@ void WipingPanel::calc_flushing_volumes() const wxColour& from = multi_colors[from_idx][i]; for (int j = 0; j < multi_colors[to_idx].size(); ++j) { const wxColour& to = multi_colors[to_idx][j]; - int volume = calc_flushing_volume(from, to); + int volume = calc_flushing_volume(from, to, m_min_flush_volume[from_idx]); flushing_volume = std::max(flushing_volume, volume); } } diff --git a/src/slic3r/GUI/WipeTowerDialog.hpp b/src/slic3r/GUI/WipeTowerDialog.hpp index 8e8cd7c2b9..caed799bee 100644 --- a/src/slic3r/GUI/WipeTowerDialog.hpp +++ b/src/slic3r/GUI/WipeTowerDialog.hpp @@ -16,7 +16,7 @@ class WipingPanel : public wxPanel { public: // BBS WipingPanel(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours, Button* calc_button, - int extra_flush_volume, float flush_multiplier); + const std::vector& extra_flush_volume, float flush_multiplier); std::vector read_matrix_values(); std::vector read_extruders_values(); void toggle_advanced(bool user_action = false); @@ -36,7 +36,7 @@ class WipingPanel : public wxPanel { private: void fill_in_matrix(); bool advanced_matches_simple(); - int calc_flushing_volume(const wxColour& from, const wxColour& to); + int calc_flushing_volume(const wxColour& from, const wxColour& to,int min_flush_volume); void update_warning_texts(); std::vector m_old; @@ -58,7 +58,7 @@ class WipingPanel : public wxPanel { std::vector icon_list1; std::vector icon_list2; - const int m_min_flush_volume; + const std::vector m_min_flush_volume; const int m_max_flush_volume; wxTextCtrl* m_flush_multiplier_ebox = nullptr; @@ -75,7 +75,7 @@ class WipingDialog : public Slic3r::GUI::DPIDialog { public: WipingDialog(wxWindow* parent, const std::vector& matrix, const std::vector& extruders, const std::vector& extruder_colours, - int extra_flush_volume, float flush_multiplier); + const std::vector&extra_flush_volume,float flush_multiplier); std::vector get_matrix() const { return m_output_matrix; } std::vector get_extruders() const { return m_output_extruders; } wxBoxSizer* create_btn_sizer(long flags); From b02cb8aa4415f5072a65cca071201e04573ae2f0 Mon Sep 17 00:00:00 2001 From: "chunmao.guo" Date: Fri, 22 Mar 2024 16:03:59 +0800 Subject: [PATCH 21/70] FIX: enable resumed read only Field Change-Id: Id09e671932458699c020f0a061d8cfc11a6958ab Jira: STUDIO-6641 --- src/slic3r/GUI/Field.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index fac266f00a..de3c7666a1 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -128,6 +128,8 @@ void Field::PostInitialize() if (getWindow()) { if (m_opt.readonly) { this->disable(); + } else { + this->enable(); } getWindow()->Bind(wxEVT_KEY_UP, [](wxKeyEvent& evt) { if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) { From ffc5626296e57b01b324803ebfe613687501075a Mon Sep 17 00:00:00 2001 From: "zhimin.zeng" Date: Thu, 28 Mar 2024 20:56:20 +0800 Subject: [PATCH 22/70] ENH: add precise_z_height jira: none Change-Id: Idb9fcf0063e773f1531a49961478460b91ded10f --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 4 +- src/libslic3r/PrintConfig.cpp | 9 +++ src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObjectSlice.cpp | 2 +- src/libslic3r/Slicing.cpp | 99 +++++++++++++++++++++++++++++- src/libslic3r/Slicing.hpp | 3 +- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GUI_Factories.cpp | 4 +- src/slic3r/GUI/Tab.cpp | 1 + 10 files changed, 119 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 97d46a166f..84ca29e5de 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -832,7 +832,7 @@ static std::vector s_Preset_print_options { "tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance", "tree_support_branch_diameter","tree_support_brim_width", "detect_narrow_internal_solid_infill", - "gcode_add_line_number", "enable_arc_fitting", "infill_combination", /*"adaptive_layer_height",*/ + "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination", /*"adaptive_layer_height",*/ "support_bottom_interface_spacing", "enable_overhang_speed", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "initial_layer_infill_speed", "top_one_wall_type", "top_area_threshold", "only_one_wall_first_layer", "timelapse_type", "internal_bridge_support_thickness", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 5011765ead..26d1ef0260 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -209,6 +209,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n opt_key == "initial_layer_print_height" || opt_key == "nozzle_diameter" || opt_key == "resolution" + || opt_key == "precise_z_height" // Spiral Vase forces different kind of slicing than the normal model: // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. // Therefore toggling the Spiral Vase on / off requires complete reslicing. @@ -1143,8 +1144,9 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (has_custom_layering) { std::vector> layer_z_series; layer_z_series.assign(m_objects.size(), std::vector()); + for (size_t idx_object = 0; idx_object < m_objects.size(); ++idx_object) { - layer_z_series[idx_object] = generate_object_layers(m_objects[idx_object]->slicing_parameters(), layer_height_profiles[idx_object]); + layer_z_series[idx_object] = generate_object_layers(m_objects[idx_object]->slicing_parameters(), layer_height_profiles[idx_object], m_objects[idx_object]->config().precise_z_height.value); } for (size_t idx_object = 0; idx_object < m_objects.size(); ++idx_object) { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a3aa709660..90cee472ab 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1846,6 +1846,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(30)); + // BBS + def = this->add("precise_z_height", coBool); + def->label = L("Precise Z height"); + def->tooltip = L("Enable this to get precise z height of object after slicing. " + "It will get the precise object height by fine-tuning the layer heights of the last few layers. " + "Note that this is an experimental parameter."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(0)); + // BBS def = this->add("enable_arc_fitting", coBool); def->label = L("Arc fitting"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c22b093d28..1379a7f303 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -760,6 +760,7 @@ PRINT_CONFIG_CLASS_DEFINE( // OrcaSlicer ((ConfigOptionPercent, seam_gap)) ((ConfigOptionPercent, wipe_speed)) + ((ConfigOptionBool, precise_z_height)) // BBS ) // This object is mapped to Perl as Slic3r::Config::PrintRegion. diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index f9622a6ed7..4a680a9c8e 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -790,7 +790,7 @@ void PrintObject::slice() m_print->throw_if_canceled(); m_typed_slices = false; this->clear_layers(); - m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile)); + m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile, m_config.precise_z_height.value)); this->slice_volumes(); m_print->throw_if_canceled(); int firstLayerReplacedBy = 0; diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index b7e1593166..d81684e46d 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -629,10 +629,104 @@ void adjust_layer_height_profile( #endif /* _DEBUG */ } +bool adjust_layer_series_to_align_object_height(const SlicingParameters &slicing_params, std::vector& layer_series) +{ + coordf_t object_height = slicing_params.object_print_z_height(); + if (is_approx(layer_series.back(), object_height)) + return true; + + // need at least 5 + 1(first_layer) layers to adjust the height + size_t layer_size = layer_series.size(); + if (layer_size < 12) + return false; + + std::vector last_5_layers_heght; + for (size_t i = 0; i < 5; ++i) { + last_5_layers_heght.emplace_back(layer_series[layer_size - 10 + 2 * i + 1] - layer_series[layer_size - 10 + 2 * i]); + } + + coordf_t gap = abs(layer_series.back() - object_height); + std::vector can_adjust(5, true); // to record whether every layer can adjust layer height + bool taller_than_object = layer_series.back() < object_height; + + auto get_valid_size = [&can_adjust]() -> int { + int valid_size = 0; + for (auto b_adjust : can_adjust) { + valid_size += b_adjust ? 1 : 0; + } + return valid_size; + }; + + auto adjust_layer_height = [&slicing_params, &last_5_layers_heght, &can_adjust, &get_valid_size, &taller_than_object](coordf_t gap) -> coordf_t { + coordf_t delta_gap = gap / get_valid_size(); + coordf_t remain_gap = 0; + for (size_t i = 0; i < last_5_layers_heght.size(); ++i) { + coordf_t& l_height = last_5_layers_heght[i]; + if (taller_than_object) { + if (can_adjust[i] && is_approx(l_height, slicing_params.max_layer_height)) { + remain_gap += delta_gap; + can_adjust[i] = false; + continue; + } + + if (can_adjust[i] && l_height + delta_gap > slicing_params.max_layer_height) { + remain_gap += l_height + delta_gap - slicing_params.max_layer_height; + l_height = slicing_params.max_layer_height; + can_adjust[i] = false; + } + else { + l_height += delta_gap; + } + } + else { + if (can_adjust[i] && is_approx(l_height, slicing_params.min_layer_height)) { + remain_gap += delta_gap; + can_adjust[i] = false; + continue; + } + + if (can_adjust[i] && l_height - delta_gap < slicing_params.min_layer_height) { + remain_gap += slicing_params.min_layer_height + delta_gap - l_height; + l_height = slicing_params.min_layer_height; + can_adjust[i] = false; + } + else { + l_height -= delta_gap; + } + } + } + return remain_gap; + }; + + while (gap > 0) { + int valid_size = get_valid_size(); + if (valid_size == 0) { + // 5 layers can not adjust z within valid layer height + return false; + } + + gap = adjust_layer_height(gap); + if (is_approx(gap, 0.0)) { + // adjust succeed + break; + } + } + + for (size_t i = 0; i < last_5_layers_heght.size(); ++i) { + if (i > 0) { + layer_series[layer_size - 10 + 2 * i] = layer_series[layer_size - 10 + 2 * i - 1]; + } + layer_series[layer_size - 10 + 2 * i + 1] = layer_series[layer_size - 10 + 2 * i] + last_5_layers_heght[i]; + } + + return true; +} + // Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. std::vector generate_object_layers( const SlicingParameters &slicing_params, - const std::vector &layer_height_profile) + const std::vector &layer_height_profile, + bool is_precise_z_height) { assert(! layer_height_profile.empty()); @@ -681,7 +775,8 @@ std::vector generate_object_layers( out.push_back(print_z); } - //FIXME Adjust the last layer to align with the top object layer exactly? + if (is_precise_z_height) + adjust_layer_series_to_align_object_height(slicing_params, out); return out; } diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 00ea608e6c..5407548772 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -173,7 +173,8 @@ extern void adjust_layer_height_profile( // The object layers are based at z=0, ignoring the raft layers. extern std::vector generate_object_layers( const SlicingParameters &slicing_params, - const std::vector &layer_height_profile); + const std::vector &layer_height_profile, + bool is_precise_z_height); // Check whether the layer height profile describes a fixed layer height profile. bool check_object_layers_fixed( diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b79ef70104..fa3965690d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -591,7 +591,7 @@ void GLCanvas3D::LayersEditing::generate_layer_height_texture() bool level_of_detail_2nd_level = true; m_layers_texture.cells = Slic3r::generate_layer_height_texture( *m_slicing_parameters, - Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile), + Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile, false), m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level); m_layers_texture.valid = true; } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 712140e7eb..8c16b75d35 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -77,7 +77,9 @@ std::map> SettingsFactory::OBJECT_C {"wall_sequence","",2}, {"seam_position", "",3}, {"seam_gap", "",4}, {"wipe_speed", "",5}, {"slice_closing_radius", "",6}, {"resolution", "",7}, - {"xy_hole_compensation", "",8}, {"xy_contour_compensation", "",9}, {"elefant_foot_compensation", "",10} + {"xy_hole_compensation", "",8}, {"xy_contour_compensation", "",9}, {"elefant_foot_compensation", "",10}, + {"precise_z_height", "",10} + }}, { L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3}, {"enable_support", "",4},{"support_type", "",5},{"support_threshold_angle", "",6},{"support_on_build_plate_only", "",7}, diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 87708b8798..6845abfd57 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1967,6 +1967,7 @@ void TabPrint::build() optgroup->append_single_option_line("xy_hole_compensation", "xy-hole-contour-compensation"); optgroup->append_single_option_line("xy_contour_compensation", "xy-hole-contour-compensation"); optgroup->append_single_option_line("elefant_foot_compensation", "parameter/elephant-foot"); + optgroup->append_single_option_line("precise_z_height"); optgroup = page->new_optgroup(L("Ironing"), L"param_ironing"); optgroup->append_single_option_line("ironing_type", "parameter/ironing"); From f3b4dd126321af911addd9a9c2df12dfd46d9649 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 27 Mar 2024 20:48:03 +0800 Subject: [PATCH 23/70] ENH: modify the multi-material segmentation and voronoi This patch is cherry pick from Prusa, thanks to Prusa Rework multi-material segmentation to work directly on the Voronoi diagram without creating a copy of it. Previous algorithms assume that they can get an invalid Voronoi diagram. Because of that, during the multi-material segmentation, a copy of the Voronoi diagram was created, and there were several attempts to fix missing vertices and edges. But as it shows, this wasn't a good enough approach and sometimes led to several issues like bleeding layers. After generalization, our approach for detection and repairs of invalid Voronoi diagrams from Arachne, we could assume that multi-material segmentation gets non-invalid Voronoi diagrams. With this assumption, we reimplement multi-materials segmentation to work directly on the Voronoi diagram. That should make multi-material segmentation more stable. So, this should fix several issues like bleeding layers. Also, memory consumption should decrease by a lot. Also, there should be some speedup of multi-materials segmentation. Jira: none Change-Id: I72aa6e1f9634d9ee8759aa469a0b39a36ace62f5 --- src/ankerl/README.txt | 7 + src/ankerl/unordered_dense.h | 1584 +++++++++++++++++ .../Arachne/SkeletalTrapezoidation.cpp | 384 +--- .../Arachne/SkeletalTrapezoidation.hpp | 54 +- .../Arachne/utils/PolygonsSegmentIndex.hpp | 19 + src/libslic3r/Arachne/utils/VoronoiUtils.cpp | 251 --- src/libslic3r/Arachne/utils/VoronoiUtils.hpp | 47 - src/libslic3r/CMakeLists.txt | 5 +- src/libslic3r/Geometry/MedialAxis.cpp | 2 +- src/libslic3r/Geometry/Voronoi.cpp | 354 ++++ src/libslic3r/Geometry/Voronoi.hpp | 184 +- src/libslic3r/Geometry/VoronoiOffset.cpp | 6 - src/libslic3r/Geometry/VoronoiUtils.cpp | 283 +++ src/libslic3r/Geometry/VoronoiUtils.hpp | 120 ++ src/libslic3r/Geometry/VoronoiUtilsCgal.cpp | 269 ++- src/libslic3r/Geometry/VoronoiUtilsCgal.hpp | 9 +- src/libslic3r/MultiMaterialSegmentation.cpp | 1274 ++++--------- src/libslic3r/MultiMaterialSegmentation.hpp | 30 +- 18 files changed, 3252 insertions(+), 1630 deletions(-) create mode 100644 src/ankerl/README.txt create mode 100644 src/ankerl/unordered_dense.h delete mode 100644 src/libslic3r/Arachne/utils/VoronoiUtils.cpp delete mode 100644 src/libslic3r/Arachne/utils/VoronoiUtils.hpp create mode 100644 src/libslic3r/Geometry/Voronoi.cpp create mode 100644 src/libslic3r/Geometry/VoronoiUtils.cpp create mode 100644 src/libslic3r/Geometry/VoronoiUtils.hpp diff --git a/src/ankerl/README.txt b/src/ankerl/README.txt new file mode 100644 index 0000000000..0996bc28cf --- /dev/null +++ b/src/ankerl/README.txt @@ -0,0 +1,7 @@ +THIS DIRECTORY CONTAINS PIECES OF THE +ankerl::unordered_dense::{map, set} +https://github.com/martinus/unordered_dense +unordered_dense 3.1.1 10782bfc651c2bb75b11bf90491f50da122e5432 +SOURCE DISTRIBUTION. + +THIS IS NOT THE COMPLETE unordered_dense DISTRIBUTION. ONLY FILES NEEDED FOR COMPILING PRUSASLICER WERE PUT INTO THE PRUSASLICER SOURCE DISTRIBUTION. diff --git a/src/ankerl/unordered_dense.h b/src/ankerl/unordered_dense.h new file mode 100644 index 0000000000..e294bdb4e6 --- /dev/null +++ b/src/ankerl/unordered_dense.h @@ -0,0 +1,1584 @@ +///////////////////////// ankerl::unordered_dense::{map, set} ///////////////////////// + +// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion. +// Version 3.1.1 +// https://github.com/martinus/unordered_dense +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2022-2023 Martin Leitner-Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ANKERL_UNORDERED_DENSE_H +#define ANKERL_UNORDERED_DENSE_H + +// see https://semver.org/spec/v2.0.0.html +#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 3 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes +#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality +#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes + +// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/ +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) +#define ANKERL_UNORDERED_DENSE_NAMESPACE \ + ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \ + ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH) + +#if defined(_MSVC_LANG) +# define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG +#else +# define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus +#endif + +#if defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__)) +#elif defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) +#endif + +// exceptions +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1 +#else +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0 +#endif +#ifdef _MSC_VER +# define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline) +#else +# define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline)) +#endif + +#if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L +# error ankerl::unordered_dense requires C++17 or higher +#else +# include // for array +# include // for uint64_t, uint32_t, uint8_t, UINT64_C +# include // for size_t, memcpy, memset +# include // for equal_to, hash +# include // for initializer_list +# include // for pair, distance +# include // for numeric_limits +# include // for allocator, allocator_traits, shared_ptr +# include // for out_of_range +# include // for basic_string +# include // for basic_string_view, hash +# include // for forward_as_tuple +# include // for enable_if_t, declval, conditional_t, ena... +# include // for forward, exchange, pair, as_const, piece... +# include // for vector +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() == 0 +# include // for abort +# endif + +# define ANKERL_UNORDERED_DENSE_PMR 0 // NOLINT(cppcoreguidelines-macro-usage) +# if defined(__has_include) +# if __has_include() +# undef ANKERL_UNORDERED_DENSE_PMR +# define ANKERL_UNORDERED_DENSE_PMR 1 // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR \ + std::pmr::polymorphic_allocator // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# elif __has_include() +# undef ANKERL_UNORDERED_DENSE_PMR +# define ANKERL_UNORDERED_DENSE_PMR 1 // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PMR_ALLOCATOR \ + std::experimental::pmr::polymorphic_allocator // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# endif +# endif + +# if defined(_MSC_VER) && defined(_M_X64) +# include +# pragma intrinsic(_umul128) +# endif + +# if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +# define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage) +# else +# define ANKERL_UNORDERED_DENSE_LIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# endif + +namespace ankerl::unordered_dense { +inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE { + +namespace detail { + +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() { + throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found"); +} +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() { + throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size"); +} +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() { + throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements"); +} + +# else + +[[noreturn]] inline void on_error_key_not_found() { + abort(); +} +[[noreturn]] inline void on_error_bucket_overflow() { + abort(); +} +[[noreturn]] inline void on_error_too_many_elements() { + abort(); +} + +# endif + +} // namespace detail + +// hash /////////////////////////////////////////////////////////////////////// + +// This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash +// No big-endian support (because different values on different machines don't matter), +// hardcodes seed and the secret, reformattes the code, and clang-tidy fixes. +namespace detail::wyhash { + +static inline void mum(uint64_t* a, uint64_t* b) { +# if defined(__SIZEOF_INT128__) + __uint128_t r = *a; + r *= *b; + *a = static_cast(r); + *b = static_cast(r >> 64U); +# elif defined(_MSC_VER) && defined(_M_X64) + *a = _umul128(*a, *b, b); +# else + uint64_t ha = *a >> 32U; + uint64_t hb = *b >> 32U; + uint64_t la = static_cast(*a); + uint64_t lb = static_cast(*b); + uint64_t hi{}; + uint64_t lo{}; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + (rm0 << 32U); + auto c = static_cast(t < rl); + lo = t + (rm1 << 32U); + c += static_cast(lo < t); + hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c; + *a = lo; + *b = hi; +# endif +} + +// multiply and xor mix function, aka MUM +[[nodiscard]] static inline auto mix(uint64_t a, uint64_t b) -> uint64_t { + mum(&a, &b); + return a ^ b; +} + +// read functions. WARNING: we don't care about endianness, so results are different on big endian! +[[nodiscard]] static inline auto r8(const uint8_t* p) -> uint64_t { + uint64_t v{}; + std::memcpy(&v, p, 8U); + return v; +} + +[[nodiscard]] static inline auto r4(const uint8_t* p) -> uint64_t { + uint32_t v{}; + std::memcpy(&v, p, 4); + return v; +} + +// reads 1, 2, or 3 bytes +[[nodiscard]] static inline auto r3(const uint8_t* p, size_t k) -> uint64_t { + return (static_cast(p[0]) << 16U) | (static_cast(p[k >> 1U]) << 8U) | p[k - 1]; +} + +[[maybe_unused]] [[nodiscard]] static inline auto hash(void const* key, size_t len) -> uint64_t { + static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f), + UINT64_C(0xe7037ed1a0b428db), + UINT64_C(0x8ebc6af09c88c6e3), + UINT64_C(0x589965cc75374cc3)}; + + auto const* p = static_cast(key); + uint64_t seed = secret[0]; + uint64_t a{}; + uint64_t b{}; + if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) { + if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) { + a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U)); + b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U)); + } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) { + a = r3(p, len); + b = 0; + } else { + a = 0; + b = 0; + } + } else { + size_t i = len; + if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) { + uint64_t see1 = seed; + uint64_t see2 = seed; + do { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1); + see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2); + p += 48; + i -= 48; + } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48)); + seed ^= see1 ^ see2; + } + while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + i -= 16; + p += 16; + } + a = r8(p + i - 16); + b = r8(p + i - 8); + } + + return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed)); +} + +[[nodiscard]] static inline auto hash(uint64_t x) -> uint64_t { + return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15)); +} + +} // namespace detail::wyhash + +template +struct hash { + auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) + -> uint64_t { + return std::hash{}(obj); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string const& str) const noexcept -> uint64_t { + return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size()); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string_view const& sv) const noexcept -> uint64_t { + return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size()); + } +}; + +template +struct hash { + using is_avalanching = void; + auto operator()(T* ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr)); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::unique_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::shared_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash::value>::type> { + using is_avalanching = void; + auto operator()(Enum e) const noexcept -> uint64_t { + using underlying = typename std::underlying_type_t; + return detail::wyhash::hash(static_cast(e)); + } +}; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T) \ + template <> \ + struct hash { \ + using is_avalanching = void; \ + auto operator()(T const& obj) const noexcept -> uint64_t { \ + return detail::wyhash::hash(static_cast(obj)); \ + } \ + } + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +# endif +// see https://en.cppreference.com/w/cpp/utility/hash +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char); +# if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t); +# endif +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long); + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +# endif + +// bucket_type ////////////////////////////////////////////////////////// + +namespace bucket_type { + +struct standard { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + uint32_t m_value_idx; // index into the m_values vector. +}; + +ANKERL_UNORDERED_DENSE_PACK(struct big { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + size_t m_value_idx; // index into the m_values vector. +}); + +} // namespace bucket_type + +namespace detail { + +struct nonesuch {}; + +template class Op, class... Args> +struct detector { + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; +}; + +template