diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index dc5fa39b8..5dcbb2f15 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -6,11 +6,11 @@ #include "SupportMaterial.hpp" #include "Fill/FillBase.hpp" #include "EdgeGrid.hpp" +#include "Geometry.hpp" #include #include #include -#include // #define SLIC3R_DEBUG @@ -261,14 +261,6 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) union_ex(layer_support_areas[layer_id], false)); #endif /* SLIC3R_DEBUG */ - BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; - - // Because the top and bottom contacts are thick slabs, they may overlap causing over extrusion - // and unwanted strong bonds to the object. - // Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding - // top contacts over the bottom contacts. - this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts); - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices"; // Allocate empty layers between the top / bottom support contact layers @@ -293,6 +285,14 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) union_ex((*it)->polygons, false)); #endif /* SLIC3R_DEBUG */ + BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; + + // Because the top and bottom contacts are thick slabs, they may overlap causing over extrusion + // and unwanted strong bonds to the object. + // Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding + // top contacts over the bottom contacts. + this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts); + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. @@ -376,7 +376,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) i = j; ++ layer_id; } - + BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; // Generate the actual toolpaths and save them into each layer. @@ -951,17 +951,13 @@ void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( for (size_t idx_bottom = 0; idx_bottom < bottom_contacts.size() && idx_top_first < top_contacts.size(); ++ idx_bottom) { const MyLayer &layer_bottom = *bottom_contacts[idx_bottom]; // Find the first top layer overlapping with layer_bottom. - while (idx_top_first < top_contacts.size() && top_contacts[idx_top_first]->print_z <= layer_bottom.print_z - layer_bottom.height) + while (idx_top_first < top_contacts.size() && top_contacts[idx_top_first]->bottom_z < layer_bottom.bottom_print_z() - EPSILON) ++ idx_top_first; // For all top contact layers overlapping with the thick bottom contact layer: for (size_t idx_top = idx_top_first; idx_top < top_contacts.size(); ++ idx_top) { MyLayer &layer_top = *top_contacts[idx_top]; - coordf_t interface_z = (layer_top.print_z == layer_top.bottom_z) ? - // Layer height has not been decided yet. - (layer_top.bottom_z - m_support_layer_height_min) : - // Layer height has already been assigned. - (layer_top.bottom_z + EPSILON); - if (interface_z < layer_bottom.print_z) { + assert(layer_top.bottom_z >= layer_bottom.bottom_print_z() - EPSILON); + if (layer_top.print_z < layer_bottom.print_z + EPSILON) { // Layers overlap. Trim layer_top with layer_bottom. layer_top.polygons = diff(layer_top.polygons, layer_bottom.polygons); } else @@ -1150,14 +1146,20 @@ void PrintObjectSupportMaterial::generate_base_layers( Polygons polygons_new; // Use the precomputed layer_support_areas. - while (idx_object_layer_above > 0 && object.get_layer(idx_object_layer_above - 1)->print_z > layer_intermediate.print_z - EPSILON) + while (idx_object_layer_above > 0 && object.layers[idx_object_layer_above]->print_z > layer_intermediate.print_z - EPSILON) -- idx_object_layer_above; polygons_new = layer_support_areas[idx_object_layer_above]; // Polygons to trim polygons_new. Polygons polygons_trimming; - // Find the first top_contact layer intersecting with this layer. + // Trimming the base layer with any overlapping top layer. + // Following cases are recognized: + // 1) top.bottom_z >= base.top_z -> No overlap, no trimming needed. + // 2) base.bottom_z >= top.print_z -> No overlap, no trimming needed. + // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here. + // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. + // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. int idx_top_contact_overlapping = idx_top_contact_above; while (idx_top_contact_overlapping >= 0 && top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) @@ -1167,19 +1169,33 @@ void PrintObjectSupportMaterial::generate_base_layers( MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; - polygons_append(polygons_trimming, layer_top_overlapping.polygons); + // Base must not overlap with top.bottom_z. + assert(! (layer_intermediate.print_z > layer_top_overlapping.bottom_z + EPSILON && layer_intermediate.bottom_z < layer_top_overlapping.bottom_z - EPSILON)); + if (layer_intermediate.print_z <= layer_top_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_top_overlapping.bottom_z - EPSILON) + // Base is fully inside top. Trim base by top. + polygons_append(polygons_trimming, layer_top_overlapping.polygons); } - // Find the first bottom_contact layer intersecting with this layer. + // Trimming the base layer with any overlapping bottom layer. + // Following cases are recognized: + // 1) bottom.bottom_z >= base.top_z -> No overlap, no trimming needed. + // 2) base.bottom_z >= bottom.print_z -> No overlap, no trimming needed. + // 3) base.print_z > bottom.bottom_z && base.bottom_z < bottom.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the bottom layer height where it overlaps the base layer. No trimming needed here. + // 4) base.print_z > bottom.print_z && base.bottom_z >= bottom.print_z -> Base overlaps with bottom.print_z. This must not happen. + // 5) base.print_z <= bottom.print_z && base.bottom_z >= bottom.bottom_z -> Base is fully inside top. Trim base by top. while (idx_bottom_contact_overlapping >= 0 && - bottom_contacts[idx_bottom_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) + bottom_contacts[idx_bottom_contact_overlapping]->bottom_print_z() > layer_intermediate.print_z - EPSILON) -- idx_bottom_contact_overlapping; - // Collect all the top_contact layer intersecting with this layer. + // Collect all the bottom_contacts layer intersecting with this layer. for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) { MyLayer &layer_bottom_overlapping = *bottom_contacts[i]; - if (layer_bottom_overlapping.print_z < layer_intermediate.print_z - layer_intermediate.height + EPSILON) + if (layer_bottom_overlapping.print_z < layer_intermediate.bottom_print_z() + EPSILON) break; - polygons_append(polygons_trimming, layer_bottom_overlapping.polygons); + // Base must not overlap with bottom.top_z. + assert(! (layer_intermediate.print_z > layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z < layer_bottom_overlapping.print_z - EPSILON)); + if (layer_intermediate.print_z <= layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_bottom_overlapping.bottom_print_z() - EPSILON) + // Base is fully inside bottom. Trim base by bottom. + polygons_append(polygons_trimming, layer_bottom_overlapping.polygons); } #ifdef SLIC3R_DEBUG @@ -1470,12 +1486,22 @@ static inline void fill_expolygons_generate_paths( // Support layers, partially processed. struct MyLayerExtruded { - MyLayerExtruded() : layer(nullptr) {} + MyLayerExtruded() : layer(nullptr), m_polygons_to_extrude(nullptr) {} + ~MyLayerExtruded() { delete m_polygons_to_extrude; m_polygons_to_extrude = nullptr; } bool empty() const { return layer == nullptr || layer->polygons.empty(); } + void set_polygons_to_extrude(Polygons &&polygons) { + if (m_polygons_to_extrude == nullptr) + m_polygons_to_extrude = new Polygons(std::move(polygons)); + else + *m_polygons_to_extrude = std::move(polygons); + } + Polygons& polygons_to_extrude() { return (this->m_polygons_to_extrude == nullptr) ? layer->polygons : *this->m_polygons_to_extrude; } + const Polygons& polygons_to_extrude() const { return (this->m_polygons_to_extrude == nullptr) ? layer->polygons : *this->m_polygons_to_extrude; } + bool could_merge(const MyLayerExtruded &other) const { return ! this->empty() && ! other.empty() && std::abs(this->layer->height - other.layer->height) < EPSILON && @@ -1484,9 +1510,31 @@ struct MyLayerExtruded // Merge regions, perform boolean union over the merged polygons. void merge(MyLayerExtruded &&other) { - assert(could_merge(other)); - Slic3r::polygons_append(layer->polygons, std::move(other.layer->polygons)); - layer->polygons = union_(layer->polygons, true); + assert(this->could_merge(other)); + // 1) Merge the rest polygons to extrude, if there are any. + if (other.m_polygons_to_extrude != nullptr) { + if (this->m_polygons_to_extrude == nullptr) { + // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(this->extrusions.empty()); + this->m_polygons_to_extrude = new Polygons(this->layer->polygons); + } + Slic3r::polygons_append(*this->m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); + *this->m_polygons_to_extrude = union_(*this->m_polygons_to_extrude, true); + delete other.m_polygons_to_extrude; + other.m_polygons_to_extrude = nullptr; + } else if (this->m_polygons_to_extrude != nullptr) { + assert(other.m_polygons_to_extrude == nullptr); + // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(other.extrusions.empty()); + Slic3r::polygons_append(*this->m_polygons_to_extrude, other.layer->polygons); + *this->m_polygons_to_extrude = union_(*this->m_polygons_to_extrude, true); + } + // 2) Merge the extrusions. + this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); + other.extrusions.clear(); + // 3) Merge the infill polygons. + Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); + this->layer->polygons = union_(this->layer->polygons, true); other.layer->polygons.clear(); } @@ -1499,6 +1547,9 @@ struct MyLayerExtruded PrintObjectSupportMaterial::MyLayer *layer; // Collect extrusions. They will be exported sorted by the bottom height. ExtrusionEntitiesPtr extrusions; + // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. + // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. + Polygons *m_polygons_to_extrude; }; typedef std::vector MyLayerExtrudedPtrs; @@ -1537,33 +1588,116 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const flow.height = float(top_contact_layer.layer->height); Polygons overhang_polygons; - // if (top_contact_layer.layer->aux_polygons != nullptr) + if (top_contact_layer.layer->overhang_polygons != nullptr) overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); // Generate the outermost loop. // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - Polygons top_contact_polygons = offset(top_contact_layer.layer->polygons, - 0.5f * flow.scaled_width()); + ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); + + // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. + coord_t circle_grid_resolution = 1; + coord_t circle_grid_powerof2 = 0; + { + // epsilon to account for rounding errors + coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); + while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { + circle_grid_resolution <<= 1; + ++ circle_grid_powerof2; + } + } + + struct PointAccessor { + const Point* operator()(const Point &pt) const { return &pt; } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; Polygons loops0; { // find centerline of the external loop of the contours - // only consider the loops facing the overhang + // Only consider the loops facing the overhang. Polygons external_loops; - // Positions of the loop centers. + // Holes in the external loops. Polygons circles; - Polygons overhang_with_margin = offset(overhang_polygons, 0.5f * flow.scaled_width()); - for (Polygons::const_iterator it_contact = top_contact_polygons.begin(); it_contact != top_contact_polygons.end(); ++ it_contact) - if (! intersection_pl(it_contact->split_at_first_point(), overhang_with_margin).empty()) { - external_loops.push_back(*it_contact); - Points positions_new = it_contact->equally_spaced_points(circle_distance); - for (Points::const_iterator it_center = positions_new.begin(); it_center != positions_new.end(); ++ it_center) { - circles.push_back(circle); - Polygon &circle_new = circles.back(); - for (size_t i = 0; i < circle_new.points.size(); ++ i) - circle_new.points[i].translate(*it_center); + Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); + for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { + // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. + ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); + Points circle_centers; + Point center_last; + // For each contour of the expolygon, start with the outer contour, continue with the holes. + for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { + Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; + const Point *seg_current_pt = nullptr; + coordf_t seg_current_t = 0.; + if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { + // The contour is below the overhang at least to some extent. + //FIXME ideally one would place the circles below the overhang only. + // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. + if (circle_centers.empty()) { + // Place the first circle. + seg_current_pt = &contour.points.front(); + seg_current_t = 0.; + center_last = *seg_current_pt; + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { + // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? + const Point &p1 = *(it-1); + const Point &p2 = *it; + // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. + const Pointf v_seg(coordf_t(p2.x) - coordf_t(p1.x), coordf_t(p2.y) - coordf_t(p1.y)); + const Pointf v_cntr(coordf_t(p1.x - center_last.x), coordf_t(p1.y - center_last.y)); + coordf_t a = dot(v_seg); + coordf_t b = 2. * dot(v_seg, v_cntr); + coordf_t c = dot(v_cntr) - circle_distance * circle_distance; + coordf_t disc = b * b - 4. * a * c; + if (disc > 0.) { + // The circle intersects a ray. Avoid the parts of the segment inside the circle. + coordf_t t1 = (-b - sqrt(disc)) / (2. * a); + coordf_t t2 = (-b + sqrt(disc)) / (2. * a); + coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; + // Take the lowest t in , excluding . + coordf_t t; + if (t0 <= t1) + t = t0; + else if (t2 <= 1.) + t = t2; + else { + // Try the following segment. + seg_current_pt = nullptr; + continue; + } + seg_current_pt = &p1; + seg_current_t = t; + center_last = Point(p1.x + coord_t(v_seg.x * t), p1.y + coord_t(v_seg.y * t)); + // It has been verified that the new point is far enough from center_last. + // Ensure, that it is far enough from all the centers. + std::pair circle_closest = circle_centers_lookup.find(center_last); + if (circle_closest.first != nullptr) { + -- it; + continue; + } + } else { + // All of the segment is outside the circle. Take the first point. + seg_current_pt = &p1; + seg_current_t = 0.; + center_last = p1; + } + // Place the first circle. + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + external_loops.push_back(std::move(contour)); + for (Points::const_iterator it_center = circle_centers.begin(); it_center != circle_centers.end(); ++ it_center) { + circles.push_back(circle); + circles.back().translate(*it_center); + } } } - // Apply a pattern to the loop. + } + // Apply a pattern to the external loops. loops0 = diff(external_loops, circles); } @@ -1577,11 +1711,62 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const loops0, - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); - // clip such loops to the side oriented towards the object + // Clip such loops to the side oriented towards the object. + // Collect split points, so they will be recognized after the clipping. + // At the split points the clipped pieces will be stitched back together. loop_lines.reserve(loop_polygons.size()); - for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) + std::unordered_map map_split_points; + for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { + assert(map_split_points.find(it->first_point()) == map_split_points.end()); + map_split_points[it->first_point()] = -1; loop_lines.push_back(it->split_at_first_point()); + } loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. + // Try to connect them. + for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { + Polyline &polyline = loop_lines[i_line]; + auto it = map_split_points.find(polyline.first_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + continue; + } + it = map_split_points.find(polyline.last_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + } + } + // Remove empty lines. + remove_degenerate(loop_lines); } // add the contact infill area to the interface area @@ -1589,7 +1774,9 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const // extrusions are left inside the circles; however it creates // a very large gap between loops and contact_infill_polygons, so maybe another // solution should be found to achieve both goals - top_contact_layer.layer->polygons = diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1))); + // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for + // "modulate by layer thickness". + top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); // Transform loops into ExtrusionPath objects. extrusion_entities_append_paths( @@ -1685,12 +1872,24 @@ void modulate_extrusion_by_overlapping_layers( // Fill extrusion, the source. for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { ExtrusionPath *path = dynamic_cast(*it); - svg.draw(path->polyline, "magenta", scale_(0.2)); + std::string color_name; + switch ((it - extrusions_in_out.begin()) % 9) { + case 0: color_name = "magenta"; break; + case 1: color_name = "deepskyblue"; break; + case 2: color_name = "coral"; break; + case 3: color_name = "goldenrod"; break; + case 4: color_name = "orange"; break; + case 5: color_name = "olivedrab"; break; + case 6: color_name = "blueviolet"; break; + case 7: color_name = "brown"; break; + default: color_name = "orchid"; break; + } + svg.draw(path->polyline, color_name, scale_(0.2)); } #endif /* SLIC3R_DEBUG */ // End points of the original paths. - std::vector> path_ends; + std::vector> path_ends; // Collect the paths of this_layer. { Polylines &polylines = path_fragments.back().polylines; @@ -1713,9 +1912,6 @@ void modulate_extrusion_by_overlapping_layers( Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), scale_(0.5*extrusion_width)); frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming, false); path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming, false); - // Clipper seems to reverse the polylines resulting from the difference operation. Revert the reversion. - for (auto it = path_fragments.back().polylines.begin(); it != path_fragments.back().polylines.end(); ++it) - std::reverse(it->points.begin(), it->points.end()); // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). assert(this_layer.print_z > overlapping_layer.print_z); frag.height = float(this_layer.print_z - overlapping_layer.print_z); @@ -1727,7 +1923,7 @@ void modulate_extrusion_by_overlapping_layers( #ifdef SLIC3R_DEBUG svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); - svg.Close(); + svg.Close(); #endif /* SLIC3R_DEBUG */ // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. @@ -1743,7 +1939,21 @@ void modulate_extrusion_by_overlapping_layers( size_t polyline_idx; bool is_start; }; - std::unordered_multimap map_fragment_starts; + class ExtrusionPathFragmentEndPointAccessor { + public: + ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} + // Return an end point of a fragment, or nullptr if the fragment has been consumed already. + const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { + const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; + return polyline.points.empty() ? nullptr : + (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); + } + private: + const std::vector &m_path_fragments; + }; + const coord_t search_radius = 7; + ClosestPointInRadiusLookup map_fragment_starts( + search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { @@ -1751,12 +1961,8 @@ void modulate_extrusion_by_overlapping_layers( if (polylines[i_polyline].points.size() >= 2) { const Point &pt_start = polylines[i_polyline].points.front(); const Point &pt_end = polylines[i_polyline].points.back(); - map_fragment_starts.emplace( - std::make_pair(Point(pt_start.x>>4, pt_start.y>>4), - ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true))); - map_fragment_starts.emplace( - std::make_pair(Point(pt_end.x>>4, pt_end.y>>4), - ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false))); + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); } } } @@ -1769,51 +1975,32 @@ void modulate_extrusion_by_overlapping_layers( // Find a chain of fragments with the original / reduced print height. ExtrusionMultiPath multipath; for (;;) { - // Iterate over 4 closest grid cells around pt_current, - // find the closest start point inside these cells to pt_current. - ExtrusionPathFragmentEnd fragment_end_min(size_t(-1), size_t(-1), false); - double dist_min = std::numeric_limits::max(); - // Round pt_current to a closest grid_cell corner. - Point grid_corner((pt_current.x+8)>>4, (pt_current.y+8)>>4); - // For four neighbors of grid_corner: - for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { - for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { - // Range of fragment starts around grid_corner, close to pt_current. - auto range = map_fragment_starts.equal_range(Point(grid_corner.x + neighbor_x, grid_corner.y + neighbor_y)); - // Find the fragment start closest to the current - for (auto it = range.first; it != range.second; ++it) { - const ExtrusionPathFragmentEnd &fragment_end = it->second; - const Polyline &polyline = path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; - if (polyline.points.empty()) - // This segment has been consumed. - continue; - const Point &pt = fragment_end.is_start ? polyline.points.front() : polyline.points.back(); - const double d2 = pt_current.distance_to_sq(pt); - if (d2 < dist_min) { - dist_min = d2; - fragment_end_min = fragment_end; - } - } - } - } - if (dist_min == std::numeric_limits::max()) { + // Find a closest end point to pt_current. + std::pair end_and_dist2 = map_fragment_starts.find(pt_current); + // There may be a bug in Clipper flipping the order of two last points in a fragment? + // assert(end_and_dist2.first != nullptr); + assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); + if (end_and_dist2.first == nullptr) { // New fragment connecting to pt_current was not found. // Verify that the last point found is close to the original end point of the unfragmented path. - const double d2 = pt_end.distance_to_sq(pt_current); - assert(d2 < 4 * 4); + //const double d2 = pt_end.distance_to_sq(pt_current); + //assert(d2 < coordf_t(search_radius * search_radius)); // End of the path. break; } + const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; // Fragment to consume. ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; // Path to append the fragment to. ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); if (path != nullptr) { - // Verify whether the path is compatible with the current fragment. It shall not be if the path was not split errorneously by the Clipper library. - assert(path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); - if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) - path = nullptr; + // Verify whether the path is compatible with the current fragment. + assert(this_layer.layer_type == PrintObjectSupportMaterial::sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); + if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { + path = nullptr; + } + // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. } if (path == nullptr) { // Allocate a new path. @@ -1825,6 +2012,7 @@ void modulate_extrusion_by_overlapping_layers( if (! fragment_end_min.is_start) frag_polyline.reverse(); // Enforce exact overlap of the end points of successive fragments. + assert(frag_polyline.points.front() == pt_current); frag_polyline.points.front() = pt_current; // Don't repeat the first point. if (! path->polyline.points.empty()) @@ -1849,6 +2037,10 @@ void modulate_extrusion_by_overlapping_layers( } } } + // If there are any non-consumed fragments, add them separately. + //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. + for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) + extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); } void PrintObjectSupportMaterial::generate_toolpaths( @@ -1862,16 +2054,17 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Slic3r::debugf "Generating patterns\n"; // loop_interface_processor with a given circle radius. LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); + loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; InfillPattern infill_pattern; std::vector angles; - angles.push_back(m_object_config->support_material_angle); + angles.push_back(Geometry::deg2rad(m_object_config->support_material_angle)); switch (support_pattern) { case smpRectilinearGrid: - angles.push_back(angles[0] + 90.); + angles.push_back(angles[0] + Geometry::deg2rad(90.)); // fall through case smpRectilinear: infill_pattern = ipRectilinear; @@ -1890,7 +2083,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( filler_support->set_bounding_box(bbox_object); } - coordf_t interface_angle = m_object_config->support_material_angle + 90.; + coordf_t interface_angle = Geometry::deg2rad(m_object_config->support_material_angle + 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(); @@ -1904,22 +2097,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // const coordf_t link_max_length_factor = 3.; const coordf_t link_max_length_factor = 0.; - //FIXME Parallelize the support generator: - /* - Slic3r::parallelize( - threads => $self->print_config->threads, - items => [ 0 .. n_$object.support_layers} ], - thread_cb => sub { - my $q = shift; - while (defined (my $layer_id = $q->dequeue)) { - $process_layer->($layer_id); - } - }, - no_threads_cb => sub { - $process_layer->($_) for 0 .. n_{$object.support_layers}; - }, - ); - */ + //FIXME Parallelize the support generator. // Insert the raft base layers. size_t support_layer_id = 0; for (; support_layer_id < size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); ++ support_layer_id) { @@ -1946,7 +2124,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( if (support_layer_id == 0) { // Base flange. filler = filler_interface.get(); - filler->angle = m_object_config->support_material_angle + 90.; + filler->angle = Geometry::deg2rad(m_object_config->support_material_angle + 90.); density = 0.5f; flow = m_first_layer_flow; // use the proper spacing for first layer as we don't need to align @@ -2019,15 +2197,6 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Sort the layers with the same print_z coordinate by their heights, thickest first. std::sort(mylayers.begin(), mylayers.end(), [](const MyLayerExtruded *p1, const MyLayerExtruded *p2) { return p1->layer->height > p2->layer->height; }); - /* { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("out\\layer_" . $z . ".svg", - blue_expolygons => union_ex($base), - red_expolygons => union_ex($contact), - green_expolygons => union_ex($interface), - ); - } */ - 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 (base_layer.could_merge(top_contact_layer)) @@ -2041,8 +2210,10 @@ void PrintObjectSupportMaterial::generate_toolpaths( } else { loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow); // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. - if (interface_layer.could_merge(top_contact_layer)) - interface_layer.merge(std::move(top_contact_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 (! interface_layer.empty() && ! base_layer.empty()) { @@ -2054,52 +2225,37 @@ void PrintObjectSupportMaterial::generate_toolpaths( base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); } - // interface and contact infill - if (! top_contact_layer.empty()) { + // Top and bottom contacts, interface layers. + for (size_t i = 0; i < 3; ++ i) { + MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? interface_layer : bottom_contact_layer); + if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) + continue; //FIXME When paralellizing, each thread shall have its own copy of the fillers. Flow interface_flow( - top_contact_layer.layer->bridging ? top_contact_layer.layer->height : m_support_material_interface_flow.width, - top_contact_layer.layer->height, + layer_ex.layer->bridging ? layer_ex.layer->height : m_support_material_interface_flow.width, + layer_ex.layer->height, m_support_material_interface_flow.nozzle_diameter, - top_contact_layer.layer->bridging); - filler_interface->angle = interface_angle; + layer_ex.layer->bridging); + filler_interface->angle = (i == 2 && m_object_config->support_material_interface_layers.value == 0) ? + // 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 = scale_(filler_interface->spacing * link_max_length_factor / interface_density); fill_expolygons_generate_paths( // Destination - top_contact_layer.extrusions, + layer_ex.extrusions, // Regions to fill - union_ex(top_contact_layer.layer->polygons, true), + union_ex(layer_ex.polygons_to_extrude(), true), // Filler and its parameters filler_interface.get(), interface_density, // Extrusion parameters erSupportMaterialInterface, interface_flow); } - // interface and contact infill - if (! interface_layer.empty()) { - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - Flow interface_flow( - interface_layer.layer->bridging ? interface_layer.layer->height : m_support_material_interface_flow.width, - interface_layer.layer->height, - m_support_material_interface_flow.nozzle_diameter, - interface_layer.layer->bridging); - filler_interface->angle = interface_angle; - filler_interface->spacing = m_support_material_interface_flow.spacing(); - filler_interface->link_max_length = scale_(filler_interface->spacing * link_max_length_factor / interface_density); - fill_expolygons_generate_paths( - // Destination - interface_layer.extrusions, - // Regions to fill - union_ex(interface_layer.layer->polygons, true), - // Filler and its parameters - filler_interface.get(), interface_density, - // Extrusion parameters - erSupportMaterialInterface, interface_flow); - } - - // support or flange - if (! base_layer.empty()) { + // Base support or flange. + if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { //FIXME When paralellizing, each thread shall have its own copy of the fillers. Fill *filler = filler_support.get(); filler->angle = angles[support_layer_id % angles.size()]; @@ -2112,20 +2268,12 @@ void PrintObjectSupportMaterial::generate_toolpaths( // find centerline of the external loop/extrusions ExPolygons to_infill = (support_layer_id == 0 || ! with_sheath) ? // union_ex(base_polygons, true) : - offset2_ex(base_layer.layer->polygons, SCALED_EPSILON, - SCALED_EPSILON) : - offset2_ex(base_layer.layer->polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width()); - /* { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("out\\to_infill_base" . $z . ".svg", - red_expolygons => union_ex($contact), - green_expolygons => union_ex($interface), - blue_expolygons => $to_infill, - ); - } */ + offset2_ex(base_layer.polygons_to_extrude(), SCALED_EPSILON, - SCALED_EPSILON) : + offset2_ex(base_layer.polygons_to_extrude(), SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width()); if (base_layer.layer->bottom_z < EPSILON) { // Base flange. filler = filler_interface.get(); - filler->angle = m_object_config->support_material_angle + 90.; + filler->angle = Geometry::deg2rad(m_object_config->support_material_angle + 90.); density = 0.5f; flow = m_first_layer_flow; // use the proper spacing for first layer as we don't need to align @@ -2155,32 +2303,6 @@ void PrintObjectSupportMaterial::generate_toolpaths( erSupportMaterial, flow); } - // support or flange - if (! bottom_contact_layer.empty()) { - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - Flow interface_flow( - bottom_contact_layer.layer->bridging ? bottom_contact_layer.layer->height : m_support_material_interface_flow.width, - bottom_contact_layer.layer->height, - m_support_material_interface_flow.nozzle_diameter, - bottom_contact_layer.layer->bridging); - filler_interface->angle = (m_object_config->support_material_interface_layers.value == 0) ? - // 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 = scale_(filler_interface->spacing * link_max_length_factor / interface_density); - fill_expolygons_generate_paths( - // Destination - bottom_contact_layer.extrusions, - // Regions to fill - union_ex(bottom_contact_layer.layer->polygons, true), - // Filler and its parameters - filler_interface.get(), interface_density, - // Extrusion parameters - erSupportMaterial, interface_flow); - } - // Collect the support areas with this print_z into islands, as there is no need // for retraction over these islands. Polygons polys; @@ -2200,15 +2322,17 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Collect overlapping top/bottom surfaces. MyLayersPtr overlapping; overlapping.reserve(16); - for (int i = int(idx_layer_bottom_contact) - 1; i >= 0; -- i) { - if (bottom_contacts[i]->print_z < layer.layer->print_z - layer.layer->height + EPSILON) - break; + coordf_t bottom_z = layer.layer->bottom_print_z() + EPSILON; + for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i) overlapping.push_back(bottom_contacts[i]); - } - for (int i = int(idx_layer_top_contact) - 1; i >= 0; -- i) { - if (top_contacts[i]->print_z < layer.layer->print_z - layer.layer->height + EPSILON) - break; + for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i) overlapping.push_back(top_contacts[i]); + if (layer.layer->layer_type == sltBottomContact) { + // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. + for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i) + overlapping.push_back(intermediate_layers[i]); + for (int i = int(idx_layer_inteface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i) + overlapping.push_back(interface_layers[i]); } std::sort(overlapping.begin(), overlapping.end(), MyLayersPtrCompare()); modulate_extrusion_by_overlapping_layers(layer.extrusions, *layer.layer, overlapping); diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp index a436b1da5..13b2b5e50 100644 --- a/xs/src/libslic3r/SupportMaterial.hpp +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -83,13 +83,16 @@ public: if (height > layer2.height) return true; else if (height == layer2.height) { - return bridging < layer2.bridging; + // Bridging layers first. + return bridging && ! layer2.bridging; } else return false; } else return false; } + coordf_t bottom_print_z() const { return print_z - height; } + SupporLayerType layer_type; // Z used for printing, in unscaled coordinates. coordf_t print_z;