Add the full source of BambuStudio

using version 1.0.10
This commit is contained in:
lane.wei 2022-07-15 23:37:19 +08:00 committed by Lane.Wei
parent 30bcadab3e
commit 1555904bef
3771 changed files with 1251328 additions and 0 deletions

673
src/libslic3r/Fill/Fill.cpp Normal file
View file

@ -0,0 +1,673 @@
#include <assert.h>
#include <stdio.h>
#include <memory>
#include "../ClipperUtils.hpp"
#include "../Geometry.hpp"
#include "../Layer.hpp"
#include "../Print.hpp"
#include "../PrintConfig.hpp"
#include "../Surface.hpp"
#include "FillBase.hpp"
#include "FillRectilinear.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;
// 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;
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 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;
}
};
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: 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<SurfaceFill> group_fills(const Layer &layer)
{
std::vector<SurfaceFill> surface_fills;
// Fill in a map of a region & surface to SurfaceFillParams.
std::set<SurfaceFillParams> set_surface_params;
std::vector<std::vector<const SurfaceFillParams*>> region_to_surface_params(layer.regions().size(), std::vector<const SurfaceFillParams*>());
SurfaceFillParams params;
bool has_internal_voids = false;
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 &region_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?
params.pattern = (surface.is_external() && ! is_bridge) ?
(surface.is_top() ? region_config.top_surface_pattern.value : region_config.bottom_surface_pattern.value) :
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()) || g_config_thick_bridges) :
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
// 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(Fill::infill_anchor * 0.01 * params.spacing);
params.anchor_length_max = Fill::infill_anchor_max;
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 &params : set_surface_params) {
const_cast<SurfaceFillParams&>(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));
} else
fill.expolygons.emplace_back(surface.expolygon);
}
}
}
{
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 ipConcentricGapFill 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<size_t> 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 = ipConcentricGapFill;
}
else {
// BBS: some expolygons are narrow, spilit surface_fills[i] and rearrange the expolygons
params = surface_fills[i].params;
params.pattern = ipConcentricGapFill;
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;
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<SurfaceFill> &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)
{
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<SurfaceFill> 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<Fill> f = std::unique_ptr<Fill>(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;
// 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;
// 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;
LayerRegion* layerm = this->m_regions[surface_fill.region_id];
for (ExPolygon& expoly : surface_fill.expolygons) {
f->no_overlap_expolygons = intersection_ex(layerm->fill_no_overlap_expolygons, ExPolygons() = { expoly });
// 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<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != nullptr);
#endif
}
// 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 {
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;
}
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<IroningParams> 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 = config.infill_direction * M_PI / 180.;
ironing_params.layerm = layerm;
by_extruder.emplace_back(ironing_params);
}
}
std::sort(by_extruder.begin(), by_extruder.end());
FillRectilinear fill;
FillParams fill_params;
fill.set_bounding_box(this->object()->bounding_box());
fill.layer_id = this->id();
fill.z = this->print_z;
fill.overlap = 0;
fill_params.density = 1.;
fill_params.monotonic = true;
for (size_t i = 0; i < by_extruder.size();) {
// Find span of regions equivalent to the ironing operation.
IroningParams &ironing_params = by_extruder[i];
size_t j = i;
for (++ j; j < by_extruder.size() && ironing_params == by_extruder[j]; ++ j) ;
// Create the ironing extrusions for regions <i, j)
ExPolygons ironing_areas;
double nozzle_dmr = this->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 &region_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 || (iron_everything && surface.surface_type == stBottom))
// 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.
fill.spacing = ironing_params.line_spacing;
fill.angle = float(ironing_params.angle + 0.25 * M_PI);
fill.link_max_length = (coord_t)scale_(3. * fill.spacing);
double extrusion_height = ironing_params.height * fill.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 = fill.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

View file

@ -0,0 +1,33 @@
#ifndef slic3r_Fill_hpp_
#define slic3r_Fill_hpp_
#include <memory.h>
#include <float.h>
#include <stdint.h>
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "FillBase.hpp"
namespace Slic3r {
class ExtrusionEntityCollection;
class LayerRegion;
// An interface class to Perl, aggregating an instance of a Fill and a FillData.
class Filler
{
public:
Filler() : fill(nullptr) {}
~Filler() {
delete fill;
fill = nullptr;
}
Fill *fill;
FillParams params;
};
} // namespace Slic3r
#endif // slic3r_Fill_hpp_

View file

@ -0,0 +1,174 @@
#include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "Fill3DHoneycomb.hpp"
namespace Slic3r {
/*
Creates a contiguous sequence of points at a specified height that make
up a horizontal slice of the edges of a space filling truncated
octahedron tesselation. The octahedrons are oriented so that the
square faces are in the horizontal plane with edges parallel to the X
and Y axes.
Credits: David Eccles (gringer).
*/
// Generate an array of points that are in the same direction as the
// basic printing line (i.e. Y points for columns, X points for rows)
// Note: a negative offset only causes a change in the perpendicular
// direction
static std::vector<coordf_t> colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength)
{
const coordf_t offset2 = std::abs(offset / coordf_t(2.));
std::vector<coordf_t> points;
points.push_back(baseLocation - offset2);
for (size_t i = 0; i < gridLength; ++i) {
points.push_back(baseLocation + i + offset2);
points.push_back(baseLocation + i + 1 - offset2);
}
points.push_back(baseLocation + gridLength + offset2);
return points;
}
// Generate an array of points for the dimension that is perpendicular to
// the basic printing line (i.e. X points for columns, Y points for rows)
static std::vector<coordf_t> perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength)
{
coordf_t offset2 = offset / coordf_t(2.);
coord_t side = 2 * (baseLocation & 1) - 1;
std::vector<coordf_t> points;
points.push_back(baseLocation - offset2 * side);
for (size_t i = 0; i < gridLength; ++i) {
side = 2*((i+baseLocation) & 1) - 1;
points.push_back(baseLocation + offset2 * side);
points.push_back(baseLocation + offset2 * side);
}
points.push_back(baseLocation - offset2 * side);
return points;
}
// Trims an array of points to specified rectangular limits. Point
// components that are outside these limits are set to the limits.
static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY)
{
for (Vec2d &pt : pts) {
pt.x() = std::clamp(pt.x(), minX, maxX);
pt.y() = std::clamp(pt.y(), minY, maxY);
}
}
static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coordf_t> &y)
{
assert(x.size() == y.size());
Pointfs out;
out.reserve(x.size());
for (size_t i = 0; i < x.size(); ++ i)
out.push_back(Vec2d(x[i], y[i]));
return out;
}
// Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron with edge length 1.
// curveType specifies which lines to print, 1 for vertical lines
// (columns), 2 for horizontal lines (rows), and 3 for both.
static std::vector<Pointfs> makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType)
{
// offset required to create a regular octagram
coordf_t octagramGap = coordf_t(0.5);
// sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap]
coordf_t a = std::sqrt(coordf_t(2.)); // period
coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.;
coordf_t offset = wave * octagramGap;
std::vector<Pointfs> points;
if ((curveType & 1) != 0) {
for (size_t x = 0; x <= gridWidth; ++x) {
points.push_back(Pointfs());
Pointfs &newPoints = points.back();
newPoints = zip(
perpendPoints(offset, x, gridHeight),
colinearPoints(offset, 0, gridHeight));
// trim points to grid edges
trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight));
if (x & 1)
std::reverse(newPoints.begin(), newPoints.end());
}
}
if ((curveType & 2) != 0) {
for (size_t y = 0; y <= gridHeight; ++y) {
points.push_back(Pointfs());
Pointfs &newPoints = points.back();
newPoints = zip(
colinearPoints(offset, 0, gridWidth),
perpendPoints(offset, y, gridWidth));
// trim points to grid edges
trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight));
if (y & 1)
std::reverse(newPoints.begin(), newPoints.end());
}
}
return points;
}
// Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron with a specified
// grid square size.
static Polylines makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType)
{
coord_t scaleFactor = gridSize;
coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor);
std::vector<Pointfs> polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType);
Polylines result;
result.reserve(polylines.size());
for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) {
result.push_back(Polyline());
Polyline &polyline = result.back();
for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it)
polyline.points.push_back(Point(coord_t((*it)(0) * scaleFactor), coord_t((*it)(1) * scaleFactor)));
}
return result;
}
void Fill3DHoneycomb::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern
BoundingBox bb = expolygon.contour.bounding_box();
coord_t distance = coord_t(scale_(this->spacing) / params.density);
// align bounding box to a multiple of our honeycomb grid module
// (a module is 2*$distance since one $distance half-module is
// growing while the other $distance half-module is shrinking)
bb.merge(align_to_grid(bb.min, Point(2*distance, 2*distance)));
// generate pattern
Polylines polylines = makeGrid(
scale_(this->z),
distance,
ceil(bb.size()(0) / distance) + 1,
ceil(bb.size()(1) / distance) + 1,
((this->layer_id/thickness_layers) % 2) + 1);
// move pattern in place
for (Polyline &pl : polylines)
pl.translate(bb.min);
// clip pattern to boundaries, chain the clipped polylines
polylines = intersection_pl(polylines, expolygon);
// connect lines if needed
if (params.dont_connect() || polylines.size() <= 1)
append(polylines_out, chain_polylines(std::move(polylines)));
else
this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
}
} // namespace Slic3r

View file

