From f6c2e90254994423393915450a759dc8adb7481c Mon Sep 17 00:00:00 2001 From: cjw Date: Wed, 25 Dec 2024 16:02:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=B8=8B=E6=8B=89=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/images/param_fiberspiral.svg | 19 + src/libslic3r/Fill/Fill.cpp | 843 ++++++++ src/libslic3r/Fill/FillBase.cpp | 2598 ++++++++++++++++++++++++ src/libslic3r/PrintConfig.cpp | 4 +- src/libslic3r/PrintConfig.hpp | 2 + 5 files changed, 3465 insertions(+), 1 deletion(-) create mode 100644 resources/images/param_fiberspiral.svg create mode 100644 src/libslic3r/Fill/Fill.cpp create mode 100644 src/libslic3r/Fill/FillBase.cpp 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 {