SPE-2495: Do not put scarft seam start on overhangs and blockers

Update the function offset_along_lines to offset along
perimeters and add a stop condition.
This commit is contained in:
Martin Šach 2025-01-27 14:02:33 +01:00 committed by Lukas Matena
parent b037828aef
commit 61291d9219
11 changed files with 247 additions and 155 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,17 @@ enum class PointClassification { overhang, embedded, common };
struct Perimeter;
struct PerimeterParams;
/**
* When previous_index == next_index, the point is at the point.
* Otherwise the point is at the edge.
*/
struct PointOnPerimeter
{
std::size_t previous_index{};
std::size_t next_index{};
Vec2d position{Vec2d::Zero()};
};
struct LayerInfo
{
static LayerInfo create(
@ -209,6 +220,19 @@ inline std::vector<Vec2d> extract_points(
return result;
}
using Seams::Geometry::PointOnLine;
std::optional<PointOnPerimeter> offset_along_perimeter(
const PointOnPerimeter &point,
const Perimeter& perimeter,
const double offset,
const Seams::Geometry::Direction1D direction,
const std::function<bool(const Perimeter&, const std::size_t)> &early_stop_condition
);
unsigned get_point_value(const PointType point_type, const PointClassification point_classification);
} // namespace Slic3r::Seams::Perimeters
#endif // libslic3r_SeamPerimeters_hpp_

View File

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

View File

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

View File

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

View File

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