@ -0,0 +1,32 @@
#ifndef slic3r_Fill3DHoneycomb_hpp_
#define slic3r_Fill3DHoneycomb_hpp_
#include <map>
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Fill3DHoneycomb : public Fill
{
public:
Fill* clone() const override { return new Fill3DHoneycomb(*this); };
~Fill3DHoneycomb() override {}
// require bridge flow since most of this pattern hangs in air
bool use_bridge_flow() const override { return true; }
protected:
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
};
} // namespace Slic3r
#endif // slic3r_Fill3DHoneycomb_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,79 @@
// Adaptive cubic infill was inspired by the work of @mboerwinkle
// as implemented for Cura.
// https://github.com/Ultimaker/CuraEngine/issues/381
// https://github.com/Ultimaker/CuraEngine/pull/401
//
// Our implementation is more accurate (discretizes a bit less cubes than Cura's)
// by splitting only such cubes which contain a triangle.
// Our line extraction is time optimal instead of O(n^2) when connecting extracted lines,
// and we also implemented adaptivity for supporting internal overhangs only.
#ifndef slic3r_FillAdaptive_hpp_
#define slic3r_FillAdaptive_hpp_
#include "FillBase.hpp"
struct indexed_triangle_set;
namespace Slic3r {
class PrintObject;
namespace FillAdaptive
{
struct Octree;
// To keep the definition of Octree opaque, we have to define a custom deleter.
struct OctreeDeleter { void operator()(Octree *p); };
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
// Calculate line spacing for
// 1) adaptive cubic infill
// 2) adaptive internal support cubic infill
// Returns zero for a particular infill type if no such infill is to be generated.
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object);
// Rotation of the octree to stand on one of its corners.
Eigen::Quaterniond transform_to_world();
// Inverse roation of the above.
Eigen::Quaterniond transform_to_octree();
FillAdaptive::OctreePtr build_octree(
// Mesh is rotated to the coordinate system of the octree.
const indexed_triangle_set &triangle_mesh,
// Overhang triangles extracted from fill surfaces with stInternalBridge type,
// rotated to the coordinate system of the octree.
const std::vector<Vec3d> &overhang_triangles,
coordf_t line_spacing,
// If true, octree is densified below internal overhangs only.
bool support_overhangs_only);
//
// Some of the algorithms used by class FillAdaptive were inspired by
// Cura Engine's class SubDivCube
// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h
//
class Filler : public Slic3r::Fill
{
public:
~Filler() override {}
protected:
Fill* clone() const override { return new Filler(*this); }
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
// Let the G-code export reoder the infill lines.
//FIXME letting the G-code exporter to reorder infill lines of Adaptive Cubic Infill
// may not be optimal as the internal infill lines may get extruded before the long infill
// lines to which the short infill lines are supposed to anchor.
bool no_sort() const override { return false; }
};
} // namespace FillAdaptive
} // namespace Slic3r
#endif // slic3r_FillAdaptive_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,169 @@
#ifndef slic3r_FillBase_hpp_
#define slic3r_FillBase_hpp_
#include <assert.h>
#include <memory.h>
#include <float.h>
#include <stdint.h>
#include <stdexcept>
#include <type_traits>
#include "../libslic3r.h"
#include "../BoundingBox.hpp"
#include "../Exception.hpp"
#include "../Utils.hpp"
#include "../ExPolygon.hpp"
//BBS: necessary header for new function
#include "../PrintConfig.hpp"
#include "../Flow.hpp"
#include "../ExtrusionEntity.hpp"
#include "../ExtrusionEntityCollection.hpp"
namespace Slic3r {
class Surface;
enum InfillPattern : int;
namespace FillAdaptive {
struct Octree;
};
// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError.
class InfillFailedException : public Slic3r::RuntimeError {
public:
InfillFailedException() : Slic3r::RuntimeError("Infill failed") {}
};
struct FillParams
{
bool full_infill() const { return density > 0.9999f; }
// Don't connect the fill lines around the inner perimeter.
bool dont_connect() const { return anchor_length_max < 0.05f; }
// Fill density, fraction in <0, 1>
float density { 0.f };
// Length of an infill anchor along the perimeter.
// 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 };
// G-code resolution.
double resolution { 0.0125 };
// Don't adjust spacing to fill the space evenly.
bool dont_adjust { true };
// Monotonic infill - strictly left to right for better surface quality of top infills.
bool monotonic { false };
// For Honeycomb.
// we were requested to complete each loop;
// in this case we don't try to make more continuous paths
bool complete { false };
// BBS
Flow flow;
ExtrusionRole extrusion_role{ ExtrusionRole(0) };
bool using_internal_flow{ false };
//BBS: only used for new top surface pattern
float no_extrusion_overlap{ 0.0 };
};
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
class Fill
{
public:
// Index of the layer.
size_t layer_id;
// Z coordinate of the top print surface, in unscaled coordinates
coordf_t z;
// in unscaled coordinates
coordf_t spacing;
// infill / perimeter overlap, in unscaled coordinates
coordf_t overlap;
// in radians, ccw, 0 = East
float angle;
// In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines.
// Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic.
// If left to zero, the links will not be limited.
coord_t link_max_length;
// In scaled coordinates. Used by the concentric infill pattern to clip the loops to create extrusion paths.
coord_t loop_clipping;
// In scaled coordinates. Bounding box of the 2D projection of the object.
BoundingBox bounding_box;
// Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr;
// BBS: all no overlap expolygons in same layer
ExPolygons no_overlap_expolygons;
static float infill_anchor;
static float infill_anchor_max;
public:
virtual ~Fill() {}
virtual Fill* clone() const = 0;
static Fill* new_from_type(const InfillPattern type);
static Fill* new_from_type(const std::string &type);
static bool use_bridge_flow(const InfillPattern type);
void set_bounding_box(const Slic3r::BoundingBox &bbox) { bounding_box = bbox; }
// Use bridge flow for the fill?
virtual bool use_bridge_flow() const { return false; }
// Do not sort the fill lines to optimize the print head path?
virtual bool no_sort() const { return false; }
// Perform the fill.
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
// BBS: this method is used to fill the ExtrusionEntityCollection.
// It call fill_surface by default
virtual void fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out);
protected:
Fill() :
layer_id(size_t(-1)),
z(0.),
spacing(0.),
// Infill / perimeter overlap.
overlap(0.),
// Initial angle is undefined.
angle(FLT_MAX),
link_max_length(0),
loop_clipping(0),
// The initial bounding box is empty, therefore undefined.
bounding_box(Point(0, 0), Point(-1, -1))
{}
// The expolygon may be modified by the method to avoid a copy.
virtual void _fill_surface_single(
const FillParams & /* params */,
unsigned int /* thickness_layers */,
const std::pair<float, Point> & /* direction */,
ExPolygon /* expolygon */,
Polylines & /* polylines_out */) {};
virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; }
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
public:
static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams &params);
static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams &params);
static void connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams &params);
static void connect_base_support(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params);
static void connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params);
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);
};
} // namespace Slic3r
#endif // slic3r_FillBase_hpp_

View file

@ -0,0 +1,64 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../Surface.hpp"
#include "FillConcentric.hpp"
namespace Slic3r {
void FillConcentric::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern
BoundingBox bounding_box = expolygon.contour.bounding_box();
coord_t min_spacing = scale_(this->spacing);
coord_t distance = coord_t(min_spacing / params.density);
if (params.density > 0.9999f && !params.dont_adjust) {
distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance);
this->spacing = unscale<double>(distance);
}
Polygons loops = to_polygons(expolygon);
ExPolygons last { std::move(expolygon) };
while (! last.empty()) {
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
append(loops, to_polygons(last));
}
// generate paths from the outermost to the innermost, to avoid
// adhesion problems of the first central tiny loops
loops = union_pt_chained_outside_in(loops);
// split paths using a nearest neighbor search
size_t iPathFirst = polylines_out.size();
Point last_pos(0, 0);
for (const Polygon &loop : loops) {
polylines_out.emplace_back(loop.split_at_index(last_pos.nearest_point_index(loop.points)));
last_pos = polylines_out.back().last_point();
}
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
// Keep valid paths only.
size_t j = iPathFirst;
for (size_t i = iPathFirst; i < polylines_out.size(); ++ i) {
polylines_out[i].clip_end(this->loop_clipping);
if (polylines_out[i].is_valid()) {
if (j < i)
polylines_out[j] = std::move(polylines_out[i]);
++ j;
}
}
if (j < polylines_out.size())
polylines_out.erase(polylines_out.begin() + j, polylines_out.end());
//TODO: return ExtrusionLoop objects to get better chained paths,
// otherwise the outermost loop starts at the closest point to (0, 0).
// We want the loops to be split inside the G-code generator to get optimum path planning.
}
} // namespace Slic3r

View file

@ -0,0 +1,27 @@
#ifndef slic3r_FillConcentric_hpp_
#define slic3r_FillConcentric_hpp_
#include "FillBase.hpp"
namespace Slic3r {
class FillConcentric : public Fill
{
public:
~FillConcentric() override = default;
protected:
Fill* clone() const override { return new FillConcentric(*this); };
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
bool no_sort() const override { return true; }
};
} // namespace Slic3r
#endif // slic3r_FillConcentric_hpp_

View file

@ -0,0 +1,137 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../Surface.hpp"
#include "../VariableWidth.hpp"
#include "../ShortestPath.hpp"
#include "FillConcentricWGapFill.hpp"
namespace Slic3r {
const float concentric_overlap_threshold = 0.02;
void FillConcentricWGapFill::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out)
{
//BBS: FillConcentricWGapFill.cpp is absolutely newly add by BBL for narrow internal solid infill area to reduce vibration
// Because the area is narrow, we should not use the surface->expolygon which has overlap with perimeter, but
// use no_overlap_expolygons instead to avoid overflow in narrow area.
//Slic3r::ExPolygons expp = offset_ex(surface->expolygon, double(scale_(0 - 0.5 * this->spacing)));
float min_spacing = this->spacing * (1 - concentric_overlap_threshold);
Slic3r::ExPolygons expp = offset2_ex(this->no_overlap_expolygons, -double(scale_(0.5 * this->spacing + 0.5 * min_spacing) - 1),
+double(scale_(0.5 * min_spacing) - 1));
// Create the infills for each of the regions.
Polylines polylines_out;
for (size_t i = 0; i < expp.size(); ++i) {
ExPolygon expolygon = expp[i];
coord_t distance = scale_(this->spacing / params.density);
if (params.density > 0.9999f && !params.dont_adjust) {
distance = scale_(this->spacing);
}
ExPolygons gaps;
Polygons loops = (Polygons)expolygon;
ExPolygons last = { expolygon };
bool first = true;
while (!last.empty()) {
ExPolygons next_onion = offset2_ex(last, -double(distance + scale_(this->spacing) / 2), +double(scale_(this->spacing) / 2));
for (auto it = next_onion.begin(); it != next_onion.end(); it++) {
Polygons temp_loops = (Polygons)(*it);
loops.insert(loops.end(), temp_loops.begin(), temp_loops.end());
}
append(gaps, diff_ex(
offset(last, -0.5f * distance),
offset(next_onion, 0.5f * distance + 10))); // 10 is safty offset
last = next_onion;
if (first && !this->no_overlap_expolygons.empty()) {
gaps = intersection_ex(gaps, this->no_overlap_expolygons);
}
first = false;
}
ExtrusionRole good_role = params.extrusion_role;
ExtrusionEntityCollection *coll_nosort = new ExtrusionEntityCollection();
coll_nosort->no_sort = this->no_sort(); //can be sorted inside the pass
extrusion_entities_append_loops(
coll_nosort->entities, std::move(loops),
good_role,
params.flow.mm3_per_mm(),
params.flow.width(),
params.flow.height());
//BBS: add internal gapfills between infill loops
if (!gaps.empty() && params.density >= 1) {
double min = 0.2 * distance * (1 - INSET_OVERLAP_TOLERANCE);
double max = 2. * distance;
ExPolygons gaps_ex = diff_ex(
offset2_ex(gaps, -float(min / 2), float(min / 2)),
offset2_ex(gaps, -float(max / 2), float(max / 2)),
ApplySafetyOffset::Yes);
//BBS: sort the gap_ex to avoid mess travel
Points ordering_points;
ordering_points.reserve(gaps_ex.size());
ExPolygons gaps_ex_sorted;
gaps_ex_sorted.reserve(gaps_ex.size());
for (const ExPolygon &ex : gaps_ex)
ordering_points.push_back(ex.contour.first_point());
std::vector<Points::size_type> order = chain_points(ordering_points);
for (size_t i : order)
gaps_ex_sorted.emplace_back(std::move(gaps_ex[i]));
ThickPolylines polylines;
for (ExPolygon& ex : gaps_ex_sorted) {
//BBS: medial axis algorithm can't handle duplicated points in expolygon.
//Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
ex.douglas_peucker(SCALED_RESOLUTION);
ex.medial_axis(max, min, &polylines);
}
if (!polylines.empty() && !is_bridge(good_role)) {
ExtrusionEntityCollection gap_fill;
variable_width(polylines, erGapFill, params.flow, gap_fill.entities);
coll_nosort->append(std::move(gap_fill.entities));
}
}
if (!coll_nosort->entities.empty())
out.push_back(coll_nosort);
else
delete coll_nosort;
}
//BBS: add external gapfill between perimeter and infill
ExPolygons external_gaps = diff_ex(this->no_overlap_expolygons, offset_ex(expp, double(scale_(0.5 * this->spacing))), ApplySafetyOffset::Yes);
external_gaps = union_ex(external_gaps);
if (!this->no_overlap_expolygons.empty())
external_gaps = intersection_ex(external_gaps, this->no_overlap_expolygons);
if (!external_gaps.empty()) {
double min = 0.4 * scale_(params.flow.nozzle_diameter()) * (1 - INSET_OVERLAP_TOLERANCE);
double max = 2. * params.flow.scaled_width();
//BBS: collapse, be sure we don't gapfill where the perimeters are already touching each other (negative spacing).
min = std::max(min, (double)Flow::rounded_rectangle_extrusion_width_from_spacing((float)EPSILON, (float)params.flow.height()));
ExPolygons external_gaps_collapsed = offset2_ex(external_gaps, double(-min / 2), double(+min / 2));
ThickPolylines polylines;
for (ExPolygon& ex : external_gaps_collapsed) {
//BBS: medial axis algorithm can't handle duplicated points in expolygon.
//Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
ex.douglas_peucker(SCALED_RESOLUTION);
ex.medial_axis(max, min, &polylines);
}
ExtrusionEntityCollection* coll_external_gapfill = new ExtrusionEntityCollection();
coll_external_gapfill->no_sort = this->no_sort();
if (!polylines.empty() && !is_bridge(params.extrusion_role)) {
ExtrusionEntityCollection gap_fill;
variable_width(polylines, erGapFill, params.flow, gap_fill.entities);
coll_external_gapfill->append(std::move(gap_fill.entities));
}
if (!coll_external_gapfill->entities.empty())
out.push_back(coll_external_gapfill);
else
delete coll_external_gapfill;
}
}
}

