diff --git a/resources/images/param_fiberspiral.svg b/resources/images/param_fiberspiral.svg
new file mode 100644
index 000000000..4328ea5b4
--- /dev/null
+++ b/resources/images/param_fiberspiral.svg
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
new file mode 100644
index 000000000..7b4f6a83f
--- /dev/null
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -0,0 +1,843 @@
+#include
+#include
+#include
+
+#include "../ClipperUtils.hpp"
+#include "../Geometry.hpp"
+#include "../Layer.hpp"
+#include "../Print.hpp"
+#include "../PrintConfig.hpp"
+#include "../Surface.hpp"
+
+#include "FillBase.hpp"
+#include "FillRectilinear.hpp"
+#include "FillLightning.hpp"
+#include "FillConcentricInternal.hpp"
+#include "FillConcentric.hpp"
+
+#define NARROW_INFILL_AREA_THRESHOLD 3
+
+namespace Slic3r {
+
+struct SurfaceFillParams
+{
+ // Zero based extruder ID.
+ unsigned int extruder = 0;
+ // Infill pattern, adjusted for the density etc.
+ InfillPattern pattern = InfillPattern(0);
+
+ // FillBase
+ // in unscaled coordinates
+ coordf_t spacing = 0.;
+ // infill / perimeter overlap, in unscaled coordinates
+ coordf_t overlap = 0.;
+ // Angle as provided by the region config, in radians.
+ float angle = 0.f;
+ // Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set.
+ bool bridge;
+ // Non-negative for a bridge.
+ float bridge_angle = 0.f;
+
+ // FillParams
+ float density = 0.f;
+ // Don't adjust spacing to fill the space evenly.
+// bool dont_adjust = false;
+ // Length of the infill anchor along the perimeter line.
+ // 1000mm is roughly the maximum length line that fits into a 32bit coord_t.
+ float anchor_length = 1000.f;
+ float anchor_length_max = 1000.f;
+ //BBS
+ // width, height of extrusion, nozzle diameter, is bridge
+ // For the output, for fill generator.
+ Flow flow;
+
+ // For the output
+ ExtrusionRole extrusion_role = ExtrusionRole(0);
+
+ // Various print settings?
+
+ // Index of this entry in a linear vector.
+ size_t idx = 0;
+ // infill speed settings
+ float sparse_infill_speed = 0;
+ float top_surface_speed = 0;
+ float solid_infill_speed = 0;
+
+ bool operator<(const SurfaceFillParams &rhs) const {
+#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
+#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
+
+ // Sort first by decreasing bridging angle, so that the bridges are processed with priority when trimming one layer by the other.
+ if (this->bridge_angle > rhs.bridge_angle) return true;
+ if (this->bridge_angle < rhs.bridge_angle) return false;
+
+ RETURN_COMPARE_NON_EQUAL(extruder);
+ RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, pattern);
+ RETURN_COMPARE_NON_EQUAL(spacing);
+ RETURN_COMPARE_NON_EQUAL(overlap);
+ RETURN_COMPARE_NON_EQUAL(angle);
+ RETURN_COMPARE_NON_EQUAL(density);
+// RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
+ RETURN_COMPARE_NON_EQUAL(anchor_length);
+ RETURN_COMPARE_NON_EQUAL(anchor_length_max);
+ RETURN_COMPARE_NON_EQUAL(flow.width());
+ RETURN_COMPARE_NON_EQUAL(flow.height());
+ RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
+ RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge);
+ RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
+ RETURN_COMPARE_NON_EQUAL(sparse_infill_speed);
+ RETURN_COMPARE_NON_EQUAL(top_surface_speed);
+ RETURN_COMPARE_NON_EQUAL(solid_infill_speed);
+
+ return false;
+ }
+
+ bool operator==(const SurfaceFillParams &rhs) const {
+ return this->extruder == rhs.extruder &&
+ this->pattern == rhs.pattern &&
+ this->spacing == rhs.spacing &&
+ this->overlap == rhs.overlap &&
+ this->angle == rhs.angle &&
+ this->bridge == rhs.bridge &&
+// this->bridge_angle == rhs.bridge_angle &&
+ this->density == rhs.density &&
+// this->dont_adjust == rhs.dont_adjust &&
+ this->anchor_length == rhs.anchor_length &&
+ this->anchor_length_max == rhs.anchor_length_max &&
+ this->flow == rhs.flow &&
+ this->extrusion_role == rhs.extrusion_role &&
+ this->sparse_infill_speed == rhs.sparse_infill_speed &&
+ this->top_surface_speed == rhs.top_surface_speed &&
+ this->solid_infill_speed == rhs.solid_infill_speed;
+ }
+};
+
+struct SurfaceFill {
+ SurfaceFill(const SurfaceFillParams& params) : region_id(size_t(-1)), surface(stCount, ExPolygon()), params(params) {}
+
+ size_t region_id;
+ Surface surface;
+ ExPolygons expolygons;
+ SurfaceFillParams params;
+ // BBS
+ std::vector region_id_group;
+ ExPolygons no_overlap_expolygons;
+};
+
+// BBS: used to judge whether the internal solid infill area is narrow
+static bool is_narrow_infill_area(const ExPolygon& expolygon)
+{
+ ExPolygons offsets = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD));
+ if (offsets.empty())
+ return true;
+
+ return false;
+}
+
+std::vector group_fills(const Layer &layer)
+{
+ std::vector surface_fills;
+
+ // Fill in a map of a region & surface to SurfaceFillParams.
+ std::set set_surface_params;
+ std::vector> region_to_surface_params(layer.regions().size(), std::vector());
+ SurfaceFillParams params;
+ bool has_internal_voids = false;
+ const PrintObjectConfig& object_config = layer.object()->config();
+ for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
+ const LayerRegion &layerm = *layer.regions()[region_id];
+ region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr);
+ for (const Surface &surface : layerm.fill_surfaces.surfaces)
+ if (surface.surface_type == stInternalVoid)
+ has_internal_voids = true;
+ else {
+ const PrintRegionConfig ®ion_config = layerm.region().config();
+ FlowRole extrusion_role = surface.is_top() ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill);
+ bool is_bridge = layer.id() > 0 && surface.is_bridge();
+ params.extruder = layerm.region().extruder(extrusion_role);
+ params.pattern = region_config.sparse_infill_pattern.value;
+ params.density = float(region_config.sparse_infill_density);
+
+ if (surface.is_solid()) {
+ params.density = 100.f;
+ //FIXME for non-thick bridges, shall we allow a bottom surface pattern?
+ if (surface.is_solid_infill())
+ params.pattern = region_config.internal_solid_infill_pattern.value;
+ else if (surface.is_external() && !is_bridge)
+ params.pattern = surface.is_top() ? region_config.top_surface_pattern.value : region_config.bottom_surface_pattern.value;
+ else
+ params.pattern = region_config.top_surface_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
+
+ } else if (params.density <= 0)
+ continue;
+
+ params.extrusion_role =
+ is_bridge ?
+ erBridgeInfill :
+ (surface.is_solid() ?
+ (surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : erSolidInfill)) :
+ erInternalInfill);
+ params.bridge_angle = float(surface.bridge_angle);
+ params.angle = float(Geometry::deg2rad(region_config.infill_direction.value));
+
+ // Calculate the actual flow we'll be using for this infill.
+ params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
+ params.flow = params.bridge ?
+ //BBS: always enable thick bridge for internal bridge
+ layerm.bridging_flow(extrusion_role, (surface.is_bridge() && !surface.is_external()) || object_config.thick_bridges) :
+ layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
+ //BBS: record speed params
+ if (!params.bridge) {
+ if (params.extrusion_role == erInternalInfill)
+ params.sparse_infill_speed = region_config.sparse_infill_speed;
+ else if (params.extrusion_role == erTopSolidInfill)
+ params.top_surface_speed = region_config.top_surface_speed;
+ else if (params.extrusion_role == erSolidInfill)
+ params.solid_infill_speed = region_config.internal_solid_infill_speed;
+ }
+ // Calculate flow spacing for infill pattern generation.
+ if (surface.is_solid() || is_bridge) {
+ params.spacing = params.flow.spacing();
+ // Don't limit anchor length for solid or bridging infill.
+ params.anchor_length = 1000.f;
+ params.anchor_length_max = 1000.f;
+ } else {
+ // Internal infill. Calculating infill line spacing independent of the current layer height and 1st layer status,
+ // so that internall infill will be aligned over all layers of the current region.
+ params.spacing = layerm.region().flow(*layer.object(), frInfill, layer.object()->config().layer_height, false).spacing();
+ // Anchor a sparse infill to inner perimeters with the following anchor length:
+ params.anchor_length = float(region_config.sparse_infill_anchor);
+ if (region_config.sparse_infill_anchor.percent)
+ params.anchor_length = float(params.anchor_length * 0.01 * params.spacing);
+ params.anchor_length_max = float(region_config.sparse_infill_anchor_max);
+ if (region_config.sparse_infill_anchor_max.percent)
+ params.anchor_length_max = float(params.anchor_length_max * 0.01 * params.spacing);
+ params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
+ }
+
+ auto it_params = set_surface_params.find(params);
+ if (it_params == set_surface_params.end())
+ it_params = set_surface_params.insert(it_params, params);
+ region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params);
+ }
+ }
+
+ surface_fills.reserve(set_surface_params.size());
+ for (const SurfaceFillParams ¶ms : set_surface_params) {
+ const_cast(params).idx = surface_fills.size();
+ surface_fills.emplace_back(params);
+ }
+
+ for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
+ const LayerRegion &layerm = *layer.regions()[region_id];
+ for (const Surface &surface : layerm.fill_surfaces.surfaces)
+ if (surface.surface_type != stInternalVoid) {
+ const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()];
+ if (params != nullptr) {
+ SurfaceFill &fill = surface_fills[params->idx];
+ if (fill.region_id == size_t(-1)) {
+ fill.region_id = region_id;
+ fill.surface = surface;
+ fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
+ //BBS
+ fill.region_id_group.push_back(region_id);
+ fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons;
+ } else {
+ fill.expolygons.emplace_back(surface.expolygon);
+ //BBS
+ auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id);
+ if (t == fill.region_id_group.end()) {
+ fill.region_id_group.push_back(region_id);
+ fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons);
+ }
+ }
+ }
+ }
+ }
+
+ {
+ Polygons all_polygons;
+ for (SurfaceFill &fill : surface_fills)
+ if (! fill.expolygons.empty()) {
+ if (fill.expolygons.size() > 1 || ! all_polygons.empty()) {
+ Polygons polys = to_polygons(std::move(fill.expolygons));
+ // Make a union of polygons, use a safety offset, subtract the preceding polygons.
+ // Bridges are processed first (see SurfaceFill::operator<())
+ fill.expolygons = all_polygons.empty() ? union_safety_offset_ex(polys) : diff_ex(polys, all_polygons, ApplySafetyOffset::Yes);
+ append(all_polygons, std::move(polys));
+ } else if (&fill != &surface_fills.back())
+ append(all_polygons, to_polygons(fill.expolygons));
+ }
+ }
+
+ // we need to detect any narrow surfaces that might collapse
+ // when adding spacing below
+ // such narrow surfaces are often generated in sloping walls
+ // by bridge_over_infill() and combine_infill() as a result of the
+ // subtraction of the combinable area from the layer infill area,
+ // which leaves small areas near the perimeters
+ // we are going to grow such regions by overlapping them with the void (if any)
+ // TODO: detect and investigate whether there could be narrow regions without
+ // any void neighbors
+ if (has_internal_voids) {
+ // Internal voids are generated only if "infill_only_where_needed" or "infill_every_layers" are active.
+ coord_t distance_between_surfaces = 0;
+ Polygons surfaces_polygons;
+ Polygons voids;
+ int region_internal_infill = -1;
+ int region_solid_infill = -1;
+ int region_some_infill = -1;
+ for (SurfaceFill &surface_fill : surface_fills)
+ if (! surface_fill.expolygons.empty()) {
+ distance_between_surfaces = std::max(distance_between_surfaces, surface_fill.params.flow.scaled_spacing());
+ append((surface_fill.surface.surface_type == stInternalVoid) ? voids : surfaces_polygons, to_polygons(surface_fill.expolygons));
+ if (surface_fill.surface.surface_type == stInternalSolid)
+ region_internal_infill = (int)surface_fill.region_id;
+ if (surface_fill.surface.is_solid())
+ region_solid_infill = (int)surface_fill.region_id;
+ if (surface_fill.surface.surface_type != stInternalVoid)
+ region_some_infill = (int)surface_fill.region_id;
+ }
+ if (! voids.empty() && ! surfaces_polygons.empty()) {
+ // First clip voids by the printing polygons, as the voids were ignored by the loop above during mutual clipping.
+ voids = diff(voids, surfaces_polygons);
+ // Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
+ Polygons collapsed = diff(
+ surfaces_polygons,
+ opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset)));
+ //FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being
+ // added if two offsetted void regions merge.
+ // polygons_append(voids, collapsed);
+ ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes);
+ // Now find an internal infill SurfaceFill to add these extrusions to.
+ SurfaceFill *internal_solid_fill = nullptr;
+ unsigned int region_id = 0;
+ if (region_internal_infill != -1)
+ region_id = region_internal_infill;
+ else if (region_solid_infill != -1)
+ region_id = region_solid_infill;
+ else if (region_some_infill != -1)
+ region_id = region_some_infill;
+ const LayerRegion& layerm = *layer.regions()[region_id];
+ for (SurfaceFill &surface_fill : surface_fills)
+ if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
+ internal_solid_fill = &surface_fill;
+ break;
+ }
+ if (internal_solid_fill == nullptr) {
+ // Produce another solid fill.
+ params.extruder = layerm.region().extruder(frSolidInfill);
+ params.pattern = layerm.region().config().top_surface_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
+ params.density = 100.f;
+ params.extrusion_role = erInternalInfill;
+ params.angle = float(Geometry::deg2rad(layerm.region().config().infill_direction.value));
+ // calculate the actual flow we'll be using for this infill
+ params.flow = layerm.flow(frSolidInfill);
+ params.spacing = params.flow.spacing();
+ surface_fills.emplace_back(params);
+ surface_fills.back().surface.surface_type = stInternalSolid;
+ surface_fills.back().surface.thickness = layer.height;
+ surface_fills.back().expolygons = std::move(extensions);
+ } else {
+ append(extensions, std::move(internal_solid_fill->expolygons));
+ internal_solid_fill->expolygons = union_ex(extensions);
+ }
+ }
+ }
+
+ // BBS: detect narrow internal solid infill area and use ipConcentricInternal pattern instead
+ if (layer.object()->config().detect_narrow_internal_solid_infill) {
+ size_t surface_fills_size = surface_fills.size();
+ for (size_t i = 0; i < surface_fills_size; i++) {
+ if (surface_fills[i].surface.surface_type != stInternalSolid)
+ continue;
+
+ size_t expolygons_size = surface_fills[i].expolygons.size();
+ std::vector narrow_expolygons_index;
+ narrow_expolygons_index.reserve(expolygons_size);
+ // BBS: get the index list of narrow expolygon
+ for (size_t j = 0; j < expolygons_size; j++)
+ if (is_narrow_infill_area(surface_fills[i].expolygons[j]))
+ narrow_expolygons_index.push_back(j);
+
+ if (narrow_expolygons_index.size() == 0) {
+ // BBS: has no narrow expolygon
+ continue;
+ }
+ else if (narrow_expolygons_index.size() == expolygons_size) {
+ // BBS: all expolygons are narrow, directly change the fill pattern
+ surface_fills[i].params.pattern = ipConcentricInternal;
+ }
+ else {
+ // BBS: some expolygons are narrow, spilit surface_fills[i] and rearrange the expolygons
+ params = surface_fills[i].params;
+ params.pattern = ipConcentricInternal;
+ surface_fills.emplace_back(params);
+ surface_fills.back().region_id = surface_fills[i].region_id;
+ surface_fills.back().surface.surface_type = stInternalSolid;
+ surface_fills.back().surface.thickness = surface_fills[i].surface.thickness;
+ surface_fills.back().region_id_group = surface_fills[i].region_id_group;
+ surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons;
+ for (size_t j = 0; j < narrow_expolygons_index.size(); j++) {
+ // BBS: move the narrow expolygons to new surface_fills.back();
+ surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expolygons_index[j]]));
+ }
+ for (int j = narrow_expolygons_index.size() - 1; j >= 0; j--) {
+ // BBS: delete the narrow expolygons from old surface_fills
+ surface_fills[i].expolygons.erase(surface_fills[i].expolygons.begin() + narrow_expolygons_index[j]);
+ }
+ }
+ }
+ }
+
+ return surface_fills;
+}
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+void export_group_fills_to_svg(const char *path, const std::vector &fills)
+{
+ BoundingBox bbox;
+ for (const auto &fill : fills)
+ for (const auto &expoly : fill.expolygons)
+ bbox.merge(get_extents(expoly));
+ 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 auto &fill : fills)
+ for (const auto &expoly : fill.expolygons)
+ svg.draw(expoly, surface_type_to_color_name(fill.surface.surface_type), transparency);
+ export_surface_type_legend_to_svg(svg, legend_pos);
+ svg.Close();
+}
+#endif
+
+// friend to Layer
+void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
+{
+ for (LayerRegion *layerm : m_regions)
+ layerm->fills.clear();
+
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+// this->export_region_fill_surfaces_to_svg_debug("10_fill-initial");
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ std::vector surface_fills = group_fills(*this);
+ const Slic3r::BoundingBox bbox = this->object()->bounding_box();
+ const auto resolution = this->object()->print()->config().resolution.value;
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ static int iRun = 0;
+ export_group_fills_to_svg(debug_out_path("Layer-fill_surfaces-10_fill-final-%d.svg", iRun ++).c_str(), surface_fills);
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ for (SurfaceFill &surface_fill : surface_fills) {
+ // Create the filler object.
+ std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern));
+ f->set_bounding_box(bbox);
+ f->layer_id = this->id();
+ f->z = this->print_z;
+ f->angle = surface_fill.params.angle;
+ f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
+
+ if (surface_fill.params.pattern == ipConcentricInternal) {
+ FillConcentricInternal *fill_concentric = dynamic_cast(f.get());
+ assert(fill_concentric != nullptr);
+ fill_concentric->print_config = &this->object()->print()->config();
+ fill_concentric->print_object_config = &this->object()->config();
+ } else if (surface_fill.params.pattern == ipConcentric) {
+ FillConcentric *fill_concentric = dynamic_cast(f.get());
+ assert(fill_concentric != nullptr);
+ fill_concentric->print_config = &this->object()->print()->config();
+ fill_concentric->print_object_config = &this->object()->config();
+ } else if (surface_fill.params.pattern == ipLightning)
+ dynamic_cast(f.get())->generator = lightning_generator;
+
+ // calculate flow spacing for infill pattern generation
+ bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge;
+ double link_max_length = 0.;
+ if (! surface_fill.params.bridge) {
+#if 0
+ link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
+// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
+#else
+ if (surface_fill.params.density > 80.) // 80%
+ link_max_length = 3. * f->spacing;
+#endif
+ }
+
+ // Maximum length of the perimeter segment linking two infill lines.
+ f->link_max_length = (coord_t)scale_(link_max_length);
+ // Used by the concentric infill pattern to clip the loops to create extrusion paths.
+ f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
+
+ // apply half spacing using this flow's own spacing and generate infill
+ FillParams params;
+ params.density = float(0.01 * surface_fill.params.density);
+ params.dont_adjust = false; // surface_fill.params.dont_adjust;
+ params.anchor_length = surface_fill.params.anchor_length;
+ params.anchor_length_max = surface_fill.params.anchor_length_max;
+ params.resolution = resolution;
+ params.use_arachne = surface_fill.params.pattern == ipConcentric;
+ params.layer_height = m_regions[surface_fill.region_id]->layer()->height;
+
+ // BBS
+ params.flow = surface_fill.params.flow;
+ params.extrusion_role = surface_fill.params.extrusion_role;
+ params.using_internal_flow = using_internal_flow;
+ params.no_extrusion_overlap = surface_fill.params.overlap;
+ if (surface_fill.params.pattern == ipGrid)
+ params.can_reverse = false;
+ LayerRegion* layerm = this->m_regions[surface_fill.region_id];
+ for (ExPolygon& expoly : surface_fill.expolygons) {
+ f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
+ // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
+ f->spacing = surface_fill.params.spacing;
+ surface_fill.surface.expolygon = std::move(expoly);
+ // BBS: make fill
+ f->fill_surface_extrusion(&surface_fill.surface,
+ params,
+ m_regions[surface_fill.region_id]->fills.entities);
+ }
+ }
+
+ // add thin fill regions
+ // Unpacks the collection, creates multiple collections per path.
+ // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
+ // Why the paths are unpacked?
+ for (LayerRegion *layerm : m_regions)
+ for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) {
+ ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
+ layerm->fills.entities.push_back(&collection);
+ collection.entities.push_back(thin_fill->clone());
+ }
+
+#ifndef NDEBUG
+ for (LayerRegion *layerm : m_regions)
+ for (size_t i = 0; i < layerm->fills.entities.size(); ++ i)
+ assert(dynamic_cast(layerm->fills.entities[i]) != nullptr);
+#endif
+}
+
+Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
+{
+ std::vector surface_fills = group_fills(*this);
+ const Slic3r::BoundingBox bbox = this->object()->bounding_box();
+ const auto resolution = this->object()->print()->config().resolution.value;
+
+ Polylines sparse_infill_polylines{};
+
+ for (SurfaceFill& surface_fill : surface_fills) {
+ if (surface_fill.surface.surface_type != stInternal) {
+ continue;
+ }
+
+ switch (surface_fill.params.pattern) {
+ case ipCount: continue; break;
+ case ipSupportBase: continue; break;
+ //case ipEnsuring: continue; break;
+ case ipLightning:
+ case ipAdaptiveCubic:
+ case ipSupportCubic:
+ case ipRectilinear:
+ case ipMonotonic:
+ case ipAlignedRectilinear:
+ case ipGrid:
+ case ipTriangles:
+ case ipStars:
+ case ipCubic:
+ case ipLine:
+ case ipConcentric:
+ case ipHoneycomb:
+ case ip3DHoneycomb:
+ case ipGyroid:
+ case ipHilbertCurve:
+ case ipArchimedeanChords:
+ case ipOctagramSpiral:
+ //xiamian+
+ case ipFiberSpiral:
+ break;
+ }
+
+ // Create the filler object.
+ std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern));
+ f->set_bounding_box(bbox);
+ f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers.
+ f->z = this->print_z;
+ f->angle = surface_fill.params.angle;
+ f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
+
+
+ if (surface_fill.params.pattern == ipLightning)
+ dynamic_cast(f.get())->generator = lightning_generator;
+
+ // calculate flow spacing for infill pattern generation
+ double link_max_length = 0.;
+ if (!surface_fill.params.bridge) {
+#if 0
+ link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
+ // printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
+#else
+ if (surface_fill.params.density > 80.) // 80%
+ link_max_length = 3. * f->spacing;
+#endif
+ }
+
+ // Maximum length of the perimeter segment linking two infill lines.
+ f->link_max_length = (coord_t)scale_(link_max_length);
+ // Used by the concentric infill pattern to clip the loops to create extrusion paths.
+ f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
+
+ LayerRegion& layerm = *m_regions[surface_fill.region_id];
+
+ // apply half spacing using this flow's own spacing and generate infill
+ FillParams params;
+ params.density = float(0.01 * surface_fill.params.density);
+ params.dont_adjust = false; // surface_fill.params.dont_adjust;
+ params.anchor_length = surface_fill.params.anchor_length;
+ params.anchor_length_max = surface_fill.params.anchor_length_max;
+ params.resolution = resolution;
+ params.use_arachne = false;
+ params.layer_height = layerm.layer()->height;
+
+ for (ExPolygon& expoly : surface_fill.expolygons) {
+ // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
+ f->spacing = surface_fill.params.spacing;
+ surface_fill.surface.expolygon = std::move(expoly);
+ try {
+ Polylines polylines = f->fill_surface(&surface_fill.surface, params);
+ sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end());
+ }
+ catch (InfillFailedException&) {}
+ }
+ }
+
+ return sparse_infill_polylines;
+}
+
+
+// Create ironing extrusions over top surfaces.
+void Layer::make_ironing()
+{
+ // LayerRegion::slices contains surfaces marked with SurfaceType.
+ // Here we want to collect top surfaces extruded with the same extruder.
+ // A surface will be ironed with the same extruder to not contaminate the print with another material leaking from the nozzle.
+
+ // First classify regions based on the extruder used.
+ struct IroningParams {
+ InfillPattern pattern;
+ int extruder = -1;
+ bool just_infill = false;
+ // Spacing of the ironing lines, also to calculate the extrusion flow from.
+ double line_spacing;
+ // Height of the extrusion, to calculate the extrusion flow from.
+ double height;
+ double speed;
+ double angle;
+
+ bool operator<(const IroningParams &rhs) const {
+ if (this->extruder < rhs.extruder)
+ return true;
+ if (this->extruder > rhs.extruder)
+ return false;
+ if (int(this->just_infill) < int(rhs.just_infill))
+ return true;
+ if (int(this->just_infill) > int(rhs.just_infill))
+ return false;
+ if (this->line_spacing < rhs.line_spacing)
+ return true;
+ if (this->line_spacing > rhs.line_spacing)
+ return false;
+ if (this->height < rhs.height)
+ return true;
+ if (this->height > rhs.height)
+ return false;
+ if (this->speed < rhs.speed)
+ return true;
+ if (this->speed > rhs.speed)
+ return false;
+ if (this->angle < rhs.angle)
+ return true;
+ if (this->angle > rhs.angle)
+ return false;
+ return false;
+ }
+
+ bool operator==(const IroningParams &rhs) const {
+ return this->extruder == rhs.extruder && this->just_infill == rhs.just_infill &&
+ this->line_spacing == rhs.line_spacing && this->height == rhs.height && this->speed == rhs.speed && this->angle == rhs.angle && this->pattern == rhs.pattern;
+ }
+
+ LayerRegion *layerm = nullptr;
+
+ // IdeaMaker: ironing
+ // ironing flowrate (5% percent)
+ // ironing speed (10 mm/sec)
+
+ // Kisslicer:
+ // iron off, Sweep, Group
+ // ironing speed: 15 mm/sec
+
+ // Cura:
+ // Pattern (zig-zag / concentric)
+ // line spacing (0.1mm)
+ // flow: from normal layer height. 10%
+ // speed: 20 mm/sec
+ };
+
+ std::vector by_extruder;
+ double default_layer_height = this->object()->config().layer_height;
+
+ for (LayerRegion *layerm : m_regions)
+ if (! layerm->slices.empty()) {
+ IroningParams ironing_params;
+ const PrintRegionConfig &config = layerm->region().config();
+ if (config.ironing_type != IroningType::NoIroning &&
+ (config.ironing_type == IroningType::AllSolid ||
+ (config.top_shell_layers > 0 &&
+ (config.ironing_type == IroningType::TopSurfaces ||
+ (config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
+ if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) {
+ // Iron the whole face.
+ ironing_params.extruder = config.solid_infill_filament;
+ } else {
+ // Iron just the infill.
+ ironing_params.extruder = config.solid_infill_filament;
+ }
+ }
+ if (ironing_params.extruder != -1) {
+ //TODO just_infill is currently not used.
+ ironing_params.just_infill = false;
+ ironing_params.line_spacing = config.ironing_spacing;
+ ironing_params.height = default_layer_height * 0.01 * config.ironing_flow;
+ ironing_params.speed = config.ironing_speed;
+ ironing_params.angle = (int(config.ironing_direction.value+layerm->region().config().infill_direction.value)%180) * M_PI / 180.;
+ ironing_params.pattern = config.ironing_pattern;
+ ironing_params.layerm = layerm;
+ by_extruder.emplace_back(ironing_params);
+ }
+ }
+ std::sort(by_extruder.begin(), by_extruder.end());
+
+ FillParams fill_params;
+ fill_params.density = 1.;
+ fill_params.monotonic = true;
+ InfillPattern f_pattern = ipRectilinear;
+ std::unique_ptr f = std::unique_ptr(Fill::new_from_type(f_pattern));
+ f->set_bounding_box(this->object()->bounding_box());
+ f->layer_id = this->id();
+ f->z = this->print_z;
+ f->overlap = 0;
+ for (size_t i = 0; i < by_extruder.size();) {
+ // Find span of regions equivalent to the ironing operation.
+ IroningParams &ironing_params = by_extruder[i];
+ // Create the filler object.
+ if( f_pattern != ironing_params.pattern )
+ {
+ f_pattern = ironing_params.pattern;
+ f = std::unique_ptr(Fill::new_from_type(f_pattern));
+ f->set_bounding_box(this->object()->bounding_box());
+ f->layer_id = this->id();
+ f->z = this->print_z;
+ f->overlap = 0;
+ }
+
+ size_t j = i;
+ for (++ j; j < by_extruder.size() && ironing_params == by_extruder[j]; ++ j) ;
+
+ // Create the ironing extrusions for regions object()->print()->config().nozzle_diameter.get_at(ironing_params.extruder - 1);
+ if (ironing_params.just_infill) {
+ //TODO just_infill is currently not used.
+ // Just infill.
+ } else {
+ // Infill and perimeter.
+ // Merge top surfaces with the same ironing parameters.
+ Polygons polys;
+ Polygons infills;
+ for (size_t k = i; k < j; ++ k) {
+ const IroningParams &ironing_params = by_extruder[k];
+ const PrintRegionConfig ®ion_config = ironing_params.layerm->region().config();
+ bool iron_everything = region_config.ironing_type == IroningType::AllSolid;
+ bool iron_completely = iron_everything;
+ if (iron_everything) {
+ // Check whether there is any non-solid hole in the regions.
+ bool internal_infill_solid = region_config.sparse_infill_density.value > 95.;
+ for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
+ if ((!internal_infill_solid && surface.surface_type == stInternal) || surface.surface_type == stInternalBridge || surface.surface_type == stInternalVoid) {
+ // Some fill region is not quite solid. Don't iron over the whole surface.
+ iron_completely = false;
+ break;
+ }
+ }
+ if (iron_completely) {
+ // Iron everything. This is likely only good for solid transparent objects.
+ for (const Surface &surface : ironing_params.layerm->slices.surfaces)
+ polygons_append(polys, surface.expolygon);
+ } else {
+ for (const Surface &surface : ironing_params.layerm->slices.surfaces)
+ if ((surface.surface_type == stTop && region_config.top_shell_layers > 0) || (iron_everything && surface.surface_type == stBottom && region_config.bottom_shell_layers > 0))
+ // stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges.
+ polygons_append(polys, surface.expolygon);
+ }
+ if (iron_everything && ! iron_completely) {
+ // Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these
+ // solid fill surfaces, but it is likely better than nothing.
+ for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
+ if (surface.surface_type == stInternalSolid)
+ polygons_append(infills, surface.expolygon);
+ }
+ }
+
+ if (! infills.empty() || j > i + 1) {
+ // Ironing over more than a single region or over solid internal infill.
+ if (! infills.empty())
+ // For IroningType::AllSolid only:
+ // Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
+ append(polys, std::move(infills));
+ polys = union_safety_offset(polys);
+ }
+ // Trim the top surfaces with half the nozzle diameter.
+ ironing_areas = intersection_ex(polys, offset(this->lslices, - float(scale_(0.5 * nozzle_dmr))));
+ }
+
+ // Create the filler object.
+ f->spacing = ironing_params.line_spacing;
+ f->angle = float(ironing_params.angle);
+ f->link_max_length = (coord_t) scale_(3. * f->spacing);
+ double extrusion_height = ironing_params.height * f->spacing / nozzle_dmr;
+ float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height));
+ double flow_mm3_per_mm = nozzle_dmr * extrusion_height;
+ Surface surface_fill(stTop, ExPolygon());
+ for (ExPolygon &expoly : ironing_areas) {
+ surface_fill.expolygon = std::move(expoly);
+ Polylines polylines;
+ try {
+ polylines = f->fill_surface(&surface_fill, fill_params);
+ } catch (InfillFailedException &) {
+ }
+ if (! polylines.empty()) {
+ // Save into layer.
+ ExtrusionEntityCollection *eec = nullptr;
+ ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection());
+ // Don't sort the ironing infill lines as they are monotonicly ordered.
+ eec->no_sort = true;
+ extrusion_entities_append_paths(
+ eec->entities, std::move(polylines),
+ erIroning,
+ flow_mm3_per_mm, extrusion_width, float(extrusion_height));
+ }
+ }
+
+ // Regions up to j were processed.
+ i = j;
+ }
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp
new file mode 100644
index 000000000..c6ffd8a38
--- /dev/null
+++ b/src/libslic3r/Fill/FillBase.cpp
@@ -0,0 +1,2598 @@
+#include
+#include
+
+#include "../ClipperUtils.hpp"
+#include "../EdgeGrid.hpp"
+#include "../Geometry.hpp"
+#include "../Geometry/Circle.hpp"
+#include "../Point.hpp"
+#include "../PrintConfig.hpp"
+#include "../Surface.hpp"
+#include "../libslic3r.h"
+#include "../VariableWidth.hpp"
+
+#include "FillBase.hpp"
+#include "FillConcentric.hpp"
+#include "FillHoneycomb.hpp"
+#include "Fill3DHoneycomb.hpp"
+#include "FillGyroid.hpp"
+#include "FillPlanePath.hpp"
+#include "FillLine.hpp"
+#include "FillRectilinear.hpp"
+#include "FillAdaptive.hpp"
+#include "FillLightning.hpp"
+// BBS: new infill pattern header
+#include "FillConcentricInternal.hpp"
+#include "FillCrossHatch.hpp"
+
+// #define INFILL_DEBUG_OUTPUT
+
+namespace Slic3r {
+
+Fill* Fill::new_from_type(const InfillPattern type)
+{
+ switch (type) {
+ case ipConcentric: return new FillConcentric();
+ case ipHoneycomb: return new FillHoneycomb();
+ case ip3DHoneycomb: return new Fill3DHoneycomb();
+ case ipGyroid: return new FillGyroid();
+ case ipRectilinear: return new FillRectilinear();
+ case ipAlignedRectilinear: return new FillAlignedRectilinear();
+ case ipCrossHatch: return new FillCrossHatch();
+ case ipMonotonic: return new FillMonotonic();
+ case ipLine: return new FillLine();
+ case ipGrid: return new FillGrid();
+ case ipTriangles: return new FillTriangles();
+ case ipStars: return new FillStars();
+ case ipCubic: return new FillCubic();
+ case ipArchimedeanChords: return new FillArchimedeanChords();
+ case ipHilbertCurve: return new FillHilbertCurve();
+ case ipOctagramSpiral: return new FillOctagramSpiral();
+ case ipAdaptiveCubic: return new FillAdaptive::Filler();
+ case ipSupportCubic: return new FillAdaptive::Filler();
+ case ipSupportBase: return new FillSupportBase();
+ case ipLightning: return new FillLightning::Filler();
+ // BBS: for internal solid infill only
+ case ipConcentricInternal: return new FillConcentricInternal();
+ // BBS: for bottom and top surface only
+ case ipMonotonicLine: return new FillMonotonicLineWGapFill();
+ //xiamian+
+ case ipFiberSpiral: return new FillLine();
+ default: throw Slic3r::InvalidArgument("unknown type");
+ }
+}
+
+Fill* Fill::new_from_type(const std::string &type)
+{
+ const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values();
+ t_config_enum_values::const_iterator it = enum_keys_map.find(type);
+ return (it == enum_keys_map.end()) ? nullptr : new_from_type(InfillPattern(it->second));
+}
+
+// Force initialization of the Fill::use_bridge_flow() internal static map in a thread safe fashion even on compilers
+// not supporting thread safe non-static data member initializers.
+static bool use_bridge_flow_initializer = Fill::use_bridge_flow(ipGrid);
+
+bool Fill::use_bridge_flow(const InfillPattern type)
+{
+ static std::vector cached;
+ if (cached.empty()) {
+ cached.assign(size_t(ipCount), 0);
+ for (size_t i = 0; i < cached.size(); ++ i) {
+ auto *fill = Fill::new_from_type((InfillPattern)i);
+ cached[i] = fill->use_bridge_flow();
+ delete fill;
+ }
+ }
+ return cached[type] != 0;
+}
+
+Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms)
+{
+ // Perform offset.
+ Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
+ // Create the infills for each of the regions.
+ Polylines polylines_out;
+ for (size_t i = 0; i < expp.size(); ++ i)
+ _fill_surface_single(
+ params,
+ surface->thickness_layers,
+ _infill_direction(surface),
+ std::move(expp[i]),
+ polylines_out);
+ return polylines_out;
+}
+
+ThickPolylines Fill::fill_surface_arachne(const Surface* surface, const FillParams& params)
+{
+ // Perform offset.
+ Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
+ // Create the infills for each of the regions.
+ ThickPolylines thick_polylines_out;
+ for (ExPolygon& expoly : expp)
+ _fill_surface_single(params, surface->thickness_layers, _infill_direction(surface), std::move(expoly), thick_polylines_out);
+ return thick_polylines_out;
+}
+
+// BBS: this method is used to fill the ExtrusionEntityCollection. It call fill_surface by default
+void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out)
+{
+ Polylines polylines;
+ ThickPolylines thick_polylines;
+
+ try {
+ if (params.use_arachne)
+ thick_polylines = this->fill_surface_arachne(surface, params);
+ else
+ polylines = this->fill_surface(surface, params);
+ }
+ catch (InfillFailedException&) {}
+
+ if (!polylines.empty() || !thick_polylines.empty()) {
+ // calculate actual flow from spacing (which might have been adjusted by the infill
+ // pattern generator)
+ double flow_mm3_per_mm = params.flow.mm3_per_mm();
+ double flow_width = params.flow.width();
+ if (params.using_internal_flow) {
+ // if we used the internal flow we're not doing a solid infill
+ // so we can safely ignore the slight variation that might have
+ // been applied to f->spacing
+ }
+ else {
+ Flow new_flow = params.flow.with_spacing(this->spacing);
+ flow_mm3_per_mm = new_flow.mm3_per_mm();
+ flow_width = new_flow.width();
+ }
+ // Save into layer.
+ ExtrusionEntityCollection* eec = nullptr;
+ out.push_back(eec = new ExtrusionEntityCollection());
+ // Only concentric fills are not sorted.
+ eec->no_sort = this->no_sort();
+ size_t idx = eec->entities.size();
+ if (params.use_arachne) {
+ Flow new_flow = params.flow.with_spacing(float(this->spacing));
+ variable_width(thick_polylines, params.extrusion_role, new_flow, eec->entities);
+ thick_polylines.clear();
+ }
+ else {
+ extrusion_entities_append_paths(
+ eec->entities, std::move(polylines),
+ params.extrusion_role,
+ flow_mm3_per_mm, float(flow_width), params.flow.height());
+ }
+ if (!params.can_reverse) {
+ for (size_t i = idx; i < eec->entities.size(); i++)
+ eec->entities[i]->set_reverse();
+ }
+ }
+}
+
+// Calculate a new spacing to fill width with possibly integer number of lines,
+// the first and last line being centered at the interval ends.
+// This function possibly increases the spacing, never decreases,
+// and for a narrow width the increase in spacing may become severe,
+// therefore the adjustment is limited to 20% increase.
+coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance)
+{
+ assert(width >= 0);
+ assert(distance > 0);
+ // floor(width / distance)
+ const auto number_of_intervals = coord_t((width - EPSILON) / distance);
+ coord_t distance_new = (number_of_intervals == 0) ?
+ distance :
+ coord_t((width - EPSILON) / number_of_intervals);
+ const coordf_t factor = coordf_t(distance_new) / coordf_t(distance);
+ assert(factor > 1. - 1e-5);
+ // How much could the extrusion width be increased? By 20%.
+ const coordf_t factor_max = 1.2;
+ if (factor > factor_max)
+ distance_new = coord_t(floor((coordf_t(distance) * factor_max + 0.5)));
+ return distance_new;
+}
+
+// Returns orientation of the infill and the reference point of the infill pattern.
+// For a normal print, the reference point is the center of a bounding box of the STL.
+std::pair Fill::_infill_direction(const Surface *surface) const
+{
+ // set infill angle
+ float out_angle = this->angle;
+
+ if (out_angle == FLT_MAX) {
+ //FIXME Vojtech: Add a warning?
+ printf("Using undefined infill angle\n");
+ out_angle = 0.f;
+ }
+
+ // Bounding box is the bounding box of a perl object Slic3r::Print::Object (c++ object Slic3r::PrintObject)
+ // The bounding box is only undefined in unit tests.
+ Point out_shift = empty(this->bounding_box) ?
+ surface->expolygon.contour.bounding_box().center() :
+ this->bounding_box.center();
+
+#if 0
+ if (empty(this->bounding_box)) {
+ printf("Fill::_infill_direction: empty bounding box!");
+ } else {
+ printf("Fill::_infill_direction: reference point %d, %d\n", out_shift.x, out_shift.y);
+ }
+#endif
+
+ if (surface->bridge_angle >= 0) {
+ // use bridge angle
+ //FIXME Vojtech: Add a debugf?
+ // Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle);
+#ifdef SLIC3R_DEBUG
+ printf("Filling bridge with angle %f\n", surface->bridge_angle);
+#endif /* SLIC3R_DEBUG */
+ out_angle = float(surface->bridge_angle);
+ } else if (this->layer_id != size_t(-1)) {
+ // alternate fill direction
+ out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
+ } else {
+// printf("Layer_ID undefined!\n");
+ }
+
+ out_angle += float(M_PI/2.);
+ return std::pair(out_angle, out_shift);
+}
+
+// A single T joint of an infill line to a closed contour or one of its holes.
+struct ContourIntersectionPoint {
+ // Contour and point on a contour where an infill line is connected to.
+ size_t contour_idx;
+ size_t point_idx;
+ // Eucleidean parameter of point_idx along its contour.
+ double param;
+ // Other intersection points along the same contour. If there is only a single T-joint on a contour
+ // with an intersection line, then the prev_on_contour and next_on_contour remain nulls.
+ ContourIntersectionPoint* prev_on_contour { nullptr };
+ ContourIntersectionPoint* next_on_contour { nullptr };
+ // Length of the contour not yet allocated to some extrusion path going back (clockwise), or masked out by some overlapping infill line.
+ double contour_not_taken_length_prev { std::numeric_limits::max() };
+ // Length of the contour not yet allocated to some extrusion path going forward (counter-clockwise), or masked out by some overlapping infill line.
+ double contour_not_taken_length_next { std::numeric_limits::max() };
+ // End point is consumed if an infill line connected to this T-joint was already connected left or right along the contour,
+ // or if the infill line was processed, but it was not possible to connect it left or right along the contour.
+ bool consumed { false };
+ // Whether the contour was trimmed by an overlapping infill line, or whether part of this contour was connected to some infill line.
+ bool prev_trimmed { false };
+ bool next_trimmed { false };
+
+ void consume_prev() { this->contour_not_taken_length_prev = 0.; this->prev_trimmed = true; this->consumed = true; }
+ void consume_next() { this->contour_not_taken_length_next = 0.; this->next_trimmed = true; this->consumed = true; }
+
+ void trim_prev(const double new_len) {
+ if (new_len < this->contour_not_taken_length_prev) {
+ this->contour_not_taken_length_prev = new_len;
+ this->prev_trimmed = true;
+ }
+ }
+ void trim_next(const double new_len) {
+ if (new_len < this->contour_not_taken_length_next) {
+ this->contour_not_taken_length_next = new_len;
+ this->next_trimmed = true;
+ }
+ }
+
+ // The end point of an infill line connected to this T-joint was not processed yet and a piece of the contour could be extruded going backwards.
+ bool could_take_prev() const throw() { return ! this->consumed && this->contour_not_taken_length_prev > SCALED_EPSILON; }
+ // The end point of an infill line connected to this T-joint was not processed yet and a piece of the contour could be extruded going forward.
+ bool could_take_next() const throw() { return ! this->consumed && this->contour_not_taken_length_next > SCALED_EPSILON; }
+
+ // Could extrude a complete segment from this to this->prev_on_contour.
+ bool could_connect_prev() const throw()
+ { return ! this->consumed && this->prev_on_contour != this && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; }
+ // Could extrude a complete segment from this to this->next_on_contour.
+ bool could_connect_next() const throw()
+ { return ! this->consumed && this->next_on_contour != this && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; }
+};
+
+// Distance from param1 to param2 when going counter-clockwise.
+static inline double closed_contour_distance_ccw(double param1, double param2, double contour_length)
+{
+ assert(param1 >= 0. && param1 <= contour_length);
+ assert(param2 >= 0. && param2 <= contour_length);
+ double d = param2 - param1;
+ if (d < 0.)
+ d += contour_length;
+ return d;
+}
+
+// Distance from param1 to param2 when going clockwise.
+static inline double closed_contour_distance_cw(double param1, double param2, double contour_length)
+{
+ return closed_contour_distance_ccw(param2, param1, contour_length);
+}
+
+// Length along the contour from cp1 to cp2 going counter-clockwise.
+double path_length_along_contour_ccw(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, double contour_length)
+{
+ assert(cp1 != nullptr);
+ assert(cp2 != nullptr);
+ assert(cp1->contour_idx == cp2->contour_idx);
+ assert(cp1 != cp2);
+ return closed_contour_distance_ccw(cp1->param, cp2->param, contour_length);
+}
+
+// Lengths along the contour from cp1 to cp2 going CCW and going CW.
+std::pair path_lengths_along_contour(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, double contour_length)
+{
+ // Zero'th param is the length of the contour.
+ double param_lo = cp1->param;
+ double param_hi = cp2->param;
+ assert(param_lo >= 0. && param_lo <= contour_length);
+ assert(param_hi >= 0. && param_hi <= contour_length);
+ bool reversed = false;
+ if (param_lo > param_hi) {
+ std::swap(param_lo, param_hi);
+ reversed = true;
+ }
+ auto out = std::make_pair(param_hi - param_lo, param_lo + contour_length - param_hi);
+ if (reversed)
+ std::swap(out.first, out.second);
+ return out;
+}
+
+// Add contour points from interval (idx_start, idx_end> to polyline.
+static inline void take_cw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end)
+{
+ assert(! pl.empty() && pl.points.back() == contour[idx_start]);
+ size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1;
+ while (i != idx_end) {
+ pl.points.emplace_back(contour[i]);
+ if (i == 0)
+ i = contour.size();
+ -- i;
+ }
+ pl.points.emplace_back(contour[i]);
+}
+
+// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken.
+static inline double take_cw_limited(Polyline &pl, const Points &contour, const std::vector ¶ms, size_t idx_start, size_t idx_end, double length_to_take)
+{
+ // If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line.
+ assert(pl.empty() || pl.points.back() == contour[idx_start]);
+ assert(contour.size() + 1 == params.size());
+ assert(length_to_take > SCALED_EPSILON);
+ // Length of the contour.
+ double length = params.back();
+ // Parameter (length from contour.front()) for the first point.
+ double p0 = params[idx_start];
+ // Current (2nd) point of the contour.
+ size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1;
+ // Previous point of the contour.
+ size_t iprev = idx_start;
+ // Length of the contour curve taken for iprev.
+ double lprev = 0.;
+
+ for (;;) {
+ double l = closed_contour_distance_cw(p0, params[i], length);
+ if (l >= length_to_take) {
+ // Trim the last segment.
+ double t = double(length_to_take - lprev) / (l - lprev);
+ pl.points.emplace_back(lerp(contour[iprev], contour[i], t));
+ return length_to_take;
+ }
+ // Continue with the other segments.
+ pl.points.emplace_back(contour[i]);
+ if (i == idx_end)
+ return l;
+ iprev = i;
+ lprev = l;
+ if (i == 0)
+ i = contour.size();
+ -- i;
+ }
+ assert(false);
+ return 0;
+}
+
+// Add contour points from interval (idx_start, idx_end> to polyline.
+static inline void take_ccw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end)
+{
+ assert(! pl.empty() && pl.points.back() == contour[idx_start]);
+ size_t i = idx_start;
+ if (++ i == contour.size())
+ i = 0;
+ while (i != idx_end) {
+ pl.points.emplace_back(contour[i]);
+ if (++ i == contour.size())
+ i = 0;
+ }
+ pl.points.emplace_back(contour[i]);
+}
+
+// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken.
+// Returns length of the contour taken.
+static inline double take_ccw_limited(Polyline &pl, const Points &contour, const std::vector ¶ms, size_t idx_start, size_t idx_end, double length_to_take)
+{
+ // If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line.
+ assert(pl.empty() || pl.points.back() == contour[idx_start]);
+ assert(contour.size() + 1 == params.size());
+ assert(length_to_take > SCALED_EPSILON);
+ // Length of the contour.
+ double length = params.back();
+ // Parameter (length from contour.front()) for the first point.
+ double p0 = params[idx_start];
+ // Current (2nd) point of the contour.
+ size_t i = idx_start;
+ if (++ i == contour.size())
+ i = 0;
+ // Previous point of the contour.
+ size_t iprev = idx_start;
+ // Length of the contour curve taken at iprev.
+ double lprev = 0;
+ for (;;) {
+ double l = closed_contour_distance_ccw(p0, params[i], length);
+ if (l >= length_to_take) {
+ // Trim the last segment.
+ double t = double(length_to_take - lprev) / (l - lprev);
+ pl.points.emplace_back(lerp(contour[iprev], contour[i], t));
+ return length_to_take;
+ }
+ // Continue with the other segments.
+ pl.points.emplace_back(contour[i]);
+ if (i == idx_end)
+ return l;
+ iprev = i;
+ lprev = l;
+ if (++ i == contour.size())
+ i = 0;
+ }
+ assert(false);
+ return 0;
+}
+
+// Connect end of pl1 to the start of pl2 using the perimeter contour.
+// If clockwise, then a clockwise segment from idx_start to idx_end is taken, otherwise a counter-clockwise segment is being taken.
+static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, size_t idx_start, size_t idx_end, bool clockwise)
+{
+#ifndef NDEBUG
+ assert(idx_start != idx_end);
+ assert(pl1.size() >= 2);
+ assert(pl2.size() >= 2);
+#endif /* NDEBUG */
+
+ {
+ // Reserve memory at pl1 for the connecting contour and pl2.
+ int new_points = int(idx_end) - int(idx_start) - 1;
+ if (new_points < 0)
+ new_points += int(contour.size());
+ pl1.points.reserve(pl1.points.size() + size_t(new_points) + pl2.points.size());
+ }
+
+ if (clockwise)
+ take_cw_full(pl1, contour, idx_start, idx_end);
+ else
+ take_ccw_full(pl1, contour, idx_start, idx_end);
+
+ pl1.points.insert(pl1.points.end(), pl2.points.begin() + 1, pl2.points.end());
+}
+
+static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise)
+{
+ assert(cp_start->prev_on_contour != nullptr);
+ assert(cp_start->next_on_contour != nullptr);
+ assert(cp_end ->prev_on_contour != nullptr);
+ assert(cp_end ->next_on_contour != nullptr);
+ assert(cp_start != cp_end);
+
+ take(pl1, pl2, contour, cp_start->point_idx, cp_end->point_idx, clockwise);
+
+ // Mark the contour segments in between cp_start and cp_end as consumed.
+ if (clockwise)
+ std::swap(cp_start, cp_end);
+ if (cp_start->next_on_contour != cp_end)
+ for (auto *cp = cp_start->next_on_contour; cp->next_on_contour != cp_end; cp = cp->next_on_contour) {
+ cp->consume_prev();
+ cp->consume_next();
+ }
+ cp_start->consume_next();
+ cp_end->consume_prev();
+}
+
+static void take_limited(
+ Polyline &pl1, const Points &contour, const std::vector ¶ms,
+ ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, double take_max_length, double line_half_width)
+{
+#ifndef NDEBUG
+ // This is a valid case, where a single infill line connect to two different contours (outer contour + hole or two holes).
+// assert(cp_start != cp_end);
+ assert(cp_start->prev_on_contour != nullptr);
+ assert(cp_start->next_on_contour != nullptr);
+ assert(cp_end ->prev_on_contour != nullptr);
+ assert(cp_end ->next_on_contour != nullptr);
+ assert(pl1.size() >= 2);
+ assert(contour.size() + 1 == params.size());
+#endif /* NDEBUG */
+
+ if (! (clockwise ? cp_start->could_take_prev() : cp_start->could_take_next()))
+ return;
+
+ assert(pl1.points.front() == contour[cp_start->point_idx] || pl1.points.back() == contour[cp_start->point_idx]);
+ bool add_at_start = pl1.points.front() == contour[cp_start->point_idx];
+ Points pl_tmp;
+ if (add_at_start) {
+ pl_tmp = std::move(pl1.points);
+ pl1.points.clear();
+ }
+
+ {
+ // Reserve memory at pl1 for the perimeter segment.
+ // Pessimizing - take the complete segment.
+ int new_points = int(cp_end->point_idx) - int(cp_start->point_idx) - 1;
+ if (new_points < 0)
+ new_points += int(contour.size());
+ pl1.points.reserve(pl1.points.size() + pl_tmp.size() + size_t(new_points));
+ }
+
+ double length = params.back();
+ double length_to_go = take_max_length;
+ cp_start->consumed = true;
+ if (cp_start == cp_end) {
+ length_to_go = std::max(0., std::min(length_to_go, length - line_half_width));
+ length_to_go = std::min(length_to_go, clockwise ? cp_start->contour_not_taken_length_prev : cp_start->contour_not_taken_length_next);
+ cp_start->consume_prev();
+ cp_start->consume_next();
+ if (length_to_go > SCALED_EPSILON)
+ clockwise ?
+ take_cw_limited (pl1, contour, params, cp_start->point_idx, cp_start->point_idx, length_to_go) :
+ take_ccw_limited(pl1, contour, params, cp_start->point_idx, cp_start->point_idx, length_to_go);
+ } else if (clockwise) {
+ // Going clockwise from cp_start to cp_end.
+ assert(cp_start != cp_end);
+ for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->prev_on_contour) {
+ // Length of the segment from cp to cp->prev_on_contour.
+ double l = closed_contour_distance_cw(cp->param, cp->prev_on_contour->param, length);
+ length_to_go = std::min(length_to_go, cp->contour_not_taken_length_prev);
+ //if (cp->prev_on_contour->consumed)
+ // Don't overlap with an already extruded infill line.
+ length_to_go = std::max(0., std::min(length_to_go, l - line_half_width));
+ cp->consume_prev();
+ if (l >= length_to_go) {
+ if (length_to_go > SCALED_EPSILON) {
+ cp->prev_on_contour->trim_next(l - length_to_go);
+ take_cw_limited(pl1, contour, params, cp->point_idx, cp->prev_on_contour->point_idx, length_to_go);
+ }
+ break;
+ } else {
+ cp->prev_on_contour->trim_next(0.);
+ take_cw_full(pl1, contour, cp->point_idx, cp->prev_on_contour->point_idx);
+ length_to_go -= l;
+ }
+ }
+ } else {
+ assert(cp_start != cp_end);
+ for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->next_on_contour) {
+ double l = closed_contour_distance_ccw(cp->param, cp->next_on_contour->param, length);
+ length_to_go = std::min(length_to_go, cp->contour_not_taken_length_next);
+ //if (cp->next_on_contour->consumed)
+ // Don't overlap with an already extruded infill line.
+ length_to_go = std::max(0., std::min(length_to_go, l - line_half_width));
+ cp->consume_next();
+ if (l >= length_to_go) {
+ if (length_to_go > SCALED_EPSILON) {
+ cp->next_on_contour->trim_prev(l - length_to_go);
+ take_ccw_limited(pl1, contour, params, cp->point_idx, cp->next_on_contour->point_idx, length_to_go);
+ }
+ break;
+ } else {
+ cp->next_on_contour->trim_prev(0.);
+ take_ccw_full(pl1, contour, cp->point_idx, cp->next_on_contour->point_idx);
+ length_to_go -= l;
+ }
+ }
+ }
+
+ if (add_at_start) {
+ pl1.reverse();
+ append(pl1.points, pl_tmp);
+ }
+}
+
+// Return an index of start of a segment and a point of the clipping point at distance from the end of polyline.
+struct SegmentPoint {
+ // Segment index, defining a line ::max();
+ // Parameter of point in <0, 1) along the line ::max(); }
+};
+
+static inline SegmentPoint clip_start_segment_and_point(const Points &polyline, double distance)
+{
+ assert(polyline.size() >= 2);
+ assert(distance > 0.);
+ // Initialized to "invalid".
+ SegmentPoint out;
+ if (polyline.size() >= 2) {
+ Vec2d pt_prev = polyline.front().cast();
+ for (size_t i = 1; i < polyline.size(); ++ i) {
+ Vec2d pt = polyline[i].cast();
+ Vec2d v = pt - pt_prev;
+ double l = v.norm();
+ if (l > distance) {
+ out.idx_segment = i - 1;
+ out.t = distance / l;
+ out.point = pt_prev + out.t * v;
+ break;
+ }
+ distance -= l;
+ pt_prev = pt;
+ }
+ }
+ return out;
+}
+
+static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, double distance)
+{
+ assert(polyline.size() >= 2);
+ assert(distance > 0.);
+ // Initialized to "invalid".
+ SegmentPoint out;
+ if (polyline.size() >= 2) {
+ Vec2d pt_next = polyline.back().cast();
+ for (int i = int(polyline.size()) - 2; i >= 0; -- i) {
+ Vec2d pt = polyline[i].cast();
+ Vec2d v = pt - pt_next;
+ double l = v.norm();
+ if (l > distance) {
+ out.idx_segment = i;
+ out.t = distance / l;
+ out.point = pt_next + out.t * v;
+ // Store the parameter referenced to the starting point of a segment.
+ out.t = 1. - out.t;
+ break;
+ }
+ distance -= l;
+ pt_next = pt;
+ }
+ }
+ return out;
+}
+
+// Calculate intersection of a line with a thick segment.
+// Returns Eucledian parameters of the line / thick segment overlap.
+static inline bool line_rounded_thick_segment_collision(
+ const Vec2d &line_a, const Vec2d &line_b,
+ const Vec2d &segment_a, const Vec2d &segment_b, const double offset,
+ std::pair &out_interval)
+{
+ const Vec2d line_v0 = line_b - line_a;
+ double lv = line_v0.squaredNorm();
+
+ const Vec2d segment_v = segment_b - segment_a;
+ const double segment_l = segment_v.norm();
+ const double offset2 = offset * offset;
+
+ bool intersects = false;
+ if (lv < SCALED_EPSILON * SCALED_EPSILON)
+ {
+ // Very short line vector. Just test whether the center point is inside the offset line.
+ Vec2d lpt = 0.5 * (line_a + line_b);
+ if (segment_l > SCALED_EPSILON) {
+ intersects = line_alg::distance_to_squared(Linef{ segment_a, segment_b }, lpt) < offset2;
+ } else
+ intersects = (0.5 * (segment_a + segment_b) - lpt).squaredNorm() < offset2;
+ if (intersects) {
+ out_interval.first = 0.;
+ out_interval.second = sqrt(lv);
+ }
+ }
+ else
+ {
+ // Output interval.
+ double tmin = std::numeric_limits::max();
+ double tmax = -tmin;
+ auto extend_interval = [&tmin, &tmax](double atmin, double atmax) {
+ tmin = std::min(tmin, atmin);
+ tmax = std::max(tmax, atmax);
+ };
+
+ // Intersections with the inflated segment end points.
+ auto ray_circle_intersection_interval_extend = [&extend_interval](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) {
+ std::pair pts;
+ Vec2d p0 = line_pt - segment_pt;
+ double lv2 = line_vec.squaredNorm();
+ if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.y(), - line_vec.x(), lv2, - line_vec.y() * p0.x() + line_vec.x() * p0.y(), pts)) {
+ double tmin = (pts.first - p0).dot(line_vec) / lv2;
+ double tmax = (pts.second - p0).dot(line_vec) / lv2;
+ if (tmin > tmax)
+ std::swap(tmin, tmax);
+ tmin = std::max(tmin, 0.);
+ tmax = std::min(tmax, 1.);
+ if (tmin <= tmax)
+ extend_interval(tmin, tmax);
+ }
+ };
+
+ // Intersections with the inflated segment.
+ if (segment_l > SCALED_EPSILON) {
+ ray_circle_intersection_interval_extend(segment_a, offset2, line_a, line_v0);
+ ray_circle_intersection_interval_extend(segment_b, offset2, line_a, line_v0);
+ // Clip the line segment transformed into a coordinate space of the segment,
+ // where the segment spans (0, 0) to (segment_l, 0).
+ const Vec2d dir_x = segment_v / segment_l;
+ const Vec2d dir_y(- dir_x.y(), dir_x.x());
+ const Vec2d line_p0(line_a - segment_a);
+ std::pair interval;
+ if (Geometry::liang_barsky_line_clipping_interval(
+ Vec2d(line_p0.dot(dir_x), line_p0.dot(dir_y)),
+ Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)),
+ BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)),
+ interval))
+ extend_interval(interval.first, interval.second);
+ } else
+ ray_circle_intersection_interval_extend(0.5 * (segment_a + segment_b), offset, line_a, line_v0);
+
+ intersects = tmin <= tmax;
+ if (intersects) {
+ lv = sqrt(lv);
+ out_interval.first = tmin * lv;
+ out_interval.second = tmax * lv;
+ }
+ }
+
+#if 0
+ {
+ BoundingBox bbox;
+ bbox.merge(line_a.cast());
+ bbox.merge(line_a.cast());
+ bbox.merge(segment_a.cast());
+ bbox.merge(segment_b.cast());
+ static int iRun = 0;
+ ::Slic3r::SVG svg(debug_out_path("%s-%03d.svg", "line-thick-segment-intersect", iRun ++), bbox);
+ svg.draw(Line(line_a.cast(), line_b.cast()), "black");
+ svg.draw(Line(segment_a.cast(), segment_b.cast()), "blue", offset * 2.);
+ svg.draw(segment_a.cast(), "blue", offset);
+ svg.draw(segment_b.cast(), "blue", offset);
+ svg.draw(Line(segment_a.cast(), segment_b.cast()), "black");
+ if (intersects)
+ svg.draw(Line((line_a + (line_b - line_a).normalized() * out_interval.first).cast(),
+ (line_a + (line_b - line_a).normalized() * out_interval.second).cast()), "red");
+ }
+#endif
+
+ return intersects;
+}
+
+#ifndef NDEBUG
+static inline bool inside_interval(double low, double high, double p)
+{
+ return p >= low && p <= high;
+}
+
+static inline bool interval_inside_interval(double outer_low, double outer_high, double inner_low, double inner_high, double epsilon)
+{
+ outer_low -= epsilon;
+ outer_high += epsilon;
+ return inside_interval(outer_low, outer_high, inner_low) && inside_interval(outer_low, outer_high, inner_high);
+}
+
+static inline bool cyclic_interval_inside_interval(double outer_low, double outer_high, double inner_low, double inner_high, double length)
+{
+ if (outer_low > outer_high)
+ outer_high += length;
+ if (inner_low > inner_high)
+ inner_high += length;
+ else if (inner_high < outer_low) {
+ inner_low += length;
+ inner_high += length;
+ }
+ return interval_inside_interval(outer_low, outer_high, inner_low, inner_high, double(SCALED_EPSILON));
+}
+#endif // NDEBUG
+
+#ifdef INFILL_DEBUG_OUTPUT
+static void export_infill_to_svg(
+ // Boundary contour, along which the perimeter extrusions will be drawn.
+ const std::vector &boundary,
+ // Parametrization of boundary with Euclidian length.
+ const std::vector> &boundary_parameters,
+ // Intersections (T-joints) of the infill lines with the boundary.
+ std::vector> &boundary_intersections,
+ // Infill lines, either completely inside the boundary, or touching the boundary.
+ const Polylines &infill,
+ const coord_t scaled_spacing,
+ const std::string &path,
+ const Polylines &overlap_lines = Polylines(),
+ const Polylines &polylines = Polylines(),
+ const Points &pts = Points())
+{
+ Polygons polygons;
+ std::transform(boundary.begin(), boundary.end(), std::back_inserter(polygons), [](auto &pts) { return Polygon(pts); });
+ ExPolygons expolygons = union_ex(polygons);
+ BoundingBox bbox = get_extents(polygons);
+ bbox.offset(scale_(3.));
+
+ ::Slic3r::SVG svg(path, bbox);
+ // Draw the filled infill polygons.
+ svg.draw(expolygons);
+
+ // Draw the pieces of boundary allowed to be used as anchors of infill lines, not yet consumed.
+ const std::string color_boundary_trimmed = "blue";
+ const std::string color_boundary_not_trimmed = "yellow";
+ const coordf_t boundary_line_width = scaled_spacing;
+ svg.draw_outline(polygons, "red", boundary_line_width);
+ for (const std::vector &intersections : boundary_intersections) {
+ const size_t boundary_idx = &intersections - boundary_intersections.data();
+ const Points &contour = boundary[boundary_idx];
+ const std::vector &contour_param = boundary_parameters[boundary_idx];
+ for (const ContourIntersectionPoint *ip : intersections) {
+ assert(ip->next_trimmed == ip->next_on_contour->prev_trimmed);
+ assert(ip->prev_trimmed == ip->prev_on_contour->next_trimmed);
+ {
+ Polyline pl { contour[ip->point_idx] };
+ if (ip->next_trimmed) {
+ if (ip->contour_not_taken_length_next > SCALED_EPSILON) {
+ take_ccw_limited(pl, contour, contour_param, ip->point_idx, ip->next_on_contour->point_idx, ip->contour_not_taken_length_next);
+ svg.draw(pl, color_boundary_trimmed, boundary_line_width);
+ }
+ } else {
+ take_ccw_full(pl, contour, ip->point_idx, ip->next_on_contour->point_idx);
+ svg.draw(pl, color_boundary_not_trimmed, boundary_line_width);
+ }
+ }
+ {
+ Polyline pl { contour[ip->point_idx] };
+ if (ip->prev_trimmed) {
+ if (ip->contour_not_taken_length_prev > SCALED_EPSILON) {
+ take_cw_limited(pl, contour, contour_param, ip->point_idx, ip->prev_on_contour->point_idx, ip->contour_not_taken_length_prev);
+ svg.draw(pl, color_boundary_trimmed, boundary_line_width);
+ }
+ } else {
+ take_cw_full(pl, contour, ip->point_idx, ip->prev_on_contour->point_idx);
+ svg.draw(pl, color_boundary_not_trimmed, boundary_line_width);
+ }
+ }
+ }
+ }
+
+ // Draw the full infill polygon boundary.
+ svg.draw_outline(polygons, "green");
+
+ // Draw the infill lines, first the full length with red color, then a slightly shortened length with black color.
+ svg.draw(infill, "brown");
+ static constexpr double trim_length = scale_(0.15);
+ for (Polyline polyline : infill)
+ if (! polyline.empty()) {
+ Vec2d a = polyline.points.front().cast();
+ Vec2d d = polyline.points.back().cast();
+ if (polyline.size() == 2) {
+ Vec2d v = d - a;
+ double l = v.norm();
+ if (l > 2. * trim_length) {
+ a += v * trim_length / l;
+ d -= v * trim_length / l;
+ polyline.points.front() = a.cast();
+ polyline.points.back() = d.cast();
+ } else
+ polyline.points.clear();
+ } else if (polyline.size() > 2) {
+ Vec2d b = polyline.points[1].cast();
+ Vec2d c = polyline.points[polyline.points.size() - 2].cast();
+ Vec2d v = b - a;
+ double l = v.norm();
+ if (l > trim_length) {
+ a += v * trim_length / l;
+ polyline.points.front() = a.cast();
+ } else
+ polyline.points.erase(polyline.points.begin());
+ v = d - c;
+ l = v.norm();
+ if (l > trim_length)
+ polyline.points.back() = (d - v * trim_length / l).cast();
+ else
+ polyline.points.pop_back();
+ }
+ svg.draw(polyline, "black");
+ }
+
+ svg.draw(overlap_lines, "red", scale_(0.05));
+ svg.draw(polylines, "magenta", scale_(0.05));
+ svg.draw(pts, "magenta");
+}
+#endif // INFILL_DEBUG_OUTPUT
+
+#ifndef NDEBUG
+bool validate_boundary_intersections(const std::vector> &boundary_intersections)
+{
+ for (const std::vector& contour : boundary_intersections) {
+ for (ContourIntersectionPoint* ip : contour) {
+ assert(ip->next_trimmed == ip->next_on_contour->prev_trimmed);
+ assert(ip->prev_trimmed == ip->prev_on_contour->next_trimmed);
+ }
+ }
+ return true;
+}
+#endif // NDEBUG
+
+// Mark the segments of split boundary as consumed if they are very close to some of the infill line.
+void mark_boundary_segments_touching_infill(
+ // Boundary contour, along which the perimeter extrusions will be drawn.
+ const std::vector &boundary,
+ // Parametrization of boundary with Euclidian length.
+ const std::vector> &boundary_parameters,
+ // Intersections (T-joints) of the infill lines with the boundary.
+ std::vector> &boundary_intersections,
+ // Bounding box around the boundary.
+ const BoundingBox &boundary_bbox,
+ // Infill lines, either completely inside the boundary, or touching the boundary.
+ const Polylines &infill,
+ // How much of the infill ends should be ignored when marking the boundary segments?
+ const double clip_distance,
+ // Roughly width of the infill line.
+ const double distance_colliding)
+{
+ assert(boundary.size() == boundary_parameters.size());
+#ifndef NDEBUG
+ for (size_t i = 0; i < boundary.size(); ++ i)
+ assert(boundary[i].size() + 1 == boundary_parameters[i].size());
+ assert(validate_boundary_intersections(boundary_intersections));
+#endif
+
+#ifdef INFILL_DEBUG_OUTPUT
+ static int iRun = 0;
+ ++ iRun;
+ int iStep = 0;
+ export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-start", iRun));
+ Polylines perimeter_overlaps;
+#endif // INFILL_DEBUG_OUTPUT
+
+ EdgeGrid::Grid grid;
+ // Make sure that the the grid is big enough for queries against the thick segment.
+ grid.set_bbox(boundary_bbox.inflated(distance_colliding * 1.43));
+ // Inflate the bounding box by a thick line width.
+ grid.create(boundary, coord_t(std::max(clip_distance, distance_colliding) + scale_(10.)));
+
+ // Visitor for the EdgeGrid to trim boundary_intersections with existing infill lines.
+ struct Visitor {
+ Visitor(const EdgeGrid::Grid &grid,
+ const std::vector &boundary, const std::vector> &boundary_parameters, std::vector> &boundary_intersections,
+ const double radius) :
+ grid(grid), boundary(boundary), boundary_parameters(boundary_parameters), boundary_intersections(boundary_intersections), radius(radius), trim_l_threshold(0.5 * radius) {}
+
+ // Init with a segment of an infill line.
+ void init(const Vec2d &infill_pt1, const Vec2d &infill_pt2) {
+ this->infill_pt1 = &infill_pt1;
+ this->infill_pt2 = &infill_pt2;
+ this->infill_bbox.reset();
+ this->infill_bbox.merge(infill_pt1);
+ this->infill_bbox.merge(infill_pt2);
+ this->infill_bbox.offset(this->radius + SCALED_EPSILON);
+ }
+
+ bool operator()(coord_t iy, coord_t ix) {
+ // Called with a row and colum of the grid cell, which is intersected by a line.
+ auto cell_data_range = this->grid.cell_data_range(iy, ix);
+ for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
+ // End points of the line segment and their vector.
+ auto segment = this->grid.segment(*it_contour_and_segment);
+ std::vector &intersections = boundary_intersections[it_contour_and_segment->first];
+ if (intersections.empty())
+ // There is no infil line touching this contour, thus effort will be saved to calculate overlap with other infill lines.
+ continue;
+ const Vec2d seg_pt1 = segment.first.cast();
+ const Vec2d seg_pt2 = segment.second.cast();
+ std::pair interval;
+ BoundingBoxf bbox_seg;
+ bbox_seg.merge(seg_pt1);
+ bbox_seg.merge(seg_pt2);
+#ifdef INFILL_DEBUG_OUTPUT
+ //if (this->infill_bbox.overlap(bbox_seg)) this->perimeter_overlaps.push_back({ segment.first, segment.second });
+#endif // INFILL_DEBUG_OUTPUT
+ if (this->infill_bbox.overlap(bbox_seg) && line_rounded_thick_segment_collision(seg_pt1, seg_pt2, *this->infill_pt1, *this->infill_pt2, this->radius, interval)) {
+ // The boundary segment intersects with the infill segment thickened by radius.
+ // Interval is specified in Euclidian length from seg_pt1 to seg_pt2.
+ // 1) Find the Euclidian parameters of seg_pt1 and seg_pt2 on its boundary contour.
+ const std::vector &contour_parameters = boundary_parameters[it_contour_and_segment->first];
+ const double contour_length = contour_parameters.back();
+ const double param_seg_pt1 = contour_parameters[it_contour_and_segment->second];
+ const double param_seg_pt2 = contour_parameters[it_contour_and_segment->second + 1];
+#ifdef INFILL_DEBUG_OUTPUT
+ this->perimeter_overlaps.push_back({ Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.first).cast()),
+ Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.second).cast()) });
+#endif // INFILL_DEBUG_OUTPUT
+ assert(interval.first >= 0.);
+ assert(interval.second >= 0.);
+ assert(interval.first <= interval.second);
+ const auto param_overlap1 = std::min(param_seg_pt2, param_seg_pt1 + interval.first);
+ const auto param_overlap2 = std::min(param_seg_pt2, param_seg_pt1 + interval.second);
+ // 2) Find the ContourIntersectionPoints before param_overlap1 and after param_overlap2.
+ // Find the span of ContourIntersectionPoints, that is trimmed by the interval (param_overlap1, param_overlap2).
+ ContourIntersectionPoint *ip_low, *ip_high;
+ if (intersections.size() == 1) {
+ // Only a single infill line touches this contour.
+ ip_low = ip_high = intersections.front();
+ } else {
+ assert(intersections.size() > 1);
+ auto it_low = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap1](const ContourIntersectionPoint *l) { return l->param < param_overlap1; });
+ auto it_high = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap2](const ContourIntersectionPoint *l) { return l->param < param_overlap2; });
+ ip_low = it_low == intersections.end() ? intersections.front() : *it_low;
+ ip_high = it_high == intersections.end() ? intersections.front() : *it_high;
+ if (ip_low->param != param_overlap1)
+ ip_low = ip_low->prev_on_contour;
+ assert(ip_low != ip_high);
+ // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param).
+ assert(cyclic_interval_inside_interval(ip_low->param, ip_high->param, param_overlap1, param_overlap2, contour_length));
+ }
+ assert(validate_boundary_intersections(boundary_intersections));
+ // Mark all ContourIntersectionPoints between ip_low and ip_high as consumed.
+ if (ip_low->next_on_contour != ip_high)
+ for (ContourIntersectionPoint *ip = ip_low->next_on_contour; ip != ip_high; ip = ip->next_on_contour) {
+ ip->consume_prev();
+ ip->consume_next();
+ }
+ // Subtract the interval from the first and last segments.
+ double trim_l = closed_contour_distance_ccw(ip_low->param, param_overlap1, contour_length);
+ //if (trim_l > trim_l_threshold)
+ ip_low->trim_next(trim_l);
+ trim_l = closed_contour_distance_ccw(param_overlap2, ip_high->param, contour_length);
+ //if (trim_l > trim_l_threshold)
+ ip_high->trim_prev(trim_l);
+ assert(ip_low->next_trimmed == ip_high->prev_trimmed);
+ assert(validate_boundary_intersections(boundary_intersections));
+ //FIXME mark point as consumed?
+ //FIXME verify the sequence between prev and next?
+#ifdef INFILL_DEBUG_OUTPUT
+ {
+#if 0
+ static size_t iRun = 0;
+ ExPolygon expoly(Polygon(*grid.contours().front()));
+ for (size_t i = 1; i < grid.contours().size(); ++i)
+ expoly.holes.emplace_back(Polygon(*grid.contours()[i]));
+ SVG svg(debug_out_path("%s-%d.svg", "FillBase-mark_boundary_segments_touching_infill", iRun ++).c_str(), get_extents(expoly));
+ svg.draw(expoly, "green");
+ svg.draw(Line(segment.first, segment.second), "red");
+ svg.draw(Line(this->infill_pt1->cast(), this->infill_pt2->cast()), "magenta");
+#endif
+ }
+#endif // INFILL_DEBUG_OUTPUT
+ }
+ }
+ // Continue traversing the grid along the edge.
+ return true;
+ }
+
+ const EdgeGrid::Grid &grid;
+ const std::vector &boundary;
+ const std::vector> &boundary_parameters;
+ std::vector> &boundary_intersections;
+ // Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line.
+ const double radius;
+ // Region around the contour / infill line intersection point, where the intersections are ignored.
+ const double trim_l_threshold;
+
+ const Vec2d *infill_pt1;
+ const Vec2d *infill_pt2;
+ BoundingBoxf infill_bbox;
+
+#ifdef INFILL_DEBUG_OUTPUT
+ Polylines perimeter_overlaps;
+#endif // INFILL_DEBUG_OUTPUT
+ } visitor(grid, boundary, boundary_parameters, boundary_intersections, distance_colliding);
+
+ for (const Polyline &polyline : infill) {
+#ifdef INFILL_DEBUG_OUTPUT
+ ++ iStep;
+#endif // INFILL_DEBUG_OUTPUT
+ // Clip the infill polyline by the Eucledian distance along the polyline.
+ SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance);
+ SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance);
+ if (start_point.valid() && end_point.valid() &&
+ (start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) {
+ // The clipped polyline is non-empty.
+#ifdef INFILL_DEBUG_OUTPUT
+ visitor.perimeter_overlaps.clear();
+#endif // INFILL_DEBUG_OUTPUT
+ for (size_t point_idx = start_point.idx_segment; point_idx <= end_point.idx_segment; ++ point_idx) {
+//FIXME extend the EdgeGrid to suport tracing a thick line.
+#if 0
+ Point pt1, pt2;
+ Vec2d pt1d, pt2d;
+ if (point_idx == start_point.idx_segment) {
+ pt1d = start_point.point;
+ pt1 = pt1d.cast();
+ } else {
+ pt1 = polyline.points[point_idx];
+ pt1d = pt1.cast();
+ }
+ if (point_idx == start_point.idx_segment) {
+ pt2d = end_point.point;
+ pt2 = pt1d.cast();
+ } else {
+ pt2 = polyline.points[point_idx];
+ pt2d = pt2.cast();
+ }
+ visitor.init(pt1d, pt2d);
+ grid.visit_cells_intersecting_thick_line(pt1, pt2, distance_colliding, visitor);
+#else
+ Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx ].cast();
+ Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx + 1].cast();
+#if 0
+ {
+ static size_t iRun = 0;
+ ExPolygon expoly(Polygon(*grid.contours().front()));
+ for (size_t i = 1; i < grid.contours().size(); ++i)
+ expoly.holes.emplace_back(Polygon(*grid.contours()[i]));
+ SVG svg(debug_out_path("%s-%d.svg", "FillBase-mark_boundary_segments_touching_infill0", iRun ++).c_str(), get_extents(expoly));
+ svg.draw(expoly, "green");
+ svg.draw(polyline, "blue");
+ svg.draw(Line(pt1.cast(), pt2.cast()), "magenta", scale_(0.1));
+ }
+#endif
+ visitor.init(pt1, pt2);
+ // Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size.
+ Vec2d v = (pt2 - pt1).normalized() * distance_colliding;
+ Vec2d vperp = perp(v);
+ Vec2d a = pt1 - v - vperp;
+ Vec2d b = pt2 + v - vperp;
+ assert(grid.bbox().contains(a.cast()));
+ assert(grid.bbox().contains(b.cast()));
+ grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor);
+ a = pt1 - v + vperp;
+ b = pt2 + v + vperp;
+ assert(grid.bbox().contains(a.cast()));
+ assert(grid.bbox().contains(b.cast()));
+ grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor);
+#endif
+#ifdef INFILL_DEBUG_OUTPUT
+// export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d-%03d-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-step", iRun, iStep, int(point_idx)), { polyline });
+#endif // INFILL_DEBUG_OUTPUT
+ }
+#ifdef INFILL_DEBUG_OUTPUT
+ Polylines perimeter_overlaps;
+ export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-step", iRun, iStep), visitor.perimeter_overlaps, { polyline });
+ append(perimeter_overlaps, std::move(visitor.perimeter_overlaps));
+ perimeter_overlaps.clear();
+#endif // INFILL_DEBUG_OUTPUT
+ }
+ }
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-end", iRun), perimeter_overlaps);
+#endif // INFILL_DEBUG_OUTPUT
+ assert(validate_boundary_intersections(boundary_intersections));
+}
+
+void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams ¶ms)
+{
+ assert(! boundary_src.contour.points.empty());
+ auto polygons_src = reserve_vector(boundary_src.holes.size() + 1);
+ polygons_src.emplace_back(&boundary_src.contour);
+ for (const Polygon &polygon : boundary_src.holes)
+ polygons_src.emplace_back(&polygon);
+
+ connect_infill(std::move(infill_ordered), polygons_src, get_extents(boundary_src.contour), polylines_out, spacing, params);
+}
+
+void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms)
+{
+ auto polygons_src = reserve_vector(boundary_src.size());
+ for (const Polygon &polygon : boundary_src)
+ polygons_src.emplace_back(&polygon);
+
+ connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params);
+}
+
+static constexpr auto boundary_idx_unconnected = std::numeric_limits::max();
+
+struct BoundaryInfillGraph
+{
+ std::vector boundary;
+ std::vector> boundary_params;
+ std::vector map_infill_end_point_to_boundary;
+
+ const Point& point(const ContourIntersectionPoint &cp) const {
+ assert(cp.contour_idx != size_t(-1));
+ assert(cp.point_idx != size_t(-1));
+ return this->boundary[cp.contour_idx][cp.point_idx];
+ }
+
+ const Point& infill_end_point(size_t infill_end_point_idx) const {
+ return this->point(this->map_infill_end_point_to_boundary[infill_end_point_idx]);
+ }
+
+ const Point interpolate_contour_point(const ContourIntersectionPoint &cp, double param) {
+ const Points &contour = this->boundary[cp.contour_idx];
+ const std::vector &contour_params = this->boundary_params[cp.contour_idx];
+ // Find the start of a contour segment with param.
+ auto it = std::lower_bound(contour_params.begin(), contour_params.end(), param);
+ if (*it != param) {
+ assert(it != contour_params.begin());
+ -- it;
+ }
+ size_t i = it - contour_params.begin();
+ if (i == contour.size())
+ i = 0;
+ double t1 = contour_params[i];
+ double t2 = next_value_modulo(i, contour_params);
+ return lerp(contour[i], next_value_modulo(i, contour), (param - t1) / (t2 - t1));
+ }
+
+ enum Direction {
+ Left,
+ Right,
+ Up,
+ Down,
+ Taken,
+ };
+
+ static Direction dir(const Point &p1, const Point &p2) {
+ return p1.x() == p2.x() ?
+ (p1.y() < p2.y() ? Up : Down) :
+ (p1.x() < p2.x() ? Right : Left);
+ }
+
+ const Direction dir_prev(const ContourIntersectionPoint &cp) const {
+ assert(cp.prev_on_contour);
+ return cp.could_take_prev() ?
+ dir(this->point(cp), this->point(*cp.prev_on_contour)) :
+ Taken;
+ }
+
+ const Direction dir_next(const ContourIntersectionPoint &cp) const {
+ assert(cp.next_on_contour);
+ return cp.could_take_next() ?
+ dir(this->point(cp), this->point(*cp.next_on_contour)) :
+ Taken;
+ }
+
+ bool first(const ContourIntersectionPoint &cp) const {
+ return ((&cp - this->map_infill_end_point_to_boundary.data()) & 1) == 0;
+ }
+
+ const ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) const {
+ return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)];
+ }
+
+ ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) {
+ return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)];
+ }
+
+ bool prev_vertical(const ContourIntersectionPoint &cp) const {
+ return this->point(cp).x() == this->point(*cp.prev_on_contour).x();
+ }
+
+ bool next_vertical(const ContourIntersectionPoint &cp) const {
+ return this->point(cp).x() == this->point(*cp.next_on_contour).x();
+ }
+
+};
+
+
+// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines,
+// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully
+// Mark those short boundary segments.
+static inline void mark_boundary_segments_overlapping_infill(
+ BoundaryInfillGraph &graph,
+ // Infill lines, either completely inside the boundary, or touching the boundary.
+ const Polylines &infill,
+ // Spacing (width) of the infill lines.
+ const double spacing)
+{
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ const Points &contour = graph.boundary[cp.contour_idx];
+ const std::vector &contour_params = graph.boundary_params[cp.contour_idx];
+ const Polyline &infill_polyline = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2];
+ const double radius = 0.5 * (spacing + SCALED_EPSILON);
+ assert(infill_polyline.size() == 2);
+ const Linef infill_line { infill_polyline.points.front().cast(), infill_polyline.points.back().cast() };
+ if (cp.could_take_next()) {
+ bool inside = true;
+ for (size_t i = cp.point_idx; i != cp.next_on_contour->point_idx; ) {
+ size_t j = next_idx_modulo(i, contour);
+ const Vec2d seg_pt2 = contour[j].cast();
+ if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) {
+ // The segment is completely inside.
+ } else {
+ std::pair interval;
+ line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval);
+ assert(interval.first == 0.);
+ double len_out = closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second;
+ if (len_out < cp.contour_not_taken_length_next) {
+ // Leaving the infill line region before exiting cp.contour_not_taken_length_next,
+ // thus at least some of the contour is outside and we will extrude this segment.
+ inside = false;
+ break;
+ }
+ }
+ if (closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_next)
+ break;
+ i = j;
+ }
+ if (inside) {
+ if (! cp.next_trimmed)
+ // The arc from cp to cp.next_on_contour was not trimmed yet, however it is completely overlapping the infill line.
+ cp.next_on_contour->trim_prev(0);
+ cp.trim_next(0);
+ }
+ } else
+ cp.trim_next(0);
+ if (cp.could_take_prev()) {
+ bool inside = true;
+ for (size_t i = cp.point_idx; i != cp.prev_on_contour->point_idx; ) {
+ size_t j = prev_idx_modulo(i, contour);
+ const Vec2d seg_pt2 = contour[j].cast();
+ // Distance of the second segment line from the infill line.
+ if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) {
+ // The segment is completely inside.
+ } else {
+ std::pair interval;
+ line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval);
+ assert(interval.first == 0.);
+ double len_out = closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second;
+ if (len_out < cp.contour_not_taken_length_prev) {
+ // Leaving the infill line region before exiting cp.contour_not_taken_length_next,
+ // thus at least some of the contour is outside and we will extrude this segment.
+ inside = false;
+ break;
+ }
+ }
+ if (closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_prev)
+ break;
+ i = j;
+ }
+ if (inside) {
+ if (! cp.prev_trimmed)
+ // The arc from cp to cp.prev_on_contour was not trimmed yet, however it is completely overlapping the infill line.
+ cp.prev_on_contour->trim_next(0);
+ cp.trim_prev(0);
+ }
+ } else
+ cp.trim_prev(0);
+ }
+}
+
+BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, const double spacing)
+{
+ BoundaryInfillGraph out;
+ out.boundary.assign(boundary_src.size(), Points());
+ out.boundary_params.assign(boundary_src.size(), std::vector());
+ out.map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected });
+ {
+ // Project the infill_ordered end points onto boundary_src.
+ std::vector> intersection_points;
+ {
+ EdgeGrid::Grid grid;
+ grid.set_bbox(bbox.inflated(SCALED_EPSILON));
+ grid.create(boundary_src, coord_t(scale_(10.)));
+ intersection_points.reserve(infill_ordered.size() * 2);
+ for (const Polyline &pl : infill_ordered)
+ for (const Point *pt : { &pl.points.front(), &pl.points.back() }) {
+ EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(*pt, coord_t(SCALED_EPSILON));
+ if (cp.valid()) {
+ // The infill end point shall lie on the contour.
+ assert(cp.distance <= 3.);
+ intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1));
+ }
+ }
+ std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair &cp1, const std::pair &cp2) {
+ return cp1.first.contour_idx < cp2.first.contour_idx ||
+ (cp1.first.contour_idx == cp2.first.contour_idx &&
+ (cp1.first.start_point_idx < cp2.first.start_point_idx ||
+ (cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t)));
+ });
+ }
+ auto it = intersection_points.begin();
+ auto it_end = intersection_points.end();
+ std::vector> boundary_intersection_points(out.boundary.size(), std::vector());
+ for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) {
+ // Copy contour_src to contour_dst while adding intersection points.
+ // Map infill end points map_infill_end_point_to_boundary to the newly inserted boundary points of contour_dst.
+ // chain the points of map_infill_end_point_to_boundary along their respective contours.
+ const Polygon &contour_src = *boundary_src[idx_contour];
+ Points &contour_dst = out.boundary[idx_contour];
+ std::vector &contour_intersection_points = boundary_intersection_points[idx_contour];
+ ContourIntersectionPoint *pfirst = nullptr;
+ ContourIntersectionPoint *pprev = nullptr;
+ {
+ // Reserve intersection points.
+ size_t n_intersection_points = 0;
+ for (auto itx = it; itx != it_end && itx->first.contour_idx == idx_contour; ++ itx)
+ ++ n_intersection_points;
+ contour_intersection_points.reserve(n_intersection_points);
+ }
+ for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) {
+ const Point &ipt = contour_src.points[idx_point];
+ if (contour_dst.empty() || contour_dst.back() != ipt)
+ contour_dst.emplace_back(ipt);
+ for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) {
+ // Add these points to the destination contour.
+ const Polyline &infill_line = infill_ordered[it->second / 2];
+ const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front();
+//#ifndef NDEBUG
+// {
+// const Vec2d pt1 = ipt.cast();
+// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast();
+// const Vec2d ptx = lerp(pt1, pt2, it->first.t);
+// assert(std::abs(ptx.x() - pt.x()) < SCALED_EPSILON);
+// assert(std::abs(ptx.y() - pt.y()) < SCALED_EPSILON);
+// }
+//#endif // NDEBUG
+ size_t idx_tjoint_pt = 0;
+ if (idx_point + 1 < contour_src.size() || pt != contour_dst.front()) {
+ if (pt != contour_dst.back())
+ contour_dst.emplace_back(pt);
+ idx_tjoint_pt = contour_dst.size() - 1;
+ }
+ out.map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ /* it->second, */ idx_contour, idx_tjoint_pt };
+ ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second];
+ if (pprev) {
+ pprev->next_on_contour = pthis;
+ pthis->prev_on_contour = pprev;
+ } else
+ pfirst = pthis;
+ contour_intersection_points.emplace_back(pthis);
+ pprev = pthis;
+ }
+ if (pfirst) {
+ pprev->next_on_contour = pfirst;
+ pfirst->prev_on_contour = pprev;
+ }
+ }
+ // Parametrize the new boundary with the intersection points inserted.
+ std::vector &contour_params = out.boundary_params[idx_contour];
+ contour_params.assign(contour_dst.size() + 1, 0.);
+ for (size_t i = 1; i < contour_dst.size(); ++i) {
+ contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm();
+ assert(contour_params[i] > contour_params[i - 1]);
+ }
+ contour_params.back() = contour_params[contour_params.size() - 2] + (contour_dst.back().cast() - contour_dst.front().cast()).norm();
+ assert(contour_params.back() > contour_params[contour_params.size() - 2]);
+ // Map parameters from contour_params to boundary_intersection_points.
+ for (ContourIntersectionPoint *ip : contour_intersection_points)
+ ip->param = contour_params[ip->point_idx];
+ // and measure distance to the previous and next intersection point.
+ const double contour_length = contour_params.back();
+ for (ContourIntersectionPoint *ip : contour_intersection_points)
+ if (ip->next_on_contour == ip) {
+ assert(ip->prev_on_contour == ip);
+ ip->contour_not_taken_length_prev = ip->contour_not_taken_length_next = contour_length;
+ } else {
+ assert(ip->prev_on_contour != ip);
+ ip->contour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length);
+ ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length);
+ }
+ }
+
+ assert(out.boundary.size() == boundary_src.size());
+#if 0
+ // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary.
+ assert(std::all_of(out.map_infill_end_point_to_boundary.begin(), out.map_infill_end_point_to_boundary.end(),
+ [&out.boundary](const ContourIntersectionPoint &contour_point) {
+ return contour_point.contour_idx < out.boundary.size() && contour_point.point_idx < out.boundary[contour_point.contour_idx].size();
+ }));
+#endif
+
+ // Mark the points and segments of split out.boundary as consumed if they are very close to some of the infill line.
+ {
+ // @supermerill used 2. * scale_(spacing)
+ const double clip_distance = 1.7 * scale_(spacing);
+ // Allow a bit of overlap. This value must be slightly higher than the overlap of FillAdaptive, otherwise
+ // the anchors of the adaptive infill will mask the other side of the perimeter line.
+ // (see connect_lines_using_hooks() in FillAdaptive.cpp)
+ const double distance_colliding = 0.8 * scale_(spacing);
+ mark_boundary_segments_touching_infill(out.boundary, out.boundary_params, boundary_intersection_points, bbox, infill_ordered, clip_distance, distance_colliding);
+ }
+ }
+
+ return out;
+}
+
+void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms)
+{
+ assert(! infill_ordered.empty());
+ assert(params.anchor_length >= 0.);
+ assert(params.anchor_length_max >= 0.01f);
+ assert(params.anchor_length_max >= params.anchor_length);
+ const double anchor_length = scale_(params.anchor_length);
+ const double anchor_length_max = scale_(params.anchor_length_max);
+
+#if 0
+ append(polylines_out, infill_ordered);
+ return;
+#endif
+
+ BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing);
+
+ std::vector merged_with(infill_ordered.size());
+ std::iota(merged_with.begin(), merged_with.end(), 0);
+
+ auto get_and_update_merged_with = [&merged_with](size_t polyline_idx) -> size_t {
+ for (size_t last = polyline_idx;;) {
+ size_t lower = merged_with[last];
+ assert(lower <= last);
+ if (lower == last) {
+ merged_with[polyline_idx] = last;
+ return last;
+ }
+ last = lower;
+ }
+ assert(false);
+ return std::numeric_limits::max();
+ };
+
+ const double line_half_width = 0.5 * scale_(spacing);
+
+#if 0
+ // Connection from end of one infill line to the start of another infill line.
+ //const double length_max = scale_(spacing);
+// const auto length_max = double(scale_((2. / params.density) * spacing));
+ const auto length_max = double(scale_((1000. / params.density) * spacing));
+ struct ConnectionCost {
+ ConnectionCost(size_t idx_first, double cost, bool reversed) : idx_first(idx_first), cost(cost), reversed(reversed) {}
+ size_t idx_first;
+ double cost;
+ bool reversed;
+ };
+ std::vector connections_sorted;
+ connections_sorted.reserve(infill_ordered.size() * 2 - 2);
+ for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) {
+ const ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1];
+ const ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[idx_chain * 2];
+ if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) {
+ // End points on the same contour. Try to connect them.
+ std::pair len = path_lengths_along_contour(cp1, cp2, graph.boundary_params[cp1->contour_idx].back());
+ if (len.first < length_max)
+ connections_sorted.emplace_back(idx_chain - 1, len.first, false);
+ if (len.second < length_max)
+ connections_sorted.emplace_back(idx_chain - 1, len.second, true);
+ }
+ }
+ std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; });
+
+ for (ConnectionCost &connection_cost : connections_sorted) {
+ ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1];
+ ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2];
+ assert(cp1 != cp2);
+ assert(cp1->contour_idx == cp2->contour_idx && cp1->contour_idx != boundary_idx_unconnected);
+ if (cp1->consumed || cp2->consumed)
+ continue;
+ const double length = connection_cost.cost;
+ bool could_connect;
+ {
+ // cp1, cp2 sorted CCW.
+ ContourIntersectionPoint *cp_low = connection_cost.reversed ? cp2 : cp1;
+ ContourIntersectionPoint *cp_high = connection_cost.reversed ? cp1 : cp2;
+ assert(std::abs(length - closed_contour_distance_ccw(cp_low->param, cp_high->param, graph.boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON);
+ could_connect = ! cp_low->next_trimmed && ! cp_high->prev_trimmed;
+ if (could_connect && cp_low->next_on_contour != cp_high) {
+ // Other end of cp1, may or may not be on the same contour as cp1.
+ const ContourIntersectionPoint *cp1prev = cp1 - 1;
+ // Other end of cp2, may or may not be on the same contour as cp2.
+ const ContourIntersectionPoint *cp2next = cp2 + 1;
+ for (auto *cp = cp_low->next_on_contour; cp != cp_high; cp = cp->next_on_contour)
+ if (cp->consumed || cp == cp1prev || cp == cp2next || cp->prev_trimmed || cp->next_trimmed) {
+ could_connect = false;
+ break;
+ }
+ }
+ }
+ // Indices of the polylines to be connected by a perimeter segment.
+ size_t idx_first = connection_cost.idx_first;
+ size_t idx_second = idx_first + 1;
+ idx_first = get_and_update_merged_with(idx_first);
+ assert(idx_first < idx_second);
+ assert(idx_second == merged_with[idx_second]);
+ if (could_connect && length < anchor_length_max) {
+ // Take the complete contour.
+ // Connect the two polygons using the boundary contour.
+ take(infill_ordered[idx_first], infill_ordered[idx_second], graph.boundary[cp1->contour_idx], cp1, cp2, connection_cost.reversed);
+ // Mark the second polygon as merged with the first one.
+ merged_with[idx_second] = merged_with[idx_first];
+ infill_ordered[idx_second].points.clear();
+ } else {
+ // Try to connect cp1 resp. cp2 with a piece of perimeter line.
+ take_limited(infill_ordered[idx_first], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width);
+ take_limited(infill_ordered[idx_second], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width);
+ }
+ }
+#endif
+
+ struct Arc {
+ ContourIntersectionPoint *intersection;
+ double arc_length;
+ };
+ std::vector arches;
+ if (!params.dont_sort) {
+ arches.reserve(graph.map_infill_end_point_to_boundary.size());
+ for (ContourIntersectionPoint& cp : graph.map_infill_end_point_to_boundary)
+ if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next())
+ arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, graph.boundary_params[cp.contour_idx].back()) });
+ std::sort(arches.begin(), arches.end(), [](const auto& l, const auto& r) { return l.arc_length < r.arc_length; });
+ }
+
+ //FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization.
+ for (Arc &arc : arches)
+ if (! arc.intersection->consumed && ! arc.intersection->next_on_contour->consumed) {
+ // Indices of the polylines to be connected by a perimeter segment.
+ ContourIntersectionPoint *cp1 = arc.intersection;
+ ContourIntersectionPoint *cp2 = arc.intersection->next_on_contour;
+ size_t polyline_idx1 = get_and_update_merged_with(((cp1 - graph.map_infill_end_point_to_boundary.data()) / 2));
+ size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2));
+ const Points &contour = graph.boundary[cp1->contour_idx];
+ const std::vector &contour_params = graph.boundary_params[cp1->contour_idx];
+ if (polyline_idx1 != polyline_idx2) {
+ Polyline &polyline1 = infill_ordered[polyline_idx1];
+ Polyline &polyline2 = infill_ordered[polyline_idx2];
+ if (arc.arc_length < anchor_length_max) {
+ // Not closing a loop, connecting the lines.
+ assert(contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back());
+ if (contour[cp1->point_idx] == polyline1.points.front())
+ polyline1.reverse();
+ assert(contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back());
+ if (contour[cp2->point_idx] == polyline2.points.back())
+ polyline2.reverse();
+ take(polyline1, polyline2, contour, cp1, cp2, false);
+ // Mark the second polygon as merged with the first one.
+ if (polyline_idx2 < polyline_idx1) {
+ polyline2 = std::move(polyline1);
+ polyline1.points.clear();
+ merged_with[polyline_idx1] = merged_with[polyline_idx2];
+ } else {
+ polyline2.points.clear();
+ merged_with[polyline_idx2] = merged_with[polyline_idx1];
+ }
+ } else if (anchor_length > SCALED_EPSILON) {
+ // Move along the perimeter, but don't take the whole arc.
+ take_limited(polyline1, contour, contour_params, cp1, cp2, false, anchor_length, line_half_width);
+ take_limited(polyline2, contour, contour_params, cp2, cp1, true, anchor_length, line_half_width);
+ }
+ }
+ }
+
+ // Connect the remaining open infill lines to the perimeter lines if possible.
+ for (ContourIntersectionPoint &contour_point : graph.map_infill_end_point_to_boundary)
+ if (! contour_point.consumed && contour_point.contour_idx != boundary_idx_unconnected) {
+ const Points &contour = graph.boundary[contour_point.contour_idx];
+ const std::vector &contour_params = graph.boundary_params[contour_point.contour_idx];
+
+ double lprev = contour_point.could_connect_prev() ?
+ path_length_along_contour_ccw(contour_point.prev_on_contour, &contour_point, contour_params.back()) :
+ std::numeric_limits::max();
+ double lnext = contour_point.could_connect_next() ?
+ path_length_along_contour_ccw(&contour_point, contour_point.next_on_contour, contour_params.back()) :
+ std::numeric_limits::max();
+ size_t polyline_idx = get_and_update_merged_with(((&contour_point - graph.map_infill_end_point_to_boundary.data()) / 2));
+ Polyline &polyline = infill_ordered[polyline_idx];
+ assert(! polyline.empty());
+ assert(contour[contour_point.point_idx] == polyline.points.front() || contour[contour_point.point_idx] == polyline.points.back());
+ bool connected = false;
+ for (double l : { std::min(lprev, lnext), std::max(lprev, lnext) }) {
+ if (l == std::numeric_limits::max() || l > anchor_length_max)
+ break;
+ // Take the complete contour.
+ bool reversed = l == lprev;
+ ContourIntersectionPoint *cp2 = reversed ? contour_point.prev_on_contour : contour_point.next_on_contour;
+ // Identify which end of the polyline touches the boundary.
+ size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2));
+ if (polyline_idx == polyline_idx2)
+ // Try the other side.
+ continue;
+ // Not closing a loop.
+ if (contour[contour_point.point_idx] == polyline.points.front())
+ polyline.reverse();
+ Polyline &polyline2 = infill_ordered[polyline_idx2];
+ assert(! polyline.empty());
+ assert(contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back());
+ if (contour[cp2->point_idx] == polyline2.points.back())
+ polyline2.reverse();
+ take(polyline, polyline2, contour, &contour_point, cp2, reversed);
+ if (polyline_idx < polyline_idx2) {
+ // Mark the second polyline as merged with the first one.
+ merged_with[polyline_idx2] = polyline_idx;
+ polyline2.points.clear();
+ } else {
+ // Mark the first polyline as merged with the second one.
+ merged_with[polyline_idx] = polyline_idx2;
+ polyline2 = std::move(polyline);
+ polyline.points.clear();
+ }
+ connected = true;
+ break;
+ }
+ if (! connected && anchor_length > SCALED_EPSILON) {
+ // Which to take? One could optimize for:
+ // 1) Shortest path
+ // 2) Hook length
+ // ...
+ // Let's take the longer now, as this improves the chance of another hook to be placed on the other side of this contour point.
+ double l = std::max(contour_point.contour_not_taken_length_prev, contour_point.contour_not_taken_length_next);
+ if (l > SCALED_EPSILON) {
+ if (contour_point.contour_not_taken_length_prev > contour_point.contour_not_taken_length_next)
+ take_limited(polyline, contour, contour_params, &contour_point, contour_point.prev_on_contour, true, anchor_length, line_half_width);
+ else
+ take_limited(polyline, contour, contour_params, &contour_point, contour_point.next_on_contour, false, anchor_length, line_half_width);
+ }
+ }
+ }
+
+ polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); }));
+ for (Polyline &pl : infill_ordered)
+ if (! pl.empty())
+ polylines_out.emplace_back(std::move(pl));
+}
+
+// Extend the infill lines along the perimeters, this is mainly useful for grid aligned support, where a perimeter line may be nearly
+// aligned with the infill lines.
+static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryInfillGraph &graph, const double spacing, const FillParams ¶ms)
+{
+/*
+ // Backup the source lines.
+ Lines lines;
+ lines.reserve(linfill.size());
+ std::transform(infill.begin(), infill.end(), std::back_inserter(lines), [](const Polyline &pl) { assert(pl.size() == 2); return Line(pl.points.begin(), pl.points.end()); });
+*/
+
+ const double line_spacing = scale_(spacing) / params.density;
+ // Maximum deviation perpendicular to the infill line to allow merging as a continuation of the same infill line.
+ const auto dist_max_x = coord_t(line_spacing * 0.33);
+ // Minimum length of the arc away from the infill end point to allow merging as a continuation of the same infill line.
+ const auto dist_min_y = coord_t(line_spacing * 0.5);
+
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ const Points &contour = graph.boundary[cp.contour_idx];
+ const std::vector &contour_param = graph.boundary_params[cp.contour_idx];
+ const Point &pt = contour[cp.point_idx];
+ const bool first = graph.first(cp);
+ int extend_next_idx = -1;
+ int extend_prev_idx = -1;
+ coord_t dist_y_prev;
+ coord_t dist_y_next;
+ double arc_len_prev;
+ double arc_len_next;
+
+ if (! graph.next_vertical(cp)){
+ size_t i = cp.point_idx;
+ size_t j = next_idx_modulo(i, contour);
+ while (j != cp.next_on_contour->point_idx) {
+ //const Point &p1 = contour[i];
+ const Point &p2 = contour[j];
+ if (std::abs(p2.x() - pt.x()) > dist_max_x)
+ break;
+ i = j;
+ j = next_idx_modulo(j, contour);
+ }
+ if (i != cp.point_idx) {
+ const Point &p2 = contour[i];
+ coord_t dist_y = p2.y() - pt.y();
+ if (first)
+ dist_y = - dist_y;
+ if (dist_y > dist_min_y) {
+ arc_len_next = closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[i], contour_param.back());
+ if (arc_len_next < cp.contour_not_taken_length_next) {
+ extend_next_idx = i;
+ dist_y_next = dist_y;
+ }
+ }
+ }
+ }
+
+ if (! graph.prev_vertical(cp)) {
+ size_t i = cp.point_idx;
+ size_t j = prev_idx_modulo(i, contour);
+ while (j != cp.prev_on_contour->point_idx) {
+ //const Point &p1 = contour[i];
+ const Point &p2 = contour[j];
+ if (std::abs(p2.x() - pt.x()) > dist_max_x)
+ break;
+ i = j;
+ j = prev_idx_modulo(j, contour);
+ }
+ if (i != cp.point_idx) {
+ const Point &p2 = contour[i];
+ coord_t dist_y = p2.y() - pt.y();
+ if (first)
+ dist_y = - dist_y;
+ if (dist_y > dist_min_y) {
+ arc_len_prev = closed_contour_distance_ccw(contour_param[i], contour_param[cp.point_idx], contour_param.back());
+ if (arc_len_prev < cp.contour_not_taken_length_prev) {
+ extend_prev_idx = i;
+ dist_y_prev = dist_y;
+ }
+ }
+ }
+ }
+
+ if (extend_prev_idx >= 0 && extend_next_idx >= 0)
+ // Which side to move the point?
+ dist_y_prev < dist_y_next ? extend_prev_idx : extend_next_idx = -1;
+
+ assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed);
+ assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed);
+ Polyline &infill_line = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2];
+ if (extend_prev_idx >= 0) {
+ if (first)
+ infill_line.reverse();
+ take_cw_full(infill_line, contour, cp.point_idx, extend_prev_idx);
+ if (first)
+ infill_line.reverse();
+ cp.point_idx = extend_prev_idx;
+ if (cp.prev_trimmed)
+ cp.contour_not_taken_length_prev -= arc_len_prev;
+ else
+ cp.contour_not_taken_length_prev = cp.prev_on_contour->contour_not_taken_length_next =
+ closed_contour_distance_ccw(contour_param[cp.prev_on_contour->point_idx], contour_param[cp.point_idx], contour_param.back());
+ cp.trim_next(0);
+ cp.next_on_contour->prev_trimmed = true;
+ } else if (extend_next_idx >= 0) {
+ if (first)
+ infill_line.reverse();
+ take_ccw_full(infill_line, contour, cp.point_idx, extend_next_idx);
+ if (first)
+ infill_line.reverse();
+ cp.point_idx = extend_next_idx;
+ cp.trim_prev(0);
+ cp.prev_on_contour->next_trimmed = true;
+ if (cp.next_trimmed)
+ cp.contour_not_taken_length_next -= arc_len_next;
+ else
+ cp.contour_not_taken_length_next = cp.next_on_contour->contour_not_taken_length_prev =
+ closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[cp.next_on_contour->point_idx], contour_param.back());
+ }
+ }
+}
+
+// Called by Fill::connect_base_support() as part of the sparse support infill generator.
+// Emit contour loops tracing the contour from tbegin to tend inside a band of (left, right).
+// The contour is supposed to enter the "forbidden" zone outside of the (left, right) band at tbegin and also at tend.
+static inline void emit_loops_in_band(
+ // Vertical band, which will trim the contour between tbegin and tend.
+ coord_t left,
+ coord_t right,
+ // Contour and its parametrization.
+ const Points &contour,
+ const std::vector &contour_params,
+ // Span of the parameters of an arch to trim with the vertical band.
+ double tbegin,
+ double tend,
+ // Minimum arch length to put into polylines_out. Shorter arches are not necessary to support a dense support infill.
+ double min_length,
+ Polylines &polylines_out)
+{
+ assert(left < right);
+ assert(contour.size() + 1 == contour_params.size());
+ assert(contour.size() >= 3);
+#ifndef NDEBUG
+ double contour_length = contour_params.back();
+ assert(tbegin >= 0 && tbegin < contour_length);
+ assert(tend >= 0 && tend < contour_length);
+ assert(min_length > 0);
+#endif // NDEBUG
+
+ // Find iterators of the range of segments, where the first and last segment contains tbegin and tend.
+ size_t ibegin, iend;
+ {
+ auto it_begin = std::lower_bound(contour_params.begin(), contour_params.end(), tbegin);
+ auto it_end = std::lower_bound(contour_params.begin(), contour_params.end(), tend);
+ assert(it_begin != contour_params.end());
+ assert(it_end != contour_params.end());
+ if (*it_begin != tbegin) {
+ assert(it_begin != contour_params.begin());
+ -- it_begin;
+ }
+ ibegin = it_begin - contour_params.begin();
+ iend = it_end - contour_params.begin();
+ }
+
+ if (ibegin == contour.size())
+ ibegin = 0;
+ if (iend == contour.size())
+ iend = 0;
+ assert(ibegin != iend);
+
+ // Trim the start and end segment to calculate start and end points.
+ Point pbegin, pend;
+ {
+ double t1 = contour_params[ibegin];
+ double t2 = next_value_modulo(ibegin, contour_params);
+ pbegin = lerp(contour[ibegin], next_value_modulo(ibegin, contour), (tbegin - t1) / (t2 - t1));
+ t1 = contour_params[iend];
+ t2 = prev_value_modulo(iend, contour_params);
+ pend = lerp(contour[iend], prev_value_modulo(iend, contour), (tend - t1) / (t2 - t1));
+ }
+
+ // Trace the contour from ibegin to iend.
+ enum Side {
+ Left,
+ Right,
+ Mid,
+ Unknown
+ };
+
+ enum InOutBand {
+ Entering,
+ Leaving,
+ };
+
+ class State {
+ public:
+ State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) :
+ m_left(left), m_right(right), m_min_length(min_length), m_polylines_out(polylines_out) {}
+
+ void add_inner_point(const Point* p)
+ {
+ m_polyline.points.emplace_back(*p);
+ }
+
+ void add_outer_point(const Point* p)
+ {
+ if (m_polyline_end > 0)
+ m_polyline.points.emplace_back(*p);
+ }
+
+ void add_interpolated_point(const Point* p1, const Point* p2, Side side, InOutBand inout)
+ {
+ assert(side == Left || side == Right);
+
+ coord_t x = side == Left ? m_left : m_right;
+ coord_t y = p1->y() + coord_t(double(x - p1->x()) * double(p2->y() - p1->y()) / double(p2->x() - p1->x()));
+
+ if (inout == Leaving) {
+ assert(m_polyline_end == 0);
+ m_polyline_end = m_polyline.size();
+ m_polyline.points.emplace_back(x, y);
+ } else {
+ assert(inout == Entering);
+ if (m_polyline_end > 0) {
+ if ((this->side1 == Left) == (y - m_polyline.points[m_polyline_end].y() < 0)) {
+ // Emit the vertical segment. Remove the point, where the source contour was split the last time at m_left / m_right.
+ m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end);
+ } else {
+ // Don't emit the vertical segment, split the contour.
+ this->finalize();
+ m_polyline.points.emplace_back(x, y);
+ }
+ m_polyline_end = 0;
+ } else
+ m_polyline.points.emplace_back(x, y);
+ }
+ };
+
+ void finalize()
+ {
+ m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end, m_polyline.points.end());
+ if (! m_polyline.empty()) {
+ if (! m_polylines_out.empty() && (m_polylines_out.back().points.back() - m_polyline.points.front()).cast().squaredNorm() < SCALED_EPSILON)
+ m_polylines_out.back().points.insert(m_polylines_out.back().points.end(), m_polyline.points.begin() + 1, m_polyline.points.end());
+ else if (m_polyline.length() > m_min_length)
+ m_polylines_out.emplace_back(std::move(m_polyline));
+ m_polyline.clear();
+ }
+ };
+
+ private:
+ coord_t m_left;
+ coord_t m_right;
+ double m_min_length;
+ Polylines &m_polylines_out;
+
+ Polyline m_polyline;
+ size_t m_polyline_end { 0 };
+ Polyline m_overlapping;
+
+ public:
+ Side side1 { Unknown };
+ Side side2 { Unknown };
+ };
+
+ State state { left, right, min_length, polylines_out };
+
+ const Point *p1 = &pbegin;
+ auto side = [left, right](const Point* p) {
+ coord_t x = p->x();
+ return x < left ? Left : x > right ? Right : Mid;
+ };
+ state.side1 = side(p1);
+ if (state.side1 == Mid)
+ state.add_inner_point(p1);
+
+ for (size_t i = ibegin; i != iend; ) {
+ size_t inext = i + 1;
+ if (inext == contour.size())
+ inext = 0;
+ const Point *p2 = inext == iend ? &pend : &contour[inext];
+ state.side2 = side(p2);
+ if (state.side1 == Mid) {
+ if (state.side2 == Mid) {
+ // Inside the band.
+ state.add_inner_point(p2);
+ } else {
+ // From intisde the band to the outside of the band.
+ state.add_interpolated_point(p1, p2, state.side2, Leaving);
+ state.add_outer_point(p2);
+ }
+ } else if (state.side2 == Mid) {
+ // From outside the band into the band.
+ state.add_interpolated_point(p1, p2, state.side1, Entering);
+ state.add_inner_point(p2);
+ } else if (state.side1 != state.side2) {
+ // Both points outside the band.
+ state.add_interpolated_point(p1, p2, state.side1, Entering);
+ state.add_interpolated_point(p1, p2, state.side2, Leaving);
+ } else {
+ // Complete segment is outside.
+ assert((state.side1 == Left && state.side2 == Left) || (state.side1 == Right && state.side2 == Right));
+ state.add_outer_point(p2);
+ }
+ state.side1 = state.side2;
+ p1 = p2;
+ i = inext;
+ }
+ state.finalize();
+}
+
+#ifdef INFILL_DEBUG_OUTPUT
+static void export_partial_infill_to_svg(const std::string &path, const BoundaryInfillGraph &graph, const Polylines &infill, const Polylines &emitted)
+{
+ Polygons polygons;
+ for (const Points &pts : graph.boundary)
+ polygons.emplace_back(pts);
+ BoundingBox bbox = get_extents(polygons);
+ bbox.merge(get_extents(infill));
+ ::Slic3r::SVG svg(path, bbox);
+ svg.draw(union_ex(polygons));
+ svg.draw(infill, "blue");
+ svg.draw(emitted, "darkblue");
+ for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary)
+ svg.draw(graph.point(cp), cp.consumed ? "red" : "green", scaled(0.2));
+ for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed);
+ assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed);
+ if (cp.contour_not_taken_length_next > SCALED_EPSILON) {
+ Polyline pl { graph.point(cp) };
+ take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next);
+ svg.draw(pl, cp.could_take_next() ? "lime" : "magenta", scaled(0.1));
+ }
+ if (cp.contour_not_taken_length_prev > SCALED_EPSILON) {
+ Polyline pl { graph.point(cp) };
+ take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev);
+ svg.draw(pl, cp.could_take_prev() ? "lime" : "magenta", scaled(0.1));
+ }
+ }
+}
+#endif // INFILL_DEBUG_OUTPUT
+
+// To classify perimeter segments connecting infill lines, whether they are required for structural stability of the supports.
+struct SupportArcCost
+{
+ // Connecting one end of an infill line to the other end of the same infill line.
+ bool self_loop { false };
+ // Some of the arc touches some infill line.
+ bool open { false };
+ // How needed is this arch for support structural stability.
+ // Zero means don't take. The higher number, the more likely it is to take the arc.
+ double cost { 0 };
+};
+
+static double evaluate_support_arch_cost(const Polyline &pl)
+{
+ Point front = pl.points.front();
+ Point back = pl.points.back();
+
+ coord_t ymin = front.y();
+ coord_t ymax = back.y();
+ if (ymin > ymax)
+ std::swap(ymin, ymax);
+
+ double dmax = 0;
+ // Maximum distance in Y axis out of the (ymin, ymax) band and from the (front, back) line.
+ Linef line { front.cast(), back.cast() };
+ for (const Point &pt : pl.points)
+ dmax = std::max(std::max(dmax, line_alg::distance_to(line, Vec2d(pt.cast()))), std::max(pt.y() - ymax, ymin - pt.y()));
+ return dmax;
+}
+
+// Costs for prev / next arch of each infill line end point.
+static inline std::vector evaluate_support_arches(Polylines &infill, BoundaryInfillGraph &graph, const double spacing, const FillParams ¶ms)
+{
+ std::vector arches(graph.map_infill_end_point_to_boundary.size() * 2);
+
+ Polyline pl;
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ // Not a losed loop, such loops should already be consumed.
+ assert(cp.next_on_contour != &cp);
+ const size_t infill_line_idx = &cp - graph.map_infill_end_point_to_boundary.data();
+ const bool first = (infill_line_idx & 1) == 0;
+ const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1;
+
+ SupportArcCost &out_prev = arches[infill_line_idx * 2];
+ SupportArcCost &out_next = *(&out_prev + 1);
+ out_prev.self_loop = cp.prev_on_contour == other_end;
+ out_prev.open = cp.prev_trimmed;
+ out_next.self_loop = cp.next_on_contour == other_end;
+ out_next.open = cp.next_trimmed;
+
+ if (cp.contour_not_taken_length_next > SCALED_EPSILON) {
+ pl.clear();
+ pl.points.emplace_back(graph.point(cp));
+ if (cp.next_trimmed)
+ take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next);
+ else
+ take_ccw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx);
+ out_next.cost = evaluate_support_arch_cost(pl);
+ }
+
+ if (cp.contour_not_taken_length_prev > SCALED_EPSILON) {
+ pl.clear();
+ pl.points.emplace_back(graph.point(cp));
+ if (cp.prev_trimmed)
+ take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev);
+ else
+ take_cw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx);
+ out_prev.cost = evaluate_support_arch_cost(pl);
+ }
+ }
+
+ return arches;
+}
+
+// Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical.
+void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms)
+{
+// assert(! infill_ordered.empty());
+ assert(params.anchor_length >= 0.);
+ assert(params.anchor_length_max >= 0.01f);
+ assert(params.anchor_length_max >= params.anchor_length);
+
+ BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing);
+
+#ifdef INFILL_DEBUG_OUTPUT
+ static int iRun = 0;
+ ++ iRun;
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-initial-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ const double line_half_width = 0.5 * scale_(spacing);
+ const double line_spacing = scale_(spacing) / params.density;
+ const double min_arch_length = 1.3 * line_spacing;
+ const double trim_length = line_half_width * 0.3;
+
+// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines,
+// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully.
+// Mark those short boundary segments.
+ mark_boundary_segments_overlapping_infill(graph, infill_ordered, scale_(spacing));
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-marked-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ // Detect loops with zero infill end points connected.
+ // Extrude these loops as perimeters.
+ {
+ std::vector num_boundary_contour_infill_points(graph.boundary.size(), 0);
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary)
+ ++ num_boundary_contour_infill_points[cp.contour_idx];
+ for (size_t i = 0; i < num_boundary_contour_infill_points.size(); ++ i)
+ if (num_boundary_contour_infill_points[i] == 0 && graph.boundary_params[i].back() > trim_length + 0.5 * line_spacing) {
+ // Emit a perimeter.
+ Polyline pl(graph.boundary[i]);
+ pl.points.emplace_back(pl.points.front());
+ pl.clip_end(trim_length);
+ if (pl.size() > 1)
+ polylines_out.emplace_back(std::move(pl));
+ }
+ }
+
+ // Before processing the boundary arches, emit those arches, which were trimmed by the infill lines at both sides, but which
+ // depart from the infill line at least once after touching the infill line.
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ if (cp.next_on_contour && cp.next_trimmed && cp.next_on_contour->prev_trimmed) {
+ // The arch is leaving one infill line to end up at the same infill line or at the neighbouring one.
+ // The arch is touching one of those infill lines at least once.
+ // Trace those arches and emit their parts, which are not attached to the end points and they are not overlapping with the two infill lines mentioned.
+ bool first = graph.first(cp);
+ coord_t left = graph.point(cp).x();
+ coord_t right = left;
+ if (first) {
+ left += line_half_width;
+ right += line_spacing - line_half_width;
+ } else {
+ left -= line_spacing - line_half_width;
+ right -= line_half_width;
+ }
+ double param_start = cp.param + cp.contour_not_taken_length_next;
+ double param_end = cp.next_on_contour->param - cp.next_on_contour->contour_not_taken_length_prev;
+ double contour_length = graph.boundary_params[cp.contour_idx].back();
+ if (param_start >= contour_length)
+ param_start -= contour_length;
+ if (param_end < 0)
+ param_end += contour_length;
+ // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param).
+ assert(cyclic_interval_inside_interval(cp.param, cp.next_on_contour->param, param_start, param_end, contour_length));
+ emit_loops_in_band(left, right, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], param_start, param_end, 0.5 * line_spacing, polylines_out);
+ }
+ }
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-excess-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ base_support_extend_infill_lines(infill_ordered, graph, spacing, params);
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-extended-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ std::vector merged_with(infill_ordered.size());
+ std::iota(merged_with.begin(), merged_with.end(), 0);
+ auto get_and_update_merged_with = [&graph, &merged_with](const ContourIntersectionPoint *cp) -> size_t {
+ size_t polyline_idx = (cp - graph.map_infill_end_point_to_boundary.data()) / 2;
+ for (size_t last = polyline_idx;;) {
+ size_t lower = merged_with[last];
+ assert(lower <= last);
+ if (lower == last) {
+ merged_with[polyline_idx] = last;
+ return last;
+ }
+ last = lower;
+ }
+ assert(false);
+ return std::numeric_limits::max();
+ };
+
+ auto vertical = [](BoundaryInfillGraph::Direction dir) {
+ return dir == BoundaryInfillGraph::Up || dir == BoundaryInfillGraph::Down;
+ };
+ // When both left / right arch connected to cp is vertical (ends up at the same vertical infill line), which one to take?
+ auto take_vertical_prev = [](const ContourIntersectionPoint &cp) {
+ return cp.prev_trimmed == cp.next_trimmed ?
+ // Both are either trimmed or not trimmed. Take the longer contour.
+ cp.contour_not_taken_length_prev > cp.contour_not_taken_length_next :
+ // One is trimmed, the other is not trimmed. Take the not trimmed.
+ ! cp.prev_trimmed && cp.next_trimmed;
+ };
+
+ // Connect infill lines at cp and cpo_next_on_contour.
+ // If the complete arch cannot be taken, then
+ // if (take_first)
+ // take the infill line at cp and an arc from cp towards cp.next_on_contour.
+ // else
+ // take the infill line at cp_next_on_contour and an arc from cp.next_on_contour towards cp.
+ // If cp1 == next_on_contour (a single infill line is connected to a contour, this is a valid case for contours with holes),
+ // then extrude the full circle.
+ // Nothing is done if the arch could no more be taken (one of it end points were consumed already).
+ auto take_next = [&graph, &infill_ordered, &merged_with, get_and_update_merged_with, line_half_width, trim_length](ContourIntersectionPoint &cp, bool take_first) {
+ // Indices of the polylines to be connected by a perimeter segment.
+ ContourIntersectionPoint *cp1 = &cp;
+ ContourIntersectionPoint *cp2 = cp.next_on_contour;
+ assert(cp1->next_trimmed == cp2->prev_trimmed);
+ //assert(cp1->next_trimmed || cp1->consumed == cp2->consumed);
+ if (take_first ? cp1->consumed : cp2->consumed)
+ return;
+ size_t polyline_idx1 = get_and_update_merged_with(cp1);
+ size_t polyline_idx2 = get_and_update_merged_with(cp2);
+ Polyline &polyline1 = infill_ordered[polyline_idx1];
+ Polyline &polyline2 = infill_ordered[polyline_idx2];
+ const Points &contour = graph.boundary[cp1->contour_idx];
+ const std::vector &contour_params = graph.boundary_params[cp1->contour_idx];
+ assert(cp1->consumed || contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back());
+ assert(cp2->consumed || contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back());
+ bool trimmed = take_first ? cp1->next_trimmed : cp2->prev_trimmed;
+ if (! trimmed) {
+ // Trim the end if closing a loop or making a T-joint.
+ trimmed = cp1 == cp2 || polyline_idx1 == polyline_idx2 || (take_first ? cp2->consumed : cp1->consumed);
+ if (! trimmed) {
+ const bool cp1_first = ((cp1 - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
+ const ContourIntersectionPoint* cp1_other = cp1_first ? cp1 + 1 : cp1 - 1;
+ // Self loop, connecting the end points of the same infill line.
+ trimmed = cp2 == cp1_other;
+ }
+ if (trimmed) /* [[unlikely]] */ {
+ // Single end point on a contour. This may happen on contours with holes. Extrude a loop.
+ // Or a self loop, connecting the end points of the same infill line.
+ // Or closing a chain of infill lines. This may happen if infilling a contour with a hole.
+ double len = cp1 == cp2 ? contour_params.back() : path_length_along_contour_ccw(cp1, cp2, contour_params.back());
+ if (take_first) {
+ cp1->trim_next(std::max(0., len - trim_length - SCALED_EPSILON));
+ cp2->trim_prev(0);
+ } else {
+ cp1->trim_next(0);
+ cp2->trim_prev(std::max(0., len - trim_length - SCALED_EPSILON));
+ }
+ }
+ }
+ if (trimmed) {
+ if (take_first)
+ take_limited(polyline1, contour, contour_params, cp1, cp2, false, 1e10, line_half_width);
+ else
+ take_limited(polyline2, contour, contour_params, cp2, cp1, true, 1e10, line_half_width);
+ } else if (! cp1->consumed && ! cp2->consumed) {
+ if (contour[cp1->point_idx] == polyline1.points.front())
+ polyline1.reverse();
+ if (contour[cp2->point_idx] == polyline2.points.back())
+ polyline2.reverse();
+ take(polyline1, polyline2, contour, cp1, cp2, false);
+ // Mark the second polygon as merged with the first one.
+ if (polyline_idx2 < polyline_idx1) {
+ polyline2 = std::move(polyline1);
+ polyline1.points.clear();
+ merged_with[polyline_idx1] = merged_with[polyline_idx2];
+ } else {
+ polyline2.points.clear();
+ merged_with[polyline_idx2] = merged_with[polyline_idx1];
+ }
+ }
+ };
+
+ // Consume all vertical arches. If a vertical arch is touching a neighboring vertical infill line, thus the vertical arch is trimmed,
+ // only consume the trimmed part if it is longer than min_arch_length.
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ assert(cp.contour_idx != boundary_idx_unconnected);
+ if (cp.consumed)
+ continue;
+ const ContourIntersectionPoint &cp_other = graph.other(cp);
+ assert((cp.next_on_contour == &cp_other) == (cp_other.prev_on_contour == &cp));
+ assert((cp.prev_on_contour == &cp_other) == (cp_other.next_on_contour == &cp));
+ BoundaryInfillGraph::Direction dir_prev = graph.dir_prev(cp);
+ BoundaryInfillGraph::Direction dir_next = graph.dir_next(cp);
+ // Following code will also consume contours with just a single infill line attached. (cp1->next_on_contour == cp1).
+ assert((cp.next_on_contour == &cp) == (cp.prev_on_contour == &cp));
+ bool can_take_prev = vertical(dir_prev) && ! cp.prev_on_contour->consumed && cp.prev_on_contour != &cp_other;
+ bool can_take_next = vertical(dir_next) && ! cp.next_on_contour->consumed && cp.next_on_contour != &cp_other;
+ if (can_take_prev && (! can_take_next || take_vertical_prev(cp))) {
+ if (! cp.prev_trimmed || cp.contour_not_taken_length_prev > min_arch_length)
+ // take previous
+ take_next(*cp.prev_on_contour, false);
+ } else if (can_take_next) {
+ if (! cp.next_trimmed || cp.contour_not_taken_length_next > min_arch_length)
+ // take next
+ take_next(cp, true);
+ }
+ }
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-vertical-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ const std::vector arches = evaluate_support_arches(infill_ordered, graph, spacing, params);
+ static const double cost_low = line_spacing * 1.3;
+ static const double cost_high = line_spacing * 2.;
+ static const double cost_veryhigh = line_spacing * 3.;
+
+ {
+ std::vector selected;
+ selected.reserve(graph.map_infill_end_point_to_boundary.size());
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ if (cp.consumed)
+ continue;
+ const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2];
+ const SupportArcCost &cost_next = *(&cost_prev + 1);
+ double cost_min = cost_prev.cost;
+ double cost_max = cost_next.cost;
+ if (cost_min > cost_max)
+ std::swap(cost_min, cost_max);
+ if (cost_max < cost_low || cost_min > cost_high)
+ // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken.
+ continue;
+ const double cost_diff_relative = (cost_max - cost_min) / cost_max;
+ if (cost_diff_relative < 0.25)
+ // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken.
+ continue;
+ if (cost_prev.cost > cost_low)
+ selected.emplace_back(&cost_prev);
+ if (cost_next.cost > cost_low)
+ selected.emplace_back(&cost_next);
+ }
+ // Take the longest arch first.
+ std::sort(selected.begin(), selected.end(), [](const auto *l, const auto *r) { return l->cost > r->cost; });
+ // And connect along the arches.
+ for (const SupportArcCost *arc : selected) {
+ ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(arc - arches.data()) / 2];
+ if (! cp.consumed) {
+ bool prev = ((arc - arches.data()) & 1) == 0;
+ if (prev)
+ take_next(*cp.prev_on_contour, false);
+ else
+ take_next(cp, true);
+ }
+ }
+ }
+
+#if 0
+ {
+ // Connect infill lines with long horizontal arches. Only take a horizontal arch, if it will not block
+ // the end caps (vertical arches) at the other side of the infill line.
+ struct Arc {
+ ContourIntersectionPoint *intersection;
+ double arc_length;
+ bool take_next;
+ };
+ std::vector arches;
+ arches.reserve(graph.map_infill_end_point_to_boundary.size());
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ if (cp.consumed)
+ continue;
+ // Not a losed loop, such loops should already be consumed.
+ assert(cp.next_on_contour != &cp);
+ const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
+ const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1;
+ const bool loop_next = cp.next_on_contour == other_end;
+ if (! loop_next && cp.could_connect_next()) {
+ if (cp.contour_not_taken_length_next > min_arch_length) {
+ // Try both directions. This is useful to be able to close a loop back to the same line to take a long arch.
+ arches.push_back({ &cp, cp.contour_not_taken_length_next, true });
+ arches.push_back({ cp.next_on_contour, cp.contour_not_taken_length_next, false });
+ }
+ } else {
+ //bool first = ((&cp - graph.map_infill_end_point_to_boundary) & 1) == 0;
+ if (cp.prev_trimmed && cp.could_take_prev()) {
+ //FIXME trace the trimmed line to decide what priority to assign to it.
+ // Is the end point close to the current vertical line or to the other vertical line?
+ const Point &pt = graph.point(cp);
+ const Point &prev = graph.point(*cp.prev_on_contour);
+ if (std::abs(pt.x() - prev.x()) < coord_t(0.5 * line_spacing)) {
+ // End point on the same line.
+ // Measure maximum distance from the current vertical line.
+ if (cp.contour_not_taken_length_prev > 0.5 * line_spacing)
+ arches.push_back({ &cp, cp.contour_not_taken_length_prev, false });
+ } else {
+ // End point on the other line.
+ if (cp.contour_not_taken_length_prev > min_arch_length)
+ arches.push_back({ &cp, cp.contour_not_taken_length_prev, false });
+ }
+ }
+ if (cp.next_trimmed && cp.could_take_next()) {
+ //FIXME trace the trimmed line to decide what priority to assign to it.
+ const Point &pt = graph.point(cp);
+ const Point &next = graph.point(*cp.next_on_contour);
+ if (std::abs(pt.x() - next.x()) < coord_t(0.5 * line_spacing)) {
+ // End point on the same line.
+ // Measure maximum distance from the current vertical line.
+ if (cp.contour_not_taken_length_next > 0.5 * line_spacing)
+ arches.push_back({ &cp, cp.contour_not_taken_length_next, true });
+ } else {
+ // End point on the other line.
+ if (cp.contour_not_taken_length_next > min_arch_length)
+ arches.push_back({ &cp, cp.contour_not_taken_length_next, true });
+ }
+ }
+ }
+ }
+ // Take the longest arch first.
+ std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length > r.arc_length; });
+ // And connect along the arches.
+ for (Arc &arc : arches)
+ if (arc.take_next)
+ take_next(*arc.intersection, true);
+ else
+ take_next(*arc.intersection->prev_on_contour, false);
+ }
+#endif
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-arches-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ // Traverse the unconnected lines in a zig-zag fashion, left to right only.
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ assert(cp.contour_idx != boundary_idx_unconnected);
+ if (cp.consumed)
+ continue;
+ bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
+ if (first) {
+ // Only connect if the two lines are not connected by the same line already.
+ if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour))
+ take_next(cp, true);
+ } else {
+ if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour))
+ take_next(*cp.prev_on_contour, false);
+ }
+ }
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-zigzag-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ // Add the left caps.
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0;
+ const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1;
+ const bool loop_next = cp.next_on_contour == other_end;
+ const bool loop_prev = other_end->next_on_contour == &cp;
+#ifndef NDEBUG
+ const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2];
+ const SupportArcCost &cost_next = *(&cost_prev + 1);
+ assert(cost_prev.self_loop == loop_prev);
+ assert(cost_next.self_loop == loop_next);
+#endif // NDEBUG
+ if (loop_prev && cp.could_take_prev())
+ take_next(*cp.prev_on_contour, false);
+ if (loop_next && cp.could_take_next())
+ take_next(cp, true);
+ }
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-caps-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ // Connect with T joints using long arches. Loops could be created only if a very long arc has to be added.
+ {
+ std::vector candidates;
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ if (cp.could_take_prev())
+ candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]);
+ if (cp.could_take_next())
+ candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2 + 1]);
+ }
+ std::sort(candidates.begin(), candidates.end(), [](auto *c1, auto *c2) { return c1->cost > c2->cost; });
+ for (const SupportArcCost *candidate : candidates) {
+ ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(candidate - arches.data()) / 2];
+ bool prev = ((candidate - arches.data()) & 1) == 0;
+ if (prev) {
+ if (cp.could_take_prev() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour) || candidate->cost > cost_high))
+ take_next(*cp.prev_on_contour, false);
+ } else {
+ if (cp.could_take_next() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour) || candidate->cost > cost_high))
+ take_next(cp, true);
+ }
+ }
+ }
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-Tjoints-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ // Add very long arches and reasonably long caps even if both of its end points were already consumed.
+ const double cap_cost = 0.5 * line_spacing;
+ for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) {
+ const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2];
+ const SupportArcCost &cost_next = *(&cost_prev + 1);
+ if (cp.contour_not_taken_length_prev > SCALED_EPSILON &&
+ (cost_prev.self_loop ?
+ cost_prev.cost > cap_cost :
+ cost_prev.cost > cost_veryhigh)) {
+ assert(cp.consumed && (cp.prev_on_contour->consumed || cp.prev_trimmed));
+ Polyline pl { graph.point(cp) };
+ if (! cp.prev_trimmed) {
+ cp.trim_prev(cp.contour_not_taken_length_prev - line_half_width);
+ cp.prev_on_contour->trim_next(0);
+ }
+ if (cp.contour_not_taken_length_prev > SCALED_EPSILON) {
+ take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev);
+ cp.trim_prev(0);
+ pl.clip_start(line_half_width);
+ polylines_out.emplace_back(std::move(pl));
+ }
+ }
+ if (cp.contour_not_taken_length_next > SCALED_EPSILON &&
+ (cost_next.self_loop ?
+ cost_next.cost > cap_cost :
+ cost_next.cost > cost_veryhigh)) {
+ assert(cp.consumed && (cp.next_on_contour->consumed || cp.next_trimmed));
+ Polyline pl { graph.point(cp) };
+ if (! cp.next_trimmed) {
+ cp.trim_next(cp.contour_not_taken_length_next - line_half_width);
+ cp.next_on_contour->trim_prev(0);
+ }
+ if (cp.contour_not_taken_length_next > SCALED_EPSILON) {
+ take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); // line_half_width);
+ cp.trim_next(0);
+ pl.clip_start(line_half_width);
+ polylines_out.emplace_back(std::move(pl));
+ }
+ }
+ }
+
+#ifdef INFILL_DEBUG_OUTPUT
+ export_partial_infill_to_svg(debug_out_path("connect_base_support-final-%03d.svg", iRun), graph, infill_ordered, polylines_out);
+#endif // INFILL_DEBUG_OUTPUT
+
+ polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); }));
+ for (Polyline &pl : infill_ordered)
+ if (! pl.empty())
+ polylines_out.emplace_back(std::move(pl));
+}
+
+void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms)
+{
+ auto polygons_src = reserve_vector(boundary_src.size());
+ for (const Polygon &polygon : boundary_src)
+ polygons_src.emplace_back(&polygon);
+
+ connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params);
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 2f7afc92f..5998a7c51 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -135,7 +135,9 @@ static t_config_enum_values s_keys_map_InfillPattern {
{ "octagramspiral", ipOctagramSpiral },
{ "supportcubic", ipSupportCubic },
{ "lightning", ipLightning },
- { "crosshatch", ipCrossHatch}
+ { "crosshatch", ipCrossHatch},
+ //xiamian+
+ { "fiberspiral", ipFiberSpiral }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern)
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index f97eca106..8b5d3f370 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -56,6 +56,8 @@ enum InfillPattern : int {
ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal,
ipLightning, ipCrossHatch,
ipCount,
+ //xiamian+
+ ipFiberSpiral
};
enum class IroningType {