Variable-width gap fill. Yay! #2960
This commit is contained in:
parent
5ff7511a14
commit
6dc42ee902
19 changed files with 379 additions and 166 deletions
|
@ -6,7 +6,6 @@
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "polypartition.h"
|
#include "polypartition.h"
|
||||||
#include "poly2tri/poly2tri.h"
|
#include "poly2tri/poly2tri.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
|
@ -171,10 +170,11 @@ ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
|
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
|
||||||
{
|
{
|
||||||
// init helper object
|
// init helper object
|
||||||
Slic3r::Geometry::MedialAxis ma(max_width, min_width);
|
Slic3r::Geometry::MedialAxis ma(max_width, min_width);
|
||||||
|
ma.expolygon = this;
|
||||||
|
|
||||||
// populate list of segments for the Voronoi diagram
|
// populate list of segments for the Voronoi diagram
|
||||||
ma.lines = this->contour.lines();
|
ma.lines = this->contour.lines();
|
||||||
|
@ -184,41 +184,71 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute the Voronoi diagram
|
// compute the Voronoi diagram
|
||||||
Polylines pp;
|
ThickPolylines pp;
|
||||||
ma.build(&pp);
|
ma.build(&pp);
|
||||||
|
|
||||||
// clip segments to our expolygon area
|
/*
|
||||||
// (do this before extending endpoints as external segments coule be extended into
|
SVG svg("medial_axis.svg");
|
||||||
// expolygon, this leaving wrong things inside)
|
svg.draw(*this);
|
||||||
pp = intersection(pp, *this);
|
svg.draw(pp);
|
||||||
|
svg.Close();
|
||||||
|
*/
|
||||||
|
|
||||||
// extend initial and final segments of each polyline (they will be clipped)
|
|
||||||
// unless they represent closed loops
|
|
||||||
for (Polylines::iterator polyline = pp.begin(); polyline != pp.end(); ++polyline) {
|
|
||||||
if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue;
|
|
||||||
// TODO: we should *not* extend endpoints where other polylines start/end
|
|
||||||
// (such as T joints, which are returned as three polylines by MedialAxis)
|
|
||||||
polyline->extend_start(max_width);
|
|
||||||
polyline->extend_end(max_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clip again after extending endpoints to prevent them from exceeding the expolygon boundaries
|
|
||||||
pp = intersection(pp, *this);
|
|
||||||
|
|
||||||
// remove too short polylines
|
|
||||||
// (we can't do this check before endpoints extension and clipping because we don't
|
|
||||||
// know how long will the endpoints be extended since it depends on polygon thickness
|
|
||||||
// which is variable - extension will be <= max_width/2 on each side)
|
|
||||||
for (size_t i = 0; i < pp.size(); ++i) {
|
for (size_t i = 0; i < pp.size(); ++i) {
|
||||||
if (pp[i].length() < max_width) {
|
ThickPolyline& polyline = pp[i];
|
||||||
|
|
||||||
|
// extend initial and final segments of each polyline if they're actual endpoints
|
||||||
|
/* We assign new endpoints to temporary variables because in case of a single-line
|
||||||
|
polyline, after we extend the start point it will be caught by the intersection()
|
||||||
|
call, so we keep the inner point until we perform the second intersection() as well */
|
||||||
|
Point new_front = polyline.points.front();
|
||||||
|
Point new_back = polyline.points.back();
|
||||||
|
if (polyline.endpoints.front() && !this->has_boundary_point(new_front)) {
|
||||||
|
Line line(polyline.points.front(), polyline.points[1]);
|
||||||
|
|
||||||
|
// prevent the line from touching on the other side, otherwise intersection() might return that solution
|
||||||
|
if (polyline.points.size() == 2) line.b = line.midpoint();
|
||||||
|
|
||||||
|
line.extend_start(max_width);
|
||||||
|
(void)this->contour.intersection(line, &new_front);
|
||||||
|
}
|
||||||
|
if (polyline.endpoints.back() && !this->has_boundary_point(new_back)) {
|
||||||
|
Line line(
|
||||||
|
*(polyline.points.end() - 2),
|
||||||
|
polyline.points.back()
|
||||||
|
);
|
||||||
|
|
||||||
|
// prevent the line from touching on the other side, otherwise intersection() might return that solution
|
||||||
|
if (polyline.points.size() == 2) line.a = line.midpoint();
|
||||||
|
line.extend_end(max_width);
|
||||||
|
|
||||||
|
(void)this->contour.intersection(line, &new_back);
|
||||||
|
}
|
||||||
|
polyline.points.front() = new_front;
|
||||||
|
polyline.points.back() = new_back;
|
||||||
|
|
||||||
|
/* remove too short polylines
|
||||||
|
(we can't do this check before endpoints extension and clipping because we don't
|
||||||
|
know how long will the endpoints be extended since it depends on polygon thickness
|
||||||
|
which is variable - extension will be <= max_width/2 on each side) */
|
||||||
|
if (polyline.length() < max_width) {
|
||||||
pp.erase(pp.begin() + i);
|
pp.erase(pp.begin() + i);
|
||||||
--i;
|
--i;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
polylines->insert(polylines->end(), pp.begin(), pp.end());
|
polylines->insert(polylines->end(), pp.begin(), pp.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
|
||||||
|
{
|
||||||
|
ThickPolylines tp;
|
||||||
|
this->medial_axis(max_width, min_width, &tp);
|
||||||
|
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ExPolygon::get_trapezoids(Polygons* polygons) const
|
ExPolygon::get_trapezoids(Polygons* polygons) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,6 +32,7 @@ class ExPolygon
|
||||||
Polygons simplify_p(double tolerance) const;
|
Polygons simplify_p(double tolerance) const;
|
||||||
ExPolygons simplify(double tolerance) const;
|
ExPolygons simplify(double tolerance) const;
|
||||||
void simplify(double tolerance, ExPolygons* expolygons) const;
|
void simplify(double tolerance, ExPolygons* expolygons) const;
|
||||||
|
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
|
||||||
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
|
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
|
||||||
void get_trapezoids(Polygons* polygons) const;
|
void get_trapezoids(Polygons* polygons) const;
|
||||||
void get_trapezoids(Polygons* polygons, double angle) const;
|
void get_trapezoids(Polygons* polygons, double angle) const;
|
||||||
|
|
|
@ -114,7 +114,7 @@ Polygons
|
||||||
ExtrusionPath::grow() const
|
ExtrusionPath::grow() const
|
||||||
{
|
{
|
||||||
Polygons pp;
|
Polygons pp;
|
||||||
offset(this->polyline, &pp, +this->width/2);
|
offset(this->polyline, &pp, +scale_(this->width/2));
|
||||||
return pp;
|
return pp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,10 @@ class ExtrusionLoop : public ExtrusionEntity
|
||||||
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {};
|
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {};
|
||||||
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault)
|
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault)
|
||||||
: paths(paths), role(role) {};
|
: paths(paths), role(role) {};
|
||||||
|
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault)
|
||||||
|
: role(role) {
|
||||||
|
this->paths.push_back(path);
|
||||||
|
};
|
||||||
bool is_loop() const {
|
bool is_loop() const {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -101,6 +101,20 @@ ExtrusionEntityCollection::append(const ExtrusionPaths &paths)
|
||||||
this->append(*path);
|
this->append(*path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity)
|
||||||
|
{
|
||||||
|
delete this->entities[i];
|
||||||
|
this->entities[i] = entity.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ExtrusionEntityCollection::remove(size_t i)
|
||||||
|
{
|
||||||
|
delete this->entities[i];
|
||||||
|
this->entities.erase(this->entities.begin() + i);
|
||||||
|
}
|
||||||
|
|
||||||
ExtrusionEntityCollection
|
ExtrusionEntityCollection
|
||||||
ExtrusionEntityCollection::chained_path(bool no_reverse, std::vector<size_t>* orig_indices) const
|
ExtrusionEntityCollection::chained_path(bool no_reverse, std::vector<size_t>* orig_indices) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,6 +36,8 @@ class ExtrusionEntityCollection : public ExtrusionEntity
|
||||||
void append(const ExtrusionEntity &entity);
|
void append(const ExtrusionEntity &entity);
|
||||||
void append(const ExtrusionEntitiesPtr &entities);
|
void append(const ExtrusionEntitiesPtr &entities);
|
||||||
void append(const ExtrusionPaths &paths);
|
void append(const ExtrusionPaths &paths);
|
||||||
|
void replace(size_t i, const ExtrusionEntity &entity);
|
||||||
|
void remove(size_t i);
|
||||||
ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
||||||
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
||||||
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, std::vector<size_t>* orig_indices = NULL) const;
|
||||||
|
|
|
@ -5,12 +5,13 @@
|
||||||
#include "PolylineCollection.hpp"
|
#include "PolylineCollection.hpp"
|
||||||
#include "clipper.hpp"
|
#include "clipper.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG
|
#ifdef SLIC3R_DEBUG
|
||||||
#include "SVG.hpp"
|
#include "SVG.hpp"
|
||||||
|
@ -289,19 +290,8 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb)
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
Line
|
|
||||||
MedialAxis::edge_to_line(const VD::edge_type &edge) const
|
|
||||||
{
|
|
||||||
Line line;
|
|
||||||
line.a.x = edge.vertex0()->x();
|
|
||||||
line.a.y = edge.vertex0()->y();
|
|
||||||
line.b.x = edge.vertex1()->x();
|
|
||||||
line.b.y = edge.vertex1()->y();
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
MedialAxis::build(Polylines* polylines)
|
MedialAxis::build(ThickPolylines* polylines)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
// build bounding box (we use it for clipping infinite segments)
|
// build bounding box (we use it for clipping infinite segments)
|
||||||
|
@ -317,7 +307,7 @@ MedialAxis::build(Polylines* polylines)
|
||||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||||
if (edge->is_infinite()) continue;
|
if (edge->is_infinite()) continue;
|
||||||
|
|
||||||
Polyline polyline;
|
ThickPolyline polyline;
|
||||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
||||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
||||||
polylines->push_back(polyline);
|
polylines->push_back(polyline);
|
||||||
|
@ -373,7 +363,7 @@ MedialAxis::build(Polylines* polylines)
|
||||||
assert(vertex_edges[v].size() == 1);
|
assert(vertex_edges[v].size() == 1);
|
||||||
edge_t* edge = *vertex_edges[v].begin();
|
edge_t* edge = *vertex_edges[v].begin();
|
||||||
|
|
||||||
if (!this->is_valid_edge(*edge)) {
|
if (!this->validate_edge(*edge)) {
|
||||||
// if edge is not valid, erase it and its twin from edge list
|
// if edge is not valid, erase it and its twin from edge list
|
||||||
(void)this->edges.erase(edge);
|
(void)this->edges.erase(edge);
|
||||||
(void)this->edges.erase(edge->twin());
|
(void)this->edges.erase(edge->twin());
|
||||||
|
@ -400,22 +390,36 @@ MedialAxis::build(Polylines* polylines)
|
||||||
edge_t &edge = **this->edges.begin();
|
edge_t &edge = **this->edges.begin();
|
||||||
|
|
||||||
// start a polyline
|
// start a polyline
|
||||||
Polyline polyline;
|
ThickPolyline polyline;
|
||||||
polyline.points.push_back(Point( edge.vertex0()->x(), edge.vertex0()->y() ));
|
polyline.points.push_back(Point( edge.vertex0()->x(), edge.vertex0()->y() ));
|
||||||
polyline.points.push_back(Point( edge.vertex1()->x(), edge.vertex1()->y() ));
|
polyline.points.push_back(Point( edge.vertex1()->x(), edge.vertex1()->y() ));
|
||||||
|
polyline.width.push_back(this->thickness[&edge].first);
|
||||||
|
polyline.width.push_back(this->thickness[&edge].second);
|
||||||
|
|
||||||
// remove this edge and its twin from the available edges
|
// remove this edge and its twin from the available edges
|
||||||
(void)this->edges.erase(&edge);
|
(void)this->edges.erase(&edge);
|
||||||
(void)this->edges.erase(edge.twin());
|
(void)this->edges.erase(edge.twin());
|
||||||
|
|
||||||
// get next points
|
// get next points
|
||||||
this->process_edge_neighbors(edge, &polyline.points);
|
this->process_edge_neighbors(edge, &polyline.points, &polyline.width, &polyline.endpoints);
|
||||||
|
|
||||||
// get previous points
|
// get previous points
|
||||||
{
|
{
|
||||||
Points pp;
|
Points pp;
|
||||||
this->process_edge_neighbors(*edge.twin(), &pp);
|
std::vector<coordf_t> width;
|
||||||
|
std::vector<bool> endpoints;
|
||||||
|
this->process_edge_neighbors(*edge.twin(), &pp, &width, &endpoints);
|
||||||
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend());
|
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend());
|
||||||
|
polyline.width.insert(polyline.width.begin(), width.rbegin(), width.rend());
|
||||||
|
polyline.endpoints.insert(polyline.endpoints.begin(), endpoints.rbegin(), endpoints.rend());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(polyline.width.size() == polyline.points.size()*2 - 2);
|
||||||
|
assert(polyline.endpoints.size() == polyline.points.size());
|
||||||
|
|
||||||
|
if (polyline.first_point().coincides_with(polyline.last_point())) {
|
||||||
|
polyline.endpoints.front() = false;
|
||||||
|
polyline.endpoints.back() = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// append polyline to result
|
// append polyline to result
|
||||||
|
@ -424,7 +428,16 @@ MedialAxis::build(Polylines* polylines)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MedialAxis::process_edge_neighbors(const VD::edge_type& edge, Points* points)
|
MedialAxis::build(Polylines* polylines)
|
||||||
|
{
|
||||||
|
ThickPolylines tp;
|
||||||
|
this->build(&tp);
|
||||||
|
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MedialAxis::process_edge_neighbors(const VD::edge_type& edge, Points* points,
|
||||||
|
std::vector<coordf_t>* width, std::vector<bool>* endpoints)
|
||||||
{
|
{
|
||||||
// Since rot_next() works on the edge starting point but we want
|
// Since rot_next() works on the edge starting point but we want
|
||||||
// to find neighbors on the ending point, we just swap edge with
|
// to find neighbors on the ending point, we just swap edge with
|
||||||
|
@ -439,18 +452,26 @@ MedialAxis::process_edge_neighbors(const VD::edge_type& edge, Points* points)
|
||||||
|
|
||||||
// if we have a single neighbor then we can continue recursively
|
// if we have a single neighbor then we can continue recursively
|
||||||
if (neighbors.size() == 1) {
|
if (neighbors.size() == 1) {
|
||||||
|
endpoints->push_back(false);
|
||||||
const VD::edge_type& neighbor = *neighbors.front();
|
const VD::edge_type& neighbor = *neighbors.front();
|
||||||
points->push_back(Point( neighbor.vertex1()->x(), neighbor.vertex1()->y() ));
|
points->push_back(Point( neighbor.vertex1()->x(), neighbor.vertex1()->y() ));
|
||||||
|
width->push_back(this->thickness[&neighbor].first);
|
||||||
|
width->push_back(this->thickness[&neighbor].second);
|
||||||
(void)this->edges.erase(&neighbor);
|
(void)this->edges.erase(&neighbor);
|
||||||
(void)this->edges.erase(neighbor.twin());
|
(void)this->edges.erase(neighbor.twin());
|
||||||
this->process_edge_neighbors(neighbor, points);
|
this->process_edge_neighbors(neighbor, points, width, endpoints);
|
||||||
|
} else if (neighbors.size() == 0) {
|
||||||
|
endpoints->push_back(true);
|
||||||
|
} else {
|
||||||
|
// T-shaped or star-shaped joint
|
||||||
|
endpoints->push_back(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MedialAxis::is_valid_edge(const VD::edge_type& edge) const
|
MedialAxis::validate_edge(const VD::edge_type& edge)
|
||||||
{
|
{
|
||||||
/* If the cells sharing this edge have a common vertex, we're not interested
|
/* If the cells sharing this edge have a common vertex, we're not (probably) interested
|
||||||
in this edge. Why? Because it means that the edge lies on the bisector of
|
in this edge. Why? Because it means that the edge lies on the bisector of
|
||||||
two contiguous input lines and it was included in the Voronoi graph because
|
two contiguous input lines and it was included in the Voronoi graph because
|
||||||
it's the locus of centers of circles tangent to both vertices. Due to the
|
it's the locus of centers of circles tangent to both vertices. Due to the
|
||||||
|
@ -481,24 +502,20 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
|
||||||
// and we might need to skip the edge since it's not really part of
|
// and we might need to skip the edge since it's not really part of
|
||||||
// our skeleton
|
// our skeleton
|
||||||
|
|
||||||
// get perpendicular distance of each edge vertex to the segment(s)
|
/* Calculate perpendicular distance. We consider segment2 instead of segment1
|
||||||
double dist0 = segment1.a.distance_to(segment2.b);
|
because our Voronoi edge is part of a CCW sequence going around its Voronoi cell
|
||||||
double dist1 = segment1.b.distance_to(segment2.a);
|
(segment).
|
||||||
|
This means that such segment is on the left on our edge, and goes backwards.
|
||||||
|
So we use the cell of the twin edge, which is located on the right of our edge
|
||||||
|
and goes in the same direction as it. This way we can map dist0 and dist1
|
||||||
|
correctly. */
|
||||||
|
const Line line(
|
||||||
|
Point( edge.vertex0()->x(), edge.vertex0()->y() ),
|
||||||
|
Point( edge.vertex1()->x(), edge.vertex1()->y() )
|
||||||
|
);
|
||||||
|
coordf_t dist0 = segment2.a.perp_distance_to(line)*2;
|
||||||
|
coordf_t dist1 = segment2.b.perp_distance_to(line)*2;
|
||||||
|
|
||||||
/*
|
|
||||||
Line line = this->edge_to_line(edge);
|
|
||||||
double diff = fabs(dist1 - dist0);
|
|
||||||
double dist_between_segments1 = segment1.a.distance_to(segment2);
|
|
||||||
double dist_between_segments2 = segment1.b.distance_to(segment2);
|
|
||||||
printf("w = %f/%f, dist0 = %f, dist1 = %f, diff = %f, seg1len = %f, seg2len = %f, edgelen = %f, s2s = %f / %f\n",
|
|
||||||
unscale(this->max_width), unscale(this->min_width),
|
|
||||||
unscale(dist0), unscale(dist1), unscale(diff),
|
|
||||||
unscale(segment1.length()), unscale(segment2.length()),
|
|
||||||
unscale(line.length()),
|
|
||||||
unscale(dist_between_segments1), unscale(dist_between_segments2)
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// if this edge is the centerline for a very thin area, we might want to skip it
|
// if this edge is the centerline for a very thin area, we might want to skip it
|
||||||
// in case the area is too thin
|
// in case the area is too thin
|
||||||
if (dist0 < this->min_width && dist1 < this->min_width) {
|
if (dist0 < this->min_width && dist1 < this->min_width) {
|
||||||
|
@ -506,6 +523,11 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->expolygon != NULL && !this->expolygon->contains(line))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this->thickness[&edge] = std::make_pair(dist0, dist1);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
|
#include "ExPolygon.hpp"
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "Polyline.hpp"
|
#include "Polyline.hpp"
|
||||||
|
|
||||||
|
@ -43,18 +44,22 @@ class MedialAxis {
|
||||||
public:
|
public:
|
||||||
Points points;
|
Points points;
|
||||||
Lines lines;
|
Lines lines;
|
||||||
|
const ExPolygon* expolygon;
|
||||||
double max_width;
|
double max_width;
|
||||||
double min_width;
|
double min_width;
|
||||||
MedialAxis(double _max_width, double _min_width) : max_width(_max_width), min_width(_min_width) {};
|
MedialAxis(double _max_width, double _min_width)
|
||||||
|
: max_width(_max_width), min_width(_min_width), expolygon(NULL) {};
|
||||||
|
void build(ThickPolylines* polylines);
|
||||||
void build(Polylines* polylines);
|
void build(Polylines* polylines);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef voronoi_diagram<double> VD;
|
typedef voronoi_diagram<double> VD;
|
||||||
VD vd;
|
VD vd;
|
||||||
std::set<const VD::edge_type*> edges;
|
std::set<const VD::edge_type*> edges;
|
||||||
Line edge_to_line(const VD::edge_type &edge) const;
|
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
|
||||||
void process_edge_neighbors(const voronoi_diagram<double>::edge_type& edge, Points* points);
|
void process_edge_neighbors(const voronoi_diagram<double>::edge_type& edge,
|
||||||
bool is_valid_edge(const voronoi_diagram<double>::edge_type& edge) const;
|
Points* points, std::vector<coordf_t>* width, std::vector<bool>* endpoints);
|
||||||
|
bool validate_edge(const voronoi_diagram<double>::edge_type& edge);
|
||||||
const Line& retrieve_segment(const voronoi_diagram<double>::cell_type& cell) const;
|
const Line& retrieve_segment(const voronoi_diagram<double>::cell_type& cell) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -163,6 +163,55 @@ Line::normal() const
|
||||||
return Vector((this->b.y - this->a.y), -(this->b.x - this->a.x));
|
return Vector((this->b.y - this->a.y), -(this->b.x - this->a.x));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Line::extend_end(double distance)
|
||||||
|
{
|
||||||
|
// relocate last point by extending the segment by the specified length
|
||||||
|
Line line = *this;
|
||||||
|
line.reverse();
|
||||||
|
this->b = line.point_at(-distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Line::extend_start(double distance)
|
||||||
|
{
|
||||||
|
// relocate first point by extending the first segment by the specified length
|
||||||
|
this->a = this->point_at(-distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Line::intersection(const Line& line, Point* intersection) const
|
||||||
|
{
|
||||||
|
double denom = ((line.b.y - line.a.y)*(this->b.x - this->a.x)) -
|
||||||
|
((line.b.x - line.a.x)*(this->b.y - this->a.y));
|
||||||
|
|
||||||
|
double nume_a = ((line.b.x - line.a.x)*(this->a.y - line.a.y)) -
|
||||||
|
((line.b.y - line.a.y)*(this->a.x - line.a.x));
|
||||||
|
|
||||||
|
double nume_b = ((this->b.x - this->a.x)*(this->a.y - line.a.y)) -
|
||||||
|
((this->b.y - this->a.y)*(this->a.x - line.a.x));
|
||||||
|
|
||||||
|
if (fabs(denom) < EPSILON) {
|
||||||
|
if (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON) {
|
||||||
|
return false; // coincident
|
||||||
|
}
|
||||||
|
return false; // parallel
|
||||||
|
}
|
||||||
|
|
||||||
|
double ua = nume_a / denom;
|
||||||
|
double ub = nume_b / denom;
|
||||||
|
|
||||||
|
if (ua >= 0 && ua <= 1.0f && ub >= 0 && ub <= 1.0f)
|
||||||
|
{
|
||||||
|
// Get the intersection point.
|
||||||
|
intersection->x = this->a.x + ua*(this->b.x - this->a.x);
|
||||||
|
intersection->y = this->a.y + ua*(this->b.y - this->a.y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // not intersecting
|
||||||
|
}
|
||||||
|
|
||||||
Pointf3
|
Pointf3
|
||||||
Linef3::intersect_plane(double z) const
|
Linef3::intersect_plane(double z) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,9 @@ namespace Slic3r {
|
||||||
class Line;
|
class Line;
|
||||||
class Linef3;
|
class Linef3;
|
||||||
class Polyline;
|
class Polyline;
|
||||||
|
class ThickLine;
|
||||||
typedef std::vector<Line> Lines;
|
typedef std::vector<Line> Lines;
|
||||||
|
typedef std::vector<ThickLine> ThickLines;
|
||||||
|
|
||||||
class Line
|
class Line
|
||||||
{
|
{
|
||||||
|
@ -39,6 +41,18 @@ class Line
|
||||||
double direction() const;
|
double direction() const;
|
||||||
Vector vector() const;
|
Vector vector() const;
|
||||||
Vector normal() const;
|
Vector normal() const;
|
||||||
|
void extend_end(double distance);
|
||||||
|
void extend_start(double distance);
|
||||||
|
bool intersection(const Line& line, Point* intersection) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ThickLine : public Line
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
coordf_t a_width, b_width;
|
||||||
|
|
||||||
|
ThickLine() : a_width(0), b_width(0) {};
|
||||||
|
ThickLine(Point _a, Point _b) : a_width(0), b_width(0), Line(_a, _b) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Linef
|
class Linef
|
||||||
|
|
|
@ -118,6 +118,16 @@ MultiPoint::append(const Points::const_iterator &begin, const Points::const_iter
|
||||||
this->points.insert(this->points.end(), begin, end);
|
this->points.insert(this->points.end(), begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MultiPoint::intersection(const Line& line, Point* intersection) const
|
||||||
|
{
|
||||||
|
Lines lines = this->lines();
|
||||||
|
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
|
||||||
|
if (it->intersection(line, intersection)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Points
|
Points
|
||||||
MultiPoint::_douglas_peucker(const Points &points, const double tolerance)
|
MultiPoint::_douglas_peucker(const Points &points, const double tolerance)
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,6 +36,7 @@ class MultiPoint
|
||||||
void append(const Point &point);
|
void append(const Point &point);
|
||||||
void append(const Points &points);
|
void append(const Points &points);
|
||||||
void append(const Points::const_iterator &begin, const Points::const_iterator &end);
|
void append(const Points::const_iterator &begin, const Points::const_iterator &end);
|
||||||
|
bool intersection(const Line& line, Point* intersection) const;
|
||||||
|
|
||||||
static Points _douglas_peucker(const Points &points, const double tolerance);
|
static Points _douglas_peucker(const Points &points, const double tolerance);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "PerimeterGenerator.hpp"
|
#include "PerimeterGenerator.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "ExtrusionEntityCollection.hpp"
|
#include "ExtrusionEntityCollection.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ PerimeterGenerator::process()
|
||||||
|
|
||||||
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
||||||
// (actually, something larger than that still may exist due to mitering or other causes)
|
// (actually, something larger than that still may exist due to mitering or other causes)
|
||||||
coord_t min_width = ext_pwidth / 2;
|
coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3);
|
||||||
ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2);
|
ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2);
|
||||||
|
|
||||||
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||||
|
@ -252,43 +253,37 @@ PerimeterGenerator::process()
|
||||||
// fill gaps
|
// fill gaps
|
||||||
if (!gaps.empty()) {
|
if (!gaps.empty()) {
|
||||||
/*
|
/*
|
||||||
if (false) {
|
SVG svg("gaps.svg");
|
||||||
require "Slic3r/SVG.pm";
|
svg.draw(union_ex(gaps));
|
||||||
Slic3r::SVG::output(
|
svg.Close();
|
||||||
"gaps.svg",
|
|
||||||
expolygons => union_ex(\@gaps),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth
|
// collapse
|
||||||
// where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth
|
double min = 0.1*pwidth * (1 - INSET_OVERLAP_TOLERANCE);
|
||||||
std::vector<PerimeterGeneratorGapSize> gap_sizes;
|
double max = 2*pspacing;
|
||||||
gap_sizes.push_back(PerimeterGeneratorGapSize(pwidth, 2*pspacing, 2*pwidth));
|
ExPolygons gaps_ex = diff_ex(
|
||||||
gap_sizes.push_back(PerimeterGeneratorGapSize(0.1*pwidth, pwidth, 1*pwidth));
|
offset2(gaps, -min/2, +min/2),
|
||||||
|
offset2(gaps, -max/2, +max/2),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
for (std::vector<PerimeterGeneratorGapSize>::const_iterator gap_size = gap_sizes.begin();
|
ThickPolylines polylines;
|
||||||
gap_size != gap_sizes.end(); ++gap_size) {
|
for (ExPolygons::const_iterator ex = gaps_ex.begin(); ex != gaps_ex.end(); ++ex)
|
||||||
ExtrusionEntityCollection gap_fill = this->_fill_gaps(gap_size->min,
|
ex->medial_axis(max, min/2, &polylines);
|
||||||
gap_size->max, unscale(gap_size->width), gaps);
|
|
||||||
this->gap_fill->append(gap_fill.entities);
|
if (!polylines.empty()) {
|
||||||
|
ExtrusionEntityCollection gap_fill = this->_variable_width(polylines,
|
||||||
|
erGapFill, this->solid_infill_flow);
|
||||||
|
|
||||||
// Make sure we don't infill narrow parts that are already gap-filled
|
this->gap_fill->append(gap_fill.entities);
|
||||||
// (we only consider this surface's gaps to reduce the diff() complexity).
|
|
||||||
// Growing actual extrusions ensures that gaps not filled by medial axis
|
/* Make sure we don't infill narrow parts that are already gap-filled
|
||||||
// are not subtracted from fill surfaces (they might be too short gaps
|
(we only consider this surface's gaps to reduce the diff() complexity).
|
||||||
// that medial axis skips but infill might join with other infill regions
|
Growing actual extrusions ensures that gaps not filled by medial axis
|
||||||
// and use zigzag).
|
are not subtracted from fill surfaces (they might be too short gaps
|
||||||
coord_t dist = gap_size->width/2;
|
that medial axis skips but infill might join with other infill regions
|
||||||
Polygons filled;
|
and use zigzag). */
|
||||||
for (ExtrusionEntitiesPtr::const_iterator it = gap_fill.entities.begin();
|
last = diff(last, gap_fill.grow());
|
||||||
it != gap_fill.entities.end(); ++it) {
|
|
||||||
Polygons f;
|
|
||||||
offset((*it)->as_polyline(), &f, dist);
|
|
||||||
filled.insert(filled.end(), f.begin(), f.end());
|
|
||||||
}
|
|
||||||
last = diff(last, filled);
|
|
||||||
gaps = diff(gaps, filled); // prevent more gap fill here
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,51 +449,92 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops,
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtrusionEntityCollection
|
ExtrusionEntityCollection
|
||||||
PerimeterGenerator::_fill_gaps(double min, double max, double w,
|
PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const
|
||||||
const Polygons &gaps) const
|
|
||||||
{
|
{
|
||||||
|
const double tolerance = scale_(0.1);
|
||||||
|
|
||||||
ExtrusionEntityCollection coll;
|
ExtrusionEntityCollection coll;
|
||||||
|
for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) {
|
||||||
min *= (1 - INSET_OVERLAP_TOLERANCE);
|
ExtrusionPaths paths;
|
||||||
|
ExtrusionPath path(role);
|
||||||
ExPolygons curr = diff_ex(
|
ThickLines lines = p->thicklines();
|
||||||
offset2(gaps, -min/2, +min/2),
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
offset2(gaps, -max/2, +max/2),
|
const ThickLine& line = lines[i];
|
||||||
true
|
const double thickness_delta = fabs(line.a_width - line.b_width);
|
||||||
);
|
if (thickness_delta > tolerance) {
|
||||||
|
const unsigned short segments = ceil(thickness_delta / tolerance);
|
||||||
Polylines polylines;
|
const coordf_t line_len = line.length();
|
||||||
for (ExPolygons::const_iterator ex = curr.begin(); ex != curr.end(); ++ex)
|
const coordf_t seg_len = line_len / segments;
|
||||||
ex->medial_axis(max, min/2, &polylines);
|
Points pp;
|
||||||
if (polylines.empty())
|
std::vector<coordf_t> width;
|
||||||
return coll;
|
{
|
||||||
|
pp.push_back(line.a);
|
||||||
#ifdef SLIC3R_DEBUG
|
width.push_back(line.a_width);
|
||||||
if (!curr.empty())
|
for (size_t j = 1; j < segments; ++j) {
|
||||||
printf(" %zu gaps filled with extrusion width = %f\n", curr.size(), w);
|
pp.push_back(line.point_at(j*seg_len));
|
||||||
#endif
|
|
||||||
|
coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len;
|
||||||
//my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
|
width.push_back(w);
|
||||||
Flow flow(
|
width.push_back(w);
|
||||||
w, this->layer_height, this->solid_infill_flow.nozzle_diameter
|
}
|
||||||
);
|
pp.push_back(line.b);
|
||||||
|
width.push_back(line.b_width);
|
||||||
double mm3_per_mm = flow.mm3_per_mm();
|
|
||||||
|
assert(pp.size() == segments + 1);
|
||||||
for (Polylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) {
|
assert(width.size() == segments*2);
|
||||||
ExtrusionPath path(erGapFill);
|
}
|
||||||
path.polyline = *p;
|
|
||||||
path.mm3_per_mm = mm3_per_mm;
|
// delete this line and insert new ones
|
||||||
path.width = flow.width;
|
lines.erase(lines.begin() + i);
|
||||||
path.height = this->layer_height;
|
for (size_t j = 0; j < segments; ++j) {
|
||||||
|
ThickLine new_line(pp[j], pp[j+1]);
|
||||||
|
new_line.a_width = width[2*j];
|
||||||
|
new_line.b_width = width[2*j+1];
|
||||||
|
lines.insert(lines.begin() + i + j, new_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
--i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double w = fmax(line.a_width, line.b_width);
|
||||||
|
|
||||||
|
if (path.polyline.points.empty()) {
|
||||||
|
path.polyline.append(line.a);
|
||||||
|
path.polyline.append(line.b);
|
||||||
|
|
||||||
|
flow.width = unscale(w);
|
||||||
|
#ifdef SLIC3R_DEBUG
|
||||||
|
printf(" filling %f gap\n", flow.width);
|
||||||
|
#endif
|
||||||
|
path.mm3_per_mm = flow.mm3_per_mm();
|
||||||
|
path.width = flow.width;
|
||||||
|
path.height = flow.height;
|
||||||
|
} else if (fabs(flow.width - w) <= tolerance) {
|
||||||
|
// the width difference between this line and the current flow width is
|
||||||
|
// within the accepted tolerance
|
||||||
|
|
||||||
|
path.polyline.append(line.b);
|
||||||
|
} else {
|
||||||
|
// we need to initialize a new line
|
||||||
|
paths.push_back(path);
|
||||||
|
path = ExtrusionPath(role);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!path.polyline.points.empty())
|
||||||
|
paths.push_back(path);
|
||||||
|
|
||||||
if (p->is_valid() && p->first_point().coincides_with(p->last_point())) {
|
// loop through generated paths
|
||||||
// since medial_axis() now returns only Polyline objects, detect loops here
|
for (ExtrusionPaths::const_iterator p = paths.begin(); p != paths.end(); ++p) {
|
||||||
ExtrusionLoop loop;
|
if (p->polyline.is_valid()) {
|
||||||
loop.paths.push_back(path);
|
if (p->first_point().coincides_with(p->last_point())) {
|
||||||
coll.append(loop);
|
// since medial_axis() now returns only Polyline objects, detect loops here
|
||||||
} else {
|
coll.append(ExtrusionLoop(*p));
|
||||||
coll.append(path);
|
} else {
|
||||||
|
coll.append(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,17 +66,8 @@ class PerimeterGenerator {
|
||||||
|
|
||||||
ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops,
|
ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops,
|
||||||
Polylines &thin_walls) const;
|
Polylines &thin_walls) const;
|
||||||
ExtrusionEntityCollection _fill_gaps(double min, double max, double w,
|
ExtrusionEntityCollection _variable_width
|
||||||
const Polygons &gaps) const;
|
(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const;
|
||||||
};
|
|
||||||
|
|
||||||
class PerimeterGeneratorGapSize {
|
|
||||||
public:
|
|
||||||
coord_t min;
|
|
||||||
coord_t max;
|
|
||||||
coord_t width;
|
|
||||||
PerimeterGeneratorGapSize(coord_t min, coord_t max, coord_t width)
|
|
||||||
: min(min), max(max), width(width) {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ class Polygon : public MultiPoint {
|
||||||
Polygon() {};
|
Polygon() {};
|
||||||
explicit Polygon(const Points &points): MultiPoint(points) {};
|
explicit Polygon(const Points &points): MultiPoint(points) {};
|
||||||
Point last_point() const;
|
Point last_point() const;
|
||||||
Lines lines() const;
|
virtual Lines lines() const;
|
||||||
Polyline split_at_vertex(const Point &point) const;
|
Polyline split_at_vertex(const Point &point) const;
|
||||||
Polyline split_at_index(int index) const;
|
Polyline split_at_index(int index) const;
|
||||||
Polyline split_at_first_point() const;
|
Polyline split_at_first_point() const;
|
||||||
|
|
|
@ -83,17 +83,18 @@ void
|
||||||
Polyline::extend_end(double distance)
|
Polyline::extend_end(double distance)
|
||||||
{
|
{
|
||||||
// relocate last point by extending the last segment by the specified length
|
// relocate last point by extending the last segment by the specified length
|
||||||
Line line(this->points[ this->points.size()-2 ], this->points.back());
|
Line line(
|
||||||
this->points.pop_back();
|
this->points.back(),
|
||||||
this->points.push_back(line.point_at(line.length() + distance));
|
*(this->points.end() - 2)
|
||||||
|
);
|
||||||
|
this->points.back() = line.point_at(-distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Polyline::extend_start(double distance)
|
Polyline::extend_start(double distance)
|
||||||
{
|
{
|
||||||
// relocate first point by extending the first segment by the specified length
|
// relocate first point by extending the first segment by the specified length
|
||||||
Line line(this->points[1], this->points.front());
|
this->points.front() = Line(this->points.front(), this->points[1]).point_at(-distance);
|
||||||
this->points[0] = line.point_at(line.length() + distance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this method returns a collection of points picked on the polygon contour
|
/* this method returns a collection of points picked on the polygon contour
|
||||||
|
@ -218,4 +219,20 @@ Polyline::wkt() const
|
||||||
return wkt.str();
|
return wkt.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThickLines
|
||||||
|
ThickPolyline::thicklines() const
|
||||||
|
{
|
||||||
|
ThickLines lines;
|
||||||
|
if (this->points.size() >= 2) {
|
||||||
|
lines.reserve(this->points.size() - 1);
|
||||||
|
for (size_t i = 0; i < this->points.size()-1; ++i) {
|
||||||
|
ThickLine line(this->points[i], this->points[i+1]);
|
||||||
|
line.a_width = this->width[2*i];
|
||||||
|
line.b_width = this->width[2*i+1];
|
||||||
|
lines.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
#include "Line.hpp"
|
#include "Line.hpp"
|
||||||
#include "MultiPoint.hpp"
|
#include "MultiPoint.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ExPolygon;
|
|
||||||
class Polyline;
|
class Polyline;
|
||||||
|
class ThickPolyline;
|
||||||
typedef std::vector<Polyline> Polylines;
|
typedef std::vector<Polyline> Polylines;
|
||||||
|
typedef std::vector<ThickPolyline> ThickPolylines;
|
||||||
|
|
||||||
class Polyline : public MultiPoint {
|
class Polyline : public MultiPoint {
|
||||||
public:
|
public:
|
||||||
|
@ -18,7 +20,7 @@ class Polyline : public MultiPoint {
|
||||||
operator Line() const;
|
operator Line() const;
|
||||||
Point last_point() const;
|
Point last_point() const;
|
||||||
Point leftmost_point() const;
|
Point leftmost_point() const;
|
||||||
Lines lines() const;
|
virtual Lines lines() const;
|
||||||
void clip_end(double distance);
|
void clip_end(double distance);
|
||||||
void clip_start(double distance);
|
void clip_start(double distance);
|
||||||
void extend_end(double distance);
|
void extend_end(double distance);
|
||||||
|
@ -31,6 +33,13 @@ class Polyline : public MultiPoint {
|
||||||
std::string wkt() const;
|
std::string wkt() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ThickPolyline : public Polyline {
|
||||||
|
public:
|
||||||
|
std::vector<coordf_t> width;
|
||||||
|
std::vector<bool> endpoints;
|
||||||
|
ThickLines thicklines() const;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
SVG::SVG(const char* filename)
|
SVG::SVG(const char* filename)
|
||||||
: arrows(true), fill("grey"), stroke("black"), filename(filename)
|
: arrows(false), fill("grey"), stroke("black"), filename(filename)
|
||||||
{
|
{
|
||||||
this->f = fopen(filename, "w");
|
this->f = fopen(filename, "w");
|
||||||
fprintf(this->f,
|
fprintf(this->f,
|
||||||
|
@ -90,7 +90,14 @@ void
|
||||||
SVG::draw(const Polylines &polylines, std::string stroke)
|
SVG::draw(const Polylines &polylines, std::string stroke)
|
||||||
{
|
{
|
||||||
for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
|
for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
|
||||||
this->draw(*it, fill);
|
this->draw(*it, stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SVG::draw(const ThickPolylines &polylines, std::string stroke)
|
||||||
|
{
|
||||||
|
for (ThickPolylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
|
||||||
|
this->draw((Polyline)*it, stroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -24,6 +24,7 @@ class SVG
|
||||||
void draw(const Polygons &polygons, std::string fill = "grey");
|
void draw(const Polygons &polygons, std::string fill = "grey");
|
||||||
void draw(const Polyline &polyline, std::string stroke = "black");
|
void draw(const Polyline &polyline, std::string stroke = "black");
|
||||||
void draw(const Polylines &polylines, std::string stroke = "black");
|
void draw(const Polylines &polylines, std::string stroke = "black");
|
||||||
|
void draw(const ThickPolylines &polylines, std::string stroke = "black");
|
||||||
void draw(const Point &point, std::string fill = "black", unsigned int radius = 3);
|
void draw(const Point &point, std::string fill = "black", unsigned int radius = 3);
|
||||||
void draw(const Points &points, std::string fill = "black", unsigned int radius = 3);
|
void draw(const Points &points, std::string fill = "black", unsigned int radius = 3);
|
||||||
void Close();
|
void Close();
|
||||||
|
|
Loading…
Reference in a new issue