This commit is contained in:
Joseph Lenox 2017-04-02 11:46:38 -05:00
commit 9fedc425ac
13 changed files with 156 additions and 122 deletions

View File

@ -8,7 +8,7 @@ use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles'; use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull); use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull);
use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2 use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2
intersection_pl offset2_ex diff_pl); intersection_pl offset2_ex diff_pl diff_ex);
use Slic3r::Surface ':types'; use Slic3r::Surface ':types';
has 'print_config' => (is => 'rw', required => 1); has 'print_config' => (is => 'rw', required => 1);
@ -782,7 +782,7 @@ sub generate_toolpaths {
my $base_flow = $_flow; my $base_flow = $_flow;
# find centerline of the external loop/extrusions # find centerline of the external loop/extrusions
my $to_infill = offset2_ex($base, +scaled_epsilon, -(scaled_epsilon + $_flow->scaled_width/2)); my $to_infill = offset2($base, +scaled_epsilon, -(scaled_epsilon + $_flow->scaled_width/2));
my @paths = (); my @paths = ();
@ -796,6 +796,17 @@ sub generate_toolpaths {
# use the proper spacing for first layer as we don't need to align # use the proper spacing for first layer as we don't need to align
# its pattern to the other layers # its pattern to the other layers
$filler->set_min_spacing($base_flow->spacing); $filler->set_min_spacing($base_flow->spacing);
# subtract brim so that it goes around the object fully (and support gets its own brim)
if ($self->print_config->brim_width > 0) {
my $d = +scale $self->print_config->brim_width*2;
$to_infill = diff_ex(
$to_infill,
offset($object->get_layer(0)->slices->polygons, $d),
);
} else {
$to_infill = union_ex($to_infill);
}
} else { } else {
# draw a perimeter all around support infill # draw a perimeter all around support infill
# TODO: use brim ordering algorithm # TODO: use brim ordering algorithm
@ -806,10 +817,10 @@ sub generate_toolpaths {
mm3_per_mm => $mm3_per_mm, mm3_per_mm => $mm3_per_mm,
width => $_flow->width, width => $_flow->width,
height => $layer->height, height => $layer->height,
), map @$_, @$to_infill; ), @$to_infill;
# TODO: use offset2_ex() # TODO: use offset2_ex()
$to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing); $to_infill = offset_ex($to_infill, -$_flow->scaled_spacing);
} }
my $mm3_per_mm = $base_flow->mm3_per_mm; my $mm3_per_mm = $base_flow->mm3_per_mm;

View File

@ -22,7 +22,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
{ {
my $print = Slic3r::Print->new; my $print = Slic3r::Print->new;
my $surface_width = 250; my $surface_width = 250;
my $distance = Slic3r::Filler::adjust_solid_spacing($surface_width, 47); my $distance = Slic3r::Flow::solid_spacing($surface_width, 47);
is $distance, 50, 'adjusted solid distance'; is $distance, 50, 'adjusted solid distance';
is $surface_width % $distance, 0, 'adjusted solid distance'; is $surface_width % $distance, 0, 'adjusted solid distance';
} }

View File