View file

@ -0,0 +1,21 @@
#ifndef slic3r_FillConcentricWGapFil_hpp_
#define slic3r_FillConcentricWGapFil_hpp_
#include "FillBase.hpp"
namespace Slic3r {
class FillConcentricWGapFill : public Fill
{
public:
~FillConcentricWGapFill() override = default;
void fill_surface_extrusion(const Surface *surface, const FillParams &params, ExtrusionEntitiesPtr &out) override;
protected:
Fill* clone() const override { return new FillConcentricWGapFill(*this); };
bool no_sort() const override { return true; }
};
} // namespace Slic3r
#endif // slic3r_FillConcentricWGapFil_hpp_

View file

@ -0,0 +1,210 @@
#include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include <cmath>
#include <algorithm>
#include <iostream>
#include "FillGyroid.hpp"
namespace Slic3r {
static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
{
if (vertical) {
double phase_offset = (z_cos < 0 ? M_PI : 0) + M_PI;
double a = sin(x + phase_offset);
double b = - z_cos;
double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.));
double r = sqrt(sqr(a) + sqr(b));
return asin(a/r) + asin(res/r) + M_PI;
}
else {
double phase_offset = z_sin < 0 ? M_PI : 0.;
double a = cos(x + phase_offset);
double b = - z_sin;
double res = z_cos * sin(x + phase_offset + (flip ? 0 : M_PI));
double r = sqrt(sqr(a) + sqr(b));
return (asin(a/r) + asin(res/r) + 0.5 * M_PI);
}
}
static inline Polyline make_wave(
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor,
double z_cos, double z_sin, bool vertical, bool flip)
{
std::vector<Vec2d> points = one_period;
double period = points.back()(0);
if (width != period) // do not extend if already truncated
{
points.reserve(one_period.size() * size_t(floor(width / period)));
points.pop_back();
size_t n = points.size();
do {
points.emplace_back(points[points.size()-n].x() + period, points[points.size()-n].y());
} while (points.back()(0) < width - EPSILON);
points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip)));
}
// and construct the final polyline to return:
Polyline polyline;
polyline.points.reserve(points.size());
for (auto& point : points) {
point(1) += offset;
point(1) = std::clamp(double(point.y()), 0., height);
if (vertical)
std::swap(point(0), point(1));
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
}
return polyline;
}
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
{
std::vector<Vec2d> points;
double dx = M_PI_2; // exact coordinates on main inflexion lobes
double limit = std::min(2*M_PI, width);
points.reserve(coord_t(ceil(limit / tolerance / 3)));
for (double x = 0.; x < limit - EPSILON; x += dx) {
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
}
points.emplace_back(Vec2d(limit, f(limit, z_sin, z_cos, vertical, flip)));
// piecewise increase in resolution up to requested tolerance
for(;;)
{
size_t size = points.size();
for (unsigned int i = 1;i < size; ++i) {
auto& lp = points[i-1]; // left point
auto& rp = points[i]; // right point
double x = lp(0) + (rp(0) - lp(0)) / 2;
double y = f(x, z_sin, z_cos, vertical, flip);
Vec2d ip = {x, y};
if (std::abs(cross2(Vec2d(ip - lp), Vec2d(ip - rp))) > sqr(tolerance)) {
points.emplace_back(std::move(ip));
}
}
if (size == points.size())
break;
else
{
// insert new points in order
std::sort(points.begin(), points.end(),
[](const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0); });
}
}
return points;
}
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
{
const double scaleFactor = scale_(line_spacing) / density_adjusted;
// tolerance in scaled units. clamp the maximum tolerance as there's
// no processing-speed benefit to do so beyond a certain point
const double tolerance = std::min(line_spacing / 2, FillGyroid::PatternTolerance) / unscale<double>(scaleFactor);
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
const double z = gridZ / scaleFactor;
const double z_sin = sin(z);
const double z_cos = cos(z);
bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
double lower_bound = 0.;
double upper_bound = height;
bool flip = true;
if (vertical) {
flip = false;
lower_bound = -M_PI;
upper_bound = width - M_PI_2;
std::swap(width,height);
}
std::vector<Vec2d> one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
flip = !flip; // even polylines are a bit shifted
std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
Polylines result;
for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) {
// creates odd polylines
result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
// creates even polylines
y0 += M_PI;
if (y0 < upper_bound + EPSILON) {
result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
}
}
return result;
}
// FIXME: needed to fix build on Mac on buildserver
constexpr double FillGyroid::PatternTolerance;
void FillGyroid::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
if(std::abs(infill_angle) >= EPSILON)
expolygon.rotate(-infill_angle);
BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., params.density * DensityAdjust);
// Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
// align bounding box to a multiple of our grid module
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
// generate pattern
Polylines polylines = make_gyroid_waves(
scale_(this->z),
density_adjusted,
this->spacing,
ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.);
// shift the polyline to the grid origin
for (Polyline &pl : polylines)
pl.translate(bb.min);
polylines = intersection_pl(polylines, expolygon);
if (! polylines.empty()) {
// Remove very small bits, but be careful to not remove infill lines connecting thin walls!
// The infill perimeter lines should be separated by around a single infill line width.
const double minlength = scale_(0.8 * this->spacing);
polylines.erase(
std::remove_if(polylines.begin(), polylines.end(), [minlength](const Polyline &pl) { return pl.length() < minlength; }),
polylines.end());
}
if (! polylines.empty()) {
// connect lines
size_t polylines_out_first_idx = polylines_out.size();
if (params.dont_connect())
append(polylines_out, chain_polylines(polylines));
else
this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
// new paths must be rotated back
if (std::abs(infill_angle) >= EPSILON) {
for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++ it)
it->rotate(infill_angle);
}
}
}
} // namespace Slic3r

View file

@ -0,0 +1,41 @@
#ifndef slic3r_FillGyroid_hpp_
#define slic3r_FillGyroid_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class FillGyroid : public Fill
{
public:
FillGyroid() {}
Fill* clone() const override { return new FillGyroid(*this); }
// require bridge flow since most of this pattern hangs in air
bool use_bridge_flow() const override { return false; }
// Correction applied to regular infill angle to maximize printing
// speed in default configuration (degrees)
static constexpr float CorrectionAngle = -45.;
// Density adjustment to have a good %of weight.
static constexpr double DensityAdjust = 2.44;
// Gyroid upper resolution tolerance (mm^-2)
static constexpr double PatternTolerance = 0.2;
protected:
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
};
} // namespace Slic3r
#endif // slic3r_FillGyroid_hpp_

View file

@ -0,0 +1,83 @@
#include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillHoneycomb.hpp"
namespace Slic3r {
void FillHoneycomb::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
// cache hexagons math
CacheID cache_id(params.density, this->spacing);
Cache::iterator it_m = this->cache.find(cache_id);
if (it_m == this->cache.end()) {
it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData()));
CacheData &m = it_m->second;
coord_t min_spacing = coord_t(scale_(this->spacing));
m.distance = coord_t(min_spacing / params.density);
m.hex_side = coord_t(m.distance / (sqrt(3)/2));
m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3);
coord_t hex_height = m.hex_side * 2;
m.pattern_height = hex_height + m.hex_side;
m.y_short = coord_t(m.distance * sqrt(3)/3);
m.x_offset = min_spacing / 2;
m.y_offset = coord_t(m.x_offset * sqrt(3)/3);
m.hex_center = Point(m.hex_width/2, m.hex_side);
}
CacheData &m = it_m->second;
Polylines all_polylines;
{
// adjust actual bounding box to the nearest multiple of our hex pattern
// and align it so that it matches across layers
BoundingBox bounding_box = expolygon.contour.bounding_box();
{
// rotate bounding box according to infill direction
Polygon bb_polygon = bounding_box.polygon();
bb_polygon.rotate(direction.first, m.hex_center);
bounding_box = bb_polygon.bounding_box();
// extend bounding box so that our pattern will be aligned with other layers
// $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one
// The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough.
bounding_box.merge(align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height)));
}
coord_t x = bounding_box.min(0);
while (x <= bounding_box.max(0)) {
Polyline p;
coord_t ax[2] = { x + m.x_offset, x + m.distance - m.x_offset };
for (size_t i = 0; i < 2; ++ i) {
std::reverse(p.points.begin(), p.points.end()); // turn first half upside down
for (coord_t y = bounding_box.min(1); y <= bounding_box.max(1); y += m.y_short + m.hex_side + m.y_short + m.hex_side) {
p.points.push_back(Point(ax[1], y + m.y_offset));
p.points.push_back(Point(ax[0], y + m.y_short - m.y_offset));
p.points.push_back(Point(ax[0], y + m.y_short + m.hex_side + m.y_offset));
p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short - m.y_offset));
p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short + m.hex_side + m.y_offset));
}
ax[0] = ax[0] + m.distance;
ax[1] = ax[1] + m.distance;
std::swap(ax[0], ax[1]); // draw symmetrical pattern
x += m.distance;
}
p.rotate(-direction.first, m.hex_center);
all_polylines.push_back(p);
}
}
all_polylines = intersection_pl(std::move(all_polylines), expolygon);
if (params.dont_connect() || all_polylines.size() <= 1)
append(polylines_out, chain_polylines(std::move(all_polylines)));
else
connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params);
}
} // namespace Slic3r

View file

@ -0,0 +1,57 @@
#ifndef slic3r_FillHoneycomb_hpp_
#define slic3r_FillHoneycomb_hpp_
#include <map>
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class FillHoneycomb : public Fill
{
public:
~FillHoneycomb() override {}
protected:
Fill* clone() const override { return new FillHoneycomb(*this); };
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
// Caching the
struct CacheID
{
CacheID(float adensity, coordf_t aspacing) :
density(adensity), spacing(aspacing) {}
float density;
coordf_t spacing;
bool operator<(const CacheID &other) const
{ return (density < other.density) || (density == other.density && spacing < other.spacing); }
bool operator==(const CacheID &other) const
{ return density == other.density && spacing == other.spacing; }
};
struct CacheData
{
coord_t distance;
coord_t hex_side;
coord_t hex_width;
coord_t pattern_height;
coord_t y_short;
coord_t x_offset;
coord_t y_offset;
Point hex_center;
};
typedef std::map<CacheID, CacheData> Cache;
Cache cache;
float _layer_angle(size_t idx) const override { return float(M_PI/3.) * (idx % 3); }
};
} // namespace Slic3r
#endif // slic3r_FillHoneycomb_hpp_

View file

@ -0,0 +1,29 @@
#include "../Print.hpp"
#include "FillLightning.hpp"
#include "Lightning/Generator.hpp"
#include "../Surface.hpp"
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <numeric>
namespace Slic3r::FillLightning {
Polylines Filler::fill_surface(const Surface *surface, const FillParams &params)
{
const Layer &layer = generator->getTreesForLayer(this->layer_id);
return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width());
}
void GeneratorDeleter::operator()(Generator *p) {
delete p;
}
GeneratorPtr build_generator(const PrintObject &print_object)
{
return GeneratorPtr(new Generator(print_object));
}
} // namespace Slic3r::FillAdaptive

View file

@ -0,0 +1,36 @@
#ifndef slic3r_FillLightning_hpp_
#define slic3r_FillLightning_hpp_
#include "FillBase.hpp"
namespace Slic3r {
class PrintObject;
namespace FillLightning {
class Generator;
// To keep the definition of Octree opaque, we have to define a custom deleter.
struct GeneratorDeleter { void operator()(Generator *p); };
using GeneratorPtr = std::unique_ptr<Generator, GeneratorDeleter>;
GeneratorPtr build_generator(const PrintObject &print_object);
class Filler : public Slic3r::Fill
{
public:
~Filler() override = default;
Generator *generator { nullptr };
protected:
Fill* clone() const override { return new Filler(*this); }
// Perform the fill.
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
// Let the G-code export reoder the infill lines.
bool no_sort() const override { return false; }
};
} // namespace FillAdaptive
} // namespace Slic3r
#endif // slic3r_FillLightning_hpp_

View file

