mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-31 04:41:59 +08:00
SPE 2549: Use 'bridge' annotation in seam placement
Add 'bridge' annotated points to the perimeter used for seam placement.
This commit is contained in:
parent
3e67d94803
commit
b037828aef
@ -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;
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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 ¶ms,
|
||||
const double offset_inside
|
||||
const PerimeterParams ¶ms
|
||||
) {
|
||||
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};
|
||||
}
|
||||
);
|
||||
|
@ -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 ¶ms,
|
||||
const double offset_inside
|
||||
const PerimeterParams ¶ms
|
||||
);
|
||||
|
||||
static Perimeter create_degenerate(
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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}),
|
||||
|
@ -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]") {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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{}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user