Merge remote-tracking branch 'origin/master' into custom_gcodes

This commit is contained in:
YuSanka 2020-06-08 12:27:29 +02:00
commit befbd6b0fe
35 changed files with 821 additions and 262 deletions

View file

@ -188,6 +188,8 @@ add_library(libslic3r STATIC
Time.cpp
Time.hpp
MTUtils.hpp
VoronoiOffset.cpp
VoronoiOffset.hpp
Zipper.hpp
Zipper.cpp
MinAreaBoundingBox.hpp

View file

@ -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);

View file

@ -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 &lt = 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 &lt_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.

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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();

View file

@ -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;

View file

@ -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"

View file

@ -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_

View 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 &center, 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 &center = ((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

View 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_

View file

@ -40,12 +40,19 @@
#include <Eigen/Dense>
#ifdef HAS_GLSAFE
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name)
{
#if defined(NDEBUG) && ENABLE_OPENGL_ERROR_LOGGING
// In release mode, if OpenGL debugging was forced by ENABLE_OPENGL_ERROR_LOGGING, only show
// OpenGL errors if sufficiently high loglevel.
if (Slic3r::get_logging_level() < 5)
return;
#endif // ENABLE_OPENGL_ERROR_LOGGING
GLenum err = glGetError();
if (err == GL_NO_ERROR)
return;
const char *sErr = 0;
const char* sErr = 0;
switch (err) {
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
@ -56,10 +63,10 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
default: sErr = "Unknown"; break;
}
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
assert(false);
}
#endif
#endif // HAS_GLSAFE
namespace Slic3r {

View file

@ -10,20 +10,20 @@
#include <functional>
#ifndef NDEBUG
#define HAS_GLSAFE
#if ENABLE_OPENGL_ERROR_LOGGING || ! defined(NDEBUG)
#define HAS_GLSAFE
#endif
#ifdef HAS_GLSAFE
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#else
inline void glAssertRecentCall() { }
#define glsafe(cmd) cmd
#define glcheck()
#endif
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#else // HAS_GLSAFE
inline void glAssertRecentCall() { }
#define glsafe(cmd) cmd
#define glcheck()
#endif // HAS_GLSAFE
namespace Slic3r {
namespace GUI {

View file

@ -1534,9 +1534,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
, m_retina_helper(nullptr)
#endif
, m_in_render(false)
, m_main_toolbar(GLToolbar::Normal, "Top")
, m_undoredo_toolbar(GLToolbar::Normal, "Top")
, m_collapse_toolbar(GLToolbar::Normal, "Top")
, m_main_toolbar(GLToolbar::Normal, "Main")
, m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo")
, m_gizmos(*this)
, m_use_clipping_planes(false)
, m_sidebar_field("")
@ -1914,11 +1913,6 @@ void GLCanvas3D::enable_undoredo_toolbar(bool enable)
m_undoredo_toolbar.set_enabled(enable);
}
void GLCanvas3D::enable_collapse_toolbar(bool enable)
{
m_collapse_toolbar.set_enabled(enable);
}
void GLCanvas3D::enable_dynamic_background(bool enable)
{
m_dynamic_background_enabled = enable;
@ -2112,7 +2106,7 @@ void GLCanvas3D::render()
tooltip = m_undoredo_toolbar.get_tooltip();
if (tooltip.empty())
tooltip = m_collapse_toolbar.get_tooltip();
tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip();
if (tooltip.empty())
tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip();
@ -2854,8 +2848,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_dirty |= m_main_toolbar.update_items_state();
m_dirty |= m_undoredo_toolbar.update_items_state();
m_dirty |= m_collapse_toolbar.update_items_state();
m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
m_dirty |= mouse3d_controller_applied;
@ -3473,7 +3467,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
return;
}
if (m_collapse_toolbar.on_mouse(evt, *this))
if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this))
{
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
@ -4187,7 +4181,7 @@ void GLCanvas3D::update_ui_from_settings()
#endif // ENABLE_RETINA_GL
bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1";
enable_collapse_toolbar(enable_collapse);
wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse);
}
@ -5055,51 +5049,7 @@ bool GLCanvas3D::_init_view_toolbar()
bool GLCanvas3D::_init_collapse_toolbar()
{
if (!m_collapse_toolbar.is_enabled() && m_collapse_toolbar.get_items_count() > 0)
return true;
BackgroundTexture::Metadata background_data;
background_data.filename = "toolbar_background.png";
background_data.left = 16;
background_data.top = 16;
background_data.right = 16;
background_data.bottom = 16;
if (!m_collapse_toolbar.init(background_data))
{
// unable to init the toolbar texture, disable it
m_collapse_toolbar.set_enabled(false);
return true;
}
m_collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
m_collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right);
m_collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
m_collapse_toolbar.set_border(5.0f);
m_collapse_toolbar.set_separator_size(5);
m_collapse_toolbar.set_gap_size(2);
GLToolbarItem::Data item;
item.name = "collapse_sidebar";
item.icon_filename = "collapse.svg";
item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel"));
item.sprite_id = 0;
item.left.action_callback = [this, item]() {
std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ?
_utf8(L("Collapse right panel")) : _utf8(L("Expand right panel"));
int id = m_collapse_toolbar.get_item_id("collapse_sidebar");
m_collapse_toolbar.set_tooltip(id, new_tooltip);
set_tooltip("");
wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed());
};
if (!m_collapse_toolbar.add_item(item))
return false;
return true;
return wxGetApp().plater()->init_collapse_toolbar();
}
bool GLCanvas3D::_set_current()
@ -5427,20 +5377,21 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const
float size = GLToolbar::Default_Icons_Size * scale;
// Set current size for all top toolbars. It will be used for next calculations
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
#if ENABLE_RETINA_GL
const float sc = m_retina_helper->get_scale_factor() * scale;
m_main_toolbar.set_scale(sc);
m_undoredo_toolbar.set_scale(sc);
m_collapse_toolbar.set_scale(sc);
collapse_toolbar.set_scale(sc);
size *= m_retina_helper->get_scale_factor();
#else
m_main_toolbar.set_icons_size(size);
m_undoredo_toolbar.set_icons_size(size);
m_collapse_toolbar.set_icons_size(size);
collapse_toolbar.set_icons_size(size);
#endif // ENABLE_RETINA_GL
float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + m_collapse_toolbar.get_width();
int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + m_collapse_toolbar.get_visible_items_cnt();
float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width();
int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt();
float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars
// calculate scale needed for items in all top toolbars
@ -5460,7 +5411,6 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const
wxGetApp().set_auto_toolbar_icon_scale(new_scale);
}
void GLCanvas3D::_render_overlays() const
{
glsafe(::glDisable(GL_DEPTH_TEST));
@ -5485,12 +5435,12 @@ void GLCanvas3D::_render_overlays() const
const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/);
m_main_toolbar.set_scale(scale);
m_undoredo_toolbar.set_scale(scale);
m_collapse_toolbar.set_scale(scale);
wxGetApp().plater()->get_collapse_toolbar().set_scale(scale);
#else
const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/));
m_main_toolbar.set_icons_size(size);
m_undoredo_toolbar.set_icons_size(size);
m_collapse_toolbar.set_icons_size(size);
wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size);
#endif // ENABLE_RETINA_GL
_render_main_toolbar();
@ -5594,7 +5544,8 @@ void GLCanvas3D::_render_main_toolbar() const
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f;
const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f;
float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom;
m_main_toolbar.set_position(top, left);
@ -5610,7 +5561,8 @@ void GLCanvas3D::_render_undoredo_toolbar() const
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f;
const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f;
float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom;
m_undoredo_toolbar.set_position(top, left);
m_undoredo_toolbar.render(*this);
@ -5618,8 +5570,7 @@ void GLCanvas3D::_render_undoredo_toolbar() const
void GLCanvas3D::_render_collapse_toolbar() const
{
if (!m_collapse_toolbar.is_enabled())
return;
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
Size cnv_size = get_canvas_size();
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
@ -5627,10 +5578,10 @@ void GLCanvas3D::_render_collapse_toolbar() const
float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0;
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
float left = (0.5f * (float)cnv_size.get_width() - (float)m_collapse_toolbar.get_width() - band) * inv_zoom;
float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom;
m_collapse_toolbar.set_position(top, left);
m_collapse_toolbar.render(*this);
collapse_toolbar.set_position(top, left);
collapse_toolbar.render(*this);
}
void GLCanvas3D::_render_view_toolbar() const
@ -7164,9 +7115,10 @@ bool GLCanvas3D::_activate_search_toolbar_item()
bool GLCanvas3D::_deactivate_collapse_toolbar_items()
{
if (m_collapse_toolbar.is_item_pressed("print"))
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
if (collapse_toolbar.is_item_pressed("print"))
{
m_collapse_toolbar.force_left_action(m_collapse_toolbar.get_item_id("print"), *this);
collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this);
return true;
}