@ -5,24 +5,6 @@
namespace Slic3r { namespace Slic3r {
class BridgeDirectionComparator {
public:
std::map<double,double> dir_coverage; // angle => score
BridgeDirectionComparator(double _extrusion_width)
: extrusion_width(_extrusion_width)
{};
// the best direction is the one causing most lines to be bridged (thus most coverage)
bool operator() (double a, double b) {
// Initial sort by coverage only - comparator must obey strict weak ordering
return (this->dir_coverage[a] > this->dir_coverage[b]);
};
private:
double extrusion_width;
};
BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices,
coord_t _extrusion_width) coord_t _extrusion_width)
: expolygon(_expolygon), lower_slices(_lower_slices), extrusion_width(_extrusion_width), : expolygon(_expolygon), lower_slices(_lower_slices), extrusion_width(_extrusion_width),
@ -59,6 +41,8 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle
bool bool
BridgeDetector::detect_angle() BridgeDetector::detect_angle()
{ {
// Do nothing if the bridging region is completely in the air
// and there are no anchors available at the layer below.
if (this->_edges.empty() || this->_anchors.empty()) return false; if (this->_edges.empty() || this->_anchors.empty()) return false;
/* Outset the bridge expolygon by half the amount we used for detecting anchors; /* Outset the bridge expolygon by half the amount we used for detecting anchors;
@ -70,60 +54,65 @@ BridgeDetector::detect_angle()
bridge in several directions and then sum the length of lines having both bridge in several directions and then sum the length of lines having both
endpoints within anchors */ endpoints within anchors */
// we test angles according to configured resolution // generate the list of candidate angles
std::vector<double> angles; std::vector<BridgeDirection> candidates;
for (int i = 0; i <= PI/this->resolution; ++i)
angles.push_back(i * this->resolution);
// we also test angles of each bridge contour
{ {
Polygons pp = this->expolygon; // we test angles according to configured resolution
for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { std::vector<double> angles;
Lines lines = p->lines(); for (int i = 0; i <= PI/this->resolution; ++i)
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) angles.push_back(i * this->resolution);
angles.push_back(line->direction());
// we also test angles of each bridge contour
{
Polygons pp = this->expolygon;
for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) {
Lines lines = p->lines();
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line)
angles.push_back(line->direction());
}
} }
}
/* we also test angles of each open supporting edge /* we also test angles of each open supporting edge
(this finds the optimal angle for C-shaped supports) */ (this finds the optimal angle for C-shaped supports) */
for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) { for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) {
if (edge->first_point().coincides_with(edge->last_point())) continue; if (edge->first_point().coincides_with(edge->last_point())) continue;
angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); angles.push_back(Line(edge->first_point(), edge->last_point()).direction());
}
// remove duplicates
double min_resolution = PI/180.0; // 1 degree
std::sort(angles.begin(), angles.end());
for (size_t i = 1; i < angles.size(); ++i) {
if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) {
angles.erase(angles.begin() + i);
--i;
} }
}
/* compare first value with last one and remove the greatest one (PI)
in case they are parallel (PI, 0) */
if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution))
angles.pop_back();
BridgeDirectionComparator bdcomp(this->extrusion_width); // remove duplicates
std::map<double,double> dir_avg_length; double min_resolution = PI/180.0; // 1 degree
std::sort(angles.begin(), angles.end());
for (size_t i = 1; i < angles.size(); ++i) {
if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) {
angles.erase(angles.begin() + i);
--i;
}
}
/* compare first value with last one and remove the greatest one (PI)
in case they are parallel (PI, 0) */
if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution))
angles.pop_back();
for (auto angle : angles)
candidates.push_back(BridgeDirection(angle));
}
double line_increment = this->extrusion_width; double line_increment = this->extrusion_width;
bool have_coverage = false; bool have_coverage = false;
for (std::vector<double>::const_iterator angle = angles.begin(); angle != angles.end(); ++angle) { for (BridgeDirection &candidate : candidates) {
Polygons my_clip_area = clip_area; Polygons my_clip_area = clip_area;
ExPolygons my_anchors = this->_anchors; ExPolygons my_anchors = this->_anchors;
// rotate everything - the center point doesn't matter // rotate everything - the center point doesn't matter
for (Polygons::iterator it = my_clip_area.begin(); it != my_clip_area.end(); ++it) for (Polygon &p : my_clip_area)
it->rotate(-*angle, Point(0,0)); p.rotate(-candidate.angle, Point(0,0));
for (ExPolygons::iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) for (ExPolygon &e : my_anchors)
it->rotate(-*angle, Point(0,0)); e.rotate(-candidate.angle, Point(0,0));
// generate lines in this direction // generate lines in this direction
BoundingBox bb; BoundingBox bb;
for (ExPolygons::const_iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) for (const ExPolygon &e : my_anchors)
bb.merge((Points)*it); bb.merge((Points)e);
Lines lines; Lines lines;
for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment) for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment)
@ -143,44 +132,39 @@ BridgeDetector::detect_angle()
std::vector<double> lengths; std::vector<double> lengths;
double total_length = 0; double total_length = 0;
for (Lines::const_iterator line = clipped_lines.begin(); line != clipped_lines.end(); ++line) { for (const Line &line : clipped_lines) {
double len = line->length(); const double len = line.length();
lengths.push_back(len); lengths.push_back(len);
total_length += len; total_length += len;
} }
if (total_length) have_coverage = true; if (total_length) have_coverage = true;
// sum length of bridged lines // sum length of bridged lines
bdcomp.dir_coverage[*angle] = total_length; candidate.coverage = total_length;
/* The following produces more correct results in some cases and more broken in others. /* The following produces more correct results in some cases and more broken in others.
TODO: investigate, as it looks more reliable than line clipping. */ TODO: investigate, as it looks more reliable than line clipping. */
// $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0; // $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0;
// max length of bridged lines // max length of bridged lines
dir_avg_length[*angle] = !lengths.empty() if (!lengths.empty())
? *std::max_element(lengths.begin(), lengths.end()) candidate.max_length = *std::max_element(lengths.begin(), lengths.end());
: 0;
} }
// if no direction produced coverage, then there's no bridge direction // if no direction produced coverage, then there's no bridge direction
if (!have_coverage) return false; if (!have_coverage) return false;
// sort directions by coverage - most coverage first // sort directions by coverage - most coverage first
std::sort(angles.begin(), angles.end(), bdcomp); std::sort(candidates.begin(), candidates.end());
this->angle = angles.front();
// if any other direction is within extrusion width of coverage, prefer it if shorter // if any other direction is within extrusion width of coverage, prefer it if shorter
// TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred? // TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred?
double most_coverage_angle = this->angle; size_t i_best = 0;
for (std::vector<double>::const_iterator angle = angles.begin() + 1; for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->extrusion_width; ++ i)
angle != angles.end() && bdcomp.dir_coverage[most_coverage_angle] - bdcomp.dir_coverage[*angle] < this->extrusion_width; if (candidates[i].max_length < candidates[i_best].max_length)
++angle i_best = i;
) {
if (dir_avg_length[*angle] < dir_avg_length[this->angle]) { this->angle = candidates[i_best].angle;
this->angle = *angle;
}
}
if (this->angle >= PI) this->angle -= PI; if (this->angle >= PI) this->angle -= PI;

