#include "Layer.hpp" #include "BridgeDetector.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "PerimeterGenerator.hpp" #include "Print.hpp" #include "Surface.hpp" #include "BoundingBox.hpp" #include "SVG.hpp" #include #include #include namespace Slic3r { Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const { return this->_region->flow( role, this->_layer->height, bridge, this->_layer->id() == 0, width, *this->_layer->object() ); } // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. void LayerRegion::slices_to_fill_surfaces_clipped() { // Note: this method should be idempotent, but fill_surfaces gets modified // in place. However we're now only using its boundaries (which are invariant) // so we're safe. This guarantees idempotence of prepare_infill() also in case // that combine_infill() turns some fill_surface into VOID surfaces. // Polygons fill_boundaries = to_polygons(STDMOVE(this->fill_surfaces)); Polygons fill_boundaries = to_polygons(this->fill_expolygons); // Collect polygons per surface type. std::vector polygons_by_surface; polygons_by_surface.assign(size_t(stCount), Polygons()); for (Surface &surface : this->slices.surfaces) polygons_append(polygons_by_surface[(size_t)surface.surface_type], surface.expolygon); // Trim surfaces by the fill_boundaries. this->fill_surfaces.surfaces.clear(); for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { const Polygons &polygons = polygons_by_surface[surface_type]; if (! polygons.empty()) this->fill_surfaces.append(intersection_ex(polygons, fill_boundaries), SurfaceType(surface_type)); } } void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) { this->perimeters.clear(); this->thin_fills.clear(); PerimeterGenerator g( // input: &slices, this->layer()->height, this->flow(frPerimeter), &this->region()->config, &this->layer()->object()->config, &this->layer()->object()->print()->config, // output: &this->perimeters, &this->thin_fills, fill_surfaces ); if (this->layer()->lower_layer != NULL) // Cummulative sum of polygons over all the regions. g.lower_slices = &this->layer()->lower_layer->slices; g.layer_id = this->layer()->id(); g.ext_perimeter_flow = this->flow(frExternalPerimeter); g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object()); g.solid_infill_flow = this->flow(frSolidInfill); g.process(); } //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. void LayerRegion::process_external_surfaces(const Layer* lower_layer) { const Surfaces &surfaces = this->fill_surfaces.surfaces; const double margin = scale_(EXTERNAL_INFILL_MARGIN); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset // for better anchoring. // Bottom surfaces, grown. Surfaces bottom; // Bridge surfaces, initialy not grown. Surfaces bridges; // Top surfaces, grown. Surfaces top; // Internal surfaces, not grown. Surfaces internal; // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. //FIXME if non zero infill, then fill_boundaries could be cheaply initialized from layerm->fill_expolygons. Polygons fill_boundaries; // Collect top surfaces and internal surfaces. // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! { // bottom_polygons are used to trim inflated top surfaces. fill_boundaries.reserve(number_polygons(surfaces)); bool has_infill = this->region()->config.fill_density.value > 0.; for (const Surface &surface : this->fill_surfaces.surfaces) { if (surface.surface_type == stTop) { // Collect the top surfaces, inflate them and trim them by the bottom surfaces. // This gives the priority to bottom surfaces. surfaces_append(top, offset_ex(surface.expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == NULL)) { // Grown by 3mm. surfaces_append(bottom, offset_ex(surface.expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); } else if (surface.surface_type == stBottomBridge) { if (! surface.empty()) bridges.push_back(surface); } bool internal_surface = surface.surface_type != stTop && ! surface.is_bottom(); if (has_infill || surface.surface_type != stInternal) { if (internal_surface) // Make a copy as the following line uses the move semantics. internal.push_back(surface); polygons_append(fill_boundaries, STDMOVE(surface.expolygon)); } else if (internal_surface) internal.push_back(STDMOVE(surface)); } } #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-before-grouping-%d.svg", iRun ++), true); } #endif if (bridges.empty()) { fill_boundaries = union_(fill_boundaries, true); } else { // 1) Calculate the inflated bridge regions, each constrained to its island. ExPolygons fill_boundaries_ex = union_ex(fill_boundaries, true); std::vector bridges_grown; std::vector bridge_bboxes; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRun = 0; SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); svg.draw(fill_boundaries_ex); svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); svg.Close(); } // export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ { // Bridge expolygons, grown, to be tested for intersection with other bridge regions. std::vector fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex); bridges_grown.reserve(bridges.size()); bridge_bboxes.reserve(bridges.size()); for (size_t i = 0; i < bridges.size(); ++ i) { // Find the island of this bridge. const Point pt = bridges[i].expolygon.contour.points.front(); int idx_island = -1; for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j) if (fill_boundaries_ex_bboxes[j].contains(pt) && fill_boundaries_ex[j].contains(pt)) { idx_island = j; break; } // Grown by 3mm. Polygons polys = offset(to_polygons(bridges[i].expolygon), float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS); if (idx_island == -1) { printf("Bridge did not fall into the source region!\r\n"); } else { // Found an island, to which this bridge region belongs. Trim it, polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island])); } bridge_bboxes.push_back(get_extents(polys)); bridges_grown.push_back(STDMOVE(polys)); } } // 2) Group the bridge surfaces by overlaps. std::vector bridge_group(bridges.size(), (size_t)-1); size_t n_groups = 0; for (size_t i = 0; i < bridges.size(); ++ i) { // A grup id for this bridge. size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; bridge_group[i] = group_id; // For all possibly overlaping bridges: for (size_t j = i + 1; j < bridges.size(); ++ j) { if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) continue; if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) continue; // The two bridge regions intersect. Give them the same group id. if (bridge_group[j] != -1) { // The j'th bridge has been merged with some other bridge before. size_t group_id_new = bridge_group[j]; for (size_t k = 0; k < j; ++ k) if (bridge_group[k] == group_id) bridge_group[k] = group_id_new; group_id = group_id_new; } bridge_group[j] = group_id; } } // 3) Merge the groups with the same group id, detect bridges. { BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups; for (size_t group_id = 0; group_id < n_groups; ++ group_id) { size_t n_bridges_merged = 0; size_t idx_last = (size_t)-1; for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] == group_id) { ++ n_bridges_merged; idx_last = i; } } if (n_bridges_merged == 0) // This group has no regions assigned as these were moved into another group. continue; // Collect the initial ungrown regions and the grown polygons. ExPolygons initial; Polygons grown; for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] != group_id) continue; initial.push_back(STDMOVE(bridges[i].expolygon)); polygons_append(grown, bridges_grown[i]); } // detect bridge direction before merging grown surfaces otherwise adjacent bridges // would get merged into a single one while they need different directions // also, supply the original expolygon instead of the grown one, because in case // of very thin (but still working) anchors, the grown expolygon would go beyond them BridgeDetector bd( initial, lower_layer->slices, this->flow(frInfill, true).scaled_width() ); #ifdef SLIC3R_DEBUG printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); #endif if (bd.detect_angle(Geometry::deg2rad(this->region()->config.bridge_angle.value))) { bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->config.support_material) { polygons_append(this->bridged, bd.coverage()); this->unsupported_bridge_edges.append(bd.unsupported_edges()); } } // without safety offset, artifacts are generated (GH #2494) surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); } fill_boundaries = STDMOVE(to_polygons(fill_boundaries_ex)); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; } #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); } #endif } Surfaces new_surfaces; { // Merge top and bottom in a single collection. surfaces_append(top, STDMOVE(bottom)); // Intersect the grown surfaces with the actual fill boundaries. Polygons bottom_polygons = to_polygons(bottom); for (size_t i = 0; i < top.size(); ++ i) { Surface &s1 = top[i]; if (s1.empty()) continue; Polygons polys; polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < top.size(); ++ j) { Surface &s2 = top[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); s2.clear(); } } if (s1.surface_type == stTop) // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. polys = diff(polys, bottom_polygons); surfaces_append( new_surfaces, // Don't use a safety offset as fill_boundaries were already united using the safety offset. STDMOVE(intersection_ex(polys, fill_boundaries, false)), s1); } } // Subtract the new top surfaces from the other non-top surfaces and re-add them. Polygons new_polygons = to_polygons(new_surfaces); for (size_t i = 0; i < internal.size(); ++ i) { Surface &s1 = internal[i]; if (s1.empty()) continue; Polygons polys; polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < internal.size(); ++ j) { Surface &s2 = internal[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); s2.clear(); } } ExPolygons new_expolys = diff_ex(polys, new_polygons); polygons_append(new_polygons, to_polygons(new_expolys)); surfaces_append(new_surfaces, STDMOVE(new_expolys), s1); } this->fill_surfaces.surfaces = STDMOVE(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } void LayerRegion::prepare_fill_surfaces() { #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ /* Note: in order to make the psPrepareInfill step idempotent, we should never alter fill_surfaces boundaries on which our idempotency relies since that's the only meaningful information returned by psPerimeters. */ // if no solid layers are requested, turn top/bottom surfaces to internal if (this->region()->config.top_solid_layers == 0) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) if (surface->surface_type == stTop) surface->surface_type = (this->layer()->object()->config.infill_only_where_needed) ? stInternalVoid : stInternal; } if (this->region()->config.bottom_solid_layers == 0) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge) surface->surface_type = stInternal; } } // turn too small internal regions into solid regions according to the user setting if (this->region()->config.fill_density.value > 0) { // scaling an area requires two calls! double min_area = scale_(scale_(this->region()->config.solid_infill_below_area.value)); for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type == stInternal && surface->area() <= min_area) surface->surface_type = stInternalSolid; } } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final"); export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } double LayerRegion::infill_area_threshold() const { double ss = this->flow(frSolidInfill).scaled_spacing(); return ss*ss; } void LayerRegion::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type)); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void LayerRegion::export_region_slices_to_svg_debug(const char *name) const { static std::map idx_map; size_t &idx = idx_map[name]; this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx ++).c_str()); } void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (const Surface &surface : this->fill_surfaces.surfaces) { svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); } export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) const { static std::map idx_map; size_t &idx = idx_map[name]; this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx ++).c_str()); } }