diff options
Diffstat (limited to 'src/libslic3r/LayerRegion.cpp')
-rw-r--r-- | src/libslic3r/LayerRegion.cpp | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp new file mode 100644 index 000000000..e0f97703c --- /dev/null +++ b/src/libslic3r/LayerRegion.cpp @@ -0,0 +1,443 @@ +#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 <string> +#include <map> + +#include <boost/log/trivial.hpp> + +namespace Slic3r { + +Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const +{ + return m_region->flow( + role, + m_layer->height, + bridge, + m_layer->id() == 0, + width, + *m_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> 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<Polygons> bridges_grown; + std::vector<BoundingBox> 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<BoundingBox> 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<size_t> 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(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + + 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<std::string, size_t> 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(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + + 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<std::string, size_t> 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()); +} + +} +
\ No newline at end of file |