Merge branch 'ms_seam_stop_overhangs'

This commit is contained in:
Lukas Matena 2025-02-14 12:02:47 +01:00
commit bd19f11919
14 changed files with 664 additions and 378 deletions

View File

@ -95,6 +95,7 @@ PenaltyBreakString: 600
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 50
PenaltyReturnTypeOnItsOwnLine: 300
PenaltyIndentedWhitespace: 10
PointerAlignment: Right
ReflowComments: true
SortIncludes: false

View File

@ -41,42 +41,17 @@ const Perimeters::Perimeter::OptionalPointTree &pick_tree(
throw std::runtime_error("Point tree for classification does not exist.");
}
unsigned point_value(PointType point_type, PointClassification point_classification) {
// Better be explicit than smart.
switch (point_type) {
case PointType::enforcer:
switch (point_classification) {
case PointClassification::embedded: return 9;
case PointClassification::common: return 8;
case PointClassification::overhang: return 7;
}
case PointType::common:
switch (point_classification) {
case PointClassification::embedded: return 6;
case PointClassification::common: return 5;
case PointClassification::overhang: return 4;
}
case PointType::blocker:
switch (point_classification) {
case PointClassification::embedded: return 3;
case PointClassification::common: return 2;
case PointClassification::overhang: return 1;
}
}
return 0;
}
SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOptions &options) {
const std::vector<PointType> &types{perimeter.point_types};
const std::vector<PointClassification> &classifications{perimeter.point_classifications};
const std::vector<Vec2d> &positions{perimeter.positions};
unsigned closeset_point_value =
point_value(types.at(options.closest), classifications[options.closest]);
get_point_value(types.at(options.closest), classifications[options.closest]);
if (options.snapped) {
unsigned snapped_point_value =
point_value(types.at(*options.snapped), classifications[*options.snapped]);
get_point_value(types.at(*options.snapped), classifications[*options.snapped]);
if (snapped_point_value >= closeset_point_value) {
const Vec2d position{positions.at(*options.snapped)};
return {*options.snapped, *options.snapped, position};
@ -84,7 +59,7 @@ SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOp
}
unsigned adjacent_point_value =
point_value(types.at(options.adjacent), classifications[options.adjacent]);
get_point_value(types.at(options.adjacent), classifications[options.adjacent]);
if (adjacent_point_value < closeset_point_value) {
const Vec2d position = positions[options.closest];
return {options.closest, options.closest, position};

View File

@ -14,17 +14,7 @@
#include "libslic3r/Point.hpp"
namespace Slic3r::Seams {
/**
* When previous_index == next_index, the point is at the point.
* Otherwise the point is at the edge.
*/
struct SeamChoice
{
std::size_t previous_index{};
std::size_t next_index{};
Vec2d position{Vec2d::Zero()};
};
using SeamChoice = Perimeters::PointOnPerimeter;
struct SeamPerimeterChoice
{

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;
@ -441,56 +469,4 @@ Polygon to_polygon(const ExtrusionLoop &loop) {
return Polygon{loop_points};
}
std::optional<PointOnLine> offset_along_lines(
const Vec2d &point,
const std::size_t loop_line_index,
const Linesf &loop_lines,
const double offset,
const Direction1D direction
) {
const Linef initial_line{loop_lines[loop_line_index]};
double distance{
direction == Direction1D::forward ? (initial_line.b - point).norm() :
(point - initial_line.a).norm()};
if (distance >= offset) {
const Vec2d edge_direction{(initial_line.b - initial_line.a).normalized()};
const Vec2d offset_point{direction == Direction1D::forward ? Vec2d{point + offset * edge_direction} : Vec2d{point - offset * edge_direction}};
return {{offset_point, loop_line_index}};
}
std::optional<PointOnLine> offset_point;
bool skip_first{direction == Direction1D::forward};
const auto visitor{[&](std::size_t index) {
if (skip_first) {
skip_first = false;
return false;
}
const Vec2d previous_point{
direction == Direction1D::forward ? loop_lines[index].a : loop_lines[index].b};
const Vec2d next_point{
direction == Direction1D::forward ? loop_lines[index].b : loop_lines[index].a};
const Vec2d edge{next_point - previous_point};
if (distance + edge.norm() > offset) {
const double remaining_distance{offset - distance};
offset_point =
PointOnLine{previous_point + remaining_distance * edge.normalized(), index};
return true;
}
distance += edge.norm();
return false;
}};
if (direction == Direction1D::forward) {
Geometry::visit_forward(loop_line_index, loop_lines.size(), visitor);
} else {
Geometry::visit_backward(loop_line_index, loop_lines.size(), visitor);
}
return offset_point;
}
} // namespace Slic3r::Seams::Geometry

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.
*
@ -202,14 +210,6 @@ struct PointOnLine{
std::size_t line_index;
};
std::optional<PointOnLine> offset_along_lines(
const Vec2d &point,
const std::size_t loop_line_index,
const Linesf &loop_lines,
const double offset,
const Direction1D direction
);
} // namespace Slic3r::Seams::Geometry
#endif // libslic3r_SeamGeometry_hpp_

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});
}
if (embedding > embedding_threshold) {
return PointClassification::embedded;
}
return PointClassification::common;
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;
}
}
), 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)};
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)};
// 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);
}
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
perimeter_points = Impl::oversample_painted(
perimeter_points,
is_painted,
layer_info.slice_z,
params.oversampling_max_distance
);
}
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)};
perimeter_points = Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, params.painting_radius);
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);
}
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,11 +563,113 @@ 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};
}
);
return result;
}
std::optional<PointOnPerimeter> offset_along_perimeter(
const PointOnPerimeter &point,
const Perimeter& perimeter,
const double offset,
const Seams::Geometry::Direction1D direction,
const std::function<bool(const Perimeter&, const std::size_t)> &early_stop_condition
) {
using Dir = Seams::Geometry::Direction1D;
const Linef initial_line{
perimeter.positions[point.previous_index], perimeter.positions[point.next_index]};
double distance{
direction == Dir::forward ?
(initial_line.b - point.position).norm() :
(point.position - initial_line.a).norm()};
if (distance >= offset) {
const Vec2d edge_direction{(initial_line.b - initial_line.a).normalized()};
const Vec2d offset_point{direction == Dir::forward ? Vec2d{point.position + offset * edge_direction} : Vec2d{point.position - offset * edge_direction}};
return {{point.previous_index, point.next_index, offset_point}};
}
std::optional<PointOnPerimeter> offset_point;
bool skip_first{direction == Dir::forward};
const auto visitor{[&](std::size_t index) {
if (skip_first) {
skip_first = false;
return false;
}
const std::size_t previous_index{
direction == Dir::forward ?
(index == 0 ? perimeter.positions.size() - 1 : index - 1) :
(index == perimeter.positions.size() - 1 ? 0 : index + 1)};
const Vec2d previous_point{perimeter.positions[previous_index]};
const Vec2d next_point{perimeter.positions[index]};
const Vec2d edge{next_point - previous_point};
if (early_stop_condition(perimeter, index)) {
offset_point = PointOnPerimeter{previous_index, previous_index, perimeter.positions[previous_index]};
return true;
}
if (distance + edge.norm() > offset) {
const double remaining_distance{offset - distance};
const Vec2d result{previous_point + remaining_distance * edge.normalized()};
if (direction == Dir::forward) {
offset_point = PointOnPerimeter{previous_index, index, result};
} else {
offset_point = PointOnPerimeter{index, previous_index, result};
}
return true;
}
distance += edge.norm();
return false;
}};
if (direction == Dir::forward) {
Geometry::visit_forward(point.next_index, perimeter.positions.size(), visitor);
} else {
Geometry::visit_backward(point.previous_index, perimeter.positions.size(), visitor);
}
return offset_point;
}
unsigned get_point_value(const PointType point_type, const PointClassification point_classification) {
// Better be explicit than smart.
switch (point_type) {
case PointType::enforcer:
switch (point_classification) {
case PointClassification::embedded: return 9;
case PointClassification::common: return 8;
case PointClassification::overhang: return 7;
}
case PointType::common:
switch (point_classification) {
case PointClassification::embedded: return 6;
case PointClassification::common: return 5;
case PointClassification::overhang: return 4;
}
case PointType::blocker:
switch (point_classification) {
case PointClassification::embedded: return 3;
case PointClassification::common: return 2;
case PointClassification::overhang: return 1;
}
}
return 0;
}
} // namespace Slic3r::Seams::Perimeter

