Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/libslic3r/LayerRegion.cpp')
-rw-r--r--src/libslic3r/LayerRegion.cpp443
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