mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-28 19:21:20 -06:00
Added new tech ENABLE_GCODE_VIEWER_AS_STATE -> GCodeViewer as a new application state (WIP) + fix of conflicts after merge with master
This commit is contained in:
commit
70a6fb0e20
19 changed files with 1209 additions and 252 deletions
|
|
@ -190,6 +190,8 @@ add_library(libslic3r STATIC
|
|||
Time.cpp
|
||||
Time.hpp
|
||||
MTUtils.hpp
|
||||
VoronoiOffset.cpp
|
||||
VoronoiOffset.hpp
|
||||
Zipper.hpp
|
||||
Zipper.cpp
|
||||
MinAreaBoundingBox.hpp
|
||||
|
|
|
|||
|
|
@ -48,15 +48,13 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||
size_t extruders_count = config.nozzle_diameter.values.size();
|
||||
|
||||
m_extruder_offsets.resize(extruders_count);
|
||||
for (size_t id = 0; id < extruders_count; ++id)
|
||||
{
|
||||
for (size_t id = 0; id < extruders_count; ++id) {
|
||||
Vec2f offset = config.extruder_offset.get_at(id).cast<float>();
|
||||
m_extruder_offsets[id] = Vec3f(offset(0), offset(1), 0.0f);
|
||||
}
|
||||
|
||||
m_extruders_color.resize(extruders_count);
|
||||
for (size_t id = 0; id < extruders_count; ++id)
|
||||
{
|
||||
for (size_t id = 0; id < extruders_count; ++id) {
|
||||
m_extruders_color[id] = static_cast<unsigned int>(id);
|
||||
}
|
||||
}
|
||||
|
|
@ -104,8 +102,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
|
|||
m_start_position = m_end_position;
|
||||
|
||||
std::string cmd = line.cmd();
|
||||
if (cmd.length() > 1)
|
||||
{
|
||||
if (cmd.length() > 1) {
|
||||
// process command lines
|
||||
switch (::toupper(cmd[0]))
|
||||
{
|
||||
|
|
@ -163,8 +160,7 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
{
|
||||
// extrusion role tag
|
||||
size_t pos = comment.find(Extrusion_Role_Tag);
|
||||
if (pos != comment.npos)
|
||||
{
|
||||
if (pos != comment.npos) {
|
||||
try
|
||||
{
|
||||
int role = std::stoi(comment.substr(pos + Extrusion_Role_Tag.length()));
|
||||
|
|
@ -185,8 +181,7 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
|
||||
// width tag
|
||||
pos = comment.find(Width_Tag);
|
||||
if (pos != comment.npos)
|
||||
{
|
||||
if (pos != comment.npos) {
|
||||
try
|
||||
{
|
||||
m_width = std::stof(comment.substr(pos + Width_Tag.length()));
|
||||
|
|
@ -200,8 +195,7 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
|
||||
// height tag
|
||||
pos = comment.find(Height_Tag);
|
||||
if (pos != comment.npos)
|
||||
{
|
||||
if (pos != comment.npos) {
|
||||
try
|
||||
{
|
||||
m_height = std::stof(comment.substr(pos + Height_Tag.length()));
|
||||
|
|
@ -215,8 +209,7 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
|
||||
// mm3 per mm tag
|
||||
pos = comment.find(Mm3_Per_Mm_Tag);
|
||||
if (pos != comment.npos)
|
||||
{
|
||||
if (pos != comment.npos) {
|
||||
try
|
||||
{
|
||||
m_mm3_per_mm = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length()));
|
||||
|
|
@ -230,8 +223,7 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
|
||||
// color change tag
|
||||
pos = comment.find(Color_Change_Tag);
|
||||
if (pos != comment.npos)
|
||||
{
|
||||
if (pos != comment.npos) {
|
||||
pos = comment.find_last_of(",T");
|
||||
try
|
||||
{
|
||||
|
|
@ -258,16 +250,14 @@ void GCodeProcessor::process_tags(const std::string& comment)
|
|||
|
||||
// pause print tag
|
||||
pos = comment.find(Pause_Print_Tag);
|
||||
if (pos != comment.npos)
|
||||
{
|
||||
if (pos != comment.npos) {
|
||||
store_move_vertex(EMoveType::Pause_Print);
|
||||
return;
|
||||
}
|
||||
|
||||
// custom code tag
|
||||
pos = comment.find(Custom_Code_Tag);
|
||||
if (pos != comment.npos)
|
||||
{
|
||||
if (pos != comment.npos) {
|
||||
store_move_vertex(EMoveType::Custom_GCode);
|
||||
return;
|
||||
}
|
||||
|
|
@ -281,8 +271,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
if (axis == E)
|
||||
is_relative |= (m_e_local_positioning_type == EPositioningType::Relative);
|
||||
|
||||
if (lineG1.has(Slic3r::Axis(axis)))
|
||||
{
|
||||
if (lineG1.has(Slic3r::Axis(axis))) {
|
||||
float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
|
||||
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
|
||||
return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret;
|
||||
|
|
@ -294,32 +283,43 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
auto move_type = [this](const AxisCoords& delta_pos) {
|
||||
EMoveType type = EMoveType::Noop;
|
||||
|
||||
if (delta_pos[E] < 0.0f)
|
||||
{
|
||||
if (delta_pos[E] < 0.0f) {
|
||||
if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f)
|
||||
type = EMoveType::Travel;
|
||||
else
|
||||
type = EMoveType::Retract;
|
||||
}
|
||||
else if (delta_pos[E] > 0.0f)
|
||||
{
|
||||
} else if (delta_pos[E] > 0.0f) {
|
||||
if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f)
|
||||
type = EMoveType::Unretract;
|
||||
else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f))
|
||||
type = EMoveType::Extrude;
|
||||
}
|
||||
else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f)
|
||||
} else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f)
|
||||
type = EMoveType::Travel;
|
||||
|
||||
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
#if ENABLE_GCODE_VIEWER_AS_STATE
|
||||
if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f))
|
||||
{
|
||||
if (m_extrusion_role != erCustom)
|
||||
{
|
||||
m_width = 0.5f;
|
||||
m_height = 0.5f;
|
||||
}
|
||||
type = EMoveType::Travel;
|
||||
}
|
||||
#else
|
||||
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f || !is_valid_extrusion_role(m_extrusion_role)))
|
||||
type = EMoveType::Travel;
|
||||
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
#endif // ENABLE_GCODE_VIEWER_AS_STATE
|
||||
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
|
||||
return type;
|
||||
};
|
||||
|
||||
// updates axes positions from line
|
||||
for (unsigned char a = X; a <= E; ++a)
|
||||
{
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
m_end_position[a] = absolute_position((Axis)a, line);
|
||||
}
|
||||
|
||||
|
|
@ -330,8 +330,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
// calculates movement deltas
|
||||
float max_abs_delta = 0.0f;
|
||||
AxisCoords delta_pos;
|
||||
for (unsigned char a = X; a <= E; ++a)
|
||||
{
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
delta_pos[a] = m_end_position[a] - m_start_position[a];
|
||||
max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a]));
|
||||
}
|
||||
|
|
@ -383,38 +382,32 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line)
|
|||
float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
|
||||
bool anyFound = false;
|
||||
|
||||
if (line.has_x())
|
||||
{
|
||||
if (line.has_x()) {
|
||||
m_origin[X] = m_end_position[X] - line.x() * lengthsScaleFactor;
|
||||
anyFound = true;
|
||||
}
|
||||
|
||||
if (line.has_y())
|
||||
{
|
||||
if (line.has_y()) {
|
||||
m_origin[Y] = m_end_position[Y] - line.y() * lengthsScaleFactor;
|
||||
anyFound = true;
|
||||
}
|
||||
|
||||
if (line.has_z())
|
||||
{
|
||||
if (line.has_z()) {
|
||||
m_origin[Z] = m_end_position[Z] - line.z() * lengthsScaleFactor;
|
||||
anyFound = true;
|
||||
}
|
||||
|
||||
if (line.has_e())
|
||||
{
|
||||
if (line.has_e()) {
|
||||
// extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments,
|
||||
// we set the value taken from the G92 line as the new current position for it
|
||||
m_end_position[E] = line.e() * lengthsScaleFactor;
|
||||
anyFound = true;
|
||||
}
|
||||
|
||||
if (!anyFound && !line.has_unknown_axis())
|
||||
{
|
||||
if (!anyFound && !line.has_unknown_axis()) {
|
||||
// The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510,
|
||||
// where G92 A0 B0 is called although the extruder axis is till E.
|
||||
for (unsigned char a = X; a <= E; ++a)
|
||||
{
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
m_origin[a] = m_end_position[a];
|
||||
}
|
||||
}
|
||||
|
|
@ -432,8 +425,7 @@ void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line)
|
|||
|
||||
void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
if (!line.has('P'))
|
||||
{
|
||||
if (!line.has('P')) {
|
||||
// The absence of P means the print cooling fan, so ignore anything else.
|
||||
float new_fan_speed;
|
||||
if (line.has_value('S', new_fan_speed))
|
||||
|
|
@ -502,8 +494,7 @@ void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line)
|
|||
if (m_flavor != gcfRepetier)
|
||||
return;
|
||||
|
||||
for (unsigned char a = 0; a <= 3; ++a)
|
||||
{
|
||||
for (unsigned char a = 0; a <= 3; ++a) {
|
||||
m_cached_position.position[a] = m_start_position[a];
|
||||
}
|
||||
m_cached_position.feedrate = m_feedrate;
|
||||
|
|
@ -521,10 +512,8 @@ void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line)
|
|||
bool has_xyz = !(line.has_x() || line.has_y() || line.has_z());
|
||||
|
||||
float p = FLT_MAX;
|
||||
for (unsigned char a = X; a <= Z; ++a)
|
||||
{
|
||||
if (has_xyz || line.has(a))
|
||||
{
|
||||
for (unsigned char a = X; a <= Z; ++a) {
|
||||
if (has_xyz || line.has(a)) {
|
||||
p = m_cached_position.position[a];
|
||||
if (p != FLT_MAX)
|
||||
m_start_position[a] = p;
|
||||
|
|
@ -550,18 +539,15 @@ void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line)
|
|||
|
||||
void GCodeProcessor::process_T(const std::string& command)
|
||||
{
|
||||
if (command.length() > 1)
|
||||
{
|
||||
if (command.length() > 1) {
|
||||
try
|
||||
{
|
||||
unsigned char id = static_cast<unsigned char>(std::stoi(command.substr(1)));
|
||||
if (m_extruder_id != id)
|
||||
{
|
||||
if (m_extruder_id != id) {
|
||||
unsigned char extruders_count = static_cast<unsigned char>(m_extruder_offsets.size());
|
||||
if (id >= extruders_count)
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode.";
|
||||
else
|
||||
{
|
||||
else {
|
||||
m_extruder_id = id;
|
||||
m_cp_color.current = m_extruders_color[id];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -48,10 +48,16 @@
|
|||
// 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)
|
||||
|
||||
// Enable G-Code viewer
|
||||
#define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1)
|
||||
#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER)
|
||||
#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (1 && ENABLE_GCODE_VIEWER)
|
||||
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
#define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER)
|
||||
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
|
||||
|
||||
#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