View file

@ -452,7 +452,6 @@ private:
mutable GLGizmosManager m_gizmos;
mutable GLToolbar m_main_toolbar;
mutable GLToolbar m_undoredo_toolbar;
mutable GLToolbar m_collapse_toolbar;
ClippingPlane m_clipping_planes[2];
mutable ClippingPlane m_camera_clipping_plane;
bool m_use_clipping_planes;
@ -588,7 +587,6 @@ public:
void enable_selection(bool enable);
void enable_main_toolbar(bool enable);
void enable_undoredo_toolbar(bool enable);
void enable_collapse_toolbar(bool enable);
void enable_dynamic_background(bool enable);
void enable_labels(bool enable) { m_labels.enable(enable); }
#if ENABLE_SLOPE_RENDERING

View file

@ -1238,7 +1238,7 @@ bool GLToolbar::generate_icons_texture() const
}
std::vector<std::pair<int, bool>> states;
if (m_name == "Top")
if (m_type == Normal)
{
states.push_back({ 1, false }); // Normal
states.push_back({ 0, false }); // Pressed
@ -1247,7 +1247,7 @@ bool GLToolbar::generate_icons_texture() const
states.push_back({ 0, false }); // HoverPressed
states.push_back({ 2, false }); // HoverDisabled
}
else if (m_name == "View")
else
{
states.push_back({ 1, false }); // Normal
states.push_back({ 1, true }); // Pressed

View file

@ -2506,7 +2506,6 @@ void ObjectList::merge(bool to_multipart_object)
// all objects, created from the instances will be added to the end of list
int new_objects_cnt = 0; // count of this new objects
// std::vector<int> obj_idxs;
for (auto map_item : sel_map)
{
@ -2569,22 +2568,45 @@ void ObjectList::merge(bool to_multipart_object)
new_object->name = _u8L("Merged");
DynamicPrintConfig* config = &new_object->config;
int frst_obj_idx = obj_idxs.front();
const Vec3d& main_offset = (*m_objects)[frst_obj_idx]->instances[0]->get_offset();
for (int obj_idx : obj_idxs)
{
ModelObject* object = (*m_objects)[obj_idx];
Vec3d offset = object->instances[0]->get_offset();
if (object->id() == (*m_objects)[frst_obj_idx]->id())
new_object->add_instance(*object->instances[0]);
const Geometry::Transformation& transformation = object->instances[0]->get_transformation();
Vec3d scale = transformation.get_scaling_factor();
Vec3d mirror = transformation.get_mirror();
Vec3d rotation = transformation.get_rotation();
if (object->id() == (*m_objects)[obj_idxs.front()]->id())
new_object->add_instance();
Transform3d volume_offset_correction = new_object->instances[0]->get_transformation().get_matrix().inverse() * transformation.get_matrix();
// merge volumes
for (const ModelVolume* volume : object->volumes) {
ModelVolume* new_volume = new_object->add_volume(*volume);
//set rotation
Vec3d vol_rot = new_volume->get_rotation() + rotation;
new_volume->set_rotation(vol_rot);
// set scale
Vec3d vol_sc_fact = new_volume->get_scaling_factor().cwiseProduct(scale);
new_volume->set_scaling_factor(vol_sc_fact);
// set mirror
Vec3d vol_mirror = new_volume->get_mirror().cwiseProduct(mirror);
new_volume->set_mirror(vol_mirror);
// set offset
Vec3d vol_offset = volume_offset_correction* new_volume->get_offset();
new_volume->set_offset(vol_offset);
}
// merge settings
auto new_opt_keys = config->keys();
const DynamicPrintConfig& from_config = object->config;
auto opt_keys = from_config.keys();
// merge settings
for (auto& opt_key : opt_keys) {
if (find(new_opt_keys.begin(), new_opt_keys.end(), opt_key) == new_opt_keys.end()) {
const ConfigOption* option = from_config.option(opt_key);
@ -2596,13 +2618,6 @@ void ObjectList::merge(bool to_multipart_object)
config->set_key_value(opt_key, option->clone());
}
}
// merge volumes
for (const ModelVolume* volume : object->volumes) {
ModelVolume* new_volume = new_object->add_volume(*volume);
Vec3d vol_offset = offset - main_offset + new_volume->get_offset();
new_volume->set_offset(vol_offset);
}
// save extruder value if it was set
if (object->volumes.size() == 1 && find(opt_keys.begin(), opt_keys.end(), "extruder") != opt_keys.end()) {
ModelVolume* volume = new_object->volumes.back();

View file

@ -13,6 +13,8 @@
#include "wxExtensions.hpp"
#include "ObjectDataViewModel.hpp"
#include "libslic3r/PrintConfig.hpp"
class wxBoxSizer;
class wxBitmapComboBox;
class wxMenuItem;

View file

@ -68,7 +68,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba
m_canvas->enable_selection(true);
m_canvas->enable_main_toolbar(true);
m_canvas->enable_undoredo_toolbar(true);
m_canvas->enable_collapse_toolbar(true);
m_canvas->enable_labels(true);
#if ENABLE_SLOPE_RENDERING
m_canvas->enable_slope(true);
@ -222,7 +221,6 @@ bool Preview::init(wxWindow* parent, Model* model)
m_canvas->set_process(m_process);
m_canvas->enable_legend_texture(true);
m_canvas->enable_dynamic_background(true);
m_canvas->enable_collapse_toolbar(true);
m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL);
create_double_slider();

View file

@ -54,18 +54,35 @@ bool GLGizmoFdmSupports::on_init()
return true;
}
void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate)
{
if (activate && ! m_internal_stack_active) {
Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on"));
wxGetApp().plater()->enter_gizmos_stack();
m_internal_stack_active = true;
}
if (! activate && m_internal_stack_active) {
wxGetApp().plater()->leave_gizmos_stack();
Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off"));
m_internal_stack_active = false;
}
}
void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection)
{
const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
if (! mo)
if (m_state != On)
return;
const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
if (mo && selection.is_from_single_instance()
&& (mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
&& (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
{
update_from_model_object();
m_old_mo_id = mo->id();
m_old_volumes_size = mo->volumes.size();
m_schedule_update = false;
}
}
@ -131,8 +148,10 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
// Now render both enforcers and blockers.
for (int i=0; i<2; ++i) {
glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f));
for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i])
iva.render();
for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) {
if (iva.has_VBOs())
iva.render();
}
}
glsafe(::glPopMatrix());
if (is_left_handed)
@ -493,6 +512,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
: (m_button_down == Button::Left
? _L("Add supports")
: _L("Block supports"));
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot(wxGetApp().plater(), action_name);
update_model_object();
@ -588,6 +608,8 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr
update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER);
}
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
: _L("Add supports by angle"));
update_model_object();
@ -778,12 +800,9 @@ void GLGizmoFdmSupports::on_set_state()
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
{
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned on")));
}
if (! m_parent.get_gizmos_manager().is_serializing()) {
wxGetApp().CallAfter([]() {
wxGetApp().plater()->enter_gizmos_stack();
wxGetApp().CallAfter([this]() {
activate_internal_undo_redo_stack(true);
});
}
}
@ -793,11 +812,7 @@ void GLGizmoFdmSupports::on_set_state()
m_setting_angle = false;
m_parent.use_slope(false);
}
wxGetApp().plater()->leave_gizmos_stack();
{
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned off")));
}
activate_internal_undo_redo_stack(false);
m_old_mo_id = -1;
m_ivas.clear();
m_selected_facets.clear();
@ -820,14 +835,19 @@ void GLGizmoFdmSupports::on_stop_dragging()
void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive& ar)
void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive&)
{
update_from_model_object();
// We should update the gizmo from current ModelObject, but it is not
// possible at this point. That would require having updated selection and
// common gizmos data, which is not done at this point. Instead, save
// a flag to do the update in set_fdm_support_data, which will be called
// soon after.
m_schedule_update = true;
}
void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive& ar) const
void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const
{
}

View file

@ -64,6 +64,7 @@ private:
void update_model_object() const;
void update_from_model_object();
void activate_internal_undo_redo_stack(bool activate);
void select_facets_by_angle(float threshold, bool overwrite, bool block);
bool m_overwrite_selected = false;
@ -74,6 +75,8 @@ private:
float m_clipping_plane_distance = 0.f;
std::unique_ptr<ClippingPlane> m_clipping_plane;
bool m_setting_angle = false;
bool m_internal_stack_active = false;
bool m_schedule_update = false;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.

View file

@ -28,8 +28,7 @@ bool GLGizmoFlatten::on_init()
void GLGizmoFlatten::on_set_state()
{
if (m_state == On && is_plane_update_necessary())
update_planes();
}
CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
@ -81,7 +80,8 @@ void GLGizmoFlatten::on_render() const
else
glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.5f));
m_planes[i].vbo.render();
if (m_planes[i].vbo.has_VBOs())
m_planes[i].vbo.render();
}
glsafe(::glPopMatrix());
}

View file

@ -59,7 +59,7 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&)
return;
const ModelObject* mo = m_c->selection_info()->model_object();
if (mo) {
if (m_state == On && mo) {
if (m_old_mo_id != mo->id()) {
reload_cache();
m_old_mo_id = mo->id();
@ -810,11 +810,6 @@ void GLGizmoHollow::on_set_state()
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
// we'll now reload support points:
if (m_c->selection_info()->model_object())
reload_cache();
}
if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
m_old_state = m_state;

View file

@ -67,10 +67,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
ModelObject* mo = m_c->selection_info()->model_object();
if (mo && mo->id() != m_old_mo_id) {
if (m_state == On && mo && mo->id() != m_old_mo_id) {
disable_editing_mode();
reload_cache();
m_old_mo_id = mo->id();
m_c->instances_hider()->show_supports(true);
}
// If we triggered autogeneration before, check backend and fetch results if they are there
@ -884,25 +885,23 @@ CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const
void GLGizmoSlaSupports::on_set_state()
{
const ModelObject* mo = m_c->selection_info()->model_object();
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on")));
// This function can be called from undo/redo, when selection (and hence
// common gizmos data are not yet deserialized. The CallAfter should put
// this off until after the update is done.
wxGetApp().CallAfter([this]() {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on")));
// we'll now reload support points:
if (mo)
reload_cache();
// Set default head diameter from config.
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
m_c->instances_hider()->show_supports(true);
// Set default head diameter from config.
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
});
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
bool will_ask = mo && m_editing_mode && unsaved_changes();
bool will_ask = m_editing_mode && unsaved_changes();
if (will_ask) {
wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem
@ -922,7 +921,7 @@ void GLGizmoSlaSupports::on_set_state()
disable_editing_mode(); // so it is not active next time the gizmo opens
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off")));
m_normal_cache.clear();
m_old_mo_id = -1;
}
}
m_old_state = m_state;

View file

@ -8,6 +8,8 @@
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include <GL/glew.h>
namespace Slic3r {
@ -170,7 +172,8 @@ void InstancesHider::show_supports(bool show) {
void HollowedMesh::on_update()
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
if (! mo)
bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
if (! mo || ! is_sla)
return;
const GLCanvas3D* canvas = get_pool()->get_canvas();
@ -376,7 +379,8 @@ void ObjectClipper::set_position(double pos, bool keep_normal)
void SupportsClipper::on_update()
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
if (! mo)
bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
if (! mo || ! is_sla)
return;
const GLCanvas3D* canvas = get_pool()->get_canvas();

View file

@ -1104,9 +1104,16 @@ void GLGizmosManager::activate_gizmo(EType type)
}
m_current = type;
m_common_gizmos_data->update(get_current()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
// Updating common data should be left to the update_data function, which
// is always called after this one. activate_gizmo can be called by undo/redo,
// when selection is not yet deserialized, so the common data would update
// incorrectly (or crash if relying on unempty selection). Undo/redo stack
// will also call update_data, after selection is restored.
//m_common_gizmos_data->update(get_current()
// ? get_current()->get_requirements()
// : CommonGizmosDataID(0));
if (type != Undefined)
m_gizmos[type]->set_state(GLGizmoBase::On);

View file

@ -141,11 +141,6 @@ public:
EType new_current = m_current;
m_current = old_current;
// Update common data. They should be updated when activate_gizmo is
// called, so it can be used in on_set_state which is called from there.
if (new_current != Undefined)
m_common_gizmos_data->update(m_gizmos[new_current]->get_requirements());
// activate_gizmo call sets m_current and calls set_state for the gizmo
// it does nothing in case the gizmo is already activated
// it can safely be called for Undefined gizmo

View file

@ -329,7 +329,7 @@ void MainFrame::init_tabpanel()
Tab* tab = dynamic_cast<Tab*>(panel);
// There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology
if (panel == nullptr || (tab && tab->supports_printer_technology(m_plater->printer_technology())))
if (panel == nullptr || (tab && ! tab->supports_printer_technology(m_plater->printer_technology())))
return;
auto& tabs_list = wxGetApp().tabs_list;

View file

@ -49,7 +49,8 @@ void MeshClipper::render_cut()
if (! m_triangles_valid)
recalculate_triangles();
m_vertex_array.render();
if (m_vertex_array.has_VBOs())
m_vertex_array.render();
}

View file

@ -1589,6 +1589,7 @@ struct Plater::priv
Mouse3DController mouse3d_controller;
View3D* view3D;
GLToolbar view_toolbar;
GLToolbar collapse_toolbar;
Preview *preview;
BackgroundSlicingProcess background_process;
@ -1683,6 +1684,7 @@ struct Plater::priv
void reset_canvas_volumes();
bool init_view_toolbar();
bool init_collapse_toolbar();
void reset_all_gizmos();
void update_ui_from_settings();
@ -1878,6 +1880,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
, m_ui_jobs(this)
, delayed_scene_refresh(false)
, view_toolbar(GLToolbar::Radio, "View")
, collapse_toolbar(GLToolbar::Normal, "Collapse")
, m_project_filename(wxEmptyString)
{
this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
@ -3922,6 +3925,51 @@ bool Plater::priv::init_view_toolbar()
return true;
}
bool Plater::priv::init_collapse_toolbar()
{
if (collapse_toolbar.get_items_count() > 0)
// already initialized
return true;
BackgroundTexture::Metadata background_data;
background_data.filename = "toolbar_background.png";
background_data.left = 16;
background_data.top = 16;
background_data.right = 16;
background_data.bottom = 16;
if (!collapse_toolbar.init(background_data))
return false;
collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right);
collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
collapse_toolbar.set_border(5.0f);
collapse_toolbar.set_separator_size(5);
collapse_toolbar.set_gap_size(2);
GLToolbarItem::Data item;
item.name = "collapse_sidebar";
item.icon_filename = "collapse.svg";
item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel"));
item.sprite_id = 0;
item.left.action_callback = [this, item]() {
std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ?
_utf8(L("Collapse right panel")) : _utf8(L("Expand right panel"));
int id = collapse_toolbar.get_item_id("collapse_sidebar");
collapse_toolbar.set_tooltip(id, new_tooltip);
wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed());
};
if (!collapse_toolbar.add_item(item))
return false;
return true;
}
bool Plater::priv::can_set_instance_to_object() const
{
const int obj_idx = get_selected_object_idx();
@ -5531,6 +5579,11 @@ bool Plater::init_view_toolbar()
return p->init_view_toolbar();
}
bool Plater::init_collapse_toolbar()
{
return p->init_collapse_toolbar();
}
const Camera& Plater::get_camera() const
{
return p->camera;
@ -5574,6 +5627,16 @@ GLToolbar& Plater::get_view_toolbar()
return p->view_toolbar;
}
const GLToolbar& Plater::get_collapse_toolbar() const
{
return p->collapse_toolbar;
}
GLToolbar& Plater::get_collapse_toolbar()
{
return p->collapse_toolbar;
}
const Mouse3DController& Plater::get_mouse3d_controller() const
{
return p->mouse3d_controller;

View file

@ -315,6 +315,7 @@ public:
void sys_color_changed();
bool init_view_toolbar();
bool init_collapse_toolbar();
const Camera& get_camera() const;
Camera& get_camera();
@ -330,6 +331,9 @@ public:
const GLToolbar& get_view_toolbar() const;
GLToolbar& get_view_toolbar();
const GLToolbar& get_collapse_toolbar() const;
GLToolbar& get_collapse_toolbar();
const Mouse3DController& get_mouse3d_controller() const;
Mouse3DController& get_mouse3d_controller();

View file

@ -7,6 +7,7 @@
#include <libslic3r/Polyline.hpp>
#include <libslic3r/EdgeGrid.hpp>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/VoronoiOffset.hpp>
#define BOOST_VORONOI_USE_GMP 1
#include "boost/polygon/voronoi.hpp"
@ -16,12 +17,7 @@ using boost::polygon::voronoi_diagram;
using namespace Slic3r;
struct VD : public boost::polygon::voronoi_diagram<double> {
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;
};
using VD = Geometry::VoronoiDiagram;
// #define VORONOI_DEBUG_OUT
@ -322,6 +318,7 @@ static inline void dump_voronoi_to_svg(
/* const */ VD &vd,
const Points &points,
const Lines &lines,
const Polygons &offset_curves = Polygons(),
const double scale = 0.7) // 0.2?
{
const std::string inputSegmentPointColor = "lightseagreen";
@ -336,6 +333,9 @@ static inline void dump_voronoi_to_svg(
const std::string voronoiArcColor = "red";
const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR);
const std::string offsetCurveColor = "magenta";
const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR);
const bool internalEdgesOnly = false;
const bool primaryEdgesOnly = false;
@ -408,6 +408,7 @@ static inline void dump_voronoi_to_svg(
}
#endif
svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth);
svg.Close();
}
#endif
@ -1585,6 +1586,32 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]")
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(),
vd, Points(), lines, 0.015);
vd, Points(), lines, Polygons(), 0.015);
#endif
}
TEST_CASE("Voronoi offset", "[VoronoiOffset]")
{
Polygons poly_with_hole = { Polygon {
{ 0, 10000000},
{ 700000, 0},
{ 700000, 9000000},
{ 9100000, 9000000},
{ 9100000, 0},
{10000000, 10000000}
}
};
VD vd;
Lines lines = to_lines(poly_with_hole);
construct_voronoi(lines.begin(), lines.end(), &vd);
Polygons offsetted_polygons = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset.svg").c_str(),
vd, Points(), lines, offsetted_polygons);
#endif
REQUIRE(offsetted_polygons.size() == 2);
}

View file

@ -39,7 +39,7 @@ TEST_CASE("Raycaster - find intersections of a line and cylinder")
REQUIRE(std::abs(out[1].first - std::sqrt(72.f)) < 0.001f);
}
#ifdef SLIC3R_HOLE_RAYCASTER
// Create a simple scene with a 20mm cube and a big hole in the front wall
// with 5mm radius. Then shoot rays from interesting positions and see where
// they land.
@ -94,3 +94,4 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]")
// Check for support tree correctness
test_support_model_collision("20mm_cube.obj", {}, hcfg, holes);
}
#endif

View file

@ -105,8 +105,13 @@ void test_supports(const std::string &obj_filename,
// Create the special index-triangle mesh with spatial indexing which
// is the input of the support point and support mesh generators
sla::EigenMesh3D emesh{mesh};
#ifdef SLIC3R_HOLE_RAYCASTER
if (hollowingcfg.enabled)
emesh.load_holes(drainholes);
#endif
// TODO: do the cgal hole cutting...
// Create the support point generator
sla::SupportPointGenerator::Config autogencfg;