diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index ed2d70272..50e5096bf 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -38,6 +38,7 @@ #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/ModelArrange.hpp" +#include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -603,6 +604,9 @@ bool CLI::setup(int argc, char **argv) } } + // Detect the operating system flavor after SLIC3R_LOGLEVEL is set. + detect_platform(); + #ifdef WIN32 // Notify user if blacklisted library is already loaded (Nahimic) // If there are cases of no reports with blacklisted lib - this check should be performed later. diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index b11c570f6..76aa36194 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include "Utils.hpp" // for next_highest_power_of_2() extern "C" @@ -752,6 +754,83 @@ void get_candidate_idxs(const TreeType& tree, const VectorType& v, std::vector struct Intersecting {}; + +// Intersection predicate specialization for box-box intersections +template +struct Intersecting> { + Eigen::AlignedBox box; + + Intersecting(const Eigen::AlignedBox &bb): box{bb} {} + + bool operator() (const typename Tree::Node &node) const + { + return box.intersects(node.bbox); + } +}; + +template auto intersecting(const G &g) { return Intersecting{g}; } + +template struct Containing {}; + +// Intersection predicate specialization for box-box intersections +template +struct Containing> { + Eigen::AlignedBox box; + + Containing(const Eigen::AlignedBox &bb): box{bb} {} + + bool operator() (const typename Tree::Node &node) const + { + return box.contains(node.bbox); + } +}; + +template auto containing(const G &g) { return Containing{g}; } + +namespace detail { + +template +void traverse_recurse(const Tree &tree, + size_t idx, + Pred && pred, + Fn && callback) +{ + assert(tree.node(idx).is_valid()); + + if (!pred(tree.node(idx))) return; + + if (tree.node(idx).is_leaf()) { + callback(tree.node(idx).idx); + } else { + + // call this with left and right node idx: + auto trv = [&](size_t idx) { + traverse_recurse(tree, idx, std::forward(pred), + std::forward(callback)); + }; + + // Left / right child node index. + trv(Tree::left_child_idx(idx)); + trv(Tree::right_child_idx(idx)); + } +} + +} // namespace detail + +// Tree traversal with a predicate. Example usage: +// traverse(tree, intersecting(QueryBox), [](size_t face_idx) { +// /* ... */ +// }); +template +void traverse(const Tree &tree, Predicate &&pred, Fn &&callback) +{ + if (tree.empty()) return; + + detail::traverse_recurse(tree, size_t(0), std::forward(pred), + std::forward(callback)); +} } // namespace AABBTreeIndirect } // namespace Slic3r diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6159b2c5c..4a762f7e1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -141,6 +141,8 @@ add_library(libslic3r STATIC PerimeterGenerator.hpp PlaceholderParser.cpp PlaceholderParser.hpp + Platform.cpp + Platform.hpp Point.cpp Point.hpp Polygon.cpp diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 51621ed40..9ecabd47d 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -373,7 +373,7 @@ private: void print_machine_envelope(FILE *file, Print &print); void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); - // this flag triggers first layer speeds + // On the first printing layer. This flag triggers first layer speeds. bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } friend ObjectByExtruder& object_by_extruder( diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 8e2348530..87296f8f1 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -100,6 +100,7 @@ typedef std::vector LayerRegionPtrs; class Layer { public: + // Sequential index of this layer in PrintObject::m_layers, offsetted by the number of raft layers. size_t id() const { return m_id; } void set_id(size_t id) { m_id = id; } PrintObject* object() { return m_object; } @@ -115,7 +116,7 @@ public: // Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry // (with possibly differing extruder ID and slicing parameters) and merged. - // For the first layer, if the ELephant foot compensation is applied, this lslice is uncompensated, therefore + // For the first layer, if the Elephant foot compensation is applied, this lslice is uncompensated, therefore // it includes the Elephant foot effect, thus it corresponds to the shape of the printed 1st layer. // These lslices aka islands are chained by the shortest traverse distance and this traversal // order will be applied by the G-code generator to the extrusions fitting into these lslices. @@ -170,7 +171,7 @@ protected: virtual ~Layer(); private: - // sequential number of layer, 0-based + // Sequential index of layer, 0-based, offsetted by number of raft layers. size_t m_id; PrintObject *m_object; LayerRegionPtrs m_regions; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 1bca95ca3..f6c0c9c7a 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -74,6 +74,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec const PrintRegionConfig ®ion_config = this->region()->config(); // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! bool spiral_vase = print_config.spiral_vase && + //FIXME account for raft layers. (this->layer()->id() >= size_t(region_config.bottom_solid_layers.value) && this->layer()->print_z >= region_config.bottom_solid_min_thickness - EPSILON); diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 6ffc5dec3..25ee3e4af 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -110,33 +110,19 @@ struct CGALMesh { _EpicMesh m; }; // Converions from and to CGAL mesh // ///////////////////////////////////////////////////////////////////////////// -template void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &out) +template +void triangle_mesh_to_cgal(const std::vector & V, + const std::vector &F, + _Mesh &out) { - using Index3 = std::array; - - if (M.empty()) return; - - std::vector points; - std::vector indices; - points.reserve(M.its.vertices.size()); - indices.reserve(M.its.indices.size()); - for (auto &v : M.its.vertices) points.emplace_back(v.x(), v.y(), v.z()); - for (auto &_f : M.its.indices) { - auto f = _f.cast(); - indices.emplace_back(Index3{f(0), f(1), f(2)}); - } + if (F.empty()) return; - CGALProc::orient_polygon_soup(points, indices); - CGALProc::polygon_soup_to_polygon_mesh(points, indices, out); - - // Number the faces because 'orient_to_bound_a_volume' needs a face <--> index map - unsigned index = 0; - for (auto face : out.faces()) face = CGAL::SM_Face_index(index++); - - if(CGAL::is_closed(out)) - CGALProc::orient_to_bound_a_volume(out); - else - throw Slic3r::RuntimeError("Mesh not watertight"); + for (auto &v : V) + out.add_vertex(typename _Mesh::Point{v.x(), v.y(), v.z()}); + + using VI = typename _Mesh::Vertex_index; + for (auto &f : F) + out.add_face(VI(f(0)), VI(f(1)), VI(f(2))); } inline Vec3d to_vec3d(const _EpicMesh::Point &v) @@ -164,22 +150,30 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) } for (auto &face : cgalmesh.faces()) { - auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); - int i = 0; - Vec3i trface; - for (auto v : vtc) trface(i++) = static_cast(v); - facets.emplace_back(trface); + auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); + + int i = 0; + Vec3i facet; + for (auto v : vtc) { + if (i > 2) { i = 0; break; } + facet(i++) = v; + } + + if (i == 3) + facets.emplace_back(facet); } TriangleMesh out{points, facets}; - out.require_shared_vertices(); + out.repair(); return out; } -std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) +std::unique_ptr +triangle_mesh_to_cgal(const std::vector &V, + const std::vector &F) { std::unique_ptr out(new CGALMesh{}); - triangle_mesh_to_cgal(M, out->m); + triangle_mesh_to_cgal(V, F, out->m); return out; } @@ -238,8 +232,8 @@ template void _mesh_boolean_do(Op &&op, TriangleMesh &A, const Triangl { CGALMesh meshA; CGALMesh meshB; - triangle_mesh_to_cgal(A, meshA.m); - triangle_mesh_to_cgal(B, meshB.m); + triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m); + triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m); _cgal_do(op, meshA, meshB); @@ -264,7 +258,7 @@ void intersect(TriangleMesh &A, const TriangleMesh &B) bool does_self_intersect(const TriangleMesh &mesh) { CGALMesh cgalm; - triangle_mesh_to_cgal(mesh, cgalm.m); + triangle_mesh_to_cgal(mesh.its.vertices, mesh.its.indices, cgalm.m); return CGALProc::does_self_intersect(cgalm.m); } diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index ce17a1328..8e88e2536 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -27,7 +27,19 @@ namespace cgal { struct CGALMesh; struct CGALMeshDeleter { void operator()(CGALMesh *ptr); }; -std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M); +std::unique_ptr +triangle_mesh_to_cgal(const std::vector &V, + const std::vector &F); + +inline std::unique_ptr triangle_mesh_to_cgal(const indexed_triangle_set &M) +{ + return triangle_mesh_to_cgal(M.vertices, M.indices); +} +inline std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) +{ + return triangle_mesh_to_cgal(M.its); +} + TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh); // Do boolean mesh difference with CGAL bypassing igl. diff --git a/src/slic3r/Utils/Platform.cpp b/src/libslic3r/Platform.cpp similarity index 92% rename from src/slic3r/Utils/Platform.cpp rename to src/libslic3r/Platform.cpp index 3d101d662..ae02c42b3 100644 --- a/src/slic3r/Utils/Platform.cpp +++ b/src/libslic3r/Platform.cpp @@ -1,15 +1,9 @@ #include "Platform.hpp" - -// For starting another PrusaSlicer instance on OSX. -// Fails to compile on Windows on the build server. - -#include - #include +#include namespace Slic3r { -namespace GUI { static auto s_platform = Platform::Uninitialized; static auto s_platform_flavor = PlatformFlavor::Uninitialized; @@ -74,5 +68,4 @@ PlatformFlavor platform_flavor() return s_platform_flavor; } -} // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/Utils/Platform.hpp b/src/libslic3r/Platform.hpp similarity index 80% rename from src/slic3r/Utils/Platform.hpp rename to src/libslic3r/Platform.hpp index c0ee3541d..735728e89 100644 --- a/src/slic3r/Utils/Platform.hpp +++ b/src/libslic3r/Platform.hpp @@ -1,8 +1,7 @@ -#ifndef SLIC3R_GUI_Utils_Platform_HPP -#define SLIC3R_GUI_Utils_Platform_HPP +#ifndef SLIC3R_Platform_HPP +#define SLIC3R_Platform_HPP namespace Slic3r { -namespace GUI { enum class Platform { @@ -37,8 +36,6 @@ void detect_platform(); Platform platform(); PlatformFlavor platform_flavor(); - -} // namespace GUI } // namespace Slic3r -#endif // SLIC3R_GUI_Utils_Platform_HPP +#endif // SLIC3R_Platform_HPP diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 5d2181e7a..d9a77cd35 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -36,17 +36,18 @@ struct DrainHole Vec3f normal; float radius; float height; + bool failed = false; DrainHole() : pos(Vec3f::Zero()), normal(Vec3f::UnitZ()), radius(5.f), height(10.f) {} - DrainHole(Vec3f p, Vec3f n, float r, float h) - : pos(p), normal(n), radius(r), height(h) + DrainHole(Vec3f p, Vec3f n, float r, float h, bool fl = false) + : pos(p), normal(n), radius(r), height(h), failed(fl) {} DrainHole(const DrainHole& rhs) : - DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height) {} + DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {} bool operator==(const DrainHole &sp) const; @@ -61,7 +62,7 @@ struct DrainHole template inline void serialize(Archive &ar) { - ar(pos, normal, radius, height); + ar(pos, normal, radius, height, failed); } static constexpr size_t steps = 32; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 22fee6976..fc6686201 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -12,6 +12,7 @@ #include #include +#include #include @@ -244,6 +245,8 @@ static std::vector create_exclude_mask( Vec3f face_normal = C.normalized(); for (const sla::DrainHole &dh : holes) { + if (dh.failed) continue; + Vec3d dhpos = dh.pos.cast(); Vec3d dhend = dhpos + dh.normal.cast() * dh.height; @@ -270,6 +273,36 @@ static std::vector create_exclude_mask( return exclude_mask; } +static indexed_triangle_set +remove_unconnected_vertices(const indexed_triangle_set &its) +{ + if (its.indices.empty()) {}; + + indexed_triangle_set M; + + std::vector vtransl(its.vertices.size(), -1); + int vcnt = 0; + for (auto &f : its.indices) { + + for (int i = 0; i < 3; ++i) + if (vtransl[size_t(f(i))] < 0) { + + M.vertices.emplace_back(its.vertices[size_t(f(i))]); + vtransl[size_t(f(i))] = vcnt++; + } + + std::array new_f = { + vtransl[size_t(f(0))], + vtransl[size_t(f(1))], + vtransl[size_t(f(2))] + }; + + M.indices.emplace_back(new_f[0], new_f[1], new_f[2]); + } + + return M; +} + // Drill holes into the hollowed/original mesh. void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { @@ -313,16 +346,52 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; sla::DrainHoles drainholes = po.transformed_drainhole_points(); + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + hollowed_mesh.its.vertices, + hollowed_mesh.its.indices + ); + std::uniform_real_distribution dist(0., float(EPSILON)); - auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}); - for (sla::DrainHole holept : drainholes) { + auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}, {}); + indexed_triangle_set part_to_drill = hollowed_mesh.its; + + bool hole_fail = false; + for (size_t i = 0; i < drainholes.size(); ++i) { + sla::DrainHole holept = drainholes[i]; + holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; holept.normal.normalize(); holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)}; TriangleMesh m = sla::to_triangle_mesh(holept.to_mesh()); m.require_shared_vertices(); - auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m); - MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m); + + part_to_drill.indices.clear(); + auto bb = m.bounding_box(); + Eigen::AlignedBox ebb{bb.min.cast(), + bb.max.cast()}; + + AABBTreeIndirect::traverse( + tree, + AABBTreeIndirect::intersecting(ebb), + [&part_to_drill, &hollowed_mesh](size_t faceid) + { + part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[faceid]); + }); + + auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal( + remove_unconnected_vertices(part_to_drill)); + + if (MeshBoolean::cgal::does_self_intersect(*cgal_meshpart)) { + BOOST_LOG_TRIVIAL(error) << "Failed to drill hole"; + + hole_fail = drainholes[i].failed = + po.model_object()->sla_drain_holes[i].failed = true; + + continue; + } + + auto cgal_hole = MeshBoolean::cgal::triangle_mesh_to_cgal(m); + MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_hole); } if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) @@ -332,6 +401,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) try { MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); + hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); mesh_view = hollowed_mesh; @@ -348,6 +418,10 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); } + + if (hole_fail) + po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + L("Failed to drill some holes into the model")); } // The slicing will be performed on an imaginary 1D grid which starts from diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 9caac5769..c01da8f91 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -324,20 +324,21 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object m_object (object), m_print_config (&object->print()->config()), m_object_config (&object->config()), - m_slicing_params (slicing_params), - m_first_layer_flow (support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height))), - m_support_material_flow (support_material_flow(object, float(slicing_params.layer_height))), - m_support_material_interface_flow(support_material_interface_flow(object, float(slicing_params.layer_height))), - m_support_layer_height_min(0.01) + m_slicing_params (slicing_params) { - // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - m_support_layer_height_min = 1000000.; - for (auto lh : m_print_config->min_layer_height.values) - m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh)); + m_support_params.first_layer_flow = support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height)); + m_support_params.support_material_flow = support_material_flow(object, float(slicing_params.layer_height)); + m_support_params.support_material_interface_flow = support_material_interface_flow(object, float(slicing_params.layer_height)); + m_support_params.support_layer_height_min = 0.01; - if (m_slicing_params.soluble_interface) { + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. + m_support_params.support_layer_height_min = 1000000.; + for (auto lh : m_print_config->min_layer_height.values) + m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, lh)); + + if (m_object_config->support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. - m_support_material_interface_flow = m_support_material_flow; + m_support_params.support_material_interface_flow = m_support_params.support_material_flow; } // Evaluate the XY gap between the object outer perimeters and the support structures. @@ -352,21 +353,21 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); bridge_flow_ratio += region.config().bridge_flow_ratio; } - m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); + m_support_params.gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); bridge_flow_ratio /= num_nonempty_regions; - m_support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? - m_support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : - Flow::bridging_flow(bridge_flow_ratio * m_support_material_interface_flow.nozzle_diameter(), m_support_material_interface_flow.nozzle_diameter()); + m_support_params.support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? + m_support_params.support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : + Flow::bridging_flow(bridge_flow_ratio * m_support_params.support_material_interface_flow.nozzle_diameter(), m_support_params.support_material_interface_flow.nozzle_diameter()); - m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; - if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { + m_support_params.can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; + if (!m_support_params.can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { // One of the support extruders is of "don't care" type. auto object_extruders = m_object->print()->object_extruders(); if (object_extruders.size() == 1 && *object_extruders.begin() == std::max(m_object_config->support_material_extruder.value, m_object_config->support_material_interface_extruder.value)) // Object is printed with the same extruder as the support. - m_can_merge_support_regions = true; + m_support_params.can_merge_support_regions = true; } } @@ -412,13 +413,15 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; + std::vector buildplate_covered = this->buildplate_covered(object); + // Determine the top contact surfaces of the support, defined as: // contact = overhangs - clearance + margin // This method is responsible for identifying what contact surfaces // should the support material expose to the object in order to guarantee // that it will be effective, regardless of how it's built below. // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes. - MyLayersPtr top_contacts = this->top_contact_layers(object, layer_storage); + MyLayersPtr top_contacts = this->top_contact_layers(object, buildplate_covered, layer_storage); if (top_contacts.empty()) // Nothing is supported, no supports are generated. return; @@ -440,8 +443,8 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers. std::vector layer_support_areas; MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( - object, top_contacts, layer_storage, - layer_support_areas); + object, top_contacts, buildplate_covered, + layer_storage, layer_support_areas); #ifdef SLIC3R_DEBUG for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id) @@ -460,7 +463,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); - this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); + this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); #ifdef SLIC3R_DEBUG for (const MyLayer *layer : top_contacts) @@ -662,31 +665,41 @@ Polygons collect_slices_outer(const Layer &layer) return out; } +struct SupportGridParams { + SupportGridParams(const PrintObjectConfig &object_config, const Flow &support_material_flow) : + grid_resolution(object_config.support_material_spacing.value + support_material_flow.spacing()), + support_angle(Geometry::deg2rad(object_config.support_material_angle.value)), + extrusion_width(support_material_flow.spacing()), + expansion_to_slice(coord_t(support_material_flow.scaled_spacing() / 2 + 5)), + expansion_to_propagate(-3) {} + + double grid_resolution; + double support_angle; + double extrusion_width; + coord_t expansion_to_slice; + coord_t expansion_to_propagate; +}; + class SupportGridPattern { public: - // Achtung! The support_polygons need to be trimmed by trimming_polygons, otherwise - // the selection by island_samples (see the island_samples() method) will not work! SupportGridPattern( // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) - const Polygons &support_polygons, + const Polygons *support_polygons, // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons. - const Polygons &trimming_polygons, - // Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing() - coordf_t support_spacing, - coordf_t support_angle, - coordf_t line_spacing) : - m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons), - m_support_spacing(support_spacing), m_support_angle(support_angle) + const Polygons *trimming_polygons, + const SupportGridParams ¶ms) : + m_support_polygons(support_polygons), m_trimming_polygons(trimming_polygons), + m_support_spacing(params.grid_resolution), m_support_angle(params.support_angle) { if (m_support_angle != 0.) { // Create a copy of the rotated contours. - m_support_polygons_rotated = support_polygons; - m_trimming_polygons_rotated = trimming_polygons; + m_support_polygons_rotated = *support_polygons; + m_trimming_polygons_rotated = *trimming_polygons; m_support_polygons = &m_support_polygons_rotated; m_trimming_polygons = &m_trimming_polygons_rotated; - polygons_rotate(m_support_polygons_rotated, - support_angle); - polygons_rotate(m_trimming_polygons_rotated, - support_angle); + polygons_rotate(m_support_polygons_rotated, - params.support_angle); + polygons_rotate(m_trimming_polygons_rotated, - params.support_angle); } // Resolution of the sparse support grid. @@ -696,16 +709,12 @@ public: // Align the bounding box with the sparse support grid. bbox.align_to_grid(grid_resolution); - // Sample a single point per input support polygon, keep it as a reference to maintain corresponding - // polygons if ever these polygons get split into parts by the trimming polygons. - m_island_samples = island_samples(*m_support_polygons); - #ifdef SUPPORT_USE_AGG_RASTERIZER m_bbox = bbox; // Oversample the grid to avoid leaking of supports through or around the object walls. - int oversampling = std::min(8, int(scale_(m_support_spacing) / (scale_(line_spacing) + 100))); + int oversampling = std::min(8, int(scale_(m_support_spacing) / (scale_(params.extrusion_width) + 100))); m_pixel_size = scale_(m_support_spacing / oversampling); - assert(scale_(line_spacing) + 20 < m_pixel_size); + assert(scale_(params.extrusion_width) + 20 < m_pixel_size); // Add one empty column / row boundaries. m_bbox.offset(m_pixel_size); // Grid size fitting the support polygons plus one pixel boundary around the polygons. @@ -755,29 +764,46 @@ public: // and trim the extracted polygons by trimming_polygons. // Trimming by the trimming_polygons may split the extracted polygons into pieces. // Remove all the pieces, which do not contain any of the island_samples. - Polygons extract_support(const coord_t offset_in_grid, bool fill_holes) + Polygons extract_support(const coord_t offset_in_grid, bool fill_holes +#ifdef SLIC3R_DEBUG + , const char *step_name, int iRun, size_t layer_id, double print_z +#endif + ) { #ifdef SUPPORT_USE_AGG_RASTERIZER Polygons support_polygons_simplified = contours_simplified(m_grid_size, m_pixel_size, m_bbox.min, m_grid2, offset_in_grid, fill_holes); #else // SUPPORT_USE_AGG_RASTERIZER - // Generate islands, so each island may be tested for overlap with m_island_samples. + // Generate islands, so each island may be tested for overlap with island_samples. assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); #endif // SUPPORT_USE_AGG_RASTERIZER ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); - // Extract polygons, which contain some of the m_island_samples. + // Extract polygons, which contain some of the island_samples. Polygons out; #if 0 out = to_polygons(std::move(islands)); #else + + // Sample a single point per input support polygon, keep it as a reference to maintain corresponding + // polygons if ever these polygons get split into parts by the trimming polygons. + // As offset_in_grid may be negative, m_support_polygons may stick slightly outside of islands. + // Trim ti with islands. + Points samples = island_samples( + offset_in_grid > 0 ? + // Expanding, thus m_support_polygons are all inside islands. + union_ex(*m_support_polygons) : + // Shrinking, thus m_support_polygons may be trimmed a tiny bit by islands. + intersection_ex(*m_support_polygons, to_polygons(islands))); + + std::vector> samples_inside; for (ExPolygon &island : islands) { BoundingBox bbox = get_extents(island.contour); // Samples are sorted lexicographically. - auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.min - Point(1, 1))); - auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.max + Point(1, 1))); - std::vector> samples_inside; + auto it_lower = std::lower_bound(samples.begin(), samples.end(), Point(bbox.min - Point(1, 1))); + auto it_upper = std::upper_bound(samples.begin(), samples.end(), Point(bbox.max + Point(1, 1))); + samples_inside.clear(); for (auto it = it_lower; it != it_upper; ++ it) if (bbox.contains(*it)) samples_inside.push_back(std::make_pair(*it, false)); @@ -811,9 +837,7 @@ public: } #endif - #ifdef SLIC3R_DEBUG - static int iRun = 0; - ++iRun; +#ifdef SLIC3R_DEBUG BoundingBox bbox = get_extents(*m_trimming_polygons); if (! islands.empty()) bbox.merge(get_extents(islands)); @@ -821,7 +845,7 @@ public: bbox.merge(get_extents(out)); if (!support_polygons_simplified.empty()) bbox.merge(get_extents(support_polygons_simplified)); - SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox); + SVG svg(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z).c_str(), bbox); svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); svg.draw(islands, "red", 0.5f); svg.draw(union_ex(out), "green", 0.5f); @@ -829,10 +853,10 @@ public: svg.draw_outline(islands, "red", "red", scale_(0.05)); svg.draw_outline(union_ex(out), "green", "green", scale_(0.05)); svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05)); - for (const Point &pt : m_island_samples) + for (const Point &pt : samples) svg.draw(pt, "black", coord_t(scale_(0.15))); svg.Close(); - #endif /* SLIC3R_DEBUG */ +#endif /* SLIC3R_DEBUG */ if (m_support_angle != 0.) polygons_rotate(out, m_support_angle); @@ -940,9 +964,6 @@ public: m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); m_grid.calculate_sdf(); - // Sample a single point per input support polygon, keep it as a reference to maintain corresponding - // polygons if ever these polygons get split into parts by the trimming polygons. - m_island_samples = island_samples(*m_support_polygons); return true; } @@ -1075,11 +1096,6 @@ private: return pts; } - static Points island_samples(const Polygons &polygons) - { - return island_samples(union_ex(polygons)); - } - const Polygons *m_support_polygons; const Polygons *m_trimming_polygons; Polygons m_support_polygons_rotated; @@ -1098,10 +1114,6 @@ private: Slic3r::EdgeGrid::Grid m_grid; #endif // SUPPORT_USE_AGG_RASTERIZER - // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding - // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. - Points m_island_samples; - #ifdef SLIC3R_DEBUG // support for deserialization of m_support_polygons, m_trimming_polygons Polygons m_support_polygons_deserialized; @@ -1275,65 +1287,14 @@ namespace SupportMaterialInternal { } } -#if 0 -static int Test() +std::vector PrintObjectSupportMaterial::buildplate_covered(const PrintObject &object) const { -// for (int i = 0; i < 30; ++ i) - { - int i = -1; -// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i); -// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i); - auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i); - std::vector> intersections = grid.grid().intersecting_edges(); - if (! intersections.empty()) - printf("Intersections between contours!\n"); - Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons()); - Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false)); - Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false)); - Polygons extracted = grid.extract_support(scale_(0.21 / 2), true); - Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false)); - printf("hu!"); - } - return 0; -} -static int run_support_test = Test(); -#endif /* SLIC3R_DEBUG */ - -// Generate top contact layers supporting overhangs. -// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. -// If supports over bed surface only are requested, don't generate contact layers over an object. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( - const PrintObject &object, MyLayerStorage &layer_storage) const -{ -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; -#endif /* SLIC3R_DEBUG */ - - // Slice support enforcers / support blockers. - std::vector enforcers = object.slice_support_enforcers(); - std::vector blockers = object.slice_support_blockers(); - - // Append custom supports. - object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); - object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); - - // Output layers, sorted by top Z. - MyLayersPtr contact_out; - - const bool support_auto = m_object_config->support_material.value && m_object_config->support_material_auto.value; - // If user specified a custom angle threshold, convert it to radians. - // Zero means automatic overhang detection. - const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? - M_PI * double(m_object_config->support_material_threshold.value + 1) / 180. : // +1 makes the threshold inclusive - 0.; - // Build support on a build plate only? If so, then collect and union all the surfaces below the current layer. // Unfortunately this is an inherently serial process. const bool buildplate_only = this->build_plate_only(); std::vector buildplate_covered; if (buildplate_only) { - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() - collecting regions covering the print bed."; + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - start"; buildplate_covered.assign(object.layers().size(), Polygons()); for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) { const Layer &lower_layer = *object.layers()[layer_id-1]; @@ -1347,7 +1308,546 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ polygons_append(covered, offset(lower_layer.lslices, scale_(0.01))); covered = union_(covered, false); // don't apply the safety offset. } + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - end"; } + return buildplate_covered; +} + +struct SupportAnnotations +{ + SupportAnnotations(const PrintObject &object, const std::vector &buildplate_covered) : + enforcers_layers(object.slice_support_enforcers()), + blockers_layers(object.slice_support_blockers()), + buildplate_covered(buildplate_covered) + { + // Append custom supports. + object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); + object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); + } + + std::vector enforcers_layers; + std::vector blockers_layers; + const std::vector& buildplate_covered; +}; + +struct SlicesMarginCache +{ + float offset { -1 }; + // Trimming polygons, including possibly the "build plate only" mask. + Polygons polygons; + // Trimming polygons, without the "build plate only" mask. If empty, use polygons. + Polygons all_polygons; +}; + +static inline std::tuple detect_overhangs( + const Layer &layer, + const size_t layer_id, + const Polygons &lower_layer_polygons, + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + SupportAnnotations &annotations, + SlicesMarginCache &slices_margin, + const double gap_xy +#ifdef SLIC3R_DEBUG + , size_t iRun +#endif // SLIC3R_DEBUG + ) +{ + // Snug overhang polygons. + Polygons overhang_polygons; + // Expanded for stability, trimmed by gap_xy. + Polygons contact_polygons; + // Enforcers projected to overhangs, trimmed + Polygons enforcer_polygons; + + const bool support_auto = object_config.support_material.value && object_config.support_material_auto.value; + const bool buildplate_only = ! annotations.buildplate_covered.empty(); + // If user specified a custom angle threshold, convert it to radians. + // Zero means automatic overhang detection. + const double threshold_rad = (object_config.support_material_threshold.value > 0) ? + M_PI * double(object_config.support_material_threshold.value + 1) / 180. : // +1 makes the threshold inclusive + 0.; + float no_interface_offset = 0.f; + + if (layer_id == 0) + { + // This is the first object layer, so the object is being printed on a raft and + // we're here just to get the object footprint for the raft. +#if 0 + // The following line was filling excessive holes in the raft, see GH #430 + overhang_polygons = collect_slices_outer(layer); +#else + // Don't fill in the holes. The user may apply a higher raft_expansion if one wants a better 1st layer adhesion. + overhang_polygons = to_polygons(layer.lslices); +#endif + // Expand for better stability. + contact_polygons = offset(overhang_polygons, scaled(object_config.raft_expansion.value)); + } + else + { + // Generate overhang / contact_polygons for non-raft layers. + const Layer &lower_layer = *layer.lower_layer; + const bool has_enforcer = ! annotations.enforcers_layers.empty() && ! annotations.enforcers_layers[layer_id].empty(); + float fw = 0; + for (LayerRegion *layerm : layer.regions()) { + // Extrusion width accounts for the roundings of the extrudates. + // It is the maximum widh of the extrudate. + fw = float(layerm->flow(frExternalPerimeter).scaled_width()); + no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); + float lower_layer_offset = + (layer_id < (size_t)object_config.support_material_enforce_layers.value) ? + // Enforce a full possible support, ignore the overhang angle. + 0.f : + (threshold_rad > 0. ? + // Overhang defined by an angle. + float(scale_(lower_layer.height / tan(threshold_rad))) : + // Overhang defined by half the extrusion width. + 0.5f * fw); + // Overhang polygons for this layer and region. + Polygons diff_polygons; + Polygons layerm_polygons = to_polygons(layerm->slices); + if (lower_layer_offset == 0.f) { + // Support everything. + diff_polygons = diff(layerm_polygons, lower_layer_polygons); + if (buildplate_only) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); + } + } else { + if (support_auto) { + // Get the regions needing a suport, collapse very tiny spots. + //FIXME cache the lower layer offset if this layer has multiple regions. +#if 1 + //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 + diff_polygons = offset2( + diff(layerm_polygons, + offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + // no support at all for not so steep overhangs. + - 0.1f * fw, 0.1f * fw); +#else + diff_polygons = + diff(layerm_polygons, + offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); +#endif + if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); + } + if (! diff_polygons.empty()) { + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } + } + } + + if (diff_polygons.empty()) + continue; + + // Apply the "support blockers". + if (! annotations.blockers_layers.empty() && ! annotations.blockers_layers[layer_id].empty()) { + // Expand the blocker a bit. Custom blockers produce strips + // spanning just the projection between the two slices. + // Subtracting them as they are may leave unwanted narrow + // residues of diff_polygons that would then be supported. + diff_polygons = diff(diff_polygons, + offset(union_(to_polygons(std::move(annotations.blockers_layers[layer_id]))), float(1000.*SCALED_EPSILON))); + } + + #ifdef SLIC3R_DEBUG + { + ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", + iRun, layer_id, + std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), + get_extents(diff_polygons)); + Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); + svg.draw(expolys); + } + #endif /* SLIC3R_DEBUG */ + + if (object_config.dont_support_bridges) + SupportMaterialInternal::remove_bridges_from_contacts( + print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); + + if (diff_polygons.empty()) + continue; + + #ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg", + iRun, layer_id, + std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), + layer.print_z), + union_ex(diff_polygons, false)); + #endif /* SLIC3R_DEBUG */ + + //FIXME the overhang_polygons are used to construct the support towers as well. + //if (this->has_contact_loops()) + // Store the exact contour of the overhang for the contact loops. + polygons_append(overhang_polygons, diff_polygons); + + // Let's define the required contact area by using a max gap of half the upper + // extrusion width and extending the area according to the configured margin. + // We increment the area in steps because we don't want our support to overflow + // on the other side of the object (if it's very thin). + { + //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. + //FIXME one should trim with the layer span colliding with the support layer, this layer + // may be lower than lower_layer, so the support area needed may need to be actually bigger! + // For the same reason, the non-bridging support area may be smaller than the bridging support area! + float slices_margin_offset = std::min(lower_layer_offset, float(scale_(gap_xy))); + if (slices_margin.offset != slices_margin_offset) { + slices_margin.offset = slices_margin_offset; + slices_margin.polygons = (slices_margin_offset == 0.f) ? + lower_layer_polygons : + offset2(to_polygons(lower_layer.lslices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { + if (has_enforcer) + // Make a backup of trimming polygons before enforcing "on build plate only". + slices_margin.all_polygons = slices_margin.polygons; + // Trim the inflated contact surfaces by the top surfaces as well. + slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]); + } + } + // Offset the contact polygons outside. +#if 0 + for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { + diff_polygons = diff( + offset( + diff_polygons, + scaled(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS), + ClipperLib::jtRound, + // round mitter limit + scale_(0.05)), + slices_margin_cached); + } +#else + diff_polygons = diff(diff_polygons, slices_margin.polygons); +#endif + } + polygons_append(contact_polygons, diff_polygons); + } // for each layer.region + + if (has_enforcer) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. +#ifdef SLIC3R_DEBUG + ExPolygons enforcers_united = union_ex(to_polygons(annotations.enforcers_layers[layer_id]), false); +#endif // SLIC3R_DEBUG + enforcer_polygons = diff(intersection(to_polygons(layer.lslices), to_polygons(std::move(annotations.enforcers_layers[layer_id]))), + // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. + offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, + { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "green", 0.5f } }, + { enforcers_united, { "enforcers", "blue", 0.5f } }, + { { union_ex(enforcer_polygons, true) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + polygons_append(overhang_polygons, enforcer_polygons); + polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons)); + } + } + + return std::make_tuple(std::move(overhang_polygons), std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); +} + +static inline std::pair new_contact_layer( + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + const SlicingParameters &slicing_params, + const Layer &layer, + std::deque &layer_storage, + tbb::spin_mutex &layer_storage_mutex) +{ + double print_z, bottom_z, height; + PrintObjectSupportMaterial::MyLayer* bridging_layer = nullptr; + assert(layer.id() >= slicing_params.raft_layers()); + size_t layer_id = layer.id() - slicing_params.raft_layers(); + + if (layer_id == 0) { + // This is a raft contact layer sitting directly on the print bed. + assert(slicing_params.has_raft()); + print_z = slicing_params.raft_contact_top_z; + bottom_z = slicing_params.raft_interface_top_z; + height = slicing_params.contact_raft_layer_height; + } else if (slicing_params.soluble_interface) { + // Align the contact surface height with a layer immediately below the supported layer. + // Interface layer will be synchronized with the object. + print_z = layer.bottom_z(); + height = layer.lower_layer->height; + bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z; + } else { + print_z = layer.bottom_z() - slicing_params.gap_object_support; + bottom_z = print_z; + height = 0.; + // Ignore this contact area if it's too low. + // Don't want to print a layer below the first layer height as it may not stick well. + //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact + // and it may actually make sense to do it with a thinner layer than the first layer height. + if (print_z < slicing_params.first_print_layer_height - EPSILON) { + // This contact layer is below the first layer height, therefore not printable. Don't support this surface. + return std::pair(nullptr, nullptr); + } else if (print_z < slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + print_z = slicing_params.first_print_layer_height; + bottom_z = 0; + height = slicing_params.first_print_layer_height; + } else { + // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and + // its height will be set adaptively later on. + } + + // Contact layer will be printed with a normal flow, but + // it will support layers printed with a bridging flow. + if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) { + coordf_t bridging_height = 0.; + for (const LayerRegion* region : layer.regions()) + bridging_height += region->region()->bridging_height_avg(print_config); + bridging_height /= coordf_t(layer.regions().size()); + coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object; + if (bridging_print_z >= slicing_params.first_print_layer_height - EPSILON) { + // Not below the first layer height means this layer is printable. + if (print_z < slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + bridging_print_z = slicing_params.first_print_layer_height; + } + if (bridging_print_z < print_z - EPSILON) { + // Allocate the new layer. + bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); + bridging_layer->idx_object_layer_above = layer_id; + bridging_layer->print_z = bridging_print_z; + if (bridging_print_z == slicing_params.first_print_layer_height) { + bridging_layer->bottom_z = 0; + bridging_layer->height = slicing_params.first_print_layer_height; + } + else { + // Don't know the height yet. + bridging_layer->bottom_z = bridging_print_z; + bridging_layer->height = 0; + } + } + } + } + } + + PrintObjectSupportMaterial::MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); + new_layer.idx_object_layer_above = layer_id; + new_layer.print_z = print_z; + new_layer.bottom_z = bottom_z; + new_layer.height = height; + return std::make_pair(&new_layer, bridging_layer); +} + +static inline void fill_contact_layer( + PrintObjectSupportMaterial::MyLayer &new_layer, + size_t layer_id, + const SlicingParameters &slicing_params, + const PrintObjectConfig &object_config, + const SlicesMarginCache &slices_margin, + const Polygons &overhang_polygons, + const Polygons &contact_polygons, + const Polygons &enforcer_polygons, + const Polygons &lower_layer_polygons, + const Flow &support_material_flow, + float no_interface_offset +#ifdef SLIC3R_DEBUG + , size_t iRun, + const Layer &layer +#endif // SLIC3R_DEBUG + ) +{ + const SupportGridParams grid_params(object_config, support_material_flow); + + Polygons lower_layer_polygons_for_dense_interface_cache; + auto lower_layer_polygons_for_dense_interface = [&lower_layer_polygons_for_dense_interface_cache, &lower_layer_polygons, no_interface_offset]() -> const Polygons& { + if (lower_layer_polygons_for_dense_interface_cache.empty()) + lower_layer_polygons_for_dense_interface_cache = + offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); + return lower_layer_polygons_for_dense_interface_cache; + }; + + // Stretch support islands into a grid, trim them. + SupportGridPattern support_grid_pattern(&contact_polygons, &slices_margin.polygons, grid_params); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.contact_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true +#ifdef SLIC3R_DEBUG + , "top_contact_polygons", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + )); + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + if (layer_id == 0 || slicing_params.soluble_interface) { + // if (no_interface_offset == 0.f) { + new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true +#ifdef SLIC3R_DEBUG + , "top_contact_polygons2", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); + } else { + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. + Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); + if (! dense_interface_polygons.empty()) { + dense_interface_polygons = + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin.polygons); + // Support islands, to be stretched into a grid. + //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, + // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. + // See for example GH #4874. + Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.contact_polygons); + // Stretch support islands into a grid, trim them. + SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.polygons, grid_params); + new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false +#ifdef SLIC3R_DEBUG + , "top_contact_polygons3", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); + #ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(slices_margin.polygons, false) }, { "slices_margin_cached", "blue", 0.5f } }, + { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, + { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); + //support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); + SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, + { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); + #endif /* SLIC3R_DEBUG */ + } + } + + if (! enforcer_polygons.empty() && ! slices_margin.all_polygons.empty() && layer_id > 0) { + // Support enforcers used together with support enforcers. The support enforcers need to be handled separately from the rest of the support. + + { + SupportGridPattern support_grid_pattern(&enforcer_polygons, &slices_margin.all_polygons, grid_params); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.enforcer_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true + #ifdef SLIC3R_DEBUG + , "top_contact_polygons4", iRun, layer_id, layer.print_z + #endif // SLIC3R_DEBUG + )); + } + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. + Polygons dense_interface_polygons = diff(enforcer_polygons, lower_layer_polygons_for_dense_interface()); + if (! dense_interface_polygons.empty()) { + dense_interface_polygons = + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin.all_polygons); + // Support islands, to be stretched into a grid. + //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, + // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. + // See for example GH #4874. + Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.enforcer_polygons); + SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.all_polygons, grid_params); + // Extend the polygons to extrude with the contact polygons of support enforcers. + bool needs_union = ! new_layer.polygons.empty(); + append(new_layer.polygons, support_grid_pattern.extract_support(grid_params.expansion_to_slice, false +#ifdef SLIC3R_DEBUG + , "top_contact_polygons5", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + )); + if (needs_union) + new_layer.polygons = union_(new_layer.polygons); + } + } + +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(overhang_polygons, false) }, { "overhang_polygons", "green", 0.5f } }, + { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. + // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. + + // Store the overhang polygons. + // The overhang polygons are used in the path generator for planning of the contact loops. + // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. + new_layer.overhang_polygons = std::make_unique(std::move(overhang_polygons)); + if (! enforcer_polygons.empty()) + new_layer.enforcer_polygons = std::make_unique(std::move(enforcer_polygons)); +} + +// Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), +// the top contact layer is merged into the bottom contact layer. +static void merge_contact_layers(const SlicingParameters &slicing_params, double support_layer_height_min, PrintObjectSupportMaterial::MyLayersPtr &layers) +{ + // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. + std::sort(layers.begin(), layers.end(), [](const PrintObjectSupportMaterial::MyLayer *l1, const PrintObjectSupportMaterial::MyLayer *l2) { return l1->print_z < l2->print_z; }); + + int i = 0; + int k = 0; + { + // Find the span of layers, which are to be printed at the first layer height. + int j = 0; + for (; j < (int)layers.size() && layers[j]->print_z < slicing_params.first_print_layer_height + support_layer_height_min - EPSILON; ++ j); + if (j > 0) { + // Merge the layers layers (0) to (j - 1) into the layers[0]. + PrintObjectSupportMaterial::MyLayer &dst = *layers.front(); + for (int u = 1; u < j; ++ u) + dst.merge(std::move(*layers[u])); + // Snap the first layer to the 1st layer height. + dst.print_z = slicing_params.first_print_layer_height; + dst.height = slicing_params.first_print_layer_height; + dst.bottom_z = 0; + ++ k; + } + i = j; + } + for (; i < int(layers.size()); ++ k) { + // Find the span of layers closer than m_support_layer_height_min. + int j = i + 1; + coordf_t zmax = layers[i]->print_z + support_layer_height_min + EPSILON; + for (; j < (int)layers.size() && layers[j]->print_z < zmax; ++ j) ; + if (i + 1 < j) { + // Merge the layers layers (i + 1) to (j - 1) into the layers[i]. + PrintObjectSupportMaterial::MyLayer &dst = *layers[i]; + for (int u = i + 1; u < j; ++ u) + dst.merge(std::move(*layers[u])); + } + if (k < i) + layers[k] = layers[i]; + i = j; + } + if (k < (int)layers.size()) + layers.erase(layers.begin() + k, layers.end()); +} + +// Generate top contact layers supporting overhangs. +// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. +// If supports over bed surface only are requested, don't generate contact layers over an object. +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( + const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const +{ +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; + #define SLIC3R_IRUN , iRun +#endif /* SLIC3R_DEBUG */ + + // Slice support enforcers / support blockers. + SupportAnnotations annotations(object, buildplate_covered); + + // Output layers, sorted by top Z. + MyLayersPtr contact_out; BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - start"; // Determine top contact areas. @@ -1361,439 +1861,282 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_out.assign(num_layers * 2, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out] + [this, &object, &annotations, &layer_storage, &layer_storage_mutex, &contact_out] (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { - const Layer &layer = *object.layers()[layer_id]; + const Layer &layer = *object.layers()[layer_id]; + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices); + SlicesMarginCache slices_margin; - // Detect overhangs and contact areas needed to support them. - // Collect overhangs and contacts of all regions of this layer supported by the layer immediately below. - Polygons overhang_polygons; - Polygons contact_polygons; - Polygons slices_margin_cached; - float slices_margin_cached_offset = -1.; - Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->lslices); - // Offset of the lower layer, to trim the support polygons with to calculate dense supports. - float no_interface_offset = 0.f; - if (layer_id == 0) { - // This is the first object layer, so the object is being printed on a raft and - // we're here just to get the object footprint for the raft. -#if 0 - // The following line was filling excessive holes in the raft, see GH #430 - overhang_polygons = collect_slices_outer(layer); -#else - // Don't fill in the holes. The user may apply a higher raft_expansion if one wants a better 1st layer adhesion. - overhang_polygons = to_polygons(layer.lslices); -#endif - // Expand for better stability. - contact_polygons = offset(overhang_polygons, scaled(m_object_config->raft_expansion.value)); - } else { - // Generate overhang / contact_polygons for non-raft layers. - const Layer &lower_layer = *object.layers()[layer_id-1]; - for (LayerRegion *layerm : layer.regions()) { - // Extrusion width accounts for the roundings of the extrudates. - // It is the maximum widh of the extrudate. - float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); - no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); - float lower_layer_offset = - (layer_id < (size_t)m_object_config->support_material_enforce_layers.value) ? - // Enforce a full possible support, ignore the overhang angle. - 0.f : - (threshold_rad > 0. ? - // Overhang defined by an angle. - float(scale_(lower_layer.height / tan(threshold_rad))) : - // Overhang defined by half the extrusion width. - 0.5f * fw); - // Overhang polygons for this layer and region. - Polygons diff_polygons; - Polygons layerm_polygons = to_polygons(layerm->slices); - if (lower_layer_offset == 0.f) { - // Support everything. - diff_polygons = diff(layerm_polygons, lower_layer_polygons); - if (! buildplate_covered.empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); - } - } else { - if (support_auto) { - // Get the regions needing a suport, collapse very tiny spots. - //FIXME cache the lower layer offset if this layer has multiple regions. - #if 1 - //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 - diff_polygons = offset2( - diff(layerm_polygons, - offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to - // no support at all for not so steep overhangs. - - 0.1f * fw, 0.1f * fw); - #else - diff_polygons = - diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - #endif - if (! buildplate_covered.empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); - } - if (! diff_polygons.empty()) { - // Offset the support regions back to a full overhang, restrict them to the full overhang. - // This is done to increase size of the supporting columns below, as they are calculated by - // propagating these contact surfaces downwards. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); - } - } - if (! enforcers.empty()) { - // Apply the "support enforcers". - //FIXME add the "enforcers" to the sparse support regions only. - const ExPolygons &enforcer = enforcers[layer_id]; - if (! enforcer.empty()) { - // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. -#ifdef SLIC3R_DEBUG - ExPolygons enforcers_united = union_ex(to_polygons(enforcer), false); -#endif // SLIC3R_DEBUG - Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(std::move(enforcer))), - offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(layerm_polygons, false) }, { "layerm_polygons", "gray", 0.2f } }, - { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "green", 0.5f } }, - { enforcers_united, { "enforcers", "blue", 0.5f } }, - { { union_ex(new_contacts, true) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - if (! new_contacts.empty()) { - if (diff_polygons.empty()) - diff_polygons = std::move(new_contacts); - else - diff_polygons = union_(diff_polygons, new_contacts); - } - } - } - } + auto [overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset] = + detect_overhangs(layer, layer_id, lower_layer_polygons, *m_print_config, *m_object_config, annotations, slices_margin, m_support_params.gap_xy + #ifdef SLIC3R_DEBUG + , iRun + #endif // SLIC3R_DEBUG + ); - if (diff_polygons.empty()) - continue; - - // Apply the "support blockers". - if (! blockers.empty() && ! blockers[layer_id].empty()) { - // Expand the blocker a bit. Custom blockers produce strips - // spanning just the projection between the two slices. - // Subtracting them as they are may leave unwanted narrow - // residues of diff_polygons that would then be supported. - diff_polygons = diff(diff_polygons, - offset(union_(to_polygons(std::move(blockers[layer_id]))), float(1000.*SCALED_EPSILON))); - } - - #ifdef SLIC3R_DEBUG - { - ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", - iRun, layer_id, - std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), - get_extents(diff_polygons)); - Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); - svg.draw(expolys); - } - #endif /* SLIC3R_DEBUG */ - - if (this->m_object_config->dont_support_bridges) - SupportMaterialInternal::remove_bridges_from_contacts( - *m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); - - if (diff_polygons.empty()) - continue; - - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg", - iRun, layer_id, - std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), - layer.print_z), - union_ex(diff_polygons, false)); - #endif /* SLIC3R_DEBUG */ - - //FIXME the overhang_polygons are used to construct the support towers as well. - //if (this->has_contact_loops()) - // Store the exact contour of the overhang for the contact loops. - polygons_append(overhang_polygons, diff_polygons); - - // Let's define the required contact area by using a max gap of half the upper - // extrusion width and extending the area according to the configured margin. - // We increment the area in steps because we don't want our support to overflow - // on the other side of the object (if it's very thin). - { - //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. - //FIXME one should trim with the layer span colliding with the support layer, this layer - // may be lower than lower_layer, so the support area needed may need to be actually bigger! - // For the same reason, the non-bridging support area may be smaller than the bridging support area! - float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy))); - if (slices_margin_cached_offset != slices_margin_offset) { - slices_margin_cached_offset = slices_margin_offset; - slices_margin_cached = (slices_margin_offset == 0.f) ? - lower_layer_polygons : - offset2(to_polygons(lower_layer.lslices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (! buildplate_covered.empty()) { - // Trim the inflated contact surfaces by the top surfaces as well. - polygons_append(slices_margin_cached, buildplate_covered[layer_id]); - slices_margin_cached = union_(slices_margin_cached); - } - } - // Offset the contact polygons outside. -#if 0 - for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { - diff_polygons = diff( - offset( - diff_polygons, - scaled(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS), - ClipperLib::jtRound, - // round mitter limit - scale_(0.05)), - slices_margin_cached); - } -#else - diff_polygons = diff(diff_polygons, slices_margin_cached); -#endif - } - polygons_append(contact_polygons, diff_polygons); - } // for each layer.region - } // end of Generate overhang/contact_polygons for non-raft layers. - // Now apply the contact areas to the layer where they need to be made. if (! contact_polygons.empty()) { - MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); - new_layer.idx_object_layer_above = layer_id; - MyLayer *bridging_layer = nullptr; - if (layer_id == 0) { - // This is a raft contact layer sitting directly on the print bed. - assert(this->has_raft()); - new_layer.print_z = m_slicing_params.raft_contact_top_z; - new_layer.bottom_z = m_slicing_params.raft_interface_top_z; - new_layer.height = m_slicing_params.contact_raft_layer_height; - } else if (m_slicing_params.soluble_interface) { - // Align the contact surface height with a layer immediately below the supported layer. - // Interface layer will be synchronized with the object. - new_layer.print_z = layer.bottom_z(); - new_layer.height = object.layers()[layer_id - 1]->height; - new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z; - } else { - new_layer.print_z = layer.bottom_z() - m_slicing_params.gap_object_support; - new_layer.bottom_z = new_layer.print_z; - new_layer.height = 0.; - // Ignore this contact area if it's too low. - // Don't want to print a layer below the first layer height as it may not stick well. - //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact - // and it may actually make sense to do it with a thinner layer than the first layer height. - if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { - // This contact layer is below the first layer height, therefore not printable. Don't support this surface. - continue; - } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - new_layer.print_z = m_slicing_params.first_print_layer_height; - new_layer.bottom_z = 0; - new_layer.height = m_slicing_params.first_print_layer_height; - } else { - // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and - // its height will be set adaptively later on. - } - - // Contact layer will be printed with a normal flow, but - // it will support layers printed with a bridging flow. - if (m_object_config->thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) { - coordf_t bridging_height = 0.; - for (const LayerRegion *region : layer.regions()) - bridging_height += region->region()->bridging_height_avg(*m_print_config); - bridging_height /= coordf_t(layer.regions().size()); - coordf_t bridging_print_z = layer.print_z - bridging_height - m_slicing_params.gap_support_object; - if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) { - // Not below the first layer height means this layer is printable. - if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - bridging_print_z = m_slicing_params.first_print_layer_height; - } - if (bridging_print_z < new_layer.print_z - EPSILON) { - // Allocate the new layer. - bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); - bridging_layer->idx_object_layer_above = layer_id; - bridging_layer->print_z = bridging_print_z; - if (bridging_print_z == m_slicing_params.first_print_layer_height) { - bridging_layer->bottom_z = 0; - bridging_layer->height = m_slicing_params.first_print_layer_height; - } else { - // Don't know the height yet. - bridging_layer->bottom_z = bridging_print_z; - bridging_layer->height = 0; - } - } - } - } - } - - // Achtung! The contact_polygons need to be trimmed by slices_margin_cached, otherwise - // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! - SupportGridPattern support_grid_pattern( - // Support islands, to be stretched into a grid. - contact_polygons, - // Trimming polygons, to trim the stretched support islands. - slices_margin_cached, - // Grid resolution. - m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle.value), - m_support_material_flow.spacing()); - // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true)); - // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - if (layer_id == 0 || m_slicing_params.soluble_interface) { - // if (no_interface_offset == 0.f) { - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true); - } else { - // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. - Polygons dense_interface_polygons = diff(overhang_polygons, - offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (! dense_interface_polygons.empty()) { - dense_interface_polygons = - // Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise - // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! - diff( - // Regularize the contour. - offset(dense_interface_polygons, no_interface_offset * 0.1f), - slices_margin_cached); - SupportGridPattern support_grid_pattern( - // Support islands, to be stretched into a grid. - //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, - // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. - // See for example GH #4874. - intersection(dense_interface_polygons, *new_layer.contact_polygons), - // Trimming polygons, to trim the stretched support islands. - slices_margin_cached, - // Grid resolution. - m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle.value), - m_support_material_flow.spacing()); - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); + auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, layer, layer_storage, layer_storage_mutex); + if (new_layer) { + fill_contact_layer(*new_layer, layer_id, m_slicing_params, + *m_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons, + m_support_params.support_material_flow, no_interface_offset #ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(slices_margin_cached, false) }, { "slices_margin_cached", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - //support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ + , iRun, layer + #endif // SLIC3R_DEBUG + ); + contact_out[layer_id * 2] = new_layer; + if (bridging_layer != nullptr) { + bridging_layer->polygons = new_layer->polygons; + bridging_layer->contact_polygons = std::make_unique(*new_layer->contact_polygons); + bridging_layer->overhang_polygons = std::make_unique(*new_layer->overhang_polygons); + if (new_layer->enforcer_polygons) + bridging_layer->enforcer_polygons = std::make_unique(*new_layer->enforcer_polygons); + contact_out[layer_id * 2 + 1] = bridging_layer; } } - #ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(overhang_polygons, false) }, { "overhang_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ - - // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. - // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. - - // Store the overhang polygons. - // The overhang polygons are used in the path generator for planning of the contact loops. - // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. - new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); - contact_out[layer_id * 2] = &new_layer; - if (bridging_layer != nullptr) { - bridging_layer->polygons = new_layer.polygons; - bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons); - bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons); - contact_out[layer_id * 2 + 1] = bridging_layer; - } } } }); // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); - // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. - std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; }); - // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), - // the top contact layer is merged into the bottom contact layer. - { - int i = 0; - int k = 0; - { - // Find the span of layers, which are to be printed at the first layer height. - int j = 0; - for (; j < (int)contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); - if (j > 0) { - // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. - MyLayer &dst = *contact_out.front(); - for (int u = 1; u < j; ++ u) { - MyLayer &src = *contact_out[u]; - // The union_() does not support move semantic yet, but maybe one day it will. - dst.polygons = union_(dst.polygons, std::move(src.polygons)); - *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); - *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); - // Source polygon is no more needed, it will not be refrenced. Release its data. - src.reset(); - } - // Snap the first layer to the 1st layer height. - dst.print_z = m_slicing_params.first_print_layer_height; - dst.height = m_slicing_params.first_print_layer_height; - dst.bottom_z = 0; - ++ k; - } - i = j; - } - for (; i < int(contact_out.size()); ++ k) { - // Find the span of layers closer than m_support_layer_height_min. - int j = i + 1; - coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; - for (; j < (int)contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; - if (i + 1 < j) { - // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. - MyLayer &dst = *contact_out[i]; - for (int u = i + 1; u < j; ++ u) { - MyLayer &src = *contact_out[u]; - // The union_() does not support move semantic yet, but maybe one day it will. - dst.polygons = union_(dst.polygons, std::move(src.polygons)); - *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); - *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); - // Source polygon is no more needed, it will not be refrenced. Release its data. - src.reset(); - } - } - if (k < i) - contact_out[k] = contact_out[i]; - i = j; - } - if (k < (int)contact_out.size()) - contact_out.erase(contact_out.begin() + k, contact_out.end()); - } + merge_contact_layers(m_slicing_params, m_support_params.support_layer_height_min, contact_out); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; return contact_out; } +// Find the bottom contact layers above the top surfaces of this layer. +static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( + const SlicingParameters &slicing_params, + const PrintObjectSupportMaterial::SupportParams &support_params, + const PrintObject &object, + const Layer &layer, + // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. + const PrintObjectSupportMaterial::MyLayersPtr &top_contacts, + // First top contact layer index overlapping with this new bottom interface layer. + size_t contact_idx, + // To allocate a new layer from. + std::deque &layer_storage, + // To trim the support areas above this bottom interface layer with this newly created bottom interface layer. + std::vector &layer_support_areas, + // Support areas projected from top to bottom, starting with top support interfaces. + const Polygons &supports_projected +#ifdef SLIC3R_DEBUG + , size_t iRun + , const Polygons &polygons_new +#endif // SLIC3R_DEBUG + ) +{ + Polygons top = collect_region_slices_by_type(layer, stTop); +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), + { { { union_ex(top, false) }, { "top", "blue", 0.5f } }, + { { union_ex(supports_projected, true) }, { "overhangs", "magenta", 0.5f } }, + { layer.lslices, { "layer.lslices", "green", 0.5f } }, + { { union_ex(polygons_new, true) }, { "polygons_new", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any + // top surfaces above layer.print_z falls onto this top surface. + // Touching are the contact surfaces supported exclusively by this top surfaces. + // Don't use a safety offset as it has been applied during insertion of polygons. + if (top.empty()) + return nullptr; + + Polygons touching = intersection(top, supports_projected, false); + if (touching.empty()) + return nullptr; + + assert(layer.id() >= slicing_params.raft_layers()); + size_t layer_id = layer.id() - slicing_params.raft_layers(); + + // Allocate a new bottom contact layer. + PrintObjectSupportMaterial::MyLayer &layer_new = layer_allocate(layer_storage, PrintObjectSupportMaterial::sltBottomContact); + // Grow top surfaces so that interface and support generation are generated + // with some spacing from object - it looks we don't need the actual + // top shapes so this can be done here + //FIXME calculate layer height based on the actual thickness of the layer: + // If the layer is extruded with no bridging flow, support just the normal extrusions. + layer_new.height = slicing_params.soluble_interface ? + // Align the interface layer with the object's layer height. + layer.upper_layer->height : + // Place a bridge flow interface layer or the normal flow interface layer over the top surface. + support_params.support_material_bottom_interface_flow.height(); + layer_new.print_z = slicing_params.soluble_interface ? layer.upper_layer->print_z : + layer.print_z + layer_new.height + slicing_params.gap_object_support; + layer_new.bottom_z = layer.print_z; + layer_new.idx_object_layer_below = layer_id; + layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; + //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. + //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. + layer_new.polygons = offset(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); + + if (! slicing_params.soluble_interface) { + // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, + // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. + for (size_t top_idx = size_t(std::max(0, contact_idx)); + top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + support_params.support_layer_height_min + EPSILON; + ++ top_idx) { + if (top_contacts[top_idx]->print_z > layer_new.print_z - support_params.support_layer_height_min - EPSILON) { + // A top layer has been found, which is close to the new bottom layer. + coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; + assert(std::abs(diff) <= support_params.support_layer_height_min + EPSILON); + if (diff > 0.) { + // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. + assert(diff < layer_new.height + EPSILON); + assert(layer_new.height - diff >= support_params.support_layer_height_min - EPSILON); + layer_new.print_z = top_contacts[top_idx]->print_z; + layer_new.height -= diff; + } + else { + // The top contact layer is above this layer. One may either make this layer thicker or thinner. + // By making the layer thicker, one will decrease the number of discrete layers with the price of extruding a bit too thick bridges. + // By making the layer thinner, one adds one more discrete layer. + layer_new.print_z = top_contacts[top_idx]->print_z; + layer_new.height -= diff; + } + break; + } + } + } + +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), + union_ex(layer_new.polygons, false)); +#endif /* SLIC3R_DEBUG */ + + // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. + //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? + touching = offset(touching, float(SCALED_EPSILON)); + for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++layer_id_above) { + const Layer &layer_above = *object.layers()[layer_id_above]; + if (layer_above.print_z > layer_new.print_z - EPSILON) + break; + if (! layer_support_areas[layer_id_above].empty()) { +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), + { { { union_ex(touching, false) }, { "touching", "blue", 0.5f } }, + { { union_ex(layer_support_areas[layer_id_above], true) }, { "above", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), + union_ex(layer_support_areas[layer_id_above], false)); +#endif /* SLIC3R_DEBUG */ + } + } + + return &layer_new; +} + +// Returns polygons to print + polygons to propagate downwards. +// Called twice: First for normal supports, possibly trimmed by "on build plate only", second for support enforcers not trimmed by "on build plate only". +static inline std::pair project_support_to_grid(const Layer &layer, const SupportGridParams &grid_params, const Polygons &overhangs, Polygons *layer_buildplate_covered +#ifdef SLIC3R_DEBUG + , size_t iRun, size_t layer_id, const char *debug_name +#endif /* SLIC3R_DEBUG */ +) +{ + // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. +// Polygons trimming = union_(to_polygons(layer.slices), touching, true); + Polygons trimming = layer_buildplate_covered ? std::move(*layer_buildplate_covered) : offset(layer.lslices, float(SCALED_EPSILON)); + Polygons overhangs_projection = diff(overhangs, trimming, false); + +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-%d-%lf.svg", debug_name, iRun, layer.print_z), + { { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, + { { union_ex(overhangs_projection, true) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + remove_sticks(overhangs_projection); + remove_degenerate(overhangs_projection); + +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-cleaned-%d-%lf.svg", debug_name, iRun, layer.print_z), + { { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, + { { union_ex(overhangs_projection, false) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + + SupportGridPattern support_grid_pattern(&overhangs_projection, &trimming, grid_params); + tbb::task_group task_group_inner; + + std::pair out; + + // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing + // to allow a placement of suppot zig-zag snake along the grid lines. + task_group_inner.run([&grid_params, &support_grid_pattern, &out +#ifdef SLIC3R_DEBUG + , &layer, layer_id, iRun, debug_name +#endif /* SLIC3R_DEBUG */ + ] { + out.first = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true +#ifdef SLIC3R_DEBUG + , (std::string(debug_name) + "_support_area").c_str(), iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-layer_support_area-gridded-%s-%d-%lf.svg", debug_name, iRun, layer.print_z), + union_ex(out.first, false)); +#endif /* SLIC3R_DEBUG */ + }); + + // 2) Support polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + task_group_inner.run([&grid_params, &support_grid_pattern, &out +#ifdef SLIC3R_DEBUG + , &layer, layer_id, &overhangs_projection, &trimming, iRun, debug_name +#endif /* SLIC3R_DEBUG */ + ] { + out.second = support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true +#ifdef SLIC3R_DEBUG + , "support_projection", iRun, layer_id, layer.print_z +#endif // SLIC3R_DEBUG + ); +#ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), + union_ex(out.second, false)); +#endif /* SLIC3R_DEBUG */ +#ifdef SLIC3R_DEBUG + SVG::export_expolygons(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), + { { { union_ex(trimming, false) }, { "trimming", "gray", 0.5f } }, + { { union_ex(overhangs_projection, true) }, { "overhangs_projection", "blue", 0.5f } }, + { { union_ex(out.second, true) }, { "projection_new", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif /* SLIC3R_DEBUG */ + }); + + task_group_inner.wait(); + return out; +} + // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, - std::vector &layer_support_areas) const + const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, + MyLayerStorage &layer_storage, std::vector &layer_support_areas) const { + if (top_contacts.empty()) + return MyLayersPtr(); + #ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; + static size_t s_iRun = 0; + size_t iRun = s_iRun ++; #endif /* SLIC3R_DEBUG */ + //FIXME higher expansion_to_slice here? why? + //const auto expansion_to_slice = m_support_material_flow.scaled_spacing() / 2 + 25; + const SupportGridParams grid_params(*m_object_config, m_support_params.support_material_flow); + const bool buildplate_only = ! buildplate_covered.empty(); + // Allocate empty surface areas, one per object layer. layer_support_areas.assign(object.total_layer_count(), Polygons()); @@ -1801,237 +2144,117 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // we'll use them to clip our support and detect where does it stick MyLayersPtr bottom_contacts; - if (! top_contacts.empty()) - { - // There is some support to be built, if there are non-empty top surfaces detected. - // Sum of unsupported contact areas above the current layer.print_z. - Polygons projection; - // Last top contact layer visited when collecting the projection of contact areas. - int contact_idx = int(top_contacts.size()) - 1; - for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) { - BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; - const Layer &layer = *object.get_layer(layer_id); - // Collect projections of all contact areas above or at the same level as this top surface. + // There is some support to be built, if there are non-empty top surfaces detected. + // Sum of unsupported contact areas above the current layer.print_z. + Polygons overhangs_projection; + // Sum of unsupported enforcer contact areas above the current layer.print_z. + // Only used if "supports on build plate only" is enabled and both automatic and support enforcers are enabled. + Polygons enforcers_projection; + // Last top contact layer visited when collecting the projection of contact areas. + int contact_idx = int(top_contacts.size()) - 1; + for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) { + BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; + const Layer &layer = *object.get_layer(layer_id); + // Collect projections of all contact areas above or at the same level as this top surface. #ifdef SLIC3R_DEBUG - Polygons polygons_new; + Polygons polygons_new; + Polygons enforcers_new; #endif // SLIC3R_DEBUG - for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { + for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { + MyLayer &top_contact = *top_contacts[contact_idx]; #ifndef SLIC3R_DEBUG - Polygons polygons_new; + Polygons polygons_new; + Polygons enforcers_new; #endif // SLIC3R_DEBUG - // Contact surfaces are expanded away from the object, trimmed by the object. - // Use a slight positive offset to overlap the touching regions. + // Contact surfaces are expanded away from the object, trimmed by the object. + // Use a slight positive offset to overlap the touching regions. #if 0 - // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. - polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON)); + // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. + polygons_append(polygons_new, offset(*top_contact.contact_polygons, SCALED_EPSILON)); + if (top_contact.enforcer_polygons) + polygons_append(enforcers_new, offset(*top_contact.enforcer_polygons, SCALED_EPSILON)); #else - // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller - // than the grid cells. - polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons)); + // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller + // than the grid cells. + polygons_append(polygons_new, std::move(*top_contact.contact_polygons)); + if (top_contact.enforcer_polygons) + polygons_append(enforcers_new, std::move(*top_contact.enforcer_polygons)); #endif - // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. - // Use a slight positive offset to overlap the touching regions. - polygons_append(polygons_new, offset(*top_contacts[contact_idx]->overhang_polygons, float(SCALED_EPSILON))); - polygons_append(projection, union_(polygons_new)); - } - if (projection.empty()) - continue; - Polygons projection_raw = union_(projection); - - tbb::task_group task_group; - if (! m_object_config->support_material_buildplate_only) - // Find the bottom contact layers above the top surfaces of this layer. - task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw - #ifdef SLIC3R_DEBUG - , &polygons_new - #endif // SLIC3R_DEBUG - ] { - Polygons top = collect_region_slices_by_type(layer, stTop); - #ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(projection_raw); - bbox.merge(get_extents(top)); - ::Slic3r::SVG svg(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), bbox); - svg.draw(union_ex(top, false), "blue", 0.5f); - svg.draw(union_ex(projection_raw, true), "red", 0.5f); - svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f)); - svg.draw(layer.lslices, "green", 0.5f); - svg.draw(union_ex(polygons_new, true), "magenta", 0.5f); - svg.draw_outline(union_ex(polygons_new, true), "magenta", "magenta", scale_(0.1f)); - } - #endif /* SLIC3R_DEBUG */ - - // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any - // top surfaces above layer.print_z falls onto this top surface. - // Touching are the contact surfaces supported exclusively by this top surfaces. - // Don't use a safety offset as it has been applied during insertion of polygons. - if (! top.empty()) { - Polygons touching = intersection(top, projection_raw, false); - if (! touching.empty()) { - // Allocate a new bottom contact layer. - MyLayer &layer_new = layer_allocate(layer_storage, sltBottomContact); - bottom_contacts.push_back(&layer_new); - // Grow top surfaces so that interface and support generation are generated - // with some spacing from object - it looks we don't need the actual - // top shapes so this can be done here - //FIXME calculate layer height based on the actual thickness of the layer: - // If the layer is extruded with no bridging flow, support just the normal extrusions. - layer_new.height = m_slicing_params.soluble_interface ? - // Align the interface layer with the object's layer height. - object.layers()[layer_id + 1]->height : - // Place a bridge flow interface layer or the normal flow interface layer over the top surface. - m_support_material_bottom_interface_flow.height(); - layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z : - layer.print_z + layer_new.height + m_slicing_params.gap_object_support; - layer_new.bottom_z = layer.print_z; - layer_new.idx_object_layer_below = layer_id; - layer_new.bridging = ! m_slicing_params.soluble_interface && m_object_config->thick_bridges; - //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. - //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. - layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (! m_slicing_params.soluble_interface) { - // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, - // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. - for (size_t top_idx = size_t(std::max(0, contact_idx)); - top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON; - ++ top_idx) { - if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) { - // A top layer has been found, which is close to the new bottom layer. - coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; - assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON); - if (diff > 0.) { - // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. - assert(diff < layer_new.height + EPSILON); - assert(layer_new.height - diff >= m_support_layer_height_min - EPSILON); - layer_new.print_z = top_contacts[top_idx]->print_z; - layer_new.height -= diff; - } else { - // The top contact layer is above this layer. One may either make this layer thicker or thinner. - // By making the layer thicker, one will decrease the number of discrete layers with the price of extruding a bit too thick bridges. - // By making the layer thinner, one adds one more discrete layer. - layer_new.print_z = top_contacts[top_idx]->print_z; - layer_new.height -= diff; - } - break; - } - } - } - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), - union_ex(layer_new.polygons, false)); - #endif /* SLIC3R_DEBUG */ - // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. - //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? - touching = offset(touching, float(SCALED_EPSILON)); - for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { - const Layer &layer_above = *object.layers()[layer_id_above]; - if (layer_above.print_z > layer_new.print_z - EPSILON) - break; - if (! layer_support_areas[layer_id_above].empty()) { -#ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(touching); - bbox.merge(get_extents(layer_support_areas[layer_id_above])); - ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), bbox); - svg.draw(union_ex(touching, false), "blue", 0.5f); - svg.draw(union_ex(layer_support_areas[layer_id_above], true), "red", 0.5f); - svg.draw_outline(union_ex(layer_support_areas[layer_id_above], true), "red", "blue", scale_(0.1f)); - } -#endif /* SLIC3R_DEBUG */ - layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); -#ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - union_ex(layer_support_areas[layer_id_above], false)); -#endif /* SLIC3R_DEBUG */ - } - } - } - } // ! top.empty() - }); - - Polygons &layer_support_area = layer_support_areas[layer_id]; - task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area] { - // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. - // Polygons trimming = union_(to_polygons(layer.slices), touching, true); - Polygons trimming = offset(layer.lslices, float(SCALED_EPSILON)); - projection = diff(projection_raw, trimming, false); - #ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(projection_raw); - bbox.merge(get_extents(trimming)); - ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-%d-%lf.svg", iRun, layer.print_z), bbox); - svg.draw(union_ex(trimming, false), "blue", 0.5f); - svg.draw(union_ex(projection, true), "red", 0.5f); - svg.draw_outline(union_ex(projection, true), "red", "blue", scale_(0.1f)); - } - #endif /* SLIC3R_DEBUG */ - remove_sticks(projection); - remove_degenerate(projection); - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-support-areas-raw-cleaned-%d-%lf.svg", iRun, layer.print_z), - union_ex(projection, false)); - #endif /* SLIC3R_DEBUG */ - SupportGridPattern support_grid_pattern( - // Support islands, to be stretched into a grid. - projection, - // Trimming polygons, to trim the stretched support islands. - trimming, - // Grid spacing. - m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle.value), - m_support_material_flow.spacing()); - tbb::task_group task_group_inner; - // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing - // to allow a placement of suppot zig-zag snake along the grid lines. - task_group_inner.run([this, &support_grid_pattern, &layer_support_area - #ifdef SLIC3R_DEBUG - , &layer - #endif /* SLIC3R_DEBUG */ - ] { - layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true); - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z), - union_ex(layer_support_area, false)); - #endif /* SLIC3R_DEBUG */ - }); - // 2) Support polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - Polygons projection_new; - task_group_inner.run([&projection_new, &support_grid_pattern - #ifdef SLIC3R_DEBUG - , &layer, &projection, &trimming - #endif /* SLIC3R_DEBUG */ - ] { - projection_new = support_grid_pattern.extract_support(-5, true); - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), - union_ex(projection_new, false)); - #endif /* SLIC3R_DEBUG */ -#ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(projection); - bbox.merge(get_extents(projection_new)); - bbox.merge(get_extents(trimming)); - ::Slic3r::SVG svg(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), bbox); - svg.draw(union_ex(trimming, false), "gray", 0.5f); - svg.draw(union_ex(projection_new, false), "red", 0.5f); - svg.draw(union_ex(projection, false), "blue", 0.5f); - } -#endif /* SLIC3R_DEBUG */ - }); - task_group_inner.wait(); - projection = std::move(projection_new); - }); - task_group.wait(); + // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. + // Use a slight positive offset to overlap the touching regions. + polygons_append(polygons_new, offset(*top_contact.overhang_polygons, float(SCALED_EPSILON))); + polygons_append(overhangs_projection, union_(polygons_new)); + polygons_append(enforcers_projection, enforcers_new); } - std::reverse(bottom_contacts.begin(), bottom_contacts.end()); - trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); - } // ! top_contacts.empty() + if (overhangs_projection.empty() && enforcers_projection.empty()) + continue; + // Overhangs_projection will be filled in asynchronously, move it away. + Polygons overhangs_projection_raw = union_(std::move(overhangs_projection)); + Polygons enforcers_projection_raw = union_(std::move(enforcers_projection)); + + tbb::task_group task_group; + const Polygons &overhangs_for_bottom_contacts = buildplate_only ? enforcers_projection_raw : overhangs_projection_raw; + if (! overhangs_for_bottom_contacts.empty()) + // Find the bottom contact layers above the top surfaces of this layer. + task_group.run([this, &object, &layer, &top_contacts, contact_idx, &layer_storage, &layer_support_areas, &bottom_contacts, &overhangs_for_bottom_contacts + #ifdef SLIC3R_DEBUG + , iRun, &polygons_new + #endif // SLIC3R_DEBUG + ] { + // Find the bottom contact layers above the top surfaces of this layer. + MyLayer *layer_new = detect_bottom_contacts( + m_slicing_params, m_support_params, object, layer, top_contacts, contact_idx, layer_storage, layer_support_areas, overhangs_for_bottom_contacts +#ifdef SLIC3R_DEBUG + , iRun, polygons_new +#endif // SLIC3R_DEBUG + ); + if (layer_new) + bottom_contacts.push_back(layer_new); + }); + + Polygons &layer_support_area = layer_support_areas[layer_id]; + Polygons *layer_buildplate_covered = buildplate_covered.empty() ? nullptr : &buildplate_covered[layer_id]; + task_group.run([&grid_params, &overhangs_projection, &overhangs_projection_raw, &layer, &layer_support_area, layer_buildplate_covered +#ifdef SLIC3R_DEBUG + , iRun, layer_id +#endif /* SLIC3R_DEBUG */ + ] { + // buildplate_covered[layer_id] will be consumed here. + std::tie(layer_support_area, overhangs_projection) = project_support_to_grid(layer, grid_params, overhangs_projection_raw, layer_buildplate_covered +#ifdef SLIC3R_DEBUG + , iRun, layer_id, "general" +#endif /* SLIC3R_DEBUG */ + ); + }); + + Polygons layer_support_area_enforcers; + if (! enforcers_projection.empty()) + // Project the enforcers polygons downwards, don't trim them with the "buildplate only" polygons. + task_group.run([&grid_params, &enforcers_projection, &enforcers_projection_raw, &layer, &layer_support_area_enforcers +#ifdef SLIC3R_DEBUG + , iRun, layer_id +#endif /* SLIC3R_DEBUG */ + ]{ + std::tie(layer_support_area_enforcers, enforcers_projection) = project_support_to_grid(layer, grid_params, enforcers_projection_raw, nullptr +#ifdef SLIC3R_DEBUG + , iRun, layer_id, "enforcers" +#endif /* SLIC3R_DEBUG */ + ); + }); + + task_group.wait(); + + if (! layer_support_area_enforcers.empty()) { + if (layer_support_area.empty()) + layer_support_area = std::move(layer_support_area_enforcers); + else + layer_support_area = union_(layer_support_area, layer_support_area_enforcers); + } + } // over all layers downwards + + std::reverse(bottom_contacts.begin(), bottom_contacts.end()); + trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); return bottom_contacts; } @@ -2176,7 +2399,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // Verify that the extremes are separated by m_support_layer_height_min. for (size_t i = 1; i < extremes.size(); ++ i) { assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() == 0. || - extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_layer_height_min - EPSILON); + extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_params.support_layer_height_min - EPSILON); assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. || extremes[i]->layer_type == extremes[i-1]->layer_type || (extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact)); @@ -2200,7 +2423,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). assert(extr2->layer_type == sltTopContact); assert(extr2->bottom_z == m_slicing_params.first_print_layer_height); - assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_layer_height_min - EPSILON); + assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_params.support_layer_height_min - EPSILON); if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = 0.; @@ -2236,7 +2459,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int if (dist == 0.) continue; // The new layers shall be at least m_support_layer_height_min thick. - assert(dist >= m_support_layer_height_min - EPSILON); + assert(dist >= m_support_params.support_layer_height_min - EPSILON); if (synchronize) { // Emit support layers synchronized with the object layers. // Find the first object layer, which has its print_z in this support Z range. @@ -2265,10 +2488,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int assert(n_layers_extra > 0); coordf_t step = dist / coordf_t(n_layers_extra); if (extr1 != nullptr && extr1->layer_type == sltTopContact && - extr1->print_z + m_support_layer_height_min > extr1->bottom_z + step) { + extr1->print_z + m_support_params.support_layer_height_min > extr1->bottom_z + step) { // The bottom extreme is a bottom of a top surface. Ensure that the gap // between the 1st intermediate layer print_z and extr1->print_z is not too small. - assert(extr1->bottom_z + m_support_layer_height_min < extr1->print_z + EPSILON); + assert(extr1->bottom_z + m_support_params.support_layer_height_min < extr1->print_z + EPSILON); // Generate the first intermediate layer. MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = extr1->bottom_z; @@ -2459,7 +2682,7 @@ void PrintObjectSupportMaterial::generate_base_layers( ++ iRun; #endif /* SLIC3R_DEBUG */ - this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); + this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( @@ -2649,16 +2872,16 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf new_layer.bottom_z = print_z; new_layer.polygons = interface_polygons; //FIXME misusing contact_polygons for support columns. - new_layer.contact_polygons = new Polygons(columns); + new_layer.contact_polygons = std::make_unique(columns); } } else if (columns_base != nullptr) { // Expand the bases of the support columns in the 1st layer. { Polygons &raft = columns_base->polygons; - Polygons trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + Polygons trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (inflate_factor_1st_layer > SCALED_EPSILON) { // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_first_layer_flow.scaled_width()))); + auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; for (int i = 0; i < nsteps; ++ i) raft = diff(offset(raft, step), trimming); @@ -3498,16 +3721,16 @@ void PrintObjectSupportMaterial::generate_toolpaths( const MyLayersPtr &base_interface_layers) const { // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); + LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); - coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_material_interface_flow.spacing(); - coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); - coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); - coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing); - if (m_slicing_params.soluble_interface) { + coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); + coordf_t interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / interface_spacing); + coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); + coordf_t support_density = std::min(1., m_support_params.support_material_flow.spacing() / support_spacing); + if (m_object_config->support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. interface_spacing = support_spacing; interface_density = support_density; @@ -3581,10 +3804,10 @@ void PrintObjectSupportMaterial::generate_toolpaths( ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); if (! to_infill_polygons.empty()) { assert(! raft_layer.bridging); - Flow flow(float(m_support_material_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter()); + Flow flow(float(m_support_params.support_material_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); Fill * filler = filler_support.get(); filler->angle = raft_angle_base; - filler->spacing = m_support_material_flow.spacing(); + filler->spacing = m_support_params.support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); fill_expolygons_with_sheath_generate_paths( // Destination @@ -3600,20 +3823,20 @@ void PrintObjectSupportMaterial::generate_toolpaths( } Fill *filler = filler_interface.get(); - Flow flow = m_first_layer_flow; + Flow flow = m_support_params.first_layer_flow; float density = 0.f; if (support_layer_id == 0) { // Base flange. filler->angle = raft_angle_1st_layer; - filler->spacing = m_first_layer_flow.spacing(); + filler->spacing = m_support_params.first_layer_flow.spacing(); density = float(m_object_config->raft_first_layer_density.value * 0.01); } else if (support_layer_id >= m_slicing_params.base_raft_layers) { filler->angle = raft_angle_interface; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. - filler->spacing = m_support_material_flow.spacing(); + filler->spacing = m_support_params.support_material_flow.spacing(); assert(! raft_layer.bridging); - flow = Flow(float(m_support_material_interface_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter()); + flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); density = float(interface_density); } else continue; @@ -3719,26 +3942,26 @@ void PrintObjectSupportMaterial::generate_toolpaths( base_layer.layer = intermediate_layers[idx_layer_intermediate]; if (m_object_config->support_material_interface_layers == 0) { - // If no interface layers were requested, we treat the contact layer exactly as a generic base layer. - if (m_can_merge_support_regions) { + // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. + if (m_support_params.can_merge_support_regions) { if (base_layer.could_merge(top_contact_layer)) base_layer.merge(std::move(top_contact_layer)); - else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging) - std::swap(base_layer, top_contact_layer); + else if (base_layer.empty()) + base_layer = std::move(top_contact_layer); } } else { - loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow); + loop_interface_processor.generate(top_contact_layer, m_support_params.support_material_interface_flow); // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used // to trim other layers. if (top_contact_layer.could_merge(interface_layer)) top_contact_layer.merge(std::move(interface_layer)); } - if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_can_merge_support_regions) { + if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_support_params.can_merge_support_regions) { if (base_layer.could_merge(bottom_contact_layer)) base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) - std::swap(base_layer, bottom_contact_layer); + else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) + base_layer = std::move(bottom_contact_layer); } #if 0 @@ -3758,39 +3981,41 @@ void PrintObjectSupportMaterial::generate_toolpaths( MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) continue; - bool interface_as_base = (&layer_ex == &interface_layer) && m_slicing_params.soluble_interface; + bool interface_as_base = m_object_config->support_material_interface_layers.value == 0 || + (m_object_config->support_material_bottom_interface_layers == 0 && &layer_ex == &bottom_contact_layer); //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) auto interface_flow = layer_ex.layer->bridging ? - Flow::bridging_flow(layer_ex.layer->height, m_support_material_bottom_interface_flow.nozzle_diameter()) : - (interface_as_base ? &m_support_material_flow : &m_support_material_interface_flow)->with_height(float(layer_ex.layer->height)); + Flow::bridging_flow(layer_ex.layer->height, m_support_params.support_material_bottom_interface_flow.nozzle_diameter()) : + (interface_as_base ? &m_support_params.support_material_flow : &m_support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); filler_interface->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. interface_angle; - filler_interface->spacing = m_support_material_interface_flow.spacing(); - filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / interface_density)); + double density = interface_as_base ? support_density : interface_density; + filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); + filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( // Destination layer_ex.extrusions, // Regions to fill union_ex(layer_ex.polygons_to_extrude(), true), // Filler and its parameters - filler_interface.get(), float(interface_density), + filler_interface.get(), float(density), // Extrusion parameters erSupportMaterialInterface, interface_flow); } // Base interface layers under soluble interfaces - if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()){ + if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { Fill *filler = filler_base_interface.get(); //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) assert(! base_interface_layer.layer->bridging); - Flow interface_flow = m_support_material_flow.with_height(float(base_interface_layer.layer->height)); + Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); filler->angle = interface_angle; - filler->spacing = m_support_material_interface_flow.spacing(); + filler->spacing = m_support_params.support_material_interface_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); fill_expolygons_generate_paths( // Destination @@ -3811,8 +4036,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. assert(! base_layer.layer->bridging); - auto flow = m_support_material_flow.with_height(float(base_layer.layer->height)); - filler->spacing = m_support_material_flow.spacing(); + auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); + filler->spacing = m_support_params.support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); float density = float(support_density); bool sheath = with_sheath; @@ -3822,7 +4047,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler = filler_first_layer; filler->angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); density = float(m_object_config->raft_first_layer_density.value * 0.01); - flow = m_first_layer_flow; + flow = m_support_params.first_layer_flow; // use the proper spacing for first layer as we don't need to align // its pattern to the other layers //FIXME When paralellizing, each thread shall have its own copy of the fillers. diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index da13768f6..a1bd81297 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -48,39 +48,8 @@ public: class MyLayer { public: - MyLayer() : - layer_type(sltUnknown), - print_z(0.), - bottom_z(0.), - height(0.), - idx_object_layer_above(size_t(-1)), - idx_object_layer_below(size_t(-1)), - bridging(false), - contact_polygons(nullptr), - overhang_polygons(nullptr) - {} - - ~MyLayer() - { - delete contact_polygons; - contact_polygons = nullptr; - delete overhang_polygons; - overhang_polygons = nullptr; - } - void reset() { - layer_type = sltUnknown; - print_z = 0.; - bottom_z = 0.; - height = 0.; - idx_object_layer_above = size_t(-1); - idx_object_layer_below = size_t(-1); - bridging = false; - polygons.clear(); - delete contact_polygons; - contact_polygons = nullptr; - delete overhang_polygons; - overhang_polygons = nullptr; + *this = MyLayer(); } bool operator==(const MyLayer &layer2) const { @@ -103,6 +72,21 @@ public: return false; } + void merge(MyLayer &&rhs) { + // The union_() does not support move semantic yet, but maybe one day it will. + this->polygons = union_(this->polygons, std::move(rhs.polygons)); + auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { + if (! dst || dst->empty()) + dst = std::move(src); + else if (src && ! src->empty()) + *dst = union_(*dst, std::move(*src)); + }; + merge(this->contact_polygons, rhs.contact_polygons); + merge(this->overhang_polygons, rhs.overhang_polygons); + merge(this->enforcer_polygons, rhs.enforcer_polygons); + rhs.reset(); + } + // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. // For the non-bridging flow, bottom_print_z will be equal to bottom_z. coordf_t bottom_print_z() const { return print_z - height; } @@ -110,29 +94,44 @@ public: // To sort the extremes of top / bottom interface layers. coordf_t extreme_z() const { return (this->layer_type == sltTopContact) ? this->bottom_z : this->print_z; } - SupporLayerType layer_type; + SupporLayerType layer_type { sltUnknown }; // Z used for printing, in unscaled coordinates. - coordf_t print_z; + coordf_t print_z { 0 }; // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, // otherwise bottom_z + gap + height = print_z. - coordf_t bottom_z; + coordf_t bottom_z { 0 }; // Layer height in unscaled coordinates. - coordf_t height; + coordf_t height { 0 }; // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_above; + size_t idx_object_layer_above { size_t(-1) }; // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_below; + size_t idx_object_layer_below { size_t(-1) }; // Use a bridging flow when printing this support layer. - bool bridging; + bool bridging { false }; // Polygons to be filled by the support pattern. Polygons polygons; // Currently for the contact layers only. - // MyLayer owns the contact_polygons and overhang_polygons, they are freed by the destructor. - Polygons *contact_polygons; - Polygons *overhang_polygons; + std::unique_ptr contact_polygons; + std::unique_ptr overhang_polygons; + // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. + std::unique_ptr enforcer_polygons; + }; + + struct SupportParams { + Flow first_layer_flow; + Flow support_material_flow; + Flow support_material_interface_flow; + Flow support_material_bottom_interface_flow; + // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? + bool can_merge_support_regions; + + coordf_t support_layer_height_min; + // coordf_t support_layer_height_max; + + coordf_t gap_xy; }; // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained @@ -159,17 +158,19 @@ public: void generate(PrintObject &object); private: + std::vector buildplate_covered(const PrintObject &object) const; + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. - MyLayersPtr top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const; + MyLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const; // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. MyLayersPtr bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, - std::vector &layer_support_areas) const; + const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, + MyLayerStorage &layer_storage, std::vector &layer_support_areas) const; // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const; @@ -240,18 +241,8 @@ private: // Pre-calculated parameters shared between the object slicer and the support generator, // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. SlicingParameters m_slicing_params; - - Flow m_first_layer_flow; - Flow m_support_material_flow; - Flow m_support_material_interface_flow; - Flow m_support_material_bottom_interface_flow; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool m_can_merge_support_regions; - - coordf_t m_support_layer_height_min; -// coordf_t m_support_layer_height_max; - - coordf_t m_gap_xy; + // Various precomputed support parameters to be shared with external functions. + SupportParams m_support_params; }; } // namespace Slic3r diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 0c26d42c8..1ac45f1b5 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -6,6 +6,7 @@ #include #include +#include "Platform.hpp" #include "Time.hpp" #ifdef WIN32 @@ -19,9 +20,14 @@ #ifdef BSD #include #endif - #ifdef __APPLE__ + #ifdef __APPLE__ #include #endif + #ifdef __linux__ + #include + #include + #include + #endif #endif #include @@ -417,6 +423,140 @@ std::error_code rename_file(const std::string &from, const std::string &to) #endif } +#ifdef __linux__ +// Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes, +// for example ChromeOS Linux integration or FlashAIR WebDAV. +// Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept, +// and only features supported by Linux 3.10 (on our build server with CentOS 7) are kept, namely sendfile with ranges and statx() are not supported. +bool copy_file_linux(const boost::filesystem::path &from, const boost::filesystem::path &to, boost::system::error_code &ec) +{ + using namespace boost::filesystem; + + struct fd_wrapper + { + int fd { -1 }; + fd_wrapper() = default; + explicit fd_wrapper(int fd) throw() : fd(fd) {} + ~fd_wrapper() throw() { if (fd >= 0) ::close(fd); } + }; + + ec.clear(); + int err = 0; + + // Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors + fd_wrapper infile, outfile; + + while (true) { + infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC); + if (infile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + fail: + ec.assign(err, boost::system::system_category()); + return false; + } + break; + } + + struct ::stat from_stat; + if (::fstat(infile.fd, &from_stat) != 0) { + fail_errno: + err = errno; + goto fail; + } + + const mode_t from_mode = from_stat.st_mode; + if (!S_ISREG(from_mode)) { + err = ENOSYS; + goto fail; + } + + // Enable writing for the newly created files. Having write permission set is important e.g. for NFS, + // which checks the file permission on the server, even if the client's file descriptor supports writing. + mode_t to_mode = from_mode | S_IWUSR; + int oflag = O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC; + + while (true) { + outfile.fd = ::open(to.c_str(), oflag, to_mode); + if (outfile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + goto fail; + } + break; + } + + struct ::stat to_stat; + if (::fstat(outfile.fd, &to_stat) != 0) + goto fail_errno; + + to_mode = to_stat.st_mode; + if (!S_ISREG(to_mode)) { + err = ENOSYS; + goto fail; + } + + if (from_stat.st_dev == to_stat.st_dev && from_stat.st_ino == to_stat.st_ino) { + err = EEXIST; + goto fail; + } + + //! copy_file implementation that uses sendfile loop. Requires sendfile to support file descriptors. + //FIXME Vojtech: This is a copy loop valid for Linux 2.6.33 and newer. + // copy_file_data_copy_file_range() supports cross-filesystem copying since 5.3, but Vojtech did not want to polute this + // function with that, we don't think the performance gain is worth it for the types of files we are copying, + // and our build server based on CentOS 7 with Linux 3.10 does not support that anyways. + { + // sendfile will not send more than this amount of data in one call + constexpr std::size_t max_send_size = 0x7ffff000u; + uintmax_t offset = 0u; + while (off_t(offset) < from_stat.st_size) { + uintmax_t size_left = from_stat.st_size - offset; + std::size_t size_to_copy = max_send_size; + if (size_left < static_cast(max_send_size)) + size_to_copy = static_cast(size_left); + ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy); + if (sz < 0) { + err = errno; + if (err == EINTR) + continue; + if (err == 0) + break; + goto fail; // err already contains the error code + } + offset += sz; + } + } + + // If we created a new file with an explicitly added S_IWUSR permission, + // we may need to update its mode bits to match the source file. + if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) { + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // Ignore that. 9p filesystem does not allow fmod(). + BOOST_LOG_TRIVIAL(info) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message() << + " This may be expected when writing to a 9p filesystem."; + } else { + // Generic linux. Write out an error to console. At least we may get some feedback. + BOOST_LOG_TRIVIAL(error) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message(); + } + } + + // Note: Use fsync/fdatasync followed by close to avoid dealing with the possibility of close failing with EINTR. + // Even if close fails, including with EINTR, most operating systems (presumably, except HP-UX) will close the + // file descriptor upon its return. This means that if an error happens later, when the OS flushes data to the + // underlying media, this error will go unnoticed and we have no way to receive it from close. Calling fsync/fdatasync + // ensures that all data have been written, and even if close fails for some unfathomable reason, we don't really + // care at that point. + err = ::fdatasync(outfile.fd); + if (err != 0) + goto fail_errno; + + return true; +} +#endif // __linux__ + CopyFileResult copy_file_inner(const std::string& from, const std::string& to, std::string& error_message) { const boost::filesystem::path source(from); @@ -434,7 +574,13 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to, s if (ec) BOOST_LOG_TRIVIAL(debug) << "boost::filesystem::permisions before copy error message (this could be irrelevant message based on file system): " << ec.message(); ec.clear(); +#ifdef __linux__ + // We want to allow copying files on Linux to succeed even if changing the file attributes fails. + // That may happen when copying on some exotic file system, for example Linux on Chrome. + copy_file_linux(source, target, ec); +#else // __linux__ boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec); +#endif // __linux__ if (ec) { error_message = ec.message(); return FAIL_COPY_FILE; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 6436de2ca..4b3a1c6ca 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -209,8 +209,6 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp - Utils/Platform.cpp - Utils/Platform.hpp Utils/Process.cpp Utils/Process.hpp Utils/Profile.hpp diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 00b9c2e29..de7a944f2 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -328,7 +328,12 @@ double Control::get_double_value(const SelectedSlider& selection) int Control::get_tick_from_value(double value) { - auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); + std::vector::iterator it; + if (m_is_smart_wipe_tower) + it = std::find_if(m_values.begin(), m_values.end(), + [value](const double & val) { return fabs(value - val) <= epsilon(); }); + else + it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); if (it == m_values.end()) return -1; @@ -391,6 +396,16 @@ void Control::SetLayersTimes(const std::vector& layers_times) m_layers_times[0] = layers_times[0]; for (size_t i = 1; i < layers_times.size(); i++) m_layers_times[i] = m_layers_times[i - 1] + layers_times[i]; + + // Erase duplicates values from m_values and save it to the m_layers_values + // They will be used for show the correct estimated time for MM print, when "No sparce layer" is enabled + // See https://github.com/prusa3d/PrusaSlicer/issues/6232 + m_is_smart_wipe_tower = m_values.size() != m_layers_times.size(); + if (m_is_smart_wipe_tower) { + m_layers_values = m_values; + sort(m_layers_values.begin(), m_layers_values.end()); + m_layers_values.erase(unique(m_layers_values.begin(), m_layers_values.end()), m_layers_values.end()); + } } void Control::SetLayersTimes(const std::vector& layers_times) @@ -424,6 +439,22 @@ void Control::SetExtruderColors( const std::vector& extruder_colors m_extruder_colors = extruder_colors; } +bool Control::IsNewPrint() +{ + if (GUI::wxGetApp().plater()->printer_technology() == ptSLA) + return false; + const Print& print = GUI::wxGetApp().plater()->fff_print(); + std::string idxs; + for (auto object : print.objects()) + idxs += std::to_string(object->id().id) + "_"; + + if (idxs == m_print_obj_idxs) + return false; + + m_print_obj_idxs = idxs; + return true; +} + void Control::get_lower_and_higher_position(int& lower_pos, int& higher_pos) { const double step = get_scroll_step(); @@ -495,6 +526,18 @@ void Control::render() } } +bool Control::is_wipe_tower_layer(int tick) const +{ + if (!m_is_smart_wipe_tower || tick >= (int)m_values.size()) + return false; + if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1])) + return false; + if (m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) + return true; + + return false; +} + void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) { const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; @@ -506,6 +549,11 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ if (tick == 0) return; + if (is_wipe_tower_layer(tick)) { + m_rect_tick_action = wxRect(); + return; + } + wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end()) icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); @@ -654,6 +702,21 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer if (value >= m_values.size()) return "ErrVal"; + // When "Print Settings -> Multiple Extruders -> No sparse layer" is enabled, then "Smart" Wipe Tower is used for wiping. + // As a result, each layer with tool changes is splited for min 3 parts: first tool, wiping, second tool ... + // So, vertical slider have to respect to this case. + // see https://github.com/prusa3d/PrusaSlicer/issues/6232. + // m_values contains data for all layer's parts, + // but m_layers_values contains just unique Z values. + // Use this function for correct conversion slider position to number of printed layer + auto get_layer_number = [this](int value) { + double layer_print_z = m_values[is_wipe_tower_layer(value) ? std::max(value - 1, 0) : value]; + auto it = std::lower_bound(m_layers_values.begin(), m_layers_values.end(), layer_print_z - epsilon()); + if (it == m_layers_values.end()) + return -1; + return int(it - m_layers_values.begin()); + }; + #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER if (m_draw_mode == dmSequentialGCodeView) { return (Slic3r::GUI::get_app_config()->get("seq_top_gcode_indices") == "1") ? @@ -666,15 +729,21 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER else { if (label_type == ltEstimatedTime) { - return (value < m_layers_times.size()) ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; + if (m_is_smart_wipe_tower) { + int layer_number = get_layer_number(value); + return layer_number < 0 ? "" : short_and_splitted_time(get_time_dhms(m_layers_times[layer_number])); + } + return value < m_layers_times.size() ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; } wxString str = m_values.empty() ? wxString::Format("%.*f", 2, m_label_koef * value) : wxString::Format("%.*f", 2, m_values[value]); if (label_type == ltHeight) return str; - if (label_type == ltHeightWithLayer) - return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1); + if (label_type == ltHeightWithLayer) { + size_t layer_number = m_is_smart_wipe_tower ? (size_t)get_layer_number(value) : (m_values.empty() ? value : value + 1); + return format_wxstr("%1%\n(%2%)", str, layer_number); + } } return wxEmptyString; @@ -709,10 +778,17 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, LabelType l text_pos = wxPoint(std::max(2, pos.x - text_width - 1 - m_thumb_size.x), pos.y - 0.5 * text_height + 1); } + wxColour old_clr = dc.GetTextForeground(); + const wxPen& pen = is_wipe_tower_layer(tick) && (tick == m_lower_value || tick == m_higher_value) ? DARK_ORANGE_PEN : wxPen(old_clr); + dc.SetPen(pen); + dc.SetTextForeground(pen.GetColour()); + if (label_type == ltEstimatedTime) dc.DrawLabel(label, wxRect(text_pos, wxSize(text_width, text_height)), wxALIGN_RIGHT); else dc.DrawText(label, text_pos); + + dc.SetTextForeground(old_clr); } void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const @@ -1275,6 +1351,8 @@ wxString Control::get_tooltip(int tick/*=-1*/) if (m_focus == fiColorBand) return m_mode != SingleExtruder ? "" : _L("Edit current color - Right click the colored slider segment"); + if (m_focus == fiSmartWipeTower) + return _L("This is wipe tower layer"); if (m_draw_mode == dmSlaPrint) return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode @@ -1408,8 +1486,14 @@ void Control::OnMotion(wxMouseEvent& event) else if (is_point_in_rect(pos, m_rect_higher_thumb)) m_focus = fiHigherThumb; else { - m_focus = fiTick; tick = get_tick_near_point(pos); + if (tick < 0 && m_is_smart_wipe_tower) { + tick = get_value_from_position(pos); + m_focus = tick > 0 && is_wipe_tower_layer(tick) && (tick == m_lower_value || tick == m_higher_value) ? + fiSmartWipeTower : fiTick; + } + else + m_focus = fiTick; } m_moving_pos = pos; } @@ -1938,6 +2022,9 @@ void Control::auto_color_change() Layer* layer = object->get_layer(i); double cur_area = area(layer->lslices); + if (cur_area > prev_area) + break; + if (prev_area - cur_area > delta_area) { int tick = get_tick_from_value(layer->print_z); if (tick >= 0 && !m_ticks.has_tick(tick)) { @@ -1951,6 +2038,10 @@ void Control::auto_color_change() extruder = 1; } } + + // allow max 3 auto color changes + if (m_ticks.ticks.size() == 3) + break; } prev_area = cur_area; diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 52f7be629..de50bb0a2 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -43,6 +43,7 @@ enum FocusedItem { fiActionIcon, fiLowerThumb, fiHigherThumb, + fiSmartWipeTower, fiTick }; @@ -233,6 +234,8 @@ public: void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); void SetExtruderColors(const std::vector& extruder_colors); + bool IsNewPrint(); + void set_render_as_disabled(bool value) { m_render_as_disabled = value; } bool is_rendering_as_disabled() const { return m_render_as_disabled; } @@ -303,6 +306,7 @@ protected: void correct_higher_value(); void move_current_thumb(const bool condition); void enter_window(wxMouseEvent& event, const bool enter); + bool is_wipe_tower_layer(int tick) const; private: @@ -366,6 +370,7 @@ private: bool m_is_focused = false; bool m_force_mode_apply = true; bool m_enable_action_icon = true; + bool m_is_smart_wipe_tower = false; //This flag indicates that for current print is used "smart" wipe tower (Print Settings->Multiple Extruders->No sparse layer is enabled) DrawMode m_draw_mode = dmRegular; @@ -394,7 +399,9 @@ private: std::vector m_values; TickCodeInfo m_ticks; std::vector m_layers_times; + std::vector m_layers_values; std::vector m_extruder_colors; + std::string m_print_obj_idxs; #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER std::vector m_alternate_values; diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 3c77f3335..839782741 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -9,7 +9,6 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/Utils/Platform.hpp" // To show a message box if GUI initialization ends up with an exception thrown. #include @@ -37,8 +36,6 @@ int GUI_Run(GUI_InitParams ¶ms) signal(SIGCHLD, SIG_DFL); #endif // __APPLE__ - detect_platform(); - try { GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9f9f20ffb..0042c0702 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1,3 +1,4 @@ +//#include "stdlib.h" #include "libslic3r/libslic3r.h" #include "libslic3r/Layer.hpp" #include "GUI_Preview.hpp" @@ -642,26 +643,26 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee m_layers_slider->SetLayersTimes(m_gcode_result->time_statistics.modes.front().layers_times); // Suggest the auto color change, if model looks like sign - if (ticks_info_from_model.gcodes.empty()) + if (m_layers_slider->IsNewPrint()) { - NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); - notif_mngr->close_notification_of_type(NotificationType::SignDetected); - const Print& print = wxGetApp().plater()->fff_print(); double delta_area = scale_(scale_(25)); // equal to 25 mm2 //bool is_possible_auto_color_change = false; for (auto object : print.objects()) { + // bottom layer have to be a biggest, so control relation between bottom lazer and object size + const ExPolygons& bottom = object->get_layer(0)->lslices; + double bottom_area = area(bottom); + if (bottom_area < double(object->size().x()) * double(object->size().y())) + continue; + + // if it's sign, than object have not to be a too height double height = object->height(); coord_t longer_side = std::max(object->size().x(), object->size().y()); if (height / longer_side > 0.3) continue; - const ExPolygons& bottom = object->get_layer(0)->lslices; - if (bottom.size() > 1 || !bottom[0].holes.empty()) - continue; - - double bottom_area = area(bottom); + // at least 30% of object's height have to be a solid int i; for (i = 1; i < int(0.3 * object->layers().size()); i++) if (area(object->get_layer(1)->lslices) != bottom_area) @@ -671,17 +672,17 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); if( bottom_area - top_area > delta_area) { + NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); notif_mngr->push_notification( NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", _u8L("Apply auto color change to print"), - [this, notif_mngr](wxEvtHandler*) { - notif_mngr->close_notification_of_type(NotificationType::SignDetected); + [this](wxEvtHandler*) { m_layers_slider->auto_color_change(); return true; }); - notif_mngr->set_in_preview(true); + notif_mngr->apply_in_preview(); break; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 3d0d9c79a..9db50e6f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -122,9 +122,10 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); glsafe(::glMultMatrixd(instance_matrix.data())); - float render_color[4]; + std::array render_color; const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; size_t cache_size = drain_holes.size(); + for (size_t i = 0; i < cache_size; ++i) { const sla::DrainHole& drain_hole = drain_holes[i]; @@ -136,26 +137,27 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons // First decide about the color of the point. if (picking) { std::array color = picking_color_component(i); - render_color[0] = color[0]; - render_color[1] = color[1]; - render_color[2] = color[2]; - render_color[3] = color[3]; + + render_color = color; } else { - render_color[3] = 1.f; if (size_t(m_hover_id) == i) { - render_color[0] = 0.f; - render_color[1] = 1.0f; - render_color[2] = 1.0f; + render_color = {0.f, 1.f, 1.f, 1.f}; + } else if (m_c->hollowed_mesh() && + i < m_c->hollowed_mesh()->get_drainholes().size() && + m_c->hollowed_mesh()->get_drainholes()[i].failed) { + render_color = {1.f, 0.f, 0.f, .5f}; } else { // neigher hover nor picking + render_color[0] = point_selected ? 1.0f : 0.7f; render_color[1] = point_selected ? 0.3f : 0.7f; render_color[2] = point_selected ? 0.3f : 0.7f; render_color[3] = 0.5f; } } - glsafe(::glColor4fv(render_color)); + + glsafe(::glColor4fv(render_color.data())); float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 7f6b10670..acb85d539 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -205,6 +205,7 @@ void HollowedMesh::on_update() m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); m_hollowed_mesh_transformed->transform(trafo_inv); + m_drainholes = print_object->model_object()->sla_drain_holes; m_old_hollowing_timestamp = timestamp; const TriangleMesh &interior = print_object->hollowed_interior_mesh(); @@ -215,8 +216,9 @@ void HollowedMesh::on_update() m_hollowed_interior_transformed->transform(trafo_inv); } } - else + else { m_hollowed_mesh_transformed.reset(nullptr); + } } } else diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index ace256748..28be1b97f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -5,6 +5,7 @@ #include #include "slic3r/GUI/MeshUtils.hpp" +#include "libslic3r/SLA/Hollowing.hpp" namespace Slic3r { @@ -198,6 +199,8 @@ public: CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } #endif // NDEBUG + const sla::DrainHoles &get_drainholes() const { return m_drainholes; } + const TriangleMesh* get_hollowed_mesh() const; const TriangleMesh* get_hollowed_interior() const; @@ -211,6 +214,7 @@ private: size_t m_old_hollowing_timestamp = 0; int m_print_object_idx = -1; int m_print_objects_count = 0; + sla::DrainHoles m_drainholes; }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f50c7e356..858744229 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -672,7 +672,7 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab) bool MainFrame::can_start_new_project() const { - return (m_plater != nullptr) && !m_plater->model().objects.empty(); + return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); } bool MainFrame::can_save() const @@ -1217,9 +1217,14 @@ void MainFrame::init_menubar_as_editor() append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); +#ifndef __APPLE__ + // OSX adds its own menu item to toggle fullscreen. append_menu_check_item(viewMenu, wxID_ANY, _L("&Full screen") + "\t" + "F11", _L("Full screen"), - [this](wxCommandEvent&) { this->ShowFullScreen(!this->IsFullScreen()); }, this, - []() { return true; }, [this]() { return this->IsFullScreen(); }, this); + [this](wxCommandEvent&) { this->ShowFullScreen(!this->IsFullScreen(), + // wxFULLSCREEN_ALL: wxFULLSCREEN_NOMENUBAR | wxFULLSCREEN_NOTOOLBAR | wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION + wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); }, + this, []() { return true; }, [this]() { return this->IsFullScreen(); }, this); +#endif // __APPLE__ } // Help menu diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 1dfd02c2f..e83a0014c 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -5,6 +5,7 @@ #include "ImGuiWrapper.hpp" #include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" +#include "../Utils/PrintHost.hpp" #include #include @@ -827,8 +828,6 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu //------PrintHostUploadNotification---------------- void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) { - if (m_uj_state == UploadJobState::PB_CANCELLED) - return; m_percentage = percent; if (percent >= 1.0f) { m_uj_state = UploadJobState::PB_COMPLETED; @@ -914,11 +913,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - assert(m_evt_handler != nullptr); - if (m_evt_handler != nullptr) { - auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); - wxQueueEvent(m_evt_handler, evt); - } + wxGetApp().printhost_job_queue().cancel(m_job_id - 1); } //invisible large button @@ -926,11 +921,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu ImGui::SetCursorPosY(0); if (imgui.button(" ", m_line_height * 2.f, win_size.y)) { - assert(m_evt_handler != nullptr); - if (m_evt_handler != nullptr) { - auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); - wxQueueEvent(m_evt_handler, evt); - } + wxGetApp().printhost_job_queue().cancel(m_job_id - 1); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); @@ -1023,7 +1014,7 @@ void NotificationManager::push_plater_warning_notification(const std::string& te auto notification = std::make_unique(data, m_id_provider, m_evt_handler); push_notification_data(std::move(notification), 0); // dissaper if in preview - set_in_preview(m_in_preview); + apply_in_preview(); } void NotificationManager::close_plater_warning_notification(const std::string& text) @@ -1129,28 +1120,21 @@ void NotificationManager::push_exporting_finished_notification(const std::string push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); } -void NotificationManager::push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage) +void NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage) { std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 0, text }; - push_notification_data(std::make_unique(data, m_id_provider, evt_handler, 0, id, filesize), 0); + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); } void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) { std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); -// bool found = false; for (std::unique_ptr& notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { + if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { dynamic_cast(notification.get())->set_percentage(percentage); wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); -// found = true; } } - /* - if (!found) { - push_upload_job_notification(id, filename, host, percentage); - } - */ } void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 651deace8..4f3900aeb 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -147,7 +147,7 @@ public: // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); // notification with progress bar - void push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); + void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); @@ -159,8 +159,10 @@ public: void render_notifications(GLCanvas3D& canvas, float overlay_width); // finds and closes all notifications of given type void close_notification_of_type(const NotificationType type); - // Which view is active? Plater or G-code preview? Hide warnings in G-code preview. + // Hides warnings in G-code preview. Should be called from plater only when 3d view/ preview is changed void set_in_preview(bool preview); + // Calls set_in_preview to apply appearing or disappearing of some notificatons; + void apply_in_preview() { set_in_preview(m_in_preview); } // Move to left to avoid colision with variable layer height gizmo. void set_move_from_overlay(bool move) { m_move_from_overlay = move; } // perform update_state on each notification and ask for more frames if needed, return true for render needed @@ -319,7 +321,7 @@ private: void set_large(bool l); bool get_large() { return m_is_large; } void set_print_info(const std::string &info); - virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) + virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) override { // This notification is always hidden if !large (means side bar is collapsed) if (!get_large() && !is_finished()) @@ -348,9 +350,9 @@ private: { public: PlaterWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} - virtual void close() { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } - void real_close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } - void show() { m_state = EState::Unknown; } + virtual void close() override { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void real_close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void show() { m_state = EState::Unknown; } }; @@ -365,10 +367,10 @@ private: virtual void count_spaces() override; virtual void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); + const float win_pos_x, const float win_pos_y) override; virtual void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); + const float win_pos_x, const float win_pos_y) ; virtual void render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) @@ -400,16 +402,16 @@ private: m_has_cancel_button = true; } static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return "[" + std::to_string(id) + "] " + filename + " -> " + host; } - virtual void set_percentage(float percent); + virtual void set_percentage(float percent) override; void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; } protected: virtual void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); + const float win_pos_x, const float win_pos_y) override; virtual void render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); + const float win_pos_x, const float win_pos_y) override; // Identifies job in cancel callback int m_job_id; // Size of uploaded size to be displayed in MB diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 8e8774036..c843da460 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -4,7 +4,8 @@ #include "GUI.hpp" #include "I18N.hpp" #include "3DScene.hpp" -#include "slic3r/Utils/Platform.hpp" + +#include "libslic3r/Platform.hpp" #include @@ -324,7 +325,7 @@ void OpenGLManager::detect_multisample(int* attribList) enable_multisample && // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, // at least on some platforms. - (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && + platform_flavor() != PlatformFlavor::LinuxOnChromium && wxGLCanvas::IsDisplaySupported(attribList) ? EMultisampleState::Enabled : EMultisampleState::Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 15e89168a..d7224cedc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -76,7 +76,6 @@ #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "../Utils/PresetUpdater.hpp" -#include "../Utils/Platform.hpp" #include "../Utils/Process.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" @@ -89,7 +88,9 @@ #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" + #include "libslic3r/CustomGCode.hpp" +#include "libslic3r/Platform.hpp" using boost::optional; namespace fs = boost::filesystem; @@ -3660,7 +3661,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) show_action_buttons(false); notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, // Don't offer the "Eject" button on ChromeOS, the Linux side has no control over it. - platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium); + platform_flavor() != PlatformFlavor::LinuxOnChromium); wxGetApp().removable_drive_manager()->set_exporting_finished(true); }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); @@ -4788,7 +4789,8 @@ void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } void Plater::reset() { p->reset(); } void Plater::reset_with_confirm() { - if (wxMessageDialog(static_cast(this), _L("All objects will be removed, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Delete all"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) + if (p->model.objects.empty() || + wxMessageDialog(static_cast(this), _L("All objects will be removed, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Delete all"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) reset(); } diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 0e5e10d45..181dcfda4 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -139,7 +139,6 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ? &first_layer_extrusion_width : nullptr; const float lh = float(first_layer ? first_layer_height : layer_height); - const float bfr = bridging ? bridge_flow_ratio : 0.f; double max_flow = 0.; std::string max_flow_extrusion_type; auto limit_by_first_layer_speed = [&first_layer_speed, first_layer](double speed_normal, double speed_max) { diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 598b72b94..f69db2ea3 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -224,8 +224,8 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) std::vector size; SetSize(load_user_data(UDT_SIZE, size) ? wxSize(size[0] * em, size[1] * em) : wxSize(HEIGHT * em, WIDTH * em)); - Bind(wxEVT_SIZE, [this, em](wxSizeEvent& evt) { - OnSize(evt); + Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) { + OnSize(evt); save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); }); @@ -233,7 +233,7 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) if (load_user_data(UDT_POSITION, pos)) SetPosition(wxPoint(pos[0], pos[1])); - Bind(wxEVT_MOVE, [this, em](wxMoveEvent& evt) { + Bind(wxEVT_MOVE, [this](wxMoveEvent& evt) { save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); }); @@ -245,7 +245,6 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) const JobState state = get_state(selected); if (state < ST_ERROR) { - // TODO: cancel GUI::wxGetApp().printhost_job_queue().cancel(selected); } }); @@ -282,7 +281,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) // Both strings are UTF-8 encoded. upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string()); - //wxGetApp().notification_manager()->push_upload_job_notification(this, job_list->GetItemCount(), 0, job.upload_data.upload_path.string(), job.printhost->get_host()); + wxGetApp().notification_manager()->push_upload_job_notification(job_list->GetItemCount(), (float)size_i / 1024 / 1024, job.upload_data.upload_path.string(), job.printhost->get_host()); } void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) @@ -354,7 +353,7 @@ void PrintHostQueueDialog::on_progress(Event &evt) wxVariant nm, hst; job_list->GetValue(nm, evt.job_id, COL_FILENAME); job_list->GetValue(hst, evt.job_id, COL_HOST); - wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()), 100 / evt.progress); + wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()), evt.progress / 100.f); } } @@ -409,7 +408,6 @@ void PrintHostQueueDialog::get_active_jobs(std::vectorGetSize().x / em << " " << this->GetSize().y / em << " " << this->GetPosition().x << " " << this->GetPosition().y; auto *app_config = wxGetApp().app_config; if (udt & UserDataType::UDT_SIZE) { diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index c200956e1..83f7077b4 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -1,5 +1,5 @@ #include "RemovableDriveManager.hpp" -#include "slic3r/Utils/Platform.hpp" +#include "libslic3r/Platform.hpp" #include #include @@ -186,8 +186,13 @@ namespace search_for_drives_internal { //confirms if the file is removable drive and adds it to vector - //if not same file system - could be removable drive - if (! compare_filesystem_id(path, parent_path)) { + if ( +#ifdef __linux__ + // Chromium mounts removable drives in a way that produces the same device ID. + platform_flavor() == PlatformFlavor::LinuxOnChromium || +#endif + // If not same file system - could be removable drive. + ! compare_filesystem_id(path, parent_path)) { //free space boost::system::error_code ec; boost::filesystem::space_info si = boost::filesystem::space(path, ec); @@ -232,7 +237,7 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons #else - if (platform() == Platform::Linux && platform_flavor() == PlatformFlavor::LinuxOnChromium) { + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { // ChromeOS specific: search /mnt/chromeos/removable/* folder search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); } else { @@ -452,7 +457,7 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() tbb::mutex::scoped_lock lock(m_drives_mutex); out.has_eject = // Cannot control eject on Chromium. - (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && + platform_flavor() != PlatformFlavor::LinuxOnChromium && this->find_last_save_path_drive_data() != m_current_drives.end(); out.has_removable_drives = ! m_current_drives.empty(); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6f7a21fcc..c68de4984 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1443,7 +1443,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Fuzzy skin (experimental)")); Option option = optgroup->get_option("fuzzy_skin"); - option.opt.width = 30; +// option.opt.width = 30; optgroup->append_single_option_line(option); optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_thickness")); optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_point_dist")); @@ -3841,7 +3841,7 @@ bool Tab::validate_custom_gcodes() assert(opt_group->opt_map().size() == 1); std::string key = opt_group->opt_map().begin()->first; std::string value = boost::any_cast(opt_group->get_value(key)); - std::string config_value = m_config->opt_string(key); + std::string config_value = m_type == Preset::TYPE_FILAMENT ? m_config->opt_string(key, 0u) : m_config->opt_string(key); valid &= validate_custom_gcode(opt_group->title, value); Field* field = opt_group->get_field(key); TextCtrl* text_ctrl = dynamic_cast(field);