mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-25 09:41:11 -06:00
Merge remote-tracking branch 'origin/master' into custom_gcodes
This commit is contained in:
commit
befbd6b0fe
35 changed files with 821 additions and 262 deletions
|
|
@ -188,6 +188,8 @@ add_library(libslic3r STATIC
|
|||
Time.cpp
|
||||
Time.hpp
|
||||
MTUtils.hpp
|
||||
VoronoiOffset.cpp
|
||||
VoronoiOffset.hpp
|
||||
Zipper.hpp
|
||||
Zipper.cpp
|
||||
MinAreaBoundingBox.hpp
|
||||
|
|
|
|||
|
|
@ -1298,7 +1298,28 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
|
|||
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
|
||||
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
|
||||
m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
|
||||
|
||||
{
|
||||
BoundingBoxf bbox(print.config().bed_shape.values);
|
||||
m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
|
||||
m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
|
||||
m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
|
||||
}
|
||||
{
|
||||
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
// It does NOT encompass user extrusions generated by custom G-code,
|
||||
// therefore it does NOT encompass the initial purge line.
|
||||
// It does NOT encompass MMU/MMU2 starting (wipe) areas.
|
||||
auto pts = std::make_unique<ConfigOptionPoints>();
|
||||
pts->values.reserve(print.first_layer_convex_hull().size());
|
||||
for (const Point &pt : print.first_layer_convex_hull().points)
|
||||
pts->values.emplace_back(unscale(pt));
|
||||
BoundingBoxf bbox(pts->values);
|
||||
m_placeholder_parser.set("first_layer_print_convex_hull", pts.release());
|
||||
m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
|
||||
m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
|
||||
m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
|
||||
}
|
||||
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
|
|||
max_layer_height = std::min(max_layer_height, mlh);
|
||||
}
|
||||
// The Prusa3D Fast (0.35mm layer height) print profile sets a higher layer height than what is normally allowed
|
||||
// by the nozzle. This is a hack and it works by increasing extrusion width.
|
||||
// by the nozzle. This is a hack and it works by increasing extrusion width. See GH #3919.
|
||||
max_layer_height = std::max(max_layer_height, max_object_layer_height);
|
||||
|
||||
for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) {
|
||||
|
|
@ -400,47 +400,21 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
|
|||
// and maybe other problems. We will therefore go through layer_tools and detect and fix this.
|
||||
// So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder),
|
||||
// we'll mark it with has_wipe tower.
|
||||
assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower);
|
||||
if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) {
|
||||
for (size_t i = 0; i + 1 < m_layer_tools.size();) {
|
||||
const LayerTools < = m_layer_tools[i];
|
||||
assert(lt.has_wipe_tower);
|
||||
assert(! lt.extruders.empty());
|
||||
// Find the next layer with wipe tower or mark a layer as such.
|
||||
size_t j = i + 1;
|
||||
for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j) {
|
||||
LayerTools <_next = m_layer_tools[j];
|
||||
if (lt_next.extruders.empty()) {
|
||||
//FIXME Vojtech: Lukasi, proc?
|
||||
j = m_layer_tools.size();
|
||||
break;
|
||||
}
|
||||
if (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1) {
|
||||
// Support only layer, soluble layers? Otherwise the layer should have been already marked as having wipe tower.
|
||||
assert(lt_next.has_support && ! lt_next.has_object);
|
||||
lt_next.has_wipe_tower = true;
|
||||
break;
|
||||
}
|
||||
for (unsigned int i=0; i+1<m_layer_tools.size(); ++i) {
|
||||
LayerTools& lt = m_layer_tools[i];
|
||||
LayerTools& lt_next = m_layer_tools[i+1];
|
||||
if (lt.extruders.empty() || lt_next.extruders.empty())
|
||||
break;
|
||||
if (!lt_next.has_wipe_tower && (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1))
|
||||
lt_next.has_wipe_tower = true;
|
||||
// We should also check that the next wipe tower layer is no further than max_layer_height:
|
||||
unsigned int j = i+1;
|
||||
double last_wipe_tower_print_z = lt_next.print_z;
|
||||
while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower)
|
||||
if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) {
|
||||
m_layer_tools[j].has_wipe_tower = true;
|
||||
last_wipe_tower_print_z = m_layer_tools[j].print_z;
|
||||
}
|
||||
if (j == m_layer_tools.size())
|
||||
// No wipe tower above layer i, therefore no need to add any wipe tower layer above i.
|
||||
break;
|
||||
// We should also check that the next wipe tower layer is no further than max_layer_height.
|
||||
// This algorith may in theory create very thin wipe layer j if layer closely below j is marked as wipe tower.
|
||||
// This may happen if printing with non-soluble break away supports.
|
||||
// On the other side it should not hurt as there will be no wipe, just perimeter and sparse infill printed
|
||||
// at that particular wipe tower layer without extruder change.
|
||||
double last_wipe_tower_print_z = lt.print_z;
|
||||
assert(m_layer_tools[j].has_wipe_tower);
|
||||
for (size_t k = i + 1; k < j; ++k) {
|
||||
assert(! m_layer_tools[k].has_wipe_tower);
|
||||
if (m_layer_tools[k + 1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) {
|
||||
m_layer_tools[k].has_wipe_tower = true;
|
||||
last_wipe_tower_print_z = m_layer_tools[k].print_z;
|
||||
}
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the wipe_tower_layer_height values.
|
||||
|
|
|
|||
|
|
@ -252,8 +252,16 @@ bool arrange(
|
|||
// output
|
||||
Pointfs &positions);
|
||||
|
||||
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
|
||||
public:
|
||||
typedef double coord_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
};
|
||||
|
||||
class MedialAxis {
|
||||
public:
|
||||
public:
|
||||
Lines lines;
|
||||
const ExPolygon* expolygon;
|
||||
double max_width;
|
||||
|
|
@ -263,14 +271,8 @@ class MedialAxis {
|
|||
void build(ThickPolylines* polylines);
|
||||
void build(Polylines* polylines);
|
||||
|
||||
private:
|
||||
class VD : public boost::polygon::voronoi_diagram<double> {
|
||||
public:
|
||||
typedef double coord_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
};
|
||||
private:
|
||||
using VD = VoronoiDiagram;
|
||||
VD vd;
|
||||
std::set<const VD::edge_type*> edges, valid_edges;
|
||||
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ class SupportLayer : public Layer
|
|||
{
|
||||
public:
|
||||
// Polygons covered by the supports: base, interface and contact areas.
|
||||
// Used to suppress retraction if moving for a support extrusion over these support_islands.
|
||||
ExPolygonCollection support_islands;
|
||||
// Extrusion paths for the support base and for the support interface and contacts.
|
||||
ExtrusionEntityCollection support_fills;
|
||||
|
|
|
|||
|
|
@ -1480,9 +1480,6 @@ stl_stats ModelObject::get_object_stl_stats() const
|
|||
// fill full_stats from all objet's meshes
|
||||
for (ModelVolume* volume : this->volumes)
|
||||
{
|
||||
if (volume->id() == this->volumes[0]->id())
|
||||
continue;
|
||||
|
||||
const stl_stats& stats = volume->mesh().stl.stats;
|
||||
|
||||
// initialize full_stats (for repaired errors)
|
||||
|
|
|
|||
|
|
@ -244,7 +244,6 @@ bool Print::invalidate_step(PrintStep step)
|
|||
{
|
||||
bool invalidated = Inherited::invalidate_step(step);
|
||||
// Propagate to dependent steps.
|
||||
//FIXME Why should skirt invalidate brim? Shouldn't it be vice versa?
|
||||
if (step == psSkirt)
|
||||
invalidated |= Inherited::invalidate_step(psBrim);
|
||||
if (step != psGCodeExport)
|
||||
|
|
@ -1606,6 +1605,8 @@ void Print::process()
|
|||
}
|
||||
if (this->set_started(psSkirt)) {
|
||||
m_skirt.clear();
|
||||
m_skirt_convex_hull.clear();
|
||||
m_first_layer_convex_hull.points.clear();
|
||||
if (this->has_skirt()) {
|
||||
this->set_status(88, L("Generating skirt"));
|
||||
this->_make_skirt();
|
||||
|
|
@ -1614,11 +1615,15 @@ void Print::process()
|
|||
}
|
||||
if (this->set_started(psBrim)) {
|
||||
m_brim.clear();
|
||||
m_first_layer_convex_hull.points.clear();
|
||||
if (m_config.brim_width > 0) {
|
||||
this->set_status(88, L("Generating brim"));
|
||||
this->_make_brim();
|
||||
}
|
||||
this->set_done(psBrim);
|
||||
// Brim depends on skirt (brim lines are trimmed by the skirt lines), therefore if
|
||||
// the skirt gets invalidated, brim gets invalidated as well and the following line is called.
|
||||
this->finalize_first_layer_convex_hull();
|
||||
this->set_done(psBrim);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
|
||||
}
|
||||
|
|
@ -1697,22 +1702,7 @@ void Print::_make_skirt()
|
|||
}
|
||||
|
||||
// Include the wipe tower.
|
||||
if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) {
|
||||
double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width;
|
||||
double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width;
|
||||
Vec2d pt = Vec2d(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width);
|
||||
|
||||
std::vector<Vec2d> pts;
|
||||
pts.push_back(Vec2d(pt.x(), pt.y()));
|
||||
pts.push_back(Vec2d(pt.x()+width, pt.y()));
|
||||
pts.push_back(Vec2d(pt.x()+width, pt.y()+depth));
|
||||
pts.push_back(Vec2d(pt.x(), pt.y()+depth));
|
||||
for (Vec2d& pt : pts) {
|
||||
pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt;
|
||||
pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value);
|
||||
points.push_back(Point(scale_(pt.x()), scale_(pt.y())));
|
||||
}
|
||||
}
|
||||
append(points, this->first_layer_wipe_tower_corners());
|
||||
|
||||
if (points.size() < 3)
|
||||
// At least three points required for a convex hull.
|
||||
|
|
@ -1796,28 +1786,19 @@ void Print::_make_skirt()
|
|||
}
|
||||
// Brims were generated inside out, reverse to print the outmost contour first.
|
||||
m_skirt.reverse();
|
||||
|
||||
// Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull.
|
||||
for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1))))
|
||||
append(m_skirt_convex_hull, std::move(poly.points));
|
||||
}
|
||||
|
||||
void Print::_make_brim()
|
||||
{
|
||||
// Brim is only printed on first layer and uses perimeter extruder.
|
||||
Polygons islands = this->first_layer_islands();
|
||||
Polygons loops;
|
||||
Flow flow = this->brim_flow();
|
||||
Polygons islands;
|
||||
for (PrintObject *object : m_objects) {
|
||||
Polygons object_islands;
|
||||
for (ExPolygon &expoly : object->m_layers.front()->lslices)
|
||||
object_islands.push_back(expoly.contour);
|
||||
if (! object->support_layers().empty())
|
||||
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
|
||||
islands.reserve(islands.size() + object_islands.size() * object->instances().size());
|
||||
for (const PrintInstance &instance : object->instances())
|
||||
for (Polygon &poly : object_islands) {
|
||||
islands.push_back(poly);
|
||||
islands.back().translate(instance.shift);
|
||||
}
|
||||
}
|
||||
Polygons loops;
|
||||
size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
|
||||
size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
|
||||
for (size_t i = 0; i < num_loops; ++ i) {
|
||||
this->throw_if_canceled();
|
||||
islands = offset(islands, float(flow.scaled_spacing()), jtSquare);
|
||||
|
|
@ -1828,6 +1809,11 @@ void Print::_make_brim()
|
|||
p.pop_back();
|
||||
poly.points = std::move(p);
|
||||
}
|
||||
if (i + 1 == num_loops) {
|
||||
// Remember the outer edge of the last brim line extruded as m_first_layer_convex_hull.
|
||||
for (Polygon &poly : islands)
|
||||
append(m_first_layer_convex_hull.points, poly.points);
|
||||
}
|
||||
polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
|
||||
}
|
||||
loops = union_pt_chained(loops, false);
|
||||
|
|
@ -1967,6 +1953,58 @@ void Print::_make_brim()
|
|||
}
|
||||
}
|
||||
|
||||
Polygons Print::first_layer_islands() const
|
||||
{
|
||||
Polygons islands;
|
||||
for (PrintObject *object : m_objects) {
|
||||
Polygons object_islands;
|
||||
for (ExPolygon &expoly : object->m_layers.front()->lslices)
|
||||
object_islands.push_back(expoly.contour);
|
||||
if (! object->support_layers().empty())
|
||||
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
|
||||
islands.reserve(islands.size() + object_islands.size() * object->instances().size());
|
||||
for (const PrintInstance &instance : object->instances())
|
||||
for (Polygon &poly : object_islands) {
|
||||
islands.push_back(poly);
|
||||
islands.back().translate(instance.shift);
|
||||
}
|
||||
}
|
||||
return islands;
|
||||
}
|
||||
|
||||
std::vector<Point> Print::first_layer_wipe_tower_corners() const
|
||||
{
|
||||
std::vector<Point> corners;
|
||||
if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) {
|
||||
double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width;
|
||||
double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width;
|
||||
Vec2d pt0(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width);
|
||||
for (Vec2d pt : {
|
||||
pt0,
|
||||
Vec2d(pt0.x()+width, pt0.y() ),
|
||||
Vec2d(pt0.x()+width, pt0.y()+depth),
|
||||
Vec2d(pt0.x(), pt0.y()+depth)
|
||||
}) {
|
||||
pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt;
|
||||
pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value);
|
||||
corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y())));
|
||||
}
|
||||
}
|
||||
return corners;
|
||||
}
|
||||
|
||||
void Print::finalize_first_layer_convex_hull()
|
||||
{
|
||||
append(m_first_layer_convex_hull.points, m_skirt_convex_hull);
|
||||
if (m_first_layer_convex_hull.empty()) {
|
||||
// Neither skirt nor brim was extruded. Collect points of printed objects from 1st layer.
|
||||
for (Polygon &poly : this->first_layer_islands())
|
||||
append(m_first_layer_convex_hull.points, std::move(poly.points));
|
||||
}
|
||||
append(m_first_layer_convex_hull.points, this->first_layer_wipe_tower_corners());
|
||||
m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points);
|
||||
}
|
||||
|
||||
// Wipe tower support.
|
||||
bool Print::has_wipe_tower() const
|
||||
{
|
||||
|
|
@ -1991,7 +2029,6 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_l
|
|||
return m_wipe_tower_data;
|
||||
}
|
||||
|
||||
|
||||
void Print::_make_wipe_tower()
|
||||
{
|
||||
m_wipe_tower_data.clear();
|
||||
|
|
|
|||
|
|
@ -402,6 +402,12 @@ public:
|
|||
|
||||
const ExtrusionEntityCollection& skirt() const { return m_skirt; }
|
||||
const ExtrusionEntityCollection& brim() const { return m_brim; }
|
||||
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
// It does NOT encompass user extrusions generated by custom G-code,
|
||||
// therefore it does NOT encompass the initial purge line.
|
||||
// It does NOT encompass MMU/MMU2 starting (wipe) areas.
|
||||
const Polygon& first_layer_convex_hull() const { return m_first_layer_convex_hull; }
|
||||
|
||||
const PrintStatistics& print_statistics() const { return m_print_statistics; }
|
||||
|
||||
|
|
@ -437,6 +443,12 @@ private:
|
|||
void _make_skirt();
|
||||
void _make_brim();
|
||||
void _make_wipe_tower();
|
||||
void finalize_first_layer_convex_hull();
|
||||
|
||||
// Islands of objects and their supports extruded at the 1st layer.
|
||||
Polygons first_layer_islands() const;
|
||||
// Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim.
|
||||
std::vector<Point> first_layer_wipe_tower_corners() const;
|
||||
|
||||
// Declared here to have access to Model / ModelObject / ModelInstance
|
||||
static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src);
|
||||
|
|
@ -450,6 +462,13 @@ private:
|
|||
// Ordered collections of extrusion paths to build skirt loops and brim.
|
||||
ExtrusionEntityCollection m_skirt;
|
||||
ExtrusionEntityCollection m_brim;
|
||||
// Convex hull of the 1st layer extrusions.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
// It does NOT encompass user extrusions generated by custom G-code,
|
||||
// therefore it does NOT encompass the initial purge line.
|
||||
// It does NOT encompass MMU/MMU2 starting (wipe) areas.
|
||||
Polygon m_first_layer_convex_hull;
|
||||
Points m_skirt_convex_hull;
|
||||
|
||||
// Following section will be consumed by the GCodeGenerator.
|
||||
ToolOrdering m_tool_ordering;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
// There is an implementation of a hole-aware raycaster that was eventually
|
||||
// not used in production version. It is now hidden under following define
|
||||
// for possible future use.
|
||||
#define SLIC3R_HOLE_RAYCASTER
|
||||
// #define SLIC3R_HOLE_RAYCASTER
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
|
|
|
|||
|
|
@ -48,5 +48,8 @@
|
|||
// Enable smoothing of objects normals
|
||||
#define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1)
|
||||
|
||||
// Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5
|
||||
#define ENABLE_OPENGL_ERROR_LOGGING (1 && ENABLE_2_3_0_ALPHA1)
|
||||
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
|
|
|||
393
src/libslic3r/VoronoiOffset.cpp
Normal file
393
src/libslic3r/VoronoiOffset.cpp
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
// Polygon offsetting code inspired by OpenVoronoi by Anders Wallin
|
||||
// https://github.com/aewallin/openvoronoi
|
||||
// This offsetter uses results of boost::polygon Voronoi.
|
||||
|
||||
#include "VoronoiOffset.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using VD = Geometry::VoronoiDiagram;
|
||||
|
||||
namespace detail {
|
||||
// Intersect a circle with a ray, return the two parameters
|
||||
double first_circle_segment_intersection_parameter(
|
||||
const Vec2d ¢er, const double r, const Vec2d &pt, const Vec2d &v)
|
||||
{
|
||||
const Vec2d d = pt - center;
|
||||
#ifndef NDEBUG
|
||||
double d0 = (pt - center).norm();
|
||||
double d1 = (pt + v - center).norm();
|
||||
assert(r < std::max(d0, d1) + EPSILON);
|
||||
#endif /* NDEBUG */
|
||||
const double a = v.squaredNorm();
|
||||
const double b = 2. * d.dot(v);
|
||||
const double c = d.squaredNorm() - r * r;
|
||||
std::pair<int, std::array<double, 2>> out;
|
||||
double u = b * b - 4. * a * c;
|
||||
assert(u > - EPSILON);
|
||||
double t;
|
||||
if (u <= 0) {
|
||||
// Degenerate to a single closest point.
|
||||
t = - b / (2. * a);
|
||||
assert(t >= - EPSILON && t <= 1. + EPSILON);
|
||||
return Slic3r::clamp(0., 1., t);
|
||||
} else {
|
||||
u = sqrt(u);
|
||||
out.first = 2;
|
||||
double t0 = (- b - u) / (2. * a);
|
||||
double t1 = (- b + u) / (2. * a);
|
||||
// One of the intersections shall be found inside the segment.
|
||||
assert((t0 >= - EPSILON && t0 <= 1. + EPSILON) || (t1 >= - EPSILON && t1 <= 1. + EPSILON));
|
||||
if (t1 < 0.)
|
||||
return 0.;
|
||||
if (t0 > 1.)
|
||||
return 1.;
|
||||
return (t0 > 0.) ? t0 : t1;
|
||||
}
|
||||
}
|
||||
|
||||
Vec2d voronoi_edge_offset_point(
|
||||
const VD &vd,
|
||||
const Lines &lines,
|
||||
// Distance of a VD vertex to the closest site (input polygon edge or vertex).
|
||||
const std::vector<double> &vertex_dist,
|
||||
// Minium distance of a VD edge to the closest site (input polygon edge or vertex).
|
||||
// For a parabolic segment the distance may be smaller than the distance of the two end points.
|
||||
const std::vector<double> &edge_dist,
|
||||
// Edge for which to calculate the offset point. If the distance towards the input polygon
|
||||
// is not monotonical, pick the offset point closer to edge.vertex0().
|
||||
const VD::edge_type &edge,
|
||||
// Distance from the input polygon along the edge.
|
||||
const double offset_distance)
|
||||
{
|
||||
const VD::vertex_type *v0 = edge.vertex0();
|
||||
const VD::vertex_type *v1 = edge.vertex1();
|
||||
const VD::cell_type *cell = edge.cell();
|
||||
const VD::cell_type *cell2 = edge.twin()->cell();
|
||||
const Line &line0 = lines[cell->source_index()];
|
||||
const Line &line1 = lines[cell2->source_index()];
|
||||
if (v0 == nullptr || v1 == nullptr) {
|
||||
assert(edge.is_infinite());
|
||||
assert(v0 != nullptr || v1 != nullptr);
|
||||
// Offsetting on an unconstrained edge.
|
||||
assert(offset_distance > vertex_dist[(v0 ? v0 : v1) - &vd.vertices().front()] - EPSILON);
|
||||
Vec2d pt, dir;
|
||||
double t;
|
||||
if (cell->contains_point() && cell2->contains_point()) {
|
||||
const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
|
||||
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
|
||||
// Direction vector of this unconstrained Voronoi edge.
|
||||
dir = Vec2d(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x()));
|
||||
if (v0 == nullptr) {
|
||||
v0 = v1;
|
||||
dir = - dir;
|
||||
}
|
||||
pt = Vec2d(v0->x(), v0->y());
|
||||
t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir);
|
||||
} else {
|
||||
// Infinite edges could not be created by two segment sites.
|
||||
assert(cell->contains_point() != cell2->contains_point());
|
||||
// Linear edge goes through the endpoint of a segment.
|
||||
assert(edge.is_linear());
|
||||
assert(edge.is_secondary());
|
||||
const Line &line = cell->contains_segment() ? line0 : line1;
|
||||
const Point &ipt = cell->contains_segment() ?
|
||||
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) :
|
||||
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b);
|
||||
assert(line.a == ipt || line.b == ipt);
|
||||
pt = Vec2d(ipt.x(), ipt.y());
|
||||
dir = Vec2d(line.a.y() - line.b.y(), line.b.x() - line.a.x());
|
||||
assert(dir.norm() > 0.);
|
||||
t = offset_distance / dir.norm();
|
||||
if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr))
|
||||
t = - t;
|
||||
}
|
||||
return pt + t * dir;
|
||||
} else {
|
||||
// Constrained edge.
|
||||
Vec2d p0(v0->x(), v0->y());
|
||||
Vec2d p1(v1->x(), v1->y());
|
||||
double d0 = vertex_dist[v0 - &vd.vertices().front()];
|
||||
double d1 = vertex_dist[v1 - &vd.vertices().front()];
|
||||
if (cell->contains_segment() && cell2->contains_segment()) {
|
||||
// This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically.
|
||||
double ddif = d1 - d0;
|
||||
assert(offset_distance > std::min(d0, d1) - EPSILON && offset_distance < std::max(d0, d1) + EPSILON);
|
||||
double t = (ddif == 0) ? 0. : clamp(0., 1., (offset_distance - d0) / ddif);
|
||||
return Slic3r::lerp(p0, p1, t);
|
||||
} else {
|
||||
// One cell contains a point, the other contains an edge or a point.
|
||||
assert(cell->contains_point() || cell2->contains_point());
|
||||
const Point &ipt = cell->contains_point() ?
|
||||
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) :
|
||||
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b);
|
||||
double t = detail::first_circle_segment_intersection_parameter(
|
||||
Vec2d(ipt.x(), ipt.y()), offset_distance, p0, p1 - p0);
|
||||
return Slic3r::lerp(p0, p1, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance, double discretization_error)
|
||||
{
|
||||
// Distance of a VD vertex to the closest site (input polygon edge or vertex).
|
||||
std::vector<double> vertex_dist(vd.num_vertices(), std::numeric_limits<double>::max());
|
||||
|
||||
// Minium distance of a VD edge to the closest site (input polygon edge or vertex).
|
||||
// For a parabolic segment the distance may be smaller than the distance of the two end points.
|
||||
std::vector<double> edge_dist(vd.num_edges(), std::numeric_limits<double>::max());
|
||||
|
||||
// Calculate minimum distance of input polygons to voronoi vertices and voronoi edges.
|
||||
for (const VD::edge_type &edge : vd.edges()) {
|
||||
const VD::vertex_type *v0 = edge.vertex0();
|
||||
const VD::vertex_type *v1 = edge.vertex1();
|
||||
const VD::cell_type *cell = edge.cell();
|
||||
const VD::cell_type *cell2 = edge.twin()->cell();
|
||||
const Line &line0 = lines[cell->source_index()];
|
||||
const Line &line1 = lines[cell2->source_index()];
|
||||
double d0, d1, dmin;
|
||||
if (v0 == nullptr || v1 == nullptr) {
|
||||
assert(edge.is_infinite());
|
||||
if (cell->contains_point() && cell2->contains_point()) {
|
||||
const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
|
||||
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
|
||||
d0 = d1 = std::numeric_limits<double>::max();
|
||||
if (v0 == nullptr && v1 == nullptr) {
|
||||
dmin = (pt1.cast<double>() - pt0.cast<double>()).norm();
|
||||
} else {
|
||||
Vec2d pt((pt0 + pt1).cast<double>() * 0.5);
|
||||
Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x()));
|
||||
Vec2d pt0d(pt0.x(), pt0.y());
|
||||
if (v0) {
|
||||
Vec2d a(v0->x(), v0->y());
|
||||
d0 = (a - pt0d).norm();
|
||||
dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d0;
|
||||
vertex_dist[v0 - &vd.vertices().front()] = d0;
|
||||
} else {
|
||||
Vec2d a(v1->x(), v1->y());
|
||||
d1 = (a - pt0d).norm();
|
||||
dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d1;
|
||||
vertex_dist[v1 - &vd.vertices().front()] = d1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Infinite edges could not be created by two segment sites.
|
||||
assert(cell->contains_point() != cell2->contains_point());
|
||||
// Linear edge goes through the endpoint of a segment.
|
||||
assert(edge.is_linear());
|
||||
assert(edge.is_secondary());
|
||||
#ifndef NDEBUG
|
||||
if (cell->contains_segment()) {
|
||||
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
|
||||
assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) ||
|
||||
(pt1.x() == line0.b.x() && pt1.y() == line0.b.y()));
|
||||
} else {
|
||||
const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
|
||||
assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) ||
|
||||
(pt0.x() == line1.b.x() && pt0.y() == line1.b.y()));
|
||||
}
|
||||
const Point &pt = cell->contains_segment() ?
|
||||
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) :
|
||||
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b);
|
||||
#endif /* NDEBUG */
|
||||
if (v0) {
|
||||
assert((Point(v0->x(), v0->y()) - pt).cast<double>().norm() < SCALED_EPSILON);
|
||||
d0 = dmin = 0.;
|
||||
vertex_dist[v0 - &vd.vertices().front()] = d0;
|
||||
} else {
|
||||
assert((Point(v1->x(), v1->y()) - pt).cast<double>().norm() < SCALED_EPSILON);
|
||||
d1 = dmin = 0.;
|
||||
vertex_dist[v1 - &vd.vertices().front()] = d1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Finite edge has valid points at both sides.
|
||||
if (cell->contains_segment() && cell2->contains_segment()) {
|
||||
// This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments.
|
||||
Vec2d pt(line0.a.cast<double>());
|
||||
Vec2d dir(line0.b.cast<double>() - pt);
|
||||
Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt;
|
||||
Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt;
|
||||
double l2 = dir.squaredNorm();
|
||||
assert(l2 > 0.);
|
||||
d0 = (dir * (vec0.dot(dir) / l2) - vec0).norm();
|
||||
d1 = (dir * (vec1.dot(dir) / l2) - vec1).norm();
|
||||
dmin = std::min(d0, d1);
|
||||
} else {
|
||||
assert(cell->contains_point() || cell2->contains_point());
|
||||
const Point &pt0 = cell->contains_point() ?
|
||||
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) :
|
||||
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b);
|
||||
// Project p0 to line segment <v0, v1>.
|
||||
Vec2d p0(v0->x(), v0->y());
|
||||
Vec2d p1(v1->x(), v1->y());
|
||||
Vec2d px(pt0.x(), pt0.y());
|
||||
Vec2d v = p1 - p0;
|
||||
d0 = (p0 - px).norm();
|
||||
d1 = (p1 - px).norm();
|
||||
double t = v.dot(px - p0);
|
||||
double l2 = v.squaredNorm();
|
||||
if (t > 0. && t < l2) {
|
||||
// Foot point on the line segment.
|
||||
Vec2d foot = p0 + (t / l2) * v;
|
||||
dmin = (foot - px).norm();
|
||||
} else
|
||||
dmin = std::min(d0, d1);
|
||||
}
|
||||
vertex_dist[v0 - &vd.vertices().front()] = d0;
|
||||
vertex_dist[v1 - &vd.vertices().front()] = d1;
|
||||
}
|
||||
edge_dist[&edge - &vd.edges().front()] = dmin;
|
||||
}
|
||||
|
||||
// Mark cells intersected by the offset curve.
|
||||
std::vector<unsigned char> seed_cells(vd.num_cells(), false);
|
||||
for (const VD::cell_type &cell : vd.cells()) {
|
||||
const VD::edge_type *first_edge = cell.incident_edge();
|
||||
const VD::edge_type *edge = first_edge;
|
||||
do {
|
||||
double dmin = edge_dist[edge - &vd.edges().front()];
|
||||
double dmax = std::numeric_limits<double>::max();
|
||||
const VD::vertex_type *v0 = edge->vertex0();
|
||||
const VD::vertex_type *v1 = edge->vertex1();
|
||||
if (v0 != nullptr)
|
||||
dmax = vertex_dist[v0 - &vd.vertices().front()];
|
||||
if (v1 != nullptr)
|
||||
dmax = std::max(dmax, vertex_dist[v1 - &vd.vertices().front()]);
|
||||
if (offset_distance >= dmin && offset_distance <= dmax) {
|
||||
// This cell is being intersected by the offset curve.
|
||||
seed_cells[&cell - &vd.cells().front()] = true;
|
||||
break;
|
||||
}
|
||||
edge = edge->next();
|
||||
} while (edge != first_edge);
|
||||
}
|
||||
|
||||
auto edge_dir = [&vd, &vertex_dist, &edge_dist, offset_distance](const VD::edge_type *edge) {
|
||||
const VD::vertex_type *v0 = edge->vertex0();
|
||||
const VD::vertex_type *v1 = edge->vertex1();
|
||||
double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits<double>::max();
|
||||
double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::max();
|
||||
if (d0 < offset_distance && offset_distance < d1)
|
||||
return true;
|
||||
else if (d1 < offset_distance && offset_distance < d0)
|
||||
return false;
|
||||
else {
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief starting at e, find the next edge on the face that brackets t
|
||||
///
|
||||
/// we can be in one of two modes.
|
||||
/// if direction==false then we are looking for an edge where src_t < t < trg_t
|
||||
/// if direction==true we are looning for an edge where trg_t < t < src_t
|
||||
auto next_offset_edge =
|
||||
[&vd, &vertex_dist, &edge_dist, offset_distance]
|
||||
(const VD::edge_type *start_edge, bool direction) -> const VD::edge_type* {
|
||||
const VD::edge_type *edge = start_edge;
|
||||
do {
|
||||
const VD::vertex_type *v0 = edge->vertex0();
|
||||
const VD::vertex_type *v1 = edge->vertex1();
|
||||
double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits<double>::max();
|
||||
double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::max();
|
||||
if (direction ? (d1 < offset_distance && offset_distance < d0) : (d0 < offset_distance && offset_distance < d1))
|
||||
return edge;
|
||||
edge = edge->next();
|
||||
} while (edge != start_edge);
|
||||
assert(false);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
auto dist_to_site = [&lines](const VD::cell_type &cell, const Vec2d &point) {
|
||||
const Line &line = lines[cell.source_index()];
|
||||
return cell.contains_point() ?
|
||||
(((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b).cast<double>() - point).norm() :
|
||||
line.distance_to(point.cast<coord_t>());
|
||||
};
|
||||
#endif /* NDEBUG */
|
||||
|
||||
// Track the offset curves.
|
||||
Polygons out;
|
||||
double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance);
|
||||
double sin_threshold = sin(angle_step) + EPSILON;
|
||||
for (size_t seed_cell_idx = 0; seed_cell_idx < vd.num_cells(); ++ seed_cell_idx)
|
||||
if (seed_cells[seed_cell_idx]) {
|
||||
seed_cells[seed_cell_idx] = false;
|
||||
// Initial direction should not matter, an offset curve shall intersect a cell at least at two points
|
||||
// (if it is not just touching the cell at a single vertex), and such two intersection points shall have
|
||||
// opposite direction.
|
||||
bool direction = false;
|
||||
// the first edge on the start-face
|
||||
const VD::cell_type &cell = vd.cells()[seed_cell_idx];
|
||||
const VD::edge_type *start_edge = next_offset_edge(cell.incident_edge(), direction);
|
||||
assert(start_edge->cell() == &cell);
|
||||
const VD::edge_type *edge = start_edge;
|
||||
Polygon poly;
|
||||
do {
|
||||
direction = edge_dir(edge);
|
||||
// find the next edge
|
||||
const VD::edge_type *next_edge = next_offset_edge(edge->next(), direction);
|
||||
//std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n";
|
||||
// Interpolate a circular segment or insert a linear segment between edge and next_edge.
|
||||
const VD::cell_type *cell = edge->cell();
|
||||
Vec2d p1 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *edge, offset_distance);
|
||||
Vec2d p2 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *next_edge, offset_distance);
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
double err = dist_to_site(*cell, p1) - offset_distance;
|
||||
assert(std::abs(err) < SCALED_EPSILON);
|
||||
err = dist_to_site(*cell, p2) - offset_distance;
|
||||
assert(std::abs(err) < SCALED_EPSILON);
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
if (cell->contains_point()) {
|
||||
// Discretize an arc from p1 to p2 with radius = offset_distance and discretization_error.
|
||||
// The arc should cover angle < PI.
|
||||
//FIXME we should be able to produce correctly oriented output curves based on the first edge taken!
|
||||
const Line &line0 = lines[cell->source_index()];
|
||||
const Vec2d ¢er = ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b).cast<double>();
|
||||
const Vec2d v1 = p1 - center;
|
||||
const Vec2d v2 = p2 - center;
|
||||
double orient = cross2(v1, v2);
|
||||
double orient_norm = v1.norm() * v2.norm();
|
||||
bool ccw = orient > 0;
|
||||
bool obtuse = v1.dot(v2) < 0.;
|
||||
if (! ccw)
|
||||
orient = - orient;
|
||||
assert(orient != 0.);
|
||||
if (obtuse || orient > orient_norm * sin_threshold) {
|
||||
// Angle is bigger than the threshold, therefore the arc will be discretized.
|
||||
double angle = asin(orient / orient_norm);
|
||||
if (obtuse)
|
||||
angle = M_PI - angle;
|
||||
size_t n_steps = size_t(ceil(angle / angle_step));
|
||||
double astep = angle / n_steps;
|
||||
if (! ccw)
|
||||
astep *= -1.;
|
||||
double a = astep;
|
||||
for (size_t i = 1; i < n_steps; ++ i, a += astep) {
|
||||
double c = cos(a);
|
||||
double s = sin(a);
|
||||
Vec2d p = center + Vec2d(c * v1.x() - s * v1.y(), s * v1.x() + c * v1.y());
|
||||
poly.points.emplace_back(Point(coord_t(p.x()), coord_t(p.y())));
|
||||
}
|
||||
}
|
||||
}
|
||||
poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y())));
|
||||
// although we may revisit current_face (if it is non-convex), it seems safe to mark it "done" here.
|
||||
seed_cells[cell - &vd.cells().front()] = false;
|
||||
edge = next_edge->twin();
|
||||
} while (edge != start_edge);
|
||||
out.emplace_back(std::move(poly));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
14
src/libslic3r/VoronoiOffset.hpp
Normal file
14
src/libslic3r/VoronoiOffset.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef slic3r_VoronoiOffset_hpp_
|
||||
#define slic3r_VoronoiOffset_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include "Geometry.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Polygons voronoi_offset(const Geometry::VoronoiDiagram &vd, const Lines &lines, double offset_distance, double discretization_error);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_VoronoiOffset_hpp_
|
||||
Loading…
Add table
Add a link
Reference in a new issue