View File

@ -31,6 +31,19 @@ private:
Polylines _edges; Polylines _edges;
// Closed polygons representing the supporting areas. // Closed polygons representing the supporting areas.
ExPolygons _anchors; ExPolygons _anchors;
class BridgeDirection {
public:
BridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.) {}
// the best direction is the one causing most lines to be bridged (thus most coverage)
bool operator<(const BridgeDirection &other) const {
// Initial sort by coverage only - comparator must obey strict weak ordering
return this->coverage > other.coverage;
};
double angle;
double coverage;
double max_length;
};
}; };
} }

View File

@ -1,3 +1,6 @@
#define DEBUG
#undef NDEBUG
#include <cassert>
#include <math.h> #include <math.h>
#include <stdio.h> #include <stdio.h>
@ -69,36 +72,6 @@ Fill::fill_surface(const Surface &surface)
return polylines_out; return polylines_out;
} }
// Calculate a new spacing to fill width with possibly integer number of lines,
// the first and last line being centered at the interval ends.
// This function possibly increases the spacing, never decreases,
// and for a narrow width the increase in spacing may become severe,
// therefore the adjustment is limited to 20% increase.
coord_t
Fill::adjust_solid_spacing(const coord_t width, const coord_t distance)
{
assert(width >= 0);
assert(distance > 0);
const int number_of_intervals = floor(width / distance);
if (number_of_intervals == 0) return distance;
coord_t distance_new = (width / number_of_intervals);
const coordf_t factor = coordf_t(distance_new) / coordf_t(distance);
assert(factor > 1. - 1e-5);
// How much could the extrusion width be increased? By 20%.
// Because of this limit, this method is not idempotent: each run
// will increment distance by 20%.
const coordf_t factor_max = 1.2;
if (factor > factor_max)
distance_new = floor((double)distance * factor_max + 0.5);
assert((distance_new * number_of_intervals) <= width);
return distance_new;
}
// Returns orientation of the infill and the reference point of the infill pattern. // Returns orientation of the infill and the reference point of the infill pattern.
// For a normal print, the reference point is the center of a bounding box of the STL. // For a normal print, the reference point is the center of a bounding box of the STL.
Fill::direction_t Fill::direction_t