@ -0,0 +1,122 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillLine.hpp"
namespace Slic3r {
void FillLine::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
// rotate polygons so that we can work with vertical lines here
expolygon.rotate(- direction.first);
this->_min_spacing = scale_(this->spacing);
assert(params.density > 0.0001f && params.density <= 1.f);
this->_line_spacing = coord_t(coordf_t(this->_min_spacing) / params.density);
this->_diagonal_distance = this->_line_spacing * 2;
this->_line_oscillation = this->_line_spacing - this->_min_spacing; // only for Line infill
BoundingBox bounding_box = expolygon.contour.bounding_box();
// define flow spacing according to requested density
if (params.density > 0.9999f && !params.dont_adjust) {
this->_line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), this->_line_spacing);
this->spacing = unscale<double>(this->_line_spacing);
} else {
// extend bounding box so that our pattern will be aligned with other layers
// Transform the reference point to the rotated coordinate system.
bounding_box.merge(align_to_grid(
bounding_box.min,
Point(this->_line_spacing, this->_line_spacing),
direction.second.rotated(- direction.first)));
}
// generate the basic pattern
coord_t x_max = bounding_box.max(0) + SCALED_EPSILON;
Lines lines;
for (coord_t x = bounding_box.min(0); x <= x_max; x += this->_line_spacing)
lines.push_back(this->_line(lines.size(), x, bounding_box.min(1), bounding_box.max(1)));
// clip paths against a slightly larger expolygon, so that the first and last paths
// are kept even if the expolygon has vertical sides
// the minimum offset for preventing edge lines from being clipped is SCALED_EPSILON;
// however we use a larger offset to support expolygons with slightly skewed sides and
// not perfectly straight
//FIXME Vojtech: Update the intersecton function to work directly with lines.
Polylines polylines_src;
polylines_src.reserve(lines.size());
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) {
polylines_src.push_back(Polyline());
Points &pts = polylines_src.back().points;
pts.reserve(2);
pts.push_back(it->a);
pts.push_back(it->b);
}
Polylines polylines = intersection_pl(polylines_src, offset(expolygon, scale_(0.02)));
// FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines!
const float INFILL_OVERLAP_OVER_SPACING = 0.3f;
// How much to extend an infill path from expolygon outside?
coord_t extra = coord_t(floor(this->_min_spacing * INFILL_OVERLAP_OVER_SPACING + 0.5f));
for (Polylines::iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) {
Point *first_point = &it_polyline->points.front();
Point *last_point = &it_polyline->points.back();
if (first_point->y() > last_point->y())
std::swap(first_point, last_point);
first_point->y() -= extra;
last_point->y() += extra;
}
size_t n_polylines_out_old = polylines_out.size();
// connect lines
if (! params.dont_connect() && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
// offset the expolygon by max(min_spacing/2, extra)
ExPolygon expolygon_off;
{
ExPolygons expolygons_off = offset_ex(expolygon, this->_min_spacing/2);
if (! expolygons_off.empty()) {
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
assert(expolygons_off.size() == 1);
std::swap(expolygon_off, expolygons_off.front());
}
}
bool first = true;
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back();
// Distance in X, Y.
const Vector distance = last_point - first_point;
// TODO: we should also check that both points are on a fill_boundary to avoid
// connecting paths on the boundaries of internal regions
if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out.emplace_back(std::move(polyline));
first = false;
}
}
// paths must be rotated back
for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_old; it != polylines_out.end(); ++ it) {
// No need to translate, the absolute position is irrelevant.
// it->translate(- direction.second(0), - direction.second(1));
it->rotate(direction.first);
}
}
} // namespace Slic3r

View file

@ -0,0 +1,49 @@
#ifndef slic3r_FillLine_hpp_
#define slic3r_FillLine_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Surface;
class FillLine : public Fill
{
public:
Fill* clone() const override { return new FillLine(*this); };
~FillLine() override = default;
protected:
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
coord_t _min_spacing;
coord_t _line_spacing;
// distance threshold for allowing the horizontal infill lines to be connected into a continuous path
coord_t _diagonal_distance;
// only for line infill
coord_t _line_oscillation;
Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const {
coord_t osc = (i & 1) ? this->_line_oscillation : 0;
return Line(Point(x - osc, y_min), Point(x + osc, y_max));
}
bool _can_connect(coord_t dist_X, coord_t dist_Y)
{
const auto TOLERANCE = coord_t(10 * SCALED_EPSILON);
return (dist_X >= (this->_line_spacing - this->_line_oscillation) - TOLERANCE)
&& (dist_X <= (this->_line_spacing + this->_line_oscillation) + TOLERANCE)
&& (dist_Y <= this->_diagonal_distance);
}
};
}; // namespace Slic3r
#endif // slic3r_FillLine_hpp_

View file

@ -0,0 +1,185 @@
#include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillPlanePath.hpp"
namespace Slic3r {
void FillPlanePath::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
expolygon.rotate(- direction.first);
coord_t distance_between_lines = coord_t(scale_(this->spacing) / params.density);
// align infill across layers using the object's bounding box
// Rotated bounding box of the whole object.
BoundingBox bounding_box = this->bounding_box.rotated(- direction.first);
Point shift = this->_centered() ?
bounding_box.center() :
bounding_box.min;
expolygon.translate(-shift.x(), -shift.y());
bounding_box.translate(-shift.x(), -shift.y());
Pointfs pts = _generate(
coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)),
coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)),
coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)),
coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)),
params.resolution);
if (pts.size() >= 2) {
// Convert points to a polyline, upscale.
Polylines polylines(1, Polyline());
Polyline &polyline = polylines.front();
polyline.points.reserve(pts.size());
for (const Vec2d &pt : pts)
polyline.points.emplace_back(
coord_t(floor(pt.x() * distance_between_lines + 0.5)),
coord_t(floor(pt.y() * distance_between_lines + 0.5)));
polylines = intersection_pl(polylines, expolygon);
Polylines chained;
if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1)
chained = chain_polylines(std::move(polylines));
else
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
// paths must be repositioned and rotated back
for (Polyline &pl : chained) {
pl.translate(shift.x(), shift.y());
pl.rotate(direction.first);
}
append(polylines_out, std::move(chained));
}
}
// Follow an Archimedean spiral, in polar coordinates: r=a+b\theta
Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution)
{
// Radius to achieve.
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
// Now unwind the spiral.
coordf_t a = 1.;
coordf_t b = 1./(2.*M_PI);
coordf_t theta = 0.;
coordf_t r = 1;
Pointfs out;
//FIXME Vojtech: If used as a solid infill, there is a gap left at the center.
out.emplace_back(0, 0);
out.emplace_back(1, 0);
while (r < rmax) {
// Discretization angle to achieve a discretization error lower than resolution.
theta += 2. * acos(1. - resolution / r);
r = a + b * theta;
out.emplace_back(r * cos(theta), r * sin(theta));
}
return out;
}
// Adapted from
// http://cpansearch.perl.org/src/KRYDE/Math-PlanePath-122/lib/Math/PlanePath/HilbertCurve.pm
//
// state=0 3--2 plain
// |
// 0--1
//
// state=4 1--2 transpose
// | |
// 0 3
//
// state=8
//
// state=12 3 0 rot180 + transpose
// | |
// 2--1
//
static inline Point hilbert_n_to_xy(const size_t n)
{
static constexpr const int next_state[16] { 4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0 };
static constexpr const int digit_to_x[16] { 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0 };
static constexpr const int digit_to_y[16] { 0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1 };
// Number of 2 bit digits.
size_t ndigits = 0;
{
size_t nc = n;
while(nc > 0) {
nc >>= 2;
++ ndigits;
}
}
int state = (ndigits & 1) ? 4 : 0;
coord_t x = 0;
coord_t y = 0;
for (int i = (int)ndigits - 1; i >= 0; -- i) {
int digit = (n >> (i * 2)) & 3;
state += digit;
x |= digit_to_x[state] << i;
y |= digit_to_y[state] << i;
state = next_state[state];
}
return Point(x, y);
}
Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
{
// Minimum power of two square to fit the domain.
size_t sz = 2;
size_t pw = 1;
{
size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
while (sz < sz0) {
sz = sz << 1;
++ pw;
}
}
size_t sz2 = sz * sz;
Pointfs line;
line.reserve(sz2);
for (size_t i = 0; i < sz2; ++ i) {
Point p = hilbert_n_to_xy(i);
line.emplace_back(p.x() + min_x, p.y() + min_y);
}
return line;
}
Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
{
// Radius to achieve.
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
// Now unwind the spiral.
coordf_t r = 0;
coordf_t r_inc = sqrt(2.);
Pointfs out;
out.emplace_back(0., 0.);
while (r < rmax) {
r += r_inc;
coordf_t rx = r / sqrt(2.);
coordf_t r2 = r + rx;
out.emplace_back( r, 0.);
out.emplace_back( r2, rx);
out.emplace_back( rx, rx);
out.emplace_back( rx, r2);
out.emplace_back( 0., r);
out.emplace_back(-rx, r2);
out.emplace_back(-rx, rx);
out.emplace_back(-r2, rx);
out.emplace_back(- r, 0.);
out.emplace_back(-r2, -rx);
out.emplace_back(-rx, -rx);
out.emplace_back(-rx, -r2);
out.emplace_back( 0., -r);
out.emplace_back( rx, -r2);
out.emplace_back( rx, -rx);
out.emplace_back( r2+r_inc, -rx);
}
return out;
}
} // namespace Slic3r

View file

@ -0,0 +1,69 @@
#ifndef slic3r_FillPlanePath_hpp_
#define slic3r_FillPlanePath_hpp_
#include <map>
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
// The original Perl code used path generators from Math::PlanePath library:
// http://user42.tuxfamily.org/math-planepath/
// http://user42.tuxfamily.org/math-planepath/gallery.html
class FillPlanePath : public Fill
{
public:
~FillPlanePath() override = default;
protected:
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
float _layer_angle(size_t idx) const override { return 0.f; }
virtual bool _centered() const = 0;
virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) = 0;
};
class FillArchimedeanChords : public FillPlanePath
{
public:
Fill* clone() const override { return new FillArchimedeanChords(*this); };
~FillArchimedeanChords() override = default;
protected:
bool _centered() const override { return true; }
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
};
class FillHilbertCurve : public FillPlanePath
{
public:
Fill* clone() const override { return new FillHilbertCurve(*this); };
~FillHilbertCurve() override = default;
protected:
bool _centered() const override { return false; }
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
};
class FillOctagramSpiral : public FillPlanePath
{
public:
Fill* clone() const override { return new FillOctagramSpiral(*this); };
~FillOctagramSpiral() override = default;
protected:
bool _centered() const override { return true; }
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
};
} // namespace Slic3r
#endif // slic3r_FillPlanePath_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,141 @@
#ifndef slic3r_FillRectilinear_hpp_
#define slic3r_FillRectilinear_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Surface;
class FillRectilinear : public Fill
{
public:
Fill* clone() const override { return new FillRectilinear(*this); }
~FillRectilinear() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
protected:
// Fill by single directional lines, interconnect the lines along perimeters.
bool fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out);
// Fill by multiple sweeps of differing directions.
struct SweepParams {
float angle_base;
float pattern_shift;
};
bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out);
};
class FillAlignedRectilinear : public FillRectilinear
{
public:
Fill* clone() const override { return new FillAlignedRectilinear(*this); }
~FillAlignedRectilinear() override = default;
protected:
// Always generate infill at the same angle.
virtual float _layer_angle(size_t idx) const override { return 0.f; }
};
class FillMonotonic : public FillRectilinear
{
public:
Fill* clone() const override { return new FillMonotonic(*this); }
~FillMonotonic() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
bool no_sort() const override { return true; }
};
class FillMonotonicLine : public FillRectilinear
{
public:
Fill* clone() const override { return new FillMonotonicLine(*this); }
~FillMonotonicLine() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
bool no_sort() const override { return true; }
};
class FillGrid : public FillRectilinear
{
public:
Fill* clone() const override { return new FillGrid(*this); }
~FillGrid() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
float _layer_angle(size_t idx) const override { return 0.f; }
};
class FillTriangles : public FillRectilinear
{
public:
Fill* clone() const override { return new FillTriangles(*this); }
~FillTriangles() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
float _layer_angle(size_t idx) const override { return 0.f; }
};
class FillStars : public FillRectilinear
{
public:
Fill* clone() const override { return new FillStars(*this); }
~FillStars() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
float _layer_angle(size_t idx) const override { return 0.f; }
};
class FillCubic : public FillRectilinear
{
public:
Fill* clone() const override { return new FillCubic(*this); }
~FillCubic() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
float _layer_angle(size_t idx) const override { return 0.f; }
};
class FillSupportBase : public FillRectilinear
{
public:
Fill* clone() const override { return new FillSupportBase(*this); }
~FillSupportBase() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
float _layer_angle(size_t idx) const override { return 0.f; }
};
class FillMonotonicLineWGapFill : public Fill
{
public:
~FillMonotonicLineWGapFill() override = default;
void fill_surface_extrusion(const Surface *surface, const FillParams &params, ExtrusionEntitiesPtr &out) override;
protected:
Fill* clone() const override { return new FillMonotonicLineWGapFill(*this); };
bool no_sort() const override { return true; }
private:
void fill_surface_by_lines(const Surface* surface, const FillParams& params, Polylines& polylines_out);
};
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing);
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing);
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing);
} // namespace Slic3r
#endif // slic3r_FillRectilinear_hpp_

