SPE 2549: Use 'bridge' annotation in seam placement

Add 'bridge' annotated points to the perimeter used for
seam placement.
This commit is contained in:
Martin Šach 2024-10-24 17:02:40 +02:00 committed by Lukas Matena
parent 3e67d94803
commit b037828aef
10 changed files with 417 additions and 223 deletions

View File

@ -138,12 +138,14 @@ Extrusion::Extrusion(
Polygon &&polygon,
BoundingBox bounding_box,
const double width,
const ExPolygon &island_boundary
const ExPolygon &island_boundary,
Overhangs &&overhangs
)
: polygon(polygon)
: polygon(std::move(polygon))
, bounding_box(std::move(bounding_box))
, width(width)
, island_boundary(island_boundary) {
, island_boundary(island_boundary)
, overhangs(std::move(overhangs)) {
this->island_boundary_bounding_boxes.push_back(island_boundary.contour.bounding_box());
std::transform(
@ -153,6 +155,66 @@ Extrusion::Extrusion(
);
}
Overhangs get_overhangs(const ExtrusionPaths& paths) {
if (paths.empty()) {
return {};
}
Overhangs result;
std::optional<Point> start_point;
if (paths.front().role().is_bridge()) {
start_point = paths.front().first_point();
}
Point previous_last_point{paths.front().last_point()};
for (const ExtrusionPath& path : tcb::span{paths}.subspan(1)) {
if(path.role().is_bridge() && !start_point) {
start_point = path.first_point();
}
if(!path.role().is_bridge() && start_point) {
result.push_back(Overhang{*start_point, previous_last_point});
start_point = std::nullopt;
}
previous_last_point = path.last_point();
}
if (start_point) {
result.push_back(Overhang{*start_point, previous_last_point});
}
return result;
}
Overhangs get_overhangs(const ExtrusionEntity *entity) {
Overhangs result;
if (auto path{dynamic_cast<const ExtrusionPath *>(entity)}) {
if(path->role().is_bridge()) {
result.emplace_back(Overhang{
path->first_point(),
path->last_point()
});
}
} else if (auto path{dynamic_cast<const ExtrusionMultiPath *>(entity)}) {
const Overhangs overhangs{get_overhangs(path->paths)};
result.insert(result.end(), overhangs.begin(), overhangs.end());
} else if (auto path{dynamic_cast<const ExtrusionLoop *>(entity)}) {
const bool bridge_loop{std::all_of(
path->paths.begin(),
path->paths.end(),
[](const ExtrusionPath& path){
return path.role().is_bridge();
}
)};
if (bridge_loop) {
result.emplace_back(LoopOverhang{});
} else {
const Overhangs overhangs{get_overhangs(path->paths)};
result.insert(result.end(), overhangs.begin(), overhangs.end());
}
} else {
throw std::runtime_error{"Unknown extrusion entity!"};
}
return result;
}
Geometry::Extrusions get_external_perimeters(const Slic3r::Layer &layer, const LayerSlice &slice) {
std::vector<Geometry::Extrusion> result;
for (const LayerIsland &island : slice.islands) {
@ -163,10 +225,11 @@ Geometry::Extrusions get_external_perimeters(const Slic3r::Layer &layer, const L
)};
for (const ExtrusionEntity *entity : *collection) {
if (entity->role().is_external_perimeter()) {
Overhangs overhangs{get_overhangs(entity)};
Polygon polygon{entity->as_polyline().points};
const BoundingBox bounding_box{polygon.bounding_box()};
const double width{layer_region.flow(FlowRole::frExternalPerimeter).width()};
result.emplace_back(std::move(polygon), bounding_box, width, island.boundary);
result.emplace_back(std::move(polygon), bounding_box, width, island.boundary, std::move(overhangs));
}
}
}
@ -215,19 +278,33 @@ BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimet
)};
if (distance > max_bb_distance) {
Polygons expanded_extrusion{expand(external_perimeter.polygon, Slic3r::scaled(external_perimeter.width / 2.0))};
const Polygons expanded_extrusion{expand(external_perimeter.polygon, Slic3r::scaled(external_perimeter.width / 2.0))};
if (!expanded_extrusion.empty()) {
return BoundedPolygon{
expanded_extrusion.front(), expanded_extrusion.front().bounding_box(), external_perimeter.polygon.is_clockwise(), 0.0
expanded_extrusion.front(),
external_perimeter.overhangs,
expanded_extrusion.front().bounding_box(),
external_perimeter.polygon.is_clockwise(),
};
}
return BoundedPolygon{
external_perimeter.polygon,
external_perimeter.overhangs,
external_perimeter.polygon.bounding_box(),
external_perimeter.polygon.is_clockwise()
};
}
const bool is_hole{choosen_index != 0};
const Polygon &adjacent_boundary{
!is_hole ? external_perimeter.island_boundary.contour :
external_perimeter.island_boundary.holes[choosen_index - 1]};
return BoundedPolygon{adjacent_boundary, external_perimeter.island_boundary_bounding_boxes[choosen_index], is_hole, 0.0};
return BoundedPolygon{
adjacent_boundary,
external_perimeter.overhangs,
external_perimeter.island_boundary_bounding_boxes[choosen_index],
is_hole,
};
}
);
return result;
@ -243,27 +320,6 @@ std::vector<BoundedPolygons> project_to_geometry(const std::vector<Geometry::Ext
return result;
}
std::vector<BoundedPolygons> convert_to_geometry(const std::vector<Geometry::Extrusions> &extrusions) {
std::vector<BoundedPolygons> result;
result.reserve(extrusions.size());
for (const Geometry::Extrusions &layer : extrusions) {
result.emplace_back();
using std::transform, std::back_inserter;
transform(
layer.begin(), layer.end(), back_inserter(result.back()),
[&](const Geometry::Extrusion &extrusion) {
return BoundedPolygon{
extrusion.polygon, extrusion.bounding_box, extrusion.polygon.is_clockwise(), extrusion.width / 2.0
};
}
);
}
return result;
}
std::vector<Vec2d> oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance) {
const double total_distance{(from - to).norm()};
const auto points_count{static_cast<std::size_t>(std::ceil(total_distance / max_distance)) + 1};
@ -337,34 +393,6 @@ Points scaled(const std::vector<Vec2d> &points) {
return result;
}
std::vector<double> get_embedding_distances(
const std::vector<Vec2d> &points, const AABBTreeLines::LinesDistancer<Linef> &perimeter_distancer
) {
std::vector<double> result;
result.reserve(points.size());
using std::transform, std::back_inserter;
transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) {
const double distance{perimeter_distancer.distance_from_lines<true>(point)};
return distance < 0 ? -distance : 0.0;
});
return result;
}
std::vector<double> get_overhangs(
const std::vector<Vec2d> &points,
const AABBTreeLines::LinesDistancer<Linef> &previous_layer_perimeter_distancer,
const double layer_height
) {
std::vector<double> result;
result.reserve(points.size());
using std::transform, std::back_inserter;
transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) {
const double distance{previous_layer_perimeter_distancer.distance_from_lines<true>(point)};
return distance > 0 ? M_PI / 2 - std::atan(layer_height / distance) : 0.0;
});
return result;
}
// Measured from outside, convex is positive
std::vector<double> get_vertex_angles(const std::vector<Vec2d> &points, const double min_arm_length) {
std::vector<double> result;

View File

@ -11,6 +11,8 @@
#include <optional>
#include <utility>
#include <boost/variant.hpp>
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/AABBTreeLines.hpp"
@ -30,13 +32,23 @@ template <typename LineType> class LinesDistancer;
namespace Slic3r::Seams::Geometry {
struct Overhang {
Point start;
Point end;
};
struct LoopOverhang {};
using Overhangs = std::vector<boost::variant<Overhang, LoopOverhang>>;
struct Extrusion
{
Extrusion(
Polygon &&polygon,
BoundingBox bounding_box,
const double width,
const ExPolygon &island_boundary
const ExPolygon &island_boundary,
Overhangs &&overhangs
);
Extrusion(const Extrusion &) = delete;
@ -51,6 +63,7 @@ struct Extrusion
// At index 0 there is the bounding box of contour. Rest are the bounding boxes of holes in order.
BoundingBoxes island_boundary_bounding_boxes;
Overhangs overhangs;
};
using Extrusions = std::vector<Extrusion>;
@ -59,16 +72,15 @@ std::vector<Extrusions> get_extrusions(tcb::span<const Slic3r::Layer *const> obj
struct BoundedPolygon {
Polygon polygon;
Overhangs overhangs;
BoundingBox bounding_box;
bool is_hole{false};
double offset_inside{};
};
using BoundedPolygons = std::vector<BoundedPolygon>;
BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance);
std::vector<BoundedPolygons> project_to_geometry(const std::vector<Geometry::Extrusions> &extrusions, const double max_bb_distance);
std::vector<BoundedPolygons> convert_to_geometry(const std::vector<Geometry::Extrusions> &extrusions);
Vec2d get_polygon_normal(
const std::vector<Vec2d> &points, const std::size_t index, const double min_arm_length
@ -164,10 +176,6 @@ std::vector<Linef> unscaled(const Lines &lines);
Points scaled(const std::vector<Vec2d> &points);
std::vector<double> get_embedding_distances(
const std::vector<Vec2d> &points, const AABBTreeLines::LinesDistancer<Linef> &perimeter_distancer
);
/**
* @brief Calculate overhang angle for each of the points over the previous layer perimeters.
*

View File

@ -5,6 +5,8 @@
#include <tuple>
#include <limits>
#include <boost/hana/functional/overload_linearly.hpp>
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Layer.hpp"
#include "libslic3r/GCode/SeamGeometry.hpp"
@ -15,114 +17,239 @@
#include "tcbspan/span.hpp"
namespace Slic3r::Seams::Perimeters::Impl {
std::vector<Vec2d> oversample_painted(
const std::vector<Vec2d> &points,
PerimeterPoints oversample_painted(
PerimeterPoints &points,
const std::function<bool(Vec3f, double)> &is_painted,
const double slice_z,
const double max_distance
) {
std::vector<Vec2d> result;
PerimeterPoints result;
for (std::size_t index{0}; index < points.size(); ++index) {
const Vec2d &point{points[index]};
result.push_back(point);
result.push_back(points[index]);
const Vec2d &point{points[index].position};
const std::size_t next_index{index == points.size() - 1 ? 0 : index + 1};
const Vec2d &next_point{points[next_index]};
const Vec2d &next_point{points[next_index].position};
const float next_point_distance{static_cast<float>((point - next_point).norm())};
const Vec2d middle_point{(point + next_point) / 2.0};
Vec3f point3d{to_3d(middle_point, slice_z).cast<float>()};
if (is_painted(point3d, next_point_distance / 2.0)) {
for (const Vec2d &edge_point :
Geometry::oversample_edge(point, next_point, max_distance)) {
result.push_back(edge_point);
for (const Vec2d &edge_point : Geometry::oversample_edge(point, next_point, max_distance)) {
PerimeterPoint perimeter_point;
perimeter_point.position = edge_point;
if (points[next_index].classification != PointClassification::common) {
perimeter_point.classification = points[next_index].classification;
}
if (points[index].classification != PointClassification::common) {
perimeter_point.classification = points[index].classification;
}
result.push_back(std::move(perimeter_point));
}
}
}
return result;
}
std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
const std::vector<Vec2d> &points,
const std::vector<PointType> &point_types,
PerimeterPoints remove_redundant_points(
const PerimeterPoints &points,
const double tolerance
) {
std::vector<Vec2d> points_result;
std::vector<PointType> point_types_result;
PerimeterPoints result;
auto range_start{points.begin()};
for (auto iterator{points.begin()}; iterator != points.end(); ++iterator) {
const std::int64_t index{std::distance(points.begin(), iterator)};
if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) {
std::vector<Vec2d> simplification_result;
douglas_peucker(range_start, next(iterator), std::back_inserter(simplification_result), tolerance);
points_result.insert(
points_result.end(), simplification_result.begin(), simplification_result.end()
);
const std::vector<PointType>
point_types_to_add(simplification_result.size(), point_types[index]);
point_types_result.insert(
point_types_result.end(), point_types_to_add.begin(), point_types_to_add.end()
if (
next(iterator) == points.end()
|| points[index].type != points[index + 1].type
|| points[index].classification != points[index + 1].classification
) {
douglas_peucker<double>(
range_start, next(iterator), std::back_inserter(result), tolerance,
[](const PerimeterPoint &point) {
return point.position;
}
);
range_start = next(iterator);
}
}
return {points_result, point_types_result};
return result;
}
std::vector<PointType> get_point_types(
const std::vector<Vec2d> &positions,
PerimeterPoints get_point_types(
const PerimeterPoints &perimeter_points,
const ModelInfo::Painting &painting,
const double slice_z,
const double painting_radius
) {
std::vector<PointType> result;
result.reserve(positions.size());
PerimeterPoints result;
result.reserve(perimeter_points.size());
using std::transform, std::back_inserter;
transform(
positions.begin(), positions.end(), back_inserter(result),
[&](const Vec2d &point) {
const Vec3f point3d{to_3d(point.cast<float>(), static_cast<float>(slice_z))};
perimeter_points.begin(), perimeter_points.end(), back_inserter(result),
[&](PerimeterPoint point) {
const Vec3f point3d{to_3d(point.position.cast<float>(), static_cast<float>(slice_z))};
if (painting.is_blocked(point3d, painting_radius)) {
return PointType::blocker;
point.type = PointType::blocker;
} else if (painting.is_enforced(point3d, painting_radius)) {
point.type = PointType::enforcer;
} else {
point.type = PointType::common;
}
if (painting.is_enforced(point3d, painting_radius)) {
return PointType::enforcer;
}
return PointType::common;
return point;
}
);
return result;
}
std::vector<PointClassification> classify_points(
const std::vector<double> &embeddings,
const std::optional<std::vector<double>> &overhangs,
const double overhang_threshold,
const double embedding_threshold
void project_overhang(
PerimeterPoints &points,
const AABBTreeLines::LinesDistancer<Linef> &points_distancer,
const Geometry::Overhang &overhang,
std::map<int, PerimeterPoints>& output
) {
std::vector<PointClassification> result;
result.reserve(embeddings.size());
using std::transform, std::back_inserter;
transform(
embeddings.begin(), embeddings.end(), back_inserter(result),
[&, i = 0](const double embedding) mutable {
const unsigned index = i++;
if (overhangs && overhangs->operator[](index) > overhang_threshold) {
return PointClassification::overhang;
const auto [start_distance, start_line_index, start_point]{
points_distancer.distance_from_lines_extra<false>(
unscaled(overhang.start)
)
};
PerimeterPoint common_start_point{};
common_start_point.position = start_point;
common_start_point.classification = PointClassification::common;
output[start_line_index].push_back(common_start_point);
PerimeterPoint perimeter_start_point{};
perimeter_start_point.position = start_point;
perimeter_start_point.classification = PointClassification::overhang;
output[start_line_index].push_back(perimeter_start_point);
const auto [end_distance, end_line_index, end_point]{
points_distancer.distance_from_lines_extra<false>(
unscaled(overhang.end)
)
};
PerimeterPoint perimeter_end_point{};
perimeter_end_point.position = end_point;
perimeter_end_point.classification = PointClassification::overhang;
output[end_line_index].push_back(perimeter_end_point);
PerimeterPoint common_end_point{};
common_end_point.position = end_point;
common_end_point.classification = PointClassification::common;
output[end_line_index].push_back(common_end_point);
}
double get_overhang_angle(
const Vec2d& point,
const AABBTreeLines::LinesDistancer<Linef> &previous_layer_perimeter_distancer,
const double layer_height
) {
const double distance{previous_layer_perimeter_distancer.distance_from_lines<true>(point)};
return distance > 0 ? M_PI / 2 - std::atan(layer_height / distance) : 0.0;
}
Linesf to_lines(const PerimeterPoints &points) {
Linesf lines;
for (std::size_t i{0}; i < points.size(); ++i) {
const std::size_t current_index{i};
const std::size_t next_index{i == points.size() - 1 ? 0 : i + 1};
const Vec2d a{points[current_index].position};
const Vec2d b{points[next_index].position};
lines.push_back(Linef{a, b});
}
return lines;
}
PerimeterPoints classify_overhangs(
PerimeterPoints &&points,
const Geometry::Overhangs &overhangs,
const LayerInfo &layer_info,
const double overhang_threshold
) {
using boost::apply_visitor;
using boost::hana::overload_linearly;
PerimeterPoints classified_points{std::move(points)};
if (!layer_info.previous_distancer) {
return classified_points;
}
const AABBTreeLines::LinesDistancer<Linef> points_distancer{to_lines(classified_points)};
std::map<int, PerimeterPoints> points_to_add_to_lines;
for (const auto &overhang : overhangs) {
apply_visitor(overload_linearly(
[&](const Geometry::Overhang& overhang) {
project_overhang(
classified_points,
points_distancer,
overhang,
points_to_add_to_lines
);
},
[&](const Geometry::LoopOverhang&) {
for (PerimeterPoint &point : classified_points) {
point.classification = PointClassification::overhang;
}
}
if (embedding > embedding_threshold) {
return PointClassification::embedded;
}
return PointClassification::common;
), overhang);
}
PerimeterPoints result;
for (std::size_t i{0}; i < classified_points.size(); ++i) {
PerimeterPoint &point{classified_points[i]};
if (point.classification != PointClassification::overhang) {
const double overhang_aangle{
get_overhang_angle(point.position, *layer_info.previous_distancer, layer_info.height)};
point.classification = overhang_aangle > overhang_threshold ?
PointClassification::overhang :
point.classification;
}
);
result.push_back(point);
if (points_to_add_to_lines.count(i) > 0) {
for (const PerimeterPoint &point : points_to_add_to_lines[i]) {
result.push_back(point);
}
}
}
return result;
}
PerimeterPoints classify_points(
PerimeterPoints &&points,
const Geometry::Overhangs &overhangs,
const double embedding_threshold,
const LayerInfo& layer_info,
const double overhang_threshold
) {
PerimeterPoints result{classify_overhangs(std::move(points), overhangs, layer_info, overhang_threshold)};
for (PerimeterPoint& point : result) {
if (point.classification != PointClassification::common) {
continue;
}
// This is an optimization avoiding distance_from_lines<true> which is expensive.
const double embedding_distance{layer_info.distancer.distance_from_lines<false>(point.position)};
if (embedding_distance < embedding_threshold) {
continue;
}
if (layer_info.distancer.outside(point.position) == 1) {
continue;
}
point.classification = PointClassification::embedded;
}
return result;
}
@ -187,6 +314,21 @@ std::vector<AngleType> merge_angle_types(
return result;
}
PerimeterPoints get_perimeter_points(const std::vector<Vec2d> &points){
PerimeterPoints perimeter_points;
std::transform(
points.begin(),
points.end(),
std::back_inserter(perimeter_points),
[](const Vec2d &point){
PerimeterPoint perimeter_point;
perimeter_point.position = point;
return perimeter_point;
}
);
return perimeter_points;
}
} // namespace Slic3r::Seams::Perimeters::Impl
namespace Slic3r::Seams::Perimeters {
@ -322,10 +464,10 @@ Perimeter Perimeter::create_degenerate(
Perimeter Perimeter::create(
const Polygon &polygon,
const Geometry::Overhangs &overhangs,
const ModelInfo::Painting &painting,
const LayerInfo &layer_info,
const PerimeterParams &params,
const double offset_inside
const PerimeterParams &params
) {
if (polygon.size() < 3) {
return Perimeter::create_degenerate(
@ -344,44 +486,48 @@ Perimeter Perimeter::create(
points = Geometry::unscaled(polygon.points);
}
auto is_painted{[&](const Vec3f &point, const double radius) {
PerimeterPoints perimeter_points{Impl::get_perimeter_points(points)};
perimeter_points = Impl::classify_points(
std::move(perimeter_points),
overhangs,
params.embedding_threshold,
layer_info,
params.overhang_threshold
);
const auto is_painted{[&](const Vec3f &point, const double radius) {
return painting.is_enforced(point, radius) || painting.is_blocked(point, radius);
}};
std::vector<Vec2d> perimeter_points{
Impl::oversample_painted(points, is_painted, layer_info.slice_z, params.oversampling_max_distance)};
perimeter_points = Impl::oversample_painted(
perimeter_points,
is_painted,
layer_info.slice_z,
params.oversampling_max_distance
);
std::vector<PointType> point_types{
Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, offset_inside > 0 ? offset_inside * 2 : params.painting_radius)};
perimeter_points = Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, params.painting_radius);
// Geometry converted from extrusions has non zero offset_inside.
// Do not remomve redundant points for extrusions, becouse the redundant
// points can be on overhangs.
if (offset_inside < std::numeric_limits<double>::epsilon()) {
// The following is optimization with significant impact. If in doubt, run
// the "Seam benchmarks" test case in fff_print_tests.
std::tie(perimeter_points, point_types) =
Impl::remove_redundant_points(perimeter_points, point_types, params.simplification_epsilon);
perimeter_points = Impl::remove_redundant_points(perimeter_points, params.simplification_epsilon);
std::vector<Vec2d> positions{};
std::vector<PointType> point_types{};
std::vector<PointClassification> point_classifications{};
for (const PerimeterPoint &point : perimeter_points) {
positions.push_back(point.position);
point_types.push_back(point.type);
point_classifications.push_back(point.classification);
}
const std::vector<double> embeddings{
Geometry::get_embedding_distances(perimeter_points, layer_info.distancer)};
std::optional<std::vector<double>> overhangs;
if (layer_info.previous_distancer) {
overhangs = Geometry::get_overhangs(
perimeter_points, *layer_info.previous_distancer, layer_info.height
);
}
std::vector<PointClassification> point_classifications{
Impl::classify_points(embeddings, overhangs, params.overhang_threshold, params.embedding_threshold)};
std::vector<double> smooth_angles{Geometry::get_vertex_angles(perimeter_points, params.smooth_angle_arm_length)};
std::vector<double> angles{Geometry::get_vertex_angles(perimeter_points, params.sharp_angle_arm_length)};
std::vector<double> smooth_angles{Geometry::get_vertex_angles(positions, params.smooth_angle_arm_length)};
std::vector<double> angles{Geometry::get_vertex_angles(positions, params.sharp_angle_arm_length)};
std::vector<AngleType> angle_types{
Impl::get_angle_types(angles, params.convex_threshold, params.concave_threshold)};
std::vector<AngleType> smooth_angle_types{
Impl::get_angle_types(smooth_angles, params.convex_threshold, params.concave_threshold)};
angle_types = Impl::merge_angle_types(angle_types, smooth_angle_types, perimeter_points, params.smooth_angle_arm_length);
angle_types = Impl::merge_angle_types(angle_types, smooth_angle_types, positions, params.smooth_angle_arm_length);
const bool is_hole{polygon.is_clockwise()};
@ -389,7 +535,7 @@ Perimeter Perimeter::create(
layer_info.slice_z,
layer_info.index,
is_hole,
std::move(perimeter_points),
std::move(positions),
std::move(angles),
std::move(point_types),
std::move(point_classifications),
@ -417,7 +563,13 @@ LayerPerimeters create_perimeters(
const Geometry::BoundedPolygon &bounded_polygon{layer[polygon_index]};
const LayerInfo &layer_info{layer_infos[layer_index]};
result[layer_index][polygon_index] = BoundedPerimeter{
Perimeter::create(bounded_polygon.polygon, painting, layer_info, params, bounded_polygon.offset_inside),
Perimeter::create(
bounded_polygon.polygon,
bounded_polygon.overhangs,
painting,
layer_info,
params
),
bounded_polygon.bounding_box};
}
);

View File

@ -27,9 +27,9 @@ class Painting;
}
namespace Slic3r::Seams::Perimeters {
enum class AngleType;
enum class PointType;
enum class PointClassification;
enum class AngleType { convex, concave, smooth };
enum class PointType { enforcer, blocker, common };
enum class PointClassification { overhang, embedded, common };
struct Perimeter;
struct PerimeterParams;
@ -55,6 +55,18 @@ using LayerInfos = std::vector<LayerInfo>;
LayerInfos get_layer_infos(
tcb::span<const Slic3r::Layer* const> object_layers, const double elephant_foot_compensation
);
struct PerimeterPoint {
Vec2d position{Vec2d::Zero()};
double angle{};
PointType type{PointType::common};
PointClassification classification{PointClassification::common};
AngleType angle_type{AngleType::smooth};
};
using PerimeterPoints = std::vector<PerimeterPoint>;
} // namespace Slic3r::Seams::Perimeters
namespace Slic3r::Seams::Perimeters::Impl {
@ -69,8 +81,8 @@ namespace Slic3r::Seams::Perimeters::Impl {
*
* @return All the points (original and added) in order along the edges.
*/
std::vector<Vec2d> oversample_painted(
const std::vector<Vec2d> &points,
PerimeterPoints oversample_painted(
PerimeterPoints &points,
const std::function<bool(Vec3f, double)> &is_painted,
const double slice_z,
const double max_distance
@ -83,9 +95,8 @@ std::vector<Vec2d> oversample_painted(
*
* @param tolerance Douglas-Peucker epsilon.
*/
std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
const std::vector<Vec2d> &points,
const std::vector<PointType> &point_types,
PerimeterPoints remove_redundant_points(
const PerimeterPoints &points,
const double tolerance
);
@ -93,12 +104,6 @@ std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
namespace Slic3r::Seams::Perimeters {
enum class AngleType { convex, concave, smooth };
enum class PointType { enforcer, blocker, common };
enum class PointClassification { overhang, embedded, common };
struct PerimeterParams
{
double elephant_foot_compensation{};
@ -149,10 +154,10 @@ struct Perimeter
static Perimeter create(
const Polygon &polygon,
const Geometry::Overhangs &overhangs,
const ModelInfo::Painting &painting,
const LayerInfo &layer_info,
const PerimeterParams &params,
const double offset_inside
const PerimeterParams &params
);
static Perimeter create_degenerate(

View File

@ -40,8 +40,6 @@ ObjectLayerPerimeters get_perimeters(
print_object->layers(), params.perimeter.elephant_foot_compensation
)};
const std::vector<Geometry::BoundedPolygons> projected{
print_object->config().seam_position == spRandom ?
Geometry::convert_to_geometry(extrusions) :
Geometry::project_to_geometry(extrusions, params.max_distance)
};
Perimeters::LayerPerimeters perimeters{Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
@ -137,7 +135,7 @@ Params Placer::get_params(const DynamicPrintConfig &config) {
params.max_distance = 5.0;
params.perimeter.oversampling_max_distance = 0.2;
params.perimeter.embedding_threshold = 0.5;
params.perimeter.painting_radius = 0.1;
params.perimeter.painting_radius = 0.05;
params.perimeter.simplification_epsilon = 0.001;
params.perimeter.smooth_angle_arm_length = 0.5;
params.perimeter.sharp_angle_arm_length = 0.25;

View File

@ -26,7 +26,9 @@ std::vector<PerimeterSegment> get_segments(
Vec2d previous_position{positions.front()};
double distance{0.0};
std::vector<PerimeterSegment> result;
for (std::size_t index{0}; index < positions.size(); ++index) {
for (std::size_t i{0}; i <= positions.size(); ++i) {
const std::size_t index{i == positions.size() ? 0 : i};
const double previous_distance{distance};
distance += (positions[index] - previous_position).norm();
previous_position = positions[index];
@ -38,7 +40,7 @@ std::vector<PerimeterSegment> get_segments(
}
} else {
if (current_begin) {
result.push_back(PerimeterSegment{*current_begin, distance, *current_begin_index});
result.push_back(PerimeterSegment{*current_begin, previous_distance, *current_begin_index});
}
current_begin = std::nullopt;
current_begin_index = std::nullopt;
@ -86,19 +88,21 @@ SeamChoice pick_random_point(
double distance{0.0};
std::size_t previous_index{segment.begin_index};
for (std::size_t index{segment.begin_index + 1}; index < perimeter.positions.size(); ++index) {
for (std::size_t i{segment.begin_index + 1}; i <= perimeter.positions.size(); ++i) {
const std::size_t index{i == perimeter.positions.size() ? 0 : i};
const Vec2d edge{positions[index] - positions[previous_index]};
if (distance + edge.norm() >= random_distance) {
std::size_t current_index{index};
if (random_distance - distance < std::numeric_limits<double>::epsilon()) {
index = previous_index;
current_index = previous_index;
} else if (distance + edge.norm() - random_distance < std::numeric_limits<double>::epsilon()) {
previous_index = index;
}
const double remaining_distance{random_distance - distance};
const Vec2d position{positions[previous_index] + remaining_distance * edge.normalized()};
return {previous_index, index, position};
return {previous_index, current_index, position};
}
distance += edge.norm();

View File

@ -127,26 +127,6 @@ TEST_CASE("Vertex angle is rotation agnostic", "[Seams][SeamGeometry]") {
CHECK(rotated_angles[1] == Approx(angles[1]));
}
TEST_CASE("Calculate overhangs", "[Seams][SeamGeometry]") {
const ExPolygon square{
scaled(Vec2d{0.0, 0.0}),
scaled(Vec2d{1.0, 0.0}),
scaled(Vec2d{1.0, 1.0}),
scaled(Vec2d{0.0, 1.0})
};
const std::vector<Vec2d> points{Seams::Geometry::unscaled(square.contour.points)};
ExPolygon previous_layer{square};
previous_layer.translate(scaled(Vec2d{-0.5, 0}));
AABBTreeLines::LinesDistancer<Linef> previous_layer_distancer{
to_unscaled_linesf({previous_layer})};
const std::vector<double> overhangs{
Seams::Geometry::get_overhangs(points, previous_layer_distancer, 0.5)};
REQUIRE(overhangs.size() == points.size());
CHECK_THAT(overhangs, Catch::Matchers::Approx(std::vector<double>{
0.0, M_PI / 4.0, M_PI / 4.0, 0.0
}));
}
const Linesf lines{to_unscaled_linesf({ExPolygon{
scaled(Vec2d{0.0, 0.0}),
scaled(Vec2d{1.0, 0.0}),

View File

@ -16,23 +16,26 @@ using namespace Catch;
constexpr bool debug_files{false};
const ExPolygon square{
scaled(Vec2d{0.0, 0.0}), scaled(Vec2d{1.0, 0.0}), scaled(Vec2d{1.0, 1.0}),
scaled(Vec2d{0.0, 1.0})};
TEST_CASE("Oversample painted", "[Seams][SeamPerimeters]") {
Perimeters::PerimeterPoints square(4);
square[0].position = Vec2d{0.0, 0.0};
square[1].position = Vec2d{1.0, 0.0};
square[2].position = Vec2d{1.0, 1.0};
square[3].position = Vec2d{0.0, 1.0};
auto is_painted{[](const Vec3f &position, float radius) {
return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius;
}};
std::vector<Vec2d> points{Perimeters::Impl::oversample_painted(
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.2
Perimeters::PerimeterPoints points{Perimeters::Impl::oversample_painted(
square, is_painted, 1.0, 0.2
)};
REQUIRE(points.size() == 8);
CHECK((points[1] - Vec2d{0.2, 0.0}).norm() == Approx(0.0));
CHECK((points[1].position - Vec2d{0.2, 0.0}).norm() == Approx(0.0));
points = Perimeters::Impl::oversample_painted(
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.199
square, is_painted, 1.0, 0.199
);
CHECK(points.size() == 9);
}
@ -41,24 +44,35 @@ TEST_CASE("Remove redundant points", "[Seams][SeamPerimeters]") {
using Perimeters::PointType;
using Perimeters::PointClassification;
std::vector<Vec2d> points{{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0},
{3.0, 1.0}, {3.0, 2.0}, {0.0, 2.0}};
std::vector<PointType> point_types{PointType::common,
PointType::enforcer, // Should keep this.
PointType::enforcer, // Should keep this.
PointType::blocker,
PointType::blocker, // Should remove this.
PointType::blocker, PointType::common};
Perimeters::PerimeterPoints points(9);
points[0].position = {0.0, 0.0};
points[0].type = PointType::common;
points[1].position = {1.0, 0.0};
points[1].type = PointType::enforcer; // Should keep
points[2].position = {2.0, 0.0};
points[2].type = PointType::enforcer; // Should keep
points[3].position = {3.0, 0.0};
points[3].type = PointType::blocker;
points[4].position = {3.0, 1.0};
points[4].type = PointType::blocker; // Should remove
points[5].position = {3.0, 1.1};
points[5].type = PointType::blocker;
points[6].position = {3.0, 1.2};
points[6].type = PointType::blocker;
points[6].classification = PointClassification::overhang; // Should keep
points[7].position = {3.0, 2.0};
points[7].type = PointType::blocker;
points[8].position = {0.0, 2.0};
points[8].type = PointType::common;
const auto [resulting_points, resulting_point_types]{
Perimeters::Impl::remove_redundant_points(points, point_types, 0.1)};
Perimeters::PerimeterPoints result{
Perimeters::Impl::remove_redundant_points(points, 0.1)};
REQUIRE(resulting_points.size() == 6);
REQUIRE(resulting_point_types.size() == 6);
CHECK((resulting_points[3] - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
CHECK((resulting_points[4] - Vec2d{3.0, 2.0}).norm() == Approx(0.0));
CHECK(resulting_point_types[3] == PointType::blocker);
CHECK(resulting_point_types[4] == PointType::blocker);
REQUIRE(result.size() == 8);
CHECK((result[3].position - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
CHECK((result[4].position - Vec2d{3.0, 1.1}).norm() == Approx(0.0));
CHECK(result[3].type == PointType::blocker);
CHECK(result[4].type == PointType::blocker);
}
TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {

View File

@ -32,7 +32,7 @@ Perimeters::Perimeter get_perimeter() {
}
} // namespace RandomTest
double get_chi2_uniform(const std::vector<double> &data, double min, double max, const std::size_t bin_count) {
double get_chi2_uniform(const std::vector<double> &data, const double min, const double max, const std::size_t bin_count) {
std::vector<std::size_t> bins(bin_count);
const double bin_size{(max - min) / bin_count};
const double expected_frequncy{static_cast<double>(data.size()) / bin_count};
@ -62,7 +62,7 @@ TEST_CASE("Random is uniform", "[Seams][SeamRandom]") {
return choice->position.x();
});
const std::size_t degrees_of_freedom{10};
const double critical{18.307}; // dof 10, significance 0.05
const double critical{29.588}; // dof 10, significance 0.001
CHECK(get_chi2_uniform(x_positions, 0.0, 1.0, degrees_of_freedom + 1) < critical);
}

View File

@ -23,7 +23,12 @@ struct ProjectionFixture
double extrusion_width{0.2};
ProjectionFixture() {
extrusions.emplace_back(Polygon{extrusion_path}, extrusion_path.bounding_box(), extrusion_width, island_boundary);
extrusions.emplace_back(
Polygon{extrusion_path},
extrusion_path.bounding_box(),
extrusion_width, island_boundary,
Seams::Geometry::Overhangs{}
);
}
};