mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 04:52:02 +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,
|
Polygon &&polygon,
|
||||||
BoundingBox bounding_box,
|
BoundingBox bounding_box,
|
||||||
const double width,
|
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))
|
, bounding_box(std::move(bounding_box))
|
||||||
, width(width)
|
, 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());
|
this->island_boundary_bounding_boxes.push_back(island_boundary.contour.bounding_box());
|
||||||
|
|
||||||
std::transform(
|
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) {
|
Geometry::Extrusions get_external_perimeters(const Slic3r::Layer &layer, const LayerSlice &slice) {
|
||||||
std::vector<Geometry::Extrusion> result;
|
std::vector<Geometry::Extrusion> result;
|
||||||
for (const LayerIsland &island : slice.islands) {
|
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) {
|
for (const ExtrusionEntity *entity : *collection) {
|
||||||
if (entity->role().is_external_perimeter()) {
|
if (entity->role().is_external_perimeter()) {
|
||||||
|
Overhangs overhangs{get_overhangs(entity)};
|
||||||
Polygon polygon{entity->as_polyline().points};
|
Polygon polygon{entity->as_polyline().points};
|
||||||
const BoundingBox bounding_box{polygon.bounding_box()};
|
const BoundingBox bounding_box{polygon.bounding_box()};
|
||||||
const double width{layer_region.flow(FlowRole::frExternalPerimeter).width()};
|
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) {
|
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()) {
|
if (!expanded_extrusion.empty()) {
|
||||||
return BoundedPolygon{
|
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 bool is_hole{choosen_index != 0};
|
||||||
const Polygon &adjacent_boundary{
|
const Polygon &adjacent_boundary{
|
||||||
!is_hole ? external_perimeter.island_boundary.contour :
|
!is_hole ? external_perimeter.island_boundary.contour :
|
||||||
external_perimeter.island_boundary.holes[choosen_index - 1]};
|
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;
|
return result;
|
||||||
@ -243,27 +320,6 @@ std::vector<BoundedPolygons> project_to_geometry(const std::vector<Geometry::Ext
|
|||||||
return result;
|
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) {
|
std::vector<Vec2d> oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance) {
|
||||||
const double total_distance{(from - to).norm()};
|
const double total_distance{(from - to).norm()};
|
||||||
const auto points_count{static_cast<std::size_t>(std::ceil(total_distance / max_distance)) + 1};
|
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;
|
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
|
// 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> get_vertex_angles(const std::vector<Vec2d> &points, const double min_arm_length) {
|
||||||
std::vector<double> result;
|
std::vector<double> result;
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <boost/variant.hpp>
|
||||||
|
|
||||||
#include "libslic3r/ExtrusionEntity.hpp"
|
#include "libslic3r/ExtrusionEntity.hpp"
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
#include "libslic3r/AABBTreeLines.hpp"
|
#include "libslic3r/AABBTreeLines.hpp"
|
||||||
@ -30,13 +32,23 @@ template <typename LineType> class LinesDistancer;
|
|||||||
|
|
||||||
namespace Slic3r::Seams::Geometry {
|
namespace Slic3r::Seams::Geometry {
|
||||||
|
|
||||||
|
struct Overhang {
|
||||||
|
Point start;
|
||||||
|
Point end;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoopOverhang {};
|
||||||
|
|
||||||
|
using Overhangs = std::vector<boost::variant<Overhang, LoopOverhang>>;
|
||||||
|
|
||||||
struct Extrusion
|
struct Extrusion
|
||||||
{
|
{
|
||||||
Extrusion(
|
Extrusion(
|
||||||
Polygon &&polygon,
|
Polygon &&polygon,
|
||||||
BoundingBox bounding_box,
|
BoundingBox bounding_box,
|
||||||
const double width,
|
const double width,
|
||||||
const ExPolygon &island_boundary
|
const ExPolygon &island_boundary,
|
||||||
|
Overhangs &&overhangs
|
||||||
);
|
);
|
||||||
|
|
||||||
Extrusion(const Extrusion &) = delete;
|
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.
|
// At index 0 there is the bounding box of contour. Rest are the bounding boxes of holes in order.
|
||||||
BoundingBoxes island_boundary_bounding_boxes;
|
BoundingBoxes island_boundary_bounding_boxes;
|
||||||
|
Overhangs overhangs;
|
||||||
};
|
};
|
||||||
|
|
||||||
using Extrusions = std::vector<Extrusion>;
|
using Extrusions = std::vector<Extrusion>;
|
||||||
@ -59,16 +72,15 @@ std::vector<Extrusions> get_extrusions(tcb::span<const Slic3r::Layer *const> obj
|
|||||||
|
|
||||||
struct BoundedPolygon {
|
struct BoundedPolygon {
|
||||||
Polygon polygon;
|
Polygon polygon;
|
||||||
|
Overhangs overhangs;
|
||||||
BoundingBox bounding_box;
|
BoundingBox bounding_box;
|
||||||
bool is_hole{false};
|
bool is_hole{false};
|
||||||
double offset_inside{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using BoundedPolygons = std::vector<BoundedPolygon>;
|
using BoundedPolygons = std::vector<BoundedPolygon>;
|
||||||
|
|
||||||
BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance);
|
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> 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(
|
Vec2d get_polygon_normal(
|
||||||
const std::vector<Vec2d> &points, const std::size_t index, const double min_arm_length
|
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);
|
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.
|
* @brief Calculate overhang angle for each of the points over the previous layer perimeters.
|
||||||
*
|
*
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
|
#include <boost/hana/functional/overload_linearly.hpp>
|
||||||
|
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/Layer.hpp"
|
#include "libslic3r/Layer.hpp"
|
||||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||||
@ -15,114 +17,239 @@
|
|||||||
#include "tcbspan/span.hpp"
|
#include "tcbspan/span.hpp"
|
||||||
|
|
||||||
namespace Slic3r::Seams::Perimeters::Impl {
|
namespace Slic3r::Seams::Perimeters::Impl {
|
||||||
|
PerimeterPoints oversample_painted(
|
||||||
std::vector<Vec2d> oversample_painted(
|
PerimeterPoints &points,
|
||||||
const std::vector<Vec2d> &points,
|
|
||||||
const std::function<bool(Vec3f, double)> &is_painted,
|
const std::function<bool(Vec3f, double)> &is_painted,
|
||||||
const double slice_z,
|
const double slice_z,
|
||||||
const double max_distance
|
const double max_distance
|
||||||
) {
|
) {
|
||||||
std::vector<Vec2d> result;
|
PerimeterPoints result;
|
||||||
|
|
||||||
for (std::size_t index{0}; index < points.size(); ++index) {
|
for (std::size_t index{0}; index < points.size(); ++index) {
|
||||||
const Vec2d &point{points[index]};
|
result.push_back(points[index]);
|
||||||
|
|
||||||
result.push_back(point);
|
|
||||||
|
|
||||||
|
const Vec2d &point{points[index].position};
|
||||||
const std::size_t next_index{index == points.size() - 1 ? 0 : index + 1};
|
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 float next_point_distance{static_cast<float>((point - next_point).norm())};
|
||||||
const Vec2d middle_point{(point + next_point) / 2.0};
|
const Vec2d middle_point{(point + next_point) / 2.0};
|
||||||
Vec3f point3d{to_3d(middle_point, slice_z).cast<float>()};
|
Vec3f point3d{to_3d(middle_point, slice_z).cast<float>()};
|
||||||
if (is_painted(point3d, next_point_distance / 2.0)) {
|
if (is_painted(point3d, next_point_distance / 2.0)) {
|
||||||
for (const Vec2d &edge_point :
|
for (const Vec2d &edge_point : Geometry::oversample_edge(point, next_point, max_distance)) {
|
||||||
Geometry::oversample_edge(point, next_point, max_distance)) {
|
PerimeterPoint perimeter_point;
|
||||||
result.push_back(edge_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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
|
PerimeterPoints remove_redundant_points(
|
||||||
const std::vector<Vec2d> &points,
|
const PerimeterPoints &points,
|
||||||
const std::vector<PointType> &point_types,
|
|
||||||
const double tolerance
|
const double tolerance
|
||||||
) {
|
) {
|
||||||
std::vector<Vec2d> points_result;
|
PerimeterPoints result;
|
||||||
std::vector<PointType> point_types_result;
|
|
||||||
|
|
||||||
auto range_start{points.begin()};
|
auto range_start{points.begin()};
|
||||||
|
|
||||||
for (auto iterator{points.begin()}; iterator != points.end(); ++iterator) {
|
for (auto iterator{points.begin()}; iterator != points.end(); ++iterator) {
|
||||||
const std::int64_t index{std::distance(points.begin(), iterator)};
|
const std::int64_t index{std::distance(points.begin(), iterator)};
|
||||||
if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) {
|
if (
|
||||||
std::vector<Vec2d> simplification_result;
|
next(iterator) == points.end()
|
||||||
douglas_peucker(range_start, next(iterator), std::back_inserter(simplification_result), tolerance);
|
|| points[index].type != points[index + 1].type
|
||||||
|
|| points[index].classification != points[index + 1].classification
|
||||||
points_result.insert(
|
) {
|
||||||
points_result.end(), simplification_result.begin(), simplification_result.end()
|
douglas_peucker<double>(
|
||||||
);
|
range_start, next(iterator), std::back_inserter(result), tolerance,
|
||||||
const std::vector<PointType>
|
[](const PerimeterPoint &point) {
|
||||||
point_types_to_add(simplification_result.size(), point_types[index]);
|
return point.position;
|
||||||
point_types_result.insert(
|
}
|
||||||
point_types_result.end(), point_types_to_add.begin(), point_types_to_add.end()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
range_start = next(iterator);
|
range_start = next(iterator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {points_result, point_types_result};
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PointType> get_point_types(
|
PerimeterPoints get_point_types(
|
||||||
const std::vector<Vec2d> &positions,
|
const PerimeterPoints &perimeter_points,
|
||||||
const ModelInfo::Painting &painting,
|
const ModelInfo::Painting &painting,
|
||||||
const double slice_z,
|
const double slice_z,
|
||||||
const double painting_radius
|
const double painting_radius
|
||||||
) {
|
) {
|
||||||
std::vector<PointType> result;
|
PerimeterPoints result;
|
||||||
result.reserve(positions.size());
|
result.reserve(perimeter_points.size());
|
||||||
using std::transform, std::back_inserter;
|
using std::transform, std::back_inserter;
|
||||||
transform(
|
transform(
|
||||||
positions.begin(), positions.end(), back_inserter(result),
|
perimeter_points.begin(), perimeter_points.end(), back_inserter(result),
|
||||||
[&](const Vec2d &point) {
|
[&](PerimeterPoint point) {
|
||||||
const Vec3f point3d{to_3d(point.cast<float>(), static_cast<float>(slice_z))};
|
const Vec3f point3d{to_3d(point.position.cast<float>(), static_cast<float>(slice_z))};
|
||||||
if (painting.is_blocked(point3d, painting_radius)) {
|
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 point;
|
||||||
return PointType::enforcer;
|
|
||||||
}
|
|
||||||
return PointType::common;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PointClassification> classify_points(
|
void project_overhang(
|
||||||
const std::vector<double> &embeddings,
|
PerimeterPoints &points,
|
||||||
const std::optional<std::vector<double>> &overhangs,
|
const AABBTreeLines::LinesDistancer<Linef> &points_distancer,
|
||||||
const double overhang_threshold,
|
const Geometry::Overhang &overhang,
|
||||||
const double embedding_threshold
|
std::map<int, PerimeterPoints>& output
|
||||||
) {
|
) {
|
||||||
std::vector<PointClassification> result;
|
|
||||||
result.reserve(embeddings.size());
|
const auto [start_distance, start_line_index, start_point]{
|
||||||
using std::transform, std::back_inserter;
|
points_distancer.distance_from_lines_extra<false>(
|
||||||
transform(
|
unscaled(overhang.start)
|
||||||
embeddings.begin(), embeddings.end(), back_inserter(result),
|
)
|
||||||
[&, i = 0](const double embedding) mutable {
|
};
|
||||||
const unsigned index = i++;
|
|
||||||
if (overhangs && overhangs->operator[](index) > overhang_threshold) {
|
PerimeterPoint common_start_point{};
|
||||||
return PointClassification::overhang;
|
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) {
|
), overhang);
|
||||||
return PointClassification::embedded;
|
}
|
||||||
}
|
|
||||||
return PointClassification::common;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +314,21 @@ std::vector<AngleType> merge_angle_types(
|
|||||||
return result;
|
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::Impl
|
||||||
|
|
||||||
namespace Slic3r::Seams::Perimeters {
|
namespace Slic3r::Seams::Perimeters {
|
||||||
@ -322,10 +464,10 @@ Perimeter Perimeter::create_degenerate(
|
|||||||
|
|
||||||
Perimeter Perimeter::create(
|
Perimeter Perimeter::create(
|
||||||
const Polygon &polygon,
|
const Polygon &polygon,
|
||||||
|
const Geometry::Overhangs &overhangs,
|
||||||
const ModelInfo::Painting &painting,
|
const ModelInfo::Painting &painting,
|
||||||
const LayerInfo &layer_info,
|
const LayerInfo &layer_info,
|
||||||
const PerimeterParams ¶ms,
|
const PerimeterParams ¶ms
|
||||||
const double offset_inside
|
|
||||||
) {
|
) {
|
||||||
if (polygon.size() < 3) {
|
if (polygon.size() < 3) {
|
||||||
return Perimeter::create_degenerate(
|
return Perimeter::create_degenerate(
|
||||||
@ -344,44 +486,48 @@ Perimeter Perimeter::create(
|
|||||||
points = Geometry::unscaled(polygon.points);
|
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);
|
return painting.is_enforced(point, radius) || painting.is_blocked(point, radius);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
std::vector<Vec2d> perimeter_points{
|
perimeter_points = Impl::oversample_painted(
|
||||||
Impl::oversample_painted(points, is_painted, layer_info.slice_z, params.oversampling_max_distance)};
|
perimeter_points,
|
||||||
|
is_painted,
|
||||||
|
layer_info.slice_z,
|
||||||
|
params.oversampling_max_distance
|
||||||
|
);
|
||||||
|
|
||||||
std::vector<PointType> point_types{
|
perimeter_points = Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, params.painting_radius);
|
||||||
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.
|
perimeter_points = Impl::remove_redundant_points(perimeter_points, params.simplification_epsilon);
|
||||||
// Do not remomve redundant points for extrusions, becouse the redundant
|
|
||||||
// points can be on overhangs.
|
std::vector<Vec2d> positions{};
|
||||||
if (offset_inside < std::numeric_limits<double>::epsilon()) {
|
std::vector<PointType> point_types{};
|
||||||
// The following is optimization with significant impact. If in doubt, run
|
std::vector<PointClassification> point_classifications{};
|
||||||
// the "Seam benchmarks" test case in fff_print_tests.
|
|
||||||
std::tie(perimeter_points, point_types) =
|
for (const PerimeterPoint &point : perimeter_points) {
|
||||||
Impl::remove_redundant_points(perimeter_points, point_types, params.simplification_epsilon);
|
positions.push_back(point.position);
|
||||||
|
point_types.push_back(point.type);
|
||||||
|
point_classifications.push_back(point.classification);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<double> embeddings{
|
std::vector<double> smooth_angles{Geometry::get_vertex_angles(positions, params.smooth_angle_arm_length)};
|
||||||
Geometry::get_embedding_distances(perimeter_points, layer_info.distancer)};
|
std::vector<double> angles{Geometry::get_vertex_angles(positions, params.sharp_angle_arm_length)};
|
||||||
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<AngleType> angle_types{
|
std::vector<AngleType> angle_types{
|
||||||
Impl::get_angle_types(angles, params.convex_threshold, params.concave_threshold)};
|
Impl::get_angle_types(angles, params.convex_threshold, params.concave_threshold)};
|
||||||
std::vector<AngleType> smooth_angle_types{
|
std::vector<AngleType> smooth_angle_types{
|
||||||
Impl::get_angle_types(smooth_angles, params.convex_threshold, params.concave_threshold)};
|
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()};
|
const bool is_hole{polygon.is_clockwise()};
|
||||||
|
|
||||||
@ -389,7 +535,7 @@ Perimeter Perimeter::create(
|
|||||||
layer_info.slice_z,
|
layer_info.slice_z,
|
||||||
layer_info.index,
|
layer_info.index,
|
||||||
is_hole,
|
is_hole,
|
||||||
std::move(perimeter_points),
|
std::move(positions),
|
||||||
std::move(angles),
|
std::move(angles),
|
||||||
std::move(point_types),
|
std::move(point_types),
|
||||||
std::move(point_classifications),
|
std::move(point_classifications),
|
||||||
@ -417,7 +563,13 @@ LayerPerimeters create_perimeters(
|
|||||||
const Geometry::BoundedPolygon &bounded_polygon{layer[polygon_index]};
|
const Geometry::BoundedPolygon &bounded_polygon{layer[polygon_index]};
|
||||||
const LayerInfo &layer_info{layer_infos[layer_index]};
|
const LayerInfo &layer_info{layer_infos[layer_index]};
|
||||||
result[layer_index][polygon_index] = BoundedPerimeter{
|
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};
|
bounded_polygon.bounding_box};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -27,9 +27,9 @@ class Painting;
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace Slic3r::Seams::Perimeters {
|
namespace Slic3r::Seams::Perimeters {
|
||||||
enum class AngleType;
|
enum class AngleType { convex, concave, smooth };
|
||||||
enum class PointType;
|
enum class PointType { enforcer, blocker, common };
|
||||||
enum class PointClassification;
|
enum class PointClassification { overhang, embedded, common };
|
||||||
struct Perimeter;
|
struct Perimeter;
|
||||||
struct PerimeterParams;
|
struct PerimeterParams;
|
||||||
|
|
||||||
@ -55,6 +55,18 @@ using LayerInfos = std::vector<LayerInfo>;
|
|||||||
LayerInfos get_layer_infos(
|
LayerInfos get_layer_infos(
|
||||||
tcb::span<const Slic3r::Layer* const> object_layers, const double elephant_foot_compensation
|
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
|
||||||
|
|
||||||
namespace Slic3r::Seams::Perimeters::Impl {
|
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.
|
* @return All the points (original and added) in order along the edges.
|
||||||
*/
|
*/
|
||||||
std::vector<Vec2d> oversample_painted(
|
PerimeterPoints oversample_painted(
|
||||||
const std::vector<Vec2d> &points,
|
PerimeterPoints &points,
|
||||||
const std::function<bool(Vec3f, double)> &is_painted,
|
const std::function<bool(Vec3f, double)> &is_painted,
|
||||||
const double slice_z,
|
const double slice_z,
|
||||||
const double max_distance
|
const double max_distance
|
||||||
@ -83,9 +95,8 @@ std::vector<Vec2d> oversample_painted(
|
|||||||
*
|
*
|
||||||
* @param tolerance Douglas-Peucker epsilon.
|
* @param tolerance Douglas-Peucker epsilon.
|
||||||
*/
|
*/
|
||||||
std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
|
PerimeterPoints remove_redundant_points(
|
||||||
const std::vector<Vec2d> &points,
|
const PerimeterPoints &points,
|
||||||
const std::vector<PointType> &point_types,
|
|
||||||
const double tolerance
|
const double tolerance
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -93,12 +104,6 @@ std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
|
|||||||
|
|
||||||
namespace Slic3r::Seams::Perimeters {
|
namespace Slic3r::Seams::Perimeters {
|
||||||
|
|
||||||
enum class AngleType { convex, concave, smooth };
|
|
||||||
|
|
||||||
enum class PointType { enforcer, blocker, common };
|
|
||||||
|
|
||||||
enum class PointClassification { overhang, embedded, common };
|
|
||||||
|
|
||||||
struct PerimeterParams
|
struct PerimeterParams
|
||||||
{
|
{
|
||||||
double elephant_foot_compensation{};
|
double elephant_foot_compensation{};
|
||||||
@ -149,10 +154,10 @@ struct Perimeter
|
|||||||
|
|
||||||
static Perimeter create(
|
static Perimeter create(
|
||||||
const Polygon &polygon,
|
const Polygon &polygon,
|
||||||
|
const Geometry::Overhangs &overhangs,
|
||||||
const ModelInfo::Painting &painting,
|
const ModelInfo::Painting &painting,
|
||||||
const LayerInfo &layer_info,
|
const LayerInfo &layer_info,
|
||||||
const PerimeterParams ¶ms,
|
const PerimeterParams ¶ms
|
||||||
const double offset_inside
|
|
||||||
);
|
);
|
||||||
|
|
||||||
static Perimeter create_degenerate(
|
static Perimeter create_degenerate(
|
||||||
|
@ -40,8 +40,6 @@ ObjectLayerPerimeters get_perimeters(
|
|||||||
print_object->layers(), params.perimeter.elephant_foot_compensation
|
print_object->layers(), params.perimeter.elephant_foot_compensation
|
||||||
)};
|
)};
|
||||||
const std::vector<Geometry::BoundedPolygons> projected{
|
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)
|
Geometry::project_to_geometry(extrusions, params.max_distance)
|
||||||
};
|
};
|
||||||
Perimeters::LayerPerimeters perimeters{Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
|
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.max_distance = 5.0;
|
||||||
params.perimeter.oversampling_max_distance = 0.2;
|
params.perimeter.oversampling_max_distance = 0.2;
|
||||||
params.perimeter.embedding_threshold = 0.5;
|
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.simplification_epsilon = 0.001;
|
||||||
params.perimeter.smooth_angle_arm_length = 0.5;
|
params.perimeter.smooth_angle_arm_length = 0.5;
|
||||||
params.perimeter.sharp_angle_arm_length = 0.25;
|
params.perimeter.sharp_angle_arm_length = 0.25;
|
||||||
|
@ -26,7 +26,9 @@ std::vector<PerimeterSegment> get_segments(
|
|||||||
Vec2d previous_position{positions.front()};
|
Vec2d previous_position{positions.front()};
|
||||||
double distance{0.0};
|
double distance{0.0};
|
||||||
std::vector<PerimeterSegment> result;
|
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();
|
distance += (positions[index] - previous_position).norm();
|
||||||
previous_position = positions[index];
|
previous_position = positions[index];
|
||||||
|
|
||||||
@ -38,7 +40,7 @@ std::vector<PerimeterSegment> get_segments(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (current_begin) {
|
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 = std::nullopt;
|
||||||
current_begin_index = std::nullopt;
|
current_begin_index = std::nullopt;
|
||||||
@ -86,19 +88,21 @@ SeamChoice pick_random_point(
|
|||||||
|
|
||||||
double distance{0.0};
|
double distance{0.0};
|
||||||
std::size_t previous_index{segment.begin_index};
|
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]};
|
const Vec2d edge{positions[index] - positions[previous_index]};
|
||||||
|
|
||||||
if (distance + edge.norm() >= random_distance) {
|
if (distance + edge.norm() >= random_distance) {
|
||||||
|
std::size_t current_index{index};
|
||||||
if (random_distance - distance < std::numeric_limits<double>::epsilon()) {
|
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()) {
|
} else if (distance + edge.norm() - random_distance < std::numeric_limits<double>::epsilon()) {
|
||||||
previous_index = index;
|
previous_index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
const double remaining_distance{random_distance - distance};
|
const double remaining_distance{random_distance - distance};
|
||||||
const Vec2d position{positions[previous_index] + remaining_distance * edge.normalized()};
|
const Vec2d position{positions[previous_index] + remaining_distance * edge.normalized()};
|
||||||
return {previous_index, index, position};
|
return {previous_index, current_index, position};
|
||||||
}
|
}
|
||||||
|
|
||||||
distance += edge.norm();
|
distance += edge.norm();
|
||||||
|
@ -127,26 +127,6 @@ TEST_CASE("Vertex angle is rotation agnostic", "[Seams][SeamGeometry]") {
|
|||||||
CHECK(rotated_angles[1] == Approx(angles[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{
|
const Linesf lines{to_unscaled_linesf({ExPolygon{
|
||||||
scaled(Vec2d{0.0, 0.0}),
|
scaled(Vec2d{0.0, 0.0}),
|
||||||
scaled(Vec2d{1.0, 0.0}),
|
scaled(Vec2d{1.0, 0.0}),
|
||||||
|
@ -16,23 +16,26 @@ using namespace Catch;
|
|||||||
|
|
||||||
constexpr bool debug_files{false};
|
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]") {
|
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) {
|
auto is_painted{[](const Vec3f &position, float radius) {
|
||||||
return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius;
|
return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius;
|
||||||
}};
|
}};
|
||||||
std::vector<Vec2d> points{Perimeters::Impl::oversample_painted(
|
Perimeters::PerimeterPoints points{Perimeters::Impl::oversample_painted(
|
||||||
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.2
|
square, is_painted, 1.0, 0.2
|
||||||
)};
|
)};
|
||||||
|
|
||||||
REQUIRE(points.size() == 8);
|
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(
|
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);
|
CHECK(points.size() == 9);
|
||||||
}
|
}
|
||||||
@ -41,24 +44,35 @@ TEST_CASE("Remove redundant points", "[Seams][SeamPerimeters]") {
|
|||||||
using Perimeters::PointType;
|
using Perimeters::PointType;
|
||||||
using Perimeters::PointClassification;
|
using Perimeters::PointClassification;
|
||||||
|
|
||||||
std::vector<Vec2d> points{{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0},
|
Perimeters::PerimeterPoints points(9);
|
||||||
{3.0, 1.0}, {3.0, 2.0}, {0.0, 2.0}};
|
points[0].position = {0.0, 0.0};
|
||||||
std::vector<PointType> point_types{PointType::common,
|
points[0].type = PointType::common;
|
||||||
PointType::enforcer, // Should keep this.
|
points[1].position = {1.0, 0.0};
|
||||||
PointType::enforcer, // Should keep this.
|
points[1].type = PointType::enforcer; // Should keep
|
||||||
PointType::blocker,
|
points[2].position = {2.0, 0.0};
|
||||||
PointType::blocker, // Should remove this.
|
points[2].type = PointType::enforcer; // Should keep
|
||||||
PointType::blocker, PointType::common};
|
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::PerimeterPoints result{
|
||||||
Perimeters::Impl::remove_redundant_points(points, point_types, 0.1)};
|
Perimeters::Impl::remove_redundant_points(points, 0.1)};
|
||||||
|
|
||||||
REQUIRE(resulting_points.size() == 6);
|
REQUIRE(result.size() == 8);
|
||||||
REQUIRE(resulting_point_types.size() == 6);
|
CHECK((result[3].position - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
|
||||||
CHECK((resulting_points[3] - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
|
CHECK((result[4].position - Vec2d{3.0, 1.1}).norm() == Approx(0.0));
|
||||||
CHECK((resulting_points[4] - Vec2d{3.0, 2.0}).norm() == Approx(0.0));
|
CHECK(result[3].type == PointType::blocker);
|
||||||
CHECK(resulting_point_types[3] == PointType::blocker);
|
CHECK(result[4].type == PointType::blocker);
|
||||||
CHECK(resulting_point_types[4] == PointType::blocker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {
|
TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {
|
||||||
|
@ -32,7 +32,7 @@ Perimeters::Perimeter get_perimeter() {
|
|||||||
}
|
}
|
||||||
} // namespace RandomTest
|
} // 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);
|
std::vector<std::size_t> bins(bin_count);
|
||||||
const double bin_size{(max - min) / bin_count};
|
const double bin_size{(max - min) / bin_count};
|
||||||
const double expected_frequncy{static_cast<double>(data.size()) / 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();
|
return choice->position.x();
|
||||||
});
|
});
|
||||||
const std::size_t degrees_of_freedom{10};
|
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);
|
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};
|
double extrusion_width{0.2};
|
||||||
|
|
||||||
ProjectionFixture() {
|
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