OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::SeamPlacerImpl Namespace Reference

Classes

class  Frame
 Coordinate frame. More...
struct  CoordinateFunctor
struct  PaintedContour
struct  GlobalModelInfo
struct  PaintedInterval
struct  SeamComparator
struct  Perimeter
struct  SeamCandidate
struct  SeamCandidateCoordinateFunctor

Enumerations

enum class  EnforcedBlockedSeamPoint { Blocked = 0 , Neutral = 1 , Enforced = 2 }

Functions

template<typename T>
int sgn (T val)
float gauss (float value, float mean_x_coord, float mean_value, float falloff_speed)
float compute_angle_penalty (float ccw_angle)
Vec3f sample_sphere_uniform (const Vec2f &samples)
Vec3f sample_hemisphere_uniform (const Vec2f &samples)
Vec3f sample_power_cosine_hemisphere (const Vec2f &samples, float power)
std::vector< float > raycast_visibility (const AABBTreeIndirect::Tree< 3, float > &raycasting_tree, const indexed_triangle_set &triangles, const TriangleSetSamples &samples, size_t negative_volumes_start_index, SeamPosition seam_position=spAligned)
std::vector< float > calculate_polygon_angles_at_vertices (const Polygon &polygon, const std::vector< float > &lengths, float min_arm_length)
Polygons extract_perimeter_polygons (const Layer *layer, std::vector< const LayerRegion * > &corresponding_regions_out, std::vector< std::optional< bool > > &boundary_types_out, bool has_precise_seam_modifiers)
bool fully_enforced_loop (const std::vector< Vec2d > &loop, double z, double reach, const GlobalModelInfo &info)
std::vector< PaintedInterval > exact_painted_intervals (const std::vector< Vec2d > &loop, const std::vector< double > &arc, double z, double reach, const PaintedContour &contour)
void process_perimeter_polygon (const Polygon &orig_polygon, float z_coord, const LayerRegion *region, const GlobalModelInfo &global_model_info, PrintObjectSeamData::LayerSeams &result, PreciseSeam::PreciseSeamWarnings *warnings=nullptr, std::optional< bool > boundary_hole=std::nullopt)
void compute_global_occlusion (GlobalModelInfo &result, const PrintObject *po, std::function< void(void)> throw_if_canceled, SeamPosition seam_position=spAligned)
void append_painted_contour (PaintedContour &result, const indexed_triangle_set &painted)
void gather_enforcers_blockers (GlobalModelInfo &result, const PrintObject *po)
void pick_seam_point (std::vector< SeamCandidate > &perimeter_points, size_t start_index, const SeamComparator &comparator)
size_t pick_nearest_seam_point_index (const std::vector< SeamCandidate > &perimeter_points, size_t start_index, const Vec2f &preffered_location)
void pick_random_seam_point (const std::vector< SeamCandidate > &perimeter_points, size_t start_index)
float smoothstep01 (float x)
float enforced_corner_influence (const std::vector< SeamCandidate > &points, size_t index, float protect, float fade_end)
Vec2d edge_normal (const Vec2d &a, const Vec2d &b)
Vec3f place_on_loop (const std::vector< SeamCandidate > &points, size_t index, const Vec3f &target)

Variables

constexpr double enforced_end_tolerance = 1e-3
constexpr float enforced_corner_angle_min = 15.0f * float(PI) / 180.0f
constexpr float enforced_corner_angle_max = 30.0f * float(PI) / 180.0f
constexpr float enforced_corner_fade_factor = 2.0f

Class Documentation

◆ Slic3r::SeamPlacerImpl::PaintedInterval

struct Slic3r::SeamPlacerImpl::PaintedInterval
Class Members
double begin
double end

◆ Slic3r::SeamPlacerImpl::Perimeter

struct Slic3r::SeamPlacerImpl::Perimeter
Class Members
size_t contour_index = 0
bool contour_reliable = false
size_t end_index {}
bool exact_blocked_ends = false
bool exact_enforced_ends = false
Vec3f final_seam_position = Vec3f::Zero()
bool finalized = false
float flow_width {}
bool is_hole = false
size_t precise_seam_index {}
optional< Vec3f > precise_seam_point
size_t seam_index {}
size_t start_index {}

Enumeration Type Documentation

◆ EnforcedBlockedSeamPoint

Enumerator
Blocked 
Neutral 
Enforced 
38 {
39 Blocked = 0,
40 Neutral = 1,
41 Enforced = 2,
42};

Function Documentation

◆ append_painted_contour()

void Slic3r::SeamPlacerImpl::append_painted_contour ( PaintedContour & result,
const indexed_triangle_set & painted )
1079 {
1080 const auto edge_key = [](int a, int b) { return (uint64_t(std::min(a, b)) << 32) | uint64_t(std::max(a, b)); };
1081 std::unordered_map<uint64_t, int> uses;
1082 for (const stl_triangle_vertex_indices &face : painted.indices)
1083 for (int i = 0; i < 3; ++i)
1084 ++uses[edge_key(face[i], face[(i + 1) % 3])];
1085 for (const stl_triangle_vertex_indices &face : painted.indices) {
1086 const Vec3d v[3] = {painted.vertices[face[0]].cast<double>(), painted.vertices[face[1]].cast<double>(),
1087 painted.vertices[face[2]].cast<double>()};
1088 const Vec3d normal = (v[1] - v[0]).cross(v[2] - v[0]);
1089 for (int i = 0; i < 3; ++i) {
1090 const Vec3d inward = normal.cross(v[(i + 1) % 3] - v[i]);
1091 if (uses[edge_key(face[i], face[(i + 1) % 3])] == 1 && inward.squaredNorm() > 0.0) {
1092 result.edges.emplace_back(v[i], v[(i + 1) % 3]);
1093 result.inward.emplace_back(inward.normalized());
1094 }
1095 }
1096 }
1097}
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition Point.hpp:68
T cross(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:168
Point normal(Point pt, double scale)
Definition TreeSupport.cpp:112
std::vector< Linef3 > edges
Definition SeamPlacer.cpp:326
std::vector< Vec3d > inward
Definition SeamPlacer.cpp:327

References append_painted_contour(), Slic3r::cross(), Slic3r::SeamPlacerImpl::PaintedContour::edges, Slic3r::SeamPlacerImpl::PaintedContour::inward, and Slic3r::normal().

Referenced by append_painted_contour(), and gather_enforcers_blockers().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ calculate_polygon_angles_at_vertices()

std::vector< float > Slic3r::SeamPlacerImpl::calculate_polygon_angles_at_vertices ( const Polygon & polygon,
const std::vector< float > & lengths,
float min_arm_length )
261 {
262 std::vector<float> result(polygon.size());
263
264 if (polygon.size() == 1) {
265 result[0] = 0.0f;
266 }
267
268 size_t idx_prev = 0;
269 size_t idx_curr = 0;
270 size_t idx_next = 0;
271
272 float distance_to_prev = 0;
273 float distance_to_next = 0;
274
275 //push idx_prev far enough back as initialization
276 while (distance_to_prev < min_arm_length) {
277 idx_prev = Slic3r::prev_idx_modulo(idx_prev, polygon.size());
278 distance_to_prev += lengths[idx_prev];
279 }
280
281 for (size_t _i = 0; _i < polygon.size(); ++_i) {
282 // pull idx_prev to current as much as possible, while respecting the min_arm_length
283 while (distance_to_prev - lengths[idx_prev] > min_arm_length) {
284 distance_to_prev -= lengths[idx_prev];
285 idx_prev = Slic3r::next_idx_modulo(idx_prev, polygon.size());
286 }
287
288 //push idx_next forward as far as needed
289 while (distance_to_next < min_arm_length) {
290 distance_to_next += lengths[idx_next];
291 idx_next = Slic3r::next_idx_modulo(idx_next, polygon.size());
292 }
293
294 // Calculate angle between idx_prev, idx_curr, idx_next.
295 const Point &p0 = polygon.points[idx_prev];
296 const Point &p1 = polygon.points[idx_curr];
297 const Point &p2 = polygon.points[idx_next];
298 result[idx_curr] = float(angle(p1 - p0, p2 - p1));
299
300 // increase idx_curr by one
301 float curr_distance = lengths[idx_curr];
302 idx_curr++;
303 distance_to_prev += curr_distance;
304 distance_to_next -= curr_distance;
305 }
306
307 return result;
308}
Points points
Definition MultiPoint.hpp:22
size_t size() const
Definition MultiPoint.hpp:50
Kernel::Point_3 Point
Definition MeshRepair.cpp:81
double angle(const Eigen::MatrixBase< Derived > &v1, const Eigen::MatrixBase< Derived2 > &v2)
Definition Point.hpp:138
INDEX_TYPE prev_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count)
Definition Utils.hpp:445
INDEX_TYPE next_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count)
Definition Utils.hpp:453

References Slic3r::angle(), calculate_polygon_angles_at_vertices(), Slic3r::next_idx_modulo(), Slic3r::MultiPoint::points, Slic3r::prev_idx_modulo(), and Slic3r::MultiPoint::size().

Referenced by calculate_polygon_angles_at_vertices(), and process_perimeter_polygon().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ compute_angle_penalty()

float Slic3r::SeamPlacerImpl::compute_angle_penalty ( float ccw_angle)
82 {
83 // This function is used:
84 // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x)))
85 // looks scary, but it is gaussian combined with sigmoid,
86 // so that concave points have much smaller penalty over convex ones
87 // https://github.com/prusa3d/PrusaSlicer/tree/master/doc/seam_placement/corner_penalty_function.png
88 return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) +
89 1.0f / (2 + std::exp(-ccw_angle));
90}
float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed)
Definition SeamPlacer.cpp:75

References gauss().

Referenced by Slic3r::SeamPlacerImpl::SeamComparator::is_first_not_much_worse().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ compute_global_occlusion()

void Slic3r::SeamPlacerImpl::compute_global_occlusion ( GlobalModelInfo & result,
const PrintObject * po,
std::function< void(void)> throw_if_canceled,
SeamPosition seam_position = spAligned )
988 {
989 BOOST_LOG_TRIVIAL(debug)
990 << "SeamPlacer: gather occlusion meshes: start";
991 auto obj_transform = po->trafo_sliced();
992 indexed_triangle_set triangle_set;
993 indexed_triangle_set negative_volumes_set;
994 //add all parts
995 for (const ModelVolume *model_volume : po->model_object()->volumes) {
996 if (model_volume->type() == ModelVolumeType::MODEL_PART
997 || model_volume->type() == ModelVolumeType::NEGATIVE_VOLUME) {
998 auto model_transformation = model_volume->get_matrix();
999 indexed_triangle_set model_its = model_volume->mesh().its;
1000 // ORCA fix (not related to Precise Seam, discovered during its development):
1001 // Mirror transforms have negative determinant which flips triangle winding.
1002 // fix_left_handed=true swaps indices to keep normals pointing outward.
1003 its_transform(model_its, model_transformation, true);
1004 if (model_volume->type() == ModelVolumeType::MODEL_PART) {
1005 its_merge(triangle_set, model_its);
1006 } else {
1007 its_merge(negative_volumes_set, model_its);
1008 }
1009 }
1010 }
1011 throw_if_canceled();
1012
1013 BOOST_LOG_TRIVIAL(debug)
1014 << "SeamPlacer: gather occlusion meshes: end";
1015
1016 BOOST_LOG_TRIVIAL(debug)
1017 << "SeamPlacer: decimate: start";
1020
1021 size_t negative_volumes_start_index = triangle_set.indices.size();
1022 its_merge(triangle_set, negative_volumes_set);
1023 // ORCA fix (not related to Precise Seam, discovered during its development):
1024 // Object-level transform may include mirroring (negative determinant),
1025 // which inverts triangle winding. fix_left_handed=true corrects this
1026 // so visibility ray sampling sees outward-facing normals.
1027 its_transform(triangle_set, obj_transform, true);
1028 BOOST_LOG_TRIVIAL(debug)
1029 << "SeamPlacer: decimate: end";
1030
1031 BOOST_LOG_TRIVIAL(debug)
1032 << "SeamPlacer: Compute visibility sample points: start";
1033
1035 triangle_set);
1037 result.mesh_samples_tree = KDTreeIndirect<3, float, CoordinateFunctor>(result.mesh_samples_coordinate_functor,
1038 result.mesh_samples.positions.size());
1039
1040 // The following code determines search area for random visibility samples on the mesh when calculating visibility of each perimeter point
1041 // number of random samples in the given radius (area) is approximately poisson distribution
1042 // to compute ideal search radius (area), we use exponential distribution (complementary distr to poisson)
1043 // parameters of exponential distribution to compute area that will have with probability="probability" more than given number of samples="samples"
1044 float probability = 0.9f;
1045 float samples = 4;
1047 // exponential probability distrubtion function is : f(x) = P(X > x) = e^(l*x) where l is the rate parameter (computed as 1/u where u is mean value)
1048 // probability that sampled area A with S samples contains more than samples count:
1049 // P(S > samples in A) = e^-(samples/(density*A)); express A:
1050 float search_area = samples / (-logf(probability) * density);
1051 float search_radius = sqrt(search_area / PI);
1052 result.mesh_samples_radius = search_radius;
1053
1054 BOOST_LOG_TRIVIAL(debug)
1055 << "SeamPlacer: Compute visiblity sample points: end";
1056 throw_if_canceled();
1057
1058 BOOST_LOG_TRIVIAL(debug)
1059 << "SeamPlacer: Mesh sample raidus: " << result.mesh_samples_radius;
1060
1061 BOOST_LOG_TRIVIAL(debug)
1062 << "SeamPlacer: build AABB tree: start";
1063 auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices,
1064 triangle_set.indices);
1065
1066 throw_if_canceled();
1067 BOOST_LOG_TRIVIAL(debug)
1068 << "SeamPlacer: build AABB tree: end";
1069 result.mesh_samples_visibility = raycast_visibility(raycasting_tree, triangle_set, result.mesh_samples,
1070 negative_volumes_start_index, seam_position);
1071 throw_if_canceled();
1072#ifdef DEBUG_FILES
1073 result.debug_export(triangle_set);
1074#endif
1075}
ModelVolumePtrs volumes
Definition Model.hpp:393
Definition Model.hpp:832
const ModelObject * model_object() const
Definition PrintBase.hpp:346
Transform3d trafo_sliced() const
Definition PrintObjectSlice.cpp:1735
static constexpr size_t raycasting_visibility_samples_count
Definition SeamPlacer.hpp:135
static constexpr size_t fast_decimation_triangle_count_target
Definition SeamPlacer.hpp:136
static constexpr double PI
Definition libslic3r.h:81
Tree< 3, typename VertexType::Scalar > build_aabb_tree_over_indexed_triangle_set(const std::vector< VertexType > &vertices, const std::vector< IndexedFaceType > &faces, const typename VertexType::Scalar eps=0)
Definition AABBTreeIndirect.hpp:677
std::vector< float > raycast_visibility(const AABBTreeIndirect::Tree< 3, float > &raycasting_tree, const indexed_triangle_set &triangles, const TriangleSetSamples &samples, size_t negative_volumes_start_index, SeamPosition seam_position=spAligned)
Definition SeamPlacer.cpp:159
std::vector< Vec3f > positions
Definition TriangleSetSampling.hpp:13
@ MODEL_PART
Definition Model.hpp:357
@ NEGATIVE_VOLUME
Definition Model.hpp:358
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B)
Definition TriangleMesh.cpp:1448
float total_area
Definition TriangleSetSampling.hpp:12
void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_count)
Definition ShortEdgeCollapse.cpp:15
TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const indexed_triangle_set &triangle_set)
Definition TriangleSetSampling.cpp:14
Definition SeamPlacer.cpp:310
KDTreeIndirect< 3, float, CoordinateFunctor > mesh_samples_tree
Definition SeamPlacer.cpp:351
float mesh_samples_radius
Definition SeamPlacer.cpp:352
std::vector< float > mesh_samples_visibility
Definition SeamPlacer.cpp:349
TriangleSetSamples mesh_samples
Definition SeamPlacer.cpp:348
CoordinateFunctor mesh_samples_coordinate_functor
Definition SeamPlacer.cpp:350

References Slic3r::AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(), compute_global_occlusion(), Slic3r::SeamPlacer::fast_decimation_triangle_count_target, Slic3r::its_merge(), Slic3r::its_short_edge_collpase(), Slic3r::SeamPlacerImpl::GlobalModelInfo::mesh_samples, Slic3r::SeamPlacerImpl::GlobalModelInfo::mesh_samples_coordinate_functor, Slic3r::SeamPlacerImpl::GlobalModelInfo::mesh_samples_radius, Slic3r::SeamPlacerImpl::GlobalModelInfo::mesh_samples_tree, Slic3r::SeamPlacerImpl::GlobalModelInfo::mesh_samples_visibility, Slic3r::PrintObjectBase::model_object(), Slic3r::MODEL_PART, Slic3r::NEGATIVE_VOLUME, PI, Slic3r::TriangleSetSamples::positions, raycast_visibility(), Slic3r::SeamPlacer::raycasting_visibility_samples_count, Slic3r::sample_its_uniform_parallel(), Slic3r::spAligned, Slic3r::TriangleSetSamples::total_area, Slic3r::PrintObject::trafo_sliced(), and Slic3r::ModelObject::volumes.

Referenced by compute_global_occlusion().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ edge_normal()

Vec2d Slic3r::SeamPlacerImpl::edge_normal ( const Vec2d & a,
const Vec2d & b )
1490 {
1491 const Vec2d d = b - a;
1492 const double length = d.norm();
1493 return length > 0.0 ? Vec2d(-d.y() / length, d.x() / length) : Vec2d::Zero();
1494}
double length(const Points &pts)
Definition MultiPoint.hpp:181
Eigen::Matrix< double, 2, 1, Eigen::DontAlign > Vec2d
Definition Point.hpp:67

References edge_normal(), and Slic3r::length().

Referenced by edge_normal(), and place_on_loop().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ enforced_corner_influence()

float Slic3r::SeamPlacerImpl::enforced_corner_influence ( const std::vector< SeamCandidate > & points,
size_t index,
float protect,
float fade_end )
1462 {
1463 const Perimeter &perimeter = points[index].perimeter;
1464 const size_t start = perimeter.start_index;
1465 const size_t count = perimeter.end_index - start;
1466 const auto contribution = [&](size_t i, float distance) {
1467 const float corner = smoothstep01((std::abs(points[i].local_ccw_angle) - enforced_corner_angle_min) /
1469 const float weight = distance <= protect ? 1.0f : 1.0f - smoothstep01((distance - protect) / (fade_end - protect));
1470 return corner * weight;
1471 };
1472 float influence = contribution(index, 0.0f);
1473 for (const bool forward : { false, true }) {
1474 size_t i = index;
1475 float distance = 0.0f;
1476 // At most count - 1 steps per direction, so short loops and zero-length edges terminate.
1477 for (size_t step = 1; step < count && influence < 1.0f; ++step) {
1478 const size_t j = start + (forward ? next_idx_modulo(i - start, count) : prev_idx_modulo(i - start, count));
1479 distance += (points[j].position - points[i].position).head<2>().norm();
1480 if (distance >= fade_end)
1481 break;
1482 influence = std::max(influence, contribution(j, distance));
1483 i = j;
1484 }
1485 }
1486 return influence;
1487}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
size_t end_index
Definition SeamPlacer.hpp:50
float smoothstep01(float x)
Definition SeamPlacer.cpp:1455
constexpr float enforced_corner_angle_max
Definition SeamPlacer.cpp:1451
constexpr float enforced_corner_angle_min
Definition SeamPlacer.cpp:1450
size_t start_index
Definition SeamPlacer.hpp:49
Definition SeamPlacer.hpp:45

References count, Slic3r::SeamPlacerImpl::Perimeter::end_index, enforced_corner_angle_max, enforced_corner_angle_min, enforced_corner_influence(), Slic3r::next_idx_modulo(), Slic3r::prev_idx_modulo(), smoothstep01(), and Slic3r::SeamPlacerImpl::Perimeter::start_index.

Referenced by enforced_corner_influence().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ exact_painted_intervals()

std::vector< PaintedInterval > Slic3r::SeamPlacerImpl::exact_painted_intervals ( const std::vector< Vec2d > & loop,
const std::vector< double > & arc,
double z,
double reach,
const PaintedContour & contour )
602 {
603 const size_t count = loop.size();
604 if (count < 3 || contour.edges.empty())
605 return {};
606 Vec2d min = loop.front(), max = loop.front();
607 for (const Vec2d &p : loop) {
608 min = min.cwiseMin(p);
609 max = max.cwiseMax(p);
610 }
611 const auto crossings = contour.crossings(z, min - Vec2d(reach, reach), max + Vec2d(reach, reach));
612 // Direction of the first edge from edge i on, forward or backward, that is longer than the tolerance: loops
613 // repeat their first point and the shared ends of their paths.
614 const auto direction = [&](size_t i, bool forward) {
615 for (size_t step = 0; step < count; ++step, i = forward ? (i + 1) % count : (i + count - 1) % count) {
616 const Vec2d d = loop[(i + 1) % count] - loop[i];
617 if (d.norm() >= enforced_end_tolerance)
618 return Vec2d(d.normalized());
619 }
620 return Vec2d(Vec2d::Zero());
621 };
622 std::vector<std::pair<double, bool>> ends; // Position along the loop; true if paint lies ahead.
623 for (const auto &[point, inward] : crossings) {
624 double best = std::numeric_limits<double>::max(), position = 0.0;
625 Vec2d tangent = Vec2d::Zero();
626 for (size_t i = 0; i < count; ++i) {
627 const Vec2d a = loop[i], ab = loop[(i + 1) % count] - a;
628 const double length2 = ab.squaredNorm();
629 const double t = length2 > 0.0 ? std::clamp((point - a).dot(ab) / length2, 0.0, 1.0) : 0.0;
630 const double distance2 = (a + t * ab - point).squaredNorm();
631 if (distance2 < best) {
632 best = distance2;
633 const double along = t * (arc[i + 1] - arc[i]);
634 position = arc[i] + along;
635 // At a vertex the loop turns, so its direction there is the mean of the two edges; a crossing at a
636 // corner may land a rounding error off the vertex, on either edge.
637 const size_t vertex = along < enforced_end_tolerance ? i :
638 arc[i + 1] - position < enforced_end_tolerance ? (i + 1) % count :
639 count;
640 tangent = vertex < count ? direction((vertex + count - 1) % count, false) + direction(vertex, true) :
641 direction(i, true);
642 }
643 }
644 if (best > reach * reach)
645 continue;
646 const double side = inward.dot(tangent);
647 if (std::abs(side) < 1e-6)
648 return {}; // The contour runs along the loop here: no clear end.
649 ends.emplace_back(position, side > 0.0);
650 }
651 if (ends.empty() || ends.size() % 2 != 0)
652 return {};
653 std::sort(ends.begin(), ends.end());
654 for (size_t i = 0; i < ends.size(); ++i)
655 if (ends[i].second == ends[(i + 1) % ends.size()].second)
656 return {}; // Paint must start and stop in turn along the loop.
657 const size_t first = ends.front().second ? 0 : 1;
658 std::vector<PaintedInterval> intervals;
659 for (size_t i = first; i < first + ends.size(); i += 2) {
660 const double begin = ends[i % ends.size()].first;
661 double end = ends[(i + 1) % ends.size()].first;
662 if (end < begin)
663 end += arc.back();
664 intervals.push_back({begin, end});
665 }
666 return intervals;
667}
T * begin(Slic3r::Mat< N, M, T > &mat)
Definition Point.hpp:838
T * end(Slic3r::Mat< N, M, T > &mat)
Definition Point.hpp:841
void arc(std::vector< std::pair< Vec2d, Vec2d > > &out, double cx, double cy, double rx, double ry, double a0, double a1, int n=14)
Definition DesignSketchTool.cpp:7759
constexpr double enforced_end_tolerance
Definition SeamPlacer.cpp:554
T dot(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:154
const Polygon & contour(const ExPolygon &p)
Definition AGGRaster.hpp:30

References Slic3r::contour(), count, Slic3r::dot(), enforced_end_tolerance, and exact_painted_intervals().

Referenced by exact_painted_intervals(), and process_perimeter_polygon().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ extract_perimeter_polygons()

Polygons Slic3r::SeamPlacerImpl::extract_perimeter_polygons ( const Layer * layer,
std::vector< const LayerRegion * > & corresponding_regions_out,
std::vector< std::optional< bool > > & boundary_types_out,
bool has_precise_seam_modifiers )
476 {
477 Polygons polygons;
478 const auto boundary_type = [](const ExtrusionEntity *entity) -> std::optional<bool> {
479 if (!entity->is_loop()) return std::nullopt;
480 return (static_cast<const ExtrusionLoop*>(entity)->loop_role() & elrHole) != 0;
481 };
482 for (const LayerRegion *layer_region : layer->regions()) {
483 for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) {
484 if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters
485 const size_t collection_begin = polygons.size(); // The fallback below applies per collection, not per layer.
486 for (const ExtrusionEntity *perimeter : static_cast<const ExtrusionEntityCollection*>(ex_entity)->entities) {
487 ExtrusionRole role = perimeter->role();
488 if (perimeter->is_loop()) {
489 for (const ExtrusionPath &path : static_cast<const ExtrusionLoop*>(perimeter)->paths) {
490 if (path.role() == ExtrusionRole::erExternalPerimeter) {
492 }
493 }
494 // A fully overhanging outer loop has no external paths but keeps inset_idx 0.
495 if (perimeter->inset_idx == 0)
497 }
498
500 Points p;
501 perimeter->collect_points(p);
502 polygons.emplace_back(std::move(p));
503 corresponding_regions_out.push_back(layer_region);
504 boundary_types_out.push_back(boundary_type(perimeter));
505 }
506 }
507 if (polygons.size() == collection_begin) {
508 Points p;
509 ex_entity->collect_points(p);
510 polygons.emplace_back(std::move(p));
511 corresponding_regions_out.push_back(layer_region);
512 boundary_types_out.push_back(boundary_type(ex_entity));
513 }
514 } else {
515 Points p;
516 ex_entity->collect_points(p);
517 polygons.emplace_back(std::move(p));
518 corresponding_regions_out.push_back(layer_region);
519 boundary_types_out.push_back(boundary_type(ex_entity));
520 }
521 }
522 }
523
524 if (has_precise_seam_modifiers) {
525 // Extrusion loops repeat their start point; Polygon closes the contour implicitly.
526 // Normalize here for Precise Seam without changing ordinary seam candidates.
527 for (Polygon &polygon : polygons) {
528 // Adjacent extrusion paths share endpoints; zero-length edges would prevent refinement at their junctions.
529 // Remove only consecutive duplicates, preserving distinct visits to a self-touching contour point.
530 polygon.points.erase(std::unique(polygon.points.begin(), polygon.points.end()), polygon.points.end());
531 while (polygon.size() > 1 && polygon.points.front() == polygon.points.back())
532 polygon.points.pop_back();
533 }
534 }
535
536 if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point
537 // it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway
538 polygons.emplace_back(Points{ { 0, 0 } });
539 corresponding_regions_out.push_back(nullptr);
540 boundary_types_out.push_back(std::nullopt);
541 }
542
543 return polygons;
544}
Definition ExtrusionEntityCollection.hpp:33
Definition ExtrusionEntity.hpp:121
virtual bool is_collection() const
Definition ExtrusionEntity.hpp:124
virtual void collect_points(Points &dst) const =0
Definition ExtrusionEntity.hpp:468
Definition ExtrusionEntity.hpp:175
Definition Layer.hpp:48
const LayerRegionPtrs & regions() const
Definition Layer.hpp:185
@ elrHole
Definition ExtrusionEntity.hpp:55
ExtrusionRole
Definition ExtrusionEntity.hpp:26
@ erExternalPerimeter
Definition ExtrusionEntity.hpp:29
std::vector< Polygon, PointsAllocator< Polygon > > Polygons
Definition Polygon.hpp:22
std::vector< Point, PointsAllocator< Point > > Points
Definition Point.hpp:73

References Slic3r::ExtrusionEntity::collect_points(), Slic3r::elrHole, Slic3r::erExternalPerimeter, extract_perimeter_polygons(), Slic3r::ExtrusionEntity::is_collection(), and Slic3r::Layer::regions().

Referenced by extract_perimeter_polygons().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fully_enforced_loop()

bool Slic3r::SeamPlacerImpl::fully_enforced_loop ( const std::vector< Vec2d > & loop,
double z,
double reach,
const GlobalModelInfo & info )
557 {
558 if (loop.size() < 3 || info.enforcers.empty())
559 return false;
560 if (info.non_enforcers.empty())
561 return true; // Every relevant model surface is green, so paint adds no seam information.
562 if (reach <= enforced_end_tolerance)
563 return false;
564 Vec2d min = loop.front(), max = loop.front();
565 for (const Vec2d &point : loop) {
566 min = min.cwiseMin(point);
567 max = max.cwiseMax(point);
568 }
569 // A conservative box also rejects tiny gaps and a nearby opposite unpainted wall.
570 const Eigen::AlignedBox<float, 3> box(Vec3f(float(min.x() - reach), float(min.y() - reach), float(z - reach)),
571 Vec3f(float(max.x() + reach), float(max.y() + reach), float(z + reach)));
572 bool non_green_nearby = false;
574 non_green_nearby = true;
575 return false; // Stop on the first uncertain surface rather than infer a full painted ring.
576 });
577 if (non_green_nearby)
578 return false;
579 const auto covered = [&](const auto &self, const Vec3d &a, const Vec3d &b, unsigned depth) -> bool {
580 const Vec3d middle = 0.5 * (a + b);
581 // A paint point this close to the midpoint covers the entire segment by the triangle inequality.
582 const double radius = reach - 0.5 * (b - a).norm() - enforced_end_tolerance;
583 if (radius > 0.0 && info.is_enforced(middle.cast<float>(), float(radius)))
584 return true;
585 if (depth == 8)
586 return false; // A bounded proof failure preserves the existing conservative paint behavior.
587 return self(self, a, middle, depth + 1) && self(self, middle, b, depth + 1);
588 };
589 for (size_t i = 0; i < loop.size(); ++i) {
590 const Vec2d &a = loop[i], &b = loop[(i + 1) % loop.size()];
591 if (!covered(covered, Vec3d(a.x(), a.y(), z), Vec3d(b.x(), b.y(), z), 0))
592 return false;
593 }
594 return true;
595}
auto intersecting(const G &g)
Definition AABBTreeIndirect.hpp:925
void traverse(const Tree< Dims, T > &tree, Predicate &&pred, Fn &&callback)
Definition AABBTreeIndirect.hpp:986
Eigen::Matrix< float, 3, 1, Eigen::DontAlign > Vec3f
Definition Point.hpp:65
indexed_triangle_set enforcers
Definition SeamPlacer.cpp:354
AABBTreeIndirect::Tree< 3, float > non_enforcers_tree
Definition SeamPlacer.cpp:360
indexed_triangle_set non_enforcers
Definition SeamPlacer.cpp:357
bool is_enforced(const Vec3f &position, float radius) const
Definition SeamPlacer.cpp:374

References enforced_end_tolerance, Slic3r::SeamPlacerImpl::GlobalModelInfo::enforcers, fully_enforced_loop(), Slic3r::AABBTreeIndirect::intersecting(), Slic3r::SeamPlacerImpl::GlobalModelInfo::is_enforced(), Slic3r::SeamPlacerImpl::GlobalModelInfo::non_enforcers, Slic3r::SeamPlacerImpl::GlobalModelInfo::non_enforcers_tree, and Slic3r::AABBTreeIndirect::traverse().

Referenced by fully_enforced_loop(), and process_perimeter_polygon().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gather_enforcers_blockers()

void Slic3r::SeamPlacerImpl::gather_enforcers_blockers ( GlobalModelInfo & result,
const PrintObject * po )
1099 {
1100 BOOST_LOG_TRIVIAL(debug)
1101 << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start";
1102
1103 auto obj_transform = po->trafo_sliced();
1104
1105 indexed_triangle_set painted_non_green; // Used only if the object has green paint.
1106 for (const ModelVolume *mv : po->model_object()->volumes) {
1107 // Collect painting only from model parts (what the gizmo edits) and negative volumes (the only way
1108 // to paint a hole's wall); painting left on modifiers and helpers after a type change is ignored.
1109 // TODO: painting on negative volumes still affects the seam, but the gizmo neither shows nor edits it;
1110 // making it editable also needs model_custom_seam_data_changed() to track it.
1111 if (!mv->is_model_part() && !mv->is_negative_volume())
1112 continue;
1113 const auto model_transformation = obj_transform * mv->get_matrix();
1114 if (mv->is_seam_painted()) {
1115 // One selector serves every paint state; each FacetsAnnotation query would rebuild it from the mesh.
1116 TriangleSelector selector(mv->mesh());
1117 selector.deserialize(mv->seam_facets.get_data(), false);
1118 indexed_triangle_set enforcers = selector.get_facets(EnforcerBlockerType::ENFORCER);
1119 // ORCA fix (not related to Precise Seam): fix winding for mirrored transforms
1120 its_transform(enforcers, model_transformation, true);
1121 its_merge(result.enforcers, enforcers);
1122
1123 indexed_triangle_set painted = selector.get_facets_strict(EnforcerBlockerType::ENFORCER);
1124 its_transform(painted, model_transformation, true);
1126
1127 indexed_triangle_set blockers = selector.get_facets(EnforcerBlockerType::BLOCKER);
1128 // ORCA fix (not related to Precise Seam): fix winding for mirrored transforms
1129 its_transform(blockers, model_transformation, true);
1130 its_merge(result.blockers, blockers);
1131 painted = selector.get_facets_strict(EnforcerBlockerType::BLOCKER);
1132 its_transform(painted, model_transformation, true);
1133 append_painted_contour(result.blocker_contour, painted);
1134
1135 indexed_triangle_set non_green = selector.get_facets(EnforcerBlockerType::NONE);
1136 its_transform(non_green, model_transformation, true);
1137 its_merge(painted_non_green, non_green);
1138 }
1139 }
1140
1141 // Unpainted objects need no extra surface copy or tree; only green paint requires a coverage proof.
1142 if (!result.enforcers.empty()) {
1143 result.non_enforcers = std::move(painted_non_green);
1144 for (const ModelVolume *mv : po->model_object()->volumes) {
1145 if ((!mv->is_model_part() && !mv->is_negative_volume()) || mv->is_seam_painted())
1146 continue;
1147 indexed_triangle_set non_green = mv->mesh().its;
1148 its_transform(non_green, obj_transform * mv->get_matrix(), true);
1149 its_merge(result.non_enforcers, non_green);
1150 }
1151 its_merge(result.non_enforcers, result.blockers); // Red paint must also prevent full-green normalization.
1153 result.non_enforcers.indices);
1154 }
1155
1157 result.enforcers.indices);
1159 result.blockers.indices);
1162
1163 BOOST_LOG_TRIVIAL(debug)
1164 << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end";
1165}
Definition TriangleSelector.hpp:75
AABBTreeIndirect::Tree< LineType::Dim, typename LineType::Scalar > build_aabb_tree_over_indexed_lines(const std::vector< LineType > &lines)
Definition AABBTreeLines.hpp:172
void append_painted_contour(PaintedContour &result, const indexed_triangle_set &painted)
Definition SeamPlacer.cpp:1079
@ ENFORCER
Definition TriangleSelector.hpp:27
@ BLOCKER
Definition TriangleSelector.hpp:28
@ NONE
Definition TriangleSelector.hpp:26
AABBTreeIndirect::Tree< 3, float > blockers_tree
Definition SeamPlacer.cpp:359
AABBTreeIndirect::Tree< 3, float > enforcers_tree
Definition SeamPlacer.cpp:358
PaintedContour enforcer_contour
Definition SeamPlacer.cpp:363
PaintedContour blocker_contour
Definition SeamPlacer.cpp:364
indexed_triangle_set blockers
Definition SeamPlacer.cpp:355
AABBTreeIndirect::Tree< 3, double > tree
Definition SeamPlacer.cpp:328

References append_painted_contour(), Slic3r::BLOCKER, Slic3r::SeamPlacerImpl::GlobalModelInfo::blocker_contour, Slic3r::SeamPlacerImpl::GlobalModelInfo::blockers, Slic3r::SeamPlacerImpl::GlobalModelInfo::blockers_tree, Slic3r::AABBTreeLines::build_aabb_tree_over_indexed_lines(), Slic3r::AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(), Slic3r::TriangleSelector::deserialize(), Slic3r::SeamPlacerImpl::PaintedContour::edges, Slic3r::ENFORCER, Slic3r::SeamPlacerImpl::GlobalModelInfo::enforcer_contour, Slic3r::SeamPlacerImpl::GlobalModelInfo::enforcers, Slic3r::SeamPlacerImpl::GlobalModelInfo::enforcers_tree, gather_enforcers_blockers(), Slic3r::TriangleSelector::get_facets(), Slic3r::TriangleSelector::get_facets_strict(), Slic3r::its_merge(), Slic3r::PrintObjectBase::model_object(), Slic3r::SeamPlacerImpl::GlobalModelInfo::non_enforcers, Slic3r::SeamPlacerImpl::GlobalModelInfo::non_enforcers_tree, Slic3r::NONE, Slic3r::PrintObject::trafo_sliced(), Slic3r::SeamPlacerImpl::PaintedContour::tree, and Slic3r::ModelObject::volumes.

Referenced by gather_enforcers_blockers().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gauss()

float Slic3r::SeamPlacerImpl::gauss ( float value,
float mean_x_coord,
float mean_value,
float falloff_speed )
75 {
76 float shifted = value - mean_x_coord;
77 float denominator = falloff_speed * shifted * shifted + 1.0f;
78 float exponent = 1.0f / denominator;
79 return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f);
80}

Referenced by compute_angle_penalty().

Here is the caller graph for this function:

◆ pick_nearest_seam_point_index()

size_t Slic3r::SeamPlacerImpl::pick_nearest_seam_point_index ( const std::vector< SeamCandidate > & perimeter_points,
size_t start_index,
const Vec2f & preffered_location )
1353 {
1354 size_t end_index = perimeter_points[start_index].perimeter.end_index;
1355 SeamComparator comparator { spNearest };
1356
1357 size_t seam_index = start_index;
1358 for (size_t index = start_index; index < end_index; ++index) {
1359 if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index], preffered_location)) {
1360 seam_index = index;
1361 }
1362 }
1363 return seam_index;
1364}
@ spNearest
Definition PrintConfig.hpp:395
Definition SeamPlacer.cpp:1167
bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location=Vec2f { 0.0f, 0.0f }) const
Definition SeamPlacer.cpp:1178

References Slic3r::SeamPlacerImpl::SeamComparator::is_first_better(), pick_nearest_seam_point_index(), and Slic3r::spNearest.

Referenced by pick_nearest_seam_point_index().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ pick_random_seam_point()

void Slic3r::SeamPlacerImpl::pick_random_seam_point ( const std::vector< SeamCandidate > & perimeter_points,
size_t start_index )
1367 {
1368 SeamComparator comparator { spRandom };
1369
1370 // algorithm keeps a list of viable points and their lengths. If it finds a point
1371 // that is much better than the viable_example_index (e.g. better type, no overhang; see is_first_not_much_worse)
1372 // then it throws away stored lists and starts from start
1373 // in the end, the list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not
1374 // big overhang.
1375 size_t viable_example_index = start_index;
1376 size_t end_index = perimeter_points[start_index].perimeter.end_index;
1377 struct Viable {
1378 // Candidate seam point index.
1379 size_t index;
1380 float edge_length;
1381 Vec3f edge;
1382 };
1383 std::vector<Viable> viables;
1384
1385 const auto edge_from = [&](size_t index) -> Vec3f {
1386 const size_t next = index == end_index - 1 ? start_index : index + 1;
1387 // The exact green endpoint is Enforced, but its outgoing edge leaves the permitted painted run.
1388 if (perimeter_points[index].perimeter.exact_enforced_ends &&
1389 perimeter_points[index].type == EnforcedBlockedSeamPoint::Enforced &&
1390 perimeter_points[next].type != EnforcedBlockedSeamPoint::Enforced)
1391 return Vec3f::Zero();
1392 // Random samples the outgoing edge, not just the candidate; do not sample toward a closed red boundary.
1393 if (perimeter_points[index].perimeter.exact_blocked_ends &&
1394 perimeter_points[index].type != EnforcedBlockedSeamPoint::Blocked &&
1395 perimeter_points[next].type == EnforcedBlockedSeamPoint::Blocked)
1396 return Vec3f::Zero();
1397 return perimeter_points[next].position - perimeter_points[index].position;
1398 };
1399
1400 const Vec3f pseudornd_seed = perimeter_points[viable_example_index].position;
1401 float rand = std::abs(sin(pseudornd_seed.dot(Vec3f(12.9898f,78.233f, 133.3333f))) * 43758.5453f);
1402 rand = rand - (int) rand;
1403
1404 for (size_t index = start_index; index < end_index; ++index) {
1405 if (comparator.are_similar(perimeter_points[index], perimeter_points[viable_example_index])) {
1406 // index ok, push info into viables
1407 Vec3f edge_to_next = edge_from(index);
1408 float dist_to_next = edge_to_next.norm();
1409 viables.push_back( { index, dist_to_next, edge_to_next });
1410 } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index],
1411 perimeter_points[index])) {
1412 // index is worse then viable_example_index, skip this point
1413 } else {
1414 // index is better than viable example index, update example, clear gathered info, start again
1415 // clear up all gathered info, start from scratch, update example index
1416 viable_example_index = index;
1417 viables.clear();
1418
1419 Vec3f edge_to_next = edge_from(index);
1420 float dist_to_next = edge_to_next.norm();
1421 viables.push_back( { index, dist_to_next, edge_to_next });
1422 }
1423 }
1424
1425 // now pick random point from the stored options
1426 float len_sum = std::accumulate(viables.begin(), viables.end(), 0.0f, [](const float acc, const Viable &v) {
1427 return acc + v.edge_length;
1428 });
1429 float picked_len = len_sum * rand;
1430
1431 size_t point_idx = 0;
1432 // Float accumulation can leave a positive residual after the final edge, including a zero-length edge.
1433 while (point_idx + 1 < viables.size() && picked_len - viables[point_idx].edge_length > 0) {
1434 picked_len = picked_len - viables[point_idx].edge_length;
1435 point_idx++;
1436 }
1437 picked_len = std::clamp(picked_len, 0.0f, viables[point_idx].edge_length);
1438
1439 Perimeter &perimeter = perimeter_points[start_index].perimeter;
1440 perimeter.seam_index = viables[point_idx].index;
1441 perimeter.final_seam_position = perimeter_points[perimeter.seam_index].position
1442 + (viables[point_idx].edge_length > 0.f ?
1443 Vec3f(viables[point_idx].edge.normalized() * picked_len) : Vec3f::Zero());
1444 perimeter.finalized = true;
1445}
@ spRandom
Definition PrintConfig.hpp:395
static double edge_length(const VD::edge_type &edge)
Definition MultiMaterialSegmentation.cpp:1115
bool are_similar(const SeamCandidate &a, const SeamCandidate &b) const
Definition SeamPlacer.cpp:1273
bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const
Definition SeamPlacer.cpp:1227

References Slic3r::SeamPlacerImpl::SeamComparator::are_similar(), Blocked, Slic3r::edge_length(), Enforced, Slic3r::SeamPlacerImpl::Perimeter::final_seam_position, Slic3r::SeamPlacerImpl::Perimeter::finalized, Slic3r::SeamPlacerImpl::SeamComparator::is_first_not_much_worse(), pick_random_seam_point(), Slic3r::SeamPlacerImpl::Perimeter::seam_index, and Slic3r::spRandom.

Referenced by pick_random_seam_point().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ pick_seam_point()

void Slic3r::SeamPlacerImpl::pick_seam_point ( std::vector< SeamCandidate > & perimeter_points,
size_t start_index,
const SeamComparator & comparator )
1340 {
1341 size_t end_index = perimeter_points[start_index].perimeter.end_index;
1342
1343 size_t seam_index = start_index;
1344 for (size_t index = start_index; index < end_index; ++index) {
1345 if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) {
1346 seam_index = index;
1347 }
1348 }
1349 perimeter_points[start_index].perimeter.seam_index = seam_index;
1350}

References Slic3r::SeamPlacerImpl::SeamComparator::is_first_better(), and pick_seam_point().

Referenced by pick_seam_point().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ place_on_loop()

Vec3f Slic3r::SeamPlacerImpl::place_on_loop ( const std::vector< SeamCandidate > & points,
size_t index,
const Vec3f & target )
1498 {
1499 const Perimeter &perimeter = points[index].perimeter;
1500 const size_t start = perimeter.start_index;
1501 const size_t count = perimeter.end_index - start;
1502 if (count < 2)
1503 return points[index].position;
1504 const auto next = [&](size_t i) { return start + next_idx_modulo(i - start, count); };
1505 const auto prev = [&](size_t i) { return start + prev_idx_modulo(i - start, count); };
1506 const auto xy = [&](size_t i) { return Vec2d(points[i].position.head<2>().cast<double>()); };
1507 // Nearest neighbor at a different position, so that duplicated points share one normal.
1508 const auto distinct = [&](size_t i, bool forward) {
1509 size_t j = i;
1510 for (size_t step = 1; step < count; ++step) {
1511 j = forward ? next(j) : prev(j);
1512 if (xy(j) != xy(i))
1513 return j;
1514 }
1515 return i;
1516 };
1517 const auto vertex_normal = [&](size_t i) {
1518 const Vec2d sum = edge_normal(xy(distinct(i, false)), xy(i)) + edge_normal(xy(i), xy(distinct(i, true)));
1519 return sum.squaredNorm() > 0.0 ? Vec2d(sum.normalized()) : Vec2d::Zero();
1520 };
1521 const auto cross = [](const Vec2d &a, const Vec2d &b) { return a.x() * b.y() - a.y() * b.x(); };
1522
1523 // Allowed arc on one side: the Enforced run for an Enforced candidate, plus the oversampling distance unless
1524 // the run ends exactly at the paint; up to a Blocked candidate otherwise. Returns its length and end point;
1525 // infinite length if unlimited.
1526 const bool enforced = points[index].type == EnforcedBlockedSeamPoint::Enforced;
1527 const bool blocked = points[index].type == EnforcedBlockedSeamPoint::Blocked;
1528 const auto allowed_arc = [&](bool forward) {
1529 double arc = 0.0;
1530 double extension = enforced && !perimeter.exact_enforced_ends ? SeamPlacer::enforcer_oversampling_distance : 0.0;
1531 bool in_run = true;
1532 size_t i = index;
1533 // All count edges, the closing one back to the candidate included, before the arc is unlimited.
1534 for (size_t visited = 1; visited <= count; ++visited) {
1535 const size_t j = forward ? next(i) : prev(i);
1536 const EnforcedBlockedSeamPoint type = points[j].type;
1537 if (in_run && (enforced ? type != EnforcedBlockedSeamPoint::Enforced :
1538 !blocked && type == EnforcedBlockedSeamPoint::Blocked))
1539 in_run = false;
1540 if (!in_run && (!enforced || type == EnforcedBlockedSeamPoint::Blocked))
1541 return std::make_pair(arc, xy(i)); // Only an Enforced run is extended, and never into a Blocked candidate.
1542 const double length = (xy(j) - xy(i)).norm();
1543 if (!in_run) {
1544 const double reach = std::min(length, extension);
1545 extension -= reach;
1546 if (reach < length)
1547 return std::make_pair(arc + reach, Vec2d(xy(i) + (reach / length) * (xy(j) - xy(i))));
1548 }
1549 arc += length;
1550 i = j;
1551 }
1552 return std::make_pair(std::numeric_limits<double>::infinity(), xy(index));
1553 };
1554
1555 // Closest solution over the whole loop, walking outward on the shorter side first so that solutions near
1556 // the candidate prune the edges farther from the target than them.
1557 const Vec2d p = target.head<2>().cast<double>();
1558 std::optional<Vec2d> best;
1559 double best_distance = std::numeric_limits<double>::infinity();
1560 bool best_forward = true;
1561 double best_arc = 0.0;
1562 double arc[2] = { 0.0, 0.0 }; // Walked arc behind and ahead of the candidate.
1563 size_t cursor[2] = { index, index };
1564 for (size_t visited = 0; visited < count; ++visited) {
1565 const bool forward = arc[1] <= arc[0];
1566 const size_t i = cursor[forward];
1567 const size_t j = forward ? next(i) : prev(i);
1568 // The edge in index order; P(u) = xy(a) + u * d.
1569 const size_t a = forward ? i : j;
1570 const size_t b = forward ? j : i;
1571 const Vec2d d = xy(b) - xy(a);
1572 const double length = d.norm();
1573 const Vec2d g = p - xy(a);
1574 const double u_closest = length > 0.0 ? std::clamp(g.dot(d) / (length * length), 0.0, 1.0) : 0.0;
1575 if (length > 0.0 && (g - u_closest * d).squaredNorm() < best_distance) {
1576 // Solve cross(p - P(u), N(u)) = 0 with the normal N blended linearly between the vertex normals.
1577 Vec2d na = vertex_normal(a);
1578 Vec2d nb = vertex_normal(b);
1579 if (na.squaredNorm() == 0.0 || nb.squaredNorm() == 0.0)
1580 na = nb = edge_normal(xy(a), xy(b));
1581 const Vec2d m = nb - na;
1582 const double c0 = cross(g, na);
1583 const double c1 = cross(g, m) - cross(d, na);
1584 const double c2 = -cross(d, m);
1585 const auto consider = [&](double u) {
1586 if (u < 0.0 || u > 1.0)
1587 return;
1588 const double distance = (g - u * d).squaredNorm();
1589 if (distance < best_distance) {
1590 best = Vec2d(xy(a) + u * d);
1591 best_distance = distance;
1592 best_forward = forward;
1593 best_arc = arc[forward] + (forward ? u : 1.0 - u) * length;
1594 }
1595 };
1596 // Absolute threshold on the coefficients (mm), only to tell the degenerate equations apart.
1597 constexpr double eps = 1e-12;
1598 if (std::abs(c2) < eps && std::abs(c1) < eps) {
1599 if (std::abs(c0) < eps)
1600 consider(u_closest); // Every point is a solution.
1601 } else if (std::abs(c2) < eps) {
1602 consider(-c0 / c1);
1603 } else {
1604 const double discriminant = c1 * c1 - 4.0 * c2 * c0;
1605 if (discriminant >= 0.0) {
1606 const double root = std::sqrt(discriminant);
1607 consider((-c1 - root) / (2.0 * c2));
1608 consider((-c1 + root) / (2.0 * c2));
1609 }
1610 }
1611 }
1612 arc[forward] += length;
1613 cursor[forward] = j;
1614 }
1615 if (!best)
1616 return points[index].position;
1617
1618 // A solution outside the allowed arc gives way to the arc end closer to it, so that a solution leaving
1619 // the arc stays at the end it left through.
1620 Vec2d result = *best;
1621 const double loop_length = arc[0] + arc[1];
1622 const double ahead = best_forward ? best_arc : loop_length - best_arc;
1623 const auto [ahead_limit, ahead_end] = allowed_arc(true);
1624 const auto [behind_limit, behind_end] = allowed_arc(false);
1625 if (ahead > ahead_limit && loop_length - ahead > behind_limit)
1626 result = (ahead_end - *best).squaredNorm() <= (behind_end - *best).squaredNorm() ? ahead_end : behind_end;
1627 return to_3d(Vec2f(result.cast<float>()), points[index].position.z());
1628}
static constexpr float enforcer_oversampling_distance
Definition SeamPlacer.hpp:150
EnforcedBlockedSeamPoint
Definition SeamPlacer.hpp:38
Vec2d edge_normal(const Vec2d &a, const Vec2d &b)
Definition SeamPlacer.cpp:1490
bool exact_enforced_ends
Definition SeamPlacer.hpp:55
float distance(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b)
Definition SpiralVase.cpp:19
Eigen::Matrix< typename Derived::Scalar, 3, 1, Eigen::DontAlign > to_3d(const Eigen::MatrixBase< Derived > &pt, const typename Derived::Scalar z)
Definition Point.hpp:153
Eigen::Matrix< float, 2, 1, Eigen::DontAlign > Vec2f
Definition Point.hpp:64
auto cast(const BoundingBoxBase< Tin > &b)
Definition BoundingBox.hpp:299
void step(Coord &crd, const Dir d, const long n=1)
Definition MarchingSquares.hpp:213

References Blocked, Slic3r::cast(), count, Slic3r::cross(), edge_normal(), Slic3r::SeamPlacerImpl::Perimeter::end_index, Enforced, Slic3r::SeamPlacer::enforcer_oversampling_distance, Slic3r::SeamPlacerImpl::Perimeter::exact_enforced_ends, Slic3r::length(), Slic3r::next_idx_modulo(), place_on_loop(), Slic3r::prev_idx_modulo(), Slic3r::SeamPlacerImpl::Perimeter::start_index, and Slic3r::to_3d().

Referenced by place_on_loop().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ process_perimeter_polygon()

void Slic3r::SeamPlacerImpl::process_perimeter_polygon ( const Polygon & orig_polygon,
float z_coord,
const LayerRegion * region,
const GlobalModelInfo & global_model_info,
PrintObjectSeamData::LayerSeams & result,
PreciseSeam::PreciseSeamWarnings * warnings = nullptr,
std::optional< bool > boundary_hole = std::nullopt )
676 {
677 if (orig_polygon.size() == 0) {
678 return;
679 }
680 Polygon polygon = orig_polygon;
681 bool was_clockwise = polygon.make_counter_clockwise();
682
683 // Process Precise Seam modifiers to find seam placement
684 const Layer* layer = region ? region->layer() : nullptr;
685
686 // Use pre-computed Precise Seam volumes from global_model_info (computed once in init)
687 const auto& strong_volumes = global_model_info.precise_seam_strong_volumes;
688 const auto& weak_volumes = global_model_info.precise_seam_weak_volumes;
689
690 std::optional<Point> seam_point;
691 std::vector<PreciseSeam::WeakModifierSegment> weak_segments;
692 if (layer != nullptr && (!strong_volumes.empty() || !weak_volumes.empty())) {
693 // Share validation, bounds and clipping line across all modifiers while the polygon is unchanged.
694 // A strong insertion ends processing; otherwise weak reads the same preparation before inserting.
695 const PreciseSeam::PreparedPerimeter prepared(polygon);
697 strong_volumes, polygon, prepared, layer, global_model_info.precise_seam_slices, warnings);
698 if (!seam_point.has_value())
700 weak_volumes, polygon, prepared, layer, global_model_info.precise_seam_slices, warnings);
701 }
702
703 // Store the inserted point position for marking as central_enforcer later.
704 std::optional<Vec3f> inserted_seam_position;
705 if (seam_point.has_value()) {
706 Vec2f unscaled_p = unscale(seam_point.value()).cast<float>();
707 inserted_seam_position = Vec3f(unscaled_p.x(), unscaled_p.y(), z_coord);
708 }
709
710 float angle_arm_len = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).nozzle_diameter() : 0.5f;
711
712 std::vector<float> lengths { };
713 for (size_t point_idx = 0; point_idx < polygon.size() - 1; ++point_idx) {
714 lengths.push_back((unscale(polygon[point_idx]) - unscale(polygon[point_idx + 1])).norm());
715 }
716 lengths.push_back(std::max((unscale(polygon[0]) - unscale(polygon[polygon.size() - 1])).norm(), 0.1));
717 std::vector<float> polygon_angles = calculate_polygon_angles_at_vertices(polygon, lengths,
718 angle_arm_len);
719
720 result.perimeters.push_back( { });
721 Perimeter &perimeter = result.perimeters.back();
722 // The loop role is independent of the configured printing direction.
723 perimeter.is_hole = boundary_hole.value_or(was_clockwise);
724 perimeter.contour_reliable = boundary_hole.has_value(); // Keep unknown roles out of geometric matching.
725 perimeter.contour_index = result.perimeters.size() - 1;
726
727 std::queue<Vec3f> orig_polygon_points { };
728 for (size_t index = 0; index < polygon.size(); ++index) {
729 Vec2f unscaled_p = unscale(polygon[index]).cast<float>();
730 orig_polygon_points.emplace(unscaled_p.x(), unscaled_p.y(), z_coord);
731 }
732 Vec3f first = orig_polygon_points.front();
733 std::queue<Vec3f> oversampled_points { };
734 size_t orig_angle_index = 0;
735 perimeter.start_index = result.points.size();
736 perimeter.flow_width = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).width() : 0.0f;
737
738 // Painted ranges bounded exactly by the paint contour crossing this layer; without them, points within
739 // flow_width of paint are Enforced.
740 std::vector<Vec2d> loop_xy;
741 std::vector<double> loop_arc { 0.0 };
742 for (const Point &point : polygon.points)
743 loop_xy.push_back(unscale(point));
744 for (size_t i = 0; i < loop_xy.size(); ++i)
745 loop_arc.push_back(loop_arc.back() + (loop_xy[(i + 1) % loop_xy.size()] - loop_xy[i]).norm());
746 const double loop_length = loop_arc.back();
747 const auto intervals = exact_painted_intervals(loop_xy, loop_arc, z_coord, perimeter.flow_width,
748 global_model_info.enforcer_contour);
749 const auto blocked_intervals = exact_painted_intervals(loop_xy, loop_arc, z_coord, perimeter.flow_width,
750 global_model_info.blocker_contour);
751 perimeter.exact_blocked_ends = !blocked_intervals.empty();
752 const bool exact_marks = !intervals.empty() || perimeter.exact_blocked_ends;
753 // Keep active Precise Seam modifiers authoritative; normalize ordinary full paint only.
754 const bool ignore_full_paint = intervals.empty() && !inserted_seam_position && weak_segments.empty() &&
755 fully_enforced_loop(loop_xy, z_coord, perimeter.flow_width, global_model_info);
756 if (ignore_full_paint)
757 BOOST_LOG_TRIVIAL(debug) << "[SeamPaint] full_perimeter z=" << z_coord << " normalized=true";
758 perimeter.exact_enforced_ends = !intervals.empty();
759 const auto in_interval = [&](double at, const std::vector<PaintedInterval> &ranges) {
760 for (const PaintedInterval &interval : ranges)
761 for (const double s : { at, at + loop_length })
762 if (s >= interval.begin - enforced_end_tolerance && s <= interval.end + enforced_end_tolerance)
763 return true;
764 return false;
765 };
766 const auto point_at = [&](double s) {
767 const size_t i = std::min(size_t(std::upper_bound(loop_arc.begin(), loop_arc.end(), s) - loop_arc.begin()),
768 loop_xy.size()) - 1;
769 const double length = loop_arc[i + 1] - loop_arc[i];
770 const Vec2d p = loop_xy[i] + (length > 0.0 ? (s - loop_arc[i]) / length : 0.0) *
771 (loop_xy[(i + 1) % loop_xy.size()] - loop_xy[i]);
772 return Vec3f(float(p.x()), float(p.y()), z_coord);
773 };
774 // Green paint without exact ends: an edge whose start is within the edge length of paint gets a point every
775 // enforcer_oversampling_distance, passed to emit as its distance from the start.
776 const auto oversample_green = [&](const Vec3f &start, float length, const auto &emit) {
777 if (ignore_full_paint || !global_model_info.is_enforced(start, length))
778 return;
781 emit(step);
782 };
783 // Points to insert: the range ends, middles and every enforcer_oversampling_distance from their begin, and the
784 // middle of each gap between ranges, which keeps neighboring ranges apart as patches; as sorted loop positions
785 // in [0, loop_length).
786 std::vector<double> marks;
787 std::vector<std::pair<Vec3f, double>> interval_middles; // Middle point and length of each range.
788 for (size_t k = 0; k < intervals.size(); ++k) {
789 const PaintedInterval &interval = intervals[k];
790 const double middle = 0.5 * (interval.begin + interval.end);
791 // Ranges come in loop order from the first vertex.
792 const double next_begin = k + 1 < intervals.size() ? intervals[k + 1].begin : intervals.front().begin + loop_length;
793 marks.insert(marks.end(), { interval.begin, interval.end, middle, 0.5 * (interval.end + next_begin) });
794 for (double s = interval.begin + SeamPlacer::enforcer_oversampling_distance; s < interval.end - enforced_end_tolerance;
796 marks.push_back(s);
797 interval_middles.emplace_back(point_at(std::fmod(middle, loop_length)), interval.end - interval.begin);
798 }
799 for (size_t k = 0; k < blocked_intervals.size(); ++k) {
800 const auto &interval = blocked_intervals[k];
801 const double next_begin = k + 1 < blocked_intervals.size() ? blocked_intervals[k + 1].begin :
802 blocked_intervals.front().begin + loop_length;
803 const double gap = next_begin - interval.end;
804 // Keep the boundary itself blocked, with a permitted sample just outside it. Both alignment and
805 // the iterative solvers stop at these samples, within 2 um of the geometric paint edge.
806 const double guard = std::min(2.0 * enforced_end_tolerance, 0.25 * gap);
807 marks.insert(marks.end(), {interval.begin, interval.end, 0.5 * (interval.begin + interval.end),
808 interval.end + guard, next_begin - guard, 0.5 * (interval.end + next_begin)});
809 }
810 if (perimeter.exact_blocked_ends && !perimeter.exact_enforced_ends) {
811 // Red marks must not suppress legacy green oversampling when its boundary is uncertain.
812 for (size_t i = 0; i < loop_xy.size(); ++i)
813 oversample_green(point_at(loop_arc[i]), float(loop_arc[i + 1] - loop_arc[i]),
814 [&](float step) { marks.push_back(loop_arc[i] + step); });
815 }
816 for (double &s : marks)
817 s = std::fmod(s, loop_length);
818 std::sort(marks.begin(), marks.end());
819 // A mark closer than the tolerance to the last one kept is the same point.
820 size_t kept = 0;
821 for (size_t i = 0; i < marks.size(); ++i)
822 if (kept == 0 || marks[i] - marks[kept - 1] >= enforced_end_tolerance)
823 marks[kept++] = marks[i];
824 marks.resize(kept);
825
826 std::queue<double> oversampled_arcs { }; // Loop positions of the inserted points with exact ranges.
827 bool some_point_enforced = false;
828 while (!orig_polygon_points.empty() || !oversampled_points.empty()) {
830 Vec3f position;
831 float local_ccw_angle = 0;
832 bool orig_point = false;
833 double at = 0.0; // Loop position, used with exact ranges.
834 if (!oversampled_points.empty()) {
835 position = oversampled_points.front();
836 oversampled_points.pop();
837 if (exact_marks) {
838 at = oversampled_arcs.front();
839 oversampled_arcs.pop();
840 }
841 } else {
842 position = orig_polygon_points.front();
843 orig_polygon_points.pop();
844 local_ccw_angle = was_clockwise ? -polygon_angles[orig_angle_index] : polygon_angles[orig_angle_index];
845 at = loop_arc[orig_angle_index];
846 orig_angle_index++;
847 orig_point = true;
848 }
849
850 // Projected ends bound paint but cannot prove that a detour between them stays near its surface.
851 if (!ignore_full_paint && (!perimeter.exact_enforced_ends || in_interval(at, intervals)) &&
852 global_model_info.is_enforced(position, perimeter.flow_width)) {
854 }
855
856 if ((!perimeter.exact_blocked_ends || in_interval(at, blocked_intervals)) &&
857 global_model_info.is_blocked(position, perimeter.flow_width)) {
859 }
860 some_point_enforced = some_point_enforced || type == EnforcedBlockedSeamPoint::Enforced;
861
862 if (orig_point && exact_marks) {
863 // Marks strictly inside the edge from this vertex; one at a vertex is the vertex itself.
864 const double from = loop_arc[orig_angle_index - 1], to = loop_arc[orig_angle_index];
865 for (auto it = std::upper_bound(marks.begin(), marks.end(), from + enforced_end_tolerance);
866 it != marks.end() && *it < to - enforced_end_tolerance; ++it) {
867 oversampled_points.push(point_at(*it));
868 oversampled_arcs.push(*it);
869 }
870 } else if (orig_point) {
871 const Vec3f pos_of_next = orig_polygon_points.empty() ? first : orig_polygon_points.front();
872 const Vec3f vec_to_next = (pos_of_next - position).normalized();
873 oversample_green(position, (position - pos_of_next).norm(),
874 [&](float step) { oversampled_points.push(position + vec_to_next * step); });
875 }
876
877 result.points.emplace_back(position, perimeter, local_ccw_angle, type);
878 }
879
880 perimeter.end_index = result.points.size();
881
882 // Apply weak modifiers if no strong modifier was inserted
883 if (!inserted_seam_position.has_value() && !weak_segments.empty()) {
885 weak_segments, result, perimeter, some_point_enforced, warnings);
886 }
887
888 if (some_point_enforced) {
889 // Choose the continuous enforced patch with the most candidates, then select its middle
890 // candidate or a sharp corner. Patch length here is a point count, not geometric distance.
891 size_t perimeter_size = perimeter.end_index - perimeter.start_index;
892 const auto next_index = [&](size_t idx) {
893 return perimeter.start_index + Slic3r::next_idx_modulo(idx - perimeter.start_index, perimeter_size);
894 };
895
896 std::vector<size_t> patches_starts_ends;
897 for (size_t i = perimeter.start_index; i < perimeter.end_index; ++i) {
898 if (result.points[i].type != EnforcedBlockedSeamPoint::Enforced &&
899 result.points[next_index(i)].type == EnforcedBlockedSeamPoint::Enforced) {
900 patches_starts_ends.push_back(next_index(i));
901 }
902 if (result.points[i].type == EnforcedBlockedSeamPoint::Enforced &&
903 result.points[next_index(i)].type != EnforcedBlockedSeamPoint::Enforced) {
904 patches_starts_ends.push_back(next_index(i));
905 }
906 }
907 // If patches_starts_ends are empty, the whole perimeter is enforced, or no point is enforced any more
908 // (Precise Seam weak zones retyped every painted enforcer); don't do anything in either case.
909 if (!patches_starts_ends.empty()) {
910 //if the first point in the patches is not enforced, it marks a patch end. in that case, put it to the end and start on next
911 // to simplify the processing
912 assert(patches_starts_ends.size() % 2 == 0);
913 bool start_on_second = false;
914 if (result.points[patches_starts_ends[0]].type != EnforcedBlockedSeamPoint::Enforced) {
915 start_on_second = true;
916 patches_starts_ends.push_back(patches_starts_ends[0]);
917 }
918 //now pick the longest patch
919 std::pair<size_t, size_t> longest_patch { 0, 0 };
920 auto patch_len = [perimeter_size](const std::pair<size_t, size_t> &start_end) {
921 if (start_end.second < start_end.first) {
922 // Count [start, end) across the closing edge, independently of the contour's start.
923 // Subtract indices first: they are offsets in the layer, not local perimeter indices.
924 return perimeter_size - (start_end.first - start_end.second);
925 } else {
926 return start_end.second - start_end.first;
927 }
928 };
929 for (size_t patch_idx = start_on_second ? 1 : 0; patch_idx < patches_starts_ends.size(); patch_idx += 2) {
930 std::pair<size_t, size_t> current_patch { patches_starts_ends[patch_idx], patches_starts_ends[patch_idx
931 + 1] };
932 if (patch_len(longest_patch) < patch_len(current_patch)) {
933 longest_patch = current_patch;
934 }
935 }
936 std::vector<size_t> viable_points_indices;
937 std::vector<size_t> large_angle_points_indices;
938 for (size_t point_idx = longest_patch.first; point_idx != longest_patch.second;
939 point_idx = next_index(point_idx)) {
940 viable_points_indices.push_back(point_idx);
941 if (std::abs(result.points[point_idx].local_ccw_angle)
943 large_angle_points_indices.push_back(point_idx);
944 }
945 }
946 assert(viable_points_indices.size() > 0);
947 if (large_angle_points_indices.empty()) {
948 size_t central_idx = viable_points_indices[viable_points_indices.size() / 2];
949 // With exact ranges, the middle of the longest range in the patch.
950 double longest = -1.0;
951 for (const size_t idx : viable_points_indices)
952 for (const auto &[middle, length] : interval_middles)
953 if (length > longest && (result.points[idx].position - middle).norm() <= enforced_end_tolerance) {
954 central_idx = idx;
955 longest = length;
956 }
957 result.points[central_idx].central_enforcer = true;
958 } else {
959 size_t central_idx = large_angle_points_indices.size() / 2;
960 result.points[large_angle_points_indices[central_idx]].central_enforcer = true;
961 }
962 }
963 }
964
965 // Apply precise seam point if it was inserted
966 if (inserted_seam_position.has_value()) {
967 // Set single point as Enforced, block all others
968 for (size_t i = perimeter.start_index; i < perimeter.end_index; ++i) {
969 if (result.points[i].position == inserted_seam_position.value()) {
970 // Mark as the single enforced point with highest priority
972 result.points[i].central_enforcer = true;
973 perimeter.precise_seam_point = inserted_seam_position;
974 perimeter.precise_seam_index = i;
975 } else {
976 // Block all other points
978 result.points[i].central_enforcer = false;
979 }
980 }
981 }
982
983}
float width() const
Definition Flow.hpp:63
float nozzle_diameter() const
Definition Flow.hpp:73
Flow flow(FlowRole role) const
Definition LayerRegion.cpp:48
Layer * layer()
Definition Layer.hpp:50
Definition Layer.hpp:138
Definition Polygon.hpp:31
bool make_counter_clockwise()
Definition Polygon.cpp:94
static constexpr float sharp_angle_snapping_threshold
Definition SeamPlacer.hpp:141
std::optional< Point > insert_strong_seam_point(const std::vector< const ModelVolume * > &strong_volumes, Polygon &polygon, const PreparedPerimeter &prepared, const Layer *layer, const ModifierRegionsCache &slices_cache, PreciseSeamWarnings *warnings)
Definition PreciseSeam.cpp:695
std::vector< WeakModifierSegment > collect_weak_modifier_segments(const std::vector< const ModelVolume * > &weak_volumes, Polygon &polygon, const PreparedPerimeter &prepared, const Layer *layer, const ModifierRegionsCache &slices_cache, PreciseSeamWarnings *warnings)
Definition PreciseSeam.cpp:929
void apply_weak_modifiers_to_perimeter(const std::vector< WeakModifierSegment > &weak_segments, PrintObjectSeamData::LayerSeams &result, const SeamPlacerImpl::Perimeter &perimeter, bool &some_point_enforced, PreciseSeamWarnings *warnings)
Definition PreciseSeam.cpp:968
std::vector< PaintedInterval > exact_painted_intervals(const std::vector< Vec2d > &loop, const std::vector< double > &arc, double z, double reach, const PaintedContour &contour)
Definition SeamPlacer.cpp:601
bool fully_enforced_loop(const std::vector< Vec2d > &loop, double z, double reach, const GlobalModelInfo &info)
Definition SeamPlacer.cpp:557
std::vector< float > calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector< float > &lengths, float min_arm_length)
Definition SeamPlacer.cpp:260
double begin
Definition SeamPlacer.cpp:549
Definition SeamPlacer.cpp:548
@ frExternalPerimeter
Definition Flow.hpp:19
T unscale(Q v)
Definition libslic3r.h:138
Definition PreciseSeam.hpp:97
std::vector< SeamPlacerImpl::SeamCandidate > points
Definition SeamPlacer.hpp:115
Slic3r::deque< SeamPlacerImpl::Perimeter > perimeters
Definition SeamPlacer.hpp:114
std::vector< const ModelVolume * > precise_seam_weak_volumes
Definition SeamPlacer.cpp:369
PreciseSeam::ModifierRegionsCache precise_seam_slices
Definition SeamPlacer.cpp:372
bool is_blocked(const Vec3f &position, float radius) const
Definition SeamPlacer.cpp:383
std::vector< const ModelVolume * > precise_seam_strong_volumes
Definition SeamPlacer.cpp:367

References Slic3r::PreciseSeam::apply_weak_modifiers_to_perimeter(), Slic3r::SeamPlacerImpl::PaintedInterval::begin, Blocked, Slic3r::SeamPlacerImpl::GlobalModelInfo::blocker_contour, calculate_polygon_angles_at_vertices(), Slic3r::PreciseSeam::collect_weak_modifier_segments(), Slic3r::SeamPlacerImpl::Perimeter::contour_index, Slic3r::SeamPlacerImpl::Perimeter::contour_reliable, Slic3r::SeamPlacerImpl::PaintedInterval::end, Slic3r::SeamPlacerImpl::Perimeter::end_index, Enforced, enforced_end_tolerance, Slic3r::SeamPlacerImpl::GlobalModelInfo::enforcer_contour, Slic3r::SeamPlacer::enforcer_oversampling_distance, Slic3r::SeamPlacerImpl::Perimeter::exact_blocked_ends, Slic3r::SeamPlacerImpl::Perimeter::exact_enforced_ends, exact_painted_intervals(), Slic3r::LayerRegion::flow(), Slic3r::SeamPlacerImpl::Perimeter::flow_width, Slic3r::frExternalPerimeter, fully_enforced_loop(), Slic3r::PreciseSeam::insert_strong_seam_point(), Slic3r::SeamPlacerImpl::GlobalModelInfo::is_blocked(), Slic3r::SeamPlacerImpl::GlobalModelInfo::is_enforced(), Slic3r::SeamPlacerImpl::Perimeter::is_hole, Slic3r::LayerRegion::layer(), Slic3r::length(), Slic3r::Polygon::make_counter_clockwise(), Neutral, Slic3r::next_idx_modulo(), Slic3r::Flow::nozzle_diameter(), Slic3r::PrintObjectSeamData::LayerSeams::perimeters, Slic3r::MultiPoint::points, Slic3r::PrintObjectSeamData::LayerSeams::points, Slic3r::SeamPlacerImpl::Perimeter::precise_seam_index, Slic3r::SeamPlacerImpl::Perimeter::precise_seam_point, Slic3r::SeamPlacerImpl::GlobalModelInfo::precise_seam_slices, Slic3r::SeamPlacerImpl::GlobalModelInfo::precise_seam_strong_volumes, Slic3r::SeamPlacerImpl::GlobalModelInfo::precise_seam_weak_volumes, process_perimeter_polygon(), Slic3r::SeamPlacer::sharp_angle_snapping_threshold, Slic3r::MultiPoint::size(), Slic3r::SeamPlacerImpl::Perimeter::start_index, Slic3r::unscale(), and Slic3r::Flow::width().

Referenced by process_perimeter_polygon().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ raycast_visibility()

std::vector< float > Slic3r::SeamPlacerImpl::raycast_visibility ( const AABBTreeIndirect::Tree< 3, float > & raycasting_tree,
const indexed_triangle_set & triangles,
const TriangleSetSamples & samples,
size_t negative_volumes_start_index,
SeamPosition seam_position = spAligned )
163 {
164 BOOST_LOG_TRIVIAL(debug)
165 << "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size()
166 << " triangles: end";
167
168 //prepare uniform samples of a hemisphere
169 float step_size = 1.0f / SeamPlacer::sqr_rays_per_sample_point;
170 std::vector<Vec3f> precomputed_sample_directions(
172 for (size_t x_idx = 0; x_idx < SeamPlacer::sqr_rays_per_sample_point; ++x_idx) {
173 float sample_x = x_idx * step_size + step_size / 2.0;
174 for (size_t y_idx = 0; y_idx < SeamPlacer::sqr_rays_per_sample_point; ++y_idx) {
175 size_t dir_index = x_idx * SeamPlacer::sqr_rays_per_sample_point + y_idx;
176 float sample_y = y_idx * step_size + step_size / 2.0;
177 precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y });
178 }
179 }
180
181 bool model_contains_negative_parts = negative_volumes_start_index < triangles.indices.size();
182
183 std::vector<float> result(samples.positions.size());
184 tbb::parallel_for(tbb::blocked_range<size_t>(0, result.size()),
185 [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index,
186 &raycasting_tree, &result, &samples, seam_position](tbb::blocked_range<size_t> r) {
187 // Maintaining hits memory outside of the loop, so it does not have to be reallocated for each query.
188 std::vector<igl::Hit<float>> hits;
189 for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) {
190 result[s_idx] = 1.0f;
191 constexpr float decrease_step = 1.0f
192 / (SeamPlacer::sqr_rays_per_sample_point * SeamPlacer::sqr_rays_per_sample_point);
193
194 const Vec3f &center = samples.positions[s_idx];
195 const Vec3f &normal = samples.normals[s_idx];
196 if (seam_position == spAlignedBack) {
197 const float front_adjustment = std::clamp((normal.dot(Vec3f(0.0f, -1.0f, 0.0f)) + 1.2f) * 0.5f, 0.0f, 1.0f);
198 result[s_idx] += front_adjustment;
199 }
200
201 // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward
202 Frame f;
203 f.set_from_z(normal);
204
205 for (const auto &dir : precomputed_sample_directions) {
206 Vec3f final_ray_dir = (f.to_world(dir));
207 if (!model_contains_negative_parts) {
208 igl::Hit<float> hitpoint;
209 // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and
210 // direction.
211 Vec3d final_ray_dir_d = final_ray_dir.cast<double>();
212 Vec3d ray_origin_d = (center + normal * 0.01f).cast<double>(); // start above surface.
213 bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices,
214 triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint);
215 if (hit && its_face_normal(triangles, hitpoint.id).dot(final_ray_dir) <= 0) {
216 result[s_idx] -= decrease_step;
217 }
218 } else { //TODO improve logic for order based boolean operations - consider order of volumes
219 bool casting_from_negative_volume = samples.triangle_indices[s_idx]
220 >= negative_volumes_start_index;
221
222 Vec3d ray_origin_d = (center + normal * 0.01f).cast<double>(); // start above surface.
223 if (casting_from_negative_volume) { // if casting from negative volume face, invert direction, change start pos
224 final_ray_dir = -1.0 * final_ray_dir;
225 ray_origin_d = (center - normal * 0.01f).cast<double>();
226 }
227 Vec3d final_ray_dir_d = final_ray_dir.cast<double>();
228 bool some_hit = AABBTreeIndirect::intersect_ray_all_hits(triangles.vertices,
229 triangles.indices, raycasting_tree,
230 ray_origin_d, final_ray_dir_d, hits);
231 if (some_hit) {
232 int counter = 0;
233 // NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point;
234 // It cannot be inside model, and it cannot be inside negative volume
235 for (int hit_index = int(hits.size()) - 1; hit_index >= 0; --hit_index) {
236 Vec3f face_normal = its_face_normal(triangles, hits[hit_index].id);
237 if (hits[hit_index].id >= int(negative_volumes_start_index)) { //negative volume hit
238 counter -= sgn(face_normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space
239 // which in reverse hit analysis means, that we are entering negative space :) and vice versa
240 } else {
241 counter += sgn(face_normal.dot(final_ray_dir));
242 }
243 }
244 if (counter == 0) {
245 result[s_idx] -= decrease_step;
246 }
247 }
248 }
249 }
250 }
251 });
252
253 BOOST_LOG_TRIVIAL(debug)
254 << "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size()
255 << " triangles: end";
256
257 return result;
258}
static constexpr size_t sqr_rays_per_sample_point
Definition SeamPlacer.hpp:138
Vec3f sample_hemisphere_uniform(const Vec2f &samples)
Definition SeamPlacer.cpp:144

References Slic3r::TriangleSetSamples::positions, sample_hemisphere_uniform(), Slic3r::spAligned, and Slic3r::SeamPlacer::sqr_rays_per_sample_point.

Referenced by compute_global_occlusion().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sample_hemisphere_uniform()

Vec3f Slic3r::SeamPlacerImpl::sample_hemisphere_uniform ( const Vec2f & samples)
144 {
145 float term1 = 2.0f * float(PI) * samples.x();
146 float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y());
147 return {cos(term1) * term2, sin(term1) * term2,
148 abs(1.0f - 2.0f * samples.y())};
149}

References PI.

Referenced by raycast_visibility().

Here is the caller graph for this function:

◆ sample_power_cosine_hemisphere()

Vec3f Slic3r::SeamPlacerImpl::sample_power_cosine_hemisphere ( const Vec2f & samples,
float power )
151 {
152 float term1 = 2.f * float(PI) * samples.x();
153 float term2 = pow(samples.y(), 1.f / (power + 1.f));
154 float term3 = sqrt(1.f - term2 * term2);
155
156 return Vec3f(cos(term1) * term3, sin(term1) * term3, term2);
157}

References PI.

◆ sample_sphere_uniform()

Vec3f Slic3r::SeamPlacerImpl::sample_sphere_uniform ( const Vec2f & samples)
137 {
138 float term1 = 2.0f * float(PI) * samples.x();
139 float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y());
140 return {cos(term1) * term2, sin(term1) * term2,
141 1.0f - 2.0f * samples.y()};
142}

References PI.

◆ sgn()

template<typename T>
int Slic3r::SeamPlacerImpl::sgn ( T val)
69 {
70 return int(T(0) < val) - int(val < T(0));
71}

◆ smoothstep01()

float Slic3r::SeamPlacerImpl::smoothstep01 ( float x)
1455 {
1456 x = std::clamp(x, 0.0f, 1.0f);
1457 return x * x * (3.0f - 2.0f * x);
1458}

References smoothstep01().

Referenced by enforced_corner_influence(), and smoothstep01().

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ enforced_corner_angle_max

float Slic3r::SeamPlacerImpl::enforced_corner_angle_max = 30.0f * float(PI) / 180.0f
constexpr

◆ enforced_corner_angle_min

float Slic3r::SeamPlacerImpl::enforced_corner_angle_min = 15.0f * float(PI) / 180.0f
constexpr

◆ enforced_corner_fade_factor

float Slic3r::SeamPlacerImpl::enforced_corner_fade_factor = 2.0f
constexpr

◆ enforced_end_tolerance

double Slic3r::SeamPlacerImpl::enforced_end_tolerance = 1e-3
constexpr