View file

@ -0,0 +1,99 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "DistanceField.hpp" //Class we're implementing.
#include "../FillRectilinear.hpp"
#include "../../ClipperUtils.hpp"
namespace Slic3r::FillLightning
{
constexpr coord_t radius_per_cell_size = 6; // The cell-size should be small compared to the radius, but not so small as to be inefficient.
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang) :
m_cell_size(radius / radius_per_cell_size),
m_supporting_radius(radius)
{
m_supporting_radius2 = double(radius) * double(radius);
// Sample source polygons with a regular grid sampling pattern.
for (const ExPolygon &expoly : union_ex(current_outline)) {
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
// Find a squared distance to the source expolygon boundary.
double d2 = std::numeric_limits<double>::max();
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++ icontour) {
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
if (contour.size() > 2) {
Point prev = contour.points.back();
for (const Point &p2 : contour.points) {
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
prev = p2;
}
}
}
m_unsupported_points.emplace_back(p, sqrt(d2));
}
}
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
constexpr coord_t prime_for_hash = 191;
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
a.dist_to_boundary < b.dist_to_boundary :
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
});
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
UnsupportedCell& cell = *it;
m_unsupported_points_grid.emplace(Point{ cell.loc.x() / m_cell_size, cell.loc.y() / m_cell_size }, it);
}
}
void DistanceField::update(const Point& to_node, const Point& added_leaf)
{
Vec2d v = (added_leaf - to_node).cast<double>();
auto l2 = v.squaredNorm();
Vec2d extent = Vec2d(-v.y(), v.x()) * m_supporting_radius / sqrt(l2);
BoundingBox grid;
{
Point diagonal(m_supporting_radius, m_supporting_radius);
Point iextent(extent.cast<coord_t>());
grid = BoundingBox(added_leaf - diagonal, added_leaf + diagonal);
grid.merge(to_node - iextent);
grid.merge(to_node + iextent);
grid.merge(added_leaf - iextent);
grid.merge(added_leaf + iextent);
grid.min /= m_cell_size;
grid.max /= m_cell_size;
}
Point grid_loc;
for (coord_t row = grid.min.y(); row <= grid.max.y(); ++ row) {
grid_loc.y() = row * m_cell_size;
for (coord_t col = grid.min.x(); col <= grid.max.y(); ++ col) {
grid_loc.x() = col * m_cell_size;
// Test inside a circle at the new leaf.
if ((grid_loc - added_leaf).cast<double>().squaredNorm() > m_supporting_radius2) {
// Not inside a circle at the end of the new leaf.
// Test inside a rotated rectangle.
Vec2d vx = (grid_loc - to_node).cast<double>();
double d = v.dot(vx);
if (d >= 0 && d <= l2) {
d = extent.dot(vx);
if (d < -1. || d > 1.)
// Not inside a rotated rectangle.
continue;
}
}
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
// Remove unsupported leafs at this grid location.
if (auto it = m_unsupported_points_grid.find(grid_loc); it != m_unsupported_points_grid.end()) {
std::list<UnsupportedCell>::iterator& list_it = it->second;
UnsupportedCell& cell = *list_it;
if ((cell.loc - added_leaf).cast<double>().squaredNorm() <= m_supporting_radius2) {
m_unsupported_points.erase(list_it);
m_unsupported_points_grid.erase(it);
}
}
}
}
}
} // namespace Slic3r::FillLightning

View file

@ -0,0 +1,100 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_DISTANCE_FIELD_H
#define LIGHTNING_DISTANCE_FIELD_H
#include "../../Point.hpp"
#include "../../Polygon.hpp"
namespace Slic3r::FillLightning
{
/*!
* 2D field that maintains locations which need to be supported for Lightning
* Infill.
*
* This field contains a set of "cells", spaced out in a grid. Each cell
* maintains how far it is removed from the edge, which is used to determine
* how it gets supported by Lightning Infill.
*/
class DistanceField
{
public:
/*!
* Construct a new field to calculate Lightning Infill with.
* \param radius The radius of influence that an infill line is expected to
* support in the layer above.
* \param current_outline The total infill area on this layer.
* \param current_overhang The overhang that needs to be supported on this
* layer.
*/
DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang);
/*!
* Gets the next unsupported location to be supported by a new branch.
* \param p Output variable for the next point to support.
* \return ``true`` if successful, or ``false`` if there are no more points
* to consider.
*/
bool tryGetNextPoint(Point* p) const {
if (m_unsupported_points.empty())
return false;
*p = m_unsupported_points.front().loc;
return true;
}
/*!
* Update the distance field with a newly added branch.
*
* The branch is a line extending from \p to_node to \p added_leaf . This
* function updates the grid cells so that the distance field knows how far
* off it is from being supported by the current pattern. Grid points are
* updated with sampling points spaced out by the supporting radius along
* the line.
* \param to_node The node endpoint of the newly added branch.
* \param added_leaf The location of the leaf of the newly added branch,
* drawing a straight line to the node.
*/
void update(const Point& to_node, const Point& added_leaf);
protected:
/*!
* Spacing between grid points to consider supporting.
*/
coord_t m_cell_size;
/*!
* The radius of the area of the layer above supported by a point on a
* branch of a tree.
*/
coord_t m_supporting_radius;
double m_supporting_radius2;
/*!
* Represents a small discrete area of infill that needs to be supported.
*/
struct UnsupportedCell
{
UnsupportedCell(Point loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
// The position of the center of this cell.
Point loc;
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
coord_t dist_to_boundary;
};
/*!
* Cells which still need to be supported at some point.
*/
std::list<UnsupportedCell> m_unsupported_points;
/*!
* Links the unsupported points to a grid point, so that we can quickly look
* up the cell belonging to a certain position in the grid.
*/
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
};
} // namespace Slic3r::FillLightning
#endif //LIGHTNING_DISTANCE_FIELD_H

View file