View File

@ -64,7 +64,6 @@ public:
public: public:
static Fill* new_from_type(const InfillPattern type); static Fill* new_from_type(const InfillPattern type);
static Fill* new_from_type(const std::string &type); static Fill* new_from_type(const std::string &type);
static coord_t adjust_solid_spacing(const coord_t width, const coord_t distance);
virtual Fill* clone() const = 0; virtual Fill* clone() const = 0;
virtual ~Fill() {}; virtual ~Fill() {};

View File

@ -1,5 +1,6 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp" #include "../ExPolygon.hpp"
#include "../Flow.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include "FillConcentric.hpp" #include "FillConcentric.hpp"
@ -20,7 +21,7 @@ FillConcentric::_fill_surface_single(
if (this->density > 0.9999f && !this->dont_adjust) { if (this->density > 0.9999f && !this->dont_adjust) {
BoundingBox bounding_box = expolygon.contour.bounding_box(); BoundingBox bounding_box = expolygon.contour.bounding_box();
distance = this->adjust_solid_spacing(bounding_box.size().x, distance); distance = Flow::solid_spacing(bounding_box.size().x, distance);
this->_spacing = unscale(distance); this->_spacing = unscale(distance);
} }

View File

@ -6,6 +6,7 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp" #include "../ExPolygon.hpp"
#include "../Flow.hpp"
#include "../PolylineCollection.hpp" #include "../PolylineCollection.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include <algorithm> #include <algorithm>
@ -48,7 +49,7 @@ FillRectilinear::_fill_single_direction(ExPolygon expolygon,
// define flow spacing according to requested density // define flow spacing according to requested density
if (this->density > 0.9999f && !this->dont_adjust) { if (this->density > 0.9999f && !this->dont_adjust) {
line_spacing = this->adjust_solid_spacing(bounding_box.size().x, line_spacing); line_spacing = Flow::solid_spacing(bounding_box.size().x, line_spacing);
this->_spacing = unscale(line_spacing); this->_spacing = unscale(line_spacing);
} else { } else {
// extend bounding box so that our pattern will be aligned with other layers // extend bounding box so that our pattern will be aligned with other layers

View File

@ -50,6 +50,11 @@ Flow::spacing() const {
return this->width - OVERLAP_FACTOR * (this->width - min_flow_spacing); return this->width - OVERLAP_FACTOR * (this->width - min_flow_spacing);
} }
void
Flow::set_spacing(float spacing) {
this->width = Flow::_width_from_spacing(spacing, this->nozzle_diameter, this->height, this->bridge);
}
/* This method returns the centerline spacing between an extrusion using this /* This method returns the centerline spacing between an extrusion using this
flow and another one using another flow. flow and another one using another flow.
this->spacing(other) shall return the same value as other.spacing(*this) */ this->spacing(other) shall return the same value as other.spacing(*this) */
@ -115,4 +120,37 @@ Flow::_width_from_spacing(float spacing, float nozzle_diameter, float height, bo
return spacing + OVERLAP_FACTOR * height * (1 - PI/4.0); return spacing + OVERLAP_FACTOR * height * (1 - PI/4.0);
} }
// Calculate a new spacing to fill width with possibly integer number of lines,
// the first and last line being centered at the interval ends.
// This function possibly increases the spacing, never decreases,
// and for a narrow width the increase in spacing may become severe,
// therefore the adjustment is limited to 20% increase.
template <class T>
T
Flow::solid_spacing(const T total_width, const T spacing)
{
assert(total_width >= 0);
assert(spacing > 0);
const int number_of_intervals = floor(total_width / spacing);
if (number_of_intervals == 0) return spacing;
T spacing_new = (total_width / number_of_intervals);
const double factor = (double)spacing_new / (double)spacing;
assert(factor > 1. - 1e-5);
// How much could the extrusion width be increased? By 20%.
// Because of this limit, this method is not idempotent: each run
// will increment spacing by 20%.
const double factor_max = 1.2;
if (factor > factor_max)
spacing_new = floor((double)spacing * factor_max + 0.5);
assert((spacing_new * number_of_intervals) <= total_width);
return spacing_new;
}
template coord_t Flow::solid_spacing<coord_t>(const coord_t total_width, const coord_t spacing);
template coordf_t Flow::solid_spacing<coordf_t>(const coordf_t total_width, const coordf_t spacing);
} }

