diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9aa4cf3c6..10ef9127c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include "SVG.hpp" #include #include "GCodeWriter.hpp" @@ -1055,12 +1057,18 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ // This method is used by the auto arrange function. Polygon ModelObject::convex_hull_2d(const Transform3d& trafo_instance) const { - Points pts; - for (const ModelVolume* v : volumes) { - if (v->is_model_part()) - append(pts, its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast(), 0.0f).points); - } - return Geometry::convex_hull(std::move(pts)); + tbb::concurrent_vector chs; + chs.reserve(volumes.size()); + tbb::parallel_for(tbb::blocked_range(0, volumes.size()), [&](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + const ModelVolume* v = volumes[i]; + chs.emplace_back(its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast(), 0.0f)); + } + }); + + Polygons polygons; + polygons.assign(chs.begin(), chs.end()); + return Geometry::convex_hull(polygons); } void ModelObject::center_around_origin(bool include_modifiers) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 5b23dc260..7e196c85f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -469,11 +469,13 @@ std::string Print::validate(std::string* warning) const return _u8L("The supplied settings will cause an empty print."); if (m_config.complete_objects) { - if (! sequential_print_horizontal_clearance_valid(*this)) + if (!sequential_print_horizontal_clearance_valid(*this, const_cast(&m_sequential_print_clearance_contours))) return _u8L("Some objects are too close; your extruder will collide with them."); - if (! sequential_print_vertical_clearance_valid(*this)) - return _u8L("Some objects are too tall and cannot be printed without extruder collisions."); + if (!sequential_print_vertical_clearance_valid(*this)) + return _u8L("Some objects are too tall and cannot be printed without extruder collisions."); } + else + const_cast(&m_sequential_print_clearance_contours)->clear(); if (m_config.avoid_crossing_perimeters && m_config.avoid_crossing_curled_overhangs) { return _u8L("Avoid crossing perimeters option and avoid crossing curled overhangs option cannot be both enabled together."); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 30f6bb41a..c96b0ca9a 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -609,6 +609,7 @@ public: const PrintRegion& get_print_region(size_t idx) const { return *m_print_regions[idx]; } const ToolOrdering& get_tool_ordering() const { return m_wipe_tower_data.tool_ordering; } + const Polygons& get_sequential_print_clearance_contours() const { return m_sequential_print_clearance_contours; } static bool sequential_print_horizontal_clearance_valid(const Print& print, Polygons* polygons = nullptr); protected: @@ -658,6 +659,9 @@ private: // Estimated print time, filament consumed. PrintStatistics m_print_statistics; + // Cache to store sequential print clearance contours + Polygons m_sequential_print_clearance_contours; + // To allow GCode to set the Print's GCodeExport step status. friend class GCode; // To allow GCodeProcessor to emit warnings. diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index d35251d20..66bf913e0 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -26,6 +26,8 @@ #include #include +#include + #include #include @@ -871,11 +873,38 @@ void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, c } template -Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const TransformVertex &transform_fn, const float z) +Polygon its_convex_hull_2d_above(const indexed_triangle_set& its, const TransformVertex& transform_fn, const float z) { - Points all_pts; - its_collect_mesh_projection_points_above(its, transform_fn, z, all_pts); - return Geometry::convex_hull(std::move(all_pts)); + auto collect_mesh_projection_points_above = [&](const tbb::blocked_range& range) { + Points pts; + pts.reserve(range.size() * 4); // there can be up to 4 vertices per triangle + for (size_t i = range.begin(); i < range.end(); ++i) { + const stl_triangle_vertex_indices& tri = its.indices[i]; + const Vec3f tri_pts[3] = { transform_fn(its.vertices[tri(0)]), transform_fn(its.vertices[tri(1)]), transform_fn(its.vertices[tri(2)]) }; + int iprev = 2; + for (int iedge = 0; iedge < 3; ++iedge) { + const Vec3f& p1 = tri_pts[iprev]; + const Vec3f& p2 = tri_pts[iedge]; + if ((p1.z() < z && p2.z() > z) || (p2.z() < z && p1.z() > z)) { + // Edge crosses the z plane. Calculate intersection point with the plane. + const float t = (z - p1.z()) / (p2.z() - p1.z()); + pts.emplace_back(scaled(p1.x() + (p2.x() - p1.x()) * t), scaled(p1.y() + (p2.y() - p1.y()) * t)); + } + if (p2.z() >= z) + pts.emplace_back(scaled(p2.x()), scaled(p2.y())); + iprev = iedge; + } + } + return Geometry::convex_hull(std::move(pts)); + }; + + tbb::concurrent_vector chs; + tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), [&](const tbb::blocked_range& range) { + chs.push_back(collect_mesh_projection_points_above(range)); + }); + + const Polygons polygons(chs.begin(), chs.end()); + return Geometry::convex_hull(polygons); } Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index dea40fb3e..7c5008554 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -833,64 +833,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab } } -bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, ModelInstanceEPrintVolumeState *out_state) const -{ - const Model& model = GUI::wxGetApp().plater()->model(); - auto volume_below = [](GLVolume& volume) -> bool - { return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_below_printbed(); }; - // Volume is partially below the print bed, thus a pre-calculated convex hull cannot be used. - auto volume_sinking = [](GLVolume& volume) -> bool - { return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_sinking(); }; - // Cached bounding box of a volume above the print bed. - auto volume_bbox = [volume_sinking](GLVolume& volume) -> BoundingBoxf3 - { return volume_sinking(volume) ? volume.transformed_non_sinking_bounding_box() : volume.transformed_convex_hull_bounding_box(); }; - // Cached 3D convex hull of a volume above the print bed. - auto volume_convex_mesh = [volume_sinking, &model](GLVolume& volume) -> const TriangleMesh& - { return volume_sinking(volume) ? model.objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : *volume.convex_hull(); }; - - ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside; - bool contained_min_one = false; - - for (GLVolume* volume : this->volumes) - if (! volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (! volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) { - BuildVolume::ObjectState state; - if (volume_below(*volume)) - state = BuildVolume::ObjectState::Below; - else { - switch (build_volume.type()) { - case BuildVolume::Type::Rectangle: - //FIXME this test does not evaluate collision of a build volume bounding box with non-convex objects. - state = build_volume.volume_state_bbox(volume_bbox(*volume)); - break; - case BuildVolume::Type::Circle: - case BuildVolume::Type::Convex: - //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. - case BuildVolume::Type::Custom: - state = build_volume.object_state(volume_convex_mesh(*volume).its, volume->world_matrix().cast(), volume_sinking(*volume)); - break; - default: - // Ignore, don't produce any collision. - state = BuildVolume::ObjectState::Inside; - break; - } - assert(state != BuildVolume::ObjectState::Below); - } - volume->is_outside = state != BuildVolume::ObjectState::Inside; - if (volume->printable) { - if (overall_state == ModelInstancePVS_Inside && volume->is_outside) - overall_state = ModelInstancePVS_Fully_Outside; - if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding) - overall_state = ModelInstancePVS_Partly_Outside; - contained_min_one |= !volume->is_outside; - } - } - - if (out_state != nullptr) - *out_state = overall_state; - - return contained_min_one; -} - void GLVolumeCollection::reset_outside_state() { for (GLVolume* volume : this->volumes) { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2f7e0a767..805972ce7 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -450,9 +450,6 @@ public: void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } - // returns true if all the volumes are completely contained in the print volume - // returns the containment state in the given out_state, if non-null - bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const; void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c90eae5d3..d3bbe9d15 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -880,20 +880,22 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas ImGui::PopStyleVar(2); } -void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons) +void GLCanvas3D::SequentialPrintClearance::set_contours(const ContoursList& contours, bool generate_fill) { - m_perimeter.reset(); + m_contours.clear(); + m_instances.clear(); m_fill.reset(); - if (polygons.empty()) + + if (contours.empty()) return; - if (m_render_fill) { + if (generate_fill) { GLModel::Geometry fill_data; fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; + fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; // vertices + indices - const ExPolygons polygons_union = union_ex(polygons); + const ExPolygons polygons_union = union_ex(contours.contours); unsigned int vertices_counter = 0; for (const ExPolygon& poly : polygons_union) { const std::vector triangulation = triangulate_expolygon_3d(poly); @@ -906,17 +908,48 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } } - m_fill.init_from(std::move(fill_data)); } - m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting + for (size_t i = 0; i < contours.contours.size(); ++i) { + GLModel& model = m_contours.emplace_back(GLModel()); + model.init_from(contours.contours[i], 0.025f); // add a small positive z to avoid z-fighting + } + + if (contours.trafos.has_value()) { + // create the requested instances + for (const auto& instance : *contours.trafos) { + m_instances.emplace_back(instance.first, instance.second); + } + } + else { + // no instances have been specified + // create one instance for every polygon + for (size_t i = 0; i < contours.contours.size(); ++i) { + m_instances.emplace_back(i, Transform3f::Identity()); + } + } +} + +void GLCanvas3D::SequentialPrintClearance::update_instances_trafos(const std::vector& trafos) +{ + if (trafos.size() == m_instances.size()) { + for (size_t i = 0; i < trafos.size(); ++i) { + m_instances[i].second = trafos[i]; + } + } + else + assert(false); } void GLCanvas3D::SequentialPrintClearance::render() { - const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; - const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; + const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; + const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; + const ColorRGBA NO_FILL_EVALUATING_COLOR = { 1.0f, 1.0f, 0.0f, 1.0f }; + + if (m_contours.empty() || m_instances.empty()) + return; GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader == nullptr) @@ -933,9 +966,34 @@ void GLCanvas3D::SequentialPrintClearance::render() glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); - m_perimeter.render(); - m_fill.render(); + if (!m_evaluating) + m_fill.render(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("dashed_thick_lines"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 1.0f); + shader->set_uniform("gap_size", 0.0f); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + + for (const auto& [id, trafo] : m_instances) { + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo); + assert(id < m_contours.size()); + m_contours[id].set_color((!m_evaluating && m_fill.is_initialized()) ? FILL_COLOR : m_evaluating ? NO_FILL_EVALUATING_COLOR : NO_FILL_COLOR); + m_contours[id].render(); + } glsafe(::glDisable(GL_BLEND)); glsafe(::glEnable(GL_CULL_FACE)); @@ -1450,14 +1508,97 @@ void GLCanvas3D::reset_volumes() _set_warning_notification(EWarning::ObjectOutside, false); } -ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const +ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(bool selection_only) const { ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; - if (m_initialized) - m_volumes.check_outside_state(m_bed.build_volume(), &state); + if (m_initialized && !m_volumes.empty()) + check_volumes_outside_state(m_bed.build_volume(), &state, selection_only); return state; } +bool GLCanvas3D::check_volumes_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state, bool selection_only) const +{ + auto volume_below = [](GLVolume& volume) -> bool + { return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_below_printbed(); }; + // Volume is partially below the print bed, thus a pre-calculated convex hull cannot be used. + auto volume_sinking = [](GLVolume& volume) -> bool + { return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_sinking(); }; + // Cached bounding box of a volume above the print bed. + auto volume_bbox = [volume_sinking](GLVolume& volume) -> BoundingBoxf3 + { return volume_sinking(volume) ? volume.transformed_non_sinking_bounding_box() : volume.transformed_convex_hull_bounding_box(); }; + // Cached 3D convex hull of a volume above the print bed. + auto volume_convex_mesh = [this, volume_sinking](GLVolume& volume) -> const TriangleMesh& + { return volume_sinking(volume) ? m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : *volume.convex_hull(); }; + + auto volumes_to_process_idxs = [this, selection_only]() { + std::vector ret; + if (!selection_only || m_selection.is_empty()) { + ret = std::vector(m_volumes.volumes.size()); + std::iota(ret.begin(), ret.end(), 0); + } + else { + const GUI::Selection::IndicesList& selected_volume_idxs = m_selection.get_volume_idxs(); + ret.assign(selected_volume_idxs.begin(), selected_volume_idxs.end()); + } + return ret; + }; + + ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside; + bool contained_min_one = false; + + const std::vector volumes_idxs = volumes_to_process_idxs(); + + for (unsigned int vol_idx : volumes_idxs) { + GLVolume* volume = m_volumes.volumes[vol_idx]; + if (!volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (!volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) { + BuildVolume::ObjectState state; + if (volume_below(*volume)) + state = BuildVolume::ObjectState::Below; + else { + switch (build_volume.type()) { + case BuildVolume::Type::Rectangle: + //FIXME this test does not evaluate collision of a build volume bounding box with non-convex objects. + state = build_volume.volume_state_bbox(volume_bbox(*volume)); + break; + case BuildVolume::Type::Circle: + case BuildVolume::Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case BuildVolume::Type::Custom: + state = build_volume.object_state(volume_convex_mesh(*volume).its, volume->world_matrix().cast(), volume_sinking(*volume)); + break; + default: + // Ignore, don't produce any collision. + state = BuildVolume::ObjectState::Inside; + break; + } + assert(state != BuildVolume::ObjectState::Below); + } + volume->is_outside = state != BuildVolume::ObjectState::Inside; + if (volume->printable) { + if (overall_state == ModelInstancePVS_Inside && volume->is_outside) + overall_state = ModelInstancePVS_Fully_Outside; + if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding) + overall_state = ModelInstancePVS_Partly_Outside; + contained_min_one |= !volume->is_outside; + } + } + } + + for (unsigned int vol_idx = 0; vol_idx < m_volumes.volumes.size(); ++vol_idx) { + if (std::find(volumes_idxs.begin(), volumes_idxs.end(), vol_idx) == volumes_idxs.end()) { + if (!m_volumes.volumes[vol_idx]->is_outside) { + contained_min_one = true; + break; + } + } + } + + if (out_state != nullptr) + *out_state = overall_state; + + return contained_min_one; +} + void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) { if (current_printer_technology() != ptSLA) @@ -2462,7 +2603,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { ModelInstanceEPrintVolumeState state; - const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); + const bool contained_min_one = check_volumes_outside_state(m_bed.build_volume(), &state, !force_full_scene_refresh); const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); @@ -3429,17 +3570,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // Not only detection of some modifiers !!! if (evt.Dragging()) { GLGizmosManager::EType c = m_gizmos.get_current_type(); - if (current_printer_technology() == ptFFF && - fff_print()->config().complete_objects){ - if (c == GLGizmosManager::EType::Move || - c == GLGizmosManager::EType::Scale || - c == GLGizmosManager::EType::Rotate ) - update_sequential_clearance(); - } else { - if (c == GLGizmosManager::EType::Move || - c == GLGizmosManager::EType::Scale || - c == GLGizmosManager::EType::Rotate) - show_sinking_contours(); + if (c == GLGizmosManager::EType::Move || + c == GLGizmosManager::EType::Scale || + c == GLGizmosManager::EType::Rotate) { + show_sinking_contours(); + if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) + update_sequential_clearance(true); } } else if (evt.LeftUp() && @@ -3642,7 +3778,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) trafo_type.set_relative(); m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) - update_sequential_clearance(); + update_sequential_clearance(false); wxGetApp().obj_manipul()->set_dirty(); m_dirty = true; } @@ -3948,7 +4084,10 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) if (wipe_tower_origin != Vec3d::Zero()) post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); - reset_sequential_print_clearance(); + if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) { + update_sequential_clearance(true); + m_sequential_print_clearance.m_evaluating = true; + } m_dirty = true; } @@ -4033,6 +4172,11 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) { + update_sequential_clearance(true); + m_sequential_print_clearance.m_evaluating = true; + } + m_dirty = true; } @@ -4105,6 +4249,11 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); + if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) { + update_sequential_clearance(true); + m_sequential_print_clearance.m_evaluating = true; + } + m_dirty = true; } @@ -4372,16 +4521,33 @@ void GLCanvas3D::mouse_up_cleanup() m_canvas->ReleaseMouse(); } -void GLCanvas3D::update_sequential_clearance() +void GLCanvas3D::update_sequential_clearance(bool force_contours_generation) { if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects) return; - if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) + if (m_layers_editing.is_enabled()) return; + auto instance_transform_from_volumes = [this](int object_idx, int instance_idx) { + for (const GLVolume* v : m_volumes.volumes) { + if (v->object_idx() == object_idx && v->instance_idx() == instance_idx) + return v->get_instance_transformation(); + } + assert(false); + return Geometry::Transformation(); + }; + + auto is_object_outside_printbed = [this](int object_idx) { + for (const GLVolume* v : m_volumes.volumes) { + if (v->object_idx() == object_idx && v->is_outside) + return true; + } + return false; + }; + // collects instance transformations from volumes - // first define temporary cache + // first: define temporary cache unsigned int instances_count = 0; std::vector>> instance_transforms; for (size_t obj = 0; obj < m_model->objects.size(); ++obj) { @@ -4396,67 +4562,95 @@ void GLCanvas3D::update_sequential_clearance() if (instances_count == 1) return; - // second fill temporary cache with data from volumes + // second: fill temporary cache with data from volumes for (const GLVolume* v : m_volumes.volumes) { - if (v->is_modifier || v->is_wipe_tower) + if (v->is_wipe_tower) continue; - auto& transform = instance_transforms[v->object_idx()][v->instance_idx()]; + const int object_idx = v->object_idx(); + const int instance_idx = v->instance_idx(); + auto& transform = instance_transforms[object_idx][instance_idx]; if (!transform.has_value()) - transform = v->get_instance_transformation(); + transform = instance_transform_from_volumes(object_idx, instance_idx); } + // helper function to calculate the transformation to be applied to the sequential print clearance contours + auto instance_trafo = [](const Transform3d& hull_trafo, const Geometry::Transformation& inst_trafo) { + Vec3d offset = inst_trafo.get_offset() - hull_trafo.translation(); + offset.z() = 0.0; + return Geometry::translation_transform(offset) * + Geometry::rotation_transform(Geometry::rotation_diff_z(hull_trafo, inst_trafo.get_matrix()) * Vec3d::UnitZ()); + }; + // calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) // this is done only the first time this method is called while moving the mouse, // the results are then cached for following displacements - if (m_sequential_print_clearance_first_displacement) { - m_sequential_print_clearance.m_hull_2d_cache.clear(); + if (force_contours_generation || m_sequential_print_clearance_first_displacement) { + m_sequential_print_clearance.m_evaluating = false; + m_sequential_print_clearance.m_hulls_2d_cache.clear(); const float shrink_factor = static_cast(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON)); const double mitter_limit = scale_(0.1); - m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size()); + m_sequential_print_clearance.m_hulls_2d_cache.reserve(m_model->objects.size()); for (size_t i = 0; i < m_model->objects.size(); ++i) { ModelObject* model_object = m_model->objects[i]; - ModelInstance* model_instance0 = model_object->instances.front(); - Geometry::Transformation trafo = model_instance0->get_transformation(); - trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() }); - const Polygon hull_2d = offset(model_object->convex_hull_2d(trafo.get_matrix()), + Geometry::Transformation trafo = instance_transform_from_volumes((int)i, 0); + trafo.set_offset({ 0.0, 0.0, trafo.get_offset().z() }); + Pointf3s& new_hull_2d = m_sequential_print_clearance.m_hulls_2d_cache.emplace_back(std::make_pair(Pointf3s(), trafo.get_matrix())).first; + if (is_object_outside_printbed((int)i)) + continue; + + Polygon hull_2d = model_object->convex_hull_2d(trafo.get_matrix()); + if (!hull_2d.empty()) { // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - shrink_factor, - jtRound, mitter_limit).front(); + const Polygons offset_res = offset(hull_2d, shrink_factor, jtRound, mitter_limit); + if (!offset_res.empty()) + hull_2d = offset_res.front(); + } - Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s()); - cache_hull_2d.reserve(hull_2d.points.size()); + new_hull_2d.reserve(hull_2d.points.size()); const Transform3d inv_trafo = trafo.get_matrix().inverse(); for (const Point& p : hull_2d.points) { - cache_hull_2d.emplace_back(inv_trafo * Vec3d(unscale(p.x()), unscale(p.y()), 0.0)); + new_hull_2d.emplace_back(Vec3d(unscale(p.x()), unscale(p.y()), 0.0)); } } + + ContoursList contours; + contours.contours.reserve(instance_transforms.size()); + contours.trafos = std::vector>(); + (*contours.trafos).reserve(instances_count); + for (size_t i = 0; i < instance_transforms.size(); ++i) { + const auto& [hull, hull_trafo] = m_sequential_print_clearance.m_hulls_2d_cache[i]; + Points hull_pts; + hull_pts.reserve(hull.size()); + for (size_t j = 0; j < hull.size(); ++j) { + hull_pts.emplace_back(scaled(hull[j].x()), scaled(hull[j].y())); + } + contours.contours.emplace_back(Geometry::convex_hull(std::move(hull_pts))); + + const auto& instances = instance_transforms[i]; + for (const auto& instance : instances) { + (*contours.trafos).emplace_back(i, instance_trafo(hull_trafo, *instance)); + } + } + + set_sequential_print_clearance_contours(contours, false); m_sequential_print_clearance_first_displacement = false; } - - // calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) - Polygons polygons; - polygons.reserve(instances_count); - for (size_t i = 0; i < instance_transforms.size(); ++i) { - const auto& instances = instance_transforms[i]; - for (const auto& instance : instances) { - const Transform3d& trafo = instance->get_matrix(); - const Pointf3s& hull_2d = m_sequential_print_clearance.m_hull_2d_cache[i]; - Points inst_pts; - inst_pts.reserve(hull_2d.size()); - for (size_t j = 0; j < hull_2d.size(); ++j) { - const Vec3d p = trafo * hull_2d[j]; - inst_pts.emplace_back(scaled(p.x()), scaled(p.y())); + else { + if (!m_sequential_print_clearance.empty()) { + std::vector trafos; + trafos.reserve(instances_count); + for (size_t i = 0; i < instance_transforms.size(); ++i) { + const auto& [hull, hull_trafo] = m_sequential_print_clearance.m_hulls_2d_cache[i]; + const auto& instances = instance_transforms[i]; + for (const auto& instance : instances) { + trafos.emplace_back(instance_trafo(hull_trafo, *instance)); + } } - polygons.emplace_back(Geometry::convex_hull(std::move(inst_pts))); + m_sequential_print_clearance.update_instances_trafos(trafos); } } - - // sends instances 2d hulls to be rendered - set_sequential_print_clearance_visible(true); - set_sequential_print_clearance_render_fill(false); - set_sequential_print_clearance_polygons(polygons); } bool GLCanvas3D::is_object_sinking(int object_idx) const @@ -5936,7 +6130,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) } } if (m_requires_check_outside_state) { - m_volumes.check_outside_state(build_volume, nullptr); + check_volumes_outside_state(build_volume, nullptr); m_requires_check_outside_state = false; } } @@ -6031,15 +6225,20 @@ void GLCanvas3D::_render_selection() void GLCanvas3D::_render_sequential_clearance() { - if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) + if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects) + return; + + if (m_layers_editing.is_enabled()) return; switch (m_gizmos.get_current_type()) { case GLGizmosManager::EType::Flatten: case GLGizmosManager::EType::Cut: - case GLGizmosManager::EType::Hollow: - case GLGizmosManager::EType::SlaSupports: + case GLGizmosManager::EType::MmuSegmentation: + case GLGizmosManager::EType::Measure: + case GLGizmosManager::EType::Emboss: + case GLGizmosManager::EType::Simplify: case GLGizmosManager::EType::FdmSupports: case GLGizmosManager::EType::Seam: { return; } default: { break; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index b9677fda6..a58decbcd 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -619,23 +619,35 @@ public: return ret; } + struct ContoursList + { + // list of unique contours + Polygons contours; + // if defined: list of transforms to apply to contours + std::optional>> trafos; + + bool empty() const { return contours.empty(); } + }; + private: void load_arrange_settings(); class SequentialPrintClearance { GLModel m_fill; - GLModel m_perimeter; - bool m_render_fill{ true }; - bool m_visible{ false }; + // list of unique contours + std::vector m_contours; + // list of transforms used to render the contours + std::vector> m_instances; + bool m_evaluating{ false }; - std::vector m_hull_2d_cache; + std::vector> m_hulls_2d_cache; public: - void set_polygons(const Polygons& polygons); - void set_render_fill(bool render_fill) { m_render_fill = render_fill; } - void set_visible(bool visible) { m_visible = visible; } + void set_contours(const ContoursList& contours, bool generate_fill); + void update_instances_trafos(const std::vector& trafos); void render(); + bool empty() const { return m_contours.empty(); } friend class GLCanvas3D; }; @@ -725,7 +737,10 @@ public: unsigned int get_volumes_count() const; const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); - ModelInstanceEPrintVolumeState check_volumes_outside_state() const; + ModelInstanceEPrintVolumeState check_volumes_outside_state(bool selection_only = true) const; + // returns true if all the volumes are completely contained in the print volume + // returns the containment state in the given out_state, if non-null + bool check_volumes_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state, bool selection_only = true) const; void init_gcode_viewer() { m_gcode_viewer.init(); } void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } @@ -959,24 +974,24 @@ public: } void reset_sequential_print_clearance() { - m_sequential_print_clearance.set_visible(false); - m_sequential_print_clearance.set_render_fill(false); - m_sequential_print_clearance.set_polygons(Polygons()); + m_sequential_print_clearance.m_evaluating = false; + m_sequential_print_clearance.set_contours(ContoursList(), false); } - void set_sequential_print_clearance_visible(bool visible) { - m_sequential_print_clearance.set_visible(visible); + void set_sequential_print_clearance_contours(const ContoursList& contours, bool generate_fill) { + m_sequential_print_clearance.set_contours(contours, generate_fill); } - void set_sequential_print_clearance_render_fill(bool render_fill) { - m_sequential_print_clearance.set_render_fill(render_fill); + bool is_sequential_print_clearance_empty() const { + return m_sequential_print_clearance.empty(); } - void set_sequential_print_clearance_polygons(const Polygons& polygons) { - m_sequential_print_clearance.set_polygons(polygons); + bool is_sequential_print_clearance_evaluating() const { + return m_sequential_print_clearance.m_evaluating; } - void update_sequential_clearance(); + void update_sequential_clearance(bool force_contours_generation); + void set_sequential_clearance_as_evaluating() { m_sequential_print_clearance.m_evaluating = true; } const Print* fff_print() const; const SLAPrint* sla_print() const; diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 61736f9ac..3a7c00285 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -596,6 +596,38 @@ void GLModel::init_from(const indexed_triangle_set& its) } } +void GLModel::init_from(const Polygon& polygon, float z) +{ + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Lines, Geometry::EVertexLayout::P3 }; + + const size_t segments_count = polygon.points.size(); + data.reserve_vertices(2 * segments_count); + data.reserve_indices(2 * segments_count); + + // vertices + indices + unsigned int vertices_counter = 0; + for (size_t i = 0; i < segments_count; ++i) { + const Point& p0 = polygon.points[i]; + const Point& p1 = (i == segments_count - 1) ? polygon.points.front() : polygon.points[i + 1]; + data.add_vertex(Vec3f(unscale(p0.x()), unscale(p0.y()), z)); + data.add_vertex(Vec3f(unscale(p1.x()), unscale(p1.y()), z)); + vertices_counter += 2; + data.add_line(vertices_counter - 2, vertices_counter - 1); + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(data.extract_position_3(i).cast()); + } +} + void GLModel::init_from(const Polygons& polygons, float z) { if (is_initialized()) { diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index fbf6ed533..3d3a8aac3 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -227,6 +227,7 @@ namespace GUI { void init_from(const TriangleMesh& mesh); #endif // ENABLE_SMOOTH_NORMALS void init_from(const indexed_triangle_set& its); + void init_from(const Polygon& polygon, float z); void init_from(const Polygons& polygons, float z); bool init_from_file(const std::string& filename); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 65ca0f1fc..bd2208fd5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4016,8 +4016,10 @@ void ObjectList::update_selections_on_canvas() selection.add_volumes(mode, volume_idxs, single_selection); } - wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); - wxGetApp().plater()->canvas3D()->render(); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->update_gizmos_on_off_state(); + canvas->check_volumes_outside_state(); + canvas->render(); } void ObjectList::select_item(const wxDataViewItem& item) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d86c8275e..ae40e0f76 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -859,13 +859,13 @@ wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) #if ENABLE_OBJECT_MANIPULATION_DEBUG void ObjectManipulation::render_debug_window() { - ImGuiWrapper& imgui = *wxGetApp().imgui(); -// ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once); - imgui.begin(std::string("ObjectManipulation"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Coordinates type"); - ImGui::SameLine(); - imgui.text(coordinate_type_str(m_coordinates_type)); - imgui.end(); + ImGuiWrapper& imgui = *wxGetApp().imgui(); +// ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once); + imgui.begin(std::string("ObjectManipulation"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Coordinates type"); + ImGui::SameLine(); + imgui.text(coordinate_type_str(m_coordinates_type)); + imgui.end(); } #endif // ENABLE_OBJECT_MANIPULATION_DEBUG diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index d7c01c368..2aeb525ca 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -355,7 +355,7 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { ap.apply(); } - m_plater->update(); + m_plater->update((unsigned int)Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH); wxGetApp().obj_manipul()->set_dirty(); if (!m_unarranged.empty()) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2e22dc0de..6a20f30bb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1765,11 +1765,6 @@ struct Plater::priv void render_project_state_debug_window() const { dirty_state.render_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW - enum class UpdateParams { - FORCE_FULL_SCREEN_REFRESH = 1, - FORCE_BACKGROUND_PROCESSING_UPDATE = 2, - POSTPONE_VALIDATION_ERROR_MESSAGE = 4, - }; void update(unsigned int flags = 0); void select_view(const std::string& direction); void select_view_3D(const std::string& name); @@ -3055,8 +3050,10 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx) sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx); model.delete_object(obj_idx); + update(); object_list_changed(); + return true; } @@ -3253,7 +3250,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool if (view3D->is_layers_editing_enabled()) view3D->get_wxglcanvas()->Refresh(); - if (background_process.empty()) + if (invalidated == Print::APPLY_STATUS_CHANGED || background_process.empty()) view3D->get_canvas3d()->reset_sequential_print_clearance(); if (invalidated == Print::APPLY_STATUS_INVALIDATED) { @@ -3292,29 +3289,43 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // or hide the old one. process_validation_warning(warning); if (printer_technology == ptFFF) { - view3D->get_canvas3d()->reset_sequential_print_clearance(); - view3D->get_canvas3d()->set_as_dirty(); - view3D->get_canvas3d()->request_extra_frame(); + GLCanvas3D* canvas = view3D->get_canvas3d(); + canvas->reset_sequential_print_clearance(); + canvas->set_as_dirty(); + canvas->request_extra_frame(); } } else { - // The print is not valid. - // Show error as notification. + // The print is not valid. + // Show error as notification. notification_manager->push_validate_error_notification(err); return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; if (printer_technology == ptFFF) { - const Print* print = background_process.fff_print(); - Polygons polygons; - if (print->config().complete_objects) - Print::sequential_print_horizontal_clearance_valid(*print, &polygons); - view3D->get_canvas3d()->set_sequential_print_clearance_visible(true); - view3D->get_canvas3d()->set_sequential_print_clearance_render_fill(true); - view3D->get_canvas3d()->set_sequential_print_clearance_polygons(polygons); + GLCanvas3D* canvas = view3D->get_canvas3d(); + if (canvas->is_sequential_print_clearance_empty() || canvas->is_sequential_print_clearance_evaluating()) { + GLCanvas3D::ContoursList contours; + contours.contours = background_process.fff_print()->get_sequential_print_clearance_contours(); + canvas->set_sequential_print_clearance_contours(contours, true); + canvas->set_as_dirty(); + canvas->request_extra_frame(); + } } } } else { if (invalidated == Print::APPLY_STATUS_UNCHANGED && !background_process.empty()) { + if (printer_technology == ptFFF) { + // Object manipulation with gizmos may end up in a null transformation. + // In this case, we need to trigger the completion of the sequential print clearance contours evaluation + GLCanvas3D* canvas = view3D->get_canvas3d(); + if (canvas->is_sequential_print_clearance_evaluating()) { + GLCanvas3D::ContoursList contours; + contours.contours = background_process.fff_print()->get_sequential_print_clearance_contours(); + canvas->set_sequential_print_clearance_contours(contours, true); + canvas->set_as_dirty(); + canvas->request_extra_frame(); + } + } std::string warning; std::string err = background_process.validate(&warning); if (!err.empty()) @@ -4416,7 +4427,6 @@ void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) void Plater::priv::on_3dcanvas_mouse_dragging_started(SimpleEvent&) { - view3D->get_canvas3d()->reset_sequential_print_clearance(); } // Update the scene from the background processing, @@ -6005,7 +6015,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/* return true; } -void Plater::update() { p->update(); } +void Plater::update(unsigned int flags) { p->update(flags); } Worker &Plater::get_ui_job_worker() { return p->m_worker; } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 19152a7a5..880448c86 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -185,7 +185,12 @@ public: const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; } - void update(); + enum class UpdateParams { + FORCE_FULL_SCREEN_REFRESH = 1, + FORCE_BACKGROUND_PROCESSING_UPDATE = 2, + POSTPONE_VALIDATION_ERROR_MESSAGE = 4, + }; + void update(unsigned int flags = 0); // Get the worker handling the UI jobs (arrange, fill bed, etc...) // Here is an example of starting up an ad-hoc job: diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e9d131c43..666d4fe59 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1538,6 +1538,11 @@ void Selection::erase() wxGetApp().obj_list()->delete_from_model_and_list(items); ensure_not_below_bed(); } + + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->set_sequential_clearance_as_evaluating(); + canvas->set_as_dirty(); + canvas->request_extra_frame(); } void Selection::render(float scale_factor)