View File

@ -27,12 +27,23 @@ 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;
/**
* When previous_index == next_index, the point is at the point.
* Otherwise the point is at the edge.
*/
struct PointOnPerimeter
{
std::size_t previous_index{};
std::size_t next_index{};
Vec2d position{Vec2d::Zero()};
};
struct LayerInfo
{
static LayerInfo create(
@ -55,6 +66,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 +92,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 +106,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 +115,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 +165,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(
@ -204,6 +220,19 @@ inline std::vector<Vec2d> extract_points(
return result;
}
using Seams::Geometry::PointOnLine;
std::optional<PointOnPerimeter> offset_along_perimeter(
const PointOnPerimeter &point,
const Perimeter& perimeter,
const double offset,
const Seams::Geometry::Direction1D direction,
const std::function<bool(const Perimeter&, const std::size_t)> &early_stop_condition
);
unsigned get_point_value(const PointType point_type, const PointClassification point_classification);
} // namespace Slic3r::Seams::Perimeters
#endif // libslic3r_SeamPerimeters_hpp_

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;
@ -253,6 +251,20 @@ Geometry::Direction1D get_direction(
return result;
}
unsigned get_seam_choice_value(const SeamChoice &seam_choice, const Perimeters::Perimeter& perimeter) {
const unsigned previous_value{Perimeters::get_point_value(
perimeter.point_types[seam_choice.previous_index],
perimeter.point_classifications[seam_choice.previous_index]
)};
const unsigned next_value{Perimeters::get_point_value(
perimeter.point_types[seam_choice.next_index],
perimeter.point_classifications[seam_choice.next_index]
)};
return std::max(previous_value, next_value);
}
boost::variant<Point, Scarf::Scarf> finalize_seam_position(
const ExtrusionLoop &loop,
const PrintRegion *region,
@ -261,6 +273,9 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
const bool staggered_inner_seams,
const bool flipped
) {
using Perimeters::offset_along_perimeter;
using Perimeters::PointOnPerimeter;
const Polygon loop_polygon{Geometry::to_polygon(loop)};
const bool do_staggering{staggered_inner_seams && loop.role() == ExtrusionRole::Perimeter};
const double loop_width{loop.paths.empty() ? 0.0 : loop.paths.front().width()};
@ -275,6 +290,25 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
const Geometry::Direction1D offset_direction{get_direction(flipped, perimeter_polygon, loop)};
const auto offset_stop_condition{
[choice_value = get_seam_choice_value(seam_choice, perimeter)](
const Perimeters::Perimeter &perimeter, const std::size_t index
) {
const unsigned current_point_value{Perimeters::get_point_value(
perimeter.point_types[index], perimeter.point_classifications[index]
)};
if (
current_point_value < choice_value
&& (
perimeter.point_types[index] == Perimeters::PointType::blocker
|| perimeter.point_classifications[index] == Perimeters::PointClassification::overhang
)
) {
return true;
}
return false;
}};
// ExtrusionRole::Perimeter is inner perimeter.
if (do_staggering) {
const double depth = (loop_point - seam_choice.position).norm() -
@ -282,13 +316,16 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
const double staggering_offset{depth};
std::optional<Geometry::PointOnLine> staggered_point{Geometry::offset_along_lines(
loop_point, seam_choice.previous_index, perimeter_lines, staggering_offset,
offset_direction
std::optional<PointOnPerimeter> staggered_point{offset_along_perimeter(
{seam_choice.previous_index, seam_choice.next_index, loop_point},
perimeter,
staggering_offset,
offset_direction,
offset_stop_condition
)};
if (staggered_point) {
seam_choice = to_seam_choice(*staggered_point, perimeter);
seam_choice = *staggered_point;
std::tie(loop_line_index, loop_point) = project_to_extrusion_loop(seam_choice, perimeter, distancer);
}
}
@ -317,12 +354,12 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
scarf.start_height = std::min(region->config().scarf_seam_start_height.get_abs_value(1.0), 1.0);
const double offset{scarf.entire_loop ? 0.0 : region->config().scarf_seam_length.value};
const std::optional<Geometry::PointOnLine> outter_scarf_start_point{Geometry::offset_along_lines(
seam_choice.position,
seam_choice.previous_index,
perimeter_lines,
const std::optional<PointOnPerimeter> outter_scarf_start_point{offset_along_perimeter(
seam_choice,
perimeter,
offset,
offset_direction
offset_direction,
offset_stop_condition
)};
if (!outter_scarf_start_point) {
return scaled(loop_point);
@ -330,11 +367,11 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
if (loop.role() != ExtrusionRole::Perimeter) { // Outter perimeter
const Vec2d start_point_candidate{project_to_extrusion_loop(
to_seam_choice(*outter_scarf_start_point, perimeter),
*outter_scarf_start_point,
perimeter,
distancer
).second};
if ((start_point_candidate - outter_scarf_start_point->point).norm() > 5.0) {
if ((start_point_candidate - outter_scarf_start_point->position).norm() > 5.0) {
return scaled(loop_point);
}
scarf.start_point = scaled(start_point_candidate);
@ -342,7 +379,7 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
scarf.end_point_previous_index = loop_line_index;
return scarf;
} else {
Geometry::PointOnLine inner_scarf_end_point{
PointOnPerimeter inner_scarf_end_point{
*outter_scarf_start_point
};
@ -352,12 +389,12 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
Geometry::Direction1D::backward :
Geometry::Direction1D::forward
};
if (auto result{Geometry::offset_along_lines(
seam_choice.position,
seam_choice.previous_index,
perimeter_lines,
if (auto result{offset_along_perimeter(
seam_choice,
perimeter,
offset,
external_first_offset_direction
external_first_offset_direction,
offset_stop_condition
)}) {
inner_scarf_end_point = *result;
} else {
@ -366,36 +403,36 @@ boost::variant<Point, Scarf::Scarf> finalize_seam_position(
}
if (!region->config().scarf_seam_on_inner_perimeters) {
return scaled(inner_scarf_end_point.point);
return scaled(inner_scarf_end_point.position);
}
const std::optional<Geometry::PointOnLine> inner_scarf_start_point{Geometry::offset_along_lines(
inner_scarf_end_point.point,
inner_scarf_end_point.line_index,
perimeter_lines,
const std::optional<PointOnPerimeter> inner_scarf_start_point{offset_along_perimeter(
inner_scarf_end_point,
perimeter,
offset,
offset_direction
offset_direction,
offset_stop_condition
)};
if (!inner_scarf_start_point) {
return scaled(inner_scarf_end_point.point);
return scaled(inner_scarf_end_point.position);
}
const Vec2d start_point_candidate{project_to_extrusion_loop(
to_seam_choice(*inner_scarf_start_point, perimeter),
*inner_scarf_start_point,
perimeter,
distancer
).second};
if ((start_point_candidate - inner_scarf_start_point->point).norm() > 5.0) {
if ((start_point_candidate - inner_scarf_start_point->position).norm() > 5.0) {
return scaled(loop_point);
}
scarf.start_point = scaled(start_point_candidate);
const auto [end_point_previous_index, end_point]{project_to_extrusion_loop(
to_seam_choice(inner_scarf_end_point, perimeter),
inner_scarf_end_point,
perimeter,
distancer
)};
if ((end_point - inner_scarf_end_point.point).norm() > 5.0) {
if ((end_point - inner_scarf_end_point.position).norm() > 5.0) {
return scaled(loop_point);
}

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

@ -3,12 +3,12 @@
#include <optional>
#include <vector>
#include "libslic3r/GCode/SeamGeometry.hpp"
#include "libslic3r/GCode/SeamChoice.hpp"
#include "libslic3r/GCode/SeamPerimeters.hpp"
namespace Slic3r {
namespace Seams {
struct SeamChoice;
struct SeamPerimeterChoice;
} // namespace Seams
} // namespace Slic3r

View File

@ -126,50 +126,3 @@ TEST_CASE("Vertex angle is rotation agnostic", "[Seams][SeamGeometry]") {
std::vector<double> rotated_angles = Seams::Geometry::get_vertex_angles(points, 0.1);
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}),
scaled(Vec2d{1.0, 1.0}),
scaled(Vec2d{0.0, 1.0})
}})};
TEST_CASE("Offset along loop lines forward", "[Seams][SeamGeometry]") {
const std::optional<Seams::Geometry::PointOnLine> result{Seams::Geometry::offset_along_lines(
{0.5, 0.0}, 0, lines, 3.9, Seams::Geometry::Direction1D::forward
)};
REQUIRE(result);
const auto &[point, line_index] = *result;
CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON));
CHECK(line_index == 0);
}
TEST_CASE("Offset along loop lines backward", "[Seams][SeamGeometry]") {
const std::optional<Seams::Geometry::PointOnLine> result{Seams::Geometry::offset_along_lines(
{1.0, 0.5}, 1, lines, 1.8, Seams::Geometry::Direction1D::backward
)};
REQUIRE(result);
const auto &[point, line_index] = *result;
CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON));
CHECK(line_index == 3);
}

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]") {
@ -182,3 +196,57 @@ TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeter
serialize_shells(csv, shells);
}
}
using Dir = Seams::Geometry::Direction1D;
Perimeters::Perimeter get_perimeter(){
Perimeters::Perimeter perimeter;
perimeter.positions = {
Vec2d{0.0, 0.0},
Vec2d{1.0, 0.0},
Vec2d{1.0, 1.0},
Vec2d{0.0, 1.0}
};
return perimeter;
}
TEST_CASE("Offset along perimeter forward", "[Seams][SeamPerimeters]") {
const std::optional<Perimeters::PointOnPerimeter> result{Perimeters::offset_along_perimeter(
{0, 1, {0.5, 0.0}}, get_perimeter(), 3.9, Dir::forward,
[](const Perimeters::Perimeter &, const std::size_t) { return false; }
)};
REQUIRE(result);
const auto &[previous_index, next_index, point] = *result;
CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON));
CHECK(previous_index == 0);
CHECK(next_index == 1);
}
TEST_CASE("Offset along perimeter backward", "[Seams][SeamPerimeters]") {
const std::optional<Perimeters::PointOnPerimeter> result{Perimeters::offset_along_perimeter(
{1, 2, {1.0, 0.5}}, get_perimeter(), 1.8, Dir::backward,
[](const Perimeters::Perimeter &, const std::size_t) { return false; }
)};
REQUIRE(result);
const auto &[previous_index, next_index, point] = *result;
CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON));
CHECK(previous_index == 3);
CHECK(next_index == 0);
}
TEST_CASE("Offset along perimeter forward respects stop condition", "[Seams][SeamPerimeters]") {
Perimeters::Perimeter perimeter{get_perimeter()};
perimeter.point_types = std::vector<Perimeters::PointType>(perimeter.positions.size(), Perimeters::PointType::common);
perimeter.point_types[2] = Perimeters::PointType::blocker;
const std::optional<Perimeters::PointOnPerimeter> result{Perimeters::offset_along_perimeter(
{0, 1, {0.5, 0.0}}, perimeter, 3.9, Dir::forward,
[](const Perimeters::Perimeter &perimeter, const std::size_t index) {
return perimeter.point_types[index] == Perimeters::PointType::blocker;
}
)};
REQUIRE(result);
const auto &[previous_index, next_index, point] = *result;
CHECK((scaled(point) - Point::new_scale(1.0, 0.0)).norm() < scaled(EPSILON));
CHECK(previous_index == 1);
CHECK(next_index == 1);
}

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{}
);
}
};