View File

@ -30,6 +30,13 @@ class Flow
: width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}; : width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {};
float spacing() const; float spacing() const;
float spacing(const Flow &other) const; float spacing(const Flow &other) const;
void set_spacing(float spacing);
void set_solid_spacing(const coord_t total_width) {
this->set_spacing(Flow::solid_spacing(total_width, this->scaled_spacing()));
};
void set_solid_spacing(const coordf_t total_width) {
this->set_spacing(Flow::solid_spacing(total_width, (coordf_t)this->spacing()));
};
double mm3_per_mm() const; double mm3_per_mm() const;
coord_t scaled_width() const { coord_t scaled_width() const {
return scale_(this->width); return scale_(this->width);
@ -43,12 +50,12 @@ class Flow
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
template <class T> static T solid_spacing(const T total_width, const T spacing);
private: private:
static float _bridge_width(float nozzle_diameter, float bridge_flow_ratio); static float _bridge_width(float nozzle_diameter, float bridge_flow_ratio);
static float _auto_width(FlowRole role, float nozzle_diameter, float height); static float _auto_width(FlowRole role, float nozzle_diameter, float height);
static float _width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); static float _width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
static float _spacing(float width, float nozzle_diameter, float height, float bridge_flow_ratio);
}; };
} }

View File

@ -163,6 +163,10 @@ Print::invalidate_state_by_config(const PrintConfigBase &config)
|| opt_key == "min_skirt_length" || opt_key == "min_skirt_length"
|| opt_key == "ooze_prevention") { || opt_key == "ooze_prevention") {
steps.insert(psSkirt); steps.insert(psSkirt);
} else if (opt_key == "brim_width") {
steps.insert(psBrim);
steps.insert(psSkirt);
osteps.insert(posSupportMaterial);
} else if (opt_key == "brim_width" } else if (opt_key == "brim_width"
|| opt_key == "interior_brim_width" || opt_key == "interior_brim_width"
|| opt_key == "brim_connections_width") { || opt_key == "brim_connections_width") {
@ -759,13 +763,18 @@ Print::brim_flow() const
extruders and take the one with, say, the smallest index. extruders and take the one with, say, the smallest index.
The same logic should be applied to the code that selects the extruder during G-code The same logic should be applied to the code that selects the extruder during G-code
generation as well. */ generation as well. */
return Flow::new_from_config_width( Flow flow = Flow::new_from_config_width(
frPerimeter, frPerimeter,
width, width,
this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1), this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1),
this->skirt_first_layer_height(), this->skirt_first_layer_height(),
0 0
); );
// Adjust extrusion width in order to fill the total brim width with an integer number of lines.
flow.set_solid_spacing(this->config.brim_width.value);
return flow;
} }
Flow Flow
@ -844,8 +853,8 @@ Print::_make_brim()
// perimeters because here we're offsetting outwards) // perimeters because here we're offsetting outwards)
append_to(loops, offset2( append_to(loops, offset2(
islands, islands,
flow.scaled_width() + flow.scaled_spacing() * (i - 1.0 + 0.5), flow.scaled_width() + flow.scaled_spacing() * (i - 1.5 + 0.5),
flow.scaled_spacing() * -1.0, flow.scaled_spacing() * -0.5,
100000, 100000,
ClipperLib::jtSquare ClipperLib::jtSquare
)); ));

View File

@ -78,8 +78,3 @@ new_from_type(CLASS, type)
%} %}
}; };
%package{Slic3r::Filler};
coord_t adjust_solid_spacing(coord_t width, coord_t distance)
%code{% RETVAL = Fill::adjust_solid_spacing(width, distance); %};

View File

@ -81,3 +81,6 @@ _constant()
%} %}
coord_t solid_spacing(coord_t width, coord_t distance)
%code{% RETVAL = Flow::solid_spacing(width, distance); %};