@ -0,0 +1,127 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "Generator.hpp"
#include "TreeNode.hpp"
#include "../../ClipperUtils.hpp"
#include "../../Layer.hpp"
#include "../../Print.hpp"
#include "../../Surface.hpp"
/* Possible future tasks/optimizations,etc.:
* - Improve connecting heuristic to favor connecting to shorter trees
* - Change which node of a tree is the root when that would be better in reconnectRoots.
* - (For implementation in Infill classes & elsewhere): Outline offset, infill-overlap & perimeter gaps.
* - Allow for polylines, i.e. merge Tims PR about polyline fixes
* - Unit Tests?
* - Optimization: let the square grid store the closest point on boundary
* - Optimization: only compute the closest dist to / point on boundary for the outer cells and flood-fill the rest
* - Make a pass with Arachne over the output. Somehow.
* - Generate all to-be-supported points at once instead of sequentially: See branch interlocking_gen PolygonUtils::spreadDots (Or work with sparse grids.)
* - Lots of magic values ... to many to parameterize. But are they the best?
* - Move more complex computations from Generator constructor to elsewhere.
*/
namespace Slic3r::FillLightning {
Generator::Generator(const PrintObject &print_object)
{
const PrintConfig &print_config = print_object.print()->config();
const PrintObjectConfig &object_config = print_object.config();
const PrintRegionConfig &region_config = print_object.shared_regions()->all_regions.front()->config();
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
// const int sparse_infill_filament = region_config.sparse_infill_filament.value;
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
const double layer_thickness = object_config.layer_height;
m_infill_extrusion_width = scaled<float>(region_config.sparse_infill_line_width.value);
m_supporting_radius = scaled<coord_t>(m_infill_extrusion_width * 0.001 / region_config.sparse_infill_density);
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
m_wall_supporting_radius = layer_thickness * std::tan(lightning_infill_overhang_angle);
m_prune_length = layer_thickness * std::tan(lightning_infill_prune_angle);
m_straightening_max_distance = layer_thickness * std::tan(lightning_infill_straightening_angle);
generateInitialInternalOverhangs(print_object);
generateTrees(print_object);
}
void Generator::generateInitialInternalOverhangs(const PrintObject &print_object)
{
m_overhang_per_layer.resize(print_object.layers().size());
const float infill_wall_offset = - m_infill_extrusion_width;
Polygons infill_area_above;
//Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging.
for (int layer_nr = print_object.layers().size() - 1; layer_nr >= 0; layer_nr--) {
Polygons infill_area_here;
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
for (const Surface& surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
append(infill_area_here, offset(surface.expolygon, infill_wall_offset));
//Remove the part of the infill area that is already supported by the walls.
Polygons overhang = diff(offset(infill_area_here, -m_wall_supporting_radius), infill_area_above);
m_overhang_per_layer[layer_nr] = overhang;
infill_area_above = std::move(infill_area_here);
}
}
const Layer& Generator::getTreesForLayer(const size_t& layer_id) const
{
assert(layer_id < m_lightning_layers.size());
return m_lightning_layers[layer_id];
}
void Generator::generateTrees(const PrintObject &print_object)
{
m_lightning_layers.resize(print_object.layers().size());
const coord_t infill_wall_offset = - m_infill_extrusion_width;
std::vector<Polygons> infill_outlines(print_object.layers().size(), Polygons());
// For-each layer from top to bottom:
for (int layer_id = print_object.layers().size() - 1; layer_id >= 0; layer_id--)
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
for (const Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
append(infill_outlines[layer_id], offset(surface.expolygon, infill_wall_offset));
// For various operations its beneficial to quickly locate nearby features on the polygon:
const size_t top_layer_id = print_object.layers().size() - 1;
EdgeGrid::Grid outlines_locator(get_extents(infill_outlines[top_layer_id]).inflated(SCALED_EPSILON));
outlines_locator.create(infill_outlines[top_layer_id], locator_cell_size);
// For-each layer from top to bottom:
for (int layer_id = top_layer_id; layer_id >= 0; layer_id--)
{
Layer& current_lightning_layer = m_lightning_layers[layer_id];
Polygons& current_outlines = infill_outlines[layer_id];
// register all trees propagated from the previous layer as to-be-reconnected
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
// Initialize trees for next lower layer from the current one.
if (layer_id == 0)
return;
const Polygons& below_outlines = infill_outlines[layer_id - 1];
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
outlines_locator.create(below_outlines, locator_cell_size);
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
for (auto& tree : current_lightning_layer.tree_roots)
tree->propagateToNextLayer(lower_trees, below_outlines, outlines_locator, m_prune_length, m_straightening_max_distance, locator_cell_size / 2);
}
}
} // namespace Slic3r::FillLightning

View file

@ -0,0 +1,132 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_GENERATOR_H
#define LIGHTNING_GENERATOR_H
#include "Layer.hpp"
#include <functional>
#include <memory>
#include <vector>
namespace Slic3r
{
class PrintObject;
namespace FillLightning
{
/*!
* Generates the Lightning Infill pattern.
*
* The lightning infill pattern is designed to use a minimal amount of material
* to support the top skin of the print, while still printing with reasonably
* consistently flowing lines. It sacrifices strength completely in favour of
* top surface quality and reduced print time / material usage.
*
* Lightning Infill is so named because the patterns it creates resemble a
* forked path with one main path and many small lines on the side. These paths
* grow out from the sides of the model just below where the top surface needs
* to be supported from the inside, so that minimal material is needed.
*
* This pattern is based on a paper called "Ribbed Support Vaults for 3D
* Printing of Hollowed Objects" by Tricard, Claux and Lefebvre:
* https://www.researchgate.net/publication/333808588_Ribbed_Support_Vaults_for_3D_Printing_of_Hollowed_Objects
*/
class Generator // "Just like Nicola used to make!"
{
public:
/*!
* Create a generator to fill a certain mesh with infill.
*
* This generator will pre-compute things in preparation of generating
* Lightning Infill for the infill areas in that mesh. The infill areas must
* already be calculated at this point.
* \param mesh The mesh to generate infill for.
*/
Generator(const PrintObject &print_object);
/*!
* Get a tree of paths generated for a certain layer of the mesh.
*
* This tree represents the paths that must be traced to print the infill.
* \param layer_id The layer number to get the path tree for. This is within
* the range of layers of the mesh (not the global layer numbers).
* \return A tree structure representing paths to print to create the
* Lightning Infill pattern.
*/
const Layer& getTreesForLayer(const size_t& layer_id) const;
float infilll_extrusion_width() const { return m_infill_extrusion_width; }
protected:
/*!
* Calculate the overhangs above the infill areas that need to be supported
* by infill.
*
* Normally, overhangs are only generated for the outside of the model and
* only when support is generated. For this pattern, we also need to
* generate overhang areas for the inside of the model.
*/
void generateInitialInternalOverhangs(const PrintObject &print_object);
/*!
* Calculate the tree structure of all layers.
*/
void generateTrees(const PrintObject &print_object);
float m_infill_extrusion_width;
/*!
* How far each piece of infill can support skin in the layer above.
*/
coord_t m_supporting_radius;
/*!
* How far a wall can support the wall above it. If a wall completely
* supports the wall above it, no infill needs to support that.
*
* This is similar to the overhang distance calculated for support. It is
* determined by the lightning_infill_overhang_angle setting.
*/
coord_t m_wall_supporting_radius;
/*!
* How far each piece of infill can support other infill in the layer above.
*
* This may be different than \ref supporting_radius, because the infill is
* printed with one end floating in mid-air. This endpoint will sag more, so
* an infill line may need to be supported more than a skin line.
*/
coord_t m_prune_length;
/*!
* How far a line may be shifted in order to straighten the line out.
*
* Straightening the line reduces material and time usage and reduces
* accelerations needed to print the pattern. However it makes the infill
* weak if lines are partially suspended next to the line on the previous
* layer.
*/
coord_t m_straightening_max_distance;
/*!
* For each layer, the overhang that needs to be supported by the pattern.
*
* This is generated by \ref generateInitialInternalOverhangs.
*/
std::vector<Polygons> m_overhang_per_layer;
/*!
* For each layer, the generated lightning paths.
*
* This is generated by \ref generateTrees.
*/
std::vector<Layer> m_lightning_layers;
};
} // namespace FillLightning
} // namespace Slic3r
#endif // LIGHTNING_GENERATOR_H

View file

@ -0,0 +1,410 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "Layer.hpp" //The class we're implementing.
#include <iterator> // advance
#include "DistanceField.hpp"
#include "TreeNode.hpp"
#include "../../Geometry.hpp"
namespace Slic3r::FillLightning {
coord_t Layer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location)
{
return coord_t((boundary_loc - unsupported_location).cast<double>().norm());
}
Point GroundingLocation::p() const
{
assert(tree_node || boundary_location);
return tree_node ? tree_node->getLocation() : *boundary_location;
}
void Layer::fillLocator(SparseNodeGrid &tree_node_locator)
{
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator](NodeSPtr node) {
tree_node_locator.insert(std::make_pair(Point(node->getLocation().x() / locator_cell_size, node->getLocation().y() / locator_cell_size), node));
};
for (auto& tree : tree_roots)
tree->visitNodes(add_node_to_locator_func);
}
void Layer::generateNewTrees
(
const Polygons& current_overhang,
const Polygons& current_outlines,
const EdgeGrid::Grid& outlines_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
)
{
DistanceField distance_field(supporting_radius, current_outlines, current_overhang);
SparseNodeGrid tree_node_locator;
fillLocator(tree_node_locator);
// Until no more points need to be added to support all:
// Determine next point from tree/outline areas via distance-field
Point unsupported_location;
while (distance_field.tryGetNextPoint(&unsupported_location)) {
GroundingLocation grounding_loc = getBestGroundingLocation(
unsupported_location, current_outlines, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
NodeSPtr new_parent;
NodeSPtr new_child;
this->attach(unsupported_location, grounding_loc, new_child, new_parent);
tree_node_locator.insert(std::make_pair(Point(new_child->getLocation().x() / locator_cell_size, new_child->getLocation().y() / locator_cell_size), new_child));
if (new_parent)
tree_node_locator.insert(std::make_pair(Point(new_parent->getLocation().x() / locator_cell_size, new_parent->getLocation().y() / locator_cell_size), new_parent));
// update distance field
distance_field.update(grounding_loc.p(), unsupported_location);
}
}
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const EdgeGrid::Grid &loc_to_line)
{
struct Visitor {
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
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 = 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 = grid.segment(*it_contour_and_segment);
if (Geometry::segments_intersect(segment.first, segment.second, line.a, line.b)) {
this->intersect = true;
return false;
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid& grid;
Line line;
bool intersect = false;
} visitor(loc_to_line);
loc_to_line.visit_cells_intersecting_line(from, to, visitor);
return visitor.intersect;
}
GroundingLocation Layer::getBestGroundingLocation
(
const Point& unsupported_location,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius,
const SparseNodeGrid& tree_node_locator,
const NodeSPtr& exclude_tree
)
{
// Closest point on current_outlines to unsupported_location:
Point node_location;
{
double d2 = std::numeric_limits<double>::max();
for (const Polygon &contour : current_outlines)
if (contour.size() > 2) {
Point prev = contour.points.back();
for (const Point &p2 : contour.points) {
if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) {
d2 = d;
node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast<coord_t>();
}
prev = p2;
}
}
}
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
NodeSPtr sub_tree{ nullptr };
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
const coord_t search_radius = std::min(current_dist, within_dist);
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
region.min /= locator_cell_size;
region.max /= locator_cell_size;
Point grid_addr;
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
auto it_range = tree_node_locator.equal_range(grid_addr);
for (auto it = it_range.first; it != it_range.second; ++ it) {
auto candidate_sub_tree = it->second.lock();
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
if (candidate_dist < current_dist) {
current_dist = candidate_dist;
sub_tree = candidate_sub_tree;
}
}
}
}
}
return ! sub_tree ?
GroundingLocation{ nullptr, node_location } :
GroundingLocation{ sub_tree, std::optional<Point>() };
}
bool Layer::attach(
const Point& unsupported_location,
const GroundingLocation& grounding_loc,
NodeSPtr& new_child,
NodeSPtr& new_root)
{
// Update trees & distance fields.
if (grounding_loc.boundary_location) {
new_root = Node::create(grounding_loc.p(), std::make_optional(grounding_loc.p()));
new_child = new_root->addChild(unsupported_location);
tree_roots.push_back(new_root);
return true;
} else {
new_child = grounding_loc.tree_node->addChild(unsupported_location);
return false;
}
}
void Layer::reconnectRoots
(
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
)
{
constexpr coord_t tree_connecting_ignore_offset = 100;
SparseNodeGrid tree_node_locator;
fillLocator(tree_node_locator);
const coord_t within_max_dist = outline_locator.resolution() * 2;
for (auto root_ptr : to_be_reconnected_tree_roots)
{
auto old_root_it = std::find(tree_roots.begin(), tree_roots.end(), root_ptr);
if (root_ptr->getLastGroundingLocation())
{
const Point& ground_loc = *root_ptr->getLastGroundingLocation();
if (ground_loc != root_ptr->getLocation())
{
Point new_root_pt;
// Find an intersection of the line segment from root_ptr->getLocation() to ground_loc, at within_max_dist from ground_loc.
if (lineSegmentPolygonsIntersection(root_ptr->getLocation(), ground_loc, outline_locator, new_root_pt, within_max_dist)) {
auto new_root = Node::create(new_root_pt, new_root_pt);
root_ptr->addChild(new_root);
new_root->reroot();
tree_node_locator.insert(std::make_pair(Point(new_root->getLocation().x() / locator_cell_size, new_root->getLocation().y() / locator_cell_size), new_root));
*old_root_it = std::move(new_root); // replace old root with new root
continue;
}
}
}
const coord_t tree_connecting_ignore_width = wall_supporting_radius - tree_connecting_ignore_offset; // Ideally, the boundary size in which the valence rule is ignored would be configurable.
GroundingLocation ground =
getBestGroundingLocation
(
root_ptr->getLocation(),
current_outlines,
outline_locator,
supporting_radius,
tree_connecting_ignore_width,
tree_node_locator,
root_ptr
);
if (ground.boundary_location)
{
if (*ground.boundary_location == root_ptr->getLocation())
continue; // Already on the boundary.
auto new_root = Node::create(ground.p(), ground.p());
auto attach_ptr = root_ptr->closestNode(new_root->getLocation());
attach_ptr->reroot();
new_root->addChild(attach_ptr);
tree_node_locator.insert(std::make_pair(new_root->getLocation(), new_root));
*old_root_it = std::move(new_root); // replace old root with new root
}
else
{
assert(ground.tree_node);
assert(ground.tree_node != root_ptr);
assert(!root_ptr->hasOffspring(ground.tree_node));
assert(!ground.tree_node->hasOffspring(root_ptr));
auto attach_ptr = root_ptr->closestNode(ground.tree_node->getLocation());
attach_ptr->reroot();
ground.tree_node->addChild(attach_ptr);
// remove old root
*old_root_it = std::move(tree_roots.back());
tree_roots.pop_back();
}
}
}
/*
* Implementation assumes moving inside, but moving outside should just as well be possible.
*/
static unsigned int moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
{
Point ret = from;
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
unsigned int bestPoly = static_cast<unsigned int>(-1);
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
const Polygon &poly = polygons[poly_idx];
if (poly.size() < 2)
continue;
Point p0 = poly[poly.size() - 2];
Point p1 = poly.back();
// because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop
// to avoid integer rounding edge cases
bool projected_p_beyond_prev_segment = (p1 - p0).cast<int64_t>().dot((from - p0).cast<int64_t>()) >= (p1 - p0).cast<int64_t>().squaredNorm();
for (const Point& p2 : poly)
{
// X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A));
// = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A);
// X = P projected on AB
const Point& a = p1;
const Point& b = p2;
const Point& p = from;
Point ab = b - a;
Point ap = p - a;
int64_t ab_length2 = ab.cast<int64_t>().squaredNorm();
if (ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
{
p1 = p2; //Skip only one of the points.
continue;
}
int64_t dot_prod = ab.cast<int64_t>().dot(ap.cast<int64_t>());
if (dot_prod <= 0) // x is projected to before ab
{
if (projected_p_beyond_prev_segment)
{ // case which looks like: > .
projected_p_beyond_prev_segment = false;
Point& x = p1;
int64_t dist2 = (x - p).cast<int64_t>().squaredNorm();
if (dist2 < bestDist2)
{
bestDist2 = dist2;
bestPoly = poly_idx;
if (distance == 0) {
ret = x;
} else {
// inward direction irrespective of sign of [distance]
Point inward_dir = perp((ab.cast<double>().normalized() * scaled<double>(10.0) + (p1 - p0).cast<double>().normalized() * scaled<double>(10.0)).eval()).cast<coord_t>();
// MM2INT(10.0) to retain precision for the eventual normalization
ret = x + (inward_dir.cast<double>().normalized() * distance).cast<coord_t>();
is_already_on_correct_side_of_boundary = inward_dir.cast<int64_t>().dot((p - x).cast<int64_t>()) * distance >= 0;
}
}
}
else
{
projected_p_beyond_prev_segment = false;
p0 = p1;
p1 = p2;
continue;
}
}
else if (dot_prod >= ab_length2) // x is projected to beyond ab
{
projected_p_beyond_prev_segment = true;
p0 = p1;
p1 = p2;
continue;
}
else
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
projected_p_beyond_prev_segment = false;
Point x = a + ab * dot_prod / ab_length2;
int64_t dist2 = (p - x).cast<int64_t>().squaredNorm();
if (dist2 < bestDist2)
{
bestDist2 = dist2;
bestPoly = poly_idx;
if (distance == 0) { ret = x; }
else
{
// inward or outward depending on the sign of [distance]
Vec2d inward_dir = perp((ab.cast<double>().normalized() * distance).eval());
ret = x + inward_dir.cast<coord_t>();
is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast<double>()) >= 0;
}
}
}
p0 = p1;
p1 = p2;
}
}
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
{
if (bestDist2 < distance * distance)
{
from = ret;
}
else
{
// from = from; // original point stays unaltered. It is already inside by enough distance
}
return bestPoly;
}
else if (bestDist2 < maxDist2)
{
from = ret;
return bestPoly;
}
return static_cast<unsigned int>(-1);
}
// Returns 'added someting'.
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
{
if (tree_roots.empty())
return {};
Polygons result_lines;
for (const auto& tree : tree_roots) {
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
constexpr coord_t epsilon = 5;
Point should_be_inside = tree->getLocation();
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
if (inside(limit_to_outline, should_be_inside))
tree->convertToPolylines(result_lines, line_width);
}
// TODO: allow for polylines!
Polylines split_lines;
for (Polygon &line : result_lines) {
if (line.size() <= 1)
continue;
Point last = line[0];
for (size_t point_idx = 1; point_idx < line.size(); point_idx++) {
Point here = line[point_idx];
split_lines.push_back({ last, here });
last = here;
}
}
return split_lines;
}
} // namespace Slic3r::Lightning

View file

@ -0,0 +1,88 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_LAYER_H
#define LIGHTNING_LAYER_H
#include "../../EdgeGrid.hpp"
#include "../../Polygon.hpp"
#include <memory>
#include <vector>
#include <list>
#include <unordered_map>
#include <optional>
namespace Slic3r::FillLightning
{
class Node;
using NodeSPtr = std::shared_ptr<Node>;
using SparseNodeGrid = std::unordered_multimap<Point, std::weak_ptr<Node>, PointHash>;
struct GroundingLocation
{
NodeSPtr tree_node; //!< not null if the gounding location is on a tree
std::optional<Point> boundary_location; //!< in case the gounding location is on the boundary
Point p() const;
};
/*!
* A layer of the lightning fill.
*
* Contains the trees to be printed and propagated to the next layer below.
*/
class Layer
{
public:
std::vector<NodeSPtr> tree_roots;
void generateNewTrees
(
const Polygons& current_overhang,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
);
/*! Determine & connect to connection point in tree/outline.
* \param min_dist_from_boundary_for_tree If the unsupported point is closer to the boundary than this then don't consider connecting it to a tree
*/
GroundingLocation getBestGroundingLocation
(
const Point& unsupported_location,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius,
const SparseNodeGrid& tree_node_locator,
const NodeSPtr& exclude_tree = nullptr
);
/*!
* \param[out] new_child The new child node introduced
* \param[out] new_root The new root node if one had been made
* \return Whether a new root was added
*/
bool attach(const Point& unsupported_location, const GroundingLocation& ground, NodeSPtr& new_child, NodeSPtr& new_root);
void reconnectRoots
(
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
);
Polylines convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const;
coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location);
void fillLocator(SparseNodeGrid& tree_node_locator);
};
} // namespace Slic3r::FillLightning
#endif // LIGHTNING_LAYER_H

View file

@ -0,0 +1,411 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "TreeNode.hpp"
#include "../../Geometry.hpp"
#include "../../ClipperUtils.hpp"
namespace Slic3r::FillLightning {
coord_t Node::getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const
{
constexpr coord_t min_valence_for_boost = 0;
constexpr coord_t max_valence_for_boost = 4;
constexpr coord_t valence_boost_multiplier = 4;
const size_t valence = (!m_is_root) + m_children.size();
const coord_t valence_boost = (min_valence_for_boost < valence && valence < max_valence_for_boost) ? valence_boost_multiplier * supporting_radius : 0;
const auto dist_here = coord_t((getLocation() - unsupported_location).cast<double>().norm());
return dist_here - valence_boost;
}
bool Node::hasOffspring(const NodeSPtr& to_be_checked) const
{
if (to_be_checked == shared_from_this())
return true;
for (auto& child_ptr : m_children)
if (child_ptr->hasOffspring(to_be_checked))
return true;
return false;
}
NodeSPtr Node::addChild(const Point& child_loc)
{
assert(m_p != child_loc);
NodeSPtr child = Node::create(child_loc);
return addChild(child);
}
NodeSPtr Node::addChild(NodeSPtr& new_child)
{
assert(new_child != shared_from_this());
//assert(p != new_child->p); // NOTE: No problem for now. Issue to solve later. Maybe even afetr final. Low prio.
m_children.push_back(new_child);
new_child->m_parent = shared_from_this();
new_child->m_is_root = false;
return new_child;
}
void Node::propagateToNextLayer(
std::vector<NodeSPtr>& next_trees,
const Polygons& next_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t prune_distance,
const coord_t smooth_magnitude,
const coord_t max_remove_colinear_dist) const
{
auto tree_below = deepCopy();
tree_below->prune(prune_distance);
tree_below->straighten(smooth_magnitude, max_remove_colinear_dist);
if (tree_below->realign(next_outlines, outline_locator, next_trees))
next_trees.push_back(tree_below);
}
// NOTE: Depth-first, as currently implemented.
// Skips the root (because that has no root itself), but all initial nodes will have the root point anyway.
void Node::visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const
{
for (const auto& node : m_children) {
assert(node->m_parent.lock() == shared_from_this());
visitor(m_p, node->m_p);
node->visitBranches(visitor);
}
}
// NOTE: Depth-first, as currently implemented.
void Node::visitNodes(const std::function<void(NodeSPtr)>& visitor)
{
visitor(shared_from_this());
for (const auto& node : m_children) {
assert(node->m_parent.lock() == shared_from_this());
node->visitNodes(visitor);
}
}
Node::Node(const Point& p, const std::optional<Point>& last_grounding_location /*= std::nullopt*/) :
m_is_root(true), m_p(p), m_last_grounding_location(last_grounding_location)
{}
NodeSPtr Node::deepCopy() const
{
NodeSPtr local_root = Node::create(m_p);
local_root->m_is_root = m_is_root;
if (m_is_root)
{
local_root->m_last_grounding_location = m_last_grounding_location.value_or(m_p);
}
local_root->m_children.reserve(m_children.size());
for (const auto& node : m_children)
{
NodeSPtr child = node->deepCopy();
child->m_parent = local_root;
local_root->m_children.push_back(child);
}
return local_root;
}
void Node::reroot(NodeSPtr new_parent /*= nullptr*/)
{
if (! m_is_root) {
auto old_parent = m_parent.lock();
old_parent->reroot(shared_from_this());
m_children.push_back(old_parent);
}
if (new_parent) {
m_children.erase(std::remove(m_children.begin(), m_children.end(), new_parent), m_children.end());
m_is_root = false;
m_parent = new_parent;
} else {
m_is_root = true;
m_parent.reset();
}
}
NodeSPtr Node::closestNode(const Point& loc)
{
NodeSPtr result = shared_from_this();
auto closest_dist2 = coord_t((m_p - loc).cast<double>().norm());
for (const auto& child : m_children) {
NodeSPtr candidate_node = child->closestNode(loc);
const auto child_dist2 = coord_t((candidate_node->m_p - loc).cast<double>().norm());
if (child_dist2 < closest_dist2) {
closest_dist2 = child_dist2;
result = candidate_node;
}
}
return result;
}
bool inside(const Polygons &polygons, const Point p)
{
int poly_count_inside = 0;
for (const Polygon &poly : polygons) {
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
if (is_inside_this_poly == -1)
return true;
poly_count_inside += is_inside_this_poly;
}
return (poly_count_inside % 2) == 1;
}
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist)
{
struct Visitor {
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 = 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 = grid.segment(*it_contour_and_segment);
if (Vec2d ip; Geometry::segment_segment_intersection(segment.first.cast<double>(), segment.second.cast<double>(), this->line_a, this->line_b, ip))
if (double d = (this->intersection_pt - this->line_b).squaredNorm(); d < d2min) {
this->d2min = d;
this->intersection_pt = ip;
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid& grid;
Vec2d line_a;
Vec2d line_b;
Vec2d intersection_pt;
double d2min { std::numeric_limits<double>::max() };
} visitor { outline_locator, a.cast<double>(), b.cast<double>() };
outline_locator.visit_cells_intersecting_line(a, b, visitor);
return visitor.d2min < within_max_dist * within_max_dist;
}
bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts)
{
if (outlines.empty())
return false;
if (inside(outlines, m_p)) {
// Only keep children that have an unbroken connection to here, realign will put the rest in rerooted parts due to recursion:
Point coll;
bool reground_me = false;
m_children.erase(std::remove_if(m_children.begin(), m_children.end(), [&](const NodeSPtr &child) {
bool connect_branch = child->realign(outlines, outline_locator, rerooted_parts);
// Find an intersection of the line segment from p to child->p, at maximum outline_locator.resolution() * 2 distance from p.
if (connect_branch && lineSegmentPolygonsIntersection(child->m_p, m_p, outline_locator, coll, outline_locator.resolution() * 2)) {
child->m_last_grounding_location.reset();
child->m_parent.reset();
child->m_is_root = true;
rerooted_parts.push_back(child);
reground_me = true;
connect_branch = false;
}
return ! connect_branch;
}), m_children.end());
if (reground_me)
m_last_grounding_location.reset();
return true;
}
// 'Lift' any decendants out of this tree:
for (auto& child : m_children)
if (child->realign(outlines, outline_locator, rerooted_parts)) {
child->m_last_grounding_location = m_p;
child->m_parent.reset();
child->m_is_root = true;
rerooted_parts.push_back(child);
}
m_children.clear();
return false;
}
void Node::straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist)
{
straighten(magnitude, m_p, 0, max_remove_colinear_dist * max_remove_colinear_dist);
}
Node::RectilinearJunction Node::straighten(
const coord_t magnitude,
const Point& junction_above,
const coord_t accumulated_dist,
const coord_t max_remove_colinear_dist2)
{
constexpr coord_t junction_magnitude_factor_numerator = 3;
constexpr coord_t junction_magnitude_factor_denominator = 4;
const coord_t junction_magnitude = magnitude * junction_magnitude_factor_numerator / junction_magnitude_factor_denominator;
if (m_children.size() == 1)
{
auto child_p = m_children.front();
auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
RectilinearJunction junction_below = child_p->straighten(magnitude, junction_above, accumulated_dist + child_dist, max_remove_colinear_dist2);
coord_t total_dist_to_junction_below = junction_below.total_recti_dist;
Point a = junction_above;
Point b = junction_below.junction_loc;
if (a != b) // should always be true!
{
Point ab = b - a;
Point destination = a + ab * accumulated_dist / std::max(coord_t(1), total_dist_to_junction_below);
if ((destination - m_p).cast<double>().squaredNorm() <= magnitude * magnitude)
m_p = destination;
else
m_p += ((destination - m_p).cast<double>().normalized() * magnitude).cast<coord_t>();
}
{ // remove nodes on linear segments
constexpr coord_t close_enough = 10;
child_p = m_children.front(); //recursive call to straighten might have removed the child
const NodeSPtr& parent_node = m_parent.lock();
if (parent_node &&
(child_p->m_p - parent_node->m_p).cast<double>().squaredNorm() < max_remove_colinear_dist2 &&
Line::distance_to_squared(m_p, parent_node->m_p, child_p->m_p) < close_enough * close_enough) {
child_p->m_parent = m_parent;
for (auto& sibling : parent_node->m_children)
{ // find this node among siblings
if (sibling == shared_from_this())
{
sibling = child_p; // replace this node by child
break;
}
}
}
}
return junction_below;
}
else
{
constexpr coord_t weight = 1000;
Point junction_moving_dir = ((junction_above - m_p).cast<double>().normalized() * weight).cast<coord_t>();
bool prevent_junction_moving = false;
for (auto& child_p : m_children)
{
const auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
RectilinearJunction below = child_p->straighten(magnitude, m_p, child_dist, max_remove_colinear_dist2);
junction_moving_dir += ((below.junction_loc - m_p).cast<double>().normalized() * weight).cast<coord_t>();
if (below.total_recti_dist < magnitude) // TODO: make configurable?
{
prevent_junction_moving = true; // prevent flipflopping in branches due to straightening and junctoin moving clashing
}
}
if (junction_moving_dir != Point(0, 0) && ! m_children.empty() && ! m_is_root && ! prevent_junction_moving)
{
auto junction_moving_dir_len = coord_t(junction_moving_dir.norm());
if (junction_moving_dir_len > junction_magnitude)
{
junction_moving_dir = junction_moving_dir * junction_magnitude / junction_moving_dir_len;
}
m_p += junction_moving_dir;
}
return RectilinearJunction{ accumulated_dist, m_p };
}
}
// Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
coord_t Node::prune(const coord_t& pruning_distance)
{
if (pruning_distance <= 0)
return 0;
coord_t max_distance_pruned = 0;
for (auto child_it = m_children.begin(); child_it != m_children.end(); ) {
auto& child = *child_it;
coord_t dist_pruned_child = child->prune(pruning_distance);
if (dist_pruned_child >= pruning_distance)
{ // pruning is finished for child; dont modify further
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child);
++child_it;
} else {
const Point a = getLocation();
const Point b = child->getLocation();
const Point ba = a - b;
const auto ab_len = coord_t(ba.cast<double>().norm());
if (dist_pruned_child + ab_len <= pruning_distance) {
// we're still in the process of pruning
assert(child->m_children.empty() && "when pruning away a node all it's children must already have been pruned away");
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child + ab_len);
child_it = m_children.erase(child_it);
} else {
// pruning stops in between this node and the child
const Point n = b + (ba.cast<double>().normalized() * (pruning_distance - dist_pruned_child)).cast<coord_t>();
assert(std::abs((n - b).cast<double>().norm() + dist_pruned_child - pruning_distance) < 10 && "total pruned distance must be equal to the pruning_distance");
max_distance_pruned = std::max(max_distance_pruned, pruning_distance);
child->setLocation(n);
++child_it;
}
}
}
return max_distance_pruned;
}
void Node::convertToPolylines(Polygons& output, const coord_t line_width) const
{
Polygons result;
output.emplace_back();
convertToPolylines(0, result);
removeJunctionOverlap(result, line_width);
append(output, std::move(result));
}
void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
{
if (m_children.empty()) {
output[long_line_idx].points.push_back(m_p);
return;
}
size_t first_child_idx = rand() % m_children.size();
m_children[first_child_idx]->convertToPolylines(long_line_idx, output);
output[long_line_idx].points.push_back(m_p);
for (size_t idx_offset = 1; idx_offset < m_children.size(); idx_offset++) {
size_t child_idx = (first_child_idx + idx_offset) % m_children.size();
const Node& child = *m_children[child_idx];
output.emplace_back();
size_t child_line_idx = output.size() - 1;
child.convertToPolylines(child_line_idx, output);
output[child_line_idx].points.emplace_back(m_p);
}
}
void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
{
const coord_t reduction = line_width / 2; // TODO make configurable?
for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) {
Polygon &polyline = *poly_it;
if (polyline.size() <= 1) {
polyline = std::move(result_lines.back());
result_lines.pop_back();
continue;
}
coord_t to_be_reduced = reduction;
Point a = polyline.back();
for (int point_idx = polyline.size() - 2; point_idx >= 0; point_idx--) {
const Point b = polyline[point_idx];
const Point ab = b - a;
const auto ab_len = coord_t(ab.cast<double>().norm());
if (ab_len >= to_be_reduced) {
polyline.points.back() = a + (ab.cast<double>() * (double(to_be_reduced) / ab_len)).cast<coord_t>();
break;
} else {
to_be_reduced -= ab_len;
polyline.points.pop_back();
}
a = b;
}
if (polyline.size() <= 1) {
polyline = std::move(result_lines.back());
result_lines.pop_back();
} else
++ poly_it;
}
}
} // namespace Slic3r::FillLightning

View file

@ -0,0 +1,275 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_TREE_NODE_H
#define LIGHTNING_TREE_NODE_H
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#include "../../EdgeGrid.hpp"
#include "../../Polygon.hpp"
namespace Slic3r::FillLightning
{
constexpr auto locator_cell_size = scaled<coord_t>(4.);
class Node;
using NodeSPtr = std::shared_ptr<Node>;
// NOTE: As written, this struct will only be valid for a single layer, will have to be updated for the next.
// NOTE: Reasons for implementing this with some separate closures:
// - keep clear deliniation during development
// - possibility of multiple distance field strategies
/*!
* A single vertex of a Lightning Tree, the structure that determines the paths
* to be printed to form Lightning Infill.
*
* In essence these vertices are just a position linked to other positions in
* 2D. The nodes have a hierarchical structure of parents and children, forming
* a tree. The class also has some helper functions specific to Lightning Infill
* e.g. to straighten the paths around this node.
*/
class Node : public std::enable_shared_from_this<Node>
{
public:
// Workaround for private/protected constructors and 'make_shared': https://stackoverflow.com/a/27832765
template<typename ...Arg> NodeSPtr static create(Arg&&...arg)
{
struct EnableMakeShared : public Node
{
EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
};
return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
}
/*!
* Get the position on this layer that this node represents, a vertex of the
* path to print.
* \return The position that this node represents.
*/
const Point& getLocation() const { return m_p; }
/*!
* Change the position on this layer that the node represents.
* \param p The position that the node needs to represent.
*/
void setLocation(const Point& p) { m_p = p; }
/*!
* Construct a new ``Node`` instance and add it as a child of
* this node.
* \param p The location of the new node.
* \return A shared pointer to the new node.
*/
NodeSPtr addChild(const Point& p);
/*!
* Add an existing ``Node`` as a child of this node.
* \param new_child The node that must be added as a child.
* \return Always returns \p new_child.
*/
NodeSPtr addChild(NodeSPtr& new_child);
/*!
* Propagate this node's sub-tree to the next layer.
*
* Creates a copy of this tree, realign it to the new layer boundaries
* \p next_outlines and reduce (i.e. prune and straighten) it. A copy of
* this node and all of its descendant nodes will be added to the
* \p next_trees vector.
* \param next_trees A collection of tree nodes to use for the next layer.
* \param next_outlines The shape of the layer below, to make sure that the
* tree stays within the bounds of the infill area.
* \param prune_distance The maximum distance that a leaf node may be moved
* such that it still supports the current node.
* \param smooth_magnitude The maximum distance that a line may be shifted
* to straighten the tree's paths, such that it still supports the current
* paths.
* \param max_remove_colinear_dist The maximum distance of a line-segment
* from which straightening may remove a colinear point.
*/
void propagateToNextLayer
(
std::vector<NodeSPtr>& next_trees,
const Polygons& next_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t prune_distance,
const coord_t smooth_magnitude,
const coord_t max_remove_colinear_dist
) const;
/*!
* Executes a given function for every line segment in this node's sub-tree.
*
* The function takes two `Point` arguments. These arguments will be filled
* in with the higher-order node (closer to the root) first, and the
* downtree node (closer to the leaves) as the second argument. The segment
* from this node's parent to this node itself is not included.
* The order in which the segments are visited is depth-first.
* \param visitor A function to execute for every branch in the node's sub-
* tree.
*/
void visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const;
/*!
* Execute a given function for every node in this node's sub-tree.
*
* The visitor function takes a node as input. This node is not const, so
* this can be used to change the tree.
* Nodes are visited in depth-first order. This node itself is visited as
* well (pre-order).
* \param visitor A function to execute for every node in this node's sub-
* tree.
*/
void visitNodes(const std::function<void(NodeSPtr)>& visitor);
/*!
* Get a weighted distance from an unsupported point to this node (given the current supporting radius).
*
* When attaching a unsupported location to a node, not all nodes have the same priority.
* (Eucludian) closer nodes are prioritised, but that's not the whole story.
* For instance, we give some nodes a 'valence boost' depending on the nr. of branches.
* \param unsupported_location The (unsuppported) location of which the weighted distance needs to be calculated.
* \param supporting_radius The maximum distance which can be bridged without (infill) supporting it.
* \return The weighted distance.
*/
coord_t getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const;
/*!
* Returns whether this node is the root of a lightning tree. It is the root
* if it has no parents.
* \return ``true`` if this node is the root (no parents) or ``false`` if it
* is a child node of some other node.
*/
bool isRoot() const { return m_is_root; }
/*!
* Reverse the parent-child relationship all the way to the root, from this node onward.
* This has the effect of 're-rooting' the tree at the current node if no immediate parent is given as argument.
* That is, the current node will become the root, it's (former) parent if any, will become one of it's children.
* This is then recursively bubbled up until it reaches the (former) root, which then will become a leaf.
* \param new_parent The (new) parent-node of the root, useful for recursing or immediately attaching the node to another tree.
*/
void reroot(NodeSPtr new_parent = nullptr);
/*!
* Retrieves the closest node to the specified location.
* \param loc The specified location.
* \result The branch that starts at the position closest to the location within this tree.
*/
NodeSPtr closestNode(const Point& loc);
/*!
* Returns whether the given tree node is a descendant of this node.
*
* If this node itself is given, it is also considered to be a descendant.
* \param to_be_checked A node to find out whether it is a descendant of
* this node.
* \return ``true`` if the given node is a descendant or this node itself,
* or ``false`` if it is not in the sub-tree.
*/
bool hasOffspring(const NodeSPtr& to_be_checked) const;
protected:
Node() = delete; // Don't allow empty contruction
/*!
* Construct a new node, either for insertion in a tree or as root.
* \param p The physical location in the 2D layer that this node represents.
* Connecting other nodes to this node indicates that a line segment should
* be drawn between those two physical positions.
*/
Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
/*!
* Copy this node and its entire sub-tree.
* \return The equivalent of this node in the copy (the root of the new sub-
* tree).
*/
NodeSPtr deepCopy() const;
/*! Reconnect trees from the layer above to the new outlines of the lower layer.
* \return Wether or not the root is kept (false is no, true is yes).
*/
bool realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts);
struct RectilinearJunction
{
coord_t total_recti_dist; //!< rectilinear distance along the tree from the last junction above to the junction below
Point junction_loc; //!< junction location below
};
/*!
* Smoothen the tree to make it a bit more printable, while still supporting
* the trees above.
* \param magnitude The maximum allowed distance to move the node.
* \param max_remove_colinear_dist Maximum distance of the (compound) line-segment from which a co-linear point may be removed.
*/
void straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist);
/*! Recursive part of \ref straighten(.)
* \param junction_above The last seen junction with multiple children above
* \param accumulated_dist The distance along the tree from the last seen junction to this node
* \param max_remove_colinear_dist2 Maximum distance _squared_ of the (compound) line-segment from which a co-linear point may be removed.
* \return the total distance along the tree from the last junction above to the first next junction below and the location of the next junction below
*/
RectilinearJunction straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2);
/*! Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
* \return The distance that has been pruned. If less than \p distance, then the whole tree was puned away.
*/
coord_t prune(const coord_t& distance);
public:
/*!
* Convert the tree into polylines
*
* At each junction one line is chosen at random to continue
*
* The lines start at a leaf and end in a junction
*
* \param output all branches in this tree connected into polylines
*/
void convertToPolylines(Polygons& output, const coord_t line_width) const;
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
*
* This needs to be known when roots are reconnected, so that the last (higher) layer is supported by the next one.
*/
const std::optional<Point>& getLastGroundingLocation() const { return m_last_grounding_location; }
protected:
/*!
* Convert the tree into polylines
*
* At each junction one line is chosen at random to continue
*
* The lines start at a leaf and end in a junction
*
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
* \param output all branches in this tree connected into polylines
*/
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
void removeJunctionOverlap(Polygons& polylines, const coord_t line_width) const;
bool m_is_root;
Point m_p;
std::weak_ptr<Node> m_parent;
std::vector<NodeSPtr> m_children;
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
};
bool inside(const Polygons &polygons, const Point p);
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist);
} // namespace Slic3r::FillLightning
#endif // LIGHTNING_TREE_NODE_H