From fdfe19ef4968615c2f73012d222775c79742bed0 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 28 Jan 2022 13:25:37 +0100 Subject: [PATCH 01/71] init version of occlusion estimation, raycasting implemented but for some reason only parts of model are estimated --- src/libslic3r/CMakeLists.txt | 4 +- src/libslic3r/GCode.hpp | 2 +- src/libslic3r/GCode/SeamPlacerNG.cpp | 266 +++++++++++++++++++++++++++ src/libslic3r/GCode/SeamPlacerNG.hpp | 82 +++++++++ 4 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 src/libslic3r/GCode/SeamPlacerNG.cpp create mode 100644 src/libslic3r/GCode/SeamPlacerNG.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 838af44fb..16af2bfdc 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -118,8 +118,8 @@ set(SLIC3R_SOURCES GCode/PrintExtents.hpp GCode/SpiralVase.cpp GCode/SpiralVase.hpp - GCode/SeamPlacer.cpp - GCode/SeamPlacer.hpp + GCode/SeamPlacerNG.cpp + GCode/SeamPlacerNG.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp GCode/WipeTower.cpp diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index aa4682e34..44f4b2271 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -14,7 +14,7 @@ #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" -#include "GCode/SeamPlacer.hpp" +#include "GCode/SeamPlacerNG.hpp" #include "GCode/GCodeProcessor.hpp" #include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp new file mode 100644 index 000000000..098de2574 --- /dev/null +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -0,0 +1,266 @@ +#include "SeamPlacerNG.hpp" + +#include "tbb/parallel_for.h" +#include "tbb/blocked_range.h" +#include "tbb/parallel_reduce.h" +#include +#include +#include +#include + +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/SVG.hpp" +#include "libslic3r/Layer.hpp" + +//TODO remove +#include + +namespace Slic3r { + +namespace SeamPlacerImpl { + +/// Coordinate frame +class Frame +{ +public: + Frame() + { + mX = Vec3d(1, 0, 0); + mY = Vec3d(0, 1, 0); + mZ = Vec3d(0, 0, 1); + } + + Frame(const Vec3d &x, const Vec3d &y, const Vec3d &z) + : mX(x), mY(y), mZ(z) + {} + + void set_from_z(const Vec3d &z) + { + mZ = z.normalized(); + Vec3d tmpZ = mZ; + Vec3d tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3d(0, 1, 0) : + Vec3d(1, 0, 0); + mY = (tmpZ.cross(tmpX)).normalized(); + mX = mY.cross(tmpZ); + } + + Vec3d to_world(const Vec3d &a) const + { + return a.x() * mX + a.y() * mY + a.z() * mZ; + } + + Vec3d to_local(const Vec3d &a) const + { + return Vec3d(mX.dot(a), mY.dot(a), mZ.dot(a)); + } + + const Vec3d &binormal() const { return mX; } + + const Vec3d &tangent() const { return mY; } + + const Vec3d &normal() const { return mZ; } + +private: + Vec3d mX, mY, mZ; +}; + +Vec3d sample_sphere_uniform(const Vec2f &samples) +{ + float term_one = 2.0f * M_PIf32 * samples.x(); + float term_two = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term_one) * term_two, sin(term_one) * term_two, + 1.0f - 2.0f * samples.y()}; +} + +Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) +{ + const float term1 = 2.f * M_PIf32 * samples.x(); + const float term2 = pow(samples.y(), 1.f / (power + 1.f)); + const float term3 = sqrt(1.f - term2 * term2); + + return Vec3d(cos(term1) * term3, sin(term1) * term3, term2); +} + +void raycast_visibility( + size_t ray_count, + const AABBTreeIndirect::Tree<3, float> &raycasting_tree, + const indexed_triangle_set &triangles, + const KDTreeIndirect<3, coordf_t, KDTreeCoordinateFunctor> + &perimeter_points_tree, + std::vector &perimeter_points) +{ + auto bbox = raycasting_tree.node(0).bbox; + Vec3d vision_sphere_center = bbox.center().cast(); + float vision_sphere_raidus = (bbox.sizes().maxCoeff() * + 0.55); // 0.5 (half) covers whole object, + // 0.05 added to avoid corner cases + + // Prepare random samples per ray + std::random_device rnd_device; + std::mt19937 mersenne_engine{rnd_device()}; + std::uniform_real_distribution dist{0, 1}; + + auto gen = [&dist, &mersenne_engine]() { + return Vec2f(dist(mersenne_engine), dist(mersenne_engine)); + }; + + BOOST_LOG_TRIVIAL(debug) << "PM: generate random samples: start"; + std::vector global_dir_random_samples(ray_count); + generate(begin(global_dir_random_samples), end(global_dir_random_samples), + gen); + std::vector local_dir_random_samples(ray_count); + generate(begin(local_dir_random_samples), end(local_dir_random_samples), + gen); + + BOOST_LOG_TRIVIAL(debug) << "PM: generate random samples: end"; + + std::vector> visibility_counters( + perimeter_points.size()); + + BOOST_LOG_TRIVIAL(debug) + << "PM: raycast visibility for " << ray_count << " rays: start"; + // raycast visibility + tbb::parallel_for( + tbb::blocked_range(0, ray_count), + [&](tbb::blocked_range r) { + for (size_t index = r.begin(); index < r.end(); ++index) { + Vec3d global_ray_dir = sample_sphere_uniform( + global_dir_random_samples[index]); + Vec3d ray_origin = (vision_sphere_center + + global_ray_dir * vision_sphere_raidus); + Vec3d local_dir = sample_power_cosine_hemisphere( + local_dir_random_samples[index], 1.0); + + Frame f; + f.set_from_z(global_ray_dir); + Vec3d final_ray_dir = (f.to_world(local_dir)); + + igl::Hit hitpoint; + // FIXME: This query will not compile for float ray origin and + // direction for some reason + auto hit = AABBTreeIndirect::intersect_ray_first_hit( + triangles.vertices, triangles.indices, raycasting_tree, + ray_origin, final_ray_dir, hitpoint); + + if (hit) { + auto face = triangles.indices[hitpoint.id]; + auto edge1 = triangles.vertices[face[1]] - + triangles.vertices[face[0]]; + auto edge2 = triangles.vertices[face[2]] - + triangles.vertices[face[0]]; + + Vec3d hit_pos = (triangles.vertices[face[0]] + + edge1 * hitpoint.u + edge2 * hitpoint.v) + .cast(); + + auto perimeter_point_index = + find_closest_point(perimeter_points_tree, hit_pos); + + visibility_counters[perimeter_point_index] + .fetch_add(1, std::memory_order_relaxed); + } + } + }); + + BOOST_LOG_TRIVIAL(debug) + << "PM: raycast visibility for " << ray_count << " rays: end"; + + BOOST_LOG_TRIVIAL(debug) + << "PM: assign visibility to perimenter points : start"; + + tbb::parallel_for(tbb::blocked_range(0, perimeter_points.size()), + [&](tbb::blocked_range r) { + for (size_t index = r.begin(); index < r.end(); + ++index) { + perimeter_points[index].visibility = + visibility_counters[index]; + } + }); + BOOST_LOG_TRIVIAL(debug) + << "PM: assign visibility to perimenter points : end"; + + its_write_obj(triangles, "triangles.obj"); + + Slic3r::CNumericLocalesSetter locales_setter; + FILE *fp = boost::nowide::fopen("perimeter.obj", "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "Couldn't open " + << "perimeter.obj" + << " for writing"; + } + + for (size_t i = 0; i < perimeter_points.size(); ++i) + fprintf(fp, "v %f %f %f %zu\n", perimeter_points[i].position[0], + perimeter_points[i].position[1], + perimeter_points[i].position[2], + perimeter_points[i].visibility); + fclose(fp); +} + +} // namespace SeamPlacerImpl + +void SeamPlacer::init(const Print &print) +{ + using namespace SeamPlacerImpl; + seam_candidates_trees.clear(); + + for (const PrintObject *po : print.objects()) { + BOOST_LOG_TRIVIAL(debug) + << "PM: build AABB tree for raycasting: start"; + // Build AABB tree for raycasting + auto obj_transform = po->trafo_centered(); + auto triangle_set = po->model_object()->raw_indexed_triangle_set(); + its_transform(triangle_set, obj_transform); + + auto raycasting_tree = + AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + triangle_set.vertices, triangle_set.indices); + + BOOST_LOG_TRIVIAL(debug) << "PM: build AABB tree for raycasting: end"; + + BOOST_LOG_TRIVIAL(debug) + << "PM: gather and build KD tree with seam candidates: start"; + // gather seam candidates (perimeter points) + auto seam_candidates = tbb::parallel_reduce( + tbb::blocked_range(0, po->layers().size()), + std::vector{}, + [&](tbb::blocked_range r, + std::vector init) { + for (size_t index = r.begin(); index < r.end(); ++index) { + const auto layer = po->layers()[index]; + auto unscaled_z = layer->slice_z; + for (Points points : layer->lslices) { + for (Point point : points) { + auto unscaled_p = unscale(point); + auto unscaled_position = Vec3d{unscaled_p.x(), + unscaled_p.y(), + unscaled_z}; + init.emplace_back(unscaled_position); + } + } + } + return init; + }, + [](std::vector prev, + std::vector next) { + prev.insert(prev.end(), next.begin(), next.end()); + return prev; + }); + // Build KD tree with seam candidates + auto functor = KDTreeCoordinateFunctor{&seam_candidates}; + auto perimeter_points_tree = KDTreeIndirect< + 3, coordf_t, KDTreeCoordinateFunctor>{functor, + seam_candidates.size()}; + + BOOST_LOG_TRIVIAL(debug) + << "PM: gather and build KD tree with seam candidates: end"; + + raycast_visibility(1000000, raycasting_tree, triangle_set, + perimeter_points_tree, seam_candidates); + } +} +} // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp new file mode 100644 index 000000000..75c394213 --- /dev/null +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -0,0 +1,82 @@ +#ifndef libslic3r_SeamPlacerNG_hpp_ +#define libslic3r_SeamPlacerNG_hpp_ + +#include +#include + +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/KDTreeIndirect.hpp" + +namespace Slic3r { + +class PrintObject; +class ExtrusionLoop; +class Print; +class Layer; + +namespace EdgeGrid { +class Grid; +} + +namespace SeamPlacerImpl { + +struct SeamCandidate +{ + SeamCandidate(const Vec3d &pos) : position(pos), visibility(0) {} + // TODO is there some equivalent for Vec with coordf_t type? + Vec3d position; + size_t visibility; +}; + +struct KDTreeCoordinateFunctor +{ + KDTreeCoordinateFunctor(std::vector *seam_candidates) + : seam_candidates(seam_candidates) + {} + std::vector *seam_candidates; + float operator()(size_t index, size_t dim) const + { + return seam_candidates->operator[](index).position[dim]; + } +}; +} // namespace SeamPlacerImpl + +class SeamPlacer +{ +public: + std::vector< + KDTreeIndirect<3, coordf_t, SeamPlacerImpl::KDTreeCoordinateFunctor>> + seam_candidates_trees; + + void init(const Print &print); + + void plan_perimeters(const std::vector perimeters, + const Layer &layer, + SeamPosition seam_position, + Point last_pos, + coordf_t nozzle_dmr, + const PrintObject *po, + const EdgeGrid::Grid *lower_layer_edge_grid) + {} + + void place_seam(ExtrusionLoop &loop, + const Point &last_pos, + bool external_first, + double nozzle_diameter, + const EdgeGrid::Grid *lower_layer_edge_grid) + { + Point seam = last_pos; + if (!loop.split_at_vertex(seam)) + // The point is not in the original loop. + // Insert it. + loop.split_at(seam, true); + } +}; + +} // namespace Slic3r + +#endif // libslic3r_SeamPlacerNG_hpp_ From fea247f261961f4276d0db0fb6315fe77f6db0ad Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 28 Jan 2022 14:17:09 +0100 Subject: [PATCH 02/71] Fixed bug - the rays had opposite directions, so all of them flown away from the object; also fixed compilation bug - missing import of timer.h --- src/libslic3r/GCode/SeamPlacerNG.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 098de2574..0b179797f 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -130,7 +130,7 @@ void raycast_visibility( for (size_t index = r.begin(); index < r.end(); ++index) { Vec3d global_ray_dir = sample_sphere_uniform( global_dir_random_samples[index]); - Vec3d ray_origin = (vision_sphere_center + + Vec3d ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); Vec3d local_dir = sample_power_cosine_hemisphere( local_dir_random_samples[index], 1.0); @@ -259,7 +259,7 @@ void SeamPlacer::init(const Print &print) BOOST_LOG_TRIVIAL(debug) << "PM: gather and build KD tree with seam candidates: end"; - raycast_visibility(1000000, raycasting_tree, triangle_set, + raycast_visibility(100000, raycasting_tree, triangle_set, perimeter_points_tree, seam_candidates); } } From 55e0f2dd83ac5e5f66ef639d7f3c6bc7bdcad6d7 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 1 Feb 2022 16:17:32 +0100 Subject: [PATCH 03/71] refactoring raycaster, gathering only hitpoints and their normals. --- src/libslic3r/GCode.cpp | 10 +- src/libslic3r/GCode/SeamPlacerNG.cpp | 345 +++++++++++++++------------ src/libslic3r/GCode/SeamPlacerNG.hpp | 80 ++++--- src/libslic3r/KDTreeIndirect.hpp | 53 +++- 4 files changed, 286 insertions(+), 202 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3d0ca5069..5f312183b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2586,7 +2586,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou loop.split_at(last_pos, false); } else - m_seam_placer.place_seam(loop, this->last_pos(), m_config.external_perimeters_first, + m_seam_placer.place_seam(m_layer->object(), loop, m_layer->slice_z, this->last_pos(), m_config.external_perimeters_first, EXTRUDER_CONFIG(nozzle_diameter), lower_layer_edge_grid ? lower_layer_edge_grid->get() : nullptr); // clip the path to avoid the extruder to get exactly on the first point of the loop; @@ -2715,10 +2715,10 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorlower_layer && ! lower_layer_edge_grid) lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer); - m_seam_placer.plan_perimeters(std::vector(region.perimeters.begin(), region.perimeters.end()), - *m_layer, m_config.seam_position, this->last_pos(), EXTRUDER_CONFIG(nozzle_diameter), - (m_layer == NULL ? nullptr : m_layer->object()), - (lower_layer_edge_grid ? lower_layer_edge_grid.get() : nullptr)); +// m_seam_placer.plan_perimeters(std::vector(region.perimeters.begin(), region.perimeters.end()), +// *m_layer, m_config.seam_position, this->last_pos(), EXTRUDER_CONFIG(nozzle_diameter), +// (m_layer == NULL ? nullptr : m_layer->object()), +// (lower_layer_edge_grid ? lower_layer_edge_grid.get() : nullptr)); for (const ExtrusionEntity* ee : region.perimeters) gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid); diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 0b179797f..a93ac299a 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Print.hpp" @@ -16,7 +15,7 @@ #include "libslic3r/SVG.hpp" #include "libslic3r/Layer.hpp" -//TODO remove +// TODO remove #include namespace Slic3r { @@ -24,60 +23,58 @@ namespace Slic3r { namespace SeamPlacerImpl { /// Coordinate frame -class Frame -{ +class Frame { public: - Frame() - { + Frame() { mX = Vec3d(1, 0, 0); mY = Vec3d(0, 1, 0); mZ = Vec3d(0, 0, 1); } - Frame(const Vec3d &x, const Vec3d &y, const Vec3d &z) - : mX(x), mY(y), mZ(z) - {} - - void set_from_z(const Vec3d &z) - { - mZ = z.normalized(); - Vec3d tmpZ = mZ; - Vec3d tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3d(0, 1, 0) : - Vec3d(1, 0, 0); - mY = (tmpZ.cross(tmpX)).normalized(); - mX = mY.cross(tmpZ); + Frame(const Vec3d &x, const Vec3d &y, const Vec3d &z) : + mX(x), mY(y), mZ(z) { } - Vec3d to_world(const Vec3d &a) const - { + void set_from_z(const Vec3d &z) { + mZ = z.normalized(); + Vec3d tmpZ = mZ; + Vec3d tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3d(0, 1, 0) : Vec3d(1, 0, 0); + mY = (tmpZ.cross(tmpX)).normalized(); + mX = mY.cross(tmpZ); + } + + Vec3d to_world(const Vec3d &a) const { return a.x() * mX + a.y() * mY + a.z() * mZ; } - Vec3d to_local(const Vec3d &a) const - { + Vec3d to_local(const Vec3d &a) const { return Vec3d(mX.dot(a), mY.dot(a), mZ.dot(a)); } - const Vec3d &binormal() const { return mX; } + const Vec3d& binormal() const { + return mX; + } - const Vec3d &tangent() const { return mY; } + const Vec3d& tangent() const { + return mY; + } - const Vec3d &normal() const { return mZ; } + const Vec3d& normal() const { + return mZ; + } private: Vec3d mX, mY, mZ; }; -Vec3d sample_sphere_uniform(const Vec2f &samples) -{ +Vec3d sample_sphere_uniform(const Vec2f &samples) { float term_one = 2.0f * M_PIf32 * samples.x(); float term_two = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); return {cos(term_one) * term_two, sin(term_one) * term_two, - 1.0f - 2.0f * samples.y()}; + 1.0f - 2.0f * samples.y()}; } -Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) -{ +Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) { const float term1 = 2.f * M_PIf32 * samples.x(); const float term2 = pow(samples.y(), 1.f / (power + 1.f)); const float term3 = sqrt(1.f - term2 * term2); @@ -85,182 +82,220 @@ Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) return Vec3d(cos(term1) * term3, sin(term1) * term3, term2); } -void raycast_visibility( - size_t ray_count, - const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles, - const KDTreeIndirect<3, coordf_t, KDTreeCoordinateFunctor> - &perimeter_points_tree, - std::vector &perimeter_points) -{ - auto bbox = raycasting_tree.node(0).bbox; +void resolve_geometry_hit(const igl::Hit &hitpoint, + const indexed_triangle_set &triangles, + const KDTreeIndirect<3, coordf_t, KDTreeCoordinateFunctor> &perimeter_points_tree, + const std::vector &perimeter_points, + std::vector> &visibility_counters) { + auto face = triangles.indices[hitpoint.id]; + auto edge1 = triangles.vertices[face[1]] - triangles.vertices[face[0]]; + auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; + + Vec3d hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v).cast(); + auto perimeter_point_index = find_closest_point(perimeter_points_tree, hit_pos); + auto perimeter_point_pos = perimeter_points[perimeter_point_index].position; + auto dist_squared = (perimeter_point_pos - hit_pos).squaredNorm(); + + visibility_counters[perimeter_point_index].fetch_add(1.0 / dist_squared, std::memory_order_relaxed); +} + +std::vector raycast_visibility(size_t ray_count, + const AABBTreeIndirect::Tree<3, float> &raycasting_tree, + const indexed_triangle_set &triangles) { + auto bbox = raycasting_tree.node(0).bbox; Vec3d vision_sphere_center = bbox.center().cast(); - float vision_sphere_raidus = (bbox.sizes().maxCoeff() * - 0.55); // 0.5 (half) covers whole object, - // 0.05 added to avoid corner cases + float vision_sphere_raidus = (bbox.sizes().maxCoeff() * 0.55); // 0.5 (half) covers whole object, + // 0.05 added to avoid corner cases // Prepare random samples per ray - std::random_device rnd_device; - std::mt19937 mersenne_engine{rnd_device()}; - std::uniform_real_distribution dist{0, 1}; + std::random_device rnd_device; + std::mt19937 mersenne_engine { rnd_device() }; + std::uniform_real_distribution dist { 0, 1 }; auto gen = [&dist, &mersenne_engine]() { return Vec2f(dist(mersenne_engine), dist(mersenne_engine)); }; - BOOST_LOG_TRIVIAL(debug) << "PM: generate random samples: start"; + BOOST_LOG_TRIVIAL(debug) + << "PM: generate random samples: start"; std::vector global_dir_random_samples(ray_count); - generate(begin(global_dir_random_samples), end(global_dir_random_samples), - gen); + generate(begin(global_dir_random_samples), end(global_dir_random_samples), gen); std::vector local_dir_random_samples(ray_count); - generate(begin(local_dir_random_samples), end(local_dir_random_samples), - gen); - - BOOST_LOG_TRIVIAL(debug) << "PM: generate random samples: end"; - - std::vector> visibility_counters( - perimeter_points.size()); + generate(begin(local_dir_random_samples), end(local_dir_random_samples), gen); BOOST_LOG_TRIVIAL(debug) - << "PM: raycast visibility for " << ray_count << " rays: start"; + << "PM: generate random samples: end"; + + BOOST_LOG_TRIVIAL(debug) + << "PM: raycast visibility for " << ray_count << " rays: start"; // raycast visibility - tbb::parallel_for( - tbb::blocked_range(0, ray_count), - [&](tbb::blocked_range r) { - for (size_t index = r.begin(); index < r.end(); ++index) { - Vec3d global_ray_dir = sample_sphere_uniform( - global_dir_random_samples[index]); - Vec3d ray_origin = (vision_sphere_center - - global_ray_dir * vision_sphere_raidus); - Vec3d local_dir = sample_power_cosine_hemisphere( - local_dir_random_samples[index], 1.0); + std::vector hit_points = tbb::parallel_reduce(tbb::blocked_range(0, ray_count), + std::vector { }, + [&](tbb::blocked_range r, std::vector init) { + for (size_t index = r.begin(); index < r.end(); ++index) { + Vec3d global_ray_dir = sample_sphere_uniform(global_dir_random_samples[index]); + Vec3d ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); + Vec3d local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], 1.5); - Frame f; - f.set_from_z(global_ray_dir); - Vec3d final_ray_dir = (f.to_world(local_dir)); + Frame f; + f.set_from_z(global_ray_dir); + Vec3d final_ray_dir = (f.to_world(local_dir)); - igl::Hit hitpoint; - // FIXME: This query will not compile for float ray origin and - // direction for some reason - auto hit = AABBTreeIndirect::intersect_ray_first_hit( - triangles.vertices, triangles.indices, raycasting_tree, - ray_origin, final_ray_dir, hitpoint); + igl::Hit hitpoint; + // FIXME: This query will not compile for float ray origin and + // direction for some reason + auto hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, + triangles.indices, raycasting_tree, ray_origin, final_ray_dir, hitpoint); - if (hit) { - auto face = triangles.indices[hitpoint.id]; - auto edge1 = triangles.vertices[face[1]] - - triangles.vertices[face[0]]; - auto edge2 = triangles.vertices[face[2]] - - triangles.vertices[face[0]]; + if (hit) { + auto face = triangles.indices[hitpoint.id]; + auto edge1 = triangles.vertices[face[1]] - triangles.vertices[face[0]]; + auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; - Vec3d hit_pos = (triangles.vertices[face[0]] + - edge1 * hitpoint.u + edge2 * hitpoint.v) - .cast(); + Vec3d hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v).cast(); + Vec3d surface_normal = edge1.cross(edge2).cast(); - auto perimeter_point_index = - find_closest_point(perimeter_points_tree, hit_pos); - - visibility_counters[perimeter_point_index] - .fetch_add(1, std::memory_order_relaxed); + init.push_back(HitInfo { hit_pos, surface_normal }); + } } + return init; + }, + [](std::vector left, std::vector right) { + left.insert(left.end(), right.begin(), right.end()); + return left; } - }); + ); BOOST_LOG_TRIVIAL(debug) - << "PM: raycast visibility for " << ray_count << " rays: end"; - - BOOST_LOG_TRIVIAL(debug) - << "PM: assign visibility to perimenter points : start"; - - tbb::parallel_for(tbb::blocked_range(0, perimeter_points.size()), - [&](tbb::blocked_range r) { - for (size_t index = r.begin(); index < r.end(); - ++index) { - perimeter_points[index].visibility = - visibility_counters[index]; - } - }); - BOOST_LOG_TRIVIAL(debug) - << "PM: assign visibility to perimenter points : end"; + << "PM: raycast visibility for " << ray_count << " rays: end"; +//TODO disable, only debug code +//#ifdef 0 its_write_obj(triangles, "triangles.obj"); Slic3r::CNumericLocalesSetter locales_setter; FILE *fp = boost::nowide::fopen("perimeter.obj", "w"); if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) << "Couldn't open " - << "perimeter.obj" - << " for writing"; + BOOST_LOG_TRIVIAL(error) + << "Couldn't open " << "perimeter.obj" << " for writing"; } - for (size_t i = 0; i < perimeter_points.size(); ++i) - fprintf(fp, "v %f %f %f %zu\n", perimeter_points[i].position[0], - perimeter_points[i].position[1], - perimeter_points[i].position[2], - perimeter_points[i].visibility); + for (size_t i = 0; i < hit_points.size(); ++i) + fprintf(fp, "v %f %f %f \n", hit_points[i].m_position[0], hit_points[i].m_position[1], + hit_points[i].m_position[2]); fclose(fp); +//#endif + + return hit_points; } } // namespace SeamPlacerImpl -void SeamPlacer::init(const Print &print) -{ +void SeamPlacer::init(const Print &print) { using namespace SeamPlacerImpl; - seam_candidates_trees.clear(); + m_perimeter_points_trees_per_object.clear(); + m_perimeter_points_per_object.clear(); for (const PrintObject *po : print.objects()) { BOOST_LOG_TRIVIAL(debug) - << "PM: build AABB tree for raycasting: start"; + << "PM: build AABB tree for raycasting: start"; // Build AABB tree for raycasting auto obj_transform = po->trafo_centered(); - auto triangle_set = po->model_object()->raw_indexed_triangle_set(); + auto triangle_set = po->model_object()->raw_indexed_triangle_set(); its_transform(triangle_set, obj_transform); - auto raycasting_tree = - AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - triangle_set.vertices, triangle_set.indices); - - BOOST_LOG_TRIVIAL(debug) << "PM: build AABB tree for raycasting: end"; + auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, + triangle_set.indices); BOOST_LOG_TRIVIAL(debug) - << "PM: gather and build KD tree with seam candidates: start"; + << "PM: build AABB tree for raycasting: end"; + + std::vector hit_points = raycast_visibility(ray_count_per_object, raycasting_tree, triangle_set); + HitInfoCoordinateFunctor hit_points_functor { &hit_points }; + KDTreeIndirect<3, coordf_t, HitInfoCoordinateFunctor> hit_points_tree { hit_points_functor, hit_points.size() }; + + BOOST_LOG_TRIVIAL(debug) + << "PM: gather and build KD tree with seam candidates: start"; // gather seam candidates (perimeter points) - auto seam_candidates = tbb::parallel_reduce( - tbb::blocked_range(0, po->layers().size()), - std::vector{}, - [&](tbb::blocked_range r, - std::vector init) { - for (size_t index = r.begin(); index < r.end(); ++index) { - const auto layer = po->layers()[index]; - auto unscaled_z = layer->slice_z; - for (Points points : layer->lslices) { - for (Point point : points) { - auto unscaled_p = unscale(point); - auto unscaled_position = Vec3d{unscaled_p.x(), - unscaled_p.y(), - unscaled_z}; - init.emplace_back(unscaled_position); + m_perimeter_points_per_object[po] = tbb::parallel_reduce(tbb::blocked_range(0, po->layers().size()), + std::vector { }, [&](tbb::blocked_range r, std::vector init) { + for (size_t index = r.begin(); index < r.end(); ++index) { + const auto layer = po->layers()[index]; + auto unscaled_z = layer->slice_z; + for (const ExPolygon &expoly : layer->lslices) { + // Contour - insert first point marked as Polygon + // start, then insert rest sequentially. + { + auto unscaled_p = unscale(expoly.contour[0]); + init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }, true); + } + for (size_t index = 1; index < expoly.contour.size(); ++index) { + auto unscaled_p = unscale(expoly.contour[index]); + init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); + } + + for (const Polygon &hole : expoly.holes) { + // Perform the same for each hole + { + auto unscaled_p = unscale(hole[0]); + init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }, true); + } + for (size_t index = 1; index < hole.size(); ++index) { + auto unscaled_p = unscale(hole[index]); + init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); + } + } } } - } - return init; - }, - [](std::vector prev, - std::vector next) { - prev.insert(prev.end(), next.begin(), next.end()); - return prev; - }); + return init; + }, + [](std::vector prev, std::vector next) { + prev.insert(prev.end(), next.begin(), next.end()); + return prev; + }); + auto &perimeter_points = m_perimeter_points_per_object[po]; // Build KD tree with seam candidates - auto functor = KDTreeCoordinateFunctor{&seam_candidates}; - auto perimeter_points_tree = KDTreeIndirect< - 3, coordf_t, KDTreeCoordinateFunctor>{functor, - seam_candidates.size()}; + auto functor = KDTreeCoordinateFunctor { &perimeter_points }; + m_perimeter_points_trees_per_object.emplace(std::piecewise_construct, std::forward_as_tuple(po), + std::forward_as_tuple(functor, m_perimeter_points_per_object[po].size())); + SeamPlacer::PointTree &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; BOOST_LOG_TRIVIAL(debug) - << "PM: gather and build KD tree with seam candidates: end"; - - raycast_visibility(100000, raycasting_tree, triangle_set, - perimeter_points_tree, seam_candidates); + << "PM: gather and build KD tree with seam candidates: end"; } } + +void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, const Point &last_pos, + bool external_first, + double nozzle_diameter, const EdgeGrid::Grid *lower_layer_edge_grid) { + assert(m_perimeter_points_trees_per_object.find(po) != nullptr); + assert(m_perimeter_points_per_object.find(po) != nullptr); + const auto &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; + const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second; + + Points loop_points { }; + loop.collect_points(loop_points); + + // vector of pairs: first-> index into perimeter points, second-> index into loop points + auto closest_perimeter_point_indices = std::vector>(loop_points.size()); + for (size_t p_index = 0; p_index < loop_points.size(); ++p_index) { + auto unscaled_p = unscale(loop_points[p_index]); + closest_perimeter_point_indices.emplace_back(find_closest_point(perimeter_points_tree, + Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }), p_index); + } + + std::sort(closest_perimeter_point_indices.begin(), closest_perimeter_point_indices.end(), + [&](const std::pair left, const std::pair right) { + return perimeter_points[left.first].visibility < perimeter_points[right.first].visibility; + }); + + loop.split_at_vertex(loop_points[closest_perimeter_point_indices[0].second]); +// +// Point seam = last_pos; +// if (!loop.split_at_vertex(seam)) +// // The point is not in the original loop. +// // Insert it. +// loop.split_at(seam, true); +} + } // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 75c394213..feeedaec8 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -24,57 +24,59 @@ class Grid; namespace SeamPlacerImpl { -struct SeamCandidate -{ - SeamCandidate(const Vec3d &pos) : position(pos), visibility(0) {} - // TODO is there some equivalent for Vec with coordf_t type? - Vec3d position; - size_t visibility; +struct SeamCandidate { + explicit SeamCandidate(const Vec3d &pos) : + position(pos), visibility(0.0), polygon_start(false) { + } + SeamCandidate(const Vec3d &pos, bool polygon_start) : + position(pos), visibility(0.0), polygon_start(polygon_start) { + } + Vec3d position; + float visibility; + bool polygon_start; }; -struct KDTreeCoordinateFunctor -{ - KDTreeCoordinateFunctor(std::vector *seam_candidates) - : seam_candidates(seam_candidates) - {} +struct HitInfo { + Vec3d m_position; + Vec3d m_surface_normal; +}; + + +struct KDTreeCoordinateFunctor { + KDTreeCoordinateFunctor(std::vector *seam_candidates) : + seam_candidates(seam_candidates) { + } std::vector *seam_candidates; - float operator()(size_t index, size_t dim) const - { + float operator()(size_t index, size_t dim) const { return seam_candidates->operator[](index).position[dim]; } }; + +struct HitInfoCoordinateFunctor { + HitInfoCoordinateFunctor(std::vector *hit_points) : + m_hit_points(hit_points) { + } + std::vector *m_hit_points; + float operator()(size_t index, size_t dim) const { + return m_hit_points->operator[](index).m_position[dim]; + } +}; } // namespace SeamPlacerImpl -class SeamPlacer -{ +class SeamPlacer { + using PointTree = + KDTreeIndirect<3, coordf_t, SeamPlacerImpl::KDTreeCoordinateFunctor>; + const size_t ray_count_per_object = 100000; + public: - std::vector< - KDTreeIndirect<3, coordf_t, SeamPlacerImpl::KDTreeCoordinateFunctor>> - seam_candidates_trees; + std::unordered_map m_perimeter_points_trees_per_object; + std::unordered_map> m_perimeter_points_per_object; void init(const Print &print); - void plan_perimeters(const std::vector perimeters, - const Layer &layer, - SeamPosition seam_position, - Point last_pos, - coordf_t nozzle_dmr, - const PrintObject *po, - const EdgeGrid::Grid *lower_layer_edge_grid) - {} - - void place_seam(ExtrusionLoop &loop, - const Point &last_pos, - bool external_first, - double nozzle_diameter, - const EdgeGrid::Grid *lower_layer_edge_grid) - { - Point seam = last_pos; - if (!loop.split_at_vertex(seam)) - // The point is not in the original loop. - // Insert it. - loop.split_at(seam, true); - } + void place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, const Point &last_pos, + bool external_first, + double nozzle_diameter, const EdgeGrid::Grid *lower_layer_edge_grid); }; } // namespace Slic3r diff --git a/src/libslic3r/KDTreeIndirect.hpp b/src/libslic3r/KDTreeIndirect.hpp index 12e462569..38e020e1f 100644 --- a/src/libslic3r/KDTreeIndirect.hpp +++ b/src/libslic3r/KDTreeIndirect.hpp @@ -59,7 +59,7 @@ public: CONTINUE_RIGHT = 2, STOP = 4, }; - template + template unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const { CoordType dist = point_coord - this->coordinate(idx, dimension); @@ -110,8 +110,8 @@ private: // Partition the input m_nodes at "k" and "dimension" using the QuickSelect method: // https://en.wikipedia.org/wiki/Quickselect - // Items left of the k'th item are lower than the k'th item in the "dimension", - // items right of the k'th item are higher than the k'th item in the "dimension", + // Items left of the k'th item are lower than the k'th item in the "dimension", + // items right of the k'th item are higher than the k'th item in the "dimension", void partition_input(std::vector &input, const size_t dimension, size_t left, size_t right, const size_t k) const { while (left < right) { @@ -231,6 +231,53 @@ size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& poi return find_closest_point(kdtree, point, [](size_t) { return true; }); } +// Find a nearby points (spherical neighbourhood) using Euclidian metrics. +template +std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, + typename KDTreeIndirectType::CoordType& max_distance, FilterFn filter) + { + using CoordType = typename KDTreeIndirectType::CoordType; + + struct Visitor { + const KDTreeIndirectType &kdtree; + const PointType ¢er; + const CoordType &max_distance_squared; + const FilterFn filter; + std::vector result; + + Visitor(const KDTreeIndirectType &kdtree, const PointType ¢er, const CoordType &max_distance, + FilterFn filter) : + kdtree(kdtree), center(center), max_distance_squared(max_distance*max_distance), filter(filter) { + } + unsigned int operator()(size_t idx, size_t dimension) { + if (this->filter(idx)) { + auto dist = CoordType(0); + for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) { + CoordType d = center[i] - kdtree.coordinate(idx, i); + dist += d * d; + } + if (dist < max_distance_squared) { + result.push_back(idx); + } + } + return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension); + } + } visitor(kdtree, center, max_distance, filter); + + kdtree.visit(visitor); + return visitor.result; +} + +template +std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, + typename KDTreeIndirectType::CoordType& max_distance) + { + return find_nearby_points(kdtree, center, max_distance, [](size_t) { + return true; + }); +} + + } // namespace Slic3r #endif /* slic3r_KDTreeIndirect_hpp_ */ From 45b49ad545ced055b969188dd7b1e44e903b5ac5 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 2 Feb 2022 14:14:55 +0100 Subject: [PATCH 04/71] initial demo fixed KD tree neighbour search, finished refactoring of raycasts, substantially improved performance of seam placement - seams are now precomputed and their indexes stored such that place_seam does almost nothing. --- src/libslic3r/GCode/SeamPlacerNG.cpp | 192 ++++++++++++++++++--------- src/libslic3r/GCode/SeamPlacerNG.hpp | 20 ++- src/libslic3r/KDTreeIndirect.hpp | 14 +- 3 files changed, 143 insertions(+), 83 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index a93ac299a..53c740a63 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -82,23 +82,6 @@ Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) { return Vec3d(cos(term1) * term3, sin(term1) * term3, term2); } -void resolve_geometry_hit(const igl::Hit &hitpoint, - const indexed_triangle_set &triangles, - const KDTreeIndirect<3, coordf_t, KDTreeCoordinateFunctor> &perimeter_points_tree, - const std::vector &perimeter_points, - std::vector> &visibility_counters) { - auto face = triangles.indices[hitpoint.id]; - auto edge1 = triangles.vertices[face[1]] - triangles.vertices[face[0]]; - auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; - - Vec3d hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v).cast(); - auto perimeter_point_index = find_closest_point(perimeter_points_tree, hit_pos); - auto perimeter_point_pos = perimeter_points[perimeter_point_index].position; - auto dist_squared = (perimeter_point_pos - hit_pos).squaredNorm(); - - visibility_counters[perimeter_point_index].fetch_add(1.0 / dist_squared, std::memory_order_relaxed); -} - std::vector raycast_visibility(size_t ray_count, const AABBTreeIndirect::Tree<3, float> &raycasting_tree, const indexed_triangle_set &triangles) { @@ -135,7 +118,7 @@ std::vector raycast_visibility(size_t ray_count, for (size_t index = r.begin(); index < r.end(); ++index) { Vec3d global_ray_dir = sample_sphere_uniform(global_dir_random_samples[index]); Vec3d ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); - Vec3d local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], 1.5); + Vec3d local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], 2.0); Frame f; f.set_from_z(global_ray_dir); @@ -153,14 +136,14 @@ std::vector raycast_visibility(size_t ray_count, auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; Vec3d hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v).cast(); - Vec3d surface_normal = edge1.cross(edge2).cast(); + Vec3d surface_normal = edge1.cross(edge2).cast().normalized(); init.push_back(HitInfo { hit_pos, surface_normal }); } } return init; }, - [](std::vector left, std::vector right) { + [](std::vector left, const std::vector& right) { left.insert(left.end(), right.begin(), right.end()); return left; } @@ -174,10 +157,10 @@ std::vector raycast_visibility(size_t ray_count, its_write_obj(triangles, "triangles.obj"); Slic3r::CNumericLocalesSetter locales_setter; - FILE *fp = boost::nowide::fopen("perimeter.obj", "w"); + FILE *fp = boost::nowide::fopen("hits.obj", "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << "perimeter.obj" << " for writing"; + << "Couldn't open " << "hits.obj" << " for writing"; } for (size_t i = 0; i < hit_points.size(); ++i) @@ -189,6 +172,30 @@ std::vector raycast_visibility(size_t ray_count, return hit_points; } +void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::vector &result_vec) { + for (size_t index = 0; index < polygon.size(); ++index) { + Vec2d unscaled_p = unscale(polygon[index]); + Vec3d unscaled_position = Vec3d { unscaled_p.x(), unscaled_p.y(), z_coord }; + result_vec.emplace_back(unscaled_position, polygon.size() - index - 1); + } +} + +void pick_seam_point(std::vector &perimeter_points, size_t start_index, size_t end_index) { + auto min_visibility = perimeter_points[start_index].m_visibility; + size_t seam_index = start_index; + for (size_t index = start_index + 1; index <= end_index; ++index) { + if (perimeter_points[index].m_visibility < min_visibility) { + min_visibility = perimeter_points[index].m_visibility; + seam_index = index; + } + } + + for (size_t index = start_index; index <= end_index; ++index) { + perimeter_points[index].m_seam_index = seam_index; + } + +} + } // namespace SeamPlacerImpl void SeamPlacer::init(const Print &print) { @@ -223,45 +230,109 @@ void SeamPlacer::init(const Print &print) { const auto layer = po->layers()[index]; auto unscaled_z = layer->slice_z; for (const ExPolygon &expoly : layer->lslices) { - // Contour - insert first point marked as Polygon - // start, then insert rest sequentially. - { - auto unscaled_p = unscale(expoly.contour[0]); - init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }, true); - } - for (size_t index = 1; index < expoly.contour.size(); ++index) { - auto unscaled_p = unscale(expoly.contour[index]); - init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); - } - + process_perimeter_polygon(expoly.contour, unscaled_z, init); for (const Polygon &hole : expoly.holes) { - // Perform the same for each hole - { - auto unscaled_p = unscale(hole[0]); - init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }, true); - } - for (size_t index = 1; index < hole.size(); ++index) { - auto unscaled_p = unscale(hole[index]); - init.emplace_back(Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); - } + process_perimeter_polygon(hole, unscaled_z, init); } } } return init; }, - [](std::vector prev, std::vector next) { + [](std::vector prev, const std::vector &next) { prev.insert(prev.end(), next.begin(), next.end()); return prev; }); auto &perimeter_points = m_perimeter_points_per_object[po]; + + BOOST_LOG_TRIVIAL(debug) + << "PM: gather and build KD tree with seam candidates: end"; + + BOOST_LOG_TRIVIAL(debug) + << "PM: gather visibility data into perimeter points : start"; + + tbb::parallel_for(tbb::blocked_range(0, perimeter_points.size()), + [&](tbb::blocked_range r) { + for (size_t index = r.begin(); index < r.end(); ++index) { + SeamCandidate &perimeter_point = perimeter_points[index]; + auto filter = [&](size_t hit_point_index) { + const HitInfo &hit_point = hit_points[hit_point_index]; + Vec3d tolerance_center = hit_point.m_position - hit_point.m_surface_normal * EPSILON; + double signed_distance_from_hit_place = (perimeter_point.m_position - tolerance_center).dot( + hit_point.m_surface_normal); + return signed_distance_from_hit_place >= 0 && signed_distance_from_hit_place <= 1.0; + }; + + auto nearby_points = find_nearby_points(hit_points_tree, perimeter_point.m_position, + considered_hits_distance, filter); + double visibility = 0; + for (const auto &hit_point_index : nearby_points) { + double distance = + (perimeter_point.m_position - hit_points[hit_point_index].m_position).norm(); + visibility += considered_hits_distance - distance; // The further away from the perimeter point, + // the less representative ray hit is + } + perimeter_point.m_visibility = visibility; + } + }); + + //TODO disable, only debug code + //#ifdef 0 + Slic3r::CNumericLocalesSetter locales_setter; + FILE *fp = boost::nowide::fopen("perimeters.obj", "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Couldn't open " << "perimeters.obj" << " for writing"; + } + + for (size_t i = 0; i < perimeter_points.size(); ++i) + fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].m_position[0], perimeter_points[i].m_position[1], + perimeter_points[i].m_position[2], perimeter_points[i].m_visibility); + fclose(fp); + //#endif + // Build KD tree with seam candidates auto functor = KDTreeCoordinateFunctor { &perimeter_points }; m_perimeter_points_trees_per_object.emplace(std::piecewise_construct, std::forward_as_tuple(po), std::forward_as_tuple(functor, m_perimeter_points_per_object[po].size())); - SeamPlacer::PointTree &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; +// SeamPlacer::PointTree &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; BOOST_LOG_TRIVIAL(debug) - << "PM: gather and build KD tree with seam candidates: end"; + << "PM: gather visibility data into perimeter points : end"; + + BOOST_LOG_TRIVIAL(debug) + << "PM: find seam for each perimeter polygon and store its position in each member of the polygon : start"; + + assert(perimeter_points.back().m_polygon_index_reverse == 0); + + tbb::parallel_for(tbb::blocked_range(0, perimeter_points.size()), + [&](tbb::blocked_range r) { + //find nearest next perimeter polygon start + size_t start = r.begin(); + while (perimeter_points[start].m_polygon_index_reverse != 0) { + start++; + }; + start++; + if (start == perimeter_points.size()) + return; + //find nearest polygon end after range end; The perimeter_points must end with point with index 0 + // start at r.end() -1, because tbb uses exlusive range. + size_t end = r.end() - 1; + while (perimeter_points[end].m_polygon_index_reverse != 0) { + end++; + }; + while (start <= end) { + pick_seam_point(perimeter_points, start, + start + perimeter_points[start].m_polygon_index_reverse); + start += perimeter_points[start].m_polygon_index_reverse + 1; + } + + }); + //Above parallel_for does not consider the first perimeter polygon, so do it additionally + pick_seam_point(perimeter_points, 0, perimeter_points[0].m_polygon_index_reverse); + + BOOST_LOG_TRIVIAL(debug) + << "PM: find seam for each perimeter polygon and store its position in each member of the polygon : end"; + } } @@ -273,29 +344,20 @@ void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t const auto &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second; - Points loop_points { }; - loop.collect_points(loop_points); + const Point &fp = loop.first_point(); - // vector of pairs: first-> index into perimeter points, second-> index into loop points - auto closest_perimeter_point_indices = std::vector>(loop_points.size()); - for (size_t p_index = 0; p_index < loop_points.size(); ++p_index) { - auto unscaled_p = unscale(loop_points[p_index]); - closest_perimeter_point_indices.emplace_back(find_closest_point(perimeter_points_tree, - Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }), p_index); - } + auto unscaled_p = unscale(fp); + auto closest_perimeter_point_index = find_closest_point(perimeter_points_tree, + Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); + size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].m_seam_index; + Vec3d seam_position = perimeter_points[perimeter_seam_index].m_position; - std::sort(closest_perimeter_point_indices.begin(), closest_perimeter_point_indices.end(), - [&](const std::pair left, const std::pair right) { - return perimeter_points[left.first].visibility < perimeter_points[right.first].visibility; - }); + Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); - loop.split_at_vertex(loop_points[closest_perimeter_point_indices[0].second]); -// -// Point seam = last_pos; -// if (!loop.split_at_vertex(seam)) -// // The point is not in the original loop. -// // Insert it. -// loop.split_at(seam, true); + if (!loop.split_at_vertex(seam_point)) + // The point is not in the original loop. + // Insert it. + loop.split_at(seam_point, true); } } // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index feeedaec8..7b027288e 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -25,15 +25,13 @@ class Grid; namespace SeamPlacerImpl { struct SeamCandidate { - explicit SeamCandidate(const Vec3d &pos) : - position(pos), visibility(0.0), polygon_start(false) { + SeamCandidate(const Vec3d &pos, size_t polygon_index_reverse) : + m_position(pos), m_visibility(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index(0) { } - SeamCandidate(const Vec3d &pos, bool polygon_start) : - position(pos), visibility(0.0), polygon_start(polygon_start) { - } - Vec3d position; - float visibility; - bool polygon_start; + Vec3d m_position; + float m_visibility; + size_t m_polygon_index_reverse; + size_t m_seam_index; }; struct HitInfo { @@ -41,20 +39,19 @@ struct HitInfo { Vec3d m_surface_normal; }; - struct KDTreeCoordinateFunctor { KDTreeCoordinateFunctor(std::vector *seam_candidates) : seam_candidates(seam_candidates) { } std::vector *seam_candidates; float operator()(size_t index, size_t dim) const { - return seam_candidates->operator[](index).position[dim]; + return seam_candidates->operator[](index).m_position[dim]; } }; struct HitInfoCoordinateFunctor { HitInfoCoordinateFunctor(std::vector *hit_points) : - m_hit_points(hit_points) { + m_hit_points(hit_points) { } std::vector *m_hit_points; float operator()(size_t index, size_t dim) const { @@ -67,6 +64,7 @@ class SeamPlacer { using PointTree = KDTreeIndirect<3, coordf_t, SeamPlacerImpl::KDTreeCoordinateFunctor>; const size_t ray_count_per_object = 100000; + const double considered_hits_distance = 4.0; public: std::unordered_map m_perimeter_points_trees_per_object; diff --git a/src/libslic3r/KDTreeIndirect.hpp b/src/libslic3r/KDTreeIndirect.hpp index 38e020e1f..36a157456 100644 --- a/src/libslic3r/KDTreeIndirect.hpp +++ b/src/libslic3r/KDTreeIndirect.hpp @@ -231,21 +231,21 @@ size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& poi return find_closest_point(kdtree, point, [](size_t) { return true; }); } -// Find a nearby points (spherical neighbourhood) using Euclidian metrics. +// Find nearby points (spherical neighbourhood) using Euclidian metrics. template std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, - typename KDTreeIndirectType::CoordType& max_distance, FilterFn filter) + const typename KDTreeIndirectType::CoordType& max_distance, FilterFn filter) { using CoordType = typename KDTreeIndirectType::CoordType; struct Visitor { const KDTreeIndirectType &kdtree; - const PointType ¢er; - const CoordType &max_distance_squared; + const PointType center; + const CoordType max_distance_squared; const FilterFn filter; std::vector result; - Visitor(const KDTreeIndirectType &kdtree, const PointType ¢er, const CoordType &max_distance, + Visitor(const KDTreeIndirectType &kdtree, const PointType& center, const CoordType &max_distance, FilterFn filter) : kdtree(kdtree), center(center), max_distance_squared(max_distance*max_distance), filter(filter) { } @@ -260,7 +260,7 @@ std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const P result.push_back(idx); } } - return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension); + return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension); } } visitor(kdtree, center, max_distance, filter); @@ -270,7 +270,7 @@ std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const P template std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, - typename KDTreeIndirectType::CoordType& max_distance) + const typename KDTreeIndirectType::CoordType& max_distance) { return find_nearby_points(kdtree, center, max_distance, [](size_t) { return true; From 38a6e231f2f4835dc05b6deaafe8790dfb32c9ba Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 3 Feb 2022 12:37:35 +0100 Subject: [PATCH 05/71] minor changes, more transparent parameters settings --- src/libslic3r/GCode/SeamPlacerNG.cpp | 55 ++++++++++++++-------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 5 ++- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 53c740a63..97a0304e7 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -68,16 +68,16 @@ private: }; Vec3d sample_sphere_uniform(const Vec2f &samples) { - float term_one = 2.0f * M_PIf32 * samples.x(); - float term_two = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); - return {cos(term_one) * term_two, sin(term_one) * term_two, + float term1 = 2.0f * M_PIf32 * samples.x(); + float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term1) * term2, sin(term1) * term2, 1.0f - 2.0f * samples.y()}; } Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) { - const float term1 = 2.f * M_PIf32 * samples.x(); - const float term2 = pow(samples.y(), 1.f / (power + 1.f)); - const float term3 = sqrt(1.f - term2 * term2); + float term1 = 2.f * M_PIf32 * samples.x(); + float term2 = pow(samples.y(), 1.f / (power + 1.f)); + float term3 = sqrt(1.f - term2 * term2); return Vec3d(cos(term1) * term3, sin(term1) * term3, term2); } @@ -118,7 +118,7 @@ std::vector raycast_visibility(size_t ray_count, for (size_t index = r.begin(); index < r.end(); ++index) { Vec3d global_ray_dir = sample_sphere_uniform(global_dir_random_samples[index]); Vec3d ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); - Vec3d local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], 2.0); + Vec3d local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], SeamPlacer::cosine_hemisphere_sampling_power); Frame f; f.set_from_z(global_ray_dir); @@ -153,7 +153,7 @@ std::vector raycast_visibility(size_t ray_count, << "PM: raycast visibility for " << ray_count << " rays: end"; //TODO disable, only debug code -//#ifdef 0 +#ifdef DEBUG_FILES its_write_obj(triangles, "triangles.obj"); Slic3r::CNumericLocalesSetter locales_setter; @@ -167,7 +167,7 @@ std::vector raycast_visibility(size_t ray_count, fprintf(fp, "v %f %f %f \n", hit_points[i].m_position[0], hit_points[i].m_position[1], hit_points[i].m_position[2]); fclose(fp); -//#endif +#endif return hit_points; } @@ -254,16 +254,16 @@ void SeamPlacer::init(const Print &print) { [&](tbb::blocked_range r) { for (size_t index = r.begin(); index < r.end(); ++index) { SeamCandidate &perimeter_point = perimeter_points[index]; - auto filter = [&](size_t hit_point_index) { - const HitInfo &hit_point = hit_points[hit_point_index]; - Vec3d tolerance_center = hit_point.m_position - hit_point.m_surface_normal * EPSILON; - double signed_distance_from_hit_place = (perimeter_point.m_position - tolerance_center).dot( - hit_point.m_surface_normal); - return signed_distance_from_hit_place >= 0 && signed_distance_from_hit_place <= 1.0; - }; +// auto filter = [&](size_t hit_point_index) { +// const HitInfo &hit_point = hit_points[hit_point_index]; +// Vec3d tolerance_center = hit_point.m_position - hit_point.m_surface_normal * EPSILON; +// double signed_distance_from_hit_place = (perimeter_point.m_position - tolerance_center).dot( +// hit_point.m_surface_normal); +// return signed_distance_from_hit_place >= 0 && signed_distance_from_hit_place <= 1.0; +// }; auto nearby_points = find_nearby_points(hit_points_tree, perimeter_point.m_position, - considered_hits_distance, filter); + considered_hits_distance); double visibility = 0; for (const auto &hit_point_index : nearby_points) { double distance = @@ -275,8 +275,7 @@ void SeamPlacer::init(const Print &print) { } }); - //TODO disable, only debug code - //#ifdef 0 +#ifdef DEBUG_FILES Slic3r::CNumericLocalesSetter locales_setter; FILE *fp = boost::nowide::fopen("perimeters.obj", "w"); if (fp == nullptr) { @@ -285,19 +284,19 @@ void SeamPlacer::init(const Print &print) { } for (size_t i = 0; i < perimeter_points.size(); ++i) - fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].m_position[0], perimeter_points[i].m_position[1], - perimeter_points[i].m_position[2], perimeter_points[i].m_visibility); + fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].m_position[0], perimeter_points[i].m_position[1], + perimeter_points[i].m_position[2], perimeter_points[i].m_visibility); fclose(fp); - //#endif +#endif + + BOOST_LOG_TRIVIAL(debug) + << "PM: gather visibility data into perimeter points : end"; // Build KD tree with seam candidates auto functor = KDTreeCoordinateFunctor { &perimeter_points }; m_perimeter_points_trees_per_object.emplace(std::piecewise_construct, std::forward_as_tuple(po), std::forward_as_tuple(functor, m_perimeter_points_per_object[po].size())); -// SeamPlacer::PointTree &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; - - BOOST_LOG_TRIVIAL(debug) - << "PM: gather visibility data into perimeter points : end"; + // SeamPlacer::PointTree &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; BOOST_LOG_TRIVIAL(debug) << "PM: find seam for each perimeter polygon and store its position in each member of the polygon : start"; @@ -315,7 +314,7 @@ void SeamPlacer::init(const Print &print) { if (start == perimeter_points.size()) return; //find nearest polygon end after range end; The perimeter_points must end with point with index 0 - // start at r.end() -1, because tbb uses exlusive range. + // start at r.end() -1, because tbb uses exlusive range, and we want inclusive range size_t end = r.end() - 1; while (perimeter_points[end].m_polygon_index_reverse != 0) { end++; @@ -327,7 +326,7 @@ void SeamPlacer::init(const Print &print) { } }); - //Above parallel_for does not consider the first perimeter polygon, so do it additionally + //Above parallel_for does not consider the first perimeter polygon, so add it additionally pick_seam_point(perimeter_points, 0, perimeter_points[0].m_polygon_index_reverse); BOOST_LOG_TRIVIAL(debug) diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 7b027288e..235b7a665 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -63,10 +63,11 @@ struct HitInfoCoordinateFunctor { class SeamPlacer { using PointTree = KDTreeIndirect<3, coordf_t, SeamPlacerImpl::KDTreeCoordinateFunctor>; - const size_t ray_count_per_object = 100000; - const double considered_hits_distance = 4.0; + const size_t ray_count_per_object = 150000; + const double considered_hits_distance = 2.0; public: + static constexpr float cosine_hemisphere_sampling_power = 1.5; std::unordered_map m_perimeter_points_trees_per_object; std::unordered_map> m_perimeter_points_per_object; From 53e9bb3ebfcf753700f686060e3dc5f2c3010ba8 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 4 Feb 2022 16:22:30 +0100 Subject: [PATCH 06/71] integration of enforcers and blockers --- src/libslic3r/GCode.cpp | 3 +- src/libslic3r/GCode/SeamPlacerNG.cpp | 289 +++++++++++++++++++-------- src/libslic3r/GCode/SeamPlacerNG.hpp | 39 ++-- 3 files changed, 233 insertions(+), 98 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5f312183b..4c7135103 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2586,8 +2586,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou loop.split_at(last_pos, false); } else - m_seam_placer.place_seam(m_layer->object(), loop, m_layer->slice_z, this->last_pos(), m_config.external_perimeters_first, - EXTRUDER_CONFIG(nozzle_diameter), lower_layer_edge_grid ? lower_layer_edge_grid->get() : nullptr); + m_seam_placer.place_seam(m_layer->object(), loop, m_layer->slice_z, m_layer_index, m_config.external_perimeters_first); // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 97a0304e7..ecb19acc2 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -91,8 +91,8 @@ std::vector raycast_visibility(size_t ray_count, // 0.05 added to avoid corner cases // Prepare random samples per ray - std::random_device rnd_device; - std::mt19937 mersenne_engine { rnd_device() }; +// std::random_device rnd_device; + std::mt19937 mersenne_engine { 12345 }; std::uniform_real_distribution dist { 0, 1 }; auto gen = [&dist, &mersenne_engine]() { @@ -125,7 +125,7 @@ std::vector raycast_visibility(size_t ray_count, Vec3d final_ray_dir = (f.to_world(local_dir)); igl::Hit hitpoint; - // FIXME: This query will not compile for float ray origin and + // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and // direction for some reason auto hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, triangles.indices, raycasting_tree, ray_origin, final_ray_dir, hitpoint); @@ -135,7 +135,8 @@ std::vector raycast_visibility(size_t ray_count, auto edge1 = triangles.vertices[face[1]] - triangles.vertices[face[0]]; auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; - Vec3d hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v).cast(); + Vec3d hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v).cast< + double>(); Vec3d surface_normal = edge1.cross(edge2).cast().normalized(); init.push_back(HitInfo { hit_pos, surface_normal }); @@ -172,20 +173,130 @@ std::vector raycast_visibility(size_t ray_count, return hit_points; } -void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::vector &result_vec) { +std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, + float min_arm_length) + { + assert(polygon.points.size() + 1 == lengths.size()); + if (min_arm_length > 0.25f * lengths.back()) + min_arm_length = 0.25f * lengths.back(); + + // Find the initial prev / next point span. + size_t idx_prev = polygon.points.size(); + size_t idx_curr = 0; + size_t idx_next = 1; + while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) + --idx_prev; + while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) + ++idx_next; + + std::vector angles(polygon.points.size(), 0.f); + for (; idx_curr < polygon.points.size(); ++idx_curr) { + // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. + if (idx_prev >= idx_curr) { + while (idx_prev < polygon.points.size() + && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) + ++idx_prev; + if (idx_prev == polygon.points.size()) + idx_prev = 0; + } + while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) + ++idx_prev; + // Move idx_prev one step back. + if (idx_prev == 0) + idx_prev = polygon.points.size() - 1; + else + --idx_prev; + // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. + if (idx_curr <= idx_next) { + while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) + ++idx_next; + if (idx_next == polygon.points.size()) + idx_next = 0; + } + while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) + ++idx_next; + // Calculate angle between idx_prev, idx_curr, idx_next. + const Point &p0 = polygon.points[idx_prev]; + const Point &p1 = polygon.points[idx_curr]; + const Point &p2 = polygon.points[idx_next]; + const Point v1 = p1 - p0; + const Point v2 = p2 - p1; + int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); + int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); + float angle = float(atan2(double(cross), double(dot))); + angles[idx_curr] = angle; + } + + return angles; +} + +template +void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::vector &result_vec, + const EnforcerDistance &enforcer_distance_check, const BlockerDistance &blocker_distance_check) { + std::vector lengths = polygon.parameter_by_length(); + std::vector angles = calculate_polygon_angles_at_vertices(polygon, lengths, + SeamPlacer::polygon_angles_arm_distance); + bool is_ccw = polygon.is_counter_clockwise(); + + Vec3d last_enforcer_checked_point { 0, 0, -1 }; + double enforcer_dist_sqr = enforcer_distance_check(last_enforcer_checked_point); + Vec3d last_blocker_checked_point { 0, 0, -1 }; + double blocker_dist_sqr = blocker_distance_check(last_blocker_checked_point); + for (size_t index = 0; index < polygon.size(); ++index) { Vec2d unscaled_p = unscale(polygon[index]); Vec3d unscaled_position = Vec3d { unscaled_p.x(), unscaled_p.y(), z_coord }; - result_vec.emplace_back(unscaled_position, polygon.size() - index - 1); + EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::NONE; + + float ccw_angle = angles[index]; + if (!is_ccw) { + ccw_angle = -ccw_angle; + } + + if (enforcer_dist_sqr >= 0) { // if enforcer dist < 0, it means there are no enforcers, skip + //if there is enforcer, any other enforcer cannot be in a sphere defined by last check point and enforcer distance + // so as long as we are at least enforcer_blocker_distance_tolerance deep in that area, and the enforcer distance is greater than + // enforcer_blocker_distance_tolerance, we are fine. + if (enforcer_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance + || + (last_enforcer_checked_point - unscaled_position).squaredNorm() + >= enforcer_dist_sqr - 2 * SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { + //do check + enforcer_dist_sqr = enforcer_distance_check(unscaled_position); + last_enforcer_checked_point = unscaled_position; + if (enforcer_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { + type = EnforcedBlockedSeamPoint::ENFORCED; + } + } + } + //same for blockers + if (blocker_dist_sqr >= 0) { + if (blocker_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance + || + (last_blocker_checked_point - unscaled_position).squaredNorm() + >= blocker_dist_sqr - 2 * SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { + blocker_dist_sqr = blocker_distance_check(unscaled_position); + last_blocker_checked_point = unscaled_position; + if (blocker_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { + type = EnforcedBlockedSeamPoint::BLOCKED; + } + } + } + + result_vec.emplace_back(unscaled_position, polygon.size() - index - 1, ccw_angle, type); } } void pick_seam_point(std::vector &perimeter_points, size_t start_index, size_t end_index) { auto min_visibility = perimeter_points[start_index].m_visibility; + auto type = perimeter_points[start_index].m_type; size_t seam_index = start_index; for (size_t index = start_index + 1; index <= end_index; ++index) { - if (perimeter_points[index].m_visibility < min_visibility) { + if ((perimeter_points[index].m_visibility < min_visibility && perimeter_points[index].m_type == type) + || + (perimeter_points[index].m_type > type)) { min_visibility = perimeter_points[index].m_visibility; + type = perimeter_points[index].m_type; seam_index = index; } } @@ -193,7 +304,6 @@ void pick_seam_point(std::vector &perimeter_points, size_t start_ for (size_t index = start_index; index <= end_index; ++index) { perimeter_points[index].m_seam_index = seam_index; } - } } // namespace SeamPlacerImpl @@ -217,32 +327,73 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "PM: build AABB tree for raycasting: end"; + BOOST_LOG_TRIVIAL(debug) + << "PM: build AABB trees for raycasting enforcers/blockers: start"; + + indexed_triangle_set enforcers { }; + indexed_triangle_set blockers { }; + for (const ModelVolume *mv : po->model_object()->volumes) { + if (mv->is_model_part()) { + its_merge(enforcers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER)); + its_merge(blockers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER)); + } + } + its_transform(enforcers, obj_transform); + its_transform(blockers, obj_transform); + + auto enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(enforcers.vertices, + enforcers.indices); + auto blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(blockers.vertices, + blockers.indices); + + auto enforcer_distance_check = [&](const Vec3d ¢er) { + size_t hit_idx_out; + Vec3d closest_vec3d; + return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(enforcers.vertices, enforcers.indices, + enforcers_tree, center, hit_idx_out, closest_vec3d); + }; + auto blocker_distance_check = [&](const Vec3d ¢er) { + size_t hit_idx_out; + Vec3d closest_vec3d; + return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(blockers.vertices, blockers.indices, + blockers_tree, center, hit_idx_out, closest_vec3d); + }; + + BOOST_LOG_TRIVIAL(debug) + << "PM: build AABB trees for raycasting enforcers/blockers: end"; + std::vector hit_points = raycast_visibility(ray_count_per_object, raycasting_tree, triangle_set); HitInfoCoordinateFunctor hit_points_functor { &hit_points }; KDTreeIndirect<3, coordf_t, HitInfoCoordinateFunctor> hit_points_tree { hit_points_functor, hit_points.size() }; BOOST_LOG_TRIVIAL(debug) - << "PM: gather and build KD tree with seam candidates: start"; - // gather seam candidates (perimeter points) - m_perimeter_points_per_object[po] = tbb::parallel_reduce(tbb::blocked_range(0, po->layers().size()), - std::vector { }, [&](tbb::blocked_range r, std::vector init) { - for (size_t index = r.begin(); index < r.end(); ++index) { - const auto layer = po->layers()[index]; + << "PM: gather and build KD trees with seam candidates: start"; + + m_perimeter_points_per_object.emplace(po, po->layer_count()); + m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); + + tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + + const auto layer = po->get_layer(layer_idx); auto unscaled_z = layer->slice_z; - for (const ExPolygon &expoly : layer->lslices) { - process_perimeter_polygon(expoly.contour, unscaled_z, init); - for (const Polygon &hole : expoly.holes) { - process_perimeter_polygon(hole, unscaled_z, init); + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + auto polygons = ex_entity->polygons_covered_by_width(); + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + enforcer_distance_check, blocker_distance_check); + } } } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( + functor, layer_candidates.size())); + } - return init; - }, - [](std::vector prev, const std::vector &next) { - prev.insert(prev.end(), next.begin(), next.end()); - return prev; }); - auto &perimeter_points = m_perimeter_points_per_object[po]; BOOST_LOG_TRIVIAL(debug) << "PM: gather and build KD tree with seam candidates: end"; @@ -250,31 +401,27 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "PM: gather visibility data into perimeter points : start"; - tbb::parallel_for(tbb::blocked_range(0, perimeter_points.size()), + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { - for (size_t index = r.begin(); index < r.end(); ++index) { - SeamCandidate &perimeter_point = perimeter_points[index]; -// auto filter = [&](size_t hit_point_index) { -// const HitInfo &hit_point = hit_points[hit_point_index]; -// Vec3d tolerance_center = hit_point.m_position - hit_point.m_surface_normal * EPSILON; -// double signed_distance_from_hit_place = (perimeter_point.m_position - tolerance_center).dot( -// hit_point.m_surface_normal); -// return signed_distance_from_hit_place >= 0 && signed_distance_from_hit_place <= 1.0; -// }; - - auto nearby_points = find_nearby_points(hit_points_tree, perimeter_point.m_position, - considered_hits_distance); - double visibility = 0; - for (const auto &hit_point_index : nearby_points) { - double distance = - (perimeter_point.m_position - hit_points[hit_point_index].m_position).norm(); - visibility += considered_hits_distance - distance; // The further away from the perimeter point, - // the less representative ray hit is + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + auto nearby_points = find_nearby_points(hit_points_tree, perimeter_point.m_position, + considered_hits_distance); + double visibility = 0; + for (const auto &hit_point_index : nearby_points) { + double distance = + (perimeter_point.m_position - hit_points[hit_point_index].m_position).norm(); + visibility += considered_hits_distance - distance; // The further away from the perimeter point, + // the less representative ray hit is + } + perimeter_point.m_visibility = visibility; } - perimeter_point.m_visibility = visibility; } }); + BOOST_LOG_TRIVIAL(debug) + << "PM: gather visibility data into perimeter points : end"; + #ifdef DEBUG_FILES Slic3r::CNumericLocalesSetter locales_setter; FILE *fp = boost::nowide::fopen("perimeters.obj", "w"); @@ -289,45 +436,22 @@ void SeamPlacer::init(const Print &print) { fclose(fp); #endif - BOOST_LOG_TRIVIAL(debug) - << "PM: gather visibility data into perimeter points : end"; - - // Build KD tree with seam candidates - auto functor = KDTreeCoordinateFunctor { &perimeter_points }; - m_perimeter_points_trees_per_object.emplace(std::piecewise_construct, std::forward_as_tuple(po), - std::forward_as_tuple(functor, m_perimeter_points_per_object[po].size())); - // SeamPlacer::PointTree &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; - BOOST_LOG_TRIVIAL(debug) << "PM: find seam for each perimeter polygon and store its position in each member of the polygon : start"; - assert(perimeter_points.back().m_polygon_index_reverse == 0); - - tbb::parallel_for(tbb::blocked_range(0, perimeter_points.size()), + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { - //find nearest next perimeter polygon start - size_t start = r.begin(); - while (perimeter_points[start].m_polygon_index_reverse != 0) { - start++; - }; - start++; - if (start == perimeter_points.size()) - return; - //find nearest polygon end after range end; The perimeter_points must end with point with index 0 - // start at r.end() -1, because tbb uses exlusive range, and we want inclusive range - size_t end = r.end() - 1; - while (perimeter_points[end].m_polygon_index_reverse != 0) { - end++; - }; - while (start <= end) { - pick_seam_point(perimeter_points, start, - start + perimeter_points[start].m_polygon_index_reverse); - start += perimeter_points[start].m_polygon_index_reverse + 1; + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + size_t current = 0; + while (current < layer_perimeter_points.size()) { + pick_seam_point(layer_perimeter_points, current, + current + layer_perimeter_points[current].m_polygon_index_reverse); + current += layer_perimeter_points[current].m_polygon_index_reverse + 1; + } } - }); - //Above parallel_for does not consider the first perimeter polygon, so add it additionally - pick_seam_point(perimeter_points, 0, perimeter_points[0].m_polygon_index_reverse); BOOST_LOG_TRIVIAL(debug) << "PM: find seam for each perimeter polygon and store its position in each member of the polygon : end"; @@ -335,13 +459,14 @@ void SeamPlacer::init(const Print &print) { } } -void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, const Point &last_pos, - bool external_first, - double nozzle_diameter, const EdgeGrid::Grid *lower_layer_edge_grid) { +void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, int layer_index, + bool external_first) { assert(m_perimeter_points_trees_per_object.find(po) != nullptr); assert(m_perimeter_points_per_object.find(po) != nullptr); - const auto &perimeter_points_tree = m_perimeter_points_trees_per_object.find(po)->second; - const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second; + + assert(layer_index >= 0); + const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; + const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; const Point &fp = loop.first_point(); diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 235b7a665..f7cac19ff 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Polygon.hpp" @@ -24,14 +25,23 @@ class Grid; namespace SeamPlacerImpl { +enum EnforcedBlockedSeamPoint{ + BLOCKED = 0, + NONE = 1, + ENFORCED = 2, +}; + struct SeamCandidate { - SeamCandidate(const Vec3d &pos, size_t polygon_index_reverse) : - m_position(pos), m_visibility(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index(0) { + SeamCandidate(const Vec3d &pos, size_t polygon_index_reverse, float ccw_angle, EnforcedBlockedSeamPoint type) : + m_position(pos), m_visibility(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index(0), m_ccw_angle( + ccw_angle), m_type(type) { } Vec3d m_position; float m_visibility; size_t m_polygon_index_reverse; size_t m_seam_index; + float m_ccw_angle; + EnforcedBlockedSeamPoint m_type; }; struct HitInfo { @@ -39,8 +49,8 @@ struct HitInfo { Vec3d m_surface_normal; }; -struct KDTreeCoordinateFunctor { - KDTreeCoordinateFunctor(std::vector *seam_candidates) : +struct SeamCandidateCoordinateFunctor { + SeamCandidateCoordinateFunctor(std::vector *seam_candidates) : seam_candidates(seam_candidates) { } std::vector *seam_candidates; @@ -61,21 +71,22 @@ struct HitInfoCoordinateFunctor { } // namespace SeamPlacerImpl class SeamPlacer { - using PointTree = - KDTreeIndirect<3, coordf_t, SeamPlacerImpl::KDTreeCoordinateFunctor>; - const size_t ray_count_per_object = 150000; - const double considered_hits_distance = 2.0; - public: + using SeamCandidatesTree = + KDTreeIndirect<3, coordf_t, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; + const size_t ray_count_per_object = 100000; + const double considered_hits_distance = 2.0; static constexpr float cosine_hemisphere_sampling_power = 1.5; - std::unordered_map m_perimeter_points_trees_per_object; - std::unordered_map> m_perimeter_points_per_object; + static constexpr float polygon_angles_arm_distance = 0.6; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.02; + //perimeter points per object per layer idx, and their corresponding KD trees + std::unordered_map>> m_perimeter_points_per_object; + std::unordered_map>> m_perimeter_points_trees_per_object; void init(const Print &print); - void place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, const Point &last_pos, - bool external_first, - double nozzle_diameter, const EdgeGrid::Grid *lower_layer_edge_grid); + void place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, int layer_index, + bool external_first); }; } // namespace Slic3r From e8f740dabbfaac8fe1e1b2b2d52932ae1702f041 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 10 Feb 2022 17:34:36 +0100 Subject: [PATCH 07/71] implemented overhang calculation and alignemnt iterations for seams now only external perimeters are considered which reduced time complexity --- src/libslic3r/GCode/SeamPlacerNG.cpp | 502 ++++++++++++++++++--------- src/libslic3r/GCode/SeamPlacerNG.hpp | 23 +- 2 files changed, 362 insertions(+), 163 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index ecb19acc2..23ce7f81b 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -100,17 +100,17 @@ std::vector raycast_visibility(size_t ray_count, }; BOOST_LOG_TRIVIAL(debug) - << "PM: generate random samples: start"; + << "SeamPlacer: generate random samples: start"; std::vector global_dir_random_samples(ray_count); generate(begin(global_dir_random_samples), end(global_dir_random_samples), gen); std::vector local_dir_random_samples(ray_count); generate(begin(local_dir_random_samples), end(local_dir_random_samples), gen); BOOST_LOG_TRIVIAL(debug) - << "PM: generate random samples: end"; + << "SeamPlacer: generate random samples: end"; BOOST_LOG_TRIVIAL(debug) - << "PM: raycast visibility for " << ray_count << " rays: start"; + << "SeamPlacer: raycast visibility for " << ray_count << " rays: start"; // raycast visibility std::vector hit_points = tbb::parallel_reduce(tbb::blocked_range(0, ray_count), std::vector { }, @@ -151,24 +151,7 @@ std::vector raycast_visibility(size_t ray_count, ); BOOST_LOG_TRIVIAL(debug) - << "PM: raycast visibility for " << ray_count << " rays: end"; - -//TODO disable, only debug code -#ifdef DEBUG_FILES - its_write_obj(triangles, "triangles.obj"); - - Slic3r::CNumericLocalesSetter locales_setter; - FILE *fp = boost::nowide::fopen("hits.obj", "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << "hits.obj" << " for writing"; - } - - for (size_t i = 0; i < hit_points.size(); ++i) - fprintf(fp, "v %f %f %f \n", hit_points[i].m_position[0], hit_points[i].m_position[1], - hit_points[i].m_position[2]); - fclose(fp); -#endif + << "SeamPlacer: raycast visibility for " << ray_count << " rays: end"; return hit_points; } @@ -230,18 +213,59 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, return angles; } -template -void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::vector &result_vec, - const EnforcerDistance &enforcer_distance_check, const BlockerDistance &blocker_distance_check) { +struct GlobalModelInfo { + std::vector geometry_raycast_hits; + KDTreeIndirect<3, coordf_t, HitInfoCoordinateFunctor> raycast_hits_tree; + indexed_triangle_set enforcers; + indexed_triangle_set blockers; + AABBTreeIndirect::Tree<3, float> enforcers_tree; + AABBTreeIndirect::Tree<3, float> blockers_tree; + + GlobalModelInfo() : + raycast_hits_tree(HitInfoCoordinateFunctor { &geometry_raycast_hits }) { + } + + double enforcer_distance_check(const Vec3d &position) const { + size_t hit_idx_out; + Vec3d closest_vec3d; + return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(enforcers.vertices, enforcers.indices, + enforcers_tree, position, hit_idx_out, closest_vec3d); + } + + double blocker_distance_check(const Vec3d &position) const { + size_t hit_idx_out; + Vec3d closest_vec3d; + return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(blockers.vertices, blockers.indices, + blockers_tree, position, hit_idx_out, closest_vec3d); + } + + double calculate_point_visibility(const Vec3d &position, double max_distance) const { + auto nearby_points = find_nearby_points(raycast_hits_tree, position, max_distance); + double visibility = 0; + for (const auto &hit_point_index : nearby_points) { + double distance = + (position - geometry_raycast_hits[hit_point_index].m_position).norm(); + visibility += max_distance - distance; // The further away from the perimeter point, + // the less representative ray hit is + } + return visibility; + + } +} +; + +void process_perimeter_polygon(const Polygon &orig_polygon, coordf_t z_coord, std::vector &result_vec, + const GlobalModelInfo &global_model_info) { + Polygon polygon = orig_polygon; + polygon.make_counter_clockwise(); std::vector lengths = polygon.parameter_by_length(); std::vector angles = calculate_polygon_angles_at_vertices(polygon, lengths, SeamPlacer::polygon_angles_arm_distance); - bool is_ccw = polygon.is_counter_clockwise(); Vec3d last_enforcer_checked_point { 0, 0, -1 }; - double enforcer_dist_sqr = enforcer_distance_check(last_enforcer_checked_point); + double enforcer_dist_sqr = global_model_info.enforcer_distance_check(last_enforcer_checked_point); Vec3d last_blocker_checked_point { 0, 0, -1 }; - double blocker_dist_sqr = blocker_distance_check(last_blocker_checked_point); + double blocker_dist_sqr = global_model_info.blocker_distance_check(last_blocker_checked_point); for (size_t index = 0; index < polygon.size(); ++index) { Vec2d unscaled_p = unscale(polygon[index]); @@ -249,9 +273,6 @@ void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::ve EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::NONE; float ccw_angle = angles[index]; - if (!is_ccw) { - ccw_angle = -ccw_angle; - } if (enforcer_dist_sqr >= 0) { // if enforcer dist < 0, it means there are no enforcers, skip //if there is enforcer, any other enforcer cannot be in a sphere defined by last check point and enforcer distance @@ -262,7 +283,7 @@ void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::ve (last_enforcer_checked_point - unscaled_position).squaredNorm() >= enforcer_dist_sqr - 2 * SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { //do check - enforcer_dist_sqr = enforcer_distance_check(unscaled_position); + enforcer_dist_sqr = global_model_info.enforcer_distance_check(unscaled_position); last_enforcer_checked_point = unscaled_position; if (enforcer_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { type = EnforcedBlockedSeamPoint::ENFORCED; @@ -275,7 +296,7 @@ void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::ve || (last_blocker_checked_point - unscaled_position).squaredNorm() >= blocker_dist_sqr - 2 * SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { - blocker_dist_sqr = blocker_distance_check(unscaled_position); + blocker_dist_sqr = global_model_info.blocker_distance_check(unscaled_position); last_blocker_checked_point = unscaled_position; if (blocker_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { type = EnforcedBlockedSeamPoint::BLOCKED; @@ -287,157 +308,288 @@ void process_perimeter_polygon(const Polygon &polygon, coordf_t z_coord, std::ve } } -void pick_seam_point(std::vector &perimeter_points, size_t start_index, size_t end_index) { - auto min_visibility = perimeter_points[start_index].m_visibility; - auto type = perimeter_points[start_index].m_type; +std::pair find_previous_and_next_perimeter_point(const std::vector &perimeter_points, + size_t index) { + const SeamCandidate ¤t = perimeter_points[index]; + + size_t prev = index > 0 ? index - 1 : index; + size_t next = index + 1 < perimeter_points.size() ? index + 1 : index; + + //NOTE: dont forget that m_polygon_index_reverse are reversed indexes, so 0 is last point + if (current.m_polygon_index_reverse == 0) { + // next is at the start of loop + //find start + size_t start = index; + while (start > 0 && perimeter_points[start - 1].m_polygon_index_reverse != 0) { + start--; + } + next = start; + } + + if (index > 1 && perimeter_points[index - 1].m_polygon_index_reverse == 0) { + //prev is at the end of loop + prev = index + perimeter_points[index].m_polygon_index_reverse; + } + + return {prev,next}; +} + +float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b, + const SeamCandidate &under_c) { + auto p = Vec2d { point.m_position.x(), point.m_position.y() }; + auto a = Vec2d { under_a.m_position.x(), under_a.m_position.y() }; + auto b = Vec2d { under_b.m_position.x(), under_b.m_position.y() }; + auto c = Vec2d { under_c.m_position.x(), under_c.m_position.y() }; + + auto oriented_line_dist = [](const Vec2d a, const Vec2d b, const Vec2d p) { + return -((b.x() - a.x()) * (a.y() - p.y()) - (a.x() - p.x()) * (b.y() - a.y())) / (a - b).norm(); + }; + + auto dist_ab = oriented_line_dist(a, b, p); + auto dist_bc = oriented_line_dist(b, c, p); + + if (under_b.m_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside + return 0; + } + + if (under_b.m_ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside + return 0; + } + + return Vec2d((p - b).norm(), std::min(abs(dist_ab), abs(dist_bc))).norm(); + +} + +template +void pick_seam_point(std::vector &perimeter_points, size_t start_index, size_t end_index, + const CompareFunc &is_first_better) { size_t seam_index = start_index; for (size_t index = start_index + 1; index <= end_index; ++index) { - if ((perimeter_points[index].m_visibility < min_visibility && perimeter_points[index].m_type == type) - || - (perimeter_points[index].m_type > type)) { - min_visibility = perimeter_points[index].m_visibility; - type = perimeter_points[index].m_type; + if (is_first_better(perimeter_points[index], perimeter_points[seam_index])) { seam_index = index; } } for (size_t index = start_index; index <= end_index; ++index) { perimeter_points[index].m_seam_index = seam_index; + perimeter_points[index].m_nearby_seam_points.get()->store(0, std::memory_order_relaxed); } } +void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; +// Build AABB tree for raycasting + auto obj_transform = po->trafo_centered(); + auto triangle_set = po->model_object()->raw_indexed_triangle_set(); + its_transform(triangle_set, obj_transform); + + auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, + triangle_set.indices); + + result.geometry_raycast_hits = raycast_visibility(SeamPlacer::ray_count_per_object, raycasting_tree, + triangle_set); + result.raycast_hits_tree.build(result.geometry_raycast_hits.size()); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; + + for (const ModelVolume *mv : po->model_object()->volumes) { + if (mv->is_model_part()) { + its_merge(result.enforcers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER)); + its_merge(result.blockers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER)); + } + } + its_transform(result.enforcers, obj_transform); + its_transform(result.blockers, obj_transform); + + result.enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.enforcers.vertices, + result.enforcers.indices); + result.blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.blockers.vertices, + result.blockers.indices); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; +} + +struct DefaultSeamComparator { + //is a better? + bool operator()(const SeamCandidate &a, const SeamCandidate &b) const { + if (a.m_type > b.m_type) { + return true; + } + if (b.m_type > a.m_type) { + return false; + } + +// if (a.m_overhang > 0.2 && b.m_overhang < a.m_overhang) { +// return false; +// } +// +// if (b.m_ccw_angle < -float(0.3 * PI) && a.m_ccw_angle > -float(0.3 * PI)){ +// return false; +// } + + if (*b.m_nearby_seam_points > *a.m_nearby_seam_points) { + return false; + } + + if (b.m_visibility < 1.2*a.m_visibility) { + return false; + } + + + return true; + } +} +; + } // namespace SeamPlacerImpl void SeamPlacer::init(const Print &print) { - using namespace SeamPlacerImpl; - m_perimeter_points_trees_per_object.clear(); - m_perimeter_points_per_object.clear(); +using namespace SeamPlacerImpl; +m_perimeter_points_trees_per_object.clear(); +m_perimeter_points_per_object.clear(); - for (const PrintObject *po : print.objects()) { - BOOST_LOG_TRIVIAL(debug) - << "PM: build AABB tree for raycasting: start"; - // Build AABB tree for raycasting - auto obj_transform = po->trafo_centered(); - auto triangle_set = po->model_object()->raw_indexed_triangle_set(); - its_transform(triangle_set, obj_transform); +for (const PrintObject *po : print.objects()) { - auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, - triangle_set.indices); + GlobalModelInfo global_model_info { }; + gather_global_model_info(global_model_info, po); - BOOST_LOG_TRIVIAL(debug) - << "PM: build AABB tree for raycasting: end"; + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather and build KD trees with seam candidates: start"; - BOOST_LOG_TRIVIAL(debug) - << "PM: build AABB trees for raycasting enforcers/blockers: start"; + m_perimeter_points_per_object.emplace(po, po->layer_count()); + m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); - indexed_triangle_set enforcers { }; - indexed_triangle_set blockers { }; - for (const ModelVolume *mv : po->model_object()->volumes) { - if (mv->is_model_part()) { - its_merge(enforcers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER)); - its_merge(blockers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER)); + tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + const auto layer = po->get_layer(layer_idx); + auto unscaled_z = layer->slice_z; + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + Polygons polygons; + if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters + for (const ExtrusionEntity *perimeter : + static_cast(ex_entity)->entities) { + if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { + perimeter->polygons_covered_by_width(polygons, 0); + } + } + } else { + polygons = ex_entity->polygons_covered_by_width(); + } + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + global_model_info); } } - its_transform(enforcers, obj_transform); - its_transform(blockers, obj_transform); + } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( + functor, layer_candidates.size())); - auto enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(enforcers.vertices, - enforcers.indices); - auto blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(blockers.vertices, - blockers.indices); +} +} ); - auto enforcer_distance_check = [&](const Vec3d ¢er) { - size_t hit_idx_out; - Vec3d closest_vec3d; - return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(enforcers.vertices, enforcers.indices, - enforcers_tree, center, hit_idx_out, closest_vec3d); - }; - auto blocker_distance_check = [&](const Vec3d ¢er) { - size_t hit_idx_out; - Vec3d closest_vec3d; - return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(blockers.vertices, blockers.indices, - blockers_tree, center, hit_idx_out, closest_vec3d); - }; + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather and build KD tree with seam candidates: end"; - BOOST_LOG_TRIVIAL(debug) - << "PM: build AABB trees for raycasting enforcers/blockers: end"; - - std::vector hit_points = raycast_visibility(ray_count_per_object, raycasting_tree, triangle_set); - HitInfoCoordinateFunctor hit_points_functor { &hit_points }; - KDTreeIndirect<3, coordf_t, HitInfoCoordinateFunctor> hit_points_tree { hit_points_functor, hit_points.size() }; - - BOOST_LOG_TRIVIAL(debug) - << "PM: gather and build KD trees with seam candidates: start"; - - m_perimeter_points_per_object.emplace(po, po->layer_count()); - m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); - - tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; - - const auto layer = po->get_layer(layer_idx); - auto unscaled_z = layer->slice_z; - for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { - auto polygons = ex_entity->polygons_covered_by_width(); - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - enforcer_distance_check, blocker_distance_check); - } - } - } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( - functor, layer_candidates.size())); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather visibility data into perimeter points : start"; + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + perimeter_point.m_visibility = global_model_info.calculate_point_visibility( + perimeter_point.m_position, considered_hits_distance); } - }); + } + }); - BOOST_LOG_TRIVIAL(debug) - << "PM: gather and build KD tree with seam candidates: end"; + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather visibility data into perimeter points : end"; - BOOST_LOG_TRIVIAL(debug) - << "PM: gather visibility data into perimeter points : start"; + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: compute overhangs : start"; + + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + if (layer_idx > 0) { + size_t closest_supporter = find_closest_point( + *m_perimeter_points_trees_per_object[po][layer_idx - 1], + perimeter_point.m_position); + const SeamCandidate &supporter_point = + m_perimeter_points_per_object[po][layer_idx - 1][closest_supporter]; + + auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][layer_idx-1], closest_supporter); + const SeamCandidate &prev_point = + m_perimeter_points_per_object[po][layer_idx - 1][prev_next.first]; + const SeamCandidate &next_point = + m_perimeter_points_per_object[po][layer_idx - 1][prev_next.second]; + + perimeter_point.m_overhang = calculate_overhang(perimeter_point, prev_point, + supporter_point, next_point); - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - auto nearby_points = find_nearby_points(hit_points_tree, perimeter_point.m_position, - considered_hits_distance); - double visibility = 0; - for (const auto &hit_point_index : nearby_points) { - double distance = - (perimeter_point.m_position - hit_points[hit_point_index].m_position).norm(); - visibility += considered_hits_distance - distance; // The further away from the perimeter point, - // the less representative ray hit is - } - perimeter_point.m_visibility = visibility; } } - }); + } + }); - BOOST_LOG_TRIVIAL(debug) - << "PM: gather visibility data into perimeter points : end"; + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: compute overhangs : end"; + + for (size_t iteration = 0; iteration < seam_align_iterations; ++iteration) { + if (iteration > 0) { //skip this in first iteration, no seam has been picked yet + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: distribute seam positions to other layers : start"; + + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + size_t current = 0; + while (current < layer_perimeter_points.size()) { + auto seam_position = + layer_perimeter_points[layer_perimeter_points[current].m_seam_index].m_position; + + size_t other_layer_idx_start = std::max( + (int) layer_idx - (int) seam_align_layer_dist, 0); + size_t other_layer_idx_end = std::min(layer_idx + seam_align_layer_dist, + m_perimeter_points_per_object[po].size() - 1); + + for (size_t other_layer_idx = other_layer_idx_start; + other_layer_idx <= other_layer_idx_end; ++other_layer_idx) { + + size_t closest_point_idx = find_closest_point( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + seam_position); + + m_perimeter_points_per_object[po][other_layer_idx][closest_point_idx].m_nearby_seam_points->fetch_add( + 1, std::memory_order_relaxed); -#ifdef DEBUG_FILES - Slic3r::CNumericLocalesSetter locales_setter; - FILE *fp = boost::nowide::fopen("perimeters.obj", "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << "perimeters.obj" << " for writing"; } - for (size_t i = 0; i < perimeter_points.size(); ++i) - fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].m_position[0], perimeter_points[i].m_position[1], - perimeter_points[i].m_position[2], perimeter_points[i].m_visibility); - fclose(fp); -#endif + current += layer_perimeter_points[current].m_polygon_index_reverse + 1; + } + } + }); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: distribute seam positions to other layers : end"; + } BOOST_LOG_TRIVIAL(debug) - << "PM: find seam for each perimeter polygon and store its position in each member of the polygon : start"; + << "SeamPlacer: find seam for each perimeter polygon and store its position in each member of the polygon : start"; tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { @@ -447,29 +599,37 @@ void SeamPlacer::init(const Print &print) { size_t current = 0; while (current < layer_perimeter_points.size()) { pick_seam_point(layer_perimeter_points, current, - current + layer_perimeter_points[current].m_polygon_index_reverse); + current + layer_perimeter_points[current].m_polygon_index_reverse, + DefaultSeamComparator{}); current += layer_perimeter_points[current].m_polygon_index_reverse + 1; } } }); BOOST_LOG_TRIVIAL(debug) - << "PM: find seam for each perimeter polygon and store its position in each member of the polygon : end"; + << "SeamPlacer: find seam for each perimeter polygon and store its position in each member of the polygon : end"; } } +} void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, int layer_index, - bool external_first) { - assert(m_perimeter_points_trees_per_object.find(po) != nullptr); - assert(m_perimeter_points_per_object.find(po) != nullptr); + bool external_first) { +assert(m_perimeter_points_trees_per_object.find(po) != nullptr); +assert(m_perimeter_points_per_object.find(po) != nullptr); - assert(layer_index >= 0); - const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; - const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; +assert(layer_index >= 0); +const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; +const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; - const Point &fp = loop.first_point(); +const Point &fp = loop.first_point(); +//This is backup check, so that slicer does not crash if something weird is going on +if (perimeter_points.empty()) { + BOOST_LOG_TRIVIAL(error) + << "SeamPlacer: Trying to place seam for index which does not contain any outer or overhang perimeter points, maybe new perimeter type option?"; + loop.split_at(fp, true); +} else { auto unscaled_p = unscale(fp); auto closest_perimeter_point_index = find_closest_point(perimeter_points_tree, Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); @@ -483,5 +643,37 @@ void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t // Insert it. loop.split_at(seam_point, true); } +} + +#ifdef DEBUG_FILES + Slic3r::CNumericLocalesSetter locales_setter; + FILE *fp = boost::nowide::fopen("perimeters.obj", "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Couldn't open " << "perimeters.obj" << " for writing"; + } + + for (size_t i = 0; i < perimeter_points.size(); ++i) + fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].m_position[0], perimeter_points[i].m_position[1], + perimeter_points[i].m_position[2], perimeter_points[i].m_visibility); + fclose(fp); +#endif + +//TODO disable, only debug code +#ifdef DEBUG_FILES + its_write_obj(triangles, "triangles.obj"); + + Slic3r::CNumericLocalesSetter locales_setter; + FILE *fp = boost::nowide::fopen("hits.obj", "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Couldn't open " << "hits.obj" << " for writing"; + } + + for (size_t i = 0; i < hit_points.size(); ++i) + fprintf(fp, "v %f %f %f \n", hit_points[i].m_position[0], hit_points[i].m_position[1], + hit_points[i].m_position[2]); + fclose(fp); + #endif } // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index f7cac19ff..052823c9d 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Polygon.hpp" @@ -25,22 +26,26 @@ class Grid; namespace SeamPlacerImpl { -enum EnforcedBlockedSeamPoint{ - BLOCKED = 0, - NONE = 1, - ENFORCED = 2, +enum EnforcedBlockedSeamPoint { + BLOCKED = 0, + NONE = 1, + ENFORCED = 2, }; struct SeamCandidate { SeamCandidate(const Vec3d &pos, size_t polygon_index_reverse, float ccw_angle, EnforcedBlockedSeamPoint type) : - m_position(pos), m_visibility(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index(0), m_ccw_angle( + m_position(pos), m_visibility(0.0), m_overhang(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index( + 0), m_ccw_angle( ccw_angle), m_type(type) { + m_nearby_seam_points = std::make_unique>(0); } Vec3d m_position; float m_visibility; + float m_overhang; size_t m_polygon_index_reverse; size_t m_seam_index; float m_ccw_angle; + std::unique_ptr> m_nearby_seam_points; EnforcedBlockedSeamPoint m_type; }; @@ -74,11 +79,13 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, coordf_t, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - const size_t ray_count_per_object = 100000; - const double considered_hits_distance = 2.0; + static constexpr size_t ray_count_per_object = 200000; + static constexpr double considered_hits_distance = 3.0; static constexpr float cosine_hemisphere_sampling_power = 1.5; static constexpr float polygon_angles_arm_distance = 0.6; - static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.02; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.04; + static constexpr size_t seam_align_iterations = 3; + static constexpr size_t seam_align_layer_dist = 50; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; std::unordered_map>> m_perimeter_points_trees_per_object; From 1a25058456b6b997a5916aed8db75833bfad0154 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 11 Feb 2022 17:41:58 +0100 Subject: [PATCH 08/71] workable version, some parameter tweaking probably still needed --- src/libslic3r/GCode/SeamPlacerNG.cpp | 212 +++++++++++++++++++-------- src/libslic3r/GCode/SeamPlacerNG.hpp | 9 +- 2 files changed, 152 insertions(+), 69 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 23ce7f81b..60ba5ad80 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -254,6 +254,38 @@ struct GlobalModelInfo { } ; +Polygons extract_perimeter_polygons(const Layer *layer) { + Polygons polygons; + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters + for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { + if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { + Points p; + perimeter->collect_points(p); + polygons.push_back(Polygon(p)); + } + } + if (polygons.empty()) { + Points p; + ex_entity->collect_points(p); + polygons.push_back(Polygon(p)); + } + } else { + Points p; + ex_entity->collect_points(p); + polygons.push_back(Polygon(p)); + } + } + } + + if (polygons.empty()) { + polygons.push_back(Polygon { Point { 0, 0 } }); + } + + return polygons; +} + void process_perimeter_polygon(const Polygon &orig_polygon, coordf_t z_coord, std::vector &result_vec, const GlobalModelInfo &global_model_info) { Polygon polygon = orig_polygon; @@ -416,7 +448,7 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { } struct DefaultSeamComparator { - //is a better? + //is A better? bool operator()(const SeamCandidate &a, const SeamCandidate &b) const { if (a.m_type > b.m_type) { return true; @@ -425,24 +457,41 @@ struct DefaultSeamComparator { return false; } -// if (a.m_overhang > 0.2 && b.m_overhang < a.m_overhang) { -// return false; -// } -// -// if (b.m_ccw_angle < -float(0.3 * PI) && a.m_ccw_angle > -float(0.3 * PI)){ -// return false; -// } - - if (*b.m_nearby_seam_points > *a.m_nearby_seam_points) { + if (a.m_overhang > 0.5 && b.m_overhang < a.m_overhang) { return false; } - if (b.m_visibility < 1.2*a.m_visibility) { - return false; + if (a.m_ccw_angle < -float(0.4 * PI) && b.m_ccw_angle > -float(0.4 * PI)) { + return true; } + std::vector hysteresis_values { 3.0, 2.0, 1.6, 1.2, 1.0 }; - return true; + for (float hysteresis : hysteresis_values) { + + if (*a.m_nearby_seam_points > *b.m_nearby_seam_points * hysteresis) { + return true; + } + + if (*b.m_nearby_seam_points > *a.m_nearby_seam_points * hysteresis) { + return false; + } + + if (a.m_visibility * hysteresis < b.m_visibility) { + return true; + } + + if (b.m_visibility * hysteresis < a.m_visibility) { + return false; + } + + } + + if (abs(a.m_ccw_angle) > abs(b.m_ccw_angle)) { + return true; + } + + return false; } } ; @@ -469,33 +518,19 @@ for (const PrintObject *po : print.objects()) { [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; - const auto layer = po->get_layer(layer_idx); + const Layer *layer = po->get_layer(layer_idx); auto unscaled_z = layer->slice_z; - for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { - Polygons polygons; - if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters - for (const ExtrusionEntity *perimeter : - static_cast(ex_entity)->entities) { - if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { - perimeter->polygons_covered_by_width(polygons, 0); + Polygons polygons = extract_perimeter_polygons(layer); + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + global_model_info); } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( + functor, layer_candidates.size())); } - } else { - polygons = ex_entity->polygons_covered_by_width(); } - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - global_model_info); - } - } - } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( - functor, layer_candidates.size())); - -} -} ); + ); BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather and build KD tree with seam candidates: end"; @@ -548,6 +583,7 @@ for (const PrintObject *po : print.objects()) { << "SeamPlacer: compute overhangs : end"; for (size_t iteration = 0; iteration < seam_align_iterations; ++iteration) { + if (iteration > 0) { //skip this in first iteration, no seam has been picked yet BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: distribute seam positions to other layers : start"; @@ -559,25 +595,59 @@ for (const PrintObject *po : print.objects()) { m_perimeter_points_per_object[po][layer_idx]; size_t current = 0; while (current < layer_perimeter_points.size()) { - auto seam_position = + Vec3d seam_position = layer_perimeter_points[layer_perimeter_points[current].m_seam_index].m_position; - size_t other_layer_idx_start = std::max( + int other_layer_idx_bottom = std::max( (int) layer_idx - (int) seam_align_layer_dist, 0); - size_t other_layer_idx_end = std::min(layer_idx + seam_align_layer_dist, + int other_layer_idx_top = std::min(layer_idx + seam_align_layer_dist, m_perimeter_points_per_object[po].size() - 1); - for (size_t other_layer_idx = other_layer_idx_start; - other_layer_idx <= other_layer_idx_end; ++other_layer_idx) { - - size_t closest_point_idx = find_closest_point( + Vec3d seam_projected_position = seam_position; + for (int other_layer_idx = layer_idx + 1; + other_layer_idx <= other_layer_idx_top; ++other_layer_idx) { + auto layer_z = po->get_layer(other_layer_idx)->slice_z; + seam_projected_position = Vec3d { seam_projected_position.x(), + seam_projected_position.y(), + layer_z }; + size_t closest_point_index = find_closest_point( *m_perimeter_points_trees_per_object[po][other_layer_idx], - seam_position); + seam_projected_position); + SeamCandidate &closest_point = + m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; + double distance = (seam_projected_position - closest_point.m_position).norm(); - m_perimeter_points_per_object[po][other_layer_idx][closest_point_idx].m_nearby_seam_points->fetch_add( - 1, std::memory_order_relaxed); + if (distance < seam_align_tolerable_dist) { + closest_point.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); + seam_projected_position = closest_point.m_position; + } else { + break; + } + } - } + seam_projected_position = seam_position; + if (layer_idx > 0) { + for (int other_layer_idx = layer_idx - 1; + other_layer_idx >= other_layer_idx_bottom; --other_layer_idx) { + auto layer_z = po->get_layer(other_layer_idx)->slice_z; + seam_projected_position = Vec3d { seam_projected_position.x(), + seam_projected_position.y(), + layer_z }; + size_t closest_point_index = find_closest_point( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + seam_projected_position); + SeamCandidate &closest_point = + m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; + double distance = (seam_projected_position - closest_point.m_position).norm(); + + if (distance < seam_align_tolerable_dist) { + closest_point.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); + seam_projected_position = closest_point.m_position; + } else { + break; + } + } + } current += layer_perimeter_points[current].m_polygon_index_reverse + 1; } @@ -586,6 +656,25 @@ for (const PrintObject *po : print.objects()) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: distribute seam positions to other layers : end"; + + Slic3r::CNumericLocalesSetter locales_setter; + FILE *fp = boost::nowide::fopen("seams.obj", "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Couldn't open " << "seams.obj" << " for writing"; + } + + for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { + const auto &points = m_perimeter_points_per_object[po][layer_idx]; + + for (size_t i = 0; i < points.size(); ++i) + fprintf(fp, "v %f %f %f %zu\n", points[i].m_position[0], points[i].m_position[1], + points[i].m_position[2], + points[i].m_nearby_seam_points.get()->load(std::memory_order_relaxed)); + + } + fclose(fp); + } BOOST_LOG_TRIVIAL(debug) @@ -600,7 +689,7 @@ for (const PrintObject *po : print.objects()) { while (current < layer_perimeter_points.size()) { pick_seam_point(layer_perimeter_points, current, current + layer_perimeter_points[current].m_polygon_index_reverse, - DefaultSeamComparator{}); + DefaultSeamComparator { }); current += layer_perimeter_points[current].m_polygon_index_reverse + 1; } } @@ -624,25 +713,18 @@ const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; const Point &fp = loop.first_point(); -//This is backup check, so that slicer does not crash if something weird is going on -if (perimeter_points.empty()) { - BOOST_LOG_TRIVIAL(error) - << "SeamPlacer: Trying to place seam for index which does not contain any outer or overhang perimeter points, maybe new perimeter type option?"; - loop.split_at(fp, true); -} else { - auto unscaled_p = unscale(fp); - auto closest_perimeter_point_index = find_closest_point(perimeter_points_tree, - Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); - size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].m_seam_index; - Vec3d seam_position = perimeter_points[perimeter_seam_index].m_position; +auto unscaled_p = unscale(fp); +auto closest_perimeter_point_index = find_closest_point(perimeter_points_tree, + Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); +size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].m_seam_index; +Vec3d seam_position = perimeter_points[perimeter_seam_index].m_position; - Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); +Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); - if (!loop.split_at_vertex(seam_point)) - // The point is not in the original loop. - // Insert it. - loop.split_at(seam_point, true); -} +if (!loop.split_at_vertex(seam_point)) +// The point is not in the original loop. +// Insert it. + loop.split_at(seam_point, true); } #ifdef DEBUG_FILES diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 052823c9d..2a561ff3d 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -80,12 +80,13 @@ public: using SeamCandidatesTree = KDTreeIndirect<3, coordf_t, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; static constexpr size_t ray_count_per_object = 200000; - static constexpr double considered_hits_distance = 3.0; + static constexpr double considered_hits_distance = 4.0; static constexpr float cosine_hemisphere_sampling_power = 1.5; static constexpr float polygon_angles_arm_distance = 0.6; - static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.04; - static constexpr size_t seam_align_iterations = 3; - static constexpr size_t seam_align_layer_dist = 50; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2; + static constexpr size_t seam_align_iterations = 10; + static constexpr size_t seam_align_layer_dist = 30; + static constexpr float seam_align_tolerable_dist = 1; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; std::unordered_map>> m_perimeter_points_trees_per_object; From 36a4906536eef95da5671fbabe38b2d953e36547 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 14 Feb 2022 16:46:18 +0100 Subject: [PATCH 09/71] refactored init method - split into several parts, added blur filter to seam placement distribution --- src/libslic3r/GCode/SeamPlacerNG.cpp | 451 ++++++++++++++------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 24 +- 2 files changed, 251 insertions(+), 224 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 60ba5ad80..f87dbae8e 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -15,13 +15,26 @@ #include "libslic3r/SVG.hpp" #include "libslic3r/Layer.hpp" -// TODO remove #include namespace Slic3r { namespace SeamPlacerImpl { +void atomic_fetch_add_float(std::atomic &atomic, float increment) + { + float old_val; + float new_val; + + do + { + old_val = atomic.load(std::memory_order_relaxed); + new_val = old_val + increment; + } while (!atomic.compare_exchange_weak(old_val, new_val, + std::memory_order_release, + std::memory_order_relaxed)); +} + /// Coordinate frame class Frame { public: @@ -82,13 +95,18 @@ Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) { return Vec3d(cos(term1) * term3, sin(term1) * term3, term2); } -std::vector raycast_visibility(size_t ray_count, - const AABBTreeIndirect::Tree<3, float> &raycasting_tree, +std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, const indexed_triangle_set &triangles) { + auto bbox = raycasting_tree.node(0).bbox; Vec3d vision_sphere_center = bbox.center().cast(); - float vision_sphere_raidus = (bbox.sizes().maxCoeff() * 0.55); // 0.5 (half) covers whole object, - // 0.05 added to avoid corner cases + Vec3d side_sizes = bbox.sizes().cast(); + float vision_sphere_raidus = (sqrt(side_sizes.dot(side_sizes)) * 0.55); // 0.5 (half) covers whole object, + // 0.05 added to avoid corner cases + double approx_area = 2 * side_sizes.x() * side_sizes.y() + 2 * side_sizes.x() * side_sizes.z() + + 2 * side_sizes.y() * side_sizes.z(); + auto considered_hits_area = PI * SeamPlacer::considered_hits_distance * SeamPlacer::considered_hits_distance; + size_t ray_count = SeamPlacer::expected_hits_per_area * (approx_area / considered_hits_area); // Prepare random samples per ray // std::random_device rnd_device; @@ -153,6 +171,20 @@ std::vector raycast_visibility(size_t ray_count, BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: raycast visibility for " << ray_count << " rays: end"; + its_write_obj(triangles, "triangles.obj"); + + Slic3r::CNumericLocalesSetter locales_setter; + FILE *fp = boost::nowide::fopen("hits.obj", "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Couldn't open " << "hits.obj" << " for writing"; + } + + for (size_t i = 0; i < hit_points.size(); ++i) + fprintf(fp, "v %f %f %f \n", hit_points[i].m_position[0], hit_points[i].m_position[1], + hit_points[i].m_position[2]); + fclose(fp); + return hit_points; } @@ -389,7 +421,6 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ } return Vec2d((p - b).norm(), std::min(abs(dist_ab), abs(dist_bc))).norm(); - } template @@ -419,8 +450,7 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, triangle_set.indices); - result.geometry_raycast_hits = raycast_visibility(SeamPlacer::ray_count_per_object, raycasting_tree, - triangle_set); + result.geometry_raycast_hits = raycast_visibility(raycasting_tree, triangle_set); result.raycast_hits_tree.build(result.geometry_raycast_hits.size()); BOOST_LOG_TRIVIAL(debug) @@ -450,6 +480,7 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { struct DefaultSeamComparator { //is A better? bool operator()(const SeamCandidate &a, const SeamCandidate &b) const { + // Blockers/Enforcers discrimination, top priority if (a.m_type > b.m_type) { return true; } @@ -457,136 +488,217 @@ struct DefaultSeamComparator { return false; } + //avoid overhangs if (a.m_overhang > 0.5 && b.m_overhang < a.m_overhang) { return false; } - if (a.m_ccw_angle < -float(0.4 * PI) && b.m_ccw_angle > -float(0.4 * PI)) { + auto angle_score = [](float ccw_angle) { + if (ccw_angle > 0) { + float normalized = (ccw_angle / float(PI)); + return normalized * normalized * normalized * 0.8; + } else { + float normalized = (-ccw_angle / float(PI)); + return normalized * normalized * normalized * 1.0; + } + }; + + auto vis_score = [](float visibility) { + return 1.0 - visibility / SeamPlacer::expected_hits_per_area; + }; + + auto align_score = [](float nearby_seams) { + return nearby_seams / (0.25 * (sqrt(2) * SeamPlacer::seam_align_layer_dist)); + }; + + float score_a = angle_score(a.m_ccw_angle) + vis_score(a.m_visibility) + align_score(*a.m_nearby_seam_points); + float score_b = angle_score(b.m_ccw_angle) + vis_score(b.m_visibility) + align_score(*b.m_nearby_seam_points); + + if (score_a > score_b) return true; - } - - std::vector hysteresis_values { 3.0, 2.0, 1.6, 1.2, 1.0 }; - - for (float hysteresis : hysteresis_values) { - - if (*a.m_nearby_seam_points > *b.m_nearby_seam_points * hysteresis) { - return true; - } - - if (*b.m_nearby_seam_points > *a.m_nearby_seam_points * hysteresis) { - return false; - } - - if (a.m_visibility * hysteresis < b.m_visibility) { - return true; - } - - if (b.m_visibility * hysteresis < a.m_visibility) { - return false; - } - - } - - if (abs(a.m_ccw_angle) > abs(b.m_ccw_angle)) { - return true; - } - - return false; + else + return false; } } ; } // namespace SeamPlacerImpl -void SeamPlacer::init(const Print &print) { +void SeamPlacer::gather_seam_candidates(const PrintObject *po, + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { using namespace SeamPlacerImpl; -m_perimeter_points_trees_per_object.clear(); -m_perimeter_points_per_object.clear(); -for (const PrintObject *po : print.objects()) { +m_perimeter_points_per_object.emplace(po, po->layer_count()); +m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); - GlobalModelInfo global_model_info { }; - gather_global_model_info(global_model_info, po); +tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + const Layer *layer = po->get_layer(layer_idx); + auto unscaled_z = layer->slice_z; + Polygons polygons = extract_perimeter_polygons(layer); + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + global_model_info); + } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( + functor, layer_candidates.size())); + } + } +); +} - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather and build KD trees with seam candidates: start"; +void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { +using namespace SeamPlacerImpl; - m_perimeter_points_per_object.emplace(po, po->layer_count()); - m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); - - tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; - const Layer *layer = po->get_layer(layer_idx); - auto unscaled_z = layer->slice_z; - Polygons polygons = extract_perimeter_polygons(layer); - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - global_model_info); - } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( - functor, layer_candidates.size())); +tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + perimeter_point.m_visibility = global_model_info.calculate_point_visibility( + perimeter_point.m_position, considered_hits_distance); } } - ); + }); +} - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather and build KD tree with seam candidates: end"; +void SeamPlacer::calculate_overhangs(const PrintObject *po) { +using namespace SeamPlacerImpl; - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather visibility data into perimeter points : start"; +tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + if (layer_idx > 0) { + size_t closest_supporter = find_closest_point( + *m_perimeter_points_trees_per_object[po][layer_idx - 1], + perimeter_point.m_position); + const SeamCandidate &supporter_point = + m_perimeter_points_per_object[po][layer_idx - 1][closest_supporter]; + + auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][layer_idx-1], closest_supporter); + const SeamCandidate &prev_point = + m_perimeter_points_per_object[po][layer_idx - 1][prev_next.first]; + const SeamCandidate &next_point = + m_perimeter_points_per_object[po][layer_idx - 1][prev_next.second]; + + perimeter_point.m_overhang = calculate_overhang(perimeter_point, prev_point, + supporter_point, next_point); - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.m_visibility = global_model_info.calculate_point_visibility( - perimeter_point.m_position, considered_hits_distance); } } - }); + } + }); + } - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather visibility data into perimeter points : end"; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: compute overhangs : start"; +void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) { + using namespace SeamPlacerImpl; tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - if (layer_idx > 0) { - size_t closest_supporter = find_closest_point( - *m_perimeter_points_trees_per_object[po][layer_idx - 1], - perimeter_point.m_position); - const SeamCandidate &supporter_point = - m_perimeter_points_per_object[po][layer_idx - 1][closest_supporter]; + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + size_t current = 0; + while (current < layer_perimeter_points.size()) { + Vec3d seam_position = + layer_perimeter_points[layer_perimeter_points[current].m_seam_index].m_position; - auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][layer_idx-1], closest_supporter); - const SeamCandidate &prev_point = - m_perimeter_points_per_object[po][layer_idx - 1][prev_next.first]; - const SeamCandidate &next_point = - m_perimeter_points_per_object[po][layer_idx - 1][prev_next.second]; + int other_layer_idx_bottom = std::max( + (int) layer_idx - (int) seam_align_layer_dist, 0); + int other_layer_idx_top = std::min(layer_idx + seam_align_layer_dist, + m_perimeter_points_per_object[po].size() - 1); - perimeter_point.m_overhang = calculate_overhang(perimeter_point, prev_point, - supporter_point, next_point); + for (int other_layer_idx = layer_idx + 1; + other_layer_idx <= other_layer_idx_top; ++other_layer_idx) { + std::vector nearby_point_indexes = find_nearby_points( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + seam_position, + seam_align_tolerable_dist * (other_layer_idx - layer_idx)); + + if (nearby_point_indexes.empty()) { + break; + } + + for (size_t nearby_point_index : nearby_point_indexes) { + SeamCandidate &point_ref = + m_perimeter_points_per_object[po][other_layer_idx][nearby_point_index]; + float distance = (seam_position - point_ref.m_position).norm(); + atomic_fetch_add_float(*point_ref.m_nearby_seam_points, 1.0 / distance); + + } } + + if (layer_idx > 0) { + for (int other_layer_idx = layer_idx - 1; + other_layer_idx >= other_layer_idx_bottom; --other_layer_idx) { + + std::vector nearby_point_indexes = find_nearby_points( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + seam_position, + seam_align_tolerable_dist * (layer_idx - other_layer_idx)); + + if (nearby_point_indexes.empty()) { + break; + } + + for (size_t nearby_point_index : nearby_point_indexes) { + SeamCandidate &point_ref = + m_perimeter_points_per_object[po][other_layer_idx][nearby_point_index]; + float distance = (seam_position - point_ref.m_position).norm(); + atomic_fetch_add_float(*point_ref.m_nearby_seam_points, 1.0 / distance); + + } + + } + } + + current += layer_perimeter_points[current].m_polygon_index_reverse + 1; } } }); +} - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: compute overhangs : end"; +void SeamPlacer::init(const Print &print) { + using namespace SeamPlacerImpl; + m_perimeter_points_trees_per_object.clear(); + m_perimeter_points_per_object.clear(); - for (size_t iteration = 0; iteration < seam_align_iterations; ++iteration) { + for (const PrintObject *po : print.objects()) { - if (iteration > 0) { //skip this in first iteration, no seam has been picked yet - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: distribute seam positions to other layers : start"; + GlobalModelInfo global_model_info { }; + gather_global_model_info(global_model_info, po); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather_seam_candidates: start"; + gather_seam_candidates(po, global_model_info); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather_seam_candidates: end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_candidates_visibility : start"; + calculate_candidates_visibility(po, global_model_info); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_candidates_visibility : end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_overhangs : start"; + calculate_overhangs(po); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_overhangs : end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: distribute_seam_positions_for_alignment, pick_seams : start"; + + for (size_t iteration = 0; iteration < seam_align_iterations; ++iteration) { + + if (iteration > 0) { //skip this in first iteration, no seam has been picked yet; nothing to distribute + distribute_seam_positions_for_alignment(po); + } tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { @@ -595,139 +707,47 @@ for (const PrintObject *po : print.objects()) { m_perimeter_points_per_object[po][layer_idx]; size_t current = 0; while (current < layer_perimeter_points.size()) { - Vec3d seam_position = - layer_perimeter_points[layer_perimeter_points[current].m_seam_index].m_position; - - int other_layer_idx_bottom = std::max( - (int) layer_idx - (int) seam_align_layer_dist, 0); - int other_layer_idx_top = std::min(layer_idx + seam_align_layer_dist, - m_perimeter_points_per_object[po].size() - 1); - - Vec3d seam_projected_position = seam_position; - for (int other_layer_idx = layer_idx + 1; - other_layer_idx <= other_layer_idx_top; ++other_layer_idx) { - auto layer_z = po->get_layer(other_layer_idx)->slice_z; - seam_projected_position = Vec3d { seam_projected_position.x(), - seam_projected_position.y(), - layer_z }; - size_t closest_point_index = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - seam_projected_position); - SeamCandidate &closest_point = - m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; - double distance = (seam_projected_position - closest_point.m_position).norm(); - - if (distance < seam_align_tolerable_dist) { - closest_point.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); - seam_projected_position = closest_point.m_position; - } else { - break; - } - } - - seam_projected_position = seam_position; - if (layer_idx > 0) { - for (int other_layer_idx = layer_idx - 1; - other_layer_idx >= other_layer_idx_bottom; --other_layer_idx) { - auto layer_z = po->get_layer(other_layer_idx)->slice_z; - seam_projected_position = Vec3d { seam_projected_position.x(), - seam_projected_position.y(), - layer_z }; - size_t closest_point_index = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - seam_projected_position); - SeamCandidate &closest_point = - m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; - double distance = (seam_projected_position - closest_point.m_position).norm(); - - if (distance < seam_align_tolerable_dist) { - closest_point.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); - seam_projected_position = closest_point.m_position; - } else { - break; - } - } - } - + pick_seam_point(layer_perimeter_points, current, + current + layer_perimeter_points[current].m_polygon_index_reverse, + DefaultSeamComparator { }); current += layer_perimeter_points[current].m_polygon_index_reverse + 1; } } }); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: distribute seam positions to other layers : end"; - - Slic3r::CNumericLocalesSetter locales_setter; - FILE *fp = boost::nowide::fopen("seams.obj", "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << "seams.obj" << " for writing"; - } - - for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { - const auto &points = m_perimeter_points_per_object[po][layer_idx]; - - for (size_t i = 0; i < points.size(); ++i) - fprintf(fp, "v %f %f %f %zu\n", points[i].m_position[0], points[i].m_position[1], - points[i].m_position[2], - points[i].m_nearby_seam_points.get()->load(std::memory_order_relaxed)); - - } - fclose(fp); - } - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: find seam for each perimeter polygon and store its position in each member of the polygon : start"; - - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - size_t current = 0; - while (current < layer_perimeter_points.size()) { - pick_seam_point(layer_perimeter_points, current, - current + layer_perimeter_points[current].m_polygon_index_reverse, - DefaultSeamComparator { }); - current += layer_perimeter_points[current].m_polygon_index_reverse + 1; - } - } - }); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: find seam for each perimeter polygon and store its position in each member of the polygon : end"; - + << "SeamPlacer: distribute_seam_positions_for_alignment, pick_seams : end"; } } -} void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, int layer_index, - bool external_first) { -assert(m_perimeter_points_trees_per_object.find(po) != nullptr); -assert(m_perimeter_points_per_object.find(po) != nullptr); + bool external_first) { + assert(m_perimeter_points_trees_per_object.find(po) != nullptr); + assert(m_perimeter_points_per_object.find(po) != nullptr); -assert(layer_index >= 0); -const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; -const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; + assert(layer_index >= 0); + const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; + const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; -const Point &fp = loop.first_point(); + const Point &fp = loop.first_point(); -auto unscaled_p = unscale(fp); -auto closest_perimeter_point_index = find_closest_point(perimeter_points_tree, - Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); -size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].m_seam_index; -Vec3d seam_position = perimeter_points[perimeter_seam_index].m_position; + auto unscaled_p = unscale(fp); + auto closest_perimeter_point_index = find_closest_point(perimeter_points_tree, + Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); + size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].m_seam_index; + Vec3d seam_position = perimeter_points[perimeter_seam_index].m_position; -Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); + Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); -if (!loop.split_at_vertex(seam_point)) + if (!loop.split_at_vertex(seam_point)) // The point is not in the original loop. // Insert it. - loop.split_at(seam_point, true); + loop.split_at(seam_point, true); } -#ifdef DEBUG_FILES +// Disabled debug code, can be used to export debug data into obj files (e.g. point cloud of visibility hits +#if 0 + #include Slic3r::CNumericLocalesSetter locales_setter; FILE *fp = boost::nowide::fopen("perimeters.obj", "w"); if (fp == nullptr) { @@ -741,8 +761,7 @@ if (!loop.split_at_vertex(seam_point)) fclose(fp); #endif -//TODO disable, only debug code -#ifdef DEBUG_FILES +#if 0 its_write_obj(triangles, "triangles.obj"); Slic3r::CNumericLocalesSetter locales_setter; diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 2a561ff3d..456b5d321 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -26,6 +26,8 @@ class Grid; namespace SeamPlacerImpl { +struct GlobalModelInfo; + enum EnforcedBlockedSeamPoint { BLOCKED = 0, NONE = 1, @@ -37,7 +39,7 @@ struct SeamCandidate { m_position(pos), m_visibility(0.0), m_overhang(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index( 0), m_ccw_angle( ccw_angle), m_type(type) { - m_nearby_seam_points = std::make_unique>(0); + m_nearby_seam_points = std::make_unique>(0.0); } Vec3d m_position; float m_visibility; @@ -45,7 +47,7 @@ struct SeamCandidate { size_t m_polygon_index_reverse; size_t m_seam_index; float m_ccw_angle; - std::unique_ptr> m_nearby_seam_points; + std::unique_ptr> m_nearby_seam_points; EnforcedBlockedSeamPoint m_type; }; @@ -79,13 +81,13 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, coordf_t, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr size_t ray_count_per_object = 200000; - static constexpr double considered_hits_distance = 4.0; + static constexpr double considered_hits_distance = 2.0; + static constexpr double expected_hits_per_area = 40.0; static constexpr float cosine_hemisphere_sampling_power = 1.5; - static constexpr float polygon_angles_arm_distance = 0.6; - static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2; - static constexpr size_t seam_align_iterations = 10; - static constexpr size_t seam_align_layer_dist = 30; + static constexpr float polygon_angles_arm_distance = 1.0; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4; + static constexpr size_t seam_align_iterations = 5; + static constexpr size_t seam_align_layer_dist = 50; static constexpr float seam_align_tolerable_dist = 1; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; @@ -95,6 +97,12 @@ public: void place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, int layer_index, bool external_first); + +private: + void gather_seam_candidates(const PrintObject* po, const SeamPlacerImpl::GlobalModelInfo& global_model_info); + void calculate_candidates_visibility(const PrintObject* po, const SeamPlacerImpl::GlobalModelInfo& global_model_info); + void calculate_overhangs(const PrintObject* po); + void distribute_seam_positions_for_alignment(const PrintObject* po); }; } // namespace Slic3r From 8226061da437c8dc27557e65c28266b49149c34e Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 15 Feb 2022 13:33:15 +0100 Subject: [PATCH 10/71] so far best version --- src/libslic3r/GCode/SeamPlacerNG.cpp | 104 ++++++++++----------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 16 ++--- 2 files changed, 46 insertions(+), 74 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index f87dbae8e..b6cba310d 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -15,26 +15,10 @@ #include "libslic3r/SVG.hpp" #include "libslic3r/Layer.hpp" -#include - namespace Slic3r { namespace SeamPlacerImpl { -void atomic_fetch_add_float(std::atomic &atomic, float increment) - { - float old_val; - float new_val; - - do - { - old_val = atomic.load(std::memory_order_relaxed); - new_val = old_val + increment; - } while (!atomic.compare_exchange_weak(old_val, new_val, - std::memory_order_release, - std::memory_order_relaxed)); -} - /// Coordinate frame class Frame { public: @@ -171,20 +155,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: raycast visibility for " << ray_count << " rays: end"; - its_write_obj(triangles, "triangles.obj"); - - Slic3r::CNumericLocalesSetter locales_setter; - FILE *fp = boost::nowide::fopen("hits.obj", "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << "hits.obj" << " for writing"; - } - - for (size_t i = 0; i < hit_points.size(); ++i) - fprintf(fp, "v %f %f %f \n", hit_points[i].m_position[0], hit_points[i].m_position[1], - hit_points[i].m_position[2]); - fclose(fp); - return hit_points; } @@ -495,24 +465,31 @@ struct DefaultSeamComparator { auto angle_score = [](float ccw_angle) { if (ccw_angle > 0) { - float normalized = (ccw_angle / float(PI)); - return normalized * normalized * normalized * 0.8; + float normalized = (ccw_angle / float(PI)) * 0.9; + return normalized; } else { - float normalized = (-ccw_angle / float(PI)); - return normalized * normalized * normalized * 1.0; + float normalized = (-ccw_angle / float(PI)) * 1.1; + return normalized; } }; + float angle_weight = 2.0; auto vis_score = [](float visibility) { - return 1.0 - visibility / SeamPlacer::expected_hits_per_area; + return (1.0 - visibility / SeamPlacer::expected_hits_per_area); }; + float vis_weight = 1.2; auto align_score = [](float nearby_seams) { - return nearby_seams / (0.25 * (sqrt(2) * SeamPlacer::seam_align_layer_dist)); + return nearby_seams / SeamPlacer::seam_align_layer_dist; }; + float align_weight = 1.0; - float score_a = angle_score(a.m_ccw_angle) + vis_score(a.m_visibility) + align_score(*a.m_nearby_seam_points); - float score_b = angle_score(b.m_ccw_angle) + vis_score(b.m_visibility) + align_score(*b.m_nearby_seam_points); + float score_a = angle_score(a.m_ccw_angle) * angle_weight + + vis_score(a.m_visibility) * vis_weight + + align_score(*a.m_nearby_seam_points) * align_weight; + float score_b = angle_score(b.m_ccw_angle) * angle_weight + + vis_score(b.m_visibility) * vis_weight + + align_score(*b.m_nearby_seam_points) * align_weight; if (score_a > score_b) return true; @@ -612,48 +589,42 @@ void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) int other_layer_idx_top = std::min(layer_idx + seam_align_layer_dist, m_perimeter_points_per_object[po].size() - 1); + Vec3d last_point_position = seam_position; for (int other_layer_idx = layer_idx + 1; other_layer_idx <= other_layer_idx_top; ++other_layer_idx) { - - std::vector nearby_point_indexes = find_nearby_points( + Vec3d projected_position { last_point_position.x(), last_point_position.y(), po->get_layer(other_layer_idx)->slice_z}; + size_t closest_point_index = find_closest_point( *m_perimeter_points_trees_per_object[po][other_layer_idx], - seam_position, - seam_align_tolerable_dist * (other_layer_idx - layer_idx)); + projected_position); - if (nearby_point_indexes.empty()) { + SeamCandidate &point_ref = + m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; + if ((point_ref.m_position - projected_position).norm() + < SeamPlacer::seam_align_tolerable_dist) { + point_ref.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); + last_point_position = point_ref.m_position; + } else { break; } - - for (size_t nearby_point_index : nearby_point_indexes) { - SeamCandidate &point_ref = - m_perimeter_points_per_object[po][other_layer_idx][nearby_point_index]; - float distance = (seam_position - point_ref.m_position).norm(); - atomic_fetch_add_float(*point_ref.m_nearby_seam_points, 1.0 / distance); - - } } - + last_point_position = seam_position; if (layer_idx > 0) { for (int other_layer_idx = layer_idx - 1; other_layer_idx >= other_layer_idx_bottom; --other_layer_idx) { - - std::vector nearby_point_indexes = find_nearby_points( + Vec3d projected_position { last_point_position.x(), last_point_position.y(), po->get_layer(other_layer_idx)->slice_z}; + size_t closest_point_index = find_closest_point( *m_perimeter_points_trees_per_object[po][other_layer_idx], - seam_position, - seam_align_tolerable_dist * (layer_idx - other_layer_idx)); + projected_position); - if (nearby_point_indexes.empty()) { + SeamCandidate &point_ref = + m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; + if ((point_ref.m_position - projected_position).norm() + < SeamPlacer::seam_align_tolerable_dist) { + point_ref.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); + last_point_position = point_ref.m_position; + } else { break; } - - for (size_t nearby_point_index : nearby_point_indexes) { - SeamCandidate &point_ref = - m_perimeter_points_per_object[po][other_layer_idx][nearby_point_index]; - float distance = (seam_position - point_ref.m_position).norm(); - atomic_fetch_add_float(*point_ref.m_nearby_seam_points, 1.0 / distance); - - } - } } @@ -707,6 +678,7 @@ void SeamPlacer::init(const Print &print) { m_perimeter_points_per_object[po][layer_idx]; size_t current = 0; while (current < layer_perimeter_points.size()) { + //NOTE: pick seam point function also resets the m_nearby_seam_points count on all passed points pick_seam_point(layer_perimeter_points, current, current + layer_perimeter_points[current].m_polygon_index_reverse, DefaultSeamComparator { }); diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 456b5d321..057640776 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -39,7 +39,7 @@ struct SeamCandidate { m_position(pos), m_visibility(0.0), m_overhang(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index( 0), m_ccw_angle( ccw_angle), m_type(type) { - m_nearby_seam_points = std::make_unique>(0.0); + m_nearby_seam_points = std::make_unique>(0); } Vec3d m_position; float m_visibility; @@ -47,7 +47,7 @@ struct SeamCandidate { size_t m_polygon_index_reverse; size_t m_seam_index; float m_ccw_angle; - std::unique_ptr> m_nearby_seam_points; + std::unique_ptr> m_nearby_seam_points; EnforcedBlockedSeamPoint m_type; }; @@ -81,14 +81,14 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, coordf_t, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr double considered_hits_distance = 2.0; - static constexpr double expected_hits_per_area = 40.0; + static constexpr double considered_hits_distance = 4.0; + static constexpr double expected_hits_per_area = 250.0; static constexpr float cosine_hemisphere_sampling_power = 1.5; - static constexpr float polygon_angles_arm_distance = 1.0; + static constexpr float polygon_angles_arm_distance = 0.6; static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4; - static constexpr size_t seam_align_iterations = 5; - static constexpr size_t seam_align_layer_dist = 50; - static constexpr float seam_align_tolerable_dist = 1; + static constexpr size_t seam_align_iterations = 4; + static constexpr size_t seam_align_layer_dist = 30; + static constexpr float seam_align_tolerable_dist = 0.3; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; std::unordered_map>> m_perimeter_points_trees_per_object; From 4b3db29d3216dd98952739bcd8c7d710e3b79aab Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 15 Feb 2022 16:45:19 +0100 Subject: [PATCH 11/71] refactoring into floats, fixed problems with float/double mixing, returned to fixed ray count, yields better results --- src/libslic3r/GCode/SeamPlacerNG.cpp | 137 +++++++++++++++------------ src/libslic3r/GCode/SeamPlacerNG.hpp | 26 ++--- 2 files changed, 88 insertions(+), 75 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index b6cba310d..b23572eb0 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -23,74 +23,74 @@ namespace SeamPlacerImpl { class Frame { public: Frame() { - mX = Vec3d(1, 0, 0); - mY = Vec3d(0, 1, 0); - mZ = Vec3d(0, 0, 1); + mX = Vec3f(1, 0, 0); + mY = Vec3f(0, 1, 0); + mZ = Vec3f(0, 0, 1); } - Frame(const Vec3d &x, const Vec3d &y, const Vec3d &z) : + Frame(const Vec3f &x, const Vec3f &y, const Vec3f &z) : mX(x), mY(y), mZ(z) { } - void set_from_z(const Vec3d &z) { + void set_from_z(const Vec3f &z) { mZ = z.normalized(); - Vec3d tmpZ = mZ; - Vec3d tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3d(0, 1, 0) : Vec3d(1, 0, 0); + Vec3f tmpZ = mZ; + Vec3f tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3f(0, 1, 0) : Vec3f(1, 0, 0); mY = (tmpZ.cross(tmpX)).normalized(); mX = mY.cross(tmpZ); } - Vec3d to_world(const Vec3d &a) const { + Vec3f to_world(const Vec3f &a) const { return a.x() * mX + a.y() * mY + a.z() * mZ; } - Vec3d to_local(const Vec3d &a) const { - return Vec3d(mX.dot(a), mY.dot(a), mZ.dot(a)); + Vec3f to_local(const Vec3f &a) const { + return Vec3f(mX.dot(a), mY.dot(a), mZ.dot(a)); } - const Vec3d& binormal() const { + const Vec3f& binormal() const { return mX; } - const Vec3d& tangent() const { + const Vec3f& tangent() const { return mY; } - const Vec3d& normal() const { + const Vec3f& normal() const { return mZ; } private: - Vec3d mX, mY, mZ; + Vec3f mX, mY, mZ; }; -Vec3d sample_sphere_uniform(const Vec2f &samples) { - float term1 = 2.0f * M_PIf32 * samples.x(); +Vec3f sample_sphere_uniform(const Vec2f &samples) { + float term1 = 2.0f * float(PI) * samples.x(); float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); return {cos(term1) * term2, sin(term1) * term2, 1.0f - 2.0f * samples.y()}; } -Vec3d sample_power_cosine_hemisphere(const Vec2f &samples, float power) { +Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { float term1 = 2.f * M_PIf32 * samples.x(); float term2 = pow(samples.y(), 1.f / (power + 1.f)); float term3 = sqrt(1.f - term2 * term2); - return Vec3d(cos(term1) * term3, sin(term1) * term3, term2); + return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); } -std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles) { +std::vector raycast_visibility(size_t ray_count, const AABBTreeIndirect::Tree<3, float> &raycasting_tree, + const indexed_triangle_set &triangles, float& area_to_consider) { auto bbox = raycasting_tree.node(0).bbox; - Vec3d vision_sphere_center = bbox.center().cast(); - Vec3d side_sizes = bbox.sizes().cast(); + Vec3f vision_sphere_center = bbox.center().cast(); + Vec3f side_sizes = bbox.sizes().cast(); float vision_sphere_raidus = (sqrt(side_sizes.dot(side_sizes)) * 0.55); // 0.5 (half) covers whole object, // 0.05 added to avoid corner cases - double approx_area = 2 * side_sizes.x() * side_sizes.y() + 2 * side_sizes.x() * side_sizes.z() + // very rough approximation of object surface area from its bounding box + float approx_area = 2 * side_sizes.x() * side_sizes.y() + 2 * side_sizes.x() * side_sizes.z() + 2 * side_sizes.y() * side_sizes.z(); - auto considered_hits_area = PI * SeamPlacer::considered_hits_distance * SeamPlacer::considered_hits_distance; - size_t ray_count = SeamPlacer::expected_hits_per_area * (approx_area / considered_hits_area); + area_to_consider = SeamPlacer::expected_hits_per_area * approx_area / ray_count; // Prepare random samples per ray // std::random_device rnd_device; @@ -118,28 +118,29 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & std::vector { }, [&](tbb::blocked_range r, std::vector init) { for (size_t index = r.begin(); index < r.end(); ++index) { - Vec3d global_ray_dir = sample_sphere_uniform(global_dir_random_samples[index]); - Vec3d ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); - Vec3d local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], SeamPlacer::cosine_hemisphere_sampling_power); + Vec3f global_ray_dir = sample_sphere_uniform(global_dir_random_samples[index]); + Vec3f ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); + Vec3f local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], SeamPlacer::cosine_hemisphere_sampling_power); Frame f; f.set_from_z(global_ray_dir); - Vec3d final_ray_dir = (f.to_world(local_dir)); + Vec3f final_ray_dir = (f.to_world(local_dir)); igl::Hit hitpoint; // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and - // direction for some reason - auto hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, - triangles.indices, raycasting_tree, ray_origin, final_ray_dir, hitpoint); + // direction. + Vec3d ray_origin_d = ray_origin.cast(); + Vec3d final_ray_dir_d = final_ray_dir.cast(); + bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, + triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); if (hit) { auto face = triangles.indices[hitpoint.id]; auto edge1 = triangles.vertices[face[1]] - triangles.vertices[face[0]]; auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; - Vec3d hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v).cast< - double>(); - Vec3d surface_normal = edge1.cross(edge2).cast().normalized(); + Vec3f hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v); + Vec3f surface_normal = edge1.cross(edge2).normalized(); init.push_back(HitInfo { hit_pos, surface_normal }); } @@ -208,7 +209,7 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const Point v2 = p2 - p1; int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); - float angle = float(atan2(double(cross), double(dot))); + float angle = float(atan2(float(cross), float(dot))); angles[idx_curr] = angle; } @@ -217,7 +218,8 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, struct GlobalModelInfo { std::vector geometry_raycast_hits; - KDTreeIndirect<3, coordf_t, HitInfoCoordinateFunctor> raycast_hits_tree; + KDTreeIndirect<3, float, HitInfoCoordinateFunctor> raycast_hits_tree; + float hits_area_to_consider{}; indexed_triangle_set enforcers; indexed_triangle_set blockers; AABBTreeIndirect::Tree<3, float> enforcers_tree; @@ -227,25 +229,25 @@ struct GlobalModelInfo { raycast_hits_tree(HitInfoCoordinateFunctor { &geometry_raycast_hits }) { } - double enforcer_distance_check(const Vec3d &position) const { + float enforcer_distance_check(const Vec3f &position) const { size_t hit_idx_out; - Vec3d closest_vec3d; + Vec3f closest_point; return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(enforcers.vertices, enforcers.indices, - enforcers_tree, position, hit_idx_out, closest_vec3d); + enforcers_tree, position, hit_idx_out, closest_point); } - double blocker_distance_check(const Vec3d &position) const { + float blocker_distance_check(const Vec3f &position) const { size_t hit_idx_out; - Vec3d closest_vec3d; + Vec3f closest_point; return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(blockers.vertices, blockers.indices, - blockers_tree, position, hit_idx_out, closest_vec3d); + blockers_tree, position, hit_idx_out, closest_point); } - double calculate_point_visibility(const Vec3d &position, double max_distance) const { + float calculate_point_visibility(const Vec3f &position, float max_distance) const { auto nearby_points = find_nearby_points(raycast_hits_tree, position, max_distance); - double visibility = 0; + float visibility = 0; for (const auto &hit_point_index : nearby_points) { - double distance = + float distance = (position - geometry_raycast_hits[hit_point_index].m_position).norm(); visibility += max_distance - distance; // The further away from the perimeter point, // the less representative ray hit is @@ -288,7 +290,7 @@ Polygons extract_perimeter_polygons(const Layer *layer) { return polygons; } -void process_perimeter_polygon(const Polygon &orig_polygon, coordf_t z_coord, std::vector &result_vec, +void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, const GlobalModelInfo &global_model_info) { Polygon polygon = orig_polygon; polygon.make_counter_clockwise(); @@ -296,14 +298,14 @@ void process_perimeter_polygon(const Polygon &orig_polygon, coordf_t z_coord, st std::vector angles = calculate_polygon_angles_at_vertices(polygon, lengths, SeamPlacer::polygon_angles_arm_distance); - Vec3d last_enforcer_checked_point { 0, 0, -1 }; - double enforcer_dist_sqr = global_model_info.enforcer_distance_check(last_enforcer_checked_point); - Vec3d last_blocker_checked_point { 0, 0, -1 }; - double blocker_dist_sqr = global_model_info.blocker_distance_check(last_blocker_checked_point); + Vec3f last_enforcer_checked_point { 0, 0, -1 }; + float enforcer_dist_sqr = global_model_info.enforcer_distance_check(last_enforcer_checked_point); + Vec3f last_blocker_checked_point { 0, 0, -1 }; + float blocker_dist_sqr = global_model_info.blocker_distance_check(last_blocker_checked_point); for (size_t index = 0; index < polygon.size(); ++index) { - Vec2d unscaled_p = unscale(polygon[index]); - Vec3d unscaled_position = Vec3d { unscaled_p.x(), unscaled_p.y(), z_coord }; + Vec2f unscaled_p = unscale(polygon[index]).cast(); + Vec3f unscaled_position = Vec3f { unscaled_p.x(), unscaled_p.y(), z_coord }; EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::NONE; float ccw_angle = angles[index]; @@ -368,6 +370,7 @@ std::pair find_previous_and_next_perimeter_point(const std::vect return {prev,next}; } +//NOTE: only rough esitmation of overhang distance float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b, const SeamCandidate &under_c) { auto p = Vec2d { point.m_position.x(), point.m_position.y() }; @@ -420,8 +423,10 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, triangle_set.indices); - result.geometry_raycast_hits = raycast_visibility(raycasting_tree, triangle_set); + float hits_area_to_consider; + result.geometry_raycast_hits = raycast_visibility(SeamPlacer::ray_count, raycasting_tree, triangle_set, hits_area_to_consider); result.raycast_hits_tree.build(result.geometry_raycast_hits.size()); + result.hits_area_to_consider = hits_area_to_consider; BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; @@ -531,6 +536,8 @@ void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info) { using namespace SeamPlacerImpl; +float considered_hits_distance = sqrt(global_model_info.hits_area_to_consider / float(PI)); + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { @@ -581,7 +588,7 @@ void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) m_perimeter_points_per_object[po][layer_idx]; size_t current = 0; while (current < layer_perimeter_points.size()) { - Vec3d seam_position = + Vec3f seam_position = layer_perimeter_points[layer_perimeter_points[current].m_seam_index].m_position; int other_layer_idx_bottom = std::max( @@ -589,10 +596,11 @@ void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) int other_layer_idx_top = std::min(layer_idx + seam_align_layer_dist, m_perimeter_points_per_object[po].size() - 1); - Vec3d last_point_position = seam_position; + //distribute from current layer upwards + Vec3f last_point_position = seam_position; for (int other_layer_idx = layer_idx + 1; other_layer_idx <= other_layer_idx_top; ++other_layer_idx) { - Vec3d projected_position { last_point_position.x(), last_point_position.y(), po->get_layer(other_layer_idx)->slice_z}; + Vec3f projected_position { last_point_position.x(), last_point_position.y(), float(po->get_layer(other_layer_idx)->slice_z)}; size_t closest_point_index = find_closest_point( *m_perimeter_points_trees_per_object[po][other_layer_idx], projected_position); @@ -604,14 +612,16 @@ void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) point_ref.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); last_point_position = point_ref.m_position; } else { + //break when no close point found break; } } + //distribute downwards last_point_position = seam_position; if (layer_idx > 0) { for (int other_layer_idx = layer_idx - 1; other_layer_idx >= other_layer_idx_bottom; --other_layer_idx) { - Vec3d projected_position { last_point_position.x(), last_point_position.y(), po->get_layer(other_layer_idx)->slice_z}; + Vec3f projected_position { last_point_position.x(), last_point_position.y(), float(po->get_layer(other_layer_idx)->slice_z)}; size_t closest_point_index = find_closest_point( *m_perimeter_points_trees_per_object[po][other_layer_idx], projected_position); @@ -671,6 +681,7 @@ void SeamPlacer::init(const Print &print) { distribute_seam_positions_for_alignment(po); } + //pick seam point tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { @@ -703,11 +714,11 @@ void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t const Point &fp = loop.first_point(); - auto unscaled_p = unscale(fp); - auto closest_perimeter_point_index = find_closest_point(perimeter_points_tree, - Vec3d { unscaled_p.x(), unscaled_p.y(), unscaled_z }); + Vec2f unscaled_p = unscale(fp).cast(); + size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, + Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].m_seam_index; - Vec3d seam_position = perimeter_points[perimeter_seam_index].m_position; + Vec3f seam_position = perimeter_points[perimeter_seam_index].m_position; Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); @@ -717,7 +728,7 @@ void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t loop.split_at(seam_point, true); } -// Disabled debug code, can be used to export debug data into obj files (e.g. point cloud of visibility hits +// Disabled debug code, can be used to export debug data into obj files (e.g. point cloud of visibility hits) #if 0 #include Slic3r::CNumericLocalesSetter locales_setter; diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 057640776..ee85f94ab 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -35,13 +35,13 @@ enum EnforcedBlockedSeamPoint { }; struct SeamCandidate { - SeamCandidate(const Vec3d &pos, size_t polygon_index_reverse, float ccw_angle, EnforcedBlockedSeamPoint type) : - m_position(pos), m_visibility(0.0), m_overhang(0.0), m_polygon_index_reverse(polygon_index_reverse), m_seam_index( + SeamCandidate(const Vec3f &pos, size_t polygon_index_reverse, float ccw_angle, EnforcedBlockedSeamPoint type) : + m_position(pos), m_visibility(0.0f), m_overhang(0.0f), m_polygon_index_reverse(polygon_index_reverse), m_seam_index( 0), m_ccw_angle( ccw_angle), m_type(type) { m_nearby_seam_points = std::make_unique>(0); } - Vec3d m_position; + Vec3f m_position; float m_visibility; float m_overhang; size_t m_polygon_index_reverse; @@ -52,8 +52,8 @@ struct SeamCandidate { }; struct HitInfo { - Vec3d m_position; - Vec3d m_surface_normal; + Vec3f m_position; + Vec3f m_surface_normal; }; struct SeamCandidateCoordinateFunctor { @@ -80,15 +80,17 @@ struct HitInfoCoordinateFunctor { class SeamPlacer { public: using SeamCandidatesTree = - KDTreeIndirect<3, coordf_t, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr double considered_hits_distance = 4.0; - static constexpr double expected_hits_per_area = 250.0; - static constexpr float cosine_hemisphere_sampling_power = 1.5; - static constexpr float polygon_angles_arm_distance = 0.6; - static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4; + KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; + static constexpr float expected_hits_per_area = 100.0f; + static constexpr size_t ray_count = 150000; //NOTE: fixed count of rays is better: + // on small models, the visibility has huge impact and precision is welcomed. + // on large models, it would be very expensive to get similar results, and the effect is arguably less important. + static constexpr float cosine_hemisphere_sampling_power = 1.5f; + static constexpr float polygon_angles_arm_distance = 0.6f; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4f; static constexpr size_t seam_align_iterations = 4; static constexpr size_t seam_align_layer_dist = 30; - static constexpr float seam_align_tolerable_dist = 0.3; + static constexpr float seam_align_tolerable_dist = 0.3f; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; std::unordered_map>> m_perimeter_points_trees_per_object; From 132f4bb5906838e70ba7087971ba11d4a81a6ab1 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 15 Feb 2022 16:57:57 +0100 Subject: [PATCH 12/71] bugfix: usage of undeclared M_PIf32 instead of PI --- src/libslic3r/GCode/SeamPlacerNG.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index b23572eb0..7e9037a80 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -72,7 +72,7 @@ Vec3f sample_sphere_uniform(const Vec2f &samples) { } Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { - float term1 = 2.f * M_PIf32 * samples.x(); + float term1 = 2.f * float(PI) * samples.x(); float term2 = pow(samples.y(), 1.f / (power + 1.f)); float term3 = sqrt(1.f - term2 * term2); @@ -464,30 +464,30 @@ struct DefaultSeamComparator { } //avoid overhangs - if (a.m_overhang > 0.5 && b.m_overhang < a.m_overhang) { + if (a.m_overhang > 0.5f && b.m_overhang < a.m_overhang) { return false; } auto angle_score = [](float ccw_angle) { if (ccw_angle > 0) { - float normalized = (ccw_angle / float(PI)) * 0.9; + float normalized = (ccw_angle / float(PI)) * 0.9f; return normalized; } else { - float normalized = (-ccw_angle / float(PI)) * 1.1; + float normalized = (-ccw_angle / float(PI)) * 1.1f; return normalized; } }; - float angle_weight = 2.0; + float angle_weight = 2.0f; auto vis_score = [](float visibility) { - return (1.0 - visibility / SeamPlacer::expected_hits_per_area); + return (1.0f - visibility / SeamPlacer::expected_hits_per_area); }; - float vis_weight = 1.2; + float vis_weight = 1.2f; auto align_score = [](float nearby_seams) { return nearby_seams / SeamPlacer::seam_align_layer_dist; }; - float align_weight = 1.0; + float align_weight = 1.0f; float score_a = angle_score(a.m_ccw_angle) * angle_weight + vis_score(a.m_visibility) * vis_weight + From 356ed93ad782b41f380e8885f3e32a914e607ece Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 16 Feb 2022 14:27:33 +0100 Subject: [PATCH 13/71] Raft layers cause SeamPlacer crash - fix --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/GCode/SeamPlacerNG.cpp | 10 +++++----- src/libslic3r/GCode/SeamPlacerNG.hpp | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4c7135103..e1d429488 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2586,7 +2586,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou loop.split_at(last_pos, false); } else - m_seam_placer.place_seam(m_layer->object(), loop, m_layer->slice_z, m_layer_index, m_config.external_perimeters_first); + m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first); // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 7e9037a80..fe7e7f2ae 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -703,12 +703,12 @@ void SeamPlacer::init(const Print &print) { } } -void SeamPlacer::place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, int layer_index, - bool external_first) { - assert(m_perimeter_points_trees_per_object.find(po) != nullptr); - assert(m_perimeter_points_per_object.find(po) != nullptr); +void SeamPlacer::place_seam(const Layer* layer, ExtrusionLoop &loop, bool external_first) { + const PrintObject* po = layer->object(); + //NOTE this is necessary, since layer->id() is quite unreliable + size_t layer_index = std::max(0,int(layer->id()) - int(po->slicing_parameters().raft_layers())); + double unscaled_z = layer->slice_z; - assert(layer_index >= 0); const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index ee85f94ab..3379a1935 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -97,8 +97,7 @@ public: void init(const Print &print); - void place_seam(const PrintObject *po, ExtrusionLoop &loop, coordf_t unscaled_z, int layer_index, - bool external_first); + void place_seam(const Layer* layer, ExtrusionLoop &loop, bool external_first); private: void gather_seam_candidates(const PrintObject* po, const SeamPlacerImpl::GlobalModelInfo& global_model_info); From 5a03f60c3196f09366b59ec90b90c53552d7d084 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 16 Feb 2022 16:11:47 +0100 Subject: [PATCH 14/71] fixed bug: wrong estimation of angles inside holes --- src/libslic3r/GCode/SeamPlacerNG.cpp | 6 +++--- src/libslic3r/GCode/SeamPlacerNG.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index fe7e7f2ae..69567cef3 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -293,7 +293,7 @@ Polygons extract_perimeter_polygons(const Layer *layer) { void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, const GlobalModelInfo &global_model_info) { Polygon polygon = orig_polygon; - polygon.make_counter_clockwise(); + bool was_clockwise = polygon.make_counter_clockwise(); std::vector lengths = polygon.parameter_by_length(); std::vector angles = calculate_polygon_angles_at_vertices(polygon, lengths, SeamPlacer::polygon_angles_arm_distance); @@ -308,8 +308,6 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: Vec3f unscaled_position = Vec3f { unscaled_p.x(), unscaled_p.y(), z_coord }; EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::NONE; - float ccw_angle = angles[index]; - if (enforcer_dist_sqr >= 0) { // if enforcer dist < 0, it means there are no enforcers, skip //if there is enforcer, any other enforcer cannot be in a sphere defined by last check point and enforcer distance // so as long as we are at least enforcer_blocker_distance_tolerance deep in that area, and the enforcer distance is greater than @@ -340,6 +338,8 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: } } + float ccw_angle = was_clockwise ? -angles[index] : angles[index]; + result_vec.emplace_back(unscaled_position, polygon.size() - index - 1, ccw_angle, type); } } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 3379a1935..e30530b7e 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -84,7 +84,7 @@ public: static constexpr float expected_hits_per_area = 100.0f; static constexpr size_t ray_count = 150000; //NOTE: fixed count of rays is better: // on small models, the visibility has huge impact and precision is welcomed. - // on large models, it would be very expensive to get similar results, and the effect is arguably less important. + // on large models, it would be very expensive to get similar results, and the local effect is arguably less important. static constexpr float cosine_hemisphere_sampling_power = 1.5f; static constexpr float polygon_angles_arm_distance = 0.6f; static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4f; From 3029053d43fdc85d376594d1e1638e1e4594afe8 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 22 Feb 2022 12:35:47 +0100 Subject: [PATCH 15/71] fixed some problems according to code review simplified blockers and enforcers Pre-Refactoring version --- src/libslic3r/GCode/SeamPlacerNG.cpp | 193 ++++++++++++--------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 44 +++--- 2 files changed, 105 insertions(+), 132 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 69567cef3..366bb4b6c 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -80,12 +80,12 @@ Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { } std::vector raycast_visibility(size_t ray_count, const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles, float& area_to_consider) { + const indexed_triangle_set &triangles, float &area_to_consider) { auto bbox = raycasting_tree.node(0).bbox; Vec3f vision_sphere_center = bbox.center().cast(); Vec3f side_sizes = bbox.sizes().cast(); - float vision_sphere_raidus = (sqrt(side_sizes.dot(side_sizes)) * 0.55); // 0.5 (half) covers whole object, + float vision_sphere_raidus = (side_sizes.norm() * 0.55); // 0.5 (half) covers whole object, // 0.05 added to avoid corner cases // very rough approximation of object surface area from its bounding box float approx_area = 2 * side_sizes.x() * side_sizes.y() + 2 * side_sizes.x() * side_sizes.z() @@ -219,7 +219,7 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, struct GlobalModelInfo { std::vector geometry_raycast_hits; KDTreeIndirect<3, float, HitInfoCoordinateFunctor> raycast_hits_tree; - float hits_area_to_consider{}; + float hits_area_to_consider { }; indexed_triangle_set enforcers; indexed_triangle_set blockers; AABBTreeIndirect::Tree<3, float> enforcers_tree; @@ -229,18 +229,16 @@ struct GlobalModelInfo { raycast_hits_tree(HitInfoCoordinateFunctor { &geometry_raycast_hits }) { } - float enforcer_distance_check(const Vec3f &position) const { - size_t hit_idx_out; - Vec3f closest_point; - return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(enforcers.vertices, enforcers.indices, - enforcers_tree, position, hit_idx_out, closest_point); + bool is_enforced(const Vec3f &position) const { + float radius = SeamPlacer::enforcer_blocker_sqr_distance_tolerance; + return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, + enforcers_tree, position, radius); } - float blocker_distance_check(const Vec3f &position) const { - size_t hit_idx_out; - Vec3f closest_point; - return AABBTreeIndirect::squared_distance_to_indexed_triangle_set(blockers.vertices, blockers.indices, - blockers_tree, position, hit_idx_out, closest_point); + bool is_blocked(const Vec3f &position) const { + float radius = SeamPlacer::enforcer_blocker_sqr_distance_tolerance; + return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, + blockers_tree, position, radius); } float calculate_point_visibility(const Vec3f &position, float max_distance) const { @@ -248,7 +246,7 @@ struct GlobalModelInfo { float visibility = 0; for (const auto &hit_point_index : nearby_points) { float distance = - (position - geometry_raycast_hits[hit_point_index].m_position).norm(); + (position - geometry_raycast_hits[hit_point_index].position).norm(); visibility += max_distance - distance; // The further away from the perimeter point, // the less representative ray hit is } @@ -267,24 +265,24 @@ Polygons extract_perimeter_polygons(const Layer *layer) { if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { Points p; perimeter->collect_points(p); - polygons.push_back(Polygon(p)); + polygons.emplace_back(p); } } if (polygons.empty()) { Points p; ex_entity->collect_points(p); - polygons.push_back(Polygon(p)); + polygons.emplace_back(p); } } else { Points p; ex_entity->collect_points(p); - polygons.push_back(Polygon(p)); + polygons.emplace_back(p); } } } if (polygons.empty()) { - polygons.push_back(Polygon { Point { 0, 0 } }); + polygons.emplace_back(std::vector { Point { 0, 0 } }); } return polygons; @@ -298,44 +296,17 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: std::vector angles = calculate_polygon_angles_at_vertices(polygon, lengths, SeamPlacer::polygon_angles_arm_distance); - Vec3f last_enforcer_checked_point { 0, 0, -1 }; - float enforcer_dist_sqr = global_model_info.enforcer_distance_check(last_enforcer_checked_point); - Vec3f last_blocker_checked_point { 0, 0, -1 }; - float blocker_dist_sqr = global_model_info.blocker_distance_check(last_blocker_checked_point); - for (size_t index = 0; index < polygon.size(); ++index) { Vec2f unscaled_p = unscale(polygon[index]).cast(); Vec3f unscaled_position = Vec3f { unscaled_p.x(), unscaled_p.y(), z_coord }; - EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::NONE; + EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; - if (enforcer_dist_sqr >= 0) { // if enforcer dist < 0, it means there are no enforcers, skip - //if there is enforcer, any other enforcer cannot be in a sphere defined by last check point and enforcer distance - // so as long as we are at least enforcer_blocker_distance_tolerance deep in that area, and the enforcer distance is greater than - // enforcer_blocker_distance_tolerance, we are fine. - if (enforcer_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance - || - (last_enforcer_checked_point - unscaled_position).squaredNorm() - >= enforcer_dist_sqr - 2 * SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { - //do check - enforcer_dist_sqr = global_model_info.enforcer_distance_check(unscaled_position); - last_enforcer_checked_point = unscaled_position; - if (enforcer_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { - type = EnforcedBlockedSeamPoint::ENFORCED; - } - } + if (global_model_info.is_enforced(unscaled_position)) { + type = EnforcedBlockedSeamPoint::Enforced; } - //same for blockers - if (blocker_dist_sqr >= 0) { - if (blocker_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance - || - (last_blocker_checked_point - unscaled_position).squaredNorm() - >= blocker_dist_sqr - 2 * SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { - blocker_dist_sqr = global_model_info.blocker_distance_check(unscaled_position); - last_blocker_checked_point = unscaled_position; - if (blocker_dist_sqr < SeamPlacer::enforcer_blocker_sqr_distance_tolerance) { - type = EnforcedBlockedSeamPoint::BLOCKED; - } - } + + if (global_model_info.is_blocked(unscaled_position)) { + type = EnforcedBlockedSeamPoint::Blocked; } float ccw_angle = was_clockwise ? -angles[index] : angles[index]; @@ -352,19 +323,19 @@ std::pair find_previous_and_next_perimeter_point(const std::vect size_t next = index + 1 < perimeter_points.size() ? index + 1 : index; //NOTE: dont forget that m_polygon_index_reverse are reversed indexes, so 0 is last point - if (current.m_polygon_index_reverse == 0) { + if (current.polygon_index_reverse == 0) { // next is at the start of loop //find start size_t start = index; - while (start > 0 && perimeter_points[start - 1].m_polygon_index_reverse != 0) { + while (start > 0 && perimeter_points[start - 1].polygon_index_reverse != 0) { start--; } next = start; } - if (index > 1 && perimeter_points[index - 1].m_polygon_index_reverse == 0) { + if (index > 1 && perimeter_points[index - 1].polygon_index_reverse == 0) { //prev is at the end of loop - prev = index + perimeter_points[index].m_polygon_index_reverse; + prev = index + perimeter_points[index].polygon_index_reverse; } return {prev,next}; @@ -373,10 +344,10 @@ std::pair find_previous_and_next_perimeter_point(const std::vect //NOTE: only rough esitmation of overhang distance float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b, const SeamCandidate &under_c) { - auto p = Vec2d { point.m_position.x(), point.m_position.y() }; - auto a = Vec2d { under_a.m_position.x(), under_a.m_position.y() }; - auto b = Vec2d { under_b.m_position.x(), under_b.m_position.y() }; - auto c = Vec2d { under_c.m_position.x(), under_c.m_position.y() }; + auto p = Vec2d { point.position.x(), point.position.y() }; + auto a = Vec2d { under_a.position.x(), under_a.position.y() }; + auto b = Vec2d { under_b.position.x(), under_b.position.y() }; + auto c = Vec2d { under_c.position.x(), under_c.position.y() }; auto oriented_line_dist = [](const Vec2d a, const Vec2d b, const Vec2d p) { return -((b.x() - a.x()) * (a.y() - p.y()) - (a.x() - p.x()) * (b.y() - a.y())) / (a - b).norm(); @@ -385,11 +356,11 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ auto dist_ab = oriented_line_dist(a, b, p); auto dist_bc = oriented_line_dist(b, c, p); - if (under_b.m_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside + if (under_b.ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside return 0; } - if (under_b.m_ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside + if (under_b.ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside return 0; } @@ -407,8 +378,8 @@ void pick_seam_point(std::vector &perimeter_points, size_t start_ } for (size_t index = start_index; index <= end_index; ++index) { - perimeter_points[index].m_seam_index = seam_index; - perimeter_points[index].m_nearby_seam_points.get()->store(0, std::memory_order_relaxed); + perimeter_points[index].seam_index = seam_index; + perimeter_points[index].nearby_seam_points.get()->store(0, std::memory_order_relaxed); } } @@ -424,7 +395,8 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { triangle_set.indices); float hits_area_to_consider; - result.geometry_raycast_hits = raycast_visibility(SeamPlacer::ray_count, raycasting_tree, triangle_set, hits_area_to_consider); + result.geometry_raycast_hits = raycast_visibility(SeamPlacer::ray_count, raycasting_tree, triangle_set, + hits_area_to_consider); result.raycast_hits_tree.build(result.geometry_raycast_hits.size()); result.hits_area_to_consider = hits_area_to_consider; @@ -456,15 +428,15 @@ struct DefaultSeamComparator { //is A better? bool operator()(const SeamCandidate &a, const SeamCandidate &b) const { // Blockers/Enforcers discrimination, top priority - if (a.m_type > b.m_type) { + if (a.type > b.type) { return true; } - if (b.m_type > a.m_type) { + if (b.type > a.type) { return false; } //avoid overhangs - if (a.m_overhang > 0.5f && b.m_overhang < a.m_overhang) { + if (a.overhang > 0.5f && b.overhang < a.overhang) { return false; } @@ -489,12 +461,12 @@ struct DefaultSeamComparator { }; float align_weight = 1.0f; - float score_a = angle_score(a.m_ccw_angle) * angle_weight + - vis_score(a.m_visibility) * vis_weight + - align_score(*a.m_nearby_seam_points) * align_weight; - float score_b = angle_score(b.m_ccw_angle) * angle_weight + - vis_score(b.m_visibility) * vis_weight + - align_score(*b.m_nearby_seam_points) * align_weight; + float score_a = angle_score(a.ccw_angle) * angle_weight + + vis_score(a.visibility) * vis_weight + + align_score(*a.nearby_seam_points) * align_weight; + float score_b = angle_score(b.ccw_angle) * angle_weight + + vis_score(b.visibility) * vis_weight + + align_score(*b.nearby_seam_points) * align_weight; if (score_a > score_b) return true; @@ -542,8 +514,8 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.m_visibility = global_model_info.calculate_point_visibility( - perimeter_point.m_position, considered_hits_distance); + perimeter_point.visibility = global_model_info.calculate_point_visibility( + perimeter_point.position, considered_hits_distance); } } }); @@ -559,7 +531,7 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po if (layer_idx > 0) { size_t closest_supporter = find_closest_point( *m_perimeter_points_trees_per_object[po][layer_idx - 1], - perimeter_point.m_position); + perimeter_point.position); const SeamCandidate &supporter_point = m_perimeter_points_per_object[po][layer_idx - 1][closest_supporter]; @@ -569,7 +541,7 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po const SeamCandidate &next_point = m_perimeter_points_per_object[po][layer_idx - 1][prev_next.second]; - perimeter_point.m_overhang = calculate_overhang(perimeter_point, prev_point, + perimeter_point.overhang = calculate_overhang(perimeter_point, prev_point, supporter_point, next_point); } @@ -589,7 +561,7 @@ void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) size_t current = 0; while (current < layer_perimeter_points.size()) { Vec3f seam_position = - layer_perimeter_points[layer_perimeter_points[current].m_seam_index].m_position; + layer_perimeter_points[layer_perimeter_points[current].seam_index].position; int other_layer_idx_bottom = std::max( (int) layer_idx - (int) seam_align_layer_dist, 0); @@ -601,48 +573,49 @@ void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) for (int other_layer_idx = layer_idx + 1; other_layer_idx <= other_layer_idx_top; ++other_layer_idx) { Vec3f projected_position { last_point_position.x(), last_point_position.y(), float(po->get_layer(other_layer_idx)->slice_z)}; - size_t closest_point_index = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - projected_position); +size_t closest_point_index = find_closest_point( +*m_perimeter_points_trees_per_object[po][other_layer_idx], +projected_position); - SeamCandidate &point_ref = - m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; - if ((point_ref.m_position - projected_position).norm() - < SeamPlacer::seam_align_tolerable_dist) { - point_ref.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); - last_point_position = point_ref.m_position; - } else { - //break when no close point found - break; - } - } - //distribute downwards +SeamCandidate &point_ref = +m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; +if ((point_ref.position - projected_position).norm() +< SeamPlacer::seam_align_tolerable_dist) { + point_ref.nearby_seam_points->fetch_add(1, std::memory_order_relaxed); + last_point_position = point_ref.position; +} else { + //break when no close point found + break; +} +} + //distribute downwards last_point_position = seam_position; if (layer_idx > 0) { for (int other_layer_idx = layer_idx - 1; other_layer_idx >= other_layer_idx_bottom; --other_layer_idx) { - Vec3f projected_position { last_point_position.x(), last_point_position.y(), float(po->get_layer(other_layer_idx)->slice_z)}; + Vec3f projected_position { last_point_position.x(), last_point_position.y(), float( + po->get_layer(other_layer_idx)->slice_z) }; size_t closest_point_index = find_closest_point( *m_perimeter_points_trees_per_object[po][other_layer_idx], projected_position); SeamCandidate &point_ref = m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; - if ((point_ref.m_position - projected_position).norm() + if ((point_ref.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist) { - point_ref.m_nearby_seam_points->fetch_add(1, std::memory_order_relaxed); - last_point_position = point_ref.m_position; + point_ref.nearby_seam_points->fetch_add(1, std::memory_order_relaxed); + last_point_position = point_ref.position; } else { break; } } } - current += layer_perimeter_points[current].m_polygon_index_reverse + 1; + current += layer_perimeter_points[current].polygon_index_reverse + 1; } } }); -} + } void SeamPlacer::init(const Print &print) { using namespace SeamPlacerImpl; @@ -691,9 +664,9 @@ void SeamPlacer::init(const Print &print) { while (current < layer_perimeter_points.size()) { //NOTE: pick seam point function also resets the m_nearby_seam_points count on all passed points pick_seam_point(layer_perimeter_points, current, - current + layer_perimeter_points[current].m_polygon_index_reverse, + current + layer_perimeter_points[current].polygon_index_reverse, DefaultSeamComparator { }); - current += layer_perimeter_points[current].m_polygon_index_reverse + 1; + current += layer_perimeter_points[current].polygon_index_reverse + 1; } } }); @@ -703,10 +676,10 @@ void SeamPlacer::init(const Print &print) { } } -void SeamPlacer::place_seam(const Layer* layer, ExtrusionLoop &loop, bool external_first) { - const PrintObject* po = layer->object(); +void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first) { + const PrintObject *po = layer->object(); //NOTE this is necessary, since layer->id() is quite unreliable - size_t layer_index = std::max(0,int(layer->id()) - int(po->slicing_parameters().raft_layers())); + size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); double unscaled_z = layer->slice_z; const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; @@ -717,8 +690,8 @@ void SeamPlacer::place_seam(const Layer* layer, ExtrusionLoop &loop, bool extern Vec2f unscaled_p = unscale(fp).cast(); size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); - size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].m_seam_index; - Vec3f seam_position = perimeter_points[perimeter_seam_index].m_position; + size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].seam_index; + Vec3f seam_position = perimeter_points[perimeter_seam_index].position; Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); @@ -739,8 +712,8 @@ void SeamPlacer::place_seam(const Layer* layer, ExtrusionLoop &loop, bool extern } for (size_t i = 0; i < perimeter_points.size(); ++i) - fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].m_position[0], perimeter_points[i].m_position[1], - perimeter_points[i].m_position[2], perimeter_points[i].m_visibility); + fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].position[0], perimeter_points[i].position[1], + perimeter_points[i].position[2], perimeter_points[i].visibility); fclose(fp); #endif @@ -755,8 +728,8 @@ void SeamPlacer::place_seam(const Layer* layer, ExtrusionLoop &loop, bool extern } for (size_t i = 0; i < hit_points.size(); ++i) - fprintf(fp, "v %f %f %f \n", hit_points[i].m_position[0], hit_points[i].m_position[1], - hit_points[i].m_position[2]); + fprintf(fp, "v %f %f %f \n", hit_points[i].position[0], hit_points[i].position[1], + hit_points[i].position[2]); fclose(fp); #endif diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index e30530b7e..ce69fcf12 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -28,32 +28,32 @@ namespace SeamPlacerImpl { struct GlobalModelInfo; -enum EnforcedBlockedSeamPoint { - BLOCKED = 0, - NONE = 1, - ENFORCED = 2, +enum class EnforcedBlockedSeamPoint { + Blocked = 0, + Neutral = 1, + Enforced = 2, }; struct SeamCandidate { SeamCandidate(const Vec3f &pos, size_t polygon_index_reverse, float ccw_angle, EnforcedBlockedSeamPoint type) : - m_position(pos), m_visibility(0.0f), m_overhang(0.0f), m_polygon_index_reverse(polygon_index_reverse), m_seam_index( - 0), m_ccw_angle( - ccw_angle), m_type(type) { - m_nearby_seam_points = std::make_unique>(0); + position(pos), visibility(0.0f), overhang(0.0f), polygon_index_reverse(polygon_index_reverse), seam_index( + 0), ccw_angle( + ccw_angle), type(type) { + nearby_seam_points = std::make_unique>(0); } - Vec3f m_position; - float m_visibility; - float m_overhang; - size_t m_polygon_index_reverse; - size_t m_seam_index; - float m_ccw_angle; - std::unique_ptr> m_nearby_seam_points; - EnforcedBlockedSeamPoint m_type; + Vec3f position; + float visibility; + float overhang; + size_t polygon_index_reverse; + size_t seam_index; + float ccw_angle; + std::unique_ptr> nearby_seam_points; + EnforcedBlockedSeamPoint type; }; struct HitInfo { - Vec3f m_position; - Vec3f m_surface_normal; + Vec3f position; + Vec3f surface_normal; }; struct SeamCandidateCoordinateFunctor { @@ -62,17 +62,17 @@ struct SeamCandidateCoordinateFunctor { } std::vector *seam_candidates; float operator()(size_t index, size_t dim) const { - return seam_candidates->operator[](index).m_position[dim]; + return seam_candidates->operator[](index).position[dim]; } }; struct HitInfoCoordinateFunctor { HitInfoCoordinateFunctor(std::vector *hit_points) : - m_hit_points(hit_points) { + hit_points(hit_points) { } - std::vector *m_hit_points; + std::vector *hit_points; float operator()(size_t index, size_t dim) const { - return m_hit_points->operator[](index).m_position[dim]; + return hit_points->operator[](index).position[dim]; } }; } // namespace SeamPlacerImpl From 53ff4a69e009ef2076a22a26055a2311973e6525 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 22 Feb 2022 13:53:24 +0100 Subject: [PATCH 16/71] implemented debug files export --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode/SeamPlacerNG.cpp | 64 ++++++++ src/libslic3r/GCode/SeamPlacerNG.hpp | 2 +- src/libslic3r/GCode/Subdivide.cpp | 218 +++++++++++++++++++++++++++ src/libslic3r/GCode/Subdivide.hpp | 12 ++ 5 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 src/libslic3r/GCode/Subdivide.cpp create mode 100644 src/libslic3r/GCode/Subdivide.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 16af2bfdc..6e0679291 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -120,6 +120,8 @@ set(SLIC3R_SOURCES GCode/SpiralVase.hpp GCode/SeamPlacerNG.cpp GCode/SeamPlacerNG.hpp + GCode/Subdivide.hpp + GCode/Subdivide.cpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp GCode/WipeTower.cpp diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 366bb4b6c..98347d49c 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -15,6 +15,13 @@ #include "libslic3r/SVG.hpp" #include "libslic3r/Layer.hpp" +#define DEBUG_FILES + +#ifdef DEBUG_FILES +#include "Subdivide.hpp" +#include +#endif + namespace Slic3r { namespace SeamPlacerImpl { @@ -253,6 +260,58 @@ struct GlobalModelInfo { return visibility; } + +#ifdef DEBUG_FILES + void debug_export(const indexed_triangle_set &obj_mesh, const char *file_name) const { + indexed_triangle_set divided_mesh = subdivide(obj_mesh, 3); + Slic3r::CNumericLocalesSetter locales_setter; + { + FILE *fp = boost::nowide::fopen(file_name, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << file_name << " for writing"; + return; + } + + const auto vis_to_rgb = [](float normalized_visibility) { + float ratio = 2 * normalized_visibility; + float blue = std::max(0.0f, 1.0f - ratio); + float red = std::max(0.0f, ratio - 1.0f); + float green = std::max(0.0f, 1.0f - blue - red); + return Vec3f { red, blue, green }; + }; + + for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { + float visibility = calculate_point_visibility(divided_mesh.vertices[i], + sqrt(hits_area_to_consider / float(PI))); + float normalized = visibility / SeamPlacer::expected_hits_per_area; + Vec3f color = vis_to_rgb(normalized); + fprintf(fp, "v %f %f %f %f %f %f\n", + divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), + color(0), color(1), color(2) + ); + } + for (size_t i = 0; i < divided_mesh.indices.size(); ++i) + fprintf(fp, "f %d %d %d\n", divided_mesh.indices[i][0] + 1, divided_mesh.indices[i][1] + 1, + divided_mesh.indices[i][2] + 1); + fclose(fp); + } + { + auto fname = std::string("hits_").append(file_name); + FILE *fp = boost::nowide::fopen(fname.c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Couldn't open " << fname << " for writing"; + } + + for (size_t i = 0; i < geometry_raycast_hits.size(); ++i) + fprintf(fp, "v %f %f %f \n", geometry_raycast_hits[i].position[0], geometry_raycast_hits[i].position[1], + geometry_raycast_hits[i].position[2]); + fclose(fp); + } + } +#endif + } ; @@ -422,6 +481,11 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; + +#ifdef DEBUG_FILES + auto filename = "visiblity_of_" + std::to_string(po->id().id) + ".obj"; + result.debug_export(triangle_set, filename.c_str()); +#endif } struct DefaultSeamComparator { diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index ce69fcf12..dfd320d91 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -82,7 +82,7 @@ public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; static constexpr float expected_hits_per_area = 100.0f; - static constexpr size_t ray_count = 150000; //NOTE: fixed count of rays is better: + static constexpr size_t ray_count = 1500000; //NOTE: fixed count of rays is better: // on small models, the visibility has huge impact and precision is welcomed. // on large models, it would be very expensive to get similar results, and the local effect is arguably less important. static constexpr float cosine_hemisphere_sampling_power = 1.5f; diff --git a/src/libslic3r/GCode/Subdivide.cpp b/src/libslic3r/GCode/Subdivide.cpp new file mode 100644 index 000000000..734b1147d --- /dev/null +++ b/src/libslic3r/GCode/Subdivide.cpp @@ -0,0 +1,218 @@ +#include "Subdivide.hpp" +#include "Point.hpp" + +namespace Slic3r{ + +indexed_triangle_set subdivide( + const indexed_triangle_set &its, float max_length) +{ + // same order as key order in Edge Divides + struct VerticesSequence + { + size_t start_index; + bool positive_order; + VerticesSequence(size_t start_index, bool positive_order = true) + : start_index(start_index), positive_order(positive_order){} + }; + // vertex index small, big vertex index from key.first to key.second + using EdgeDivides = std::map, VerticesSequence>; + struct Edges + { + Vec3f data[3]; + Vec3f lengths; + Edges(const Vec3crd &indices, const std::vector &vertices) + : lengths(-1.f,-1.f,-1.f) + { + const Vec3f &v0 = vertices[indices[0]]; + const Vec3f &v1 = vertices[indices[1]]; + const Vec3f &v2 = vertices[indices[2]]; + data[0] = v0 - v1; + data[1] = v1 - v2; + data[2] = v2 - v0; + } + float abs_sum(const Vec3f &v) + { + return abs(v[0]) + abs(v[1]) + abs(v[2]); + } + bool is_dividable(const float& max_length) { + Vec3f sum(abs_sum(data[0]), abs_sum(data[1]), abs_sum(data[2])); + Vec3i biggest_index = (sum[0] > sum[1]) ? + ((sum[0] > sum[2]) ? + ((sum[2] > sum[1]) ? + Vec3i(0, 2, 1) : + Vec3i(0, 1, 2)) : + Vec3i(2, 0, 1)) : + ((sum[1] > sum[2]) ? + ((sum[2] > sum[0]) ? + Vec3i(1, 2, 0) : + Vec3i(1, 0, 2)) : + Vec3i(2, 1, 0)); + for (int i = 0; i < 3; i++) { + int index = biggest_index[i]; + if (sum[index] <= max_length) return false; + lengths[index] = data[index].norm(); + if (lengths[index] <= max_length) continue; + + // calculate rest of lengths + for (int j = i + 1; j < 3; j++) { + index = biggest_index[j]; + lengths[index] = data[index].norm(); + } + return true; + } + return false; + } + }; + struct TriangleLengths + { + Vec3crd indices; + Vec3f l; // lengths + TriangleLengths(const Vec3crd &indices, const Vec3f &lengths) + : indices(indices), l(lengths) + {} + + int get_divide_index(float max_length) { + if (l[0] > l[1] && l[0] > l[2]) { + if (l[0] > max_length) return 0; + } else if (l[1] > l[2]) { + if (l[1] > max_length) return 1; + } else { + if (l[2] > max_length) return 2; + } + return -1; + } + + // divide triangle add new vertex to vertices + std::pair divide( + int divide_index, float max_length, + std::vector &vertices, + EdgeDivides &edge_divides) + { + // index to lengths and indices + size_t i0 = divide_index; + size_t i1 = (divide_index + 1) % 3; + size_t vi0 = indices[i0]; + size_t vi1 = indices[i1]; + std::pair key(vi0, vi1); + bool key_swap = false; + if (key.first > key.second) { + std::swap(key.first, key.second); + key_swap = true; + } + + float length = l[divide_index]; + size_t count_edge_vertices = static_cast(floor(length / max_length)); + float count_edge_segments = static_cast(count_edge_vertices + 1); + + auto it = edge_divides.find(key); + if (it == edge_divides.end()) { + // Create new vertices + VerticesSequence new_vs(vertices.size()); + Vec3f vf = vertices[key.first]; // copy + const Vec3f &vs = vertices[key.second]; + Vec3f dir = vs - vf; + for (size_t i = 1; i <= count_edge_vertices; ++i) { + float ratio = i / count_edge_segments; + vertices.push_back(vf + dir * ratio); + } + bool success; + std::tie(it,success) = edge_divides.insert({key, new_vs}); + assert(success); + } + const VerticesSequence &vs = it->second; + + int index_offset = count_edge_vertices/2; + size_t i2 = (divide_index + 2) % 3; + if (count_edge_vertices % 2 == 0 && key_swap == l[i1] < l[i2]) { + --index_offset; + } + int sign = (vs.positive_order) ? 1 : -1; + size_t new_index = vs.start_index + sign*index_offset; + + size_t vi2 = indices[i2]; + const Vec3f &v2 = vertices[vi2]; + Vec3f new_edge = v2 - vertices[new_index]; + float new_len = new_edge.norm(); + + float ratio = (1 + index_offset) / count_edge_segments; + float len1 = l[i0] * ratio; + float len2 = l[i0] - len1; + if (key_swap) std::swap(len1, len2); + + Vec3crd indices1(vi0, new_index, vi2); + Vec3f lengths1(len1, new_len, l[i2]); + + Vec3crd indices2(new_index, vi1, vi2); + Vec3f lengths2(len2, l[i1], new_len); + + // append key for divided edge when neccesary + if (index_offset > 0) { + std::pair new_key(key.first, new_index); + bool new_key_swap = false; + if (new_key.first > new_key.second) { + std::swap(new_key.first, new_key.second); + new_key_swap = true; + } + if (edge_divides.find(new_key) == edge_divides.end()) { + // insert new + edge_divides.insert({new_key, (new_key_swap) ? + VerticesSequence(new_index - sign, !vs.positive_order) + : vs}); + } + } + + if (index_offset < count_edge_vertices-1) { + std::pair new_key(new_index, key.second); + bool new_key_swap = false; + if (new_key.first > new_key.second) { + std::swap(new_key.first, new_key.second); + new_key_swap = true; + } + // bad order + if (edge_divides.find(new_key) == edge_divides.end()) { + edge_divides.insert({new_key, (new_key_swap) ? + VerticesSequence(vs.start_index + sign*(count_edge_vertices-1), !vs.positive_order) + : VerticesSequence(new_index + sign, vs.positive_order)}); + } + } + + return {TriangleLengths(indices1, lengths1), + TriangleLengths(indices2, lengths2)}; + } + }; + indexed_triangle_set result; + result.indices.reserve(its.indices.size()); + const std::vector &vertices = its.vertices; + result.vertices = vertices; // copy + std::queue tls; + + EdgeDivides edge_divides; + for (const Vec3crd &indices : its.indices) { + Edges edges(indices, vertices); + // speed up only sum not sqrt is apply + if (!edges.is_dividable(max_length)) { + // small triangle + result.indices.push_back(indices); + continue; + } + TriangleLengths tl(indices, edges.lengths); + do { + int divide_index = tl.get_divide_index(max_length); + if (divide_index < 0) { + // no dividing + result.indices.push_back(tl.indices); + if (tls.empty()) break; + tl = tls.front(); // copy + tls.pop(); + } else { + auto [tl1, tl2] = tl.divide(divide_index, max_length, + result.vertices, edge_divides); + tl = tl1; + tls.push(tl2); + } + } while (true); + } + return result; +} + +} diff --git a/src/libslic3r/GCode/Subdivide.hpp b/src/libslic3r/GCode/Subdivide.hpp new file mode 100644 index 000000000..3f6f96d17 --- /dev/null +++ b/src/libslic3r/GCode/Subdivide.hpp @@ -0,0 +1,12 @@ +#ifndef libslic3r_Subdivide_hpp_ +#define libslic3r_Subdivide_hpp_ + +#include "TriangleMesh.hpp" + +namespace Slic3r { + +indexed_triangle_set subdivide(const indexed_triangle_set &its, float max_length); + +} + +#endif //libslic3r_Subdivide_hpp_ From 596bd68f188929e8db140baa885d79b8ab55e735 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 22 Feb 2022 17:49:18 +0100 Subject: [PATCH 17/71] refactoring, created perimters struct to store info, removed alignment iterations, created dynamic ray count estimation --- src/libslic3r/GCode/SeamPlacerNG.cpp | 162 +++++++-------------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 44 ++++---- 2 files changed, 64 insertions(+), 142 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 98347d49c..fa5cb1aa8 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -86,21 +86,22 @@ Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); } -std::vector raycast_visibility(size_t ray_count, const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles, float &area_to_consider) { +std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, + const indexed_triangle_set &triangles) { auto bbox = raycasting_tree.node(0).bbox; Vec3f vision_sphere_center = bbox.center().cast(); Vec3f side_sizes = bbox.sizes().cast(); float vision_sphere_raidus = (side_sizes.norm() * 0.55); // 0.5 (half) covers whole object, // 0.05 added to avoid corner cases - // very rough approximation of object surface area from its bounding box - float approx_area = 2 * side_sizes.x() * side_sizes.y() + 2 * side_sizes.x() * side_sizes.z() - + 2 * side_sizes.y() * side_sizes.z(); - area_to_consider = SeamPlacer::expected_hits_per_area * approx_area / ray_count; + // very rough approximation of object surface area from its bounding sphere + float approx_area = 4 * PI * vision_sphere_raidus * vision_sphere_raidus; + float local_considered_area = PI * SeamPlacer::considered_area_radius * SeamPlacer::considered_area_radius; + size_t ray_count = SeamPlacer::expected_hits_per_area * approx_area / local_considered_area; // Prepare random samples per ray -// std::random_device rnd_device; + // std::random_device rnd_device; + // use fixed seed, we can backtrack potential issues easier std::mt19937 mersenne_engine { 12345 }; std::uniform_real_distribution dist { 0, 1 }; @@ -226,7 +227,6 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, struct GlobalModelInfo { std::vector geometry_raycast_hits; KDTreeIndirect<3, float, HitInfoCoordinateFunctor> raycast_hits_tree; - float hits_area_to_consider { }; indexed_triangle_set enforcers; indexed_triangle_set blockers; AABBTreeIndirect::Tree<3, float> enforcers_tree; @@ -248,13 +248,13 @@ struct GlobalModelInfo { blockers_tree, position, radius); } - float calculate_point_visibility(const Vec3f &position, float max_distance) const { - auto nearby_points = find_nearby_points(raycast_hits_tree, position, max_distance); + float calculate_point_visibility(const Vec3f &position) const { + auto nearby_points = find_nearby_points(raycast_hits_tree, position, SeamPlacer::considered_area_radius); float visibility = 0; for (const auto &hit_point_index : nearby_points) { float distance = (position - geometry_raycast_hits[hit_point_index].position).norm(); - visibility += max_distance - distance; // The further away from the perimeter point, + visibility += SeamPlacer::considered_area_radius - distance; // The further away from the perimeter point, // the less representative ray hit is } return visibility; @@ -263,7 +263,7 @@ struct GlobalModelInfo { #ifdef DEBUG_FILES void debug_export(const indexed_triangle_set &obj_mesh, const char *file_name) const { - indexed_triangle_set divided_mesh = subdivide(obj_mesh, 3); + indexed_triangle_set divided_mesh = subdivide(obj_mesh, SeamPlacer::considered_area_radius); Slic3r::CNumericLocalesSetter locales_setter; { FILE *fp = boost::nowide::fopen(file_name, "w"); @@ -282,9 +282,8 @@ struct GlobalModelInfo { }; for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { - float visibility = calculate_point_visibility(divided_mesh.vertices[i], - sqrt(hits_area_to_consider / float(PI))); - float normalized = visibility / SeamPlacer::expected_hits_per_area; + float visibility = calculate_point_visibility(divided_mesh.vertices[i]); + float normalized = visibility / SeamPlacer::expected_hits_per_area / 2.0; Vec3f color = vis_to_rgb(normalized); fprintf(fp, "v %f %f %f %f %f %f\n", divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), @@ -351,9 +350,14 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: const GlobalModelInfo &global_model_info) { Polygon polygon = orig_polygon; bool was_clockwise = polygon.make_counter_clockwise(); + std::vector lengths = polygon.parameter_by_length(); std::vector angles = calculate_polygon_angles_at_vertices(polygon, lengths, SeamPlacer::polygon_angles_arm_distance); + std::shared_ptr perimeter = std::make_shared(); + + perimeter->start_index = result_vec.size(); + perimeter->end_index = result_vec.size() + polygon.size() - 1; for (size_t index = 0; index < polygon.size(); ++index) { Vec2f unscaled_p = unscale(polygon[index]).cast(); @@ -370,34 +374,27 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: float ccw_angle = was_clockwise ? -angles[index] : angles[index]; - result_vec.emplace_back(unscaled_position, polygon.size() - index - 1, ccw_angle, type); + result_vec.emplace_back(unscaled_position, perimeter, ccw_angle, type); } } std::pair find_previous_and_next_perimeter_point(const std::vector &perimeter_points, size_t index) { const SeamCandidate ¤t = perimeter_points[index]; + int prev = index - 1; + int next = index + 1; - size_t prev = index > 0 ? index - 1 : index; - size_t next = index + 1 < perimeter_points.size() ? index + 1 : index; - - //NOTE: dont forget that m_polygon_index_reverse are reversed indexes, so 0 is last point - if (current.polygon_index_reverse == 0) { - // next is at the start of loop - //find start - size_t start = index; - while (start > 0 && perimeter_points[start - 1].polygon_index_reverse != 0) { - start--; - } - next = start; + if (index == current.perimeter->start_index) { + prev = current.perimeter->end_index; } - if (index > 1 && perimeter_points[index - 1].polygon_index_reverse == 0) { - //prev is at the end of loop - prev = index + perimeter_points[index].polygon_index_reverse; + if (index == current.perimeter->end_index) { + next = current.perimeter->start_index; } - return {prev,next}; + assert(prev >= 0); + assert(next >= 0); + return {size_t(prev),size_t(next)}; } //NOTE: only rough esitmation of overhang distance @@ -423,12 +420,13 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ return 0; } - return Vec2d((p - b).norm(), std::min(abs(dist_ab), abs(dist_bc))).norm(); + return (p - b).norm(); } template -void pick_seam_point(std::vector &perimeter_points, size_t start_index, size_t end_index, +void pick_seam_point(std::vector &perimeter_points, size_t start_index, const CompareFunc &is_first_better) { + size_t end_index = perimeter_points[start_index].perimeter->end_index; size_t seam_index = start_index; for (size_t index = start_index + 1; index <= end_index; ++index) { if (is_first_better(perimeter_points[index], perimeter_points[seam_index])) { @@ -436,10 +434,7 @@ void pick_seam_point(std::vector &perimeter_points, size_t start_ } } - for (size_t index = start_index; index <= end_index; ++index) { - perimeter_points[index].seam_index = seam_index; - perimeter_points[index].nearby_seam_points.get()->store(0, std::memory_order_relaxed); - } + perimeter_points[start_index].perimeter->seam_index = seam_index; } void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { @@ -453,11 +448,8 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, triangle_set.indices); - float hits_area_to_consider; - result.geometry_raycast_hits = raycast_visibility(SeamPlacer::ray_count, raycasting_tree, triangle_set, - hits_area_to_consider); + result.geometry_raycast_hits = raycast_visibility(raycasting_tree, triangle_set); result.raycast_hits_tree.build(result.geometry_raycast_hits.size()); - result.hits_area_to_consider = hits_area_to_consider; BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; @@ -520,17 +512,10 @@ struct DefaultSeamComparator { }; float vis_weight = 1.2f; - auto align_score = [](float nearby_seams) { - return nearby_seams / SeamPlacer::seam_align_layer_dist; - }; - float align_weight = 1.0f; - float score_a = angle_score(a.ccw_angle) * angle_weight + - vis_score(a.visibility) * vis_weight + - align_score(*a.nearby_seam_points) * align_weight; + vis_score(a.visibility) * vis_weight; float score_b = angle_score(b.ccw_angle) * angle_weight + - vis_score(b.visibility) * vis_weight + - align_score(*b.nearby_seam_points) * align_weight; + vis_score(b.visibility) * vis_weight; if (score_a > score_b) return true; @@ -572,14 +557,11 @@ void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info) { using namespace SeamPlacerImpl; -float considered_hits_distance = sqrt(global_model_info.hits_area_to_consider / float(PI)); - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.visibility = global_model_info.calculate_point_visibility( - perimeter_point.position, considered_hits_distance); + perimeter_point.visibility = global_model_info.calculate_point_visibility(perimeter_point.position); } } }); @@ -617,69 +599,7 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) { using namespace SeamPlacerImpl; - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - size_t current = 0; - while (current < layer_perimeter_points.size()) { - Vec3f seam_position = - layer_perimeter_points[layer_perimeter_points[current].seam_index].position; - - int other_layer_idx_bottom = std::max( - (int) layer_idx - (int) seam_align_layer_dist, 0); - int other_layer_idx_top = std::min(layer_idx + seam_align_layer_dist, - m_perimeter_points_per_object[po].size() - 1); - - //distribute from current layer upwards - Vec3f last_point_position = seam_position; - for (int other_layer_idx = layer_idx + 1; - other_layer_idx <= other_layer_idx_top; ++other_layer_idx) { - Vec3f projected_position { last_point_position.x(), last_point_position.y(), float(po->get_layer(other_layer_idx)->slice_z)}; -size_t closest_point_index = find_closest_point( -*m_perimeter_points_trees_per_object[po][other_layer_idx], -projected_position); - -SeamCandidate &point_ref = -m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; -if ((point_ref.position - projected_position).norm() -< SeamPlacer::seam_align_tolerable_dist) { - point_ref.nearby_seam_points->fetch_add(1, std::memory_order_relaxed); - last_point_position = point_ref.position; -} else { - //break when no close point found - break; } -} - //distribute downwards - last_point_position = seam_position; - if (layer_idx > 0) { - for (int other_layer_idx = layer_idx - 1; - other_layer_idx >= other_layer_idx_bottom; --other_layer_idx) { - Vec3f projected_position { last_point_position.x(), last_point_position.y(), float( - po->get_layer(other_layer_idx)->slice_z) }; - size_t closest_point_index = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - projected_position); - - SeamCandidate &point_ref = - m_perimeter_points_per_object[po][other_layer_idx][closest_point_index]; - if ((point_ref.position - projected_position).norm() - < SeamPlacer::seam_align_tolerable_dist) { - point_ref.nearby_seam_points->fetch_add(1, std::memory_order_relaxed); - last_point_position = point_ref.position; - } else { - break; - } - } - } - - current += layer_perimeter_points[current].polygon_index_reverse + 1; - } - } - }); - } void SeamPlacer::init(const Print &print) { using namespace SeamPlacerImpl; @@ -727,10 +647,8 @@ void SeamPlacer::init(const Print &print) { size_t current = 0; while (current < layer_perimeter_points.size()) { //NOTE: pick seam point function also resets the m_nearby_seam_points count on all passed points - pick_seam_point(layer_perimeter_points, current, - current + layer_perimeter_points[current].polygon_index_reverse, - DefaultSeamComparator { }); - current += layer_perimeter_points[current].polygon_index_reverse + 1; + pick_seam_point(layer_perimeter_points, current, DefaultSeamComparator { }); + current = layer_perimeter_points[current].perimeter->end_index + 1; } } }); @@ -754,7 +672,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern Vec2f unscaled_p = unscale(fp).cast(); size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); - size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].seam_index; + size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].perimeter->seam_index; Vec3f seam_position = perimeter_points[perimeter_seam_index].position; Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index dfd320d91..a6a3f7328 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -34,20 +34,22 @@ enum class EnforcedBlockedSeamPoint { Enforced = 2, }; +struct Perimeter { + size_t start_index; + size_t end_index; + size_t seam_index; +}; + struct SeamCandidate { - SeamCandidate(const Vec3f &pos, size_t polygon_index_reverse, float ccw_angle, EnforcedBlockedSeamPoint type) : - position(pos), visibility(0.0f), overhang(0.0f), polygon_index_reverse(polygon_index_reverse), seam_index( - 0), ccw_angle( - ccw_angle), type(type) { - nearby_seam_points = std::make_unique>(0); + SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, float ccw_angle, + EnforcedBlockedSeamPoint type) : + position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), ccw_angle(ccw_angle), type(type) { } - Vec3f position; + const Vec3f position; + const std::shared_ptr perimeter; float visibility; float overhang; - size_t polygon_index_reverse; - size_t seam_index; float ccw_angle; - std::unique_ptr> nearby_seam_points; EnforcedBlockedSeamPoint type; }; @@ -81,14 +83,15 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float expected_hits_per_area = 100.0f; - static constexpr size_t ray_count = 1500000; //NOTE: fixed count of rays is better: - // on small models, the visibility has huge impact and precision is welcomed. - // on large models, it would be very expensive to get similar results, and the local effect is arguably less important. - static constexpr float cosine_hemisphere_sampling_power = 1.5f; + static constexpr float expected_hits_per_area = 200.0f; + static constexpr float considered_area_radius = 2.0f; + + static constexpr float cosine_hemisphere_sampling_power = 6.0f; + static constexpr float polygon_angles_arm_distance = 0.6f; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4f; - static constexpr size_t seam_align_iterations = 4; + static constexpr size_t seam_align_iterations = 1; static constexpr size_t seam_align_layer_dist = 30; static constexpr float seam_align_tolerable_dist = 0.3f; //perimeter points per object per layer idx, and their corresponding KD trees @@ -97,13 +100,14 @@ public: void init(const Print &print); - void place_seam(const Layer* layer, ExtrusionLoop &loop, bool external_first); + void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first); private: - void gather_seam_candidates(const PrintObject* po, const SeamPlacerImpl::GlobalModelInfo& global_model_info); - void calculate_candidates_visibility(const PrintObject* po, const SeamPlacerImpl::GlobalModelInfo& global_model_info); - void calculate_overhangs(const PrintObject* po); - void distribute_seam_positions_for_alignment(const PrintObject* po); + void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); + void calculate_candidates_visibility(const PrintObject *po, + const SeamPlacerImpl::GlobalModelInfo &global_model_info); + void calculate_overhangs(const PrintObject *po); + void distribute_seam_positions_for_alignment(const PrintObject *po); }; } // namespace Slic3r From 38a9d870c06c8494c3cdbceee539397e8d2b3b6b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 24 Feb 2022 17:56:27 +0100 Subject: [PATCH 18/71] implemented seam alignment using exponential smoothing --- src/libslic3r/GCode/SeamPlacerNG.cpp | 418 ++++++++++++++++++--------- src/libslic3r/GCode/SeamPlacerNG.hpp | 29 +- 2 files changed, 297 insertions(+), 150 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index fa5cb1aa8..739fcf963 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -168,47 +168,46 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & } std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, - float min_arm_length) - { - assert(polygon.points.size() + 1 == lengths.size()); - if (min_arm_length > 0.25f * lengths.back()) - min_arm_length = 0.25f * lengths.back(); + float min_arm_length) { + if (polygon.size() == 1) { + return {0.0f}; + } - // Find the initial prev / next point span. - size_t idx_prev = polygon.points.size(); - size_t idx_curr = 0; - size_t idx_next = 1; - while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) - --idx_prev; - while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) - ++idx_next; + std::vector result(polygon.size()); - std::vector angles(polygon.points.size(), 0.f); - for (; idx_curr < polygon.points.size(); ++idx_curr) { - // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. - if (idx_prev >= idx_curr) { - while (idx_prev < polygon.points.size() - && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) - ++idx_prev; - if (idx_prev == polygon.points.size()) - idx_prev = 0; + auto make_idx_circular = [&](int index) { + while (index < 0) { + index += polygon.size(); } - while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) - ++idx_prev; - // Move idx_prev one step back. - if (idx_prev == 0) - idx_prev = polygon.points.size() - 1; - else - --idx_prev; - // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. - if (idx_curr <= idx_next) { - while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) - ++idx_next; - if (idx_next == polygon.points.size()) - idx_next = 0; + return index % polygon.size(); + }; + + int idx_prev = 0; + int idx_curr = 0; + int idx_next = 0; + + float distance_to_prev = 0; + float distance_to_next = 0; + + //push idx_prev far enough back as initialization + while (distance_to_prev < min_arm_length) { + idx_prev = make_idx_circular(idx_prev - 1); + distance_to_prev += lengths[idx_prev]; + } + + for (size_t _i = 0; _i < polygon.size(); ++_i) { + // pull idx_prev to current as much as possible, while respecting the min_arm_length + while (distance_to_prev - lengths[idx_prev] > min_arm_length) { + distance_to_prev -= lengths[idx_prev]; + idx_prev = make_idx_circular(idx_prev + 1); } - while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) - ++idx_next; + + //push idx_next forward as far as needed + while (distance_to_next < min_arm_length) { + distance_to_next += lengths[idx_next]; + idx_next = make_idx_circular(idx_next + 1); + } + // Calculate angle between idx_prev, idx_curr, idx_next. const Point &p0 = polygon.points[idx_prev]; const Point &p1 = polygon.points[idx_curr]; @@ -218,10 +217,16 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); float angle = float(atan2(float(cross), float(dot))); - angles[idx_curr] = angle; + result[idx_curr] = angle; + + // increase idx_curr by one + float curr_distance = lengths[idx_curr]; + idx_curr++; + distance_to_prev += curr_distance; + distance_to_next -= curr_distance; } - return angles; + return result; } struct GlobalModelInfo { @@ -348,12 +353,21 @@ Polygons extract_perimeter_polygons(const Layer *layer) { void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, const GlobalModelInfo &global_model_info) { + if (orig_polygon.size() == 0) { + return; + } + Polygon polygon = orig_polygon; bool was_clockwise = polygon.make_counter_clockwise(); - std::vector lengths = polygon.parameter_by_length(); - std::vector angles = calculate_polygon_angles_at_vertices(polygon, lengths, - SeamPlacer::polygon_angles_arm_distance); + std::vector lengths { }; + for (size_t point_idx = 0; point_idx < polygon.size() - 1; ++point_idx) { + lengths.push_back(std::max((unscale(polygon[point_idx]) - unscale(polygon[point_idx + 1])).norm(), 0.01)); + } + lengths.push_back(std::max((unscale(polygon[0]) - unscale(polygon[polygon.size() - 1])).norm(), 0.01)); + + std::vector local_angles = calculate_polygon_angles_at_vertices(polygon, lengths, + SeamPlacer::polygon_local_angles_arm_distance); std::shared_ptr perimeter = std::make_shared(); perimeter->start_index = result_vec.size(); @@ -372,9 +386,9 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: type = EnforcedBlockedSeamPoint::Blocked; } - float ccw_angle = was_clockwise ? -angles[index] : angles[index]; + float local_ccw_angle = was_clockwise ? -local_angles[index] : local_angles[index]; - result_vec.emplace_back(unscaled_position, perimeter, ccw_angle, type); + result_vec.emplace_back(unscaled_position, perimeter, local_ccw_angle, type); } } @@ -398,6 +412,7 @@ std::pair find_previous_and_next_perimeter_point(const std::vect } //NOTE: only rough esitmation of overhang distance +// value represents distance from edge, positive is overhang, negative is inside shape float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b, const SeamCandidate &under_c) { auto p = Vec2d { point.position.x(), point.position.y() }; @@ -412,24 +427,24 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ auto dist_ab = oriented_line_dist(a, b, p); auto dist_bc = oriented_line_dist(b, c, p); - if (under_b.ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside - return 0; + if (under_b.local_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside + return -((p - b).norm() + dist_ab + dist_bc) / 3.0; } - if (under_b.ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside - return 0; + if (under_b.local_ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside + return -((p - b).norm() + dist_ab + dist_bc) / 3.0; } - return (p - b).norm(); + return ((p - b).norm() + dist_ab + dist_bc) / 3.0; } -template +template void pick_seam_point(std::vector &perimeter_points, size_t start_index, - const CompareFunc &is_first_better) { + const Comparator &comparator) { size_t end_index = perimeter_points[start_index].perimeter->end_index; size_t seam_index = start_index; for (size_t index = start_index + 1; index <= end_index; ++index) { - if (is_first_better(perimeter_points[index], perimeter_points[seam_index])) { + if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { seam_index = index; } } @@ -481,8 +496,21 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { } struct DefaultSeamComparator { - //is A better? - bool operator()(const SeamCandidate &a, const SeamCandidate &b) const { + static constexpr float angle_clusters[] { -1.0, 0.4 * PI, 0.6 + * PI, 0.7 * PI, 0.8 * PI, 0.9 * PI }; + + const float get_angle_category(float ccw_angle) const { + float concave_bonus = ccw_angle < 0 ? 0.1 : 0; + float abs_angle = abs(ccw_angle) + concave_bonus; + auto category = std::find_if_not(std::begin(angle_clusters), std::end(angle_clusters), + [&](float category_limit) { + return abs_angle > category_limit; + }); + category--; + return *category; + } + + bool is_first_better(const SeamCandidate &a, const SeamCandidate &b) const { // Blockers/Enforcers discrimination, top priority if (a.type > b.type) { return true; @@ -492,35 +520,52 @@ struct DefaultSeamComparator { } //avoid overhangs - if (a.overhang > 0.5f && b.overhang < a.overhang) { + if (a.overhang > 0.3f && b.overhang < a.overhang) { return false; } - auto angle_score = [](float ccw_angle) { - if (ccw_angle > 0) { - float normalized = (ccw_angle / float(PI)) * 0.9f; - return normalized; - } else { - float normalized = (-ccw_angle / float(PI)) * 1.1f; - return normalized; + { //local angles + float a_local_category = get_angle_category(a.local_ccw_angle); + float b_local_category = get_angle_category(b.local_ccw_angle); + + if (a_local_category > b_local_category) { + return true; } - }; - float angle_weight = 2.0f; + if (a_local_category < b_local_category) { + return false; + } + } - auto vis_score = [](float visibility) { - return (1.0f - visibility / SeamPlacer::expected_hits_per_area); - }; - float vis_weight = 1.2f; + return a.visibility < b.visibility; + } - float score_a = angle_score(a.ccw_angle) * angle_weight + - vis_score(a.visibility) * vis_weight; - float score_b = angle_score(b.ccw_angle) * angle_weight + - vis_score(b.visibility) * vis_weight; - - if (score_a > score_b) + bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { + // Blockers/Enforcers discrimination, top priority + if (a.type > b.type) { return true; - else + } + if (b.type > a.type) { return false; + } + + //avoid overhangs + if (a.overhang > 0.3f && b.overhang < a.overhang) { + return false; + } + + { //local angles + float a_local_category = get_angle_category(a.local_ccw_angle) + 0.1 * PI; //give a slight bonus + float b_local_category = get_angle_category(b.local_ccw_angle); + + if (a_local_category > b_local_category) { + return true; + } + if (a_local_category < b_local_category) { + return false; + } + } + + return a.visibility < b.visibility * 1.3; } } ; @@ -546,8 +591,8 @@ tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), global_model_info); } auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique( - functor, layer_candidates.size())); + m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( + functor, layer_candidates.size()); } } ); @@ -561,7 +606,8 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.visibility = global_model_info.calculate_point_visibility(perimeter_point.position); + perimeter_point.visibility = global_model_info.calculate_point_visibility( + perimeter_point.position); } } }); @@ -574,31 +620,155 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - if (layer_idx > 0) { + const auto calculate_layer_overhang = [&](size_t other_layer_idx) { size_t closest_supporter = find_closest_point( - *m_perimeter_points_trees_per_object[po][layer_idx - 1], + *m_perimeter_points_trees_per_object[po][other_layer_idx], perimeter_point.position); const SeamCandidate &supporter_point = - m_perimeter_points_per_object[po][layer_idx - 1][closest_supporter]; + m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; - auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][layer_idx-1], closest_supporter); + auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); const SeamCandidate &prev_point = - m_perimeter_points_per_object[po][layer_idx - 1][prev_next.first]; + m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; const SeamCandidate &next_point = - m_perimeter_points_per_object[po][layer_idx - 1][prev_next.second]; + m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; - perimeter_point.overhang = calculate_overhang(perimeter_point, prev_point, + return calculate_overhang(perimeter_point, prev_point, supporter_point, next_point); + }; + if (layer_idx > 0) { //calculate overhang + perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); + } + if (layer_idx < m_perimeter_points_per_object[po].size() - 1) { //calculate higher_layer_overhang + perimeter_point.higher_layer_overhang = calculate_layer_overhang(layer_idx+1); } } } }); } -void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) { +// sadly cannot be const because map access operator[] is not const, since it can create new object +template +bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, const Vec3f &last_point_pos, + size_t layer_idx, const Comparator &comparator, + std::vector> &seam_strings, + std::vector> &potential_string_seams) { using namespace SeamPlacerImpl; + Vec3f projected_position { last_point_pos.x(), last_point_pos.y(), float( + po->get_layer(layer_idx)->slice_z) }; + //find closest point in next layer + size_t closest_point_index = find_closest_point( + *m_perimeter_points_trees_per_object[po][layer_idx], projected_position); + + SeamCandidate &closest_point = m_perimeter_points_per_object[po][layer_idx][closest_point_index]; + + if (closest_point.perimeter->aligned) { //already aligned, skip + return false; + } + + //from the closest point, deduce index of seam in the next layer + SeamCandidate &next_layer_seam = + m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; + + if ((next_layer_seam.position - projected_position).norm() + < SeamPlacer::seam_align_tolerable_dist) { //seam point is within limits, put in the close_by_points list + seam_strings.emplace_back(layer_idx, closest_point.perimeter->seam_index); + return true; + } else if ((closest_point.position - projected_position).norm() + < SeamPlacer::seam_align_tolerable_dist + && comparator.is_first_not_much_worse(closest_point, next_layer_seam)) { + //seam point is far, but if the close point is not much worse, do not count it as a skip and add it to potential_string_seams + potential_string_seams.emplace_back(layer_idx, closest_point_index); + return true; + } else { + return false; + } + +} + +//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 +template +void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comparator) { + using namespace SeamPlacerImpl; + + for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + size_t current_point_index = 0; + while (current_point_index < layer_perimeter_points.size()) { + if (layer_perimeter_points[current_point_index].perimeter->aligned) { + //skip + } else { + int skips = SeamPlacer::seam_align_tolerable_skips; + int next_layer = layer_idx - 1; + Vec3f last_point_pos = layer_perimeter_points[current_point_index].position; + + std::vector> seam_string; + std::vector> potential_string_seams; + + //find close by points and outliers; there is a budget of skips allowed + // search from bottom up in z dir. Heuristics which avoids smooth top surfaces (like head) where the seam is not well defined + while (skips >= 0 && next_layer >= 0) { + if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, + potential_string_seams)) { + last_point_pos = + m_perimeter_points_per_object[po][seam_string.back().first][seam_string.back().second].position; + } else { + skips--; + } + next_layer--; + } + + if (seam_string.size() > 4) { //string long enough to be worth aligning + //do additional check in back direction + next_layer = layer_idx; + skips = SeamPlacer::seam_align_tolerable_skips; + while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { + if (find_next_seam_in_string(po, last_point_pos, next_layer, DefaultSeamComparator { }, + seam_string, + potential_string_seams)) { + last_point_pos = + m_perimeter_points_per_object[po][seam_string.back().first][seam_string.back().second].position; + } else { + skips--; + } + next_layer++; + } + + // all string seams and potential string seams gathered, now do the alignment + seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); + std::sort(seam_string.begin(), seam_string.end(), + [](const std::pair &left, const std::pair &right) { + return left.first < right.first; + }); + + //https://en.wikipedia.org/wiki/Exponential_smoothing + //inititalization + float smoothing_factor = 0.5; + std::pair init = seam_string[0]; + Vec2f prev_pos_xy = m_perimeter_points_per_object[po][init.first][init.second].position.head<2>(); + for (const auto &pair : seam_string) { + Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; + float current_height = current_pos.z(); + Vec2f current_pos_xy = current_pos.head<2>(); + current_pos_xy = smoothing_factor * prev_pos_xy + (1.0 - smoothing_factor) * current_pos_xy; + + Perimeter *perimeter = + m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); + perimeter->final_seam_position = + Vec3f { current_pos_xy.x(), current_pos_xy.y(), current_height }; + perimeter->aligned = true; + prev_pos_xy = current_pos_xy; + } + + } // else string is not long enough, so dont do anything + } + current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1; + } + } + } void SeamPlacer::init(const Print &print) { @@ -630,31 +800,29 @@ void SeamPlacer::init(const Print &print) { << "SeamPlacer: calculate_overhangs : end"; BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: distribute_seam_positions_for_alignment, pick_seams : start"; - - for (size_t iteration = 0; iteration < seam_align_iterations; ++iteration) { - - if (iteration > 0) { //skip this in first iteration, no seam has been picked yet; nothing to distribute - distribute_seam_positions_for_alignment(po); - } - - //pick seam point - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - size_t current = 0; - while (current < layer_perimeter_points.size()) { - //NOTE: pick seam point function also resets the m_nearby_seam_points count on all passed points - pick_seam_point(layer_perimeter_points, current, DefaultSeamComparator { }); - current = layer_perimeter_points[current].perimeter->end_index + 1; - } + << "SeamPlacer: pick_seam_point : start"; + //pick seam point + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + size_t current = 0; + while (current < layer_perimeter_points.size()) { + pick_seam_point(layer_perimeter_points, current, DefaultSeamComparator { }); + current = layer_perimeter_points[current].perimeter->end_index + 1; } - }); - } + } + }); BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: distribute_seam_positions_for_alignment, pick_seams : end"; + << "SeamPlacer: pick_seam_point : end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: align_seam_points : start"; + align_seam_points(po, DefaultSeamComparator { }); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: align_seam_points : end"; + } } @@ -683,36 +851,4 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern loop.split_at(seam_point, true); } -// Disabled debug code, can be used to export debug data into obj files (e.g. point cloud of visibility hits) -#if 0 - #include - Slic3r::CNumericLocalesSetter locales_setter; - FILE *fp = boost::nowide::fopen("perimeters.obj", "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << "perimeters.obj" << " for writing"; - } - - for (size_t i = 0; i < perimeter_points.size(); ++i) - fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].position[0], perimeter_points[i].position[1], - perimeter_points[i].position[2], perimeter_points[i].visibility); - fclose(fp); -#endif - -#if 0 - its_write_obj(triangles, "triangles.obj"); - - Slic3r::CNumericLocalesSetter locales_setter; - FILE *fp = boost::nowide::fopen("hits.obj", "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << "hits.obj" << " for writing"; - } - - for (size_t i = 0; i < hit_points.size(); ++i) - fprintf(fp, "v %f %f %f \n", hit_points[i].position[0], hit_points[i].position[1], - hit_points[i].position[2]); - fclose(fp); - #endif - } // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index a6a3f7328..b02142016 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -38,18 +38,23 @@ struct Perimeter { size_t start_index; size_t end_index; size_t seam_index; + + bool aligned = false; + Vec3f final_seam_position; }; struct SeamCandidate { - SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, float ccw_angle, + SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type) : - position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), ccw_angle(ccw_angle), type(type) { + position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), higher_layer_overhang(0.0f), local_ccw_angle( + local_ccw_angle), type(type) { } const Vec3f position; const std::shared_ptr perimeter; float visibility; float overhang; - float ccw_angle; + float higher_layer_overhang; // represents how much is the position covered by the upper layer, useful for local visibility + float local_ccw_angle; EnforcedBlockedSeamPoint type; }; @@ -88,12 +93,12 @@ public: static constexpr float cosine_hemisphere_sampling_power = 6.0f; - static constexpr float polygon_angles_arm_distance = 0.6f; + static constexpr float polygon_local_angles_arm_distance = 0.6f; - static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4f; - static constexpr size_t seam_align_iterations = 1; - static constexpr size_t seam_align_layer_dist = 30; - static constexpr float seam_align_tolerable_dist = 0.3f; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2f; + + static constexpr float seam_align_tolerable_dist = 1.5f; + static constexpr size_t seam_align_tolerable_skips = 4; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; std::unordered_map>> m_perimeter_points_trees_per_object; @@ -107,7 +112,13 @@ private: void calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_overhangs(const PrintObject *po); - void distribute_seam_positions_for_alignment(const PrintObject *po); + template + void align_seam_points(const PrintObject *po, const Comparator &comparator); + template + bool find_next_seam_in_string(const PrintObject *po, const Vec3f &last_point_pos, + size_t layer_idx, const Comparator &comparator, + std::vector> &seam_strings, + std::vector> &outliers); }; } // namespace Slic3r From ffc7452d9e12cb0cd34cc62c71bf1754adb7a5e1 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 25 Feb 2022 16:40:28 +0100 Subject: [PATCH 19/71] improved visibility calculation - it now considers normals and accordingly counts only hits which have similar normal --- src/libslic3r/GCode/SeamPlacerNG.cpp | 201 +++++++++++++++------------ src/libslic3r/GCode/SeamPlacerNG.hpp | 19 ++- 2 files changed, 123 insertions(+), 97 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 739fcf963..fe44faa33 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -148,7 +148,7 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; Vec3f hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v); - Vec3f surface_normal = edge1.cross(edge2).normalized(); + Vec3f surface_normal = its_face_normal(triangles, hitpoint.id); init.push_back(HitInfo { hit_pos, surface_normal }); } @@ -169,12 +169,12 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) { - if (polygon.size() == 1) { - return {0.0f}; - } - std::vector result(polygon.size()); + if (polygon.size() == 1) { + result[0] = 0.0f; + } + auto make_idx_circular = [&](int index) { while (index < 0) { index += polygon.size(); @@ -254,13 +254,24 @@ struct GlobalModelInfo { } float calculate_point_visibility(const Vec3f &position) const { + size_t closest_point_index = find_closest_point(raycast_hits_tree, position); + if (closest_point_index == raycast_hits_tree.npos + || + (position - geometry_raycast_hits[closest_point_index].position).norm() + > SeamPlacer::seam_align_tolerable_dist) { + return 0; + } auto nearby_points = find_nearby_points(raycast_hits_tree, position, SeamPlacer::considered_area_radius); + Vec3f local_normal = geometry_raycast_hits[closest_point_index].surface_normal; + float visibility = 0; for (const auto &hit_point_index : nearby_points) { + // The further away from the perimeter point, + // the less representative ray hit is float distance = (position - geometry_raycast_hits[hit_point_index].position).norm(); - visibility += SeamPlacer::considered_area_radius - distance; // The further away from the perimeter point, - // the less representative ray hit is + visibility += (SeamPlacer::considered_area_radius - distance) * + std::max(0.0f, local_normal.dot(geometry_raycast_hits[hit_point_index].surface_normal)); } return visibility; @@ -288,7 +299,8 @@ struct GlobalModelInfo { for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { float visibility = calculate_point_visibility(divided_mesh.vertices[i]); - float normalized = visibility / SeamPlacer::expected_hits_per_area / 2.0; + float normalized = visibility + / (SeamPlacer::expected_hits_per_area * SeamPlacer::considered_area_radius); Vec3f color = vis_to_rgb(normalized); fprintf(fp, "v %f %f %f %f %f %f\n", divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), @@ -308,9 +320,13 @@ struct GlobalModelInfo { << "Couldn't open " << fname << " for writing"; } - for (size_t i = 0; i < geometry_raycast_hits.size(); ++i) - fprintf(fp, "v %f %f %f \n", geometry_raycast_hits[i].position[0], geometry_raycast_hits[i].position[1], - geometry_raycast_hits[i].position[2]); + for (size_t i = 0; i < geometry_raycast_hits.size(); ++i) { + Vec3f surface_normal = (geometry_raycast_hits[i].surface_normal + Vec3f(1.0, 1.0, 1.0)) / 2.0; + fprintf(fp, "v %f %f %f %f %f %f \n", geometry_raycast_hits[i].position[0], + geometry_raycast_hits[i].position[1], + geometry_raycast_hits[i].position[2], surface_normal[0], surface_normal[1], + surface_normal[2]); + } fclose(fp); } } @@ -496,11 +512,11 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { } struct DefaultSeamComparator { - static constexpr float angle_clusters[] { -1.0, 0.4 * PI, 0.6 + static constexpr float angle_clusters[] { -1.0, 0.4 * PI, 0.5 * PI, 0.6 * PI, 0.7 * PI, 0.8 * PI, 0.9 * PI }; const float get_angle_category(float ccw_angle) const { - float concave_bonus = ccw_angle < 0 ? 0.1 : 0; + float concave_bonus = ccw_angle < 0 ? 0.1 * PI : 0; float abs_angle = abs(ccw_angle) + concave_bonus; auto category = std::find_if_not(std::begin(angle_clusters), std::end(angle_clusters), [&](float category_limit) { @@ -554,7 +570,7 @@ struct DefaultSeamComparator { } { //local angles - float a_local_category = get_angle_category(a.local_ccw_angle) + 0.1 * PI; //give a slight bonus + float a_local_category = get_angle_category(a.local_ccw_angle) + 0.2 * PI; //give a slight bonus float b_local_category = get_angle_category(b.local_ccw_angle); if (a_local_category > b_local_category) { @@ -565,7 +581,7 @@ struct DefaultSeamComparator { } } - return a.visibility < b.visibility * 1.3; + return a.visibility < b.visibility * 1.5; } } ; @@ -573,86 +589,86 @@ struct DefaultSeamComparator { } // namespace SeamPlacerImpl void SeamPlacer::gather_seam_candidates(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { -using namespace SeamPlacerImpl; + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { + using namespace SeamPlacerImpl; -m_perimeter_points_per_object.emplace(po, po->layer_count()); -m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); + m_perimeter_points_per_object.emplace(po, po->layer_count()); + m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); -tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; - const Layer *layer = po->get_layer(layer_idx); - auto unscaled_z = layer->slice_z; - Polygons polygons = extract_perimeter_polygons(layer); - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - global_model_info); + tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + const Layer *layer = po->get_layer(layer_idx); + auto unscaled_z = layer->slice_z; + Polygons polygons = extract_perimeter_polygons(layer); + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + global_model_info); + } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( + functor, layer_candidates.size()); } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( - functor, layer_candidates.size()); } - } -); + ); } void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { -using namespace SeamPlacerImpl; + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { + using namespace SeamPlacerImpl; -tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.visibility = global_model_info.calculate_point_visibility( - perimeter_point.position); + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + perimeter_point.visibility = global_model_info.calculate_point_visibility( + perimeter_point.position); + } } - } - }); + }); } void SeamPlacer::calculate_overhangs(const PrintObject *po) { -using namespace SeamPlacerImpl; + using namespace SeamPlacerImpl; -tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - const auto calculate_layer_overhang = [&](size_t other_layer_idx) { - size_t closest_supporter = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - perimeter_point.position); - const SeamCandidate &supporter_point = - m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + const auto calculate_layer_overhang = [&](size_t other_layer_idx) { + size_t closest_supporter = find_closest_point( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + perimeter_point.position); + const SeamCandidate &supporter_point = + m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; - auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); - const SeamCandidate &prev_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; - const SeamCandidate &next_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; + auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); + const SeamCandidate &prev_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; + const SeamCandidate &next_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; - return calculate_overhang(perimeter_point, prev_point, - supporter_point, next_point); - }; + return calculate_overhang(perimeter_point, prev_point, + supporter_point, next_point); + }; - if (layer_idx > 0) { //calculate overhang - perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); - } - if (layer_idx < m_perimeter_points_per_object[po].size() - 1) { //calculate higher_layer_overhang - perimeter_point.higher_layer_overhang = calculate_layer_overhang(layer_idx+1); + if (layer_idx > 0) { //calculate overhang + perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); + } + if (layer_idx < m_perimeter_points_per_object[po].size() - 1) { //calculate higher_layer_overhang + perimeter_point.higher_layer_overhang = calculate_layer_overhang(layer_idx+1); + } } } - } - }); - } + }); + } // sadly cannot be const because map access operator[] is not const, since it can create new object template -bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, const Vec3f &last_point_pos, +bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, Vec3f &last_point_pos, size_t layer_idx, const Comparator &comparator, - std::vector> &seam_strings, + std::vector> &seam_string, std::vector> &potential_string_seams) { using namespace SeamPlacerImpl; @@ -674,13 +690,15 @@ bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, const Vec3f &la if ((next_layer_seam.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist) { //seam point is within limits, put in the close_by_points list - seam_strings.emplace_back(layer_idx, closest_point.perimeter->seam_index); + seam_string.emplace_back(layer_idx, closest_point.perimeter->seam_index); + last_point_pos = next_layer_seam.position; return true; } else if ((closest_point.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist && comparator.is_first_not_much_worse(closest_point, next_layer_seam)) { //seam point is far, but if the close point is not much worse, do not count it as a skip and add it to potential_string_seams potential_string_seams.emplace_back(layer_idx, closest_point_index); + last_point_pos = closest_point.position; return true; } else { return false; @@ -702,39 +720,38 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp //skip } else { int skips = SeamPlacer::seam_align_tolerable_skips; - int next_layer = layer_idx - 1; + int next_layer = layer_idx + 1; Vec3f last_point_pos = layer_perimeter_points[current_point_index].position; std::vector> seam_string; std::vector> potential_string_seams; //find close by points and outliers; there is a budget of skips allowed - // search from bottom up in z dir. Heuristics which avoids smooth top surfaces (like head) where the seam is not well defined - while (skips >= 0 && next_layer >= 0) { + while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, potential_string_seams)) { - last_point_pos = - m_perimeter_points_per_object[po][seam_string.back().first][seam_string.back().second].position; + //String added, last_point_pos updated, nothing to be done } else { + // Layer skipped, reduce number of available skips skips--; } - next_layer--; + next_layer++; } - if (seam_string.size() > 4) { //string long enough to be worth aligning + if (seam_string.size() >= seam_align_minimum_string_seams) { //string long enough to be worth aligning //do additional check in back direction - next_layer = layer_idx; + next_layer = layer_idx - 1; skips = SeamPlacer::seam_align_tolerable_skips; - while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { - if (find_next_seam_in_string(po, last_point_pos, next_layer, DefaultSeamComparator { }, + while (skips >= 0 && next_layer >= 0) { + if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, potential_string_seams)) { - last_point_pos = - m_perimeter_points_per_object[po][seam_string.back().first][seam_string.back().second].position; + //String added, last_point_pos updated, nothing to be done } else { + // Layer skipped, reduce number of available skips skips--; } - next_layer++; + next_layer--; } // all string seams and potential string seams gathered, now do the alignment @@ -746,7 +763,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp //https://en.wikipedia.org/wiki/Exponential_smoothing //inititalization - float smoothing_factor = 0.5; + float smoothing_factor = SeamPlacer::seam_align_strength; std::pair init = seam_string[0]; Vec2f prev_pos_xy = m_perimeter_points_per_object[po][init.first][init.second].position.head<2>(); for (const auto &pair : seam_string) { @@ -827,6 +844,7 @@ void SeamPlacer::init(const Print &print) { } void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first) { + using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); //NOTE this is necessary, since layer->id() is quite unreliable size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); @@ -840,8 +858,11 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern Vec2f unscaled_p = unscale(fp).cast(); size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); - size_t perimeter_seam_index = perimeter_points[closest_perimeter_point_index].perimeter->seam_index; - Vec3f seam_position = perimeter_points[perimeter_seam_index].position; + const Perimeter *perimeter = perimeter_points[closest_perimeter_point_index].perimeter.get(); + Vec3f seam_position = perimeter_points[perimeter->seam_index].position; + if (perimeter->aligned) { + seam_position = perimeter->final_seam_position; + } Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index b02142016..26f9fa6f0 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -44,9 +44,11 @@ struct Perimeter { }; struct SeamCandidate { - SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, float local_ccw_angle, + SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, + float local_ccw_angle, EnforcedBlockedSeamPoint type) : - position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), higher_layer_overhang(0.0f), local_ccw_angle( + position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), higher_layer_overhang( + 0.0f), local_ccw_angle( local_ccw_angle), type(type) { } const Vec3f position; @@ -88,17 +90,20 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float expected_hits_per_area = 200.0f; - static constexpr float considered_area_radius = 2.0f; + static constexpr float expected_hits_per_area = 800.0f; + static constexpr float considered_area_radius = 5.0f; - static constexpr float cosine_hemisphere_sampling_power = 6.0f; + static constexpr float cosine_hemisphere_sampling_power = 4.0f; static constexpr float polygon_local_angles_arm_distance = 0.6f; static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2f; - static constexpr float seam_align_tolerable_dist = 1.5f; + static constexpr float seam_align_strength = 1.0f; + static constexpr float seam_align_tolerable_dist = 1.0f; static constexpr size_t seam_align_tolerable_skips = 4; + static constexpr size_t seam_align_minimum_string_seams = 2; + //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; std::unordered_map>> m_perimeter_points_trees_per_object; @@ -115,7 +120,7 @@ private: template void align_seam_points(const PrintObject *po, const Comparator &comparator); template - bool find_next_seam_in_string(const PrintObject *po, const Vec3f &last_point_pos, + bool find_next_seam_in_string(const PrintObject *po, Vec3f &last_point_pos, size_t layer_idx, const Comparator &comparator, std::vector> &seam_strings, std::vector> &outliers); From f018160e72dc3724be90c99c3c13fd1d2ea7a02f Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 28 Feb 2022 14:17:52 +0100 Subject: [PATCH 20/71] implemented polynomial alignment, however, initital seam placement is not ideal - hard to balance visual cues and angle information --- src/libslic3r/GCode/SeamPlacerNG.cpp | 287 ++++++++++++++++++++------- src/libslic3r/GCode/SeamPlacerNG.hpp | 13 +- 2 files changed, 216 insertions(+), 84 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index fe44faa33..f7fd80cf1 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -7,6 +7,10 @@ #include #include +//For polynomial fitting +#include +#include + #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/BoundingBox.hpp" @@ -26,6 +30,75 @@ namespace Slic3r { namespace SeamPlacerImpl { +//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 +// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y +std::vector polyfit(const std::vector &points, size_t order) { + Eigen::VectorXf V0(points.size()); + Eigen::VectorXf V1(points.size()); + Eigen::VectorXf V2(points.size()); + for (size_t index = 0; index < points.size(); index++) { + V0(index) = points[index].x(); + V1(index) = points[index].y(); + V2(index) = points[index].z(); + } + + // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial + Eigen::MatrixXf T(points.size(), order + 1); + // check to make sure inputs are correct + assert(points.size() >= order + 1); + // Populate the matrix + for (size_t i = 0; i < points.size(); ++i) + { + for (size_t j = 0; j < order + 1; ++j) + { + T(i, j) = pow(V2(i), j); + } + } + + // Solve for linear least square fit + const auto QR = T.householderQr(); + Eigen::VectorXf result0 = QR.solve(V0); + Eigen::VectorXf result1 = QR.solve(V1); + std::vector coeff { order + 1 }; + for (size_t k = 0; k < order + 1; k++) { + coeff[k] = Vec2f { result0[k], result1[k] }; + } + return coeff; +} + +Vec3f get_fitted_point(const std::vector &coefficients, float z) { + size_t order = coefficients.size() - 1; + float fitted_x = 0; + float fitted_y = 0; + for (size_t index = 0; index < order + 1; ++index) { + float z_pow = pow(z, index); + fitted_x += coefficients[index].x() * z_pow; + fitted_y += coefficients[index].y() * z_pow; + } + + return Vec3f { fitted_x, fitted_y, z }; +} + +// simple linear interpolation between two points +void lerp(Vec3f &dest, const Vec3f &a, const Vec3f &b, const float t) + { + dest.x() = a.x() + (b.x() - a.x()) * t; + dest.y() = a.y() + (b.y() - a.y()) * t; +} + +// evaluate a point on a bezier-curve. t goes from 0 to 1.0 +Vec3f bezier(const Vec3f &a, const Vec3f &b, const Vec3f &c, const Vec3f &d, const float t) + { + Vec3f ab, bc, cd, abbc, bccd, dest; + lerp(ab, a, b, t); // point between a and b (green) + lerp(bc, b, c, t); // point between b and c (green) + lerp(cd, c, d, t); // point between c and d (green) + lerp(abbc, ab, bc, t); // point between ab and bc (blue) + lerp(bccd, bc, cd, t); // point between bc and cd (blue) + lerp(dest, abbc, bccd, t); // point on the bezier-curve (black) + return dest; +} + /// Coordinate frame class Frame { public: @@ -512,8 +585,7 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { } struct DefaultSeamComparator { - static constexpr float angle_clusters[] { -1.0, 0.4 * PI, 0.5 * PI, 0.6 - * PI, 0.7 * PI, 0.8 * PI, 0.9 * PI }; + static constexpr float angle_clusters[] { -1.0, 0.6 * PI, 0.9 * PI }; const float get_angle_category(float ccw_angle) const { float concave_bonus = ccw_angle < 0 ? 0.1 * PI : 0; @@ -570,18 +642,15 @@ struct DefaultSeamComparator { } { //local angles - float a_local_category = get_angle_category(a.local_ccw_angle) + 0.2 * PI; //give a slight bonus + float a_local_category = get_angle_category(a.local_ccw_angle); float b_local_category = get_angle_category(b.local_ccw_angle); - if (a_local_category > b_local_category) { - return true; - } if (a_local_category < b_local_category) { return false; } } - return a.visibility < b.visibility * 1.5; + return a.visibility <= b.visibility*1.5; } } ; @@ -589,80 +658,80 @@ struct DefaultSeamComparator { } // namespace SeamPlacerImpl void SeamPlacer::gather_seam_candidates(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { - using namespace SeamPlacerImpl; + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { +using namespace SeamPlacerImpl; - m_perimeter_points_per_object.emplace(po, po->layer_count()); - m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); +m_perimeter_points_per_object.emplace(po, po->layer_count()); +m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); - tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; - const Layer *layer = po->get_layer(layer_idx); - auto unscaled_z = layer->slice_z; - Polygons polygons = extract_perimeter_polygons(layer); - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - global_model_info); - } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( - functor, layer_candidates.size()); +tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + const Layer *layer = po->get_layer(layer_idx); + auto unscaled_z = layer->slice_z; + Polygons polygons = extract_perimeter_polygons(layer); + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + global_model_info); } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( + functor, layer_candidates.size()); } - ); + } +); } void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { - using namespace SeamPlacerImpl; + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { +using namespace SeamPlacerImpl; - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.visibility = global_model_info.calculate_point_visibility( - perimeter_point.position); - } +tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + perimeter_point.visibility = global_model_info.calculate_point_visibility( + perimeter_point.position); } - }); + } + }); } void SeamPlacer::calculate_overhangs(const PrintObject *po) { - using namespace SeamPlacerImpl; +using namespace SeamPlacerImpl; - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - const auto calculate_layer_overhang = [&](size_t other_layer_idx) { - size_t closest_supporter = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - perimeter_point.position); - const SeamCandidate &supporter_point = - m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; +tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + const auto calculate_layer_overhang = [&](size_t other_layer_idx) { + size_t closest_supporter = find_closest_point( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + perimeter_point.position); + const SeamCandidate &supporter_point = + m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; - auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); - const SeamCandidate &prev_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; - const SeamCandidate &next_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; + auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); + const SeamCandidate &prev_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; + const SeamCandidate &next_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; - return calculate_overhang(perimeter_point, prev_point, - supporter_point, next_point); - }; + return calculate_overhang(perimeter_point, prev_point, + supporter_point, next_point); + }; - if (layer_idx > 0) { //calculate overhang - perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); - } - if (layer_idx < m_perimeter_points_per_object[po].size() - 1) { //calculate higher_layer_overhang - perimeter_point.higher_layer_overhang = calculate_layer_overhang(layer_idx+1); - } + if (layer_idx > 0) { //calculate overhang + perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); + } + if (layer_idx < m_perimeter_points_per_object[po].size() - 1) { //calculate higher_layer_overhang + perimeter_point.higher_layer_overhang = calculate_layer_overhang(layer_idx+1); } } - }); - } + } + }); + } // sadly cannot be const because map access operator[] is not const, since it can create new object template @@ -711,6 +780,24 @@ template void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comparator) { using namespace SeamPlacerImpl; +#ifdef DEBUG_FILES + Slic3r::CNumericLocalesSetter locales_setter; + auto clusters_f = "seam_clusters_of_" + std::to_string(po->id().id) + ".obj"; + FILE *clusters = boost::nowide::fopen(clusters_f.c_str(), "w"); + if (clusters == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; + return; + } + auto aligned_f = "aligned_clusters_of_" + std::to_string(po->id().id) + ".obj"; + FILE *aligns = boost::nowide::fopen(aligned_f.c_str(), "w"); + if (aligns == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; + return; + } +#endif + for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { std::vector &layer_perimeter_points = m_perimeter_points_per_object[po][layer_idx]; @@ -738,7 +825,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp next_layer++; } - if (seam_string.size() >= seam_align_minimum_string_seams) { //string long enough to be worth aligning + if (seam_string.size() + potential_string_seams.size() >= seam_align_minimum_string_seams) { //string long enough to be worth aligning //do additional check in back direction next_layer = layer_idx - 1; skips = SeamPlacer::seam_align_tolerable_skips; @@ -761,31 +848,77 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp return left.first < right.first; }); - //https://en.wikipedia.org/wiki/Exponential_smoothing - //inititalization - float smoothing_factor = SeamPlacer::seam_align_strength; - std::pair init = seam_string[0]; - Vec2f prev_pos_xy = m_perimeter_points_per_object[po][init.first][init.second].position.head<2>(); + std::vector points(seam_string.size()); + for (size_t index = 0; index < seam_string.size(); ++index) { + points[index] = + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; + } + + std::vector coefficients = polyfit(points, 3); for (const auto &pair : seam_string) { - Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; - float current_height = current_pos.z(); - Vec2f current_pos_xy = current_pos.head<2>(); - current_pos_xy = smoothing_factor * prev_pos_xy + (1.0 - smoothing_factor) * current_pos_xy; + float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); + Vec3f seam_pos = get_fitted_point(coefficients, current_height); Perimeter *perimeter = m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); - perimeter->final_seam_position = - Vec3f { current_pos_xy.x(), current_pos_xy.y(), current_height }; + perimeter->final_seam_position = seam_pos; perimeter->aligned = true; - prev_pos_xy = current_pos_xy; } +// //https://en.wikipedia.org/wiki/Exponential_smoothing +// //inititalization +// float smoothing_factor = SeamPlacer::seam_align_strength; +// std::pair init = seam_string[0]; +// Vec2f prev_pos_xy = m_perimeter_points_per_object[po][init.first][init.second].position.head<2>(); +// for (const auto &pair : seam_string) { +// Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; +// float current_height = current_pos.z(); +// Vec2f current_pos_xy = current_pos.head<2>(); +// current_pos_xy = smoothing_factor * prev_pos_xy + (1.0 - smoothing_factor) * current_pos_xy; +// +// Perimeter *perimeter = +// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); +// perimeter->final_seam_position = +// Vec3f { current_pos_xy.x(), current_pos_xy.y(), current_height }; +// perimeter->aligned = true; +// prev_pos_xy = current_pos_xy; +// } + +#ifdef DEBUG_FILES + auto randf = []() { + return float(rand()) / float(RAND_MAX); + }; + Vec3f color { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; + fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], + orig_seam.position[1], + orig_seam.position[2], color[0], color[1], + color[2]); + } + + color = Vec3f { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + Perimeter *perimeter = + m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); + fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], + perimeter->final_seam_position[1], + perimeter->final_seam_position[2], color[0], color[1], + color[2]); + } +#endif + } // else string is not long enough, so dont do anything } current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1; } } +#ifdef DEBUG_FILES + fclose(clusters); + fclose(aligns); +#endif + } void SeamPlacer::init(const Print &print) { @@ -846,7 +979,7 @@ void SeamPlacer::init(const Print &print) { void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first) { using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); - //NOTE this is necessary, since layer->id() is quite unreliable +//NOTE this is necessary, since layer->id() is quite unreliable size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); double unscaled_z = layer->slice_z; diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 26f9fa6f0..c72b9bf88 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -90,19 +90,18 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float expected_hits_per_area = 800.0f; - static constexpr float considered_area_radius = 5.0f; + static constexpr float expected_hits_per_area = 400.0f; + static constexpr float considered_area_radius = 1.6f; - static constexpr float cosine_hemisphere_sampling_power = 4.0f; + static constexpr float cosine_hemisphere_sampling_power = 8.0f; static constexpr float polygon_local_angles_arm_distance = 0.6f; static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2f; - static constexpr float seam_align_strength = 1.0f; - static constexpr float seam_align_tolerable_dist = 1.0f; - static constexpr size_t seam_align_tolerable_skips = 4; - static constexpr size_t seam_align_minimum_string_seams = 2; + static constexpr float seam_align_tolerable_dist = 2.0f; + static constexpr size_t seam_align_tolerable_skips = 10; + static constexpr size_t seam_align_minimum_string_seams = 4; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; From 105b67c9a79c630fdc8125d3b2d3a698447722f9 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 1 Mar 2022 15:39:02 +0100 Subject: [PATCH 21/71] presorting seams before alignemnt mesh decimation for speed up --- src/libslic3r/GCode/SeamPlacerNG.cpp | 225 +++++++++++++++------------ src/libslic3r/GCode/SeamPlacerNG.hpp | 15 +- 2 files changed, 130 insertions(+), 110 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index f7fd80cf1..5b2a743e3 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -18,6 +18,7 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/SVG.hpp" #include "libslic3r/Layer.hpp" +#include "libslic3r/QuadricEdgeCollapse.hpp" #define DEBUG_FILES @@ -79,26 +80,6 @@ Vec3f get_fitted_point(const std::vector &coefficients, float z) { return Vec3f { fitted_x, fitted_y, z }; } -// simple linear interpolation between two points -void lerp(Vec3f &dest, const Vec3f &a, const Vec3f &b, const float t) - { - dest.x() = a.x() + (b.x() - a.x()) * t; - dest.y() = a.y() + (b.y() - a.y()) * t; -} - -// evaluate a point on a bezier-curve. t goes from 0 to 1.0 -Vec3f bezier(const Vec3f &a, const Vec3f &b, const Vec3f &c, const Vec3f &d, const float t) - { - Vec3f ab, bc, cd, abbc, bccd, dest; - lerp(ab, a, b, t); // point between a and b (green) - lerp(bc, b, c, t); // point between b and c (green) - lerp(cd, c, d, t); // point between c and d (green) - lerp(abbc, ab, bc, t); // point between ab and bc (blue) - lerp(bccd, bc, cd, t); // point between bc and cd (blue) - lerp(dest, abbc, bccd, t); // point on the bezier-curve (black) - return dest; -} - /// Coordinate frame class Frame { public: @@ -188,7 +169,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & generate(begin(global_dir_random_samples), end(global_dir_random_samples), gen); std::vector local_dir_random_samples(ray_count); generate(begin(local_dir_random_samples), end(local_dir_random_samples), gen); - BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: generate random samples: end"; @@ -339,12 +319,13 @@ struct GlobalModelInfo { float visibility = 0; for (const auto &hit_point_index : nearby_points) { - // The further away from the perimeter point, - // the less representative ray hit is - float distance = - (position - geometry_raycast_hits[hit_point_index].position).norm(); - visibility += (SeamPlacer::considered_area_radius - distance) * - std::max(0.0f, local_normal.dot(geometry_raycast_hits[hit_point_index].surface_normal)); + if (local_normal.dot(geometry_raycast_hits[hit_point_index].surface_normal) > 0) { + // The further away from the perimeter point, + // the less representative ray hit is + float distance = + (position - geometry_raycast_hits[hit_point_index].position).norm(); + visibility += (SeamPlacer::considered_area_radius - distance); + } } return visibility; @@ -373,7 +354,7 @@ struct GlobalModelInfo { for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { float visibility = calculate_point_visibility(divided_mesh.vertices[i]); float normalized = visibility - / (SeamPlacer::expected_hits_per_area * SeamPlacer::considered_area_radius); + / (SeamPlacer::expected_hits_per_area * PI * SeamPlacer::considered_area_radius); Vec3f color = vis_to_rgb(normalized); fprintf(fp, "v %f %f %f %f %f %f\n", divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), @@ -531,8 +512,20 @@ template void pick_seam_point(std::vector &perimeter_points, size_t start_index, const Comparator &comparator) { size_t end_index = perimeter_points[start_index].perimeter->end_index; - size_t seam_index = start_index; - for (size_t index = start_index + 1; index <= end_index; ++index) { + + std::vector indices(end_index + 1 - start_index); + for (size_t index = start_index; index <= end_index; ++index) { + indices[index - start_index] = index; + } + + std::sort(indices.begin(), indices.end(), + [&](size_t left, size_t right) { + return std::abs(perimeter_points[left].local_ccw_angle - 0.1 * PI) + > std::abs(perimeter_points[right].local_ccw_angle - 0.1 * PI); + }); + + size_t seam_index = indices[0]; + for (size_t index : indices) { if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { seam_index = index; } @@ -548,6 +541,8 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { auto obj_transform = po->trafo_centered(); auto triangle_set = po->model_object()->raw_indexed_triangle_set(); its_transform(triangle_set, obj_transform); + float target_error = SeamPlacer::raycasting_decimation_target_error; + its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, triangle_set.indices); @@ -585,7 +580,7 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { } struct DefaultSeamComparator { - static constexpr float angle_clusters[] { -1.0, 0.6 * PI, 0.9 * PI }; + static constexpr float angle_clusters[] { -1.0, 0.4 * PI, 0.65 * PI, 0.9 * PI }; const float get_angle_category(float ccw_angle) const { float concave_bonus = ccw_angle < 0 ? 0.1 * PI : 0; @@ -625,6 +620,7 @@ struct DefaultSeamComparator { } return a.visibility < b.visibility; + } bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { @@ -645,12 +641,16 @@ struct DefaultSeamComparator { float a_local_category = get_angle_category(a.local_ccw_angle); float b_local_category = get_angle_category(b.local_ccw_angle); + if (a_local_category > b_local_category) { + return true; + } if (a_local_category < b_local_category) { return false; } } - return a.visibility <= b.visibility*1.5; + return (a.visibility <= b.visibility + || (std::abs(a.visibility - b.visibility) < SeamPlacer::expected_hits_per_area / 17.0f)); } } ; @@ -798,72 +798,94 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } #endif + //gahter vector of all seams - pair of layer_index and seam__index within that layer + std::vector> seams; for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { std::vector &layer_perimeter_points = m_perimeter_points_per_object[po][layer_idx]; size_t current_point_index = 0; while (current_point_index < layer_perimeter_points.size()) { - if (layer_perimeter_points[current_point_index].perimeter->aligned) { - //skip - } else { - int skips = SeamPlacer::seam_align_tolerable_skips; - int next_layer = layer_idx + 1; - Vec3f last_point_pos = layer_perimeter_points[current_point_index].position; + seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter->seam_index); + current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1; + } + } - std::vector> seam_string; - std::vector> potential_string_seams; + //sort them by visiblity, before aligning + std::sort(seams.begin(), seams.end(), + [&](const std::pair &left, const std::pair &right) { + return m_perimeter_points_per_object[po][left.first][left.second].visibility + < m_perimeter_points_per_object[po][right.first][right.second].visibility; + } + ); - //find close by points and outliers; there is a budget of skips allowed - while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { - if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, + //align them + for (const std::pair &seam : seams) { + size_t layer_idx = seam.first; + size_t seam_index = seam.second; + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + if (layer_perimeter_points[seam_index].perimeter->aligned) { + // This perimeter is already aligned, skip seam + continue; + } else { + int skips = SeamPlacer::seam_align_tolerable_skips; + int next_layer = layer_idx + 1; + Vec3f last_point_pos = layer_perimeter_points[seam_index].position; + + std::vector> seam_string; + std::vector> potential_string_seams; + + //find close by points and outliers; there is a budget of skips allowed + while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { + if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, + potential_string_seams)) { + //String added, last_point_pos updated, nothing to be done + } else { + // Layer skipped, reduce number of available skips + skips--; + } + next_layer++; + } + + if (seam_string.size() + potential_string_seams.size() >= seam_align_minimum_string_seams) { //string long enough to be worth aligning + //do additional check in back direction + next_layer = layer_idx - 1; + skips = SeamPlacer::seam_align_tolerable_skips; + while (skips >= 0 && next_layer >= 0) { + if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, + seam_string, potential_string_seams)) { //String added, last_point_pos updated, nothing to be done } else { // Layer skipped, reduce number of available skips skips--; } - next_layer++; + next_layer--; } - if (seam_string.size() + potential_string_seams.size() >= seam_align_minimum_string_seams) { //string long enough to be worth aligning - //do additional check in back direction - next_layer = layer_idx - 1; - skips = SeamPlacer::seam_align_tolerable_skips; - while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, - seam_string, - potential_string_seams)) { - //String added, last_point_pos updated, nothing to be done - } else { - // Layer skipped, reduce number of available skips - skips--; - } - next_layer--; - } + // all string seams and potential string seams gathered, now do the alignment + seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); + std::sort(seam_string.begin(), seam_string.end(), + [](const std::pair &left, const std::pair &right) { + return left.first < right.first; + }); - // all string seams and potential string seams gathered, now do the alignment - seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); - std::sort(seam_string.begin(), seam_string.end(), - [](const std::pair &left, const std::pair &right) { - return left.first < right.first; - }); + std::vector points(seam_string.size()); + for (size_t index = 0; index < seam_string.size(); ++index) { + points[index] = + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; + } - std::vector points(seam_string.size()); - for (size_t index = 0; index < seam_string.size(); ++index) { - points[index] = - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; - } + std::vector coefficients = polyfit(points, 4); + for (const auto &pair : seam_string) { + float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); + Vec3f seam_pos = get_fitted_point(coefficients, current_height); - std::vector coefficients = polyfit(points, 3); - for (const auto &pair : seam_string) { - float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); - Vec3f seam_pos = get_fitted_point(coefficients, current_height); - - Perimeter *perimeter = - m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); - perimeter->final_seam_position = seam_pos; - perimeter->aligned = true; - } + Perimeter *perimeter = + m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); + perimeter->final_seam_position = seam_pos; + perimeter->aligned = true; + } // //https://en.wikipedia.org/wiki/Exponential_smoothing // //inititalization @@ -885,32 +907,29 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp // } #ifdef DEBUG_FILES - auto randf = []() { - return float(rand()) / float(RAND_MAX); - }; - Vec3f color { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; - fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], - orig_seam.position[1], - orig_seam.position[2], color[0], color[1], - color[2]); - } + auto randf = []() { + return float(rand()) / float(RAND_MAX); + }; + Vec3f color { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; + fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], + orig_seam.position[1], + orig_seam.position[2], color[0], color[1], + color[2]); + } - color = Vec3f { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - Perimeter *perimeter = - m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); - fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], - perimeter->final_seam_position[1], - perimeter->final_seam_position[2], color[0], color[1], - color[2]); - } + color = Vec3f { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + Perimeter *perimeter = + m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); + fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], + perimeter->final_seam_position[1], + perimeter->final_seam_position[2], color[0], color[1], + color[2]); + } #endif - - } // else string is not long enough, so dont do anything } - current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1; } } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index c72b9bf88..2f8bba728 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -36,7 +36,7 @@ enum class EnforcedBlockedSeamPoint { struct Perimeter { size_t start_index; - size_t end_index; + size_t end_index; //inclusive! size_t seam_index; bool aligned = false; @@ -90,18 +90,19 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float expected_hits_per_area = 400.0f; - static constexpr float considered_area_radius = 1.6f; + static constexpr float expected_hits_per_area = 600.0f; + static constexpr float considered_area_radius = 3.0f; + static constexpr float raycasting_decimation_target_error = 0.6f; - static constexpr float cosine_hemisphere_sampling_power = 8.0f; + static constexpr float cosine_hemisphere_sampling_power = 4.0f; static constexpr float polygon_local_angles_arm_distance = 0.6f; static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2f; - static constexpr float seam_align_tolerable_dist = 2.0f; - static constexpr size_t seam_align_tolerable_skips = 10; - static constexpr size_t seam_align_minimum_string_seams = 4; + static constexpr float seam_align_tolerable_dist = 1.0f; + static constexpr size_t seam_align_tolerable_skips = 6; + static constexpr size_t seam_align_minimum_string_seams = 5; //perimeter points per object per layer idx, and their corresponding KD trees std::unordered_map>> m_perimeter_points_per_object; From c72687c96c04ffbd7df716324620cc31216cad43 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 1 Mar 2022 17:29:55 +0100 Subject: [PATCH 22/71] computing smooth score instead of binary decision when picking seams some basic documentation --- src/libslic3r/GCode/SeamPlacerNG.cpp | 70 +++++++++------------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 31 ++++++++++-- 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 5b2a743e3..086cb0fdc 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -518,12 +518,6 @@ void pick_seam_point(std::vector &perimeter_points, size_t start_ indices[index - start_index] = index; } - std::sort(indices.begin(), indices.end(), - [&](size_t left, size_t right) { - return std::abs(perimeter_points[left].local_ccw_angle - 0.1 * PI) - > std::abs(perimeter_points[right].local_ccw_angle - 0.1 * PI); - }); - size_t seam_index = indices[0]; for (size_t index : indices) { if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { @@ -580,17 +574,13 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { } struct DefaultSeamComparator { - static constexpr float angle_clusters[] { -1.0, 0.4 * PI, 0.65 * PI, 0.9 * PI }; + float compute_angle_penalty(float ccw_angle) const { + if (ccw_angle >= 0) { + return PI - ccw_angle; + } else { + return (PI - ccw_angle) * 1.1f; + } - const float get_angle_category(float ccw_angle) const { - float concave_bonus = ccw_angle < 0 ? 0.1 * PI : 0; - float abs_angle = abs(ccw_angle) + concave_bonus; - auto category = std::find_if_not(std::begin(angle_clusters), std::end(angle_clusters), - [&](float category_limit) { - return abs_angle > category_limit; - }); - category--; - return *category; } bool is_first_better(const SeamCandidate &a, const SeamCandidate &b) const { @@ -603,24 +593,12 @@ struct DefaultSeamComparator { } //avoid overhangs - if (a.overhang > 0.3f && b.overhang < a.overhang) { + if (a.overhang > 0.2f && b.overhang < a.overhang) { return false; } - { //local angles - float a_local_category = get_angle_category(a.local_ccw_angle); - float b_local_category = get_angle_category(b.local_ccw_angle); - - if (a_local_category > b_local_category) { - return true; - } - if (a_local_category < b_local_category) { - return false; - } - } - - return a.visibility < b.visibility; - + return (a.visibility + 0.01) * compute_angle_penalty(a.local_ccw_angle) < + (b.visibility + 0.01) * compute_angle_penalty(b.local_ccw_angle); } bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { @@ -633,24 +611,12 @@ struct DefaultSeamComparator { } //avoid overhangs - if (a.overhang > 0.3f && b.overhang < a.overhang) { + if (a.overhang > 0.2f && b.overhang < a.overhang) { return false; } - { //local angles - float a_local_category = get_angle_category(a.local_ccw_angle); - float b_local_category = get_angle_category(b.local_ccw_angle); - - if (a_local_category > b_local_category) { - return true; - } - if (a_local_category < b_local_category) { - return false; - } - } - - return (a.visibility <= b.visibility - || (std::abs(a.visibility - b.visibility) < SeamPlacer::expected_hits_per_area / 17.0f)); + return (a.visibility + 0.01) * compute_angle_penalty(a.local_ccw_angle) * 0.8f <= + (b.visibility + 0.01) * compute_angle_penalty(b.local_ccw_angle); } } ; @@ -814,7 +780,15 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp std::sort(seams.begin(), seams.end(), [&](const std::pair &left, const std::pair &right) { return m_perimeter_points_per_object[po][left.first][left.second].visibility - < m_perimeter_points_per_object[po][right.first][right.second].visibility; + * (1.2 * PI + - std::abs( + m_perimeter_points_per_object[po][left.first][left.second].local_ccw_angle + - 0.2 * PI)) + < m_perimeter_points_per_object[po][right.first][right.second].visibility + * (1.2 * PI + - std::abs( + m_perimeter_points_per_object[po][right.first][right.second].local_ccw_angle + - 0.2 * PI)); } ); @@ -889,7 +863,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp // //https://en.wikipedia.org/wiki/Exponential_smoothing // //inititalization -// float smoothing_factor = SeamPlacer::seam_align_strength; +// float smoothing_factor = 0.8; // std::pair init = seam_string[0]; // Vec2f prev_pos_xy = m_perimeter_points_per_object[po][init.first][init.second].position.head<2>(); // for (const auto &pair : seam_string) { diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 2f8bba728..bcd178aed 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -34,32 +34,38 @@ enum class EnforcedBlockedSeamPoint { Enforced = 2, }; +// struct representing single perimeter loop struct Perimeter { size_t start_index; size_t end_index; //inclusive! size_t seam_index; + // During alignment, a final position may be stored here. In that case, aligned is set to true. + // Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position bool aligned = false; Vec3f final_seam_position; }; +//Struct over which all processing of perimeters is done. For each perimeter point, its respective candidate is created, +// then all the needed attributes are computed and finally, for each perimeter one point is chosen as seam. +// This seam position can be than further aligned struct SeamCandidate { SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type) : - position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), higher_layer_overhang( - 0.0f), local_ccw_angle( + position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), local_ccw_angle( local_ccw_angle), type(type) { } const Vec3f position; + // pointer to Perimter loop of this point. It is shared across all points of the loop const std::shared_ptr perimeter; float visibility; float overhang; - float higher_layer_overhang; // represents how much is the position covered by the upper layer, useful for local visibility float local_ccw_angle; EnforcedBlockedSeamPoint type; }; +// struct to represent hits of the mesh during occulision raycasting. struct HitInfo { Vec3f position; Vec3f surface_normal; @@ -90,22 +96,37 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; + // Rough estimates of hits of the mesh during raycasting per surface circle defined by considered_area_radius static constexpr float expected_hits_per_area = 600.0f; + // area considered when computing number of rays and then gathering visiblity info from the hits static constexpr float considered_area_radius = 3.0f; - static constexpr float raycasting_decimation_target_error = 0.6f; + // quadric error limit of quadric decimation function used on the mesh before raycasting + static constexpr float raycasting_decimation_target_error = 0.3f; + // cosine sampling power represents how prefered are forward directions when raycasting from given spot + // in this case, forward direction means towards the center of the mesh static constexpr float cosine_hemisphere_sampling_power = 4.0f; + // arm length used during angles computation static constexpr float polygon_local_angles_arm_distance = 0.6f; + // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocer or Enforcer static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2f; + // When searching for seam clusters for alignment: + // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, + //it belongs automaticaly to the cluster static constexpr float seam_align_tolerable_dist = 1.0f; + // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. + // this param limits the number of allowed skips static constexpr size_t seam_align_tolerable_skips = 6; + // minimum number of seams needed in cluster to make alignemnt happen static constexpr size_t seam_align_minimum_string_seams = 5; - //perimeter points per object per layer idx, and their corresponding KD trees + //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: + // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer std::unordered_map>> m_perimeter_points_per_object; + // Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD tree of all points of the given layer std::unordered_map>> m_perimeter_points_trees_per_object; void init(const Print &print); From ad819850f972fe3e7f2dc3c3ffdcca918953b2da Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 2 Mar 2022 11:58:31 +0100 Subject: [PATCH 23/71] tweaked parameters, fixed minor bugs --- src/libslic3r/GCode/SeamPlacerNG.cpp | 48 ++++++++++++---------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 14 ++++---- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 086cb0fdc..bb31fcd62 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -156,7 +156,7 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & // Prepare random samples per ray // std::random_device rnd_device; // use fixed seed, we can backtrack potential issues easier - std::mt19937 mersenne_engine { 12345 }; + std::mt19937 mersenne_engine { 131 }; std::uniform_real_distribution dist { 0, 1 }; auto gen = [&dist, &mersenne_engine]() { @@ -174,15 +174,19 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> & BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: raycast visibility for " << ray_count << " rays: start"; - // raycast visibility + std::vector hit_points = tbb::parallel_reduce(tbb::blocked_range(0, ray_count), std::vector { }, [&](tbb::blocked_range r, std::vector init) { for (size_t index = r.begin(); index < r.end(); ++index) { + //generate global ray direction Vec3f global_ray_dir = sample_sphere_uniform(global_dir_random_samples[index]); + //place the ray origin on the bounding sphere Vec3f ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); + // compute local ray direction as cosine hemisphere sample - the rays dont aim directly to the middle Vec3f local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], SeamPlacer::cosine_hemisphere_sampling_power); + // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward Frame f; f.set_from_z(global_ray_dir); Vec3f final_ray_dir = (f.to_world(local_dir)); @@ -282,6 +286,7 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, return result; } +// structure to store global information about the model - occlusion hits, enforcers, blockers struct GlobalModelInfo { std::vector geometry_raycast_hits; KDTreeIndirect<3, float, HitInfoCoordinateFunctor> raycast_hits_tree; @@ -311,7 +316,7 @@ struct GlobalModelInfo { if (closest_point_index == raycast_hits_tree.npos || (position - geometry_raycast_hits[closest_point_index].position).norm() - > SeamPlacer::seam_align_tolerable_dist) { + > SeamPlacer::considered_area_radius) { return 0; } auto nearby_points = find_nearby_points(raycast_hits_tree, position, SeamPlacer::considered_area_radius); @@ -354,7 +359,7 @@ struct GlobalModelInfo { for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { float visibility = calculate_point_visibility(divided_mesh.vertices[i]); float normalized = visibility - / (SeamPlacer::expected_hits_per_area * PI * SeamPlacer::considered_area_radius); + / (SeamPlacer::expected_hits_per_area * SeamPlacer::considered_area_radius); Vec3f color = vis_to_rgb(normalized); fprintf(fp, "v %f %f %f %f %f %f\n", divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), @@ -576,9 +581,9 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { struct DefaultSeamComparator { float compute_angle_penalty(float ccw_angle) const { if (ccw_angle >= 0) { - return PI - ccw_angle; + return PI * PI - ccw_angle * ccw_angle; } else { - return (PI - ccw_angle) * 1.1f; + return (PI * PI - ccw_angle * ccw_angle) * 0.8f; } } @@ -593,12 +598,12 @@ struct DefaultSeamComparator { } //avoid overhangs - if (a.overhang > 0.2f && b.overhang < a.overhang) { + if (a.overhang > 0.3f && b.overhang < a.overhang) { return false; } - return (a.visibility + 0.01) * compute_angle_penalty(a.local_ccw_angle) < - (b.visibility + 0.01) * compute_angle_penalty(b.local_ccw_angle); + return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) < + (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); } bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { @@ -611,12 +616,12 @@ struct DefaultSeamComparator { } //avoid overhangs - if (a.overhang > 0.2f && b.overhang < a.overhang) { + if (a.overhang > 0.3f && b.overhang < a.overhang) { return false; } - return (a.visibility + 0.01) * compute_angle_penalty(a.local_ccw_angle) * 0.8f <= - (b.visibility + 0.01) * compute_angle_penalty(b.local_ccw_angle); + return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.8f <= + (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); } } ; @@ -691,9 +696,6 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po if (layer_idx > 0) { //calculate overhang perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); } - if (layer_idx < m_perimeter_points_per_object[po].size() - 1) { //calculate higher_layer_overhang - perimeter_point.higher_layer_overhang = calculate_layer_overhang(layer_idx+1); - } } } }); @@ -776,19 +778,11 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } } - //sort them by visiblity, before aligning + //sort them and then align starting with the best candidates std::sort(seams.begin(), seams.end(), [&](const std::pair &left, const std::pair &right) { - return m_perimeter_points_per_object[po][left.first][left.second].visibility - * (1.2 * PI - - std::abs( - m_perimeter_points_per_object[po][left.first][left.second].local_ccw_angle - - 0.2 * PI)) - < m_perimeter_points_per_object[po][right.first][right.second].visibility - * (1.2 * PI - - std::abs( - m_perimeter_points_per_object[po][right.first][right.second].local_ccw_angle - - 0.2 * PI)); + return comparator.is_first_better(m_perimeter_points_per_object[po][left.first][left.second], + m_perimeter_points_per_object[po][right.first][right.second]); } ); @@ -850,7 +844,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; } - std::vector coefficients = polyfit(points, 4); + std::vector coefficients = polyfit(points, 3); for (const auto &pair : seam_string) { float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); Vec3f seam_pos = get_fitted_point(coefficients, current_height); diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index bcd178aed..ec27938b1 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -97,21 +97,21 @@ public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; // Rough estimates of hits of the mesh during raycasting per surface circle defined by considered_area_radius - static constexpr float expected_hits_per_area = 600.0f; + static constexpr float expected_hits_per_area = 400.0f; // area considered when computing number of rays and then gathering visiblity info from the hits - static constexpr float considered_area_radius = 3.0f; + static constexpr float considered_area_radius = 4.0f; // quadric error limit of quadric decimation function used on the mesh before raycasting - static constexpr float raycasting_decimation_target_error = 0.3f; + static constexpr float raycasting_decimation_target_error = 0.1f; // cosine sampling power represents how prefered are forward directions when raycasting from given spot // in this case, forward direction means towards the center of the mesh static constexpr float cosine_hemisphere_sampling_power = 4.0f; // arm length used during angles computation - static constexpr float polygon_local_angles_arm_distance = 0.6f; + static constexpr float polygon_local_angles_arm_distance = 1.0f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocer or Enforcer - static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2f; + static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.1f; // When searching for seam clusters for alignment: // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, @@ -119,9 +119,9 @@ public: static constexpr float seam_align_tolerable_dist = 1.0f; // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. // this param limits the number of allowed skips - static constexpr size_t seam_align_tolerable_skips = 6; + static constexpr size_t seam_align_tolerable_skips = 4; // minimum number of seams needed in cluster to make alignemnt happen - static constexpr size_t seam_align_minimum_string_seams = 5; + static constexpr size_t seam_align_minimum_string_seams = 4; //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer From 87c276b7a4cf060c21d288fc9dc9d44ec163e011 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 2 Mar 2022 13:24:35 +0100 Subject: [PATCH 24/71] comments and bugfix --- src/libslic3r/GCode/SeamPlacerNG.cpp | 188 +++++++++++++++++---------- src/libslic3r/GCode/SeamPlacerNG.hpp | 2 +- 2 files changed, 119 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index bb31fcd62..fad3911c6 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -394,6 +394,7 @@ struct GlobalModelInfo { } ; +//Extract perimeter polygons of the given layer Polygons extract_perimeter_polygons(const Layer *layer) { Polygons polygons; for (const LayerRegion *layer_region : layer->regions()) { @@ -419,13 +420,17 @@ Polygons extract_perimeter_polygons(const Layer *layer) { } } - if (polygons.empty()) { + if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point + // it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway polygons.emplace_back(std::vector { Point { 0, 0 } }); } return polygons; } +// Insert SeamCandidates created from perimeter polygons in to the result vector. +// Compute its type (Enfrocer,Blocker), angle, and position +//each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, const GlobalModelInfo &global_model_info) { if (orig_polygon.size() == 0) { @@ -467,17 +472,22 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: } } +// Get index of previous and next perimeter point of the layer. Because SeamCandidates of all polygons of the given layer +// are sequentially stored in the vector, each perimeter contains info about start and end index. These vales are used to +// deduce index of previous and next neigbour in the corresponding perimeter. std::pair find_previous_and_next_perimeter_point(const std::vector &perimeter_points, - size_t index) { - const SeamCandidate ¤t = perimeter_points[index]; - int prev = index - 1; - int next = index + 1; + size_t point_index) { + const SeamCandidate ¤t = perimeter_points[point_index]; + int prev = point_index - 1; //for majority of points, it is true that neighbours lie behind and in front of them in the vector + int next = point_index + 1; - if (index == current.perimeter->start_index) { + if (point_index == current.perimeter->start_index) { + // if point_index is equal to start, it means that the previous neighbour is at the end prev = current.perimeter->end_index; } - if (index == current.perimeter->end_index) { + if (point_index == current.perimeter->end_index) { + // if point_index is equal to end, than next neighbour is at the start next = current.perimeter->start_index; } @@ -495,13 +505,16 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ auto b = Vec2d { under_b.position.x(), under_b.position.y() }; auto c = Vec2d { under_c.position.x(), under_c.position.y() }; - auto oriented_line_dist = [](const Vec2d a, const Vec2d b, const Vec2d p) { + auto oriented_line_dist = [](const Vec2d a, const Vec2d b, const Vec2d p) { //signed distance from line return -((b.x() - a.x()) * (a.y() - p.y()) - (a.x() - p.x()) * (b.y() - a.y())) / (a - b).norm(); }; auto dist_ab = oriented_line_dist(a, b, p); auto dist_bc = oriented_line_dist(b, c, p); + // from angle and signed distances from the arms of the points on the previous layer, we + // can deduce if it is overhang and give estimation of the size. + // However, the size of the overhang is rough estimation, the sign is more reliable if (under_b.local_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside return -((p - b).norm() + dist_ab + dist_bc) / 3.0; } @@ -513,6 +526,7 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ return ((p - b).norm() + dist_ab + dist_bc) / 3.0; } +// Pick best seam point based on the given comparator template void pick_seam_point(std::vector &perimeter_points, size_t start_index, const Comparator &comparator) { @@ -533,6 +547,8 @@ void pick_seam_point(std::vector &perimeter_points, size_t start_ perimeter_points[start_index].perimeter->seam_index = seam_index; } +// Computes all global model info - transforms object, performs raycasting, +// stores enforces and blockers void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; @@ -578,6 +594,7 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { #endif } +//Comparator of seam points. It has two necessary methods: is_first_better and is_first_not_much_worse struct DefaultSeamComparator { float compute_angle_penalty(float ccw_angle) const { if (ccw_angle >= 0) { @@ -588,6 +605,8 @@ struct DefaultSeamComparator { } + // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage + // should return if a is better seamCandidate than b bool is_first_better(const SeamCandidate &a, const SeamCandidate &b) const { // Blockers/Enforcers discrimination, top priority if (a.type > b.type) { @@ -606,6 +625,8 @@ struct DefaultSeamComparator { (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); } + // Comparator used during alignment. If there is close potential aligned point, it is comapred to the current + // sema point of the perimeter, to find out if the aligned point is not much worse than the current seam bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { // Blockers/Enforcers discrimination, top priority if (a.type > b.type) { @@ -628,6 +649,9 @@ struct DefaultSeamComparator { } // namespace SeamPlacerImpl +// Parallel process and extract each perimeter polygon of the given print object. +// Gather SeamCandidates of each layer into vector and build KDtree over them +// Store results in the SeamPlacer varaibles m_perimeter_points_per_object and m_perimeter_points_trees_per_object void SeamPlacer::gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info) { using namespace SeamPlacerImpl; @@ -701,6 +725,12 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po }); } +// Estimates, if there is good seam point in the layer_idx which is close to last_point_pos +// uses comparator.is_first_not_much_worse method to compare current seam with the closest point +// (if current seam is too far away ) +// If the current chosen stream is close enough, it is stored in seam_string. returns true and updates last_point_pos +// If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos +// Otherwise does nothing, returns false // sadly cannot be const because map access operator[] is not const, since it can create new object template bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, Vec3f &last_point_pos, @@ -743,11 +773,16 @@ bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, Vec3f &last_poi } -//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 +// clusters already chosen seam points into strings across multiple layers, and then +// aligns the strings via polynomial fit +// Does not change the positions of the SeamCandidates themselves, instead stores +// the new aligned position into the shared Perimeter structure of each perimeter +// Note that this position does not necesarilly lay on the perimeter. template void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comparator) { using namespace SeamPlacerImpl; + // Prepares Debug files for writing. #ifdef DEBUG_FILES Slic3r::CNumericLocalesSetter locales_setter; auto clusters_f = "seam_clusters_of_" + std::to_string(po->id().id) + ".obj"; @@ -766,7 +801,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } #endif - //gahter vector of all seams - pair of layer_index and seam__index within that layer + //gahter vector of all seams on the print_object - pair of layer_index and seam__index within that layer std::vector> seams; for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { std::vector &layer_perimeter_points = @@ -778,7 +813,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } } - //sort them and then align starting with the best candidates + //sort them before alignment. Alignment is sensitive to intitializaion, this gives it better chance to choose something nice std::sort(seams.begin(), seams.end(), [&](const std::pair &left, const std::pair &right) { return comparator.is_first_better(m_perimeter_points_per_object[po][left.first][left.second], @@ -786,7 +821,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } ); - //align them + //align the sema points - start with the best, and check if they are aligned, if yes, skip, else start alignment for (const std::pair &seam : seams) { size_t layer_idx = seam.first; size_t seam_index = seam.second; @@ -796,14 +831,16 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp // This perimeter is already aligned, skip seam continue; } else { - int skips = SeamPlacer::seam_align_tolerable_skips; + + //initialize searching for seam string - cluster of nearby seams on previous and next layers + int skips = SeamPlacer::seam_align_tolerable_skips / 2; int next_layer = layer_idx + 1; Vec3f last_point_pos = layer_perimeter_points[seam_index].position; std::vector> seam_string; std::vector> potential_string_seams; - //find close by points and outliers; there is a budget of skips allowed + //find seams or potential seams in forward direction; there is a budget of skips allowed while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, potential_string_seams)) { @@ -815,45 +852,57 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp next_layer++; } - if (seam_string.size() + potential_string_seams.size() >= seam_align_minimum_string_seams) { //string long enough to be worth aligning - //do additional check in back direction - next_layer = layer_idx - 1; - skips = SeamPlacer::seam_align_tolerable_skips; - while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, - seam_string, - potential_string_seams)) { - //String added, last_point_pos updated, nothing to be done - } else { - // Layer skipped, reduce number of available skips - skips--; - } - next_layer--; + //do additional check in back direction + next_layer = layer_idx - 1; + skips = SeamPlacer::seam_align_tolerable_skips / 2; + last_point_pos = layer_perimeter_points[seam_index].position; + while (skips >= 0 && next_layer >= 0) { + if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, + seam_string, + potential_string_seams)) { + //String added, last_point_pos updated, nothing to be done + } else { + // Layer skipped, reduce number of available skips + skips--; } + next_layer--; + } - // all string seams and potential string seams gathered, now do the alignment - seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); - std::sort(seam_string.begin(), seam_string.end(), - [](const std::pair &left, const std::pair &right) { - return left.first < right.first; - }); + if (seam_string.size() + potential_string_seams.size() < seam_align_minimum_string_seams) { + //string long enough to be worth aligning, skip + continue; + } - std::vector points(seam_string.size()); - for (size_t index = 0; index < seam_string.size(); ++index) { - points[index] = - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; - } + // String is long engouh, all string seams and potential string seams gathered, now do the alignment + // first merge potential_string_seams and seam_string; from now on, they all will be aligned + seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); + //sort by layer index + std::sort(seam_string.begin(), seam_string.end(), + [](const std::pair &left, const std::pair &right) { + return left.first < right.first; + }); - std::vector coefficients = polyfit(points, 3); - for (const auto &pair : seam_string) { - float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); - Vec3f seam_pos = get_fitted_point(coefficients, current_height); + // gather all positions of seams + std::vector points(seam_string.size()); + for (size_t index = 0; index < seam_string.size(); ++index) { + points[index] = + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; + } - Perimeter *perimeter = - m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); - perimeter->final_seam_position = seam_pos; - perimeter->aligned = true; - } + // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. + std::vector coefficients = polyfit(points, 3); + + // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into + // Perimeter structure of the point; also set flag aligned to true + for (const auto &pair : seam_string) { + float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); + Vec3f seam_pos = get_fitted_point(coefficients, current_height); + + Perimeter *perimeter = + m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); + perimeter->final_seam_position = seam_pos; + perimeter->aligned = true; + } // //https://en.wikipedia.org/wiki/Exponential_smoothing // //inititalization @@ -875,29 +924,28 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp // } #ifdef DEBUG_FILES - auto randf = []() { - return float(rand()) / float(RAND_MAX); - }; - Vec3f color { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; - fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], - orig_seam.position[1], - orig_seam.position[2], color[0], color[1], - color[2]); - } - - color = Vec3f { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - Perimeter *perimeter = - m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); - fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], - perimeter->final_seam_position[1], - perimeter->final_seam_position[2], color[0], color[1], - color[2]); - } -#endif + auto randf = []() { + return float(rand()) / float(RAND_MAX); + }; + Vec3f color { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; + fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], + orig_seam.position[1], + orig_seam.position[2], color[0], color[1], + color[2]); } + + color = Vec3f { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + Perimeter *perimeter = + m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); + fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], + perimeter->final_seam_position[1], + perimeter->final_seam_position[2], color[0], color[1], + color[2]); + } +#endif } } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index ec27938b1..092f70749 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -121,7 +121,7 @@ public: // this param limits the number of allowed skips static constexpr size_t seam_align_tolerable_skips = 4; // minimum number of seams needed in cluster to make alignemnt happen - static constexpr size_t seam_align_minimum_string_seams = 4; + static constexpr size_t seam_align_minimum_string_seams = 6; //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer From 8f7b86915a126bf8f467315503dc86cfd7a463d2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 2 Mar 2022 15:41:20 +0100 Subject: [PATCH 25/71] oversample polygons where necessary, due to seam enforcers & blockers --- src/libslic3r/GCode/SeamPlacerNG.cpp | 90 +++++++++++++++++++++------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index fad3911c6..6dab7ac41 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -6,6 +6,7 @@ #include #include #include +#include //For polynomial fitting #include @@ -299,14 +300,18 @@ struct GlobalModelInfo { raycast_hits_tree(HitInfoCoordinateFunctor { &geometry_raycast_hits }) { } - bool is_enforced(const Vec3f &position) const { - float radius = SeamPlacer::enforcer_blocker_sqr_distance_tolerance; + bool is_enforced(const Vec3f &position, float radius) const { + if (enforcers.empty()) { + return false; + } return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, enforcers_tree, position, radius); } - bool is_blocked(const Vec3f &position) const { - float radius = SeamPlacer::enforcer_blocker_sqr_distance_tolerance; + bool is_blocked(const Vec3f &position, float radius) const { + if (blockers.empty()) { + return false; + } return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, blockers_tree, position, radius); } @@ -431,6 +436,7 @@ Polygons extract_perimeter_polygons(const Layer *layer) { // Insert SeamCandidates created from perimeter polygons in to the result vector. // Compute its type (Enfrocer,Blocker), angle, and position //each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon +// if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, const GlobalModelInfo &global_model_info) { if (orig_polygon.size() == 0) { @@ -450,26 +456,59 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: SeamPlacer::polygon_local_angles_arm_distance); std::shared_ptr perimeter = std::make_shared(); - perimeter->start_index = result_vec.size(); - perimeter->end_index = result_vec.size() + polygon.size() - 1; - + std::queue orig_polygon_points { }; for (size_t index = 0; index < polygon.size(); ++index) { Vec2f unscaled_p = unscale(polygon[index]).cast(); - Vec3f unscaled_position = Vec3f { unscaled_p.x(), unscaled_p.y(), z_coord }; + orig_polygon_points.emplace(unscaled_p.x(), unscaled_p.y(), z_coord); + } + Vec3f first = orig_polygon_points.front(); + std::queue oversampled_points { }; + size_t orig_angle_index = 0; + perimeter->start_index = result_vec.size(); + while (!orig_polygon_points.empty() || !oversampled_points.empty()) { EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; + Vec3f position; + float local_ccw_angle = 0; + bool orig_point = false; + if (!oversampled_points.empty()) { + position = oversampled_points.front(); + oversampled_points.pop(); + } else { + position = orig_polygon_points.front(); + orig_polygon_points.pop(); + local_ccw_angle = was_clockwise ? -local_angles[orig_angle_index] : local_angles[orig_angle_index]; + orig_angle_index++; + orig_point = true; + } - if (global_model_info.is_enforced(unscaled_position)) { + if (global_model_info.is_enforced(position, SeamPlacer::enforcer_blocker_sqr_distance_tolerance)) { type = EnforcedBlockedSeamPoint::Enforced; } - if (global_model_info.is_blocked(unscaled_position)) { + if (global_model_info.is_blocked(position, SeamPlacer::enforcer_blocker_sqr_distance_tolerance)) { type = EnforcedBlockedSeamPoint::Blocked; } - float local_ccw_angle = was_clockwise ? -local_angles[index] : local_angles[index]; + if (orig_point) { + Vec3f pos_of_next = orig_polygon_points.empty() ? first : orig_polygon_points.front(); + float distance_to_next = (position - pos_of_next).norm(); + if (global_model_info.is_enforced(position, distance_to_next) + || global_model_info.is_blocked(position, distance_to_next)) { + Vec3f vec_to_next = (pos_of_next - position).normalized(); + float step_size = SeamPlacer::enforcer_blocker_sqr_distance_tolerance; + float step = step_size; + while (step < distance_to_next) { + oversampled_points.push(position + vec_to_next * step); + step += step_size; + } + } + } + + result_vec.emplace_back(position, perimeter, local_ccw_angle, type); - result_vec.emplace_back(unscaled_position, perimeter, local_ccw_angle, type); } + + perimeter->end_index = result_vec.size() - 1; } // Get index of previous and next perimeter point of the layer. Because SeamCandidates of all polygons of the given layer @@ -963,6 +1002,8 @@ void SeamPlacer::init(const Print &print) { for (const PrintObject *po : print.objects()) { + SeamPosition configured_seam_preference = po->config().seam_position.value; + GlobalModelInfo global_model_info { }; gather_global_model_info(global_model_info, po); @@ -972,11 +1013,13 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather_seam_candidates: end"; - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : start"; - calculate_candidates_visibility(po, global_model_info); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : end"; + if (configured_seam_preference != spAligned) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_candidates_visibility : start"; + calculate_candidates_visibility(po, global_model_info); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_candidates_visibility : end"; + } BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: calculate_overhangs : start"; @@ -1002,12 +1045,13 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: pick_seam_point : end"; - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : start"; - align_seam_points(po, DefaultSeamComparator { }); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : end"; - + if (configured_seam_preference != spRandom) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: align_seam_points : start"; + align_seam_points(po, DefaultSeamComparator { }); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: align_seam_points : end"; + } } } From a775bf2978a6a1091549bcab1229c365ae63cf3e Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 2 Mar 2022 15:54:03 +0100 Subject: [PATCH 26/71] improved enforcers blockers oversampling, renamed parameter --- src/libslic3r/GCode/SeamPlacerNG.cpp | 8 ++++---- src/libslic3r/GCode/SeamPlacerNG.hpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 6dab7ac41..68c9f64dc 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -481,11 +481,11 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: orig_point = true; } - if (global_model_info.is_enforced(position, SeamPlacer::enforcer_blocker_sqr_distance_tolerance)) { + if (global_model_info.is_enforced(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { type = EnforcedBlockedSeamPoint::Enforced; } - if (global_model_info.is_blocked(position, SeamPlacer::enforcer_blocker_sqr_distance_tolerance)) { + if (global_model_info.is_blocked(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { type = EnforcedBlockedSeamPoint::Blocked; } @@ -495,7 +495,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: if (global_model_info.is_enforced(position, distance_to_next) || global_model_info.is_blocked(position, distance_to_next)) { Vec3f vec_to_next = (pos_of_next - position).normalized(); - float step_size = SeamPlacer::enforcer_blocker_sqr_distance_tolerance; + float step_size = SeamPlacer::enforcer_blocker_distance_tolerance / 2.0f; float step = step_size; while (step < distance_to_next) { oversampled_points.push(position + vec_to_next * step); @@ -929,7 +929,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. - std::vector coefficients = polyfit(points, 3); + std::vector coefficients = polyfit(points, 4); // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Perimeter structure of the point; also set flag aligned to true diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 092f70749..682fc0d25 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -111,7 +111,7 @@ public: static constexpr float polygon_local_angles_arm_distance = 1.0f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocer or Enforcer - static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.1f; + static constexpr float enforcer_blocker_distance_tolerance = 0.1f; // When searching for seam clusters for alignment: // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, From f8377599280d6ab1731b3d943a6b9be29f89f9e8 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 2 Mar 2022 17:16:19 +0100 Subject: [PATCH 27/71] fixed problem with multipart objects fixed bug : model volume trafo was not considered --- src/libslic3r/GCode/SeamPlacerNG.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 68c9f64dc..e0e3f317b 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -592,8 +592,18 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; // Build AABB tree for raycasting - auto obj_transform = po->trafo_centered(); + auto obj_transform = po->trafo(); auto triangle_set = po->model_object()->raw_indexed_triangle_set(); + //add model parts + for (const ModelVolume* model_volume : po->model_object()->volumes){ + if (model_volume->type() == ModelVolumeType::MODEL_PART) { + auto model_transformation = model_volume->get_matrix(); + indexed_triangle_set model_its = model_volume->mesh().its; + its_transform(model_its, model_transformation); + its_merge(triangle_set , model_its); + } + } + its_transform(triangle_set, obj_transform); float target_error = SeamPlacer::raycasting_decimation_target_error; its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); @@ -611,9 +621,16 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; for (const ModelVolume *mv : po->model_object()->volumes) { - if (mv->is_model_part()) { - its_merge(result.enforcers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER)); - its_merge(result.blockers, mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER)); + if (mv->is_seam_painted()) { + auto model_transformation = mv->get_matrix(); + + indexed_triangle_set enforcers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER); + its_transform(enforcers, model_transformation); + its_merge(result.enforcers, enforcers); + + indexed_triangle_set blockers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER); + its_transform(blockers, model_transformation); + its_merge(result.blockers, blockers); } } its_transform(result.enforcers, obj_transform); @@ -680,7 +697,7 @@ struct DefaultSeamComparator { return false; } - return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.8f <= + return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.75f <= (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); } } From 962282c9efbd8a8c7a5d92634297635f91e23f9d Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 4 Mar 2022 09:15:47 +0100 Subject: [PATCH 28/71] split occlusion and enforcers/blockers into separate functions added weights to polynomial fitting --- src/libslic3r/GCode/SeamPlacerNG.cpp | 162 ++++++++++++++++++--------- src/libslic3r/GCode/SeamPlacerNG.hpp | 13 ++- 2 files changed, 119 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index e0e3f317b..a863465bb 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -34,26 +34,33 @@ namespace SeamPlacerImpl { //https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 // interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y -std::vector polyfit(const std::vector &points, size_t order) { +std::vector polyfit(const std::vector &points, const std::vector &weights, size_t order) { + // check to make sure inputs are correct + assert(points.size() >= order + 1); + assert(points.size() == weights.size()); + + std::vector squared_weights(weights.size()); + for (size_t index = 0; index < weights.size(); ++index) { + squared_weights[index] = sqrt(weights[index]); + } + Eigen::VectorXf V0(points.size()); Eigen::VectorXf V1(points.size()); Eigen::VectorXf V2(points.size()); for (size_t index = 0; index < points.size(); index++) { - V0(index) = points[index].x(); - V1(index) = points[index].y(); + V0(index) = points[index].x() * squared_weights[index]; + V1(index) = points[index].y() * squared_weights[index]; V2(index) = points[index].z(); } // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial Eigen::MatrixXf T(points.size(), order + 1); - // check to make sure inputs are correct - assert(points.size() >= order + 1); // Populate the matrix for (size_t i = 0; i < points.size(); ++i) { for (size_t j = 0; j < order + 1; ++j) { - T(i, j) = pow(V2(i), j); + T(i, j) = pow(V2(i), j) * squared_weights[i]; } } @@ -588,19 +595,19 @@ void pick_seam_point(std::vector &perimeter_points, size_t start_ // Computes all global model info - transforms object, performs raycasting, // stores enforces and blockers -void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { +void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; // Build AABB tree for raycasting auto obj_transform = po->trafo(); auto triangle_set = po->model_object()->raw_indexed_triangle_set(); //add model parts - for (const ModelVolume* model_volume : po->model_object()->volumes){ + for (const ModelVolume *model_volume : po->model_object()->volumes) { if (model_volume->type() == ModelVolumeType::MODEL_PART) { auto model_transformation = model_volume->get_matrix(); indexed_triangle_set model_its = model_volume->mesh().its; its_transform(model_its, model_transformation); - its_merge(triangle_set , model_its); + its_merge(triangle_set, model_its); } } @@ -617,9 +624,18 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; +#ifdef DEBUG_FILES + auto filename = "visiblity_of_" + std::to_string(po->id().id) + ".obj"; + result.debug_export(triangle_set, filename.c_str()); +#endif +} + +void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; + auto obj_transform = po->trafo(); + for (const ModelVolume *mv : po->model_object()->volumes) { if (mv->is_seam_painted()) { auto model_transformation = mv->get_matrix(); @@ -643,22 +659,22 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; - -#ifdef DEBUG_FILES - auto filename = "visiblity_of_" + std::to_string(po->id().id) + ".obj"; - result.debug_export(triangle_set, filename.c_str()); -#endif } //Comparator of seam points. It has two necessary methods: is_first_better and is_first_not_much_worse -struct DefaultSeamComparator { +struct SeamComparator { + SeamPosition setup; + + SeamComparator(SeamPosition setup) : + setup(setup) { + } + float compute_angle_penalty(float ccw_angle) const { if (ccw_angle >= 0) { return PI * PI - ccw_angle * ccw_angle; } else { - return (PI * PI - ccw_angle * ccw_angle) * 0.8f; + return (PI * PI - ccw_angle * ccw_angle) * 0.7f; } - } // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage @@ -677,6 +693,10 @@ struct DefaultSeamComparator { return false; } + if (setup == SeamPosition::spRear) { + return a.position.y() > b.position.y(); + } + return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) < (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); } @@ -697,9 +717,23 @@ struct DefaultSeamComparator { return false; } + if (setup == SeamPosition::spRear) { + return a.position.y() > b.position.y(); + } + return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.75f <= (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); } + + float get_weight(const SeamCandidate &a) const { + if (setup == SeamPosition::spRear) { + return a.position.y() + int(a.type) * 10000.0f; + } + + //return negative, beacuse we want to minimize this value (sort of antiweight). normalization in alignment fixes that with respect to other points + return int(a.type) * 10000.0f + - (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle); + } } ; @@ -789,13 +823,16 @@ tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po // Otherwise does nothing, returns false // sadly cannot be const because map access operator[] is not const, since it can create new object template -bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, Vec3f &last_point_pos, +bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, + std::pair &last_point_indexes, size_t layer_idx, const Comparator &comparator, std::vector> &seam_string, std::vector> &potential_string_seams) { using namespace SeamPlacerImpl; - Vec3f projected_position { last_point_pos.x(), last_point_pos.y(), float( + const SeamCandidate& last_point = m_perimeter_points_per_object[po][last_point_indexes.first][last_point_indexes.second]; + + Vec3f projected_position { last_point.position.x(), last_point.position.y(), float( po->get_layer(layer_idx)->slice_z) }; //find closest point in next layer size_t closest_point_index = find_closest_point( @@ -811,17 +848,21 @@ bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, Vec3f &last_poi SeamCandidate &next_layer_seam = m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; + auto are_similar = [&](const SeamCandidate& a, const SeamCandidate& b) { + return comparator.is_first_not_much_worse(a,b) && comparator.is_first_not_much_worse(b,a); + }; + if ((next_layer_seam.position - projected_position).norm() - < SeamPlacer::seam_align_tolerable_dist) { //seam point is within limits, put in the close_by_points list + < SeamPlacer::seam_align_tolerable_dist && are_similar(last_point, next_layer_seam)) { //seam point is within limits, put in the close_by_points list seam_string.emplace_back(layer_idx, closest_point.perimeter->seam_index); - last_point_pos = next_layer_seam.position; + last_point_indexes = std::pair{layer_idx, closest_point.perimeter->seam_index}; return true; } else if ((closest_point.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist - && comparator.is_first_not_much_worse(closest_point, next_layer_seam)) { + && comparator.is_first_not_much_worse(closest_point, next_layer_seam) && are_similar(last_point, closest_point)) { //seam point is far, but if the close point is not much worse, do not count it as a skip and add it to potential_string_seams potential_string_seams.emplace_back(layer_idx, closest_point_index); - last_point_pos = closest_point.position; + last_point_indexes = std::pair{layer_idx, closest_point_index}; return true; } else { return false; @@ -891,14 +932,14 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp //initialize searching for seam string - cluster of nearby seams on previous and next layers int skips = SeamPlacer::seam_align_tolerable_skips / 2; int next_layer = layer_idx + 1; - Vec3f last_point_pos = layer_perimeter_points[seam_index].position; + std::pair last_point_indexes = std::pair(layer_idx, seam_index); std::vector> seam_string; std::vector> potential_string_seams; //find seams or potential seams in forward direction; there is a budget of skips allowed while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { - if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, + if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string, potential_string_seams)) { //String added, last_point_pos updated, nothing to be done } else { @@ -911,9 +952,9 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp //do additional check in back direction next_layer = layer_idx - 1; skips = SeamPlacer::seam_align_tolerable_skips / 2; - last_point_pos = layer_perimeter_points[seam_index].position; + last_point_indexes = std::pair(layer_idx, seam_index); while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, + if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string, potential_string_seams)) { //String added, last_point_pos updated, nothing to be done @@ -938,15 +979,26 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp return left.first < right.first; }); - // gather all positions of seams + // gather all positions of seams and their weights std::vector points(seam_string.size()); + std::vector weights(seam_string.size()); + float min_weight = comparator.get_weight( + m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]); + for (size_t index = 0; index < seam_string.size(); ++index) { points[index] = m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; + weights[index] = comparator.get_weight( + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); + min_weight = std::min(min_weight, weights[index]); + } + + for (float &w : weights) { + w = w - min_weight + 1.0; //makes all weights positive, nonzero } // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. - std::vector coefficients = polyfit(points, 4); + std::vector coefficients = polyfit(points, weights, 4); // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Perimeter structure of the point; also set flag aligned to true @@ -960,24 +1012,29 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp perimeter->aligned = true; } -// //https://en.wikipedia.org/wiki/Exponential_smoothing -// //inititalization -// float smoothing_factor = 0.8; -// std::pair init = seam_string[0]; -// Vec2f prev_pos_xy = m_perimeter_points_per_object[po][init.first][init.second].position.head<2>(); -// for (const auto &pair : seam_string) { -// Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; -// float current_height = current_pos.z(); -// Vec2f current_pos_xy = current_pos.head<2>(); -// current_pos_xy = smoothing_factor * prev_pos_xy + (1.0 - smoothing_factor) * current_pos_xy; +// for (Vec3f& p : points){ +// p = get_fitted_point(coefficients, p.z()); +// } + +// for (size_t iteration = 0; iteration < 20; ++iteration) { +// std::vector new_points(seam_string.size()); +// for (int point_index = 0; point_index < points.size(); ++point_index) { +// size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; +// size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; // -// Perimeter *perimeter = -// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); -// perimeter->final_seam_position = -// Vec3f { current_pos_xy.x(), current_pos_xy.y(), current_height }; -// perimeter->aligned = true; -// prev_pos_xy = current_pos_xy; -// } +// new_points[point_index] = (points[prev_idx] * weights[prev_idx] +// + points[next_idx] * weights[next_idx]) / +// (weights[prev_idx] + weights[next_idx]); +// } +// points = new_points; +// } +// +// for (size_t index = 0; index < seam_string.size(); ++index) { +// Perimeter *perimeter = +// m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].perimeter.get(); +// perimeter->final_seam_position = points[index]; +// perimeter->aligned = true; +// } #ifdef DEBUG_FILES auto randf = []() { @@ -1020,9 +1077,14 @@ void SeamPlacer::init(const Print &print) { for (const PrintObject *po : print.objects()) { SeamPosition configured_seam_preference = po->config().seam_position.value; + SeamComparator comparator { configured_seam_preference }; GlobalModelInfo global_model_info { }; - gather_global_model_info(global_model_info, po); + gather_enforcers_blockers(global_model_info, po); + + if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) { + compute_global_occlusion(global_model_info, po); + } BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather_seam_candidates: start"; @@ -1030,7 +1092,7 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather_seam_candidates: end"; - if (configured_seam_preference != spAligned) { + if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: calculate_candidates_visibility : start"; calculate_candidates_visibility(po, global_model_info); @@ -1054,7 +1116,7 @@ void SeamPlacer::init(const Print &print) { m_perimeter_points_per_object[po][layer_idx]; size_t current = 0; while (current < layer_perimeter_points.size()) { - pick_seam_point(layer_perimeter_points, current, DefaultSeamComparator { }); + pick_seam_point(layer_perimeter_points, current, comparator); current = layer_perimeter_points[current].perimeter->end_index + 1; } } @@ -1065,7 +1127,7 @@ void SeamPlacer::init(const Print &print) { if (configured_seam_preference != spRandom) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: align_seam_points : start"; - align_seam_points(po, DefaultSeamComparator { }); + align_seam_points(po, comparator); BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: align_seam_points : end"; } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 682fc0d25..2c9233544 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -97,26 +97,26 @@ public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; // Rough estimates of hits of the mesh during raycasting per surface circle defined by considered_area_radius - static constexpr float expected_hits_per_area = 400.0f; + static constexpr float expected_hits_per_area = 1000.0f; // area considered when computing number of rays and then gathering visiblity info from the hits - static constexpr float considered_area_radius = 4.0f; + static constexpr float considered_area_radius = 3.0f; // quadric error limit of quadric decimation function used on the mesh before raycasting static constexpr float raycasting_decimation_target_error = 0.1f; // cosine sampling power represents how prefered are forward directions when raycasting from given spot // in this case, forward direction means towards the center of the mesh - static constexpr float cosine_hemisphere_sampling_power = 4.0f; + static constexpr float cosine_hemisphere_sampling_power = 8.0f; // arm length used during angles computation static constexpr float polygon_local_angles_arm_distance = 1.0f; - // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocer or Enforcer + // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer static constexpr float enforcer_blocker_distance_tolerance = 0.1f; // When searching for seam clusters for alignment: // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, //it belongs automaticaly to the cluster - static constexpr float seam_align_tolerable_dist = 1.0f; + static constexpr float seam_align_tolerable_dist = 0.5f; // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. // this param limits the number of allowed skips static constexpr size_t seam_align_tolerable_skips = 4; @@ -141,7 +141,8 @@ private: template void align_seam_points(const PrintObject *po, const Comparator &comparator); template - bool find_next_seam_in_string(const PrintObject *po, Vec3f &last_point_pos, + bool find_next_seam_in_layer(const PrintObject *po, + std::pair &last_point, size_t layer_idx, const Comparator &comparator, std::vector> &seam_strings, std::vector> &outliers); From a92d5038bdf0a34e9fa4f1753deeca9cf6f4c094 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 4 Mar 2022 14:13:17 +0100 Subject: [PATCH 29/71] debug export svg info, fixing problem with weird seam placement caused by disconnected scoring function --- src/libslic3r/GCode/SeamPlacerNG.cpp | 194 +++++++++++++++++---------- src/libslic3r/GCode/SeamPlacerNG.hpp | 4 +- 2 files changed, 128 insertions(+), 70 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index a863465bb..d4b9cce10 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -26,12 +26,25 @@ #ifdef DEBUG_FILES #include "Subdivide.hpp" #include +#include #endif namespace Slic3r { namespace SeamPlacerImpl { +Vec3f value_to_rgbf(float minimum, float maximum, float value) { + float ratio = 2.0f * (value - minimum) / (maximum - minimum); + float b = std::max(0.0f, (1.0f - ratio)); + float r = std::max(0.0f, (ratio - 1.0f)); + float g = 1.0f - b - r; + return Vec3f { r, g, b }; +} + +Vec3i value_rgbi(float minimum, float maximum, float value) { + return (value_to_rgbf(minimum, maximum, value) * 255).cast(); +} + //https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 // interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y std::vector polyfit(const std::vector &points, const std::vector &weights, size_t order) { @@ -350,7 +363,7 @@ struct GlobalModelInfo { #ifdef DEBUG_FILES void debug_export(const indexed_triangle_set &obj_mesh, const char *file_name) const { - indexed_triangle_set divided_mesh = subdivide(obj_mesh, SeamPlacer::considered_area_radius); + indexed_triangle_set divided_mesh = obj_mesh; // subdivide(obj_mesh, SeamPlacer::considered_area_radius); Slic3r::CNumericLocalesSetter locales_setter; { FILE *fp = boost::nowide::fopen(file_name, "w"); @@ -402,7 +415,6 @@ struct GlobalModelInfo { } } #endif - } ; @@ -669,8 +681,10 @@ struct SeamComparator { setup(setup) { } + + //TODO "bump function": e^[1 / ( (x-0.3)^2 +1 )] -1 << =ℯ^(((1)/((x-0.3)^(2)+1)))-1 float compute_angle_penalty(float ccw_angle) const { - if (ccw_angle >= 0) { + if (ccw_angle >= -0.4) { return PI * PI - ccw_angle * ccw_angle; } else { return (PI * PI - ccw_angle * ccw_angle) * 0.7f; @@ -727,93 +741,133 @@ struct SeamComparator { float get_weight(const SeamCandidate &a) const { if (setup == SeamPosition::spRear) { - return a.position.y() + int(a.type) * 10000.0f; + return a.position.y(); } - //return negative, beacuse we want to minimize this value (sort of antiweight). normalization in alignment fixes that with respect to other points - return int(a.type) * 10000.0f - - (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle); + //return negative, beacuse we want to minimize the absolute of this value (sort of antiweight). normalization in alignment fixes that with respect to other points + return -(a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle); } } ; +#ifdef DEBUG_FILES +void debug_export_points(const std::vector> &object_perimter_points, + const BoundingBox &bounding_box, std::string object_name, const SeamComparator &comparator) { + for (size_t layer_idx = 0; layer_idx < object_perimter_points.size(); ++layer_idx) { + SVG angles_svg { object_name + "_angles_" + std::to_string(layer_idx) + ".svg", bounding_box }; + float min_vis = 0; + float max_vis = min_vis; + + float min_weight = std::numeric_limits::min(); + float max_weight = min_weight; + + for (const SeamCandidate &point : object_perimter_points[layer_idx]) { + Vec3i color = value_rgbi(-PI, PI, point.local_ccw_angle); + std::string fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," + + std::to_string(color.z()) + ")"; + angles_svg.draw(scaled(Vec2f(point.position.head<2>())), fill); + min_vis = std::min(min_vis, point.visibility); + max_vis = std::max(max_vis, point.visibility); + + min_weight = std::min(min_weight, comparator.get_weight(point)); + max_weight = std::max(max_weight, comparator.get_weight(point)); + + } + + SVG visibility_svg { object_name + "_visibility_" + std::to_string(layer_idx) + ".svg", bounding_box }; + SVG weight_svg { object_name + "_weight_" + std::to_string(layer_idx) + ".svg", bounding_box }; + for (const SeamCandidate &point : object_perimter_points[layer_idx]) { + Vec3i color = value_rgbi(min_vis, max_vis, point.visibility); + std::string visibility_fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," + + std::to_string(color.z()) + ")"; + visibility_svg.draw(scaled(Vec2f(point.position.head<2>())), visibility_fill); + + Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_weight(point)); + std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + "," + + std::to_string(weight_color.z()) + ")"; + weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); + } + } +} +#endif + } // namespace SeamPlacerImpl // Parallel process and extract each perimeter polygon of the given print object. // Gather SeamCandidates of each layer into vector and build KDtree over them // Store results in the SeamPlacer varaibles m_perimeter_points_per_object and m_perimeter_points_trees_per_object void SeamPlacer::gather_seam_candidates(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { -using namespace SeamPlacerImpl; + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { + using namespace SeamPlacerImpl; -m_perimeter_points_per_object.emplace(po, po->layer_count()); -m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); + m_perimeter_points_per_object.emplace(po, po->layer_count()); + m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); -tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; - const Layer *layer = po->get_layer(layer_idx); - auto unscaled_z = layer->slice_z; - Polygons polygons = extract_perimeter_polygons(layer); - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - global_model_info); + tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + const Layer *layer = po->get_layer(layer_idx); + auto unscaled_z = layer->slice_z; + Polygons polygons = extract_perimeter_polygons(layer); + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + global_model_info); + } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( + functor, layer_candidates.size()); } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( - functor, layer_candidates.size()); } - } -); + ); } void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { -using namespace SeamPlacerImpl; + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { + using namespace SeamPlacerImpl; -tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.visibility = global_model_info.calculate_point_visibility( - perimeter_point.position); + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + perimeter_point.visibility = global_model_info.calculate_point_visibility( + perimeter_point.position); + } } - } - }); + }); } void SeamPlacer::calculate_overhangs(const PrintObject *po) { -using namespace SeamPlacerImpl; + using namespace SeamPlacerImpl; -tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - const auto calculate_layer_overhang = [&](size_t other_layer_idx) { - size_t closest_supporter = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - perimeter_point.position); - const SeamCandidate &supporter_point = - m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + const auto calculate_layer_overhang = [&](size_t other_layer_idx) { + size_t closest_supporter = find_closest_point( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + perimeter_point.position); + const SeamCandidate &supporter_point = + m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; - auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); - const SeamCandidate &prev_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; - const SeamCandidate &next_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; + auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); + const SeamCandidate &prev_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; + const SeamCandidate &next_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; - return calculate_overhang(perimeter_point, prev_point, - supporter_point, next_point); - }; + return calculate_overhang(perimeter_point, prev_point, + supporter_point, next_point); + }; - if (layer_idx > 0) { //calculate overhang - perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); + if (layer_idx > 0) { //calculate overhang + perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); + } } } - } - }); - } + }); + } // Estimates, if there is good seam point in the layer_idx which is close to last_point_pos // uses comparator.is_first_not_much_worse method to compare current seam with the closest point @@ -830,7 +884,8 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, std::vector> &potential_string_seams) { using namespace SeamPlacerImpl; - const SeamCandidate& last_point = m_perimeter_points_per_object[po][last_point_indexes.first][last_point_indexes.second]; + const SeamCandidate &last_point = + m_perimeter_points_per_object[po][last_point_indexes.first][last_point_indexes.second]; Vec3f projected_position { last_point.position.x(), last_point.position.y(), float( po->get_layer(layer_idx)->slice_z) }; @@ -848,21 +903,22 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, SeamCandidate &next_layer_seam = m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; - auto are_similar = [&](const SeamCandidate& a, const SeamCandidate& b) { - return comparator.is_first_not_much_worse(a,b) && comparator.is_first_not_much_worse(b,a); + auto are_similar = [&](const SeamCandidate &a, const SeamCandidate &b) { + return comparator.is_first_not_much_worse(a, b) && comparator.is_first_not_much_worse(b, a); }; if ((next_layer_seam.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist && are_similar(last_point, next_layer_seam)) { //seam point is within limits, put in the close_by_points list seam_string.emplace_back(layer_idx, closest_point.perimeter->seam_index); - last_point_indexes = std::pair{layer_idx, closest_point.perimeter->seam_index}; + last_point_indexes = std::pair { layer_idx, closest_point.perimeter->seam_index }; return true; } else if ((closest_point.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist - && comparator.is_first_not_much_worse(closest_point, next_layer_seam) && are_similar(last_point, closest_point)) { + && comparator.is_first_not_much_worse(closest_point, next_layer_seam) + && are_similar(last_point, closest_point)) { //seam point is far, but if the close point is not much worse, do not count it as a skip and add it to potential_string_seams potential_string_seams.emplace_back(layer_idx, closest_point_index); - last_point_indexes = std::pair{layer_idx, closest_point_index}; + last_point_indexes = std::pair { layer_idx, closest_point_index }; return true; } else { return false; @@ -932,7 +988,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp //initialize searching for seam string - cluster of nearby seams on previous and next layers int skips = SeamPlacer::seam_align_tolerable_skips / 2; int next_layer = layer_idx + 1; - std::pair last_point_indexes = std::pair(layer_idx, seam_index); + std::pair last_point_indexes = std::pair(layer_idx, seam_index); std::vector> seam_string; std::vector> potential_string_seams; @@ -952,7 +1008,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp //do additional check in back direction next_layer = layer_idx - 1; skips = SeamPlacer::seam_align_tolerable_skips / 2; - last_point_indexes = std::pair(layer_idx, seam_index); + last_point_indexes = std::pair(layer_idx, seam_index); while (skips >= 0 && next_layer >= 0) { if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string, @@ -1131,6 +1187,8 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: align_seam_points : end"; } + + debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), comparator); } } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 2c9233544..1393dbc84 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -101,11 +101,11 @@ public: // area considered when computing number of rays and then gathering visiblity info from the hits static constexpr float considered_area_radius = 3.0f; // quadric error limit of quadric decimation function used on the mesh before raycasting - static constexpr float raycasting_decimation_target_error = 0.1f; + static constexpr float raycasting_decimation_target_error = 2.0f; // cosine sampling power represents how prefered are forward directions when raycasting from given spot // in this case, forward direction means towards the center of the mesh - static constexpr float cosine_hemisphere_sampling_power = 8.0f; + static constexpr float cosine_hemisphere_sampling_power = 5.0f; // arm length used during angles computation static constexpr float polygon_local_angles_arm_distance = 1.0f; From ab3c8d0fe8ac9f078e1e12817d20dab9b72eff81 Mon Sep 17 00:00:00 2001 From: Godrak Date: Fri, 4 Mar 2022 18:30:42 +0100 Subject: [PATCH 30/71] implemented smooth angle penalty function --- src/libslic3r/GCode/SeamPlacerNG.cpp | 21 ++++++++++----------- src/libslic3r/GCode/SeamPlacerNG.hpp | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index d4b9cce10..b1fc51fa1 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -682,13 +682,12 @@ struct SeamComparator { } - //TODO "bump function": e^[1 / ( (x-0.3)^2 +1 )] -1 << =ℯ^(((1)/((x-0.3)^(2)+1)))-1 + //"gaussian bump function": e^(1/((x-0.15)^2+1))-1 float compute_angle_penalty(float ccw_angle) const { - if (ccw_angle >= -0.4) { - return PI * PI - ccw_angle * ccw_angle; - } else { - return (PI * PI - ccw_angle * ccw_angle) * 0.7f; - } + float shifted = ccw_angle - 0.15; + float pow2 = shifted*shifted; + float exponent = 1.0f / (pow2 +1); + return std::exp(exponent) - 1; } // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage @@ -1068,9 +1067,9 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp perimeter->aligned = true; } -// for (Vec3f& p : points){ -// p = get_fitted_point(coefficients, p.z()); -// } + for (Vec3f& p : points){ + p = get_fitted_point(coefficients, p.z()); + } // for (size_t iteration = 0; iteration < 20; ++iteration) { // std::vector new_points(seam_string.size()); @@ -1079,8 +1078,8 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp // size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; // // new_points[point_index] = (points[prev_idx] * weights[prev_idx] -// + points[next_idx] * weights[next_idx]) / -// (weights[prev_idx] + weights[next_idx]); +// +points[point_index] * weights[point_index]+ points[next_idx] * weights[next_idx]) / +// (weights[prev_idx] + weights[point_index]+ weights[next_idx]); // } // points = new_points; // } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 1393dbc84..0d47e699b 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -108,10 +108,10 @@ public: static constexpr float cosine_hemisphere_sampling_power = 5.0f; // arm length used during angles computation - static constexpr float polygon_local_angles_arm_distance = 1.0f; + static constexpr float polygon_local_angles_arm_distance = 0.4f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer - static constexpr float enforcer_blocker_distance_tolerance = 0.1f; + static constexpr float enforcer_blocker_distance_tolerance = 0.2f; // When searching for seam clusters for alignment: // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, From a9f5330ad28e152156359e83ea98d61982864cb0 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 7 Mar 2022 12:17:33 +0100 Subject: [PATCH 31/71] using gauss function to smoothen criteria skips --- src/libslic3r/GCode/SeamPlacerNG.cpp | 36 ++++++++++++---------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 2 +- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index b1fc51fa1..396c1d770 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -33,6 +33,15 @@ namespace Slic3r { namespace SeamPlacerImpl { +// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) +// checkout e.g. here: https://www.geogebra.org/calculator +float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { + float shifted = value - mean_x_coord; + float denominator = falloff_speed*shifted*shifted + 1.0f; + float exponent = 1.0f / denominator; + return mean_value*(std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); +} + Vec3f value_to_rgbf(float minimum, float maximum, float value) { float ratio = 2.0f * (value - minimum) / (maximum - minimum); float b = std::max(0.0f, (1.0f - ratio)); @@ -349,12 +358,11 @@ struct GlobalModelInfo { float visibility = 0; for (const auto &hit_point_index : nearby_points) { - if (local_normal.dot(geometry_raycast_hits[hit_point_index].surface_normal) > 0) { - // The further away from the perimeter point, - // the less representative ray hit is + float dot_product = local_normal.dot(geometry_raycast_hits[hit_point_index].surface_normal); + if (dot_product > 0) { float distance = (position - geometry_raycast_hits[hit_point_index].position).norm(); - visibility += (SeamPlacer::considered_area_radius - distance); + visibility += dot_product*gauss(distance, 0.0f, 1.0f, 0.5f); } } return visibility; @@ -373,19 +381,9 @@ struct GlobalModelInfo { return; } - const auto vis_to_rgb = [](float normalized_visibility) { - float ratio = 2 * normalized_visibility; - float blue = std::max(0.0f, 1.0f - ratio); - float red = std::max(0.0f, ratio - 1.0f); - float green = std::max(0.0f, 1.0f - blue - red); - return Vec3f { red, blue, green }; - }; - for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { float visibility = calculate_point_visibility(divided_mesh.vertices[i]); - float normalized = visibility - / (SeamPlacer::expected_hits_per_area * SeamPlacer::considered_area_radius); - Vec3f color = vis_to_rgb(normalized); + Vec3f color = value_to_rgbf(0, SeamPlacer::expected_hits_per_area, visibility); fprintf(fp, "v %f %f %f %f %f %f\n", divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), color(0), color(1), color(2) @@ -682,12 +680,8 @@ struct SeamComparator { } - //"gaussian bump function": e^(1/((x-0.15)^2+1))-1 float compute_angle_penalty(float ccw_angle) const { - float shifted = ccw_angle - 0.15; - float pow2 = shifted*shifted; - float exponent = 1.0f / (pow2 +1); - return std::exp(exponent) - 1; + return gauss(ccw_angle, 0.2f, 1.0f, 0.7f); } // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage @@ -734,7 +728,7 @@ struct SeamComparator { return a.position.y() > b.position.y(); } - return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.75f <= + return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.8f <= (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 0d47e699b..d35fa3bda 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -101,7 +101,7 @@ public: // area considered when computing number of rays and then gathering visiblity info from the hits static constexpr float considered_area_radius = 3.0f; // quadric error limit of quadric decimation function used on the mesh before raycasting - static constexpr float raycasting_decimation_target_error = 2.0f; + static constexpr float raycasting_decimation_target_error = 0.5f; // cosine sampling power represents how prefered are forward directions when raycasting from given spot // in this case, forward direction means towards the center of the mesh From eccf1c155352caeb35fcceb0009f314754730cde Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 7 Mar 2022 16:22:42 +0100 Subject: [PATCH 32/71] refactored raycasting - inverted direction of raycasting - now each face is tested fixed bug with custom seam drawings - square distance parameter named incorrectly --- src/libslic3r/AABBTreeIndirect.hpp | 6 +- src/libslic3r/GCode/SeamPlacerNG.cpp | 253 +++++++++++---------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 34 +--- 3 files changed, 116 insertions(+), 177 deletions(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 217166f8c..d47ea5562 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -759,8 +759,8 @@ inline bool is_any_triangle_in_radius( const TreeType &tree, // Point to which the closest point on the indexed triangle set is searched for. const VectorType &point, - // Maximum distance in which triangle is search for - typename VectorType::Scalar &max_distance) + //Square of maximum distance in which triangle is searched for + typename VectorType::Scalar &max_distance_squared) { using Scalar = typename VectorType::Scalar; auto distancer = detail::IndexedTriangleSetDistancer @@ -774,7 +774,7 @@ inline bool is_any_triangle_in_radius( return false; } - detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point); + detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance_squared, hit_idx, hit_point); return hit_point.allFinite(); } diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 396c1d770..5037916b2 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -37,9 +37,9 @@ namespace SeamPlacerImpl { // checkout e.g. here: https://www.geogebra.org/calculator float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { float shifted = value - mean_x_coord; - float denominator = falloff_speed*shifted*shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value*(std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); + float denominator = falloff_speed * shifted * shifted + 1.0f; + float exponent = 1.0f / denominator; + return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); } Vec3f value_to_rgbf(float minimum, float maximum, float value) { @@ -162,6 +162,13 @@ Vec3f sample_sphere_uniform(const Vec2f &samples) { 1.0f - 2.0f * samples.y()}; } +Vec3f sample_hemisphere_uniform(const Vec2f &samples) { + float term1 = 2.0f * float(PI) * samples.x(); + float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term1) * term2, sin(term1) * term2, + abs(1.0f - 2.0f * samples.y())}; +} + Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { float term1 = 2.f * float(PI) * samples.x(); float term2 = pow(samples.y(), 1.f / (power + 1.f)); @@ -170,88 +177,63 @@ Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); } -std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, +std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, const indexed_triangle_set &triangles) { - - auto bbox = raycasting_tree.node(0).bbox; - Vec3f vision_sphere_center = bbox.center().cast(); - Vec3f side_sizes = bbox.sizes().cast(); - float vision_sphere_raidus = (side_sizes.norm() * 0.55); // 0.5 (half) covers whole object, - // 0.05 added to avoid corner cases - // very rough approximation of object surface area from its bounding sphere - float approx_area = 4 * PI * vision_sphere_raidus * vision_sphere_raidus; - float local_considered_area = PI * SeamPlacer::considered_area_radius * SeamPlacer::considered_area_radius; - size_t ray_count = SeamPlacer::expected_hits_per_area * approx_area / local_considered_area; - - // Prepare random samples per ray - // std::random_device rnd_device; - // use fixed seed, we can backtrack potential issues easier - std::mt19937 mersenne_engine { 131 }; - std::uniform_real_distribution dist { 0, 1 }; - - auto gen = [&dist, &mersenne_engine]() { - return Vec2f(dist(mersenne_engine), dist(mersenne_engine)); - }; - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: generate random samples: start"; - std::vector global_dir_random_samples(ray_count); - generate(begin(global_dir_random_samples), end(global_dir_random_samples), gen); - std::vector local_dir_random_samples(ray_count); - generate(begin(local_dir_random_samples), end(local_dir_random_samples), gen); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: generate random samples: end"; + << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: start"; - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: raycast visibility for " << ray_count << " rays: start"; + float step_size = 1.0f / SeamPlacer::sqr_rays_per_triangle; + std::vector precomputed_sample_directions( + SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); + for (size_t x_idx = 0; x_idx < SeamPlacer::sqr_rays_per_triangle; ++x_idx) { + float sample_x = x_idx * step_size + step_size / 2.0; + for (size_t y_idx = 0; y_idx < SeamPlacer::sqr_rays_per_triangle; ++y_idx) { + size_t dir_index = x_idx * SeamPlacer::sqr_rays_per_triangle + y_idx; + float sample_y = y_idx * step_size + step_size / 2.0; + precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y }); + } + } - std::vector hit_points = tbb::parallel_reduce(tbb::blocked_range(0, ray_count), - std::vector { }, - [&](tbb::blocked_range r, std::vector init) { - for (size_t index = r.begin(); index < r.end(); ++index) { - //generate global ray direction - Vec3f global_ray_dir = sample_sphere_uniform(global_dir_random_samples[index]); - //place the ray origin on the bounding sphere - Vec3f ray_origin = (vision_sphere_center - global_ray_dir * vision_sphere_raidus); - // compute local ray direction as cosine hemisphere sample - the rays dont aim directly to the middle - Vec3f local_dir = sample_power_cosine_hemisphere(local_dir_random_samples[index], SeamPlacer::cosine_hemisphere_sampling_power); + std::vector result(triangles.indices.size()); + tbb::parallel_for(tbb::blocked_range(0, result.size()), + [&](tbb::blocked_range r) { + for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { + FaceVisibilityInfo &dest = result[face_index]; + dest.visibility = 1.0f; + constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); - // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward + + Vec3i face = triangles.indices[face_index]; + Vec3f A = triangles.vertices[face.x()]; + Vec3f B = triangles.vertices[face.y()]; + Vec3f C = triangles.vertices[face.z()]; + Vec3f center = (A + B + C) / 3.0f; + Vec3f normal = ((B - A).cross(C - B)).normalized(); + // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward Frame f; - f.set_from_z(global_ray_dir); - Vec3f final_ray_dir = (f.to_world(local_dir)); + f.set_from_z(normal); - igl::Hit hitpoint; - // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and - // direction. - Vec3d ray_origin_d = ray_origin.cast(); - Vec3d final_ray_dir_d = final_ray_dir.cast(); - bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, - triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); + for (const auto &dir : precomputed_sample_directions) { + Vec3f final_ray_dir = (f.to_world(dir)); + igl::Hit hitpoint; + // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and + // direction. + Vec3d ray_origin_d = (center+0.1*normal).cast(); + Vec3d final_ray_dir_d = final_ray_dir.cast(); + bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, + triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); - if (hit) { - auto face = triangles.indices[hitpoint.id]; - auto edge1 = triangles.vertices[face[1]] - triangles.vertices[face[0]]; - auto edge2 = triangles.vertices[face[2]] - triangles.vertices[face[0]]; - - Vec3f hit_pos = (triangles.vertices[face[0]] + edge1 * hitpoint.u + edge2 * hitpoint.v); - Vec3f surface_normal = its_face_normal(triangles, hitpoint.id); - - init.push_back(HitInfo { hit_pos, surface_normal }); + if (hit) { + dest.visibility -= decrease; + } } } - return init; - }, - [](std::vector left, const std::vector& right) { - left.insert(left.end(), right.begin(), right.end()); - return left; - } - ); + }); BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: raycast visibility for " << ray_count << " rays: end"; + << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: end"; - return hit_points; + return result; } std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, @@ -318,54 +300,41 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, // structure to store global information about the model - occlusion hits, enforcers, blockers struct GlobalModelInfo { - std::vector geometry_raycast_hits; - KDTreeIndirect<3, float, HitInfoCoordinateFunctor> raycast_hits_tree; + indexed_triangle_set model; + AABBTreeIndirect::Tree<3, float> model_tree; + std::vector visiblity_info; indexed_triangle_set enforcers; indexed_triangle_set blockers; AABBTreeIndirect::Tree<3, float> enforcers_tree; AABBTreeIndirect::Tree<3, float> blockers_tree; - GlobalModelInfo() : - raycast_hits_tree(HitInfoCoordinateFunctor { &geometry_raycast_hits }) { - } - bool is_enforced(const Vec3f &position, float radius) const { if (enforcers.empty()) { return false; } + float radius_sqr = radius*radius; return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, - enforcers_tree, position, radius); + enforcers_tree, position, radius_sqr); } bool is_blocked(const Vec3f &position, float radius) const { if (blockers.empty()) { return false; } + float radius_sqr = radius*radius; return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, - blockers_tree, position, radius); + blockers_tree, position, radius_sqr); } float calculate_point_visibility(const Vec3f &position) const { - size_t closest_point_index = find_closest_point(raycast_hits_tree, position); - if (closest_point_index == raycast_hits_tree.npos - || - (position - geometry_raycast_hits[closest_point_index].position).norm() - > SeamPlacer::considered_area_radius) { - return 0; + size_t hit_idx; + Vec3f hit_point; + if (AABBTreeIndirect::squared_distance_to_indexed_triangle_set(model.vertices, model.indices, model_tree, + position, hit_idx, hit_point) >= 0) { + return visiblity_info[hit_idx].visibility; + } else { + return 0.0f; } - auto nearby_points = find_nearby_points(raycast_hits_tree, position, SeamPlacer::considered_area_radius); - Vec3f local_normal = geometry_raycast_hits[closest_point_index].surface_normal; - - float visibility = 0; - for (const auto &hit_point_index : nearby_points) { - float dot_product = local_normal.dot(geometry_raycast_hits[hit_point_index].surface_normal); - if (dot_product > 0) { - float distance = - (position - geometry_raycast_hits[hit_point_index].position).norm(); - visibility += dot_product*gauss(distance, 0.0f, 1.0f, 0.5f); - } - } - return visibility; } @@ -373,44 +342,28 @@ struct GlobalModelInfo { void debug_export(const indexed_triangle_set &obj_mesh, const char *file_name) const { indexed_triangle_set divided_mesh = obj_mesh; // subdivide(obj_mesh, SeamPlacer::considered_area_radius); Slic3r::CNumericLocalesSetter locales_setter; - { - FILE *fp = boost::nowide::fopen(file_name, "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << file_name << " for writing"; - return; - } - for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { - float visibility = calculate_point_visibility(divided_mesh.vertices[i]); - Vec3f color = value_to_rgbf(0, SeamPlacer::expected_hits_per_area, visibility); - fprintf(fp, "v %f %f %f %f %f %f\n", - divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), - color(0), color(1), color(2) - ); - } - for (size_t i = 0; i < divided_mesh.indices.size(); ++i) - fprintf(fp, "f %d %d %d\n", divided_mesh.indices[i][0] + 1, divided_mesh.indices[i][1] + 1, - divided_mesh.indices[i][2] + 1); - fclose(fp); + FILE *fp = boost::nowide::fopen(file_name, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << file_name << " for writing"; + return; } - { - auto fname = std::string("hits_").append(file_name); - FILE *fp = boost::nowide::fopen(fname.c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Couldn't open " << fname << " for writing"; - } - for (size_t i = 0; i < geometry_raycast_hits.size(); ++i) { - Vec3f surface_normal = (geometry_raycast_hits[i].surface_normal + Vec3f(1.0, 1.0, 1.0)) / 2.0; - fprintf(fp, "v %f %f %f %f %f %f \n", geometry_raycast_hits[i].position[0], - geometry_raycast_hits[i].position[1], - geometry_raycast_hits[i].position[2], surface_normal[0], surface_normal[1], - surface_normal[2]); - } - fclose(fp); + for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { + float visibility = calculate_point_visibility(divided_mesh.vertices[i]); + Vec3f color = value_to_rgbf(0.0f, 1.0f, + visibility); + fprintf(fp, "v %f %f %f %f %f %f\n", + divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), + color(0), color(1), color(2) + ); } + for (size_t i = 0; i < divided_mesh.indices.size(); ++i) + fprintf(fp, "f %d %d %d\n", divided_mesh.indices[i][0] + 1, divided_mesh.indices[i][1] + 1, + divided_mesh.indices[i][2] + 1); + fclose(fp); + } #endif } @@ -512,7 +465,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: if (global_model_info.is_enforced(position, distance_to_next) || global_model_info.is_blocked(position, distance_to_next)) { Vec3f vec_to_next = (pos_of_next - position).normalized(); - float step_size = SeamPlacer::enforcer_blocker_distance_tolerance / 2.0f; + float step_size = SeamPlacer::enforcer_blocker_oversampling_distance; float step = step_size; while (step < distance_to_next) { oversampled_points.push(position + vec_to_next * step); @@ -620,16 +573,17 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { its_merge(triangle_set, model_its); } } - - its_transform(triangle_set, obj_transform); float target_error = SeamPlacer::raycasting_decimation_target_error; its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); + triangle_set = subdivide(triangle_set, SeamPlacer::raycasting_subdivision_target_length); + its_transform(triangle_set, obj_transform); auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, triangle_set.indices); - result.geometry_raycast_hits = raycast_visibility(raycasting_tree, triangle_set); - result.raycast_hits_tree.build(result.geometry_raycast_hits.size()); + result.model = triangle_set; + result.model_tree = raycasting_tree; + result.visiblity_info = raycast_visibility(raycasting_tree, triangle_set); BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; @@ -679,9 +633,8 @@ struct SeamComparator { setup(setup) { } - float compute_angle_penalty(float ccw_angle) const { - return gauss(ccw_angle, 0.2f, 1.0f, 0.7f); + return gauss(ccw_angle, 0.2f, 1.0f, 4.0f); } // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage @@ -704,8 +657,8 @@ struct SeamComparator { return a.position.y() > b.position.y(); } - return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) < - (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); + return (a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle) < + (b.visibility + 1.0f) * compute_angle_penalty(b.local_ccw_angle); } // Comparator used during alignment. If there is close potential aligned point, it is comapred to the current @@ -728,17 +681,17 @@ struct SeamComparator { return a.position.y() > b.position.y(); } - return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.8f <= - (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); + return (a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle) * 0.5f <= + (b.visibility + 1.0f) * compute_angle_penalty(b.local_ccw_angle); } + //returns negative value of penalties, should be nromalized against others in the same perimeter for use float get_weight(const SeamCandidate &a) const { if (setup == SeamPosition::spRear) { return a.position.y(); } - //return negative, beacuse we want to minimize the absolute of this value (sort of antiweight). normalization in alignment fixes that with respect to other points - return -(a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle); + return -(a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle); } } ; @@ -776,7 +729,8 @@ void debug_export_points(const std::vector())), visibility_fill); Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_weight(point)); - std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + "," + std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + + "," + std::to_string(weight_color.z()) + ")"; weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); } @@ -1061,7 +1015,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp perimeter->aligned = true; } - for (Vec3f& p : points){ + for (Vec3f &p : points) { p = get_fitted_point(coefficients, p.z()); } @@ -1181,7 +1135,8 @@ void SeamPlacer::init(const Print &print) { << "SeamPlacer: align_seam_points : end"; } - debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), comparator); + debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), + comparator); } } diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index d35fa3bda..a37543bbe 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -65,10 +65,8 @@ struct SeamCandidate { EnforcedBlockedSeamPoint type; }; -// struct to represent hits of the mesh during occulision raycasting. -struct HitInfo { - Vec3f position; - Vec3f surface_normal; +struct FaceVisibilityInfo { + float visibility; }; struct SeamCandidateCoordinateFunctor { @@ -80,38 +78,24 @@ struct SeamCandidateCoordinateFunctor { return seam_candidates->operator[](index).position[dim]; } }; - -struct HitInfoCoordinateFunctor { - HitInfoCoordinateFunctor(std::vector *hit_points) : - hit_points(hit_points) { - } - std::vector *hit_points; - float operator()(size_t index, size_t dim) const { - return hit_points->operator[](index).position[dim]; - } -}; } // namespace SeamPlacerImpl class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - // Rough estimates of hits of the mesh during raycasting per surface circle defined by considered_area_radius - static constexpr float expected_hits_per_area = 1000.0f; - // area considered when computing number of rays and then gathering visiblity info from the hits - static constexpr float considered_area_radius = 3.0f; - // quadric error limit of quadric decimation function used on the mesh before raycasting - static constexpr float raycasting_decimation_target_error = 0.5f; - - // cosine sampling power represents how prefered are forward directions when raycasting from given spot - // in this case, forward direction means towards the center of the mesh - static constexpr float cosine_hemisphere_sampling_power = 5.0f; + static constexpr float raycasting_decimation_target_error = 2.0f; + static constexpr float raycasting_subdivision_target_length = 3.0f; + //square of number of rays per triangle + static constexpr size_t sqr_rays_per_triangle = 8; // arm length used during angles computation static constexpr float polygon_local_angles_arm_distance = 0.4f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer - static constexpr float enforcer_blocker_distance_tolerance = 0.2f; + static constexpr float enforcer_blocker_distance_tolerance = 0.3f; + // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size + static constexpr float enforcer_blocker_oversampling_distance = 0.3f; // When searching for seam clusters for alignment: // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, From 2274965079f5980b623b626e23d61a9a8d81c92c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 9 Mar 2022 11:22:22 +0100 Subject: [PATCH 33/71] alignment from best candidate --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/GCode/SeamPlacerNG.cpp | 351 +++++++++++++++++++-------- src/libslic3r/GCode/SeamPlacerNG.hpp | 28 ++- 3 files changed, 271 insertions(+), 110 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e1d429488..689897bc7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2586,7 +2586,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou loop.split_at(last_pos, false); } else - m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first); + m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first, this->last_pos()); // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 5037916b2..8ab76c976 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -8,6 +8,8 @@ #include #include +#include "Subdivide.hpp" + //For polynomial fitting #include #include @@ -24,7 +26,6 @@ #define DEBUG_FILES #ifdef DEBUG_FILES -#include "Subdivide.hpp" #include #include #endif @@ -200,8 +201,7 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { FaceVisibilityInfo &dest = result[face_index]; dest.visibility = 1.0f; - constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); - + constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); Vec3i face = triangles.indices[face_index]; Vec3f A = triangles.vertices[face.x()]; @@ -218,7 +218,7 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< igl::Hit hitpoint; // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and // direction. - Vec3d ray_origin_d = (center+0.1*normal).cast(); + Vec3d ray_origin_d = (center + normal).cast(); // start one mm above surface. Vec3d final_ray_dir_d = final_ray_dir.cast(); bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); @@ -312,7 +312,7 @@ struct GlobalModelInfo { if (enforcers.empty()) { return false; } - float radius_sqr = radius*radius; + float radius_sqr = radius * radius; return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, enforcers_tree, position, radius_sqr); } @@ -321,7 +321,7 @@ struct GlobalModelInfo { if (blockers.empty()) { return false; } - float radius_sqr = radius*radius; + float radius_sqr = radius * radius; return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, blockers_tree, position, radius_sqr); } @@ -535,27 +535,6 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ return ((p - b).norm() + dist_ab + dist_bc) / 3.0; } -// Pick best seam point based on the given comparator -template -void pick_seam_point(std::vector &perimeter_points, size_t start_index, - const Comparator &comparator) { - size_t end_index = perimeter_points[start_index].perimeter->end_index; - - std::vector indices(end_index + 1 - start_index); - for (size_t index = start_index; index <= end_index; ++index) { - indices[index - start_index] = index; - } - - size_t seam_index = indices[0]; - for (size_t index : indices) { - if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { - seam_index = index; - } - } - - perimeter_points[start_index].perimeter->seam_index = seam_index; -} - // Computes all global model info - transforms object, performs raycasting, // stores enforces and blockers void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { @@ -563,8 +542,8 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; // Build AABB tree for raycasting auto obj_transform = po->trafo(); - auto triangle_set = po->model_object()->raw_indexed_triangle_set(); - //add model parts + indexed_triangle_set triangle_set; + //add all parts for (const ModelVolume *model_volume : po->model_object()->volumes) { if (model_volume->type() == ModelVolumeType::MODEL_PART) { auto model_transformation = model_volume->get_matrix(); @@ -573,6 +552,7 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { its_merge(triangle_set, model_its); } } + float target_error = SeamPlacer::raycasting_decimation_target_error; its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); triangle_set = subdivide(triangle_set, SeamPlacer::raycasting_subdivision_target_length); @@ -634,12 +614,19 @@ struct SeamComparator { } float compute_angle_penalty(float ccw_angle) const { - return gauss(ccw_angle, 0.2f, 1.0f, 4.0f); + // This function is used: + // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) + // looks terribly, but it is gaussian combined with sigmoid, + // so that concave points have much smaller penalty over convex ones + + return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + + 1.0f / (2 + std::exp(-ccw_angle)); // sigmoid, which heavily favourizes concave angles } // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage // should return if a is better seamCandidate than b - bool is_first_better(const SeamCandidate &a, const SeamCandidate &b) const { + bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location = Vec2f { 0.0f, + 0.0f }) const { // Blockers/Enforcers discrimination, top priority if (a.type > b.type) { return true; @@ -649,7 +636,7 @@ struct SeamComparator { } //avoid overhangs - if (a.overhang > 0.3f && b.overhang < a.overhang) { + if (a.overhang > 0.1f && b.overhang < a.overhang) { return false; } @@ -657,8 +644,18 @@ struct SeamComparator { return a.position.y() > b.position.y(); } - return (a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle) < - (b.visibility + 1.0f) * compute_angle_penalty(b.local_ccw_angle); + float distance_penalty_a = 1.0f; + float distance_penalty_b = 1.0f; + if (setup == spNearest) { + distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.001f); + distance_penalty_b = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.001f); + } + + //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) ; total range: (0 - 2.1] + float penalty_a = (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle) * distance_penalty_a; + float penalty_b = (b.visibility + 0.5f) * compute_angle_penalty(b.local_ccw_angle) * distance_penalty_b; + + return penalty_a < penalty_b; } // Comparator used during alignment. If there is close potential aligned point, it is comapred to the current @@ -673,25 +670,32 @@ struct SeamComparator { } //avoid overhangs - if (a.overhang > 0.3f && b.overhang < a.overhang) { + if (a.overhang > 0.1f && b.overhang < a.overhang) { return false; } + if (setup == SeamPosition::spRandom) { + return true; + } + if (setup == SeamPosition::spRear) { return a.position.y() > b.position.y(); } - return (a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle) * 0.5f <= - (b.visibility + 1.0f) * compute_angle_penalty(b.local_ccw_angle); + //ranges: [0 - 1] (0 - 1.3] ; total range: (0 - 1.95]; + float penalty_a = (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle); + float penalty_b = (b.visibility + 0.5f) * compute_angle_penalty(b.local_ccw_angle); + + return penalty_a <= penalty_b || std::abs(penalty_a - penalty_b) < SeamPlacer::seam_align_score_tolerance; } - //returns negative value of penalties, should be nromalized against others in the same perimeter for use - float get_weight(const SeamCandidate &a) const { + //always nonzero, positive + float get_penalty(const SeamCandidate &a) const { if (setup == SeamPosition::spRear) { return a.position.y(); } - return -(a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle); + return (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle); } } ; @@ -715,8 +719,8 @@ void debug_export_points(const std::vector())), visibility_fill); - Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_weight(point)); + Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_penalty(point)); std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + "," + std::to_string(weight_color.z()) + ")"; @@ -738,6 +742,106 @@ void debug_export_points(const std::vector &perimeter_points, size_t start_index, + const SeamComparator &comparator) { + size_t end_index = perimeter_points[start_index].perimeter->end_index; + + size_t seam_index = start_index; + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { + seam_index = index; + } + } + perimeter_points[start_index].perimeter->seam_index = seam_index; +} + +size_t pick_nearest_seam_point_index(const std::vector &perimeter_points, size_t start_index, + const Vec2f &preffered_location) { + size_t end_index = perimeter_points[start_index].perimeter->end_index; + SeamComparator comparator { spNearest }; + + size_t seam_index = start_index; + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index], preffered_location)) { + seam_index = index; + } + } + return seam_index; +} + +// picks random seam point uniformly, respecting enforcers blockers and overhang avoidance. +void pick_random_seam_point(std::vector &perimeter_points, size_t start_index) { + SeamComparator comparator { spRandom }; + + // algorithm keeps a list of viable points and their lengths. If it finds a point + // that is much better than the viable_example_index (e.g. better type, no overhang; see is_first_not_much_worse) + // then it throws away stored lists and starts from start + // in the end, he list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not + // big overhang. + size_t viable_example_index = start_index; + size_t end_index = perimeter_points[start_index].perimeter->end_index; + std::vector viable_indices; + std::vector viable_edges_lengths; + std::vector viable_edges; + + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_not_much_worse(perimeter_points[index], perimeter_points[viable_example_index]) && + comparator.is_first_not_much_worse(perimeter_points[viable_example_index], perimeter_points[index])) { + // index ok, push info into respective vectors + Vec3f edge_to_next; + if (index == end_index) { + edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); + } else + { + edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); + } + float dist_to_next = edge_to_next.norm(); + viable_indices.push_back(index); + viable_edges_lengths.push_back(dist_to_next); + viable_edges.push_back(edge_to_next); + } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index], + perimeter_points[index])) { + // index is worse then viable_example_index, skip this point + } else { + // index is better than viable example index, update example, clear gathered info, start again + // clear up all gathered info, start from scratch, update example index + viable_example_index = index; + viable_indices.clear(); + viable_edges_lengths.clear(); + viable_edges.clear(); + + Vec3f edge_to_next; + if (index == end_index) { + edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); + } else { + edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); + } + float dist_to_next = edge_to_next.norm(); + viable_indices.push_back(index); + viable_edges_lengths.push_back(dist_to_next); + viable_edges.push_back(edge_to_next); + } + } + + // now pick random point from the stored options + float len_sum = std::accumulate(viable_edges_lengths.begin(), viable_edges_lengths.end(), 0.0f); + float picked_len = len_sum * (rand() / (float(RAND_MAX) + 1)); + + size_t point_idx = 0; + while (picked_len - viable_edges_lengths[point_idx] > 0) { + picked_len = picked_len - viable_edges_lengths[point_idx]; + point_idx++; + } + + Perimeter *perimeter = perimeter_points[start_index].perimeter.get(); + perimeter->seam_index = viable_indices[point_idx]; + perimeter->final_seam_position = perimeter_points[perimeter->seam_index].position + + viable_edges[point_idx].normalized() * picked_len; + perimeter->finalized = true; + +} + } // namespace SeamPlacerImpl // Parallel process and extract each perimeter polygon of the given print object. @@ -753,7 +857,8 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + std::vector &layer_candidates = + m_perimeter_points_per_object[po][layer_idx]; const Layer *layer = po->get_layer(layer_idx); auto unscaled_z = layer->slice_z; Polygons polygons = extract_perimeter_polygons(layer); @@ -762,8 +867,9 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, global_model_info); } auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( - functor, layer_candidates.size()); + m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( + functor, layer_candidates.size()); } } ); @@ -823,10 +929,9 @@ void SeamPlacer::calculate_overhangs(const PrintObject *po) { // If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos // Otherwise does nothing, returns false // sadly cannot be const because map access operator[] is not const, since it can create new object -template bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, std::pair &last_point_indexes, - size_t layer_idx, const Comparator &comparator, + size_t layer_idx, const SeamPlacerImpl::SeamComparator &comparator, std::vector> &seam_string, std::vector> &potential_string_seams) { using namespace SeamPlacerImpl; @@ -842,7 +947,7 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, SeamCandidate &closest_point = m_perimeter_points_per_object[po][layer_idx][closest_point_index]; - if (closest_point.perimeter->aligned) { //already aligned, skip + if (closest_point.perimeter->finalized) { //already finalized, skip return false; } @@ -878,8 +983,7 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, // Does not change the positions of the SeamCandidates themselves, instead stores // the new aligned position into the shared Perimeter structure of each perimeter // Note that this position does not necesarilly lay on the perimeter. -template -void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comparator) { +void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator) { using namespace SeamPlacerImpl; // Prepares Debug files for writing. @@ -901,7 +1005,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } #endif - //gahter vector of all seams on the print_object - pair of layer_index and seam__index within that layer + //gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer std::vector> seams; for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { std::vector &layer_perimeter_points = @@ -921,13 +1025,13 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } ); - //align the sema points - start with the best, and check if they are aligned, if yes, skip, else start alignment + //align the seam points - start with the best, and check if they are aligned, if yes, skip, else start alignment for (const std::pair &seam : seams) { size_t layer_idx = seam.first; size_t seam_index = seam.second; std::vector &layer_perimeter_points = m_perimeter_points_per_object[po][layer_idx]; - if (layer_perimeter_points[seam_index].perimeter->aligned) { + if (layer_perimeter_points[seam_index].perimeter->finalized) { // This perimeter is already aligned, skip seam continue; } else { @@ -937,7 +1041,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp int next_layer = layer_idx + 1; std::pair last_point_indexes = std::pair(layer_idx, seam_index); - std::vector> seam_string; + std::vector> seam_string { std::pair(layer_idx, seam_index) }; std::vector> potential_string_seams; //find seams or potential seams in forward direction; there is a budget of skips allowed @@ -969,12 +1073,12 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } if (seam_string.size() + potential_string_seams.size() < seam_align_minimum_string_seams) { - //string long enough to be worth aligning, skip + //string NOT long enough to be worth aligning, skip continue; } // String is long engouh, all string seams and potential string seams gathered, now do the alignment - // first merge potential_string_seams and seam_string; from now on, they all will be aligned + // first merge potential_string_seams and seam_string seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); //sort by layer index std::sort(seam_string.begin(), seam_string.end(), @@ -982,62 +1086,101 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp return left.first < right.first; }); - // gather all positions of seams and their weights + // gather all positions of seams and their weights (weights are derived as negative penalty, they are made positive in next step) std::vector points(seam_string.size()); std::vector weights(seam_string.size()); - float min_weight = comparator.get_weight( + + //init min_weight by the first point + float min_weight = -comparator.get_penalty( m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]); + // In the sorted seam_string array, point which started the alignment - the best candidate + size_t best_candidate_point_index = 0; + + //gather points positions and weights, update min_weight in each step, and find the best candidate for (size_t index = 0; index < seam_string.size(); ++index) { points[index] = m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; - weights[index] = comparator.get_weight( + weights[index] = -comparator.get_penalty( m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); min_weight = std::min(min_weight, weights[index]); + // find the best candidate by comparing the layer indexes + if (seam_string[index].first == layer_idx) { + best_candidate_point_index = index; + } } + //makes all weights positive for (float &w : weights) { - w = w - min_weight + 1.0; //makes all weights positive, nonzero + w = w - min_weight + 0.01; } - // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. - std::vector coefficients = polyfit(points, weights, 4); - - // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into - // Perimeter structure of the point; also set flag aligned to true - for (const auto &pair : seam_string) { - float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); - Vec3f seam_pos = get_fitted_point(coefficients, current_height); - - Perimeter *perimeter = - m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); - perimeter->final_seam_position = seam_pos; - perimeter->aligned = true; - } - - for (Vec3f &p : points) { - p = get_fitted_point(coefficients, p.z()); - } - -// for (size_t iteration = 0; iteration < 20; ++iteration) { + //NOTE: the following commented block does polynomial line fitting of the seam string. + // pre-smoothen by Laplace +// for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { // std::vector new_points(seam_string.size()); // for (int point_index = 0; point_index < points.size(); ++point_index) { // size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; // size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; // // new_points[point_index] = (points[prev_idx] * weights[prev_idx] -// +points[point_index] * weights[point_index]+ points[next_idx] * weights[next_idx]) / -// (weights[prev_idx] + weights[point_index]+ weights[next_idx]); +// + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / +// (weights[prev_idx] + weights[point_index] + weights[next_idx]); // } // points = new_points; // } // -// for (size_t index = 0; index < seam_string.size(); ++index) { +// // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. +// std::vector coefficients = polyfit(points, weights, 4); +// +// // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into +// // Perimeter structure of the point; also set flag aligned to true +// for (const auto &pair : seam_string) { +// float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); +// Vec3f seam_pos = get_fitted_point(coefficients, current_height); +// // Perimeter *perimeter = -// m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].perimeter.get(); -// perimeter->final_seam_position = points[index]; -// perimeter->aligned = true; +// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); +// perimeter->final_seam_position = seam_pos; +// perimeter->finalized = true; // } +// +// for (Vec3f &p : points) { +// p = get_fitted_point(coefficients, p.z()); +// } + + // LaPlace smoothing iterations over the gathered points. New positions from each iteration are stored in the new_points vector + // and assigned to points at the end of iteration + for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { + std::vector new_points(seam_string.size()); + // start from the best candidate, and smoothen down + for (int point_index = best_candidate_point_index; point_index >= 0; --point_index) { + int prev_idx = point_index > 0 ? point_index - 1 : point_index; + size_t next_idx = point_index < int(points.size()) - 1 ? point_index + 1 : point_index; + + new_points[point_index] = (points[prev_idx] * weights[prev_idx] + + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / + (weights[prev_idx] + weights[point_index] + weights[next_idx]); + } + // smoothen up the rest of the points + for (size_t point_index = best_candidate_point_index + 1; point_index < points.size(); ++point_index) { + size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; + size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; + + new_points[point_index] = (points[prev_idx] * weights[prev_idx] + + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / + (weights[prev_idx] + weights[point_index] + weights[next_idx]); + } + points = new_points; + } + + // Assign smoothened posiiton to each participating perimeter and set finalized flag + for (size_t index = 0; index < seam_string.size(); ++index) { + Perimeter *perimeter = + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].perimeter.get(); + perimeter->final_seam_position = points[index]; + perimeter->finalized = true; + } #ifdef DEBUG_FILES auto randf = []() { @@ -1085,7 +1228,7 @@ void SeamPlacer::init(const Print &print) { GlobalModelInfo global_model_info { }; gather_enforcers_blockers(global_model_info, po); - if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) { + if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { compute_global_occlusion(global_model_info, po); } @@ -1095,7 +1238,7 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather_seam_candidates: end"; - if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) { + if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: calculate_candidates_visibility : start"; calculate_candidates_visibility(po, global_model_info); @@ -1119,7 +1262,11 @@ void SeamPlacer::init(const Print &print) { m_perimeter_points_per_object[po][layer_idx]; size_t current = 0; while (current < layer_perimeter_points.size()) { - pick_seam_point(layer_perimeter_points, current, comparator); + if (configured_seam_preference == spRandom) { + pick_random_seam_point(layer_perimeter_points, current); + } else { + pick_seam_point(layer_perimeter_points, current, comparator); + } current = layer_perimeter_points[current].perimeter->end_index + 1; } } @@ -1127,7 +1274,7 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: pick_seam_point : end"; - if (configured_seam_preference != spRandom) { + if (configured_seam_preference == spAligned) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: align_seam_points : start"; align_seam_points(po, comparator); @@ -1135,20 +1282,22 @@ void SeamPlacer::init(const Print &print) { << "SeamPlacer: align_seam_points : end"; } +#ifdef DEBUG_FILES debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), comparator); +#endif } } -void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first) { +void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const { using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); //NOTE this is necessary, since layer->id() is quite unreliable size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); double unscaled_z = layer->slice_z; - const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; - const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; + const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object.find(po)->second[layer_index]; + const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second[layer_index]; const Point &fp = loop.first_point(); @@ -1156,11 +1305,19 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); const Perimeter *perimeter = perimeter_points[closest_perimeter_point_index].perimeter.get(); - Vec3f seam_position = perimeter_points[perimeter->seam_index].position; - if (perimeter->aligned) { - seam_position = perimeter->final_seam_position; + + size_t seam_index; + if (po->config().seam_position == spNearest) { + seam_index = pick_nearest_seam_point_index(perimeter_points, perimeter->start_index, + unscale(last_pos).cast()); + } else { + seam_index = perimeter->seam_index; } + Vec3f seam_position = perimeter_points[seam_index].position; + if (perimeter->finalized) { + seam_position = perimeter->final_seam_position; + } Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); if (!loop.split_at_vertex(seam_point)) diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index a37543bbe..422164c7e 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -27,6 +27,7 @@ class Grid; namespace SeamPlacerImpl { struct GlobalModelInfo; +struct SeamComparator; enum class EnforcedBlockedSeamPoint { Blocked = 0, @@ -40,9 +41,10 @@ struct Perimeter { size_t end_index; //inclusive! size_t seam_index; - // During alignment, a final position may be stored here. In that case, aligned is set to true. + // During alignment, a final position may be stored here. In that case, finalized is set to true. // Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position - bool aligned = false; + // Random position also uses this flexibility to set final seam point position + bool finalized = false; Vec3f final_seam_position; }; @@ -84,20 +86,22 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float raycasting_decimation_target_error = 2.0f; - static constexpr float raycasting_subdivision_target_length = 3.0f; + static constexpr float raycasting_decimation_target_error = 1.0f; + static constexpr float raycasting_subdivision_target_length = 1.0f; //square of number of rays per triangle - static constexpr size_t sqr_rays_per_triangle = 8; + static constexpr size_t sqr_rays_per_triangle = 7; // arm length used during angles computation - static constexpr float polygon_local_angles_arm_distance = 0.4f; + static constexpr float polygon_local_angles_arm_distance = 0.5f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer static constexpr float enforcer_blocker_distance_tolerance = 0.3f; // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size - static constexpr float enforcer_blocker_oversampling_distance = 0.3f; + static constexpr float enforcer_blocker_oversampling_distance = 0.1f; // When searching for seam clusters for alignment: + // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer + static constexpr float seam_align_score_tolerance = 0.6f; // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, //it belongs automaticaly to the cluster static constexpr float seam_align_tolerable_dist = 0.5f; @@ -106,6 +110,8 @@ public: static constexpr size_t seam_align_tolerable_skips = 4; // minimum number of seams needed in cluster to make alignemnt happen static constexpr size_t seam_align_minimum_string_seams = 6; + // iterations of laplace smoothing + static constexpr size_t seam_align_laplace_smoothing_iterations = 20; //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer @@ -115,19 +121,17 @@ public: void init(const Print &print); - void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first); + void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point& last_pos) const; private: void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_overhangs(const PrintObject *po); - template - void align_seam_points(const PrintObject *po, const Comparator &comparator); - template + void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); bool find_next_seam_in_layer(const PrintObject *po, std::pair &last_point, - size_t layer_idx, const Comparator &comparator, + size_t layer_idx,const SeamPlacerImpl::SeamComparator &comparator, std::vector> &seam_strings, std::vector> &outliers); }; From c640fb854f276b07a7b93a1e2b094022e6e210b2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 9 Mar 2022 12:02:18 +0100 Subject: [PATCH 34/71] bug fix: using trafo() instead of trafo_centred() caused misalignment between occlusion mesh and seam candidates --- src/libslic3r/GCode/SeamPlacerNG.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 8ab76c976..d585bb09c 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -541,7 +541,7 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; // Build AABB tree for raycasting - auto obj_transform = po->trafo(); + auto obj_transform = po->trafo_centered(); indexed_triangle_set triangle_set; //add all parts for (const ModelVolume *model_volume : po->model_object()->volumes) { From 6dbc7149bea1c9b3f87048d7455dcb36642c85b2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 9 Mar 2022 13:26:36 +0100 Subject: [PATCH 35/71] parameter fixes, alignemnt for enforcers simplified --- src/libslic3r/GCode/SeamPlacerNG.cpp | 30 ++++++++++++++++++---------- src/libslic3r/GCode/SeamPlacerNG.hpp | 5 +++-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index d585bb09c..1442c00f6 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -647,13 +647,15 @@ struct SeamComparator { float distance_penalty_a = 1.0f; float distance_penalty_b = 1.0f; if (setup == spNearest) { - distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.001f); - distance_penalty_b = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.001f); + distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.01f); + distance_penalty_b = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.01f); } - //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) ; total range: (0 - 2.1] - float penalty_a = (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle) * distance_penalty_a; - float penalty_b = (b.visibility + 0.5f) * compute_angle_penalty(b.local_ccw_angle) * distance_penalty_b; + //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) + float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle) + * distance_penalty_a; + float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle) + * distance_penalty_b; return penalty_a < penalty_b; } @@ -662,6 +664,14 @@ struct SeamComparator { // sema point of the perimeter, to find out if the aligned point is not much worse than the current seam bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { // Blockers/Enforcers discrimination, top priority + if (a.type == EnforcedBlockedSeamPoint::Enforced) { + return true; + } + + if (a.type == EnforcedBlockedSeamPoint::Blocked) { + return false; + } + if (a.type > b.type) { return true; } @@ -682,9 +692,9 @@ struct SeamComparator { return a.position.y() > b.position.y(); } - //ranges: [0 - 1] (0 - 1.3] ; total range: (0 - 1.95]; - float penalty_a = (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle); - float penalty_b = (b.visibility + 0.5f) * compute_angle_penalty(b.local_ccw_angle); + //ranges: [0 - 1] (0 - 1.3] ; + float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); + float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle); return penalty_a <= penalty_b || std::abs(penalty_a - penalty_b) < SeamPlacer::seam_align_score_tolerance; } @@ -695,7 +705,7 @@ struct SeamComparator { return a.position.y(); } - return (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle); + return (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); } } ; @@ -1130,7 +1140,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // points = new_points; // } // -// // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. + // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. // std::vector coefficients = polyfit(points, weights, 4); // // // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index 422164c7e..3883ba004 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -87,12 +87,13 @@ public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; static constexpr float raycasting_decimation_target_error = 1.0f; - static constexpr float raycasting_subdivision_target_length = 1.0f; + static constexpr float raycasting_subdivision_target_length = 2.0f; //square of number of rays per triangle static constexpr size_t sqr_rays_per_triangle = 7; // arm length used during angles computation static constexpr float polygon_local_angles_arm_distance = 0.5f; + static constexpr float additional_angle_importance = 0.3f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer static constexpr float enforcer_blocker_distance_tolerance = 0.3f; @@ -101,7 +102,7 @@ public: // When searching for seam clusters for alignment: // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer - static constexpr float seam_align_score_tolerance = 0.6f; + static constexpr float seam_align_score_tolerance = 0.5f; // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, //it belongs automaticaly to the cluster static constexpr float seam_align_tolerable_dist = 0.5f; From 177a1fd54a93fdf1bec1c048ee545f8fa5f78ba5 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 9 Mar 2022 15:32:36 +0100 Subject: [PATCH 36/71] finalize integration into GCode.cpp export functions, remove unused edge grids --- src/libslic3r/CMakeLists.txt | 8 +- src/libslic3r/GCode.cpp | 52 +- src/libslic3r/GCode.hpp | 8 +- src/libslic3r/GCode/SeamPlacer.cpp | 2194 +++++++++++++---------- src/libslic3r/GCode/SeamPlacer.hpp | 198 +- src/libslic3r/GCode/SeamPlacerNG.cpp | 1339 -------------- src/libslic3r/GCode/SeamPlacerNG.hpp | 142 -- src/libslic3r/{GCode => }/Subdivide.cpp | 6 +- src/libslic3r/{GCode => }/Subdivide.hpp | 2 +- 9 files changed, 1377 insertions(+), 2572 deletions(-) delete mode 100644 src/libslic3r/GCode/SeamPlacerNG.cpp delete mode 100644 src/libslic3r/GCode/SeamPlacerNG.hpp rename src/libslic3r/{GCode => }/Subdivide.cpp (97%) rename src/libslic3r/{GCode => }/Subdivide.hpp (63%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6e0679291..6ec40f42a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -118,10 +118,8 @@ set(SLIC3R_SOURCES GCode/PrintExtents.hpp GCode/SpiralVase.cpp GCode/SpiralVase.hpp - GCode/SeamPlacerNG.cpp - GCode/SeamPlacerNG.hpp - GCode/Subdivide.hpp - GCode/Subdivide.cpp + GCode/SeamPlacer.cpp + GCode/SeamPlacer.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp GCode/WipeTower.cpp @@ -227,6 +225,8 @@ set(SLIC3R_SOURCES SlicesToTriangleMesh.cpp SlicingAdaptive.cpp SlicingAdaptive.hpp + Subdivide.cpp + Subdivide.hpp SupportMaterial.cpp SupportMaterial.hpp Surface.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 689897bc7..aed2ff53f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2312,7 +2312,6 @@ GCode::LayerResult GCode::process_layer( } // for objects // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. - std::vector> lower_layer_edge_grids(layers.size()); for (unsigned int extruder_id : layer_tools.extruders) { gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? @@ -2406,9 +2405,9 @@ GCode::LayerResult GCode::process_layer( //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. if (print.config().infill_first) { gcode += this->extrude_infill(print, by_region_specific, false); - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + gcode += this->extrude_perimeters(print, by_region_specific); } else { - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + gcode += this->extrude_perimeters(print, by_region_specific); gcode += this->extrude_infill(print,by_region_specific, false); } // ironing @@ -2543,39 +2542,11 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } - - -static std::unique_ptr calculate_layer_edge_grid(const Layer& layer) -{ - auto out = make_unique(); - - // Create the distance field for a layer below. - const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); - out->create(layer.lslices, distance_field_resolution); - out->calculate_sdf(); -#if 0 - { - static int iRun = 0; - BoundingBox bbox = (*lower_layer_edge_grid)->bbox(); - bbox.min(0) -= scale_(5.f); - bbox.min(1) -= scale_(5.f); - bbox.max(0) += scale_(5.f); - bbox.max(1) += scale_(5.f); - EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++)); - } -#endif - return out; -} - - -std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr *lower_layer_edge_grid) +std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed) { // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation - if (m_layer->lower_layer && lower_layer_edge_grid != nullptr && ! *lower_layer_edge_grid) - *lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer); - // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); @@ -2675,14 +2646,14 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string return gcode; } -std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, std::unique_ptr *lower_layer_edge_grid) +std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed) { if (const ExtrusionPath* path = dynamic_cast(&entity)) return this->extrude_path(*path, description, speed); else if (const ExtrusionMultiPath* multipath = dynamic_cast(&entity)) return this->extrude_multi_path(*multipath, description, speed); else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) - return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid); + return this->extrude_loop(*loop, description, speed); else throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); return ""; @@ -2703,24 +2674,15 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou } // Extrude perimeters: Decide where to put seams (hide or align seams). -std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region, std::unique_ptr &lower_layer_edge_grid) +std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region) { std::string gcode; for (const ObjectByExtruder::Island::Region ®ion : by_region) if (! region.perimeters.empty()) { m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - // plan_perimeters tries to place seams, it needs to have the lower_layer_edge_grid calculated already. - if (m_layer->lower_layer && ! lower_layer_edge_grid) - lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer); - -// m_seam_placer.plan_perimeters(std::vector(region.perimeters.begin(), region.perimeters.end()), -// *m_layer, m_config.seam_position, this->last_pos(), EXTRUDER_CONFIG(nozzle_diameter), -// (m_layer == NULL ? nullptr : m_layer->object()), -// (lower_layer_edge_grid ? lower_layer_edge_grid.get() : nullptr)); - for (const ExtrusionEntity* ee : region.perimeters) - gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid); + gcode += this->extrude_entity(*ee, "perimeter", -1.); } return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 44f4b2271..8732e797a 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -14,7 +14,7 @@ #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" -#include "GCode/SeamPlacerNG.hpp" +#include "GCode/SeamPlacer.hpp" #include "GCode/GCodeProcessor.hpp" #include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" @@ -274,8 +274,8 @@ private: void set_extruders(const std::vector &extruder_ids); std::string preamble(); std::string change_layer(coordf_t print_z); - std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1., std::unique_ptr *lower_layer_edge_grid = nullptr); - std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1., std::unique_ptr *lower_layer_edge_grid = nullptr); + std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1.); + std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1.); std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); @@ -342,7 +342,7 @@ private: // For sequential print, the instance of the object to be printing has to be defined. const size_t single_object_instance_idx); - std::string extrude_perimeters(const Print &print, const std::vector &by_region, std::unique_ptr &lower_layer_edge_grid); + std::string extrude_perimeters(const Print &print, const std::vector &by_region); std::string extrude_infill(const Print &print, const std::vector &by_region, bool ironing); std::string extrude_support(const ExtrusionEntityCollection &support_fills); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index bcd238a72..1fd7a6002 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1,1011 +1,1327 @@ #include "SeamPlacer.hpp" +#include "tbb/parallel_for.h" +#include "tbb/blocked_range.h" +#include "tbb/parallel_reduce.h" +#include +#include +#include +#include + + +//For polynomial fitting +#include +#include + #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/EdgeGrid.hpp" #include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/SVG.hpp" #include "libslic3r/Layer.hpp" +#include "libslic3r/QuadricEdgeCollapse.hpp" +#include "libslic3r/Subdivide.hpp" + +//#define DEBUG_FILES + +#ifdef DEBUG_FILES +#include +#include +#endif namespace Slic3r { -// This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers). -static constexpr float ENFORCER_BLOCKER_PENALTY = 100; +namespace SeamPlacerImpl { -// In case there are custom enforcers/blockers, the loop polygon shall always have -// sides smaller than this (so it isn't limited to original resolution). -static constexpr float MINIMAL_POLYGON_SIDE = scaled(0.2f); - -// When spAligned is active and there is a support enforcer, -// add this penalty to its center. -static constexpr float ENFORCER_CENTER_PENALTY = -10.f; - - - -// This function was introduced in 2016 to assign penalties to overhangs. -// LukasM thinks that it discriminated a bit too much, so especially external -// seams were than placed in funny places (non-overhangs were preferred too much). -// He implemented his own version (below) which applies fixed penalty for really big overlaps. -// static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) -// { -// // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. -// // Solved by sympy package: -// /* -// from sympy import * -// (x,a,b,c,d,r,z)=symbols('x a b c d r z') -// p = a + b*x + c*x*x + d*x*x*x -// p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) -// from sympy.plotting import plot -// plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) -// */ -// if (overlap_distance < - nozzle_r) { -// // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. -// return 0.f; -// } else { -// float x = overlap_distance / nozzle_r; -// float x2 = x * x; -// float x3 = x2 * x; -// return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); -// } -// } -static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) -{ - return overlap_distance > nozzle_r ? weight_zero : 0.f; +// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) +// checkout e.g. here: https://www.geogebra.org/calculator +float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { + float shifted = value - mean_x_coord; + float denominator = falloff_speed * shifted * shifted + 1.0f; + float exponent = 1.0f / denominator; + return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); } - - -// Return a value in <0, 1> of a cubic B-spline kernel centered around zero. -// The B-spline is re-scaled so it has value 1 at zero. -static inline float bspline_kernel(float x) -{ - x = std::abs(x); - if (x < 1.f) { - return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; - } - else if (x < 2.f) { - x -= 1.f; - float x2 = x * x; - float x3 = x2 * x; - return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; - } - else - return 0; +Vec3f value_to_rgbf(float minimum, float maximum, float value) { + float ratio = 2.0f * (value - minimum) / (maximum - minimum); + float b = std::max(0.0f, (1.0f - ratio)); + float r = std::max(0.0f, (ratio - 1.0f)); + float g = 1.0f - b - r; + return Vec3f { r, g, b }; } - - -static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) -{ - assert(polygon.points.size() >= 2); - if (polygon.points.size() <= 1) - if (polygon.points.size() == 1) - return polygon.points.begin(); - - Point pt_min; - double d_min = std::numeric_limits::max(); - size_t i_min = size_t(-1); - - for (size_t i = 0; i < polygon.points.size(); ++ i) { - size_t j = i + 1; - if (j == polygon.points.size()) - j = 0; - const Point &p1 = polygon.points[i]; - const Point &p2 = polygon.points[j]; - const Slic3r::Point v_seg = p2 - p1; - const Slic3r::Point v_pt = pt - p1; - const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1)); - int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1)); - if (t_pt < 0) { - // Closest to p1. - double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1))); - if (dabs < d_min) { - d_min = dabs; - i_min = i; - pt_min = p1; - } - } - else if (t_pt > l2_seg) { - // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. - continue; - } else { - // Closest to the segment. - assert(t_pt >= 0 && t_pt <= l2_seg); - int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1)); - double d = double(d_seg) / sqrt(double(l2_seg)); - double dabs = std::abs(d); - if (dabs < d_min) { - d_min = dabs; - i_min = i; - // Evaluate the foot point. - pt_min = p1; - double linv = double(d_seg) / double(l2_seg); - pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5)); - pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); - assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); - } - } - } - - assert(i_min != size_t(-1)); - if ((pt_min - polygon.points[i_min]).cast().norm() > eps) { - // Insert a new point on the segment i_min, i_min+1. - return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); - } - return polygon.points.begin() + i_min; +Vec3i value_rgbi(float minimum, float maximum, float value) { + return (value_to_rgbf(minimum, maximum, value) * 255).cast(); } +//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 +// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y +std::vector polyfit(const std::vector &points, const std::vector &weights, size_t order) { + // check to make sure inputs are correct + assert(points.size() >= order + 1); + assert(points.size() == weights.size()); + std::vector squared_weights(weights.size()); + for (size_t index = 0; index < weights.size(); ++index) { + squared_weights[index] = sqrt(weights[index]); + } -static std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) -{ - assert(polygon.points.size() + 1 == lengths.size()); - if (min_arm_length > 0.25f * lengths.back()) - min_arm_length = 0.25f * lengths.back(); + Eigen::VectorXf V0(points.size()); + Eigen::VectorXf V1(points.size()); + Eigen::VectorXf V2(points.size()); + for (size_t index = 0; index < points.size(); index++) { + V0(index) = points[index].x() * squared_weights[index]; + V1(index) = points[index].y() * squared_weights[index]; + V2(index) = points[index].z(); + } - // Find the initial prev / next point span. - size_t idx_prev = polygon.points.size(); - size_t idx_curr = 0; - size_t idx_next = 1; - while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) - -- idx_prev; - while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) - ++ idx_next; - - std::vector angles(polygon.points.size(), 0.f); - for (; idx_curr < polygon.points.size(); ++ idx_curr) { - // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. - if (idx_prev >= idx_curr) { - while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) - ++ idx_prev; - if (idx_prev == polygon.points.size()) - idx_prev = 0; + // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial + Eigen::MatrixXf T(points.size(), order + 1); + // Populate the matrix + for (size_t i = 0; i < points.size(); ++i) + { + for (size_t j = 0; j < order + 1; ++j) + { + T(i, j) = pow(V2(i), j) * squared_weights[i]; } - while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) - ++ idx_prev; - // Move idx_prev one step back. - if (idx_prev == 0) - idx_prev = polygon.points.size() - 1; - else - -- idx_prev; - // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. - if (idx_curr <= idx_next) { - while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) - ++ idx_next; - if (idx_next == polygon.points.size()) - idx_next = 0; + } + + // Solve for linear least square fit + const auto QR = T.householderQr(); + Eigen::VectorXf result0 = QR.solve(V0); + Eigen::VectorXf result1 = QR.solve(V1); + std::vector coeff { order + 1 }; + for (size_t k = 0; k < order + 1; k++) { + coeff[k] = Vec2f { result0[k], result1[k] }; + } + return coeff; +} + +Vec3f get_fitted_point(const std::vector &coefficients, float z) { + size_t order = coefficients.size() - 1; + float fitted_x = 0; + float fitted_y = 0; + for (size_t index = 0; index < order + 1; ++index) { + float z_pow = pow(z, index); + fitted_x += coefficients[index].x() * z_pow; + fitted_y += coefficients[index].y() * z_pow; + } + + return Vec3f { fitted_x, fitted_y, z }; +} + +/// Coordinate frame +class Frame { +public: + Frame() { + mX = Vec3f(1, 0, 0); + mY = Vec3f(0, 1, 0); + mZ = Vec3f(0, 0, 1); + } + + Frame(const Vec3f &x, const Vec3f &y, const Vec3f &z) : + mX(x), mY(y), mZ(z) { + } + + void set_from_z(const Vec3f &z) { + mZ = z.normalized(); + Vec3f tmpZ = mZ; + Vec3f tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3f(0, 1, 0) : Vec3f(1, 0, 0); + mY = (tmpZ.cross(tmpX)).normalized(); + mX = mY.cross(tmpZ); + } + + Vec3f to_world(const Vec3f &a) const { + return a.x() * mX + a.y() * mY + a.z() * mZ; + } + + Vec3f to_local(const Vec3f &a) const { + return Vec3f(mX.dot(a), mY.dot(a), mZ.dot(a)); + } + + const Vec3f& binormal() const { + return mX; + } + + const Vec3f& tangent() const { + return mY; + } + + const Vec3f& normal() const { + return mZ; + } + +private: + Vec3f mX, mY, mZ; +}; + +Vec3f sample_sphere_uniform(const Vec2f &samples) { + float term1 = 2.0f * float(PI) * samples.x(); + float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term1) * term2, sin(term1) * term2, + 1.0f - 2.0f * samples.y()}; +} + +Vec3f sample_hemisphere_uniform(const Vec2f &samples) { + float term1 = 2.0f * float(PI) * samples.x(); + float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term1) * term2, sin(term1) * term2, + abs(1.0f - 2.0f * samples.y())}; +} + +Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { + float term1 = 2.f * float(PI) * samples.x(); + float term2 = pow(samples.y(), 1.f / (power + 1.f)); + float term3 = sqrt(1.f - term2 * term2); + + return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); +} + +std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, + const indexed_triangle_set &triangles) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: start"; + + float step_size = 1.0f / SeamPlacer::sqr_rays_per_triangle; + std::vector precomputed_sample_directions( + SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); + for (size_t x_idx = 0; x_idx < SeamPlacer::sqr_rays_per_triangle; ++x_idx) { + float sample_x = x_idx * step_size + step_size / 2.0; + for (size_t y_idx = 0; y_idx < SeamPlacer::sqr_rays_per_triangle; ++y_idx) { + size_t dir_index = x_idx * SeamPlacer::sqr_rays_per_triangle + y_idx; + float sample_y = y_idx * step_size + step_size / 2.0; + precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y }); } - while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) - ++ idx_next; + } + + std::vector result(triangles.indices.size()); + tbb::parallel_for(tbb::blocked_range(0, result.size()), + [&](tbb::blocked_range r) { + for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { + FaceVisibilityInfo &dest = result[face_index]; + dest.visibility = 1.0f; + constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); + + Vec3i face = triangles.indices[face_index]; + Vec3f A = triangles.vertices[face.x()]; + Vec3f B = triangles.vertices[face.y()]; + Vec3f C = triangles.vertices[face.z()]; + Vec3f center = (A + B + C) / 3.0f; + Vec3f normal = ((B - A).cross(C - B)).normalized(); + // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward + Frame f; + f.set_from_z(normal); + + for (const auto &dir : precomputed_sample_directions) { + Vec3f final_ray_dir = (f.to_world(dir)); + igl::Hit hitpoint; + // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and + // direction. + Vec3d ray_origin_d = (center + normal).cast(); // start one mm above surface. + Vec3d final_ray_dir_d = final_ray_dir.cast(); + bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, + triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); + + if (hit) { + dest.visibility -= decrease; + } + } + } + }); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: end"; + + return result; +} + +std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, + float min_arm_length) { + std::vector result(polygon.size()); + + if (polygon.size() == 1) { + result[0] = 0.0f; + } + + auto make_idx_circular = [&](int index) { + while (index < 0) { + index += polygon.size(); + } + return index % polygon.size(); + }; + + int idx_prev = 0; + int idx_curr = 0; + int idx_next = 0; + + float distance_to_prev = 0; + float distance_to_next = 0; + + //push idx_prev far enough back as initialization + while (distance_to_prev < min_arm_length) { + idx_prev = make_idx_circular(idx_prev - 1); + distance_to_prev += lengths[idx_prev]; + } + + for (size_t _i = 0; _i < polygon.size(); ++_i) { + // pull idx_prev to current as much as possible, while respecting the min_arm_length + while (distance_to_prev - lengths[idx_prev] > min_arm_length) { + distance_to_prev -= lengths[idx_prev]; + idx_prev = make_idx_circular(idx_prev + 1); + } + + //push idx_next forward as far as needed + while (distance_to_next < min_arm_length) { + distance_to_next += lengths[idx_next]; + idx_next = make_idx_circular(idx_next + 1); + } + // Calculate angle between idx_prev, idx_curr, idx_next. const Point &p0 = polygon.points[idx_prev]; const Point &p1 = polygon.points[idx_curr]; const Point &p2 = polygon.points[idx_next]; - const Point v1 = p1 - p0; - const Point v2 = p2 - p1; - int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1)); - int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0)); - float angle = float(atan2(double(cross), double(dot))); - angles[idx_curr] = angle; + const Point v1 = p1 - p0; + const Point v2 = p2 - p1; + int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); + int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); + float angle = float(atan2(float(cross), float(dot))); + result[idx_curr] = angle; + + // increase idx_curr by one + float curr_distance = lengths[idx_curr]; + idx_curr++; + distance_to_prev += curr_distance; + distance_to_next -= curr_distance; } - return angles; + return result; } +// structure to store global information about the model - occlusion hits, enforcers, blockers +struct GlobalModelInfo { + indexed_triangle_set model; + AABBTreeIndirect::Tree<3, float> model_tree; + std::vector visiblity_info; + indexed_triangle_set enforcers; + indexed_triangle_set blockers; + AABBTreeIndirect::Tree<3, float> enforcers_tree; + AABBTreeIndirect::Tree<3, float> blockers_tree; - -void SeamPlacer::init(const Print& print) -{ - m_enforcers.clear(); - m_blockers.clear(); - m_seam_history.clear(); - m_po_list.clear(); - - const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; - float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); - - - std::vector temp_enf; - std::vector temp_blk; - std::vector temp_polygons; - - for (const PrintObject* po : print.objects()) { - - auto merge_and_offset = [po, &temp_polygons, max_nozzle_dmr](EnforcerBlockerType type, std::vector& out) { - // Offset the triangles out slightly. - auto offset_out = [](Polygon& input, float offset) -> ExPolygons { - ClipperLib::Paths out(1); - std::vector deltas(input.points.size(), offset); - input.make_counter_clockwise(); - out.front() = mittered_offset_path_scaled(input.points, deltas, 3.); - return ClipperPaths_to_Slic3rExPolygons(out, true); // perform union - }; - - - temp_polygons.clear(); - po->project_and_append_custom_facets(true, type, temp_polygons); - out.clear(); - out.reserve(temp_polygons.size()); - float offset = scale_(max_nozzle_dmr + po->config().elefant_foot_compensation); - for (Polygons &src : temp_polygons) { - out.emplace_back(ExPolygons()); - for (Polygon& plg : src) { - ExPolygons offset_explg = offset_out(plg, offset); - if (! offset_explg.empty()) - out.back().emplace_back(std::move(offset_explg.front())); - } - - offset = scale_(max_nozzle_dmr); - } - }; - merge_and_offset(EnforcerBlockerType::BLOCKER, temp_blk); - merge_and_offset(EnforcerBlockerType::ENFORCER, temp_enf); - - // Remember this PrintObject and initialize a store of enforcers and blockers for it. - m_po_list.push_back(po); - size_t po_idx = m_po_list.size() - 1; - m_enforcers.emplace_back(std::vector(temp_enf.size())); - m_blockers.emplace_back(std::vector(temp_blk.size())); - - // A helper class to store data to build the AABB tree from. - class CustomTriangleRef { - public: - CustomTriangleRef(size_t idx, - Point&& centroid, - BoundingBox&& bb) - : m_idx{idx}, m_centroid{centroid}, - m_bbox{AlignedBoxType(bb.min, bb.max)} - {} - size_t idx() const { return m_idx; } - const Point& centroid() const { return m_centroid; } - const TreeType::BoundingBox& bbox() const { return m_bbox; } - - private: - size_t m_idx; - Point m_centroid; - AlignedBoxType m_bbox; - }; - - // A lambda to extract the ExPolygons and save them into the member AABB tree. - // Will be called for enforcers and blockers separately. - auto add_custom = [](std::vector& src, std::vector& dest) { - // Go layer by layer, and append all the ExPolygons into the AABB tree. - size_t layer_idx = 0; - for (ExPolygons& expolys_on_layer : src) { - CustomTrianglesPerLayer& layer_data = dest[layer_idx]; - std::vector triangles_data; - layer_data.polys.reserve(expolys_on_layer.size()); - triangles_data.reserve(expolys_on_layer.size()); - - for (ExPolygon& expoly : expolys_on_layer) { - if (expoly.empty()) - continue; - layer_data.polys.emplace_back(std::move(expoly)); - triangles_data.emplace_back(layer_data.polys.size() - 1, - layer_data.polys.back().centroid(), - layer_data.polys.back().bounding_box()); - } - // All polygons are saved, build the AABB tree for them. - layer_data.tree.build(std::move(triangles_data)); - ++layer_idx; - } - }; - - add_custom(temp_enf, m_enforcers.at(po_idx)); - add_custom(temp_blk, m_blockers.at(po_idx)); - } -} - - - -void SeamPlacer::plan_perimeters(const std::vector perimeters, - const Layer& layer, SeamPosition seam_position, - Point last_pos, coordf_t nozzle_dmr, const PrintObject* po, - const EdgeGrid::Grid* lower_layer_edge_grid) -{ - // When printing the perimeters, we want the seams on external and internal perimeters to match. - // We have a list of perimeters in the order to be printed. Each internal perimeter must inherit - // the seam from the previous external perimeter. - - m_plan.clear(); - m_plan_idx = 0; - - if (perimeters.empty() || ! po) - return; - - m_plan.resize(perimeters.size()); - - for (int i = 0; i < int(perimeters.size()); ++i) { - if (perimeters[i]->role() == erExternalPerimeter && perimeters[i]->is_loop()) { - last_pos = this->calculate_seam( - layer, seam_position, *dynamic_cast(perimeters[i]), nozzle_dmr, - po, lower_layer_edge_grid, last_pos, false); - m_plan[i].external = true; + bool is_enforced(const Vec3f &position, float radius) const { + if (enforcers.empty()) { + return false; } - m_plan[i].seam_position = seam_position; - m_plan[i].layer = &layer; - m_plan[i].po = po; - m_plan[i].pt = last_pos; - } -} - - -void SeamPlacer::place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter, - const EdgeGrid::Grid* lower_layer_edge_grid) -{ - // const double seam_offset = nozzle_diameter; - - Point seam = last_pos; - if (! m_plan.empty() && m_plan_idx < m_plan.size()) { - if (m_plan[m_plan_idx].external) { - seam = m_plan[m_plan_idx].pt; - // One more heuristics: if the seam is too far from current nozzle position, - // try to place it again. This can happen in cases where the external perimeter - // does not belong to the preceding ones and they are ordered so they end up - // far from each other. - if ((seam.cast() - last_pos.cast()).squaredNorm() > std::pow(scale_(5.*nozzle_diameter), 2.)) - seam = this->calculate_seam(*m_plan[m_plan_idx].layer, m_plan[m_plan_idx].seam_position, loop, nozzle_diameter, - m_plan[m_plan_idx].po, lower_layer_edge_grid, last_pos, false); - - if (m_plan[m_plan_idx].seam_position == spAligned) - m_seam_history.add_seam(m_plan[m_plan_idx].po, m_plan[m_plan_idx].pt, loop.polygon().bounding_box()); - } - else { - if (!external_first) { - // Internal perimeter printed before the external. - // First get list of external seams. - std::vector ext_seams; - size_t external_cnt = 0; - for (size_t i = 0; i < m_plan.size(); ++i) { - if (m_plan[i].external) { - ext_seams.emplace_back(i); - ++external_cnt; - } - } - - if (!ext_seams.empty()) { - // First find the line segment closest to an external seam: - //int path_idx = 0; - //int line_idx = 0; - size_t ext_seam_idx = size_t(-1); - double min_dist_sqr = std::numeric_limits::max(); - std::vector lines_vect; - for (int i = 0; i < int(loop.paths.size()); ++i) { - lines_vect.emplace_back(loop.paths[i].polyline.lines()); - const Lines& lines = lines_vect.back(); - for (int j = 0; j < int(lines.size()); ++j) { - for (size_t k : ext_seams) { - double d_sqr = lines[j].distance_to_squared(m_plan[k].pt); - if (d_sqr < min_dist_sqr) { - //path_idx = i; - //line_idx = j; - ext_seam_idx = k; - min_dist_sqr = d_sqr; - } - } - } - } - - // Only accept seam that is reasonably close. - if (ext_seam_idx != size_t(-1)) { - // How many nozzle diameters is considered "close"? - const double nozzle_d_limit = 2. * (1. + m_plan.size() / external_cnt); - const double limit_dist_sqr = double(scale_(scale_((unscale(m_plan[ext_seam_idx].pt) - unscale(m_plan[m_plan_idx].pt)).squaredNorm() * std::pow(nozzle_d_limit * nozzle_diameter, 2.)))); - - if (min_dist_sqr < limit_dist_sqr) { - // Now find a projection of the external seam - //const Lines& lines = lines_vect[path_idx]; - //Point closest = m_plan[ext_seam_idx].pt.projection_onto(lines[line_idx]); - - // This code does staggering of internal perimeters, turned off for now. - // - // double dist = (closest.cast() - lines[line_idx].b.cast()).norm(); - // - // // And walk along the perimeter until we make enough space for - // // seams of all perimeters beforethe external one. - // double offset = (ext_seam_idx - m_plan_idx) * scale_(seam_offset); - // double last_offset = offset; - // offset -= dist; - // const Point* a = &closest; - // const Point* b = &lines[line_idx].b; - // while (++line_idx < int(lines.size()) && offset > 0.) { - // last_offset = offset; - // offset -= lines[line_idx].length(); - // a = &lines[line_idx].a; - // b = &lines[line_idx].b; - // } - // - // // We have walked far enough, too far maybe. Interpolate on the - // // last segment to find the end precisely. - // offset = std::min(0., offset); // In case that offset is still positive (we may have "wrapped around") - // double ratio = last_offset / (last_offset - offset); - // seam = (a->cast() + ((b->cast() - a->cast()) * ratio)).cast(); - seam = m_plan[ext_seam_idx].pt; - } - } - } - } - else { - // We should have a candidate ready from before. If not, use last_pos. - if (m_plan_idx > 0 && m_plan[m_plan_idx - 1].precalculated) - seam = m_plan[m_plan_idx - 1].pt; - } - - // seam now contains a hot candidate for internal seam. Use it unless there is a sharp corner nearby. - // We will call the normal seam planning function, pretending that we are currently at the candidate point - // and set to spNearest. If the ideal seam it finds is close to current candidate, use it. - // This is to prevent having seams very close to corners, just because of external seam position. - seam = calculate_seam(*m_plan[m_plan_idx].layer, spNearest, loop, nozzle_diameter, - m_plan[m_plan_idx].po, lower_layer_edge_grid, seam, true); - } - m_plan[m_plan_idx].pt = seam; + float radius_sqr = radius * radius; + return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, + enforcers_tree, position, radius_sqr); } - - // Split the loop at the point with a minium penalty. - if (!loop.split_at_vertex(seam)) - // The point is not in the original loop. Insert it. - loop.split_at(seam, true); - - if (external_first && m_plan_idx+1 1) { -// const ExtrusionPath& last = loop.paths.back(); -// auto it = last.polyline.points.crbegin() + 1; -// for (; it != last.polyline.points.crend(); ++it) { -// running_sqr += (it->cast() - (it - 1)->cast()).squaredNorm(); -// if (running_sqr > dist_sqr) -// break; -// running_sqr_last = running_sqr; -// } -// if (running_sqr <= dist_sqr) -// it = last.polyline.points.crend() - 1; -// // Now interpolate. -// double ratio = (std::sqrt(dist_sqr) - std::sqrt(running_sqr_last)) / (std::sqrt(running_sqr) - std::sqrt(running_sqr_last)); -// m_plan[m_plan_idx + 1].pt = ((it - 1)->cast() + (it->cast() - (it - 1)->cast()) * std::min(ratio, 1.)).cast(); -// m_plan[m_plan_idx + 1].precalculated = true; - m_plan[m_plan_idx + 1].pt = m_plan[m_plan_idx].pt; - m_plan[m_plan_idx + 1].precalculated = true; -// } + bool is_blocked(const Vec3f &position, float radius) const { + if (blockers.empty()) { + return false; + } + float radius_sqr = radius * radius; + return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, + blockers_tree, position, radius_sqr); } - ++m_plan_idx; -} - - -// Returns "best" seam for a given perimeter. -Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_position, - const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po, - const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos, bool prefer_nearest) -{ - Polygon polygon = loop.polygon(); - bool was_clockwise = polygon.make_counter_clockwise(); - const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); - - size_t po_idx = std::find(m_po_list.begin(), m_po_list.end(), po) - m_po_list.begin(); - - // Find current layer in respective PrintObject. Cache the result so the - // lookup is only done once per layer, not for each loop. - const Layer* layer_po = nullptr; - if (po == m_last_po && layer.print_z == m_last_print_z) - layer_po = m_last_layer_po; - else { - layer_po = po ? po->get_layer_at_printz(layer.print_z) : nullptr; - m_last_po = po; - m_last_print_z = layer.print_z; - m_last_layer_po = layer_po; - } - if (! layer_po) - return last_pos; - - // Index of this layer in the respective PrintObject. - size_t layer_idx = layer_po->id() - po->layers().front()->id(); // raft layers - - assert(layer_idx < po->layer_count()); - - const bool custom_seam = loop.role() == erExternalPerimeter && this->is_custom_seam_on_layer(layer_idx, po_idx); - - if (custom_seam) { - // Seam enf/blockers can begin and end in between the original vertices. - // Let add extra points in between and update the leghths. - polygon.densify(MINIMAL_POLYGON_SIDE); - } - - if (seam_position != spRandom) { - // Retrieve the last start position for this object. - float last_pos_weight = 1.f; - - if (seam_position == spAligned) { - // Seam is aligned to the seam at the preceding layer. - if (po != nullptr) { - std::optional pos = m_seam_history.get_last_seam(m_po_list[po_idx], layer_idx, loop.polygon().bounding_box()); - if (pos.has_value()) { - last_pos = *pos; - last_pos_weight = is_custom_enforcer_on_layer(layer_idx, po_idx) ? 0.f : 1.f; - } - } - } - else if (seam_position == spRear) { - // Object is centered around (0,0) in its current coordinate system. - last_pos.x() = 0; - last_pos.y() = coord_t(3. * po->bounding_box().radius()); - last_pos_weight = 5.f; - } if (seam_position == spNearest) { - // last_pos already contains current nozzle position - } - - // Insert a projection of last_pos into the polygon. - size_t last_pos_proj_idx; - { - auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); - last_pos_proj_idx = it - polygon.points.begin(); - } - - // Parametrize the polygon by its length. - std::vector lengths = polygon.parameter_by_length(); - - // For each polygon point, store a penalty. - // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. - std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); - // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. - const float penaltyConvexVertex = 1.f; - const float penaltyFlatSurface = 3.f; - const float penaltyOverhangHalf = 10.f; - // Penalty for visible seams. - for (size_t i = 0; i < polygon.points.size(); ++ i) { - float ccwAngle = penalties[i]; - if (was_clockwise) - ccwAngle = - ccwAngle; - float penalty = 0; - if (ccwAngle <- float(0.6 * PI)) - // Sharp reflex vertex. We love that, it hides the seam perfectly. - penalty = 0.f; - else if (ccwAngle > float(0.6 * PI)) - // Seams on sharp convex vertices are more visible than on reflex vertices. - penalty = penaltyConvexVertex; - else if (ccwAngle < 0.f) { - // Interpolate penalty between maximum and zero. - penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } else { - assert(ccwAngle >= 0.f); - // Interpolate penalty between maximum and the penalty for a convex vertex. - penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } - // Give a negative penalty for points close to the last point or the prefered seam location. - float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? - std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : - std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); - float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr - penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); - penalties[i] = std::max(0.f, penalty); - if (prefer_nearest) { - // This hack limits the search around the nearest position projection. - penalties[i] += dist_to_last_pos_proj > 6.f * nozzle_r ? 100.f : 0.f; - } - } - - // Penalty for overhangs. - if (lower_layer_edge_grid) { - // Use the edge grid distance field structure over the lower layer to calculate overhangs. - coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); - coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); - for (size_t i = 0; i < polygon.points.size(); ++ i) { - const Point &p = polygon.points[i]; - coordf_t dist; - // Signed distance is positive outside the object, negative inside the object. - // The point is considered at an overhang, if it is more than nozzle radius - // outside of the lower layer contour. - [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist); - // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, - // then the signed distnace shall always be known. - assert(found); - penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); - } - } - - // Custom seam. Huge (negative) constant penalty is applied inside - // blockers (enforcers) to rule out points that should not win. - if (custom_seam) - this->apply_custom_seam(polygon, po_idx, penalties, lengths, layer_idx, seam_position); - - // Find a point with a minimum penalty. - size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - - if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx, po_idx)) { - // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. - // In that case use last_pos_proj_idx instead. - float penalty_aligned = penalties[last_pos_proj_idx]; - float penalty_min = penalties[idx_min]; - float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); - float penalty_max = std::max(std::abs(penalty_min), std::abs(penalty_aligned)); - float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; - // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); - if (std::abs(penalty_diff_rel) < 0.05) { - // Penalty of the aligned point is very close to the minimum penalty. - // Align the seams as accurately as possible. - idx_min = last_pos_proj_idx; - } - } - - - // Export the contour into a SVG file. - #if 0 - { - static int iRun = 0; - SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); - if (m_layer->lower_layer != NULL) - svg.draw(m_layer->lower_layer->slices); - for (size_t i = 0; i < loop.paths.size(); ++ i) - svg.draw(loop.paths[i].as_polyline(), "red"); - Polylines polylines; - for (size_t i = 0; i < loop.paths.size(); ++ i) - polylines.push_back(loop.paths[i].as_polyline()); - Slic3r::Polygons polygons; - coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); - coord_t delta = scale_(0.5*nozzle_dmr); - Slic3r::offset(polylines, &polygons, delta); -// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); - svg.draw(last_pos, "green", 3); - svg.draw(polygon.points[idx_min], "yellow", 3); - svg.Close(); - } - #endif - return polygon.points[idx_min]; - - } else - return this->get_random_seam(layer_idx, polygon, po_idx); -} - - -Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, size_t po_idx, - bool* saw_custom) const -{ - // Parametrize the polygon by its length. - const std::vector lengths = polygon.parameter_by_length(); - - // Which of the points are inside enforcers/blockers? - std::vector enforcers_idxs; - std::vector blockers_idxs; - this->get_enforcers_and_blockers(layer_idx, polygon, po_idx, enforcers_idxs, blockers_idxs); - - bool has_enforcers = ! enforcers_idxs.empty(); - bool has_blockers = ! blockers_idxs.empty(); - if (saw_custom) - *saw_custom = has_enforcers || has_blockers; - - assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end())); - assert(std::is_sorted(blockers_idxs.begin(), blockers_idxs.end())); - std::vector edges; - - // Lambda to calculate lengths of all edges of interest. Last parameter - // decides whether to measure edges inside or outside idxs. - // Negative number = not an edge of interest. - auto get_valid_length = [&lengths](const std::vector& idxs, - std::vector& edges, - bool measure_inside_edges) -> float - { - // First mark edges we are interested in by assigning a positive number. - edges.assign(lengths.size()-1, measure_inside_edges ? -1.f : 1.f); - for (size_t i=0; i the edge between them is the enforcer/blocker. - bool inside_edge = ((i != idxs.size()-1 && idxs[i+1] == this_pt_idx + 1) - || (i == idxs.size()-1 && idxs.back() == lengths.size()-2 && idxs[0] == 0)); - if (inside_edge) - edges[this_pt_idx] = measure_inside_edges ? 1.f : -1.f; - } - // Now measure them. - float running_total = 0.f; - for (size_t i=0; i 0.f) { - edges[i] = lengths[i+1] - lengths[i]; - running_total += edges[i]; - } - } - return running_total; - }; - - // Find all seam candidate edges and their lengths. - float valid_length = 0.f; - if (has_enforcers) - valid_length = get_valid_length(enforcers_idxs, edges, true); - - if (! has_enforcers || valid_length == 0.f) { - // Second condition covers case with isolated enf points. Given how the painted - // triangles are projected, this should not happen. Stay on the safe side though. - if (has_blockers) - valid_length = get_valid_length(blockers_idxs, edges, false); - if (valid_length == 0.f) // No blockers or everything blocked - use the whole polygon. - valid_length = lengths.back(); - } - assert(valid_length != 0.f); - // Now generate a random length and find the respective edge. - float rand_len = valid_length * (rand()/float(RAND_MAX)); - size_t pt_idx = 0; // Index of the edge where to put the seam. - if (valid_length == lengths.back()) { - // Whole polygon is used for placing the seam. - auto it = std::lower_bound(lengths.begin(), lengths.end(), rand_len); - pt_idx = it == lengths.begin() ? 0 : (it-lengths.begin()-1); // this takes care of a corner case where rand() returns 0 - } else { - float running = 0.f; - for (size_t i=0; i 0.f ? edges[i] : 0.f; - if (running >= rand_len) { - pt_idx = i; - break; - } - } - } - - if (! has_enforcers && ! has_blockers) { - // The polygon may be too coarse, calculate the point exactly. - assert(valid_length == lengths.back()); - bool last_seg = pt_idx == polygon.points.size()-1; - size_t next_idx = last_seg ? 0 : pt_idx+1; - const Point& prev = polygon.points[pt_idx]; - const Point& next = polygon.points[next_idx]; - assert(next_idx == 0 || pt_idx+1 == next_idx); - coordf_t diff_x = next.x() - prev.x(); - coordf_t diff_y = next.y() - prev.y(); - coordf_t dist = lengths[last_seg ? pt_idx+1 : next_idx] - lengths[pt_idx]; - return Point(prev.x() + (rand_len - lengths[pt_idx]) * (diff_x/dist), - prev.y() + (rand_len - lengths[pt_idx]) * (diff_y/dist)); - - } else { - // The polygon should be dense enough. - return polygon.points[pt_idx]; - } -} - - - - - - - - -void SeamPlacer::get_enforcers_and_blockers(size_t layer_id, - const Polygon& polygon, - size_t po_idx, - std::vector& enforcers_idxs, - std::vector& blockers_idxs) const -{ - enforcers_idxs.clear(); - blockers_idxs.clear(); - - auto is_inside = [](const Point& pt, - const CustomTrianglesPerLayer& custom_data) -> bool { - assert(! custom_data.polys.empty()); - // Now ask the AABB tree which polygons we should check and check them. - std::vector candidates; - AABBTreeIndirect::get_candidate_idxs(custom_data.tree, pt, candidates); - if (! candidates.empty()) - for (size_t idx : candidates) - if (custom_data.polys[idx].contains(pt)) - return true; - return false; - }; - - if (! m_enforcers[po_idx].empty()) { - const CustomTrianglesPerLayer& enforcers = m_enforcers[po_idx][layer_id]; - if (! enforcers.polys.empty()) { - for (size_t i=0; i find_enforcer_centers(const Polygon& polygon, - const std::vector& lengths, - const std::vector& enforcers_idxs) -{ - std::vector out; - assert(polygon.points.size()+1 == lengths.size()); - assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end())); - if (polygon.size() < 2 || enforcers_idxs.empty()) - return out; - - auto get_center_idx = [&lengths](size_t start_idx, size_t end_idx) -> size_t { - assert(end_idx >= start_idx); - if (start_idx == end_idx) - return start_idx; - float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]); - auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c); - int ret = it - lengths.begin(); - return ret; - }; - - int last_enforcer_start_idx = enforcers_idxs.front(); - bool first_pt_in_list = enforcers_idxs.front() != 0; - bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1; - bool wrap_around = last_pt_in_list && first_pt_in_list; - - for (size_t i=0; i= 0) { + return visiblity_info[hit_idx].visibility; } else { - if (! wrap_around) { - // we can safely use the last enforcer point. - out.push_back(get_center_idx(last_enforcer_start_idx, enforcers_idxs[i])); + return 0.0f; + } + + } + +#ifdef DEBUG_FILES + void debug_export(const indexed_triangle_set &obj_mesh, const char *file_name) const { + indexed_triangle_set divided_mesh = obj_mesh; + Slic3r::CNumericLocalesSetter locales_setter; + + FILE *fp = boost::nowide::fopen(file_name, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { + float visibility = calculate_point_visibility(divided_mesh.vertices[i]); + Vec3f color = value_to_rgbf(0.0f, 1.0f, + visibility); + fprintf(fp, "v %f %f %f %f %f %f\n", + divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), + color(0), color(1), color(2) + ); + } + for (size_t i = 0; i < divided_mesh.indices.size(); ++i) + fprintf(fp, "f %d %d %d\n", divided_mesh.indices[i][0] + 1, divided_mesh.indices[i][1] + 1, + divided_mesh.indices[i][2] + 1); + fclose(fp); + + } +#endif +} +; + +//Extract perimeter polygons of the given layer +Polygons extract_perimeter_polygons(const Layer *layer) { + Polygons polygons; + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters + for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { + if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { + Points p; + perimeter->collect_points(p); + polygons.emplace_back(p); + } + } + if (polygons.empty()) { + Points p; + ex_entity->collect_points(p); + polygons.emplace_back(p); + } + } else { + Points p; + ex_entity->collect_points(p); + polygons.emplace_back(p); } } } - if (wrap_around) { - // Update first center already found. - if (out.empty()) { - // Probably an enforcer around the whole contour. Return nothing. - return out; - } - - // find last point of the enforcer at the beginning: - size_t idx = 0; - while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1]) - ++idx; - - float t_s = lengths[last_enforcer_start_idx]; - float t_e = lengths[idx]; - float half_dist = 0.5f * (t_e + lengths.back() - t_s); - float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist; - - auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); - out[0] = it - lengths.begin(); - if (out[0] == lengths.size() - 1) - --out[0]; - assert(out[0] < lengths.size() - 1); + if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point + // it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway + polygons.emplace_back(std::vector { Point { 0, 0 } }); } - return out; + + return polygons; } - - -void SeamPlacer::apply_custom_seam(const Polygon& polygon, size_t po_idx, - std::vector& penalties, - const std::vector& lengths, - int layer_id, SeamPosition seam_position) const -{ - if (! is_custom_seam_on_layer(layer_id, po_idx)) +// Insert SeamCandidates created from perimeter polygons in to the result vector. +// Compute its type (Enfrocer,Blocker), angle, and position +//each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon +// if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions +void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, + const GlobalModelInfo &global_model_info) { + if (orig_polygon.size() == 0) { return; - - std::vector enforcers_idxs; - std::vector blockers_idxs; - this->get_enforcers_and_blockers(layer_id, polygon, po_idx, enforcers_idxs, blockers_idxs); - - for (size_t i : enforcers_idxs) { - assert(i < penalties.size()); - penalties[i] -= float(ENFORCER_BLOCKER_PENALTY); } - for (size_t i : blockers_idxs) { - assert(i < penalties.size()); - penalties[i] += float(ENFORCER_BLOCKER_PENALTY); + + Polygon polygon = orig_polygon; + bool was_clockwise = polygon.make_counter_clockwise(); + + std::vector lengths { }; + for (size_t point_idx = 0; point_idx < polygon.size() - 1; ++point_idx) { + lengths.push_back(std::max((unscale(polygon[point_idx]) - unscale(polygon[point_idx + 1])).norm(), 0.01)); } - if (seam_position == spAligned) { - std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); - for (size_t idx : enf_centers) { - assert(idx < penalties.size()); - penalties[idx] += ENFORCER_CENTER_PENALTY; + lengths.push_back(std::max((unscale(polygon[0]) - unscale(polygon[polygon.size() - 1])).norm(), 0.01)); + + std::vector local_angles = calculate_polygon_angles_at_vertices(polygon, lengths, + SeamPlacer::polygon_local_angles_arm_distance); + std::shared_ptr perimeter = std::make_shared(); + + std::queue orig_polygon_points { }; + for (size_t index = 0; index < polygon.size(); ++index) { + Vec2f unscaled_p = unscale(polygon[index]).cast(); + orig_polygon_points.emplace(unscaled_p.x(), unscaled_p.y(), z_coord); + } + Vec3f first = orig_polygon_points.front(); + std::queue oversampled_points { }; + size_t orig_angle_index = 0; + perimeter->start_index = result_vec.size(); + while (!orig_polygon_points.empty() || !oversampled_points.empty()) { + EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; + Vec3f position; + float local_ccw_angle = 0; + bool orig_point = false; + if (!oversampled_points.empty()) { + position = oversampled_points.front(); + oversampled_points.pop(); + } else { + position = orig_polygon_points.front(); + orig_polygon_points.pop(); + local_ccw_angle = was_clockwise ? -local_angles[orig_angle_index] : local_angles[orig_angle_index]; + orig_angle_index++; + orig_point = true; + } + + if (global_model_info.is_enforced(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { + type = EnforcedBlockedSeamPoint::Enforced; + } + + if (global_model_info.is_blocked(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { + type = EnforcedBlockedSeamPoint::Blocked; + } + + if (orig_point) { + Vec3f pos_of_next = orig_polygon_points.empty() ? first : orig_polygon_points.front(); + float distance_to_next = (position - pos_of_next).norm(); + if (global_model_info.is_enforced(position, distance_to_next) + || global_model_info.is_blocked(position, distance_to_next)) { + Vec3f vec_to_next = (pos_of_next - position).normalized(); + float step_size = SeamPlacer::enforcer_blocker_oversampling_distance; + float step = step_size; + while (step < distance_to_next) { + oversampled_points.push(position + vec_to_next * step); + step += step_size; + } + } + } + + result_vec.emplace_back(position, perimeter, local_ccw_angle, type); + + } + + perimeter->end_index = result_vec.size() - 1; +} + +// Get index of previous and next perimeter point of the layer. Because SeamCandidates of all polygons of the given layer +// are sequentially stored in the vector, each perimeter contains info about start and end index. These vales are used to +// deduce index of previous and next neigbour in the corresponding perimeter. +std::pair find_previous_and_next_perimeter_point(const std::vector &perimeter_points, + size_t point_index) { + const SeamCandidate ¤t = perimeter_points[point_index]; + int prev = point_index - 1; //for majority of points, it is true that neighbours lie behind and in front of them in the vector + int next = point_index + 1; + + if (point_index == current.perimeter->start_index) { + // if point_index is equal to start, it means that the previous neighbour is at the end + prev = current.perimeter->end_index; + } + + if (point_index == current.perimeter->end_index) { + // if point_index is equal to end, than next neighbour is at the start + next = current.perimeter->start_index; + } + + assert(prev >= 0); + assert(next >= 0); + return {size_t(prev),size_t(next)}; +} + +//NOTE: only rough esitmation of overhang distance +// value represents distance from edge, positive is overhang, negative is inside shape +float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b, + const SeamCandidate &under_c) { + auto p = Vec2d { point.position.x(), point.position.y() }; + auto a = Vec2d { under_a.position.x(), under_a.position.y() }; + auto b = Vec2d { under_b.position.x(), under_b.position.y() }; + auto c = Vec2d { under_c.position.x(), under_c.position.y() }; + + auto oriented_line_dist = [](const Vec2d a, const Vec2d b, const Vec2d p) { //signed distance from line + return -((b.x() - a.x()) * (a.y() - p.y()) - (a.x() - p.x()) * (b.y() - a.y())) / (a - b).norm(); + }; + + auto dist_ab = oriented_line_dist(a, b, p); + auto dist_bc = oriented_line_dist(b, c, p); + + // from angle and signed distances from the arms of the points on the previous layer, we + // can deduce if it is overhang and give estimation of the size. + // However, the size of the overhang is rough estimation, the sign is more reliable + if (under_b.local_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside + return -((p - b).norm() + dist_ab + dist_bc) / 3.0; + } + + if (under_b.local_ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside + return -((p - b).norm() + dist_ab + dist_bc) / 3.0; + } + + return ((p - b).norm() + dist_ab + dist_bc) / 3.0; +} + +// Computes all global model info - transforms object, performs raycasting, +// stores enforces and blockers +void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; +// Build AABB tree for raycasting + auto obj_transform = po->trafo_centered(); + indexed_triangle_set triangle_set; + //add all parts + for (const ModelVolume *model_volume : po->model_object()->volumes) { + if (model_volume->type() == ModelVolumeType::MODEL_PART) { + auto model_transformation = model_volume->get_matrix(); + indexed_triangle_set model_its = model_volume->mesh().its; + its_transform(model_its, model_transformation); + its_merge(triangle_set, model_its); } } -#if 0 - std::ostringstream os; - os << std::setw(3) << std::setfill('0') << layer_id; - int a = scale_(30.); - SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); - if (! m_enforcers[po_idx].empty()) - svg.draw(m_enforcers[po_idx][layer_id].polys, "blue"); - if (! m_blockers[po_idx].empty()) - svg.draw(m_blockers[po_idx][layer_id].polys, "red"); + float target_error = SeamPlacer::raycasting_decimation_target_error; + its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); + triangle_set = its_subdivide(triangle_set, SeamPlacer::raycasting_subdivision_target_length); + its_transform(triangle_set, obj_transform); - if (! blockers_idxs.empty()) { - ; + auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, + triangle_set.indices); + + result.model = triangle_set; + result.model_tree = raycasting_tree; + result.visiblity_info = raycast_visibility(raycasting_tree, triangle_set); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; + +#ifdef DEBUG_FILES + auto filename = debug_out_path(("visiblity_of_" + std::to_string(po->id().id) + ".obj").c_str()); + result.debug_export(triangle_set, filename.c_str()); +#endif +} + +void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; + + auto obj_transform = po->trafo(); + + for (const ModelVolume *mv : po->model_object()->volumes) { + if (mv->is_seam_painted()) { + auto model_transformation = mv->get_matrix(); + + indexed_triangle_set enforcers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER); + its_transform(enforcers, model_transformation); + its_merge(result.enforcers, enforcers); + + indexed_triangle_set blockers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER); + its_transform(blockers, model_transformation); + its_merge(result.blockers, blockers); + } + } + its_transform(result.enforcers, obj_transform); + its_transform(result.blockers, obj_transform); + + result.enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.enforcers.vertices, + result.enforcers.indices); + result.blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.blockers.vertices, + result.blockers.indices); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; +} + +//Comparator of seam points. It has two necessary methods: is_first_better and is_first_not_much_worse +struct SeamComparator { + SeamPosition setup; + + SeamComparator(SeamPosition setup) : + setup(setup) { } + float compute_angle_penalty(float ccw_angle) const { + // This function is used: + // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) + // looks terribly, but it is gaussian combined with sigmoid, + // so that concave points have much smaller penalty over convex ones - size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - - for (size_t i=0; i b.type) { + return true; + } + if (b.type > a.type) { + return false; + } + + //avoid overhangs + if (a.overhang > 0.1f && b.overhang < a.overhang) { + return false; + } + + if (setup == SeamPosition::spRear) { + return a.position.y() > b.position.y(); + } + + float distance_penalty_a = 1.0f; + float distance_penalty_b = 1.0f; + if (setup == spNearest) { + distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); + distance_penalty_b = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); + } + + //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) + float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle) + * distance_penalty_a; + float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle) + * distance_penalty_b; + + return penalty_a < penalty_b; + } + + // Comparator used during alignment. If there is close potential aligned point, it is comapred to the current + // sema point of the perimeter, to find out if the aligned point is not much worse than the current seam + bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { + // Blockers/Enforcers discrimination, top priority + if (a.type == EnforcedBlockedSeamPoint::Enforced) { + return true; + } + + if (a.type == EnforcedBlockedSeamPoint::Blocked) { + return false; + } + + if (a.type > b.type) { + return true; + } + if (b.type > a.type) { + return false; + } + + //avoid overhangs + if (a.overhang > 0.1f && b.overhang < a.overhang) { + return false; + } + + if (setup == SeamPosition::spRandom) { + return true; + } + + if (setup == SeamPosition::spRear) { + return a.position.y() > b.position.y(); + } + + //ranges: [0 - 1] (0 - 1.3] ; + float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); + float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle); + + return penalty_a <= penalty_b || std::abs(penalty_a - penalty_b) < SeamPlacer::seam_align_score_tolerance; + } + + //always nonzero, positive + float get_penalty(const SeamCandidate &a) const { + if (setup == SeamPosition::spRear) { + return a.position.y(); + } + + return (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); + } +} +; + +#ifdef DEBUG_FILES +void debug_export_points(const std::vector> &object_perimter_points, + const BoundingBox &bounding_box, std::string object_name, const SeamComparator &comparator) { + for (size_t layer_idx = 0; layer_idx < object_perimter_points.size(); ++layer_idx) { + std::string angles_file_name = debug_out_path((object_name + "_angles_" + std::to_string(layer_idx) + ".svg").c_str()); + SVG angles_svg { angles_file_name, bounding_box }; + float min_vis = 0; + float max_vis = min_vis; + + float min_weight = std::numeric_limits::min(); + float max_weight = min_weight; + + for (const SeamCandidate &point : object_perimter_points[layer_idx]) { + Vec3i color = value_rgbi(-PI, PI, point.local_ccw_angle); + std::string fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," + + std::to_string(color.z()) + ")"; + angles_svg.draw(scaled(Vec2f(point.position.head<2>())), fill); + min_vis = std::min(min_vis, point.visibility); + max_vis = std::max(max_vis, point.visibility); + + min_weight = std::min(min_weight, -comparator.get_penalty(point)); + max_weight = std::max(max_weight, -comparator.get_penalty(point)); + + } + + std::string visiblity_file_name = debug_out_path((object_name + "_visibility_" + std::to_string(layer_idx) + ".svg").c_str()); + SVG visibility_svg { visiblity_file_name, bounding_box }; + std::string weights_file_name = debug_out_path((object_name + "_weight_" + std::to_string(layer_idx) + ".svg").c_str()); + SVG weight_svg {weights_file_name, bounding_box }; + for (const SeamCandidate &point : object_perimter_points[layer_idx]) { + Vec3i color = value_rgbi(min_vis, max_vis, point.visibility); + std::string visibility_fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," + + std::to_string(color.z()) + ")"; + visibility_svg.draw(scaled(Vec2f(point.position.head<2>())), visibility_fill); + + Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_penalty(point)); + std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + + "," + + std::to_string(weight_color.z()) + ")"; + weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); + } + } +} +#endif + +// Pick best seam point based on the given comparator +void pick_seam_point(std::vector &perimeter_points, size_t start_index, + const SeamComparator &comparator) { + size_t end_index = perimeter_points[start_index].perimeter->end_index; + + size_t seam_index = start_index; + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { + seam_index = index; + } + } + perimeter_points[start_index].perimeter->seam_index = seam_index; +} + +size_t pick_nearest_seam_point_index(const std::vector &perimeter_points, size_t start_index, + const Vec2f &preffered_location) { + size_t end_index = perimeter_points[start_index].perimeter->end_index; + SeamComparator comparator { spNearest }; + + size_t seam_index = start_index; + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index], preffered_location)) { + seam_index = index; + } + } + return seam_index; +} + +// picks random seam point uniformly, respecting enforcers blockers and overhang avoidance. +void pick_random_seam_point(std::vector &perimeter_points, size_t start_index) { + SeamComparator comparator { spRandom }; + + // algorithm keeps a list of viable points and their lengths. If it finds a point + // that is much better than the viable_example_index (e.g. better type, no overhang; see is_first_not_much_worse) + // then it throws away stored lists and starts from start + // in the end, he list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not + // big overhang. + size_t viable_example_index = start_index; + size_t end_index = perimeter_points[start_index].perimeter->end_index; + std::vector viable_indices; + std::vector viable_edges_lengths; + std::vector viable_edges; + + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_not_much_worse(perimeter_points[index], perimeter_points[viable_example_index]) && + comparator.is_first_not_much_worse(perimeter_points[viable_example_index], perimeter_points[index])) { + // index ok, push info into respective vectors + Vec3f edge_to_next; + if (index == end_index) { + edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); + } else + { + edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); + } + float dist_to_next = edge_to_next.norm(); + viable_indices.push_back(index); + viable_edges_lengths.push_back(dist_to_next); + viable_edges.push_back(edge_to_next); + } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index], + perimeter_points[index])) { + // index is worse then viable_example_index, skip this point + } else { + // index is better than viable example index, update example, clear gathered info, start again + // clear up all gathered info, start from scratch, update example index + viable_example_index = index; + viable_indices.clear(); + viable_edges_lengths.clear(); + viable_edges.clear(); + + Vec3f edge_to_next; + if (index == end_index) { + edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); + } else { + edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); + } + float dist_to_next = edge_to_next.norm(); + viable_indices.push_back(index); + viable_edges_lengths.push_back(dist_to_next); + viable_edges.push_back(edge_to_next); + } + } + + // now pick random point from the stored options + float len_sum = std::accumulate(viable_edges_lengths.begin(), viable_edges_lengths.end(), 0.0f); + float picked_len = len_sum * (rand() / (float(RAND_MAX) + 1)); + + size_t point_idx = 0; + while (picked_len - viable_edges_lengths[point_idx] > 0) { + picked_len = picked_len - viable_edges_lengths[point_idx]; + point_idx++; + } + + Perimeter *perimeter = perimeter_points[start_index].perimeter.get(); + perimeter->seam_index = viable_indices[point_idx]; + perimeter->final_seam_position = perimeter_points[perimeter->seam_index].position + + viable_edges[point_idx].normalized() * picked_len; + perimeter->finalized = true; + +} + +} // namespace SeamPlacerImpl + +// Parallel process and extract each perimeter polygon of the given print object. +// Gather SeamCandidates of each layer into vector and build KDtree over them +// Store results in the SeamPlacer varaibles m_perimeter_points_per_object and m_perimeter_points_trees_per_object +void SeamPlacer::gather_seam_candidates(const PrintObject *po, + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { + using namespace SeamPlacerImpl; + + m_perimeter_points_per_object.emplace(po, po->layer_count()); + m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); + + tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_candidates = + m_perimeter_points_per_object[po][layer_idx]; + const Layer *layer = po->get_layer(layer_idx); + auto unscaled_z = layer->slice_z; + Polygons polygons = extract_perimeter_polygons(layer); + for (const auto &poly : polygons) { + process_perimeter_polygon(poly, unscaled_z, layer_candidates, + global_model_info); + } + auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; + m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( + functor, layer_candidates.size()); + } + } + ); +} + +void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, + const SeamPlacerImpl::GlobalModelInfo &global_model_info) { + using namespace SeamPlacerImpl; + + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + perimeter_point.visibility = global_model_info.calculate_point_visibility( + perimeter_point.position); + } + } + }); +} + +void SeamPlacer::calculate_overhangs(const PrintObject *po) { + using namespace SeamPlacerImpl; + + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + const auto calculate_layer_overhang = [&](size_t other_layer_idx) { + size_t closest_supporter = find_closest_point( + *m_perimeter_points_trees_per_object[po][other_layer_idx], + perimeter_point.position); + const SeamCandidate &supporter_point = + m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; + + auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); + const SeamCandidate &prev_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; + const SeamCandidate &next_point = + m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; + + return calculate_overhang(perimeter_point, prev_point, + supporter_point, next_point); + }; + + if (layer_idx > 0) { //calculate overhang + perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); + } + } + } + }); + } + +// Estimates, if there is good seam point in the layer_idx which is close to last_point_pos +// uses comparator.is_first_not_much_worse method to compare current seam with the closest point +// (if current seam is too far away ) +// If the current chosen stream is close enough, it is stored in seam_string. returns true and updates last_point_pos +// If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos +// Otherwise does nothing, returns false +// sadly cannot be const because map access operator[] is not const, since it can create new object +bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, + std::pair &last_point_indexes, + size_t layer_idx, const SeamPlacerImpl::SeamComparator &comparator, + std::vector> &seam_string) { + using namespace SeamPlacerImpl; + + const SeamCandidate &last_point = + m_perimeter_points_per_object[po][last_point_indexes.first][last_point_indexes.second]; + + Vec3f projected_position { last_point.position.x(), last_point.position.y(), float( + po->get_layer(layer_idx)->slice_z) }; + //find closest point in next layer + size_t closest_point_index = find_closest_point( + *m_perimeter_points_trees_per_object[po][layer_idx], projected_position); + + SeamCandidate &closest_point = m_perimeter_points_per_object[po][layer_idx][closest_point_index]; + + if (closest_point.perimeter->finalized) { //already finalized, skip + return false; + } + + //from the closest point, deduce index of seam in the next layer + SeamCandidate &next_layer_seam = + m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; + + auto are_similar = [&](const SeamCandidate &a, const SeamCandidate &b) { + return comparator.is_first_not_much_worse(a, b) && comparator.is_first_not_much_worse(b, a); + }; + + if ((closest_point.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist + && comparator.is_first_not_much_worse(closest_point, next_layer_seam) + && are_similar(last_point, closest_point)) { + seam_string.push_back({ layer_idx, closest_point_index }); + last_point_indexes = std::pair { layer_idx, closest_point_index }; + return true; + } else { + return false; + } + +} + +// clusters already chosen seam points into strings across multiple layers, and then +// aligns the strings via polynomial fit +// Does not change the positions of the SeamCandidates themselves, instead stores +// the new aligned position into the shared Perimeter structure of each perimeter +// Note that this position does not necesarilly lay on the perimeter. +void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator) { + using namespace SeamPlacerImpl; + + // Prepares Debug files for writing. +#ifdef DEBUG_FILES + Slic3r::CNumericLocalesSetter locales_setter; + auto clusters_f = debug_out_path(("seam_clusters_of_" + std::to_string(po->id().id) + ".obj").c_str()); + FILE *clusters = boost::nowide::fopen(clusters_f.c_str(), "w"); + if (clusters == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; + return; + } + auto aligned_f = debug_out_path(("aligned_clusters_of_" + std::to_string(po->id().id) + ".obj").c_str()); + FILE *aligns = boost::nowide::fopen(aligned_f.c_str(), "w"); + if (aligns == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; + return; + } +#endif + + //gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer + std::vector> seams; + for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + size_t current_point_index = 0; + while (current_point_index < layer_perimeter_points.size()) { + seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter->seam_index); + current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1; + } + } + + //sort them before alignment. Alignment is sensitive to intitializaion, this gives it better chance to choose something nice + std::sort(seams.begin(), seams.end(), + [&](const std::pair &left, const std::pair &right) { + return comparator.is_first_better(m_perimeter_points_per_object[po][left.first][left.second], + m_perimeter_points_per_object[po][right.first][right.second]); + } + ); + + //align the seam points - start with the best, and check if they are aligned, if yes, skip, else start alignment + for (const std::pair &seam : seams) { + size_t layer_idx = seam.first; + size_t seam_index = seam.second; + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + if (layer_perimeter_points[seam_index].perimeter->finalized) { + // This perimeter is already aligned, skip seam + continue; + } else { + + //initialize searching for seam string - cluster of nearby seams on previous and next layers + int skips = SeamPlacer::seam_align_tolerable_skips / 2; + int next_layer = layer_idx + 1; + std::pair last_point_indexes = std::pair(layer_idx, seam_index); + + std::vector> seam_string { std::pair(layer_idx, seam_index) }; + + //find seams or potential seams in forward direction; there is a budget of skips allowed + while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { + if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string)) { + //String added, last_point_pos updated, nothing to be done + } else { + // Layer skipped, reduce number of available skips + skips--; + } + next_layer++; + } + + //do additional check in back direction + next_layer = layer_idx - 1; + skips = SeamPlacer::seam_align_tolerable_skips / 2; + last_point_indexes = std::pair(layer_idx, seam_index); + while (skips >= 0 && next_layer >= 0) { + if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator,seam_string)) { + //String added, last_point_pos updated, nothing to be done + } else { + // Layer skipped, reduce number of available skips + skips--; + } + next_layer--; + } + + if (seam_string.size() < seam_align_minimum_string_seams) { + //string NOT long enough to be worth aligning, skip + continue; + } + + // String is long engouh, all string seams and potential string seams gathered, now do the alignment + //sort by layer index + std::sort(seam_string.begin(), seam_string.end(), + [](const std::pair &left, const std::pair &right) { + return left.first < right.first; + }); + + // gather all positions of seams and their weights (weights are derived as negative penalty, they are made positive in next step) + std::vector points(seam_string.size()); + std::vector weights(seam_string.size()); + + //init min_weight by the first point + float min_weight = -comparator.get_penalty( + m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]); + + // In the sorted seam_string array, point which started the alignment - the best candidate + size_t best_candidate_point_index = 0; + + //gather points positions and weights, update min_weight in each step, and find the best candidate + for (size_t index = 0; index < seam_string.size(); ++index) { + points[index] = + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; + weights[index] = -comparator.get_penalty( + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); + min_weight = std::min(min_weight, weights[index]); + // find the best candidate by comparing the layer indexes + if (seam_string[index].first == layer_idx) { + best_candidate_point_index = index; + } + } + + //makes all weights positive + for (float &w : weights) { + w = w - min_weight + 0.01; + } + + //NOTE: the following commented block does polynomial line fitting of the seam string. + // pre-smoothen by Laplace +// for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { +// std::vector new_points(seam_string.size()); +// for (int point_index = 0; point_index < points.size(); ++point_index) { +// size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; +// size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; +// +// new_points[point_index] = (points[prev_idx] * weights[prev_idx] +// + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / +// (weights[prev_idx] + weights[point_index] + weights[next_idx]); +// } +// points = new_points; +// } +// + // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. +// std::vector coefficients = polyfit(points, weights, 4); +// +// // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into +// // Perimeter structure of the point; also set flag aligned to true +// for (const auto &pair : seam_string) { +// float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); +// Vec3f seam_pos = get_fitted_point(coefficients, current_height); +// +// Perimeter *perimeter = +// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); +// perimeter->final_seam_position = seam_pos; +// perimeter->finalized = true; +// } +// +// for (Vec3f &p : points) { +// p = get_fitted_point(coefficients, p.z()); +// } + + // LaPlace smoothing iterations over the gathered points. New positions from each iteration are stored in the new_points vector + // and assigned to points at the end of iteration + for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { + std::vector new_points(seam_string.size()); + // start from the best candidate, and smoothen down + for (int point_index = best_candidate_point_index; point_index >= 0; --point_index) { + int prev_idx = point_index > 0 ? point_index - 1 : point_index; + size_t next_idx = point_index < int(points.size()) - 1 ? point_index + 1 : point_index; + + new_points[point_index] = (points[prev_idx] * weights[prev_idx] + + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / + (weights[prev_idx] + weights[point_index] + weights[next_idx]); + } + // smoothen up the rest of the points + for (size_t point_index = best_candidate_point_index + 1; point_index < points.size(); ++point_index) { + size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; + size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; + + new_points[point_index] = (points[prev_idx] * weights[prev_idx] + + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / + (weights[prev_idx] + weights[point_index] + weights[next_idx]); + } + points = new_points; + } + + // Assign smoothened posiiton to each participating perimeter and set finalized flag + for (size_t index = 0; index < seam_string.size(); ++index) { + Perimeter *perimeter = + m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].perimeter.get(); + perimeter->final_seam_position = points[index]; + perimeter->finalized = true; + } + +#ifdef DEBUG_FILES + auto randf = []() { + return float(rand()) / float(RAND_MAX); + }; + Vec3f color { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; + fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], + orig_seam.position[1], + orig_seam.position[2], color[0], color[1], + color[2]); + } + + color = Vec3f { randf(), randf(), randf() }; + for (size_t i = 0; i < seam_string.size(); ++i) { + Perimeter *perimeter = + m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); + fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], + perimeter->final_seam_position[1], + perimeter->final_seam_position[2], color[0], color[1], + color[2]); + } +#endif + } + } + +#ifdef DEBUG_FILES + fclose(clusters); + fclose(aligns); #endif } +void SeamPlacer::init(const Print &print) { + using namespace SeamPlacerImpl; + m_perimeter_points_trees_per_object.clear(); + m_perimeter_points_per_object.clear(); + for (const PrintObject *po : print.objects()) { -std::optional SeamHistory::get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb) -{ - assert(layer_id >= m_layer_id || layer_id == 0); - if (layer_id != m_layer_id) { - // Get seam was called for different layer than last time. - if (layer_id == 0) // seq printing - m_data_this_layer.clear(); - m_data_last_layer = m_data_this_layer; - m_data_this_layer.clear(); - m_layer_id = layer_id; - } + SeamPosition configured_seam_preference = po->config().seam_position.value; + SeamComparator comparator { configured_seam_preference }; - std::optional out; + GlobalModelInfo global_model_info { }; + gather_enforcers_blockers(global_model_info, po); - auto seams_it = m_data_last_layer.find(po); - if (seams_it == m_data_last_layer.end()) - return out; - - const std::vector& seam_data_po = seams_it->second; - - // Find a bounding-box on the last layer that is close to one we see now. - double min_score = std::numeric_limits::max(); - for (const SeamPoint& sp : seam_data_po) { - const BoundingBox& bb = sp.m_island_bb; - - if (! bb.overlap(island_bb)) { - // This bb does not even overlap. It is likely unrelated. - continue; + if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { + compute_global_occlusion(global_model_info, po); } - double score = std::pow(bb.min(0) - island_bb.min(0), 2.) - + std::pow(bb.min(1) - island_bb.min(1), 2.) - + std::pow(bb.max(0) - island_bb.max(0), 2.) - + std::pow(bb.max(1) - island_bb.max(1), 2.); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather_seam_candidates: start"; + gather_seam_candidates(po, global_model_info); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather_seam_candidates: end"; - if (score < min_score) { - min_score = score; - out = sp.m_pos; + if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_candidates_visibility : start"; + calculate_candidates_visibility(po, global_model_info); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_candidates_visibility : end"; } + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_overhangs : start"; + calculate_overhangs(po); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: calculate_overhangs : end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: pick_seam_point : start"; + //pick seam point + tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), + [&](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_perimeter_points = + m_perimeter_points_per_object[po][layer_idx]; + size_t current = 0; + while (current < layer_perimeter_points.size()) { + if (configured_seam_preference == spRandom) { + pick_random_seam_point(layer_perimeter_points, current); + } else { + pick_seam_point(layer_perimeter_points, current, comparator); + } + current = layer_perimeter_points[current].perimeter->end_index + 1; + } + } + }); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: pick_seam_point : end"; + + if (configured_seam_preference == spAligned) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: align_seam_points : start"; + align_seam_points(po, comparator); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: align_seam_points : end"; + } + +#ifdef DEBUG_FILES + debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), + comparator); +#endif + } +} + +void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const { + using namespace SeamPlacerImpl; + const PrintObject *po = layer->object(); +//NOTE this is necessary, since layer->id() is quite unreliable + size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); + double unscaled_z = layer->slice_z; + + const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object.find(po)->second[layer_index]; + const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second[layer_index]; + + const Point &fp = loop.first_point(); + + Vec2f unscaled_p = unscale(fp).cast(); + size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, + Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); + const Perimeter *perimeter = perimeter_points[closest_perimeter_point_index].perimeter.get(); + + size_t seam_index; + if (po->config().seam_position == spNearest) { + seam_index = pick_nearest_seam_point_index(perimeter_points, perimeter->start_index, + unscale(last_pos).cast()); + } else { + seam_index = perimeter->seam_index; } - return out; + Vec3f seam_position = perimeter_points[seam_index].position; + if (perimeter->finalized) { + seam_position = perimeter->final_seam_position; + } + Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); + + if (!loop.split_at_vertex(seam_point)) +// The point is not in the original loop. +// Insert it. + loop.split_at(seam_point, true); } - - -void SeamHistory::add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb) -{ - m_data_this_layer[po].push_back({pos, island_bb});; -} - - - -void SeamHistory::clear() -{ - m_layer_id = 0; - m_data_last_layer.clear(); - m_data_this_layer.clear(); -} - - -} +} // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 0d72939b2..2b90c1412 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -3,12 +3,15 @@ #include #include +#include +#include #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/KDTreeIndirect.hpp" namespace Slic3r { @@ -16,119 +19,124 @@ class PrintObject; class ExtrusionLoop; class Print; class Layer; -namespace EdgeGrid { class Grid; } +namespace EdgeGrid { +class Grid; +} -class SeamHistory { -public: - SeamHistory() { clear(); } - std::optional get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb); - void add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb); - void clear(); +namespace SeamPlacerImpl { -private: - struct SeamPoint { - Point m_pos; - BoundingBox m_island_bb; - }; +struct GlobalModelInfo; +struct SeamComparator; - std::map> m_data_last_layer; - std::map> m_data_this_layer; - size_t m_layer_id; +enum class EnforcedBlockedSeamPoint { + Blocked = 0, + Neutral = 1, + Enforced = 2, }; +// struct representing single perimeter loop +struct Perimeter { + size_t start_index; + size_t end_index; //inclusive! + size_t seam_index; + // During alignment, a final position may be stored here. In that case, finalized is set to true. + // Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position + // Random position also uses this flexibility to set final seam point position + bool finalized = false; + Vec3f final_seam_position; +}; + +//Struct over which all processing of perimeters is done. For each perimeter point, its respective candidate is created, +// then all the needed attributes are computed and finally, for each perimeter one point is chosen as seam. +// This seam position can be than further aligned +struct SeamCandidate { + SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, + float local_ccw_angle, + EnforcedBlockedSeamPoint type) : + position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), local_ccw_angle( + local_ccw_angle), type(type) { + } + const Vec3f position; + // pointer to Perimter loop of this point. It is shared across all points of the loop + const std::shared_ptr perimeter; + float visibility; + float overhang; + float local_ccw_angle; + EnforcedBlockedSeamPoint type; +}; + +struct FaceVisibilityInfo { + float visibility; +}; + +struct SeamCandidateCoordinateFunctor { + SeamCandidateCoordinateFunctor(std::vector *seam_candidates) : + seam_candidates(seam_candidates) { + } + std::vector *seam_candidates; + float operator()(size_t index, size_t dim) const { + return seam_candidates->operator[](index).position[dim]; + } +}; +} // namespace SeamPlacerImpl class SeamPlacer { public: - void init(const Print& print); + using SeamCandidatesTree = + KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; + static constexpr float raycasting_decimation_target_error = 1.0f; + static constexpr float raycasting_subdivision_target_length = 2.0f; + //square of number of rays per triangle + static constexpr size_t sqr_rays_per_triangle = 7; - // When perimeters are printed, first call this function with the respective - // external perimeter. SeamPlacer will find a location for its seam and remember it. - // Subsequent calls to get_seam will return this position. + // arm length used during angles computation + static constexpr float polygon_local_angles_arm_distance = 0.5f; + // increases angle importance at the cost of deacreasing visibility info importance. must be > 0 + static constexpr float additional_angle_importance = 0.3f; - void plan_perimeters(const std::vector perimeters, - const Layer& layer, SeamPosition seam_position, - Point last_pos, coordf_t nozzle_dmr, const PrintObject* po, - const EdgeGrid::Grid* lower_layer_edge_grid); + // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer + static constexpr float enforcer_blocker_distance_tolerance = 0.3f; + // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size + static constexpr float enforcer_blocker_oversampling_distance = 0.1f; - void place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter, - const EdgeGrid::Grid* lower_layer_edge_grid); - + // When searching for seam clusters for alignment: + // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer + static constexpr float seam_align_score_tolerance = 0.5f; + // seam_align_tolerable_dist - if next layer closes point is too far away, break string + static constexpr float seam_align_tolerable_dist = 1.0f; + // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. + // this param limits the number of allowed skips + static constexpr size_t seam_align_tolerable_skips = 4; + // minimum number of seams needed in cluster to make alignemnt happen + static constexpr size_t seam_align_minimum_string_seams = 6; + // iterations of laplace smoothing + static constexpr size_t seam_align_laplace_smoothing_iterations = 20; - using TreeType = AABBTreeIndirect::Tree<2, coord_t>; - using AlignedBoxType = Eigen::AlignedBox; + //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: + // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer + std::unordered_map>> m_perimeter_points_per_object; + // Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD tree of all points of the given layer + std::unordered_map>> m_perimeter_points_trees_per_object; + + void init(const Print &print); + + void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point& last_pos) const; private: - - // When given an external perimeter (!), returns the seam. - Point calculate_seam(const Layer& layer, const SeamPosition seam_position, - const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po, - const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos, bool prefer_nearest); - - struct CustomTrianglesPerLayer { - Polygons polys; - TreeType tree; - }; - - // Just a cache to save some lookups. - const Layer* m_last_layer_po = nullptr; - coordf_t m_last_print_z = -1.; - const PrintObject* m_last_po = nullptr; - - struct SeamPoint { - Point pt; - bool precalculated = false; - bool external = false; - const Layer* layer = nullptr; - SeamPosition seam_position; - const PrintObject* po = nullptr; - }; - std::vector m_plan; - size_t m_plan_idx; - - std::vector> m_enforcers; - std::vector> m_blockers; - std::vector m_po_list; - - //std::map m_last_seam_position; - SeamHistory m_seam_history; - - // Get indices of points inside enforcers and blockers. - void get_enforcers_and_blockers(size_t layer_id, - const Polygon& polygon, - size_t po_id, - std::vector& enforcers_idxs, - std::vector& blockers_idxs) const; - - // Apply penalties to points inside enforcers/blockers. - void apply_custom_seam(const Polygon& polygon, size_t po_id, - std::vector& penalties, - const std::vector& lengths, - int layer_id, SeamPosition seam_position) const; - - // Return random point of a polygon. The distribution will be uniform - // along the contour and account for enforcers and blockers. - Point get_random_seam(size_t layer_idx, const Polygon& polygon, size_t po_id, - bool* saw_custom = nullptr) const; - - // Is there any enforcer/blocker on this layer? - bool is_custom_seam_on_layer(size_t layer_id, size_t po_idx) const { - return is_custom_enforcer_on_layer(layer_id, po_idx) - || is_custom_blocker_on_layer(layer_id, po_idx); - } - - bool is_custom_enforcer_on_layer(size_t layer_id, size_t po_idx) const { - return (! m_enforcers.at(po_idx).empty() && ! m_enforcers.at(po_idx)[layer_id].polys.empty()); - } - - bool is_custom_blocker_on_layer(size_t layer_id, size_t po_idx) const { - return (! m_blockers.at(po_idx).empty() && ! m_blockers.at(po_idx)[layer_id].polys.empty()); - } + void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); + void calculate_candidates_visibility(const PrintObject *po, + const SeamPlacerImpl::GlobalModelInfo &global_model_info); + void calculate_overhangs(const PrintObject *po); + void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); + bool find_next_seam_in_layer(const PrintObject *po, + std::pair &last_point_indexes, + size_t layer_idx,const SeamPlacerImpl::SeamComparator &comparator, + std::vector> &seam_string); }; - -} +} // namespace Slic3r #endif // libslic3r_SeamPlacer_hpp_ diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp deleted file mode 100644 index 1442c00f6..000000000 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ /dev/null @@ -1,1339 +0,0 @@ -#include "SeamPlacerNG.hpp" - -#include "tbb/parallel_for.h" -#include "tbb/blocked_range.h" -#include "tbb/parallel_reduce.h" -#include -#include -#include -#include - -#include "Subdivide.hpp" - -//For polynomial fitting -#include -#include - -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/EdgeGrid.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/SVG.hpp" -#include "libslic3r/Layer.hpp" -#include "libslic3r/QuadricEdgeCollapse.hpp" - -#define DEBUG_FILES - -#ifdef DEBUG_FILES -#include -#include -#endif - -namespace Slic3r { - -namespace SeamPlacerImpl { - -// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) -// checkout e.g. here: https://www.geogebra.org/calculator -float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { - float shifted = value - mean_x_coord; - float denominator = falloff_speed * shifted * shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); -} - -Vec3f value_to_rgbf(float minimum, float maximum, float value) { - float ratio = 2.0f * (value - minimum) / (maximum - minimum); - float b = std::max(0.0f, (1.0f - ratio)); - float r = std::max(0.0f, (ratio - 1.0f)); - float g = 1.0f - b - r; - return Vec3f { r, g, b }; -} - -Vec3i value_rgbi(float minimum, float maximum, float value) { - return (value_to_rgbf(minimum, maximum, value) * 255).cast(); -} - -//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 -// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y -std::vector polyfit(const std::vector &points, const std::vector &weights, size_t order) { - // check to make sure inputs are correct - assert(points.size() >= order + 1); - assert(points.size() == weights.size()); - - std::vector squared_weights(weights.size()); - for (size_t index = 0; index < weights.size(); ++index) { - squared_weights[index] = sqrt(weights[index]); - } - - Eigen::VectorXf V0(points.size()); - Eigen::VectorXf V1(points.size()); - Eigen::VectorXf V2(points.size()); - for (size_t index = 0; index < points.size(); index++) { - V0(index) = points[index].x() * squared_weights[index]; - V1(index) = points[index].y() * squared_weights[index]; - V2(index) = points[index].z(); - } - - // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial - Eigen::MatrixXf T(points.size(), order + 1); - // Populate the matrix - for (size_t i = 0; i < points.size(); ++i) - { - for (size_t j = 0; j < order + 1; ++j) - { - T(i, j) = pow(V2(i), j) * squared_weights[i]; - } - } - - // Solve for linear least square fit - const auto QR = T.householderQr(); - Eigen::VectorXf result0 = QR.solve(V0); - Eigen::VectorXf result1 = QR.solve(V1); - std::vector coeff { order + 1 }; - for (size_t k = 0; k < order + 1; k++) { - coeff[k] = Vec2f { result0[k], result1[k] }; - } - return coeff; -} - -Vec3f get_fitted_point(const std::vector &coefficients, float z) { - size_t order = coefficients.size() - 1; - float fitted_x = 0; - float fitted_y = 0; - for (size_t index = 0; index < order + 1; ++index) { - float z_pow = pow(z, index); - fitted_x += coefficients[index].x() * z_pow; - fitted_y += coefficients[index].y() * z_pow; - } - - return Vec3f { fitted_x, fitted_y, z }; -} - -/// Coordinate frame -class Frame { -public: - Frame() { - mX = Vec3f(1, 0, 0); - mY = Vec3f(0, 1, 0); - mZ = Vec3f(0, 0, 1); - } - - Frame(const Vec3f &x, const Vec3f &y, const Vec3f &z) : - mX(x), mY(y), mZ(z) { - } - - void set_from_z(const Vec3f &z) { - mZ = z.normalized(); - Vec3f tmpZ = mZ; - Vec3f tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3f(0, 1, 0) : Vec3f(1, 0, 0); - mY = (tmpZ.cross(tmpX)).normalized(); - mX = mY.cross(tmpZ); - } - - Vec3f to_world(const Vec3f &a) const { - return a.x() * mX + a.y() * mY + a.z() * mZ; - } - - Vec3f to_local(const Vec3f &a) const { - return Vec3f(mX.dot(a), mY.dot(a), mZ.dot(a)); - } - - const Vec3f& binormal() const { - return mX; - } - - const Vec3f& tangent() const { - return mY; - } - - const Vec3f& normal() const { - return mZ; - } - -private: - Vec3f mX, mY, mZ; -}; - -Vec3f sample_sphere_uniform(const Vec2f &samples) { - float term1 = 2.0f * float(PI) * samples.x(); - float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); - return {cos(term1) * term2, sin(term1) * term2, - 1.0f - 2.0f * samples.y()}; -} - -Vec3f sample_hemisphere_uniform(const Vec2f &samples) { - float term1 = 2.0f * float(PI) * samples.x(); - float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); - return {cos(term1) * term2, sin(term1) * term2, - abs(1.0f - 2.0f * samples.y())}; -} - -Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { - float term1 = 2.f * float(PI) * samples.x(); - float term2 = pow(samples.y(), 1.f / (power + 1.f)); - float term3 = sqrt(1.f - term2 * term2); - - return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); -} - -std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: start"; - - float step_size = 1.0f / SeamPlacer::sqr_rays_per_triangle; - std::vector precomputed_sample_directions( - SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); - for (size_t x_idx = 0; x_idx < SeamPlacer::sqr_rays_per_triangle; ++x_idx) { - float sample_x = x_idx * step_size + step_size / 2.0; - for (size_t y_idx = 0; y_idx < SeamPlacer::sqr_rays_per_triangle; ++y_idx) { - size_t dir_index = x_idx * SeamPlacer::sqr_rays_per_triangle + y_idx; - float sample_y = y_idx * step_size + step_size / 2.0; - precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y }); - } - } - - std::vector result(triangles.indices.size()); - tbb::parallel_for(tbb::blocked_range(0, result.size()), - [&](tbb::blocked_range r) { - for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { - FaceVisibilityInfo &dest = result[face_index]; - dest.visibility = 1.0f; - constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); - - Vec3i face = triangles.indices[face_index]; - Vec3f A = triangles.vertices[face.x()]; - Vec3f B = triangles.vertices[face.y()]; - Vec3f C = triangles.vertices[face.z()]; - Vec3f center = (A + B + C) / 3.0f; - Vec3f normal = ((B - A).cross(C - B)).normalized(); - // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward - Frame f; - f.set_from_z(normal); - - for (const auto &dir : precomputed_sample_directions) { - Vec3f final_ray_dir = (f.to_world(dir)); - igl::Hit hitpoint; - // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and - // direction. - Vec3d ray_origin_d = (center + normal).cast(); // start one mm above surface. - Vec3d final_ray_dir_d = final_ray_dir.cast(); - bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, - triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); - - if (hit) { - dest.visibility -= decrease; - } - } - } - }); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: end"; - - return result; -} - -std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, - float min_arm_length) { - std::vector result(polygon.size()); - - if (polygon.size() == 1) { - result[0] = 0.0f; - } - - auto make_idx_circular = [&](int index) { - while (index < 0) { - index += polygon.size(); - } - return index % polygon.size(); - }; - - int idx_prev = 0; - int idx_curr = 0; - int idx_next = 0; - - float distance_to_prev = 0; - float distance_to_next = 0; - - //push idx_prev far enough back as initialization - while (distance_to_prev < min_arm_length) { - idx_prev = make_idx_circular(idx_prev - 1); - distance_to_prev += lengths[idx_prev]; - } - - for (size_t _i = 0; _i < polygon.size(); ++_i) { - // pull idx_prev to current as much as possible, while respecting the min_arm_length - while (distance_to_prev - lengths[idx_prev] > min_arm_length) { - distance_to_prev -= lengths[idx_prev]; - idx_prev = make_idx_circular(idx_prev + 1); - } - - //push idx_next forward as far as needed - while (distance_to_next < min_arm_length) { - distance_to_next += lengths[idx_next]; - idx_next = make_idx_circular(idx_next + 1); - } - - // Calculate angle between idx_prev, idx_curr, idx_next. - const Point &p0 = polygon.points[idx_prev]; - const Point &p1 = polygon.points[idx_curr]; - const Point &p2 = polygon.points[idx_next]; - const Point v1 = p1 - p0; - const Point v2 = p2 - p1; - int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); - int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); - float angle = float(atan2(float(cross), float(dot))); - result[idx_curr] = angle; - - // increase idx_curr by one - float curr_distance = lengths[idx_curr]; - idx_curr++; - distance_to_prev += curr_distance; - distance_to_next -= curr_distance; - } - - return result; -} - -// structure to store global information about the model - occlusion hits, enforcers, blockers -struct GlobalModelInfo { - indexed_triangle_set model; - AABBTreeIndirect::Tree<3, float> model_tree; - std::vector visiblity_info; - indexed_triangle_set enforcers; - indexed_triangle_set blockers; - AABBTreeIndirect::Tree<3, float> enforcers_tree; - AABBTreeIndirect::Tree<3, float> blockers_tree; - - bool is_enforced(const Vec3f &position, float radius) const { - if (enforcers.empty()) { - return false; - } - float radius_sqr = radius * radius; - return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, - enforcers_tree, position, radius_sqr); - } - - bool is_blocked(const Vec3f &position, float radius) const { - if (blockers.empty()) { - return false; - } - float radius_sqr = radius * radius; - return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, - blockers_tree, position, radius_sqr); - } - - float calculate_point_visibility(const Vec3f &position) const { - size_t hit_idx; - Vec3f hit_point; - if (AABBTreeIndirect::squared_distance_to_indexed_triangle_set(model.vertices, model.indices, model_tree, - position, hit_idx, hit_point) >= 0) { - return visiblity_info[hit_idx].visibility; - } else { - return 0.0f; - } - - } - -#ifdef DEBUG_FILES - void debug_export(const indexed_triangle_set &obj_mesh, const char *file_name) const { - indexed_triangle_set divided_mesh = obj_mesh; // subdivide(obj_mesh, SeamPlacer::considered_area_radius); - Slic3r::CNumericLocalesSetter locales_setter; - - FILE *fp = boost::nowide::fopen(file_name, "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << file_name << " for writing"; - return; - } - - for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { - float visibility = calculate_point_visibility(divided_mesh.vertices[i]); - Vec3f color = value_to_rgbf(0.0f, 1.0f, - visibility); - fprintf(fp, "v %f %f %f %f %f %f\n", - divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), - color(0), color(1), color(2) - ); - } - for (size_t i = 0; i < divided_mesh.indices.size(); ++i) - fprintf(fp, "f %d %d %d\n", divided_mesh.indices[i][0] + 1, divided_mesh.indices[i][1] + 1, - divided_mesh.indices[i][2] + 1); - fclose(fp); - - } -#endif -} -; - -//Extract perimeter polygons of the given layer -Polygons extract_perimeter_polygons(const Layer *layer) { - Polygons polygons; - for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { - if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters - for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { - if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { - Points p; - perimeter->collect_points(p); - polygons.emplace_back(p); - } - } - if (polygons.empty()) { - Points p; - ex_entity->collect_points(p); - polygons.emplace_back(p); - } - } else { - Points p; - ex_entity->collect_points(p); - polygons.emplace_back(p); - } - } - } - - if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point - // it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway - polygons.emplace_back(std::vector { Point { 0, 0 } }); - } - - return polygons; -} - -// Insert SeamCandidates created from perimeter polygons in to the result vector. -// Compute its type (Enfrocer,Blocker), angle, and position -//each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon -// if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions -void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, - const GlobalModelInfo &global_model_info) { - if (orig_polygon.size() == 0) { - return; - } - - Polygon polygon = orig_polygon; - bool was_clockwise = polygon.make_counter_clockwise(); - - std::vector lengths { }; - for (size_t point_idx = 0; point_idx < polygon.size() - 1; ++point_idx) { - lengths.push_back(std::max((unscale(polygon[point_idx]) - unscale(polygon[point_idx + 1])).norm(), 0.01)); - } - lengths.push_back(std::max((unscale(polygon[0]) - unscale(polygon[polygon.size() - 1])).norm(), 0.01)); - - std::vector local_angles = calculate_polygon_angles_at_vertices(polygon, lengths, - SeamPlacer::polygon_local_angles_arm_distance); - std::shared_ptr perimeter = std::make_shared(); - - std::queue orig_polygon_points { }; - for (size_t index = 0; index < polygon.size(); ++index) { - Vec2f unscaled_p = unscale(polygon[index]).cast(); - orig_polygon_points.emplace(unscaled_p.x(), unscaled_p.y(), z_coord); - } - Vec3f first = orig_polygon_points.front(); - std::queue oversampled_points { }; - size_t orig_angle_index = 0; - perimeter->start_index = result_vec.size(); - while (!orig_polygon_points.empty() || !oversampled_points.empty()) { - EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; - Vec3f position; - float local_ccw_angle = 0; - bool orig_point = false; - if (!oversampled_points.empty()) { - position = oversampled_points.front(); - oversampled_points.pop(); - } else { - position = orig_polygon_points.front(); - orig_polygon_points.pop(); - local_ccw_angle = was_clockwise ? -local_angles[orig_angle_index] : local_angles[orig_angle_index]; - orig_angle_index++; - orig_point = true; - } - - if (global_model_info.is_enforced(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { - type = EnforcedBlockedSeamPoint::Enforced; - } - - if (global_model_info.is_blocked(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { - type = EnforcedBlockedSeamPoint::Blocked; - } - - if (orig_point) { - Vec3f pos_of_next = orig_polygon_points.empty() ? first : orig_polygon_points.front(); - float distance_to_next = (position - pos_of_next).norm(); - if (global_model_info.is_enforced(position, distance_to_next) - || global_model_info.is_blocked(position, distance_to_next)) { - Vec3f vec_to_next = (pos_of_next - position).normalized(); - float step_size = SeamPlacer::enforcer_blocker_oversampling_distance; - float step = step_size; - while (step < distance_to_next) { - oversampled_points.push(position + vec_to_next * step); - step += step_size; - } - } - } - - result_vec.emplace_back(position, perimeter, local_ccw_angle, type); - - } - - perimeter->end_index = result_vec.size() - 1; -} - -// Get index of previous and next perimeter point of the layer. Because SeamCandidates of all polygons of the given layer -// are sequentially stored in the vector, each perimeter contains info about start and end index. These vales are used to -// deduce index of previous and next neigbour in the corresponding perimeter. -std::pair find_previous_and_next_perimeter_point(const std::vector &perimeter_points, - size_t point_index) { - const SeamCandidate ¤t = perimeter_points[point_index]; - int prev = point_index - 1; //for majority of points, it is true that neighbours lie behind and in front of them in the vector - int next = point_index + 1; - - if (point_index == current.perimeter->start_index) { - // if point_index is equal to start, it means that the previous neighbour is at the end - prev = current.perimeter->end_index; - } - - if (point_index == current.perimeter->end_index) { - // if point_index is equal to end, than next neighbour is at the start - next = current.perimeter->start_index; - } - - assert(prev >= 0); - assert(next >= 0); - return {size_t(prev),size_t(next)}; -} - -//NOTE: only rough esitmation of overhang distance -// value represents distance from edge, positive is overhang, negative is inside shape -float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b, - const SeamCandidate &under_c) { - auto p = Vec2d { point.position.x(), point.position.y() }; - auto a = Vec2d { under_a.position.x(), under_a.position.y() }; - auto b = Vec2d { under_b.position.x(), under_b.position.y() }; - auto c = Vec2d { under_c.position.x(), under_c.position.y() }; - - auto oriented_line_dist = [](const Vec2d a, const Vec2d b, const Vec2d p) { //signed distance from line - return -((b.x() - a.x()) * (a.y() - p.y()) - (a.x() - p.x()) * (b.y() - a.y())) / (a - b).norm(); - }; - - auto dist_ab = oriented_line_dist(a, b, p); - auto dist_bc = oriented_line_dist(b, c, p); - - // from angle and signed distances from the arms of the points on the previous layer, we - // can deduce if it is overhang and give estimation of the size. - // However, the size of the overhang is rough estimation, the sign is more reliable - if (under_b.local_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside - return -((p - b).norm() + dist_ab + dist_bc) / 3.0; - } - - if (under_b.local_ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside - return -((p - b).norm() + dist_ab + dist_bc) / 3.0; - } - - return ((p - b).norm() + dist_ab + dist_bc) / 3.0; -} - -// Computes all global model info - transforms object, performs raycasting, -// stores enforces and blockers -void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; -// Build AABB tree for raycasting - auto obj_transform = po->trafo_centered(); - indexed_triangle_set triangle_set; - //add all parts - for (const ModelVolume *model_volume : po->model_object()->volumes) { - if (model_volume->type() == ModelVolumeType::MODEL_PART) { - auto model_transformation = model_volume->get_matrix(); - indexed_triangle_set model_its = model_volume->mesh().its; - its_transform(model_its, model_transformation); - its_merge(triangle_set, model_its); - } - } - - float target_error = SeamPlacer::raycasting_decimation_target_error; - its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); - triangle_set = subdivide(triangle_set, SeamPlacer::raycasting_subdivision_target_length); - its_transform(triangle_set, obj_transform); - - auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, - triangle_set.indices); - - result.model = triangle_set; - result.model_tree = raycasting_tree; - result.visiblity_info = raycast_visibility(raycasting_tree, triangle_set); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; - -#ifdef DEBUG_FILES - auto filename = "visiblity_of_" + std::to_string(po->id().id) + ".obj"; - result.debug_export(triangle_set, filename.c_str()); -#endif -} - -void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; - - auto obj_transform = po->trafo(); - - for (const ModelVolume *mv : po->model_object()->volumes) { - if (mv->is_seam_painted()) { - auto model_transformation = mv->get_matrix(); - - indexed_triangle_set enforcers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER); - its_transform(enforcers, model_transformation); - its_merge(result.enforcers, enforcers); - - indexed_triangle_set blockers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER); - its_transform(blockers, model_transformation); - its_merge(result.blockers, blockers); - } - } - its_transform(result.enforcers, obj_transform); - its_transform(result.blockers, obj_transform); - - result.enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.enforcers.vertices, - result.enforcers.indices); - result.blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.blockers.vertices, - result.blockers.indices); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; -} - -//Comparator of seam points. It has two necessary methods: is_first_better and is_first_not_much_worse -struct SeamComparator { - SeamPosition setup; - - SeamComparator(SeamPosition setup) : - setup(setup) { - } - - float compute_angle_penalty(float ccw_angle) const { - // This function is used: - // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) - // looks terribly, but it is gaussian combined with sigmoid, - // so that concave points have much smaller penalty over convex ones - - return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + - 1.0f / (2 + std::exp(-ccw_angle)); // sigmoid, which heavily favourizes concave angles - } - - // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage - // should return if a is better seamCandidate than b - bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location = Vec2f { 0.0f, - 0.0f }) const { - // Blockers/Enforcers discrimination, top priority - if (a.type > b.type) { - return true; - } - if (b.type > a.type) { - return false; - } - - //avoid overhangs - if (a.overhang > 0.1f && b.overhang < a.overhang) { - return false; - } - - if (setup == SeamPosition::spRear) { - return a.position.y() > b.position.y(); - } - - float distance_penalty_a = 1.0f; - float distance_penalty_b = 1.0f; - if (setup == spNearest) { - distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.01f); - distance_penalty_b = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.01f); - } - - //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) - float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle) - * distance_penalty_a; - float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle) - * distance_penalty_b; - - return penalty_a < penalty_b; - } - - // Comparator used during alignment. If there is close potential aligned point, it is comapred to the current - // sema point of the perimeter, to find out if the aligned point is not much worse than the current seam - bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { - // Blockers/Enforcers discrimination, top priority - if (a.type == EnforcedBlockedSeamPoint::Enforced) { - return true; - } - - if (a.type == EnforcedBlockedSeamPoint::Blocked) { - return false; - } - - if (a.type > b.type) { - return true; - } - if (b.type > a.type) { - return false; - } - - //avoid overhangs - if (a.overhang > 0.1f && b.overhang < a.overhang) { - return false; - } - - if (setup == SeamPosition::spRandom) { - return true; - } - - if (setup == SeamPosition::spRear) { - return a.position.y() > b.position.y(); - } - - //ranges: [0 - 1] (0 - 1.3] ; - float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); - float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle); - - return penalty_a <= penalty_b || std::abs(penalty_a - penalty_b) < SeamPlacer::seam_align_score_tolerance; - } - - //always nonzero, positive - float get_penalty(const SeamCandidate &a) const { - if (setup == SeamPosition::spRear) { - return a.position.y(); - } - - return (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); - } -} -; - -#ifdef DEBUG_FILES -void debug_export_points(const std::vector> &object_perimter_points, - const BoundingBox &bounding_box, std::string object_name, const SeamComparator &comparator) { - for (size_t layer_idx = 0; layer_idx < object_perimter_points.size(); ++layer_idx) { - SVG angles_svg { object_name + "_angles_" + std::to_string(layer_idx) + ".svg", bounding_box }; - float min_vis = 0; - float max_vis = min_vis; - - float min_weight = std::numeric_limits::min(); - float max_weight = min_weight; - - for (const SeamCandidate &point : object_perimter_points[layer_idx]) { - Vec3i color = value_rgbi(-PI, PI, point.local_ccw_angle); - std::string fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," - + std::to_string(color.z()) + ")"; - angles_svg.draw(scaled(Vec2f(point.position.head<2>())), fill); - min_vis = std::min(min_vis, point.visibility); - max_vis = std::max(max_vis, point.visibility); - - min_weight = std::min(min_weight, -comparator.get_penalty(point)); - max_weight = std::max(max_weight, -comparator.get_penalty(point)); - - } - - SVG visibility_svg { object_name + "_visibility_" + std::to_string(layer_idx) + ".svg", bounding_box }; - SVG weight_svg { object_name + "_weight_" + std::to_string(layer_idx) + ".svg", bounding_box }; - for (const SeamCandidate &point : object_perimter_points[layer_idx]) { - Vec3i color = value_rgbi(min_vis, max_vis, point.visibility); - std::string visibility_fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," - + std::to_string(color.z()) + ")"; - visibility_svg.draw(scaled(Vec2f(point.position.head<2>())), visibility_fill); - - Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_penalty(point)); - std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) - + "," - + std::to_string(weight_color.z()) + ")"; - weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); - } - } -} -#endif - -// Pick best seam point based on the given comparator -void pick_seam_point(std::vector &perimeter_points, size_t start_index, - const SeamComparator &comparator) { - size_t end_index = perimeter_points[start_index].perimeter->end_index; - - size_t seam_index = start_index; - for (size_t index = start_index; index <= end_index; ++index) { - if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { - seam_index = index; - } - } - perimeter_points[start_index].perimeter->seam_index = seam_index; -} - -size_t pick_nearest_seam_point_index(const std::vector &perimeter_points, size_t start_index, - const Vec2f &preffered_location) { - size_t end_index = perimeter_points[start_index].perimeter->end_index; - SeamComparator comparator { spNearest }; - - size_t seam_index = start_index; - for (size_t index = start_index; index <= end_index; ++index) { - if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index], preffered_location)) { - seam_index = index; - } - } - return seam_index; -} - -// picks random seam point uniformly, respecting enforcers blockers and overhang avoidance. -void pick_random_seam_point(std::vector &perimeter_points, size_t start_index) { - SeamComparator comparator { spRandom }; - - // algorithm keeps a list of viable points and their lengths. If it finds a point - // that is much better than the viable_example_index (e.g. better type, no overhang; see is_first_not_much_worse) - // then it throws away stored lists and starts from start - // in the end, he list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not - // big overhang. - size_t viable_example_index = start_index; - size_t end_index = perimeter_points[start_index].perimeter->end_index; - std::vector viable_indices; - std::vector viable_edges_lengths; - std::vector viable_edges; - - for (size_t index = start_index; index <= end_index; ++index) { - if (comparator.is_first_not_much_worse(perimeter_points[index], perimeter_points[viable_example_index]) && - comparator.is_first_not_much_worse(perimeter_points[viable_example_index], perimeter_points[index])) { - // index ok, push info into respective vectors - Vec3f edge_to_next; - if (index == end_index) { - edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); - } else - { - edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); - } - float dist_to_next = edge_to_next.norm(); - viable_indices.push_back(index); - viable_edges_lengths.push_back(dist_to_next); - viable_edges.push_back(edge_to_next); - } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index], - perimeter_points[index])) { - // index is worse then viable_example_index, skip this point - } else { - // index is better than viable example index, update example, clear gathered info, start again - // clear up all gathered info, start from scratch, update example index - viable_example_index = index; - viable_indices.clear(); - viable_edges_lengths.clear(); - viable_edges.clear(); - - Vec3f edge_to_next; - if (index == end_index) { - edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); - } else { - edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); - } - float dist_to_next = edge_to_next.norm(); - viable_indices.push_back(index); - viable_edges_lengths.push_back(dist_to_next); - viable_edges.push_back(edge_to_next); - } - } - - // now pick random point from the stored options - float len_sum = std::accumulate(viable_edges_lengths.begin(), viable_edges_lengths.end(), 0.0f); - float picked_len = len_sum * (rand() / (float(RAND_MAX) + 1)); - - size_t point_idx = 0; - while (picked_len - viable_edges_lengths[point_idx] > 0) { - picked_len = picked_len - viable_edges_lengths[point_idx]; - point_idx++; - } - - Perimeter *perimeter = perimeter_points[start_index].perimeter.get(); - perimeter->seam_index = viable_indices[point_idx]; - perimeter->final_seam_position = perimeter_points[perimeter->seam_index].position - + viable_edges[point_idx].normalized() * picked_len; - perimeter->finalized = true; - -} - -} // namespace SeamPlacerImpl - -// Parallel process and extract each perimeter polygon of the given print object. -// Gather SeamCandidates of each layer into vector and build KDtree over them -// Store results in the SeamPlacer varaibles m_perimeter_points_per_object and m_perimeter_points_trees_per_object -void SeamPlacer::gather_seam_candidates(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { - using namespace SeamPlacerImpl; - - m_perimeter_points_per_object.emplace(po, po->layer_count()); - m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); - - tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = - m_perimeter_points_per_object[po][layer_idx]; - const Layer *layer = po->get_layer(layer_idx); - auto unscaled_z = layer->slice_z; - Polygons polygons = extract_perimeter_polygons(layer); - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - global_model_info); - } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( - functor, layer_candidates.size()); - } - } - ); -} - -void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { - using namespace SeamPlacerImpl; - - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - perimeter_point.visibility = global_model_info.calculate_point_visibility( - perimeter_point.position); - } - } - }); -} - -void SeamPlacer::calculate_overhangs(const PrintObject *po) { - using namespace SeamPlacerImpl; - - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - const auto calculate_layer_overhang = [&](size_t other_layer_idx) { - size_t closest_supporter = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - perimeter_point.position); - const SeamCandidate &supporter_point = - m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; - - auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); - const SeamCandidate &prev_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; - const SeamCandidate &next_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; - - return calculate_overhang(perimeter_point, prev_point, - supporter_point, next_point); - }; - - if (layer_idx > 0) { //calculate overhang - perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); - } - } - } - }); - } - -// Estimates, if there is good seam point in the layer_idx which is close to last_point_pos -// uses comparator.is_first_not_much_worse method to compare current seam with the closest point -// (if current seam is too far away ) -// If the current chosen stream is close enough, it is stored in seam_string. returns true and updates last_point_pos -// If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos -// Otherwise does nothing, returns false -// sadly cannot be const because map access operator[] is not const, since it can create new object -bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, - std::pair &last_point_indexes, - size_t layer_idx, const SeamPlacerImpl::SeamComparator &comparator, - std::vector> &seam_string, - std::vector> &potential_string_seams) { - using namespace SeamPlacerImpl; - - const SeamCandidate &last_point = - m_perimeter_points_per_object[po][last_point_indexes.first][last_point_indexes.second]; - - Vec3f projected_position { last_point.position.x(), last_point.position.y(), float( - po->get_layer(layer_idx)->slice_z) }; - //find closest point in next layer - size_t closest_point_index = find_closest_point( - *m_perimeter_points_trees_per_object[po][layer_idx], projected_position); - - SeamCandidate &closest_point = m_perimeter_points_per_object[po][layer_idx][closest_point_index]; - - if (closest_point.perimeter->finalized) { //already finalized, skip - return false; - } - - //from the closest point, deduce index of seam in the next layer - SeamCandidate &next_layer_seam = - m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; - - auto are_similar = [&](const SeamCandidate &a, const SeamCandidate &b) { - return comparator.is_first_not_much_worse(a, b) && comparator.is_first_not_much_worse(b, a); - }; - - if ((next_layer_seam.position - projected_position).norm() - < SeamPlacer::seam_align_tolerable_dist && are_similar(last_point, next_layer_seam)) { //seam point is within limits, put in the close_by_points list - seam_string.emplace_back(layer_idx, closest_point.perimeter->seam_index); - last_point_indexes = std::pair { layer_idx, closest_point.perimeter->seam_index }; - return true; - } else if ((closest_point.position - projected_position).norm() - < SeamPlacer::seam_align_tolerable_dist - && comparator.is_first_not_much_worse(closest_point, next_layer_seam) - && are_similar(last_point, closest_point)) { - //seam point is far, but if the close point is not much worse, do not count it as a skip and add it to potential_string_seams - potential_string_seams.emplace_back(layer_idx, closest_point_index); - last_point_indexes = std::pair { layer_idx, closest_point_index }; - return true; - } else { - return false; - } - -} - -// clusters already chosen seam points into strings across multiple layers, and then -// aligns the strings via polynomial fit -// Does not change the positions of the SeamCandidates themselves, instead stores -// the new aligned position into the shared Perimeter structure of each perimeter -// Note that this position does not necesarilly lay on the perimeter. -void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator) { - using namespace SeamPlacerImpl; - - // Prepares Debug files for writing. -#ifdef DEBUG_FILES - Slic3r::CNumericLocalesSetter locales_setter; - auto clusters_f = "seam_clusters_of_" + std::to_string(po->id().id) + ".obj"; - FILE *clusters = boost::nowide::fopen(clusters_f.c_str(), "w"); - if (clusters == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; - return; - } - auto aligned_f = "aligned_clusters_of_" + std::to_string(po->id().id) + ".obj"; - FILE *aligns = boost::nowide::fopen(aligned_f.c_str(), "w"); - if (aligns == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; - return; - } -#endif - - //gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer - std::vector> seams; - for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - size_t current_point_index = 0; - while (current_point_index < layer_perimeter_points.size()) { - seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter->seam_index); - current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1; - } - } - - //sort them before alignment. Alignment is sensitive to intitializaion, this gives it better chance to choose something nice - std::sort(seams.begin(), seams.end(), - [&](const std::pair &left, const std::pair &right) { - return comparator.is_first_better(m_perimeter_points_per_object[po][left.first][left.second], - m_perimeter_points_per_object[po][right.first][right.second]); - } - ); - - //align the seam points - start with the best, and check if they are aligned, if yes, skip, else start alignment - for (const std::pair &seam : seams) { - size_t layer_idx = seam.first; - size_t seam_index = seam.second; - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - if (layer_perimeter_points[seam_index].perimeter->finalized) { - // This perimeter is already aligned, skip seam - continue; - } else { - - //initialize searching for seam string - cluster of nearby seams on previous and next layers - int skips = SeamPlacer::seam_align_tolerable_skips / 2; - int next_layer = layer_idx + 1; - std::pair last_point_indexes = std::pair(layer_idx, seam_index); - - std::vector> seam_string { std::pair(layer_idx, seam_index) }; - std::vector> potential_string_seams; - - //find seams or potential seams in forward direction; there is a budget of skips allowed - while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { - if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string, - potential_string_seams)) { - //String added, last_point_pos updated, nothing to be done - } else { - // Layer skipped, reduce number of available skips - skips--; - } - next_layer++; - } - - //do additional check in back direction - next_layer = layer_idx - 1; - skips = SeamPlacer::seam_align_tolerable_skips / 2; - last_point_indexes = std::pair(layer_idx, seam_index); - while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, - seam_string, - potential_string_seams)) { - //String added, last_point_pos updated, nothing to be done - } else { - // Layer skipped, reduce number of available skips - skips--; - } - next_layer--; - } - - if (seam_string.size() + potential_string_seams.size() < seam_align_minimum_string_seams) { - //string NOT long enough to be worth aligning, skip - continue; - } - - // String is long engouh, all string seams and potential string seams gathered, now do the alignment - // first merge potential_string_seams and seam_string - seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); - //sort by layer index - std::sort(seam_string.begin(), seam_string.end(), - [](const std::pair &left, const std::pair &right) { - return left.first < right.first; - }); - - // gather all positions of seams and their weights (weights are derived as negative penalty, they are made positive in next step) - std::vector points(seam_string.size()); - std::vector weights(seam_string.size()); - - //init min_weight by the first point - float min_weight = -comparator.get_penalty( - m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]); - - // In the sorted seam_string array, point which started the alignment - the best candidate - size_t best_candidate_point_index = 0; - - //gather points positions and weights, update min_weight in each step, and find the best candidate - for (size_t index = 0; index < seam_string.size(); ++index) { - points[index] = - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; - weights[index] = -comparator.get_penalty( - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); - min_weight = std::min(min_weight, weights[index]); - // find the best candidate by comparing the layer indexes - if (seam_string[index].first == layer_idx) { - best_candidate_point_index = index; - } - } - - //makes all weights positive - for (float &w : weights) { - w = w - min_weight + 0.01; - } - - //NOTE: the following commented block does polynomial line fitting of the seam string. - // pre-smoothen by Laplace -// for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { -// std::vector new_points(seam_string.size()); -// for (int point_index = 0; point_index < points.size(); ++point_index) { -// size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; -// size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; -// -// new_points[point_index] = (points[prev_idx] * weights[prev_idx] -// + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / -// (weights[prev_idx] + weights[point_index] + weights[next_idx]); -// } -// points = new_points; -// } -// - // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. -// std::vector coefficients = polyfit(points, weights, 4); -// -// // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into -// // Perimeter structure of the point; also set flag aligned to true -// for (const auto &pair : seam_string) { -// float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); -// Vec3f seam_pos = get_fitted_point(coefficients, current_height); -// -// Perimeter *perimeter = -// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); -// perimeter->final_seam_position = seam_pos; -// perimeter->finalized = true; -// } -// -// for (Vec3f &p : points) { -// p = get_fitted_point(coefficients, p.z()); -// } - - // LaPlace smoothing iterations over the gathered points. New positions from each iteration are stored in the new_points vector - // and assigned to points at the end of iteration - for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { - std::vector new_points(seam_string.size()); - // start from the best candidate, and smoothen down - for (int point_index = best_candidate_point_index; point_index >= 0; --point_index) { - int prev_idx = point_index > 0 ? point_index - 1 : point_index; - size_t next_idx = point_index < int(points.size()) - 1 ? point_index + 1 : point_index; - - new_points[point_index] = (points[prev_idx] * weights[prev_idx] - + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / - (weights[prev_idx] + weights[point_index] + weights[next_idx]); - } - // smoothen up the rest of the points - for (size_t point_index = best_candidate_point_index + 1; point_index < points.size(); ++point_index) { - size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; - size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; - - new_points[point_index] = (points[prev_idx] * weights[prev_idx] - + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / - (weights[prev_idx] + weights[point_index] + weights[next_idx]); - } - points = new_points; - } - - // Assign smoothened posiiton to each participating perimeter and set finalized flag - for (size_t index = 0; index < seam_string.size(); ++index) { - Perimeter *perimeter = - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].perimeter.get(); - perimeter->final_seam_position = points[index]; - perimeter->finalized = true; - } - -#ifdef DEBUG_FILES - auto randf = []() { - return float(rand()) / float(RAND_MAX); - }; - Vec3f color { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; - fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], - orig_seam.position[1], - orig_seam.position[2], color[0], color[1], - color[2]); - } - - color = Vec3f { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - Perimeter *perimeter = - m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); - fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], - perimeter->final_seam_position[1], - perimeter->final_seam_position[2], color[0], color[1], - color[2]); - } -#endif - } - } - -#ifdef DEBUG_FILES - fclose(clusters); - fclose(aligns); -#endif - -} - -void SeamPlacer::init(const Print &print) { - using namespace SeamPlacerImpl; - m_perimeter_points_trees_per_object.clear(); - m_perimeter_points_per_object.clear(); - - for (const PrintObject *po : print.objects()) { - - SeamPosition configured_seam_preference = po->config().seam_position.value; - SeamComparator comparator { configured_seam_preference }; - - GlobalModelInfo global_model_info { }; - gather_enforcers_blockers(global_model_info, po); - - if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { - compute_global_occlusion(global_model_info, po); - } - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather_seam_candidates: start"; - gather_seam_candidates(po, global_model_info); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather_seam_candidates: end"; - - if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : start"; - calculate_candidates_visibility(po, global_model_info); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : end"; - } - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs : start"; - calculate_overhangs(po); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs : end"; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : start"; - //pick seam point - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - size_t current = 0; - while (current < layer_perimeter_points.size()) { - if (configured_seam_preference == spRandom) { - pick_random_seam_point(layer_perimeter_points, current); - } else { - pick_seam_point(layer_perimeter_points, current, comparator); - } - current = layer_perimeter_points[current].perimeter->end_index + 1; - } - } - }); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : end"; - - if (configured_seam_preference == spAligned) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : start"; - align_seam_points(po, comparator); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : end"; - } - -#ifdef DEBUG_FILES - debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), - comparator); -#endif - } -} - -void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const { - using namespace SeamPlacerImpl; - const PrintObject *po = layer->object(); -//NOTE this is necessary, since layer->id() is quite unreliable - size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); - double unscaled_z = layer->slice_z; - - const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object.find(po)->second[layer_index]; - const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second[layer_index]; - - const Point &fp = loop.first_point(); - - Vec2f unscaled_p = unscale(fp).cast(); - size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, - Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); - const Perimeter *perimeter = perimeter_points[closest_perimeter_point_index].perimeter.get(); - - size_t seam_index; - if (po->config().seam_position == spNearest) { - seam_index = pick_nearest_seam_point_index(perimeter_points, perimeter->start_index, - unscale(last_pos).cast()); - } else { - seam_index = perimeter->seam_index; - } - - Vec3f seam_position = perimeter_points[seam_index].position; - if (perimeter->finalized) { - seam_position = perimeter->final_seam_position; - } - Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); - - if (!loop.split_at_vertex(seam_point)) -// The point is not in the original loop. -// Insert it. - loop.split_at(seam_point, true); -} - -} // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp deleted file mode 100644 index 3883ba004..000000000 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ /dev/null @@ -1,142 +0,0 @@ -#ifndef libslic3r_SeamPlacerNG_hpp_ -#define libslic3r_SeamPlacerNG_hpp_ - -#include -#include -#include -#include - -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/AABBTreeIndirect.hpp" -#include "libslic3r/KDTreeIndirect.hpp" - -namespace Slic3r { - -class PrintObject; -class ExtrusionLoop; -class Print; -class Layer; - -namespace EdgeGrid { -class Grid; -} - -namespace SeamPlacerImpl { - -struct GlobalModelInfo; -struct SeamComparator; - -enum class EnforcedBlockedSeamPoint { - Blocked = 0, - Neutral = 1, - Enforced = 2, -}; - -// struct representing single perimeter loop -struct Perimeter { - size_t start_index; - size_t end_index; //inclusive! - size_t seam_index; - - // During alignment, a final position may be stored here. In that case, finalized is set to true. - // Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position - // Random position also uses this flexibility to set final seam point position - bool finalized = false; - Vec3f final_seam_position; -}; - -//Struct over which all processing of perimeters is done. For each perimeter point, its respective candidate is created, -// then all the needed attributes are computed and finally, for each perimeter one point is chosen as seam. -// This seam position can be than further aligned -struct SeamCandidate { - SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, - float local_ccw_angle, - EnforcedBlockedSeamPoint type) : - position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), local_ccw_angle( - local_ccw_angle), type(type) { - } - const Vec3f position; - // pointer to Perimter loop of this point. It is shared across all points of the loop - const std::shared_ptr perimeter; - float visibility; - float overhang; - float local_ccw_angle; - EnforcedBlockedSeamPoint type; -}; - -struct FaceVisibilityInfo { - float visibility; -}; - -struct SeamCandidateCoordinateFunctor { - SeamCandidateCoordinateFunctor(std::vector *seam_candidates) : - seam_candidates(seam_candidates) { - } - std::vector *seam_candidates; - float operator()(size_t index, size_t dim) const { - return seam_candidates->operator[](index).position[dim]; - } -}; -} // namespace SeamPlacerImpl - -class SeamPlacer { -public: - using SeamCandidatesTree = - KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float raycasting_decimation_target_error = 1.0f; - static constexpr float raycasting_subdivision_target_length = 2.0f; - //square of number of rays per triangle - static constexpr size_t sqr_rays_per_triangle = 7; - - // arm length used during angles computation - static constexpr float polygon_local_angles_arm_distance = 0.5f; - static constexpr float additional_angle_importance = 0.3f; - - // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer - static constexpr float enforcer_blocker_distance_tolerance = 0.3f; - // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size - static constexpr float enforcer_blocker_oversampling_distance = 0.1f; - - // When searching for seam clusters for alignment: - // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer - static constexpr float seam_align_score_tolerance = 0.5f; - // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, - //it belongs automaticaly to the cluster - static constexpr float seam_align_tolerable_dist = 0.5f; - // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. - // this param limits the number of allowed skips - static constexpr size_t seam_align_tolerable_skips = 4; - // minimum number of seams needed in cluster to make alignemnt happen - static constexpr size_t seam_align_minimum_string_seams = 6; - // iterations of laplace smoothing - static constexpr size_t seam_align_laplace_smoothing_iterations = 20; - - //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: - // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer - std::unordered_map>> m_perimeter_points_per_object; - // Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD tree of all points of the given layer - std::unordered_map>> m_perimeter_points_trees_per_object; - - void init(const Print &print); - - void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point& last_pos) const; - -private: - void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); - void calculate_candidates_visibility(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info); - void calculate_overhangs(const PrintObject *po); - void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); - bool find_next_seam_in_layer(const PrintObject *po, - std::pair &last_point, - size_t layer_idx,const SeamPlacerImpl::SeamComparator &comparator, - std::vector> &seam_strings, - std::vector> &outliers); -}; - -} // namespace Slic3r - -#endif // libslic3r_SeamPlacerNG_hpp_ diff --git a/src/libslic3r/GCode/Subdivide.cpp b/src/libslic3r/Subdivide.cpp similarity index 97% rename from src/libslic3r/GCode/Subdivide.cpp rename to src/libslic3r/Subdivide.cpp index 734b1147d..31988a851 100644 --- a/src/libslic3r/GCode/Subdivide.cpp +++ b/src/libslic3r/Subdivide.cpp @@ -3,7 +3,7 @@ namespace Slic3r{ -indexed_triangle_set subdivide( +indexed_triangle_set its_subdivide( const indexed_triangle_set &its, float max_length) { // same order as key order in Edge Divides @@ -123,7 +123,7 @@ indexed_triangle_set subdivide( int index_offset = count_edge_vertices/2; size_t i2 = (divide_index + 2) % 3; - if (count_edge_vertices % 2 == 0 && key_swap == l[i1] < l[i2]) { + if (count_edge_vertices % 2 == 0 && key_swap == (l[i1] < l[i2])) { --index_offset; } int sign = (vs.positive_order) ? 1 : -1; @@ -161,7 +161,7 @@ indexed_triangle_set subdivide( } } - if (index_offset < count_edge_vertices-1) { + if (index_offset < int(count_edge_vertices)-1) { std::pair new_key(new_index, key.second); bool new_key_swap = false; if (new_key.first > new_key.second) { diff --git a/src/libslic3r/GCode/Subdivide.hpp b/src/libslic3r/Subdivide.hpp similarity index 63% rename from src/libslic3r/GCode/Subdivide.hpp rename to src/libslic3r/Subdivide.hpp index 3f6f96d17..f97e4b3c5 100644 --- a/src/libslic3r/GCode/Subdivide.hpp +++ b/src/libslic3r/Subdivide.hpp @@ -5,7 +5,7 @@ namespace Slic3r { -indexed_triangle_set subdivide(const indexed_triangle_set &its, float max_length); +indexed_triangle_set its_subdivide(const indexed_triangle_set &its, float max_length); } From ca259caf336d5a69a410ab71af909d5a23724e20 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 9 Mar 2022 15:47:47 +0100 Subject: [PATCH 37/71] fix ccw angle computation in Point.cpp --- src/libslic3r/Point.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index b2427d46d..66f125bb6 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -131,9 +131,11 @@ double Point::ccw(const Line &line) const // i.e. this assumes a CCW rotation from p1 to p2 around this double Point::ccw_angle(const Point &p1, const Point &p2) const { - //FIXME this calculates an atan2 twice! Project one vector into the other! - double angle = atan2(p1.x() - (*this).x(), p1.y() - (*this).y()) - - atan2(p2.x() - (*this).x(), p2.y() - (*this).y()); + const Point v1 = *this - p1; + const Point v2 = p2 - *this; + int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); + int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); + float angle = float(atan2(float(cross), float(dot))); // we only want to return only positive angles return angle <= 0 ? angle + 2*PI : angle; } From bb89b630d9b2a27f79b809124a76626bbabce870 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 14 Mar 2022 12:42:41 +0100 Subject: [PATCH 38/71] implemented occlusion estimation for objects with negative volumes --- src/libslic3r/GCode/SeamPlacer.cpp | 135 +++++++++++++++++++++-------- src/libslic3r/GCode/SeamPlacer.hpp | 2 +- 2 files changed, 99 insertions(+), 38 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 1fd7a6002..0e04127a9 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -8,7 +8,6 @@ #include #include - //For polynomial fitting #include #include @@ -33,6 +32,10 @@ namespace Slic3r { namespace SeamPlacerImpl { +template int sgn(T val) { + return int(T(0) < val) - int(val < T(0)); +} + // base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) // checkout e.g. here: https://www.geogebra.org/calculator float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { @@ -178,7 +181,7 @@ Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { } std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles) { + const indexed_triangle_set &triangles, size_t negative_volumes_start_index) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: start"; @@ -194,6 +197,8 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< } } + bool model_contains_negative_parts = negative_volumes_start_index < triangles.indices.size(); + std::vector result(triangles.indices.size()); tbb::parallel_for(tbb::blocked_range(0, result.size()), [&](tbb::blocked_range r) { @@ -214,16 +219,44 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< for (const auto &dir : precomputed_sample_directions) { Vec3f final_ray_dir = (f.to_world(dir)); - igl::Hit hitpoint; - // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and - // direction. - Vec3d ray_origin_d = (center + normal).cast(); // start one mm above surface. - Vec3d final_ray_dir_d = final_ray_dir.cast(); - bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, - triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); + if (!model_contains_negative_parts) { + igl::Hit hitpoint; + // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and + // direction. + Vec3d final_ray_dir_d = final_ray_dir.cast(); + Vec3d ray_origin_d = (center + normal * 0.1).cast(); // start above surface. + bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, + triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); + if (hit) { + dest.visibility -= decrease; + } + } else { + std::vector hits; + int in_negative = 0; + if (face_index >= negative_volumes_start_index) { // if casting from negative volume face, invert direction + final_ray_dir = -1.0 * final_ray_dir; + normal = -normal; + } + Vec3d final_ray_dir_d = final_ray_dir.cast(); + Vec3d ray_origin_d = (center + normal * 0.1).cast(); // start above surface. + bool some_hit = AABBTreeIndirect::intersect_ray_all_hits(triangles.vertices, + triangles.indices, raycasting_tree, + ray_origin_d, final_ray_dir_d, hits); + if (some_hit) { + // NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point; + // It cannot be inside model, and it cannot be inside negative volume + for (int hit_index = hits.size() - 1; hit_index >= 0; --hit_index) { + if (hits[hit_index].id >= negative_volumes_start_index) { //negative volume hit + Vec3f normal = its_face_normal(triangles, hits[hit_index].id); + in_negative += sgn(normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space + // which in reverse hit analysis means, that we are entering negative space :) and vice versa + } else if (in_negative <= 0) { //object hit and we are not in negative space + dest.visibility -= decrease; + break; + } + } + } - if (hit) { - dest.visibility -= decrease; } } } @@ -538,34 +571,58 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ // stores enforces and blockers void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; -// Build AABB tree for raycasting + << "SeamPlacer: gather occlusion meshes: start"; auto obj_transform = po->trafo_centered(); indexed_triangle_set triangle_set; + indexed_triangle_set negative_volumes_set; //add all parts for (const ModelVolume *model_volume : po->model_object()->volumes) { - if (model_volume->type() == ModelVolumeType::MODEL_PART) { + if (model_volume->type() == ModelVolumeType::MODEL_PART + || model_volume->type() == ModelVolumeType::NEGATIVE_VOLUME) { auto model_transformation = model_volume->get_matrix(); indexed_triangle_set model_its = model_volume->mesh().its; its_transform(model_its, model_transformation); - its_merge(triangle_set, model_its); + if (model_volume->type() == ModelVolumeType::MODEL_PART) { + its_merge(triangle_set, model_its); + } else { + its_merge(negative_volumes_set, model_its); + } } } + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather occlusion meshes: end"; - float target_error = SeamPlacer::raycasting_decimation_target_error; - its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: simplify occlusion meshes: start"; + + //simplify raycasting mesh + its_quadric_edge_collapse(triangle_set, SeamPlacer::raycasting_decimation_target_triangle_count, nullptr, nullptr, + nullptr); triangle_set = its_subdivide(triangle_set, SeamPlacer::raycasting_subdivision_target_length); + + //simplify negative volumes + its_quadric_edge_collapse(negative_volumes_set, SeamPlacer::raycasting_decimation_target_triangle_count, nullptr, + nullptr, + nullptr); + negative_volumes_set = its_subdivide(negative_volumes_set, SeamPlacer::raycasting_subdivision_target_length); + + size_t negative_volumes_start_index = triangle_set.indices.size(); + its_merge(triangle_set, negative_volumes_set); its_transform(triangle_set, obj_transform); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: simplify occlusion meshes: end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer:build AABB tree: start"; auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, triangle_set.indices); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer:build AABB tree: end"; result.model = triangle_set; result.model_tree = raycasting_tree; - result.visiblity_info = raycast_visibility(raycasting_tree, triangle_set); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; + result.visiblity_info = raycast_visibility(raycasting_tree, triangle_set, negative_volumes_start_index); #ifdef DEBUG_FILES auto filename = debug_out_path(("visiblity_of_" + std::to_string(po->id().id) + ".obj").c_str()); @@ -615,7 +672,7 @@ struct SeamComparator { float compute_angle_penalty(float ccw_angle) const { // This function is used: // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) - // looks terribly, but it is gaussian combined with sigmoid, + // looks scary, but it is gaussian combined with sigmoid, // so that concave points have much smaller penalty over convex ones return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + @@ -651,9 +708,11 @@ struct SeamComparator { } //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) - float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle) + float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) + * compute_angle_penalty(a.local_ccw_angle) * distance_penalty_a; - float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle) + float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) + * compute_angle_penalty(b.local_ccw_angle) * distance_penalty_b; return penalty_a < penalty_b; @@ -692,8 +751,10 @@ struct SeamComparator { } //ranges: [0 - 1] (0 - 1.3] ; - float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); - float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle); + float penalty_a = (a.visibility + SeamPlacer::additional_angle_importance) + * compute_angle_penalty(a.local_ccw_angle); + float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) + * compute_angle_penalty(b.local_ccw_angle); return penalty_a <= penalty_b || std::abs(penalty_a - penalty_b) < SeamPlacer::seam_align_score_tolerance; } @@ -714,7 +775,7 @@ void debug_export_points(const std::vector())), fill); min_vis = std::min(min_vis, point.visibility); max_vis = std::max(max_vis, point.visibility); @@ -735,19 +796,19 @@ void debug_export_points(const std::vector())), visibility_fill); - Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_penalty(point)); + Vec3i weight_color = value_rgbi(min_weight, max_weight, -comparator.get_penalty(point)); std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) - + "," - + std::to_string(weight_color.z()) + ")"; + + "," + + std::to_string(weight_color.z()) + ")"; weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); } } @@ -789,7 +850,7 @@ void pick_random_seam_point(std::vector &perimeter_points, size_t // algorithm keeps a list of viable points and their lengths. If it finds a point // that is much better than the viable_example_index (e.g. better type, no overhang; see is_first_not_much_worse) // then it throws away stored lists and starts from start - // in the end, he list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not + // in the end, the list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not // big overhang. size_t viable_example_index = start_index; size_t end_index = perimeter_points[start_index].perimeter->end_index; @@ -973,7 +1034,7 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, if ((closest_point.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist && comparator.is_first_not_much_worse(closest_point, next_layer_seam) && are_similar(last_point, closest_point)) { - seam_string.push_back({ layer_idx, closest_point_index }); + seam_string.push_back( { layer_idx, closest_point_index }); last_point_indexes = std::pair { layer_idx, closest_point_index }; return true; } else { @@ -1063,7 +1124,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: skips = SeamPlacer::seam_align_tolerable_skips / 2; last_point_indexes = std::pair(layer_idx, seam_index); while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator,seam_string)) { + if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string)) { //String added, last_point_pos updated, nothing to be done } else { // Layer skipped, reduce number of available skips diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 2b90c1412..b8ba204c1 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -86,7 +86,7 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float raycasting_decimation_target_error = 1.0f; + static constexpr size_t raycasting_decimation_target_triangle_count = 10000; static constexpr float raycasting_subdivision_target_length = 2.0f; //square of number of rays per triangle static constexpr size_t sqr_rays_per_triangle = 7; From bbcd6be25065c5ea8d84809d33e355bf2f54d6e3 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 15 Mar 2022 14:52:55 +0100 Subject: [PATCH 39/71] Implemented piecewise data (curve) fitting with variable kernels --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode/SeamPlacer.cpp | 60 ---------- src/libslic3r/Geometry/Curves.cpp | 65 ++++++++++ src/libslic3r/Geometry/Curves.hpp | 160 +++++++++++++++++++++++++ tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_curve_fitting.cpp | 62 ++++++++++ 6 files changed, 290 insertions(+), 60 deletions(-) create mode 100644 src/libslic3r/Geometry/Curves.cpp create mode 100644 src/libslic3r/Geometry/Curves.hpp create mode 100644 tests/libslic3r/test_curve_fitting.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6ec40f42a..bc7f36fc9 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -142,6 +142,8 @@ set(SLIC3R_SOURCES Geometry/Circle.hpp Geometry/ConvexHull.cpp Geometry/ConvexHull.hpp + Geometry/Curves.cpp + Geometry/Curves.hpp Geometry/MedialAxis.cpp Geometry/MedialAxis.hpp Geometry/Voronoi.hpp diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 0e04127a9..d861b16e7 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -8,10 +8,6 @@ #include #include -//For polynomial fitting -#include -#include - #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/BoundingBox.hpp" @@ -57,62 +53,6 @@ Vec3i value_rgbi(float minimum, float maximum, float value) { return (value_to_rgbf(minimum, maximum, value) * 255).cast(); } -//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 -// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y -std::vector polyfit(const std::vector &points, const std::vector &weights, size_t order) { - // check to make sure inputs are correct - assert(points.size() >= order + 1); - assert(points.size() == weights.size()); - - std::vector squared_weights(weights.size()); - for (size_t index = 0; index < weights.size(); ++index) { - squared_weights[index] = sqrt(weights[index]); - } - - Eigen::VectorXf V0(points.size()); - Eigen::VectorXf V1(points.size()); - Eigen::VectorXf V2(points.size()); - for (size_t index = 0; index < points.size(); index++) { - V0(index) = points[index].x() * squared_weights[index]; - V1(index) = points[index].y() * squared_weights[index]; - V2(index) = points[index].z(); - } - - // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial - Eigen::MatrixXf T(points.size(), order + 1); - // Populate the matrix - for (size_t i = 0; i < points.size(); ++i) - { - for (size_t j = 0; j < order + 1; ++j) - { - T(i, j) = pow(V2(i), j) * squared_weights[i]; - } - } - - // Solve for linear least square fit - const auto QR = T.householderQr(); - Eigen::VectorXf result0 = QR.solve(V0); - Eigen::VectorXf result1 = QR.solve(V1); - std::vector coeff { order + 1 }; - for (size_t k = 0; k < order + 1; k++) { - coeff[k] = Vec2f { result0[k], result1[k] }; - } - return coeff; -} - -Vec3f get_fitted_point(const std::vector &coefficients, float z) { - size_t order = coefficients.size() - 1; - float fitted_x = 0; - float fitted_y = 0; - for (size_t index = 0; index < order + 1; ++index) { - float z_pow = pow(z, index); - fitted_x += coefficients[index].x() * z_pow; - fitted_y += coefficients[index].y() * z_pow; - } - - return Vec3f { fitted_x, fitted_y, z }; -} - /// Coordinate frame class Frame { public: diff --git a/src/libslic3r/Geometry/Curves.cpp b/src/libslic3r/Geometry/Curves.cpp new file mode 100644 index 000000000..fa95926be --- /dev/null +++ b/src/libslic3r/Geometry/Curves.cpp @@ -0,0 +1,65 @@ +#include +#include +#include "Curves.hpp" + + +namespace Slic3r { +namespace Geometry { + +PolynomialCurve fit_polynomial(const std::vector &points, const std::vector &weights, size_t order) { + // check to make sure inputs are correct + assert(points.size() >= order + 1); + assert(points.size() == weights.size()); + + std::vector squared_weights(weights.size()); + for (size_t index = 0; index < weights.size(); ++index) { + squared_weights[index] = sqrt(weights[index]); + } + + Eigen::VectorXf V0(points.size()); + Eigen::VectorXf V1(points.size()); + Eigen::VectorXf V2(points.size()); + for (size_t index = 0; index < points.size(); index++) { + V0(index) = points[index].x() * squared_weights[index]; + V1(index) = points[index].y() * squared_weights[index]; + V2(index) = points[index].z(); + } + + // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial + Eigen::MatrixXf T(points.size(), order + 1); + // Populate the matrix + for (size_t i = 0; i < points.size(); ++i) + { + for (size_t j = 0; j < order + 1; ++j) + { + T(i, j) = pow(V2(i), j) * squared_weights[i]; + } + } + + // Solve for linear least square fit + const auto QR = T.householderQr(); + Eigen::VectorXf result0 = QR.solve(V0); + Eigen::VectorXf result1 = QR.solve(V1); + std::vector coeff { order + 1 }; + for (size_t k = 0; k < order + 1; k++) { + coeff[k] = Vec2f { result0[k], result1[k] }; + } + + return PolynomialCurve{coeff}; +} + +Vec3f PolynomialCurve::get_fitted_point(float z) const { + size_t order = this->coefficients.size() - 1; + float fitted_x = 0; + float fitted_y = 0; + for (size_t index = 0; index < order + 1; ++index) { + float z_pow = pow(z, index); + fitted_x += this->coefficients[index].x() * z_pow; + fitted_y += this->coefficients[index].y() * z_pow; + } + + return Vec3f { fitted_x, fitted_y, z }; +} + +} +} diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp new file mode 100644 index 000000000..3c102f192 --- /dev/null +++ b/src/libslic3r/Geometry/Curves.hpp @@ -0,0 +1,160 @@ +#ifndef SRC_LIBSLIC3R_GEOMETRY_CURVES_HPP_ +#define SRC_LIBSLIC3R_GEOMETRY_CURVES_HPP_ + +#include "libslic3r/Point.hpp" + +#include + +namespace Slic3r { +namespace Geometry { + +struct PolynomialCurve { + std::vector coefficients; + + explicit PolynomialCurve(const std::vector &coefficients) : + coefficients(coefficients) { + } + + Vec3f get_fitted_point(float z) const; +}; + +//https://towardsdatascience.com/least-square-polynomial-CURVES-using-c-eigen-package-c0673728bd01 +// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y +PolynomialCurve fit_polynomial(const std::vector &points, const std::vector &weights, size_t order); + +namespace CurveSmoothingKernels { +//NOTE: Kernel functions are used in range [-1,1]. + +// konstant kernel is used mainly for tests +template +struct ConstantKernel { + float operator()(NumberType w) const { + return NumberType(1); + } +}; + +//https://en.wikipedia.org/wiki/Kernel_(statistics) +template +struct EpanechnikovKernel { + float operator()(NumberType w) const { + return NumberType(0.25) * (NumberType(1) - w * w); + } +}; + +} + +template +using VecX = Eigen::Matrix; + +template +struct PiecewiseFittedCurve { + std::vector> coefficients; + Kernel kernel; + NumberType normalized_kernel_bandwidth; + NumberType length; + NumberType segment_size; + + NumberType get_segment_center(size_t segment_index) const { + return segment_size * segment_index; + } + + //t should be in normalized range [0,1] with respect to the length of the original polygon line + Vec get_fitted_point(const NumberType &t) const { + Vec result { 0 }; + for (int coeff_index = 0; coeff_index < coefficients[0].size(); ++coeff_index) { + NumberType segment_center = this->get_segment_center(coeff_index); + NumberType normalized_center_dis = (segment_center - t) + / (NumberType(0.5) * normalized_kernel_bandwidth); + if (normalized_center_dis >= NumberType(-1) && normalized_center_dis <= NumberType(1)) { + for (size_t dim = 0; dim < Dimension; ++dim) { + result[dim] += kernel(normalized_center_dis) * coefficients[dim][coeff_index]; + } + } + } + return result; + } +}; + +// number_of_segments: how many curve segments (kernel applications) are used. Must be at least 2, because we are centering the segments on the first and last point +// normalized_kernel_bandwidth (0,..): how spread the kernel is over the points in normalized coordinates (points are mapped to parametric range [0,1]) +// for example, 0.5 normalized_kernel_bandwidth means that one curve segment covers half of the points +template +PiecewiseFittedCurve fit_curve(const std::vector> &points, + size_t number_of_segments, const NumberType &normalized_kernel_bandwidth, + Kernel kernel) { + + // check to make sure inputs are correct + assert(normalized_kernel_bandwidth > 0); + assert(number_of_segments >= 2); + assert(number_of_segments <= points.size()); + NumberType length { }; + std::vector knots { }; + for (size_t point_index = 0; point_index < points.size() - 1; ++point_index) { + knots.push_back(length); + length += (points[point_index + 1] - points[point_index]).norm(); + } + //last point + knots.push_back(length); + + //normalize knots + for (NumberType &knot : knots) { + knot = knot / length; + } + + PiecewiseFittedCurve result { }; + + result.kernel = kernel; + result.normalized_kernel_bandwidth = normalized_kernel_bandwidth; + result.length = length; + result.segment_size = NumberType(1) / (number_of_segments - NumberType(1)); + + std::vector> data_points(Dimension); + for (size_t dim = 0; dim < Dimension; ++dim) { + data_points[dim] = Eigen::Matrix(points.size()); + } + + for (size_t index = 0; index < points.size(); index++) { + for (size_t dim = 0; dim < Dimension; ++dim) { + data_points[dim](index) = points[index](dim); + } + } + + Eigen::MatrixXf T(knots.size(), number_of_segments); + for (size_t i = 0; i < knots.size(); ++i) { + for (size_t j = 0; j < number_of_segments; ++j) { + NumberType knot_val = knots[i]; + NumberType segment_center = result.get_segment_center(j); + NumberType normalized_center_dis = (segment_center - knot_val) + / (NumberType(0.5) * normalized_kernel_bandwidth); + if (normalized_center_dis >= NumberType(-1) && normalized_center_dis <= NumberType(1)) { + T(i, j) = kernel(normalized_center_dis); + } else { + T(i, j) = 0; + } + } + } + + // Solve for linear least square fit + std::vector> coefficients(Dimension); + const auto QR = T.colPivHouseholderQr(); + for (size_t dim = 0; dim < Dimension; ++dim) { + coefficients[dim] = QR.solve(data_points[dim]); + } + + result.coefficients = coefficients; + + return result; +} + +template +PiecewiseFittedCurve> +fit_epanechnikov_curve(const std::vector> &points, + size_t number_of_segments, const NumberType &normalized_kernel_bandwidth) { + return fit_curve(points, number_of_segments, normalized_kernel_bandwidth, + CurveSmoothingKernels::EpanechnikovKernel { }); +} + +} +} + +#endif /* SRC_LIBSLIC3R_GEOMETRY_CURVES_HPP_ */ diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 69470aad1..248245182 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(${_TEST_NAME}_tests test_clipper_utils.cpp test_color.cpp test_config.cpp + test_curve_fitting.cpp test_elephant_foot_compensation.cpp test_geometry.cpp test_placeholder_parser.cpp diff --git a/tests/libslic3r/test_curve_fitting.cpp b/tests/libslic3r/test_curve_fitting.cpp new file mode 100644 index 000000000..9968e473a --- /dev/null +++ b/tests/libslic3r/test_curve_fitting.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include + +TEST_CASE("Curves: constant kernel fitting", "[Curves]") { + using namespace Slic3r; + using namespace Slic3r::Geometry; + + std::vector> points { Vec<1, float> { 0 }, Vec<1, float> { 2 }, Vec<1, float> { 7 } }; + size_t number_of_segments = 2; + float normalized_kernel_bandwidth = 1.0f; + auto kernel = CurveSmoothingKernels::ConstantKernel { }; + auto curve = fit_curve(points, number_of_segments, normalized_kernel_bandwidth, kernel); + + REQUIRE(curve.length == Approx(7.0f)); + REQUIRE(curve.coefficients[0].size() == number_of_segments); + REQUIRE(curve.coefficients[0][0] == Approx(1.0f)); + REQUIRE(curve.coefficients[0][1] == Approx(7.0f)); + + REQUIRE(curve.get_fitted_point(0.33)[0] == Approx(1.0f)); +} + +TEST_CASE("Curves: constant kernel fitting 2", "[Curves]") { + using namespace Slic3r; + using namespace Slic3r::Geometry; + + std::vector> points { Vec<1, float> { 0 }, Vec<1, float> { 2 }, Vec<1, float> { 2 }, + Vec<1, float> { 4 } }; + size_t number_of_segments = 2; + float normalized_kernel_bandwidth = 2.0f; + auto kernel = CurveSmoothingKernels::ConstantKernel { }; + auto curve = fit_curve(points, number_of_segments, normalized_kernel_bandwidth, kernel); + + REQUIRE(curve.length == Approx(4.0f)); + REQUIRE(curve.coefficients[0].size() == number_of_segments); + REQUIRE(curve.get_fitted_point(0.33)[0] == Approx(2.0f)); +} + +TEST_CASE("Curves: 2D constant kernel fitting", "[Curves]") { + using namespace Slic3r; + using namespace Slic3r::Geometry; + + std::vector points { Vec2f { 0, 0 }, Vec2f { 2, 1 }, Vec2f { 4, 2 }, Vec2f { 6, 3 } }; + size_t number_of_segments = 4; + float normalized_kernel_bandwidth = 0.49f; + auto kernel = CurveSmoothingKernels::ConstantKernel { }; + auto curve = fit_curve(points, number_of_segments, normalized_kernel_bandwidth, kernel); + + REQUIRE(curve.length == Approx(sqrt(6 * 6 + 3 * 3))); + REQUIRE(curve.coefficients.size() == 2); + REQUIRE(curve.coefficients[0].size() == number_of_segments); + REQUIRE(curve.coefficients[0][0] == Approx(0.0f)); + REQUIRE(curve.coefficients[0][1] == Approx(2.0f)); + REQUIRE(curve.coefficients[0][2] == Approx(4.0f)); + REQUIRE(curve.coefficients[0][3] == Approx(6.0f)); + + REQUIRE(curve.coefficients[1][0] == Approx(0.0f)); + REQUIRE(curve.coefficients[1][1] == Approx(1.0f)); + REQUIRE(curve.coefficients[1][2] == Approx(2.0f)); + REQUIRE(curve.coefficients[1][3] == Approx(3.0f)); +} From 5c23d471de7dca3cdf6f9fc351d9d6d47cfd7991 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 17 Mar 2022 15:11:23 +0100 Subject: [PATCH 40/71] BSplines, Polynomial fitting --- src/libslic3r/CMakeLists.txt | 2 +- src/libslic3r/GCode/SeamPlacer.cpp | 119 +++------ src/libslic3r/GCode/SeamPlacer.hpp | 4 +- src/libslic3r/Geometry/Bicubic.hpp | 291 ++++++++++++++++++++++ src/libslic3r/Geometry/Curves.cpp | 65 ----- src/libslic3r/Geometry/Curves.hpp | 319 ++++++++++++++++--------- src/libslic3r/Point.hpp | 3 + src/libslic3r/SLA/bicubic.h | 186 -------------- tests/libslic3r/test_curve_fitting.cpp | 132 +++++++--- 9 files changed, 636 insertions(+), 485 deletions(-) create mode 100644 src/libslic3r/Geometry/Bicubic.hpp delete mode 100644 src/libslic3r/Geometry/Curves.cpp delete mode 100644 src/libslic3r/SLA/bicubic.h diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index bc7f36fc9..a9ce8dd1f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -138,11 +138,11 @@ set(SLIC3R_SOURCES GCodeWriter.hpp Geometry.cpp Geometry.hpp + Geometry/Bicubic.hpp Geometry/Circle.cpp Geometry/Circle.hpp Geometry/ConvexHull.cpp Geometry/ConvexHull.hpp - Geometry/Curves.cpp Geometry/Curves.hpp Geometry/MedialAxis.cpp Geometry/MedialAxis.hpp diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index d861b16e7..b0254762f 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -17,7 +17,9 @@ #include "libslic3r/QuadricEdgeCollapse.hpp" #include "libslic3r/Subdivide.hpp" -//#define DEBUG_FILES +#include "libslic3r/Geometry/Curves.hpp" + +#define DEBUG_FILES #ifdef DEBUG_FILES #include @@ -185,8 +187,8 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< if (some_hit) { // NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point; // It cannot be inside model, and it cannot be inside negative volume - for (int hit_index = hits.size() - 1; hit_index >= 0; --hit_index) { - if (hits[hit_index].id >= negative_volumes_start_index) { //negative volume hit + for (int hit_index = int(hits.size()) - 1; hit_index >= 0; --hit_index) { + if (hits[hit_index].id >= int(negative_volumes_start_index)) { //negative volume hit Vec3f normal = its_face_normal(triangles, hits[hit_index].id); in_negative += sgn(normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space // which in reverse hit analysis means, that we are entering negative space :) and vice versa @@ -714,8 +716,9 @@ struct SeamComparator { void debug_export_points(const std::vector> &object_perimter_points, const BoundingBox &bounding_box, std::string object_name, const SeamComparator &comparator) { for (size_t layer_idx = 0; layer_idx < object_perimter_points.size(); ++layer_idx) { - std::string angles_file_name = debug_out_path((object_name + "_angles_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG angles_svg {angles_file_name, bounding_box}; + std::string angles_file_name = debug_out_path( + (object_name + "_angles_" + std::to_string(layer_idx) + ".svg").c_str()); + SVG angles_svg { angles_file_name, bounding_box }; float min_vis = 0; float max_vis = min_vis; @@ -725,7 +728,7 @@ void debug_export_points(const std::vector())), fill); min_vis = std::min(min_vis, point.visibility); max_vis = std::max(max_vis, point.visibility); @@ -735,20 +738,22 @@ void debug_export_points(const std::vector())), visibility_fill); Vec3i weight_color = value_rgbi(min_weight, max_weight, -comparator.get_penalty(point)); std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) - + "," - + std::to_string(weight_color.z()) + ")"; + + "," + + std::to_string(weight_color.z()) + ")"; weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); } } @@ -1086,27 +1091,23 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: }); // gather all positions of seams and their weights (weights are derived as negative penalty, they are made positive in next step) - std::vector points(seam_string.size()); + std::vector observations(seam_string.size()); + std::vector observation_points(seam_string.size()); std::vector weights(seam_string.size()); //init min_weight by the first point float min_weight = -comparator.get_penalty( m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]); - // In the sorted seam_string array, point which started the alignment - the best candidate - size_t best_candidate_point_index = 0; - - //gather points positions and weights, update min_weight in each step, and find the best candidate + //gather points positions and weights, update min_weight in each step for (size_t index = 0; index < seam_string.size(); ++index) { - points[index] = + Vec3f pos = m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; + observations[index] = pos; + observation_points[index] = pos.z(); weights[index] = -comparator.get_penalty( m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); min_weight = std::min(min_weight, weights[index]); - // find the best candidate by comparing the layer indexes - if (seam_string[index].first == layer_idx) { - best_candidate_point_index = index; - } } //makes all weights positive @@ -1114,70 +1115,19 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: w = w - min_weight + 0.01; } - //NOTE: the following commented block does polynomial line fitting of the seam string. - // pre-smoothen by Laplace -// for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { -// std::vector new_points(seam_string.size()); -// for (int point_index = 0; point_index < points.size(); ++point_index) { -// size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; -// size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; -// -// new_points[point_index] = (points[prev_idx] * weights[prev_idx] -// + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / -// (weights[prev_idx] + weights[point_index] + weights[next_idx]); -// } -// points = new_points; -// } -// - // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. -// std::vector coefficients = polyfit(points, weights, 4); -// -// // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into -// // Perimeter structure of the point; also set flag aligned to true -// for (const auto &pair : seam_string) { -// float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); -// Vec3f seam_pos = get_fitted_point(coefficients, current_height); -// -// Perimeter *perimeter = -// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); -// perimeter->final_seam_position = seam_pos; -// perimeter->finalized = true; -// } -// -// for (Vec3f &p : points) { -// p = get_fitted_point(coefficients, p.z()); -// } + // Curve Fitting + size_t number_of_splines = std::max(size_t(1), size_t(observations.size() / SeamPlacer::seam_align_seams_per_spline)); + auto curve = Geometry::fit_cubic_bspline(observations, observation_points, weights, number_of_splines); - // LaPlace smoothing iterations over the gathered points. New positions from each iteration are stored in the new_points vector - // and assigned to points at the end of iteration - for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { - std::vector new_points(seam_string.size()); - // start from the best candidate, and smoothen down - for (int point_index = best_candidate_point_index; point_index >= 0; --point_index) { - int prev_idx = point_index > 0 ? point_index - 1 : point_index; - size_t next_idx = point_index < int(points.size()) - 1 ? point_index + 1 : point_index; + // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into + // Perimeter structure of the point; also set flag aligned to true + for (const auto &pair : seam_string) { + Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; + Vec3f seam_pos = curve.get_fitted_value(current_pos.z()); - new_points[point_index] = (points[prev_idx] * weights[prev_idx] - + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / - (weights[prev_idx] + weights[point_index] + weights[next_idx]); - } - // smoothen up the rest of the points - for (size_t point_index = best_candidate_point_index + 1; point_index < points.size(); ++point_index) { - size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; - size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; - - new_points[point_index] = (points[prev_idx] * weights[prev_idx] - + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / - (weights[prev_idx] + weights[point_index] + weights[next_idx]); - } - points = new_points; - } - - // Assign smoothened posiiton to each participating perimeter and set finalized flag - for (size_t index = 0; index < seam_string.size(); ++index) { Perimeter *perimeter = - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].perimeter.get(); - perimeter->final_seam_position = points[index]; + m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); + perimeter->final_seam_position = seam_pos; perimeter->finalized = true; } @@ -1326,3 +1276,4 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern } } // namespace Slic3r + diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index b8ba204c1..f42243448 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -112,8 +112,8 @@ public: static constexpr size_t seam_align_tolerable_skips = 4; // minimum number of seams needed in cluster to make alignemnt happen static constexpr size_t seam_align_minimum_string_seams = 6; - // iterations of laplace smoothing - static constexpr size_t seam_align_laplace_smoothing_iterations = 20; + // points covered by spline; determines number of splines for the given string + static constexpr size_t seam_align_seams_per_spline = 10; //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer diff --git a/src/libslic3r/Geometry/Bicubic.hpp b/src/libslic3r/Geometry/Bicubic.hpp new file mode 100644 index 000000000..9dc4c0083 --- /dev/null +++ b/src/libslic3r/Geometry/Bicubic.hpp @@ -0,0 +1,291 @@ +#ifndef BICUBIC_HPP +#define BICUBIC_HPP + +#include +#include +#include + +#include + +namespace Slic3r { + +namespace Geometry { + +namespace BicubicInternal { +// Linear kernel, to be able to test cubic methods with hat kernels. +template +struct LinearKernel +{ + typedef T FloatType; + + static T a00() { + return T(0.); + } + static T a01() { + return T(0.); + } + static T a02() { + return T(0.); + } + static T a03() { + return T(0.); + } + static T a10() { + return T(1.); + } + static T a11() { + return T(-1.); + } + static T a12() { + return T(0.); + } + static T a13() { + return T(0.); + } + static T a20() { + return T(0.); + } + static T a21() { + return T(1.); + } + static T a22() { + return T(0.); + } + static T a23() { + return T(0.); + } + static T a30() { + return T(0.); + } + static T a31() { + return T(0.); + } + static T a32() { + return T(0.); + } + static T a33() { + return T(0.); + } +}; + +// Interpolation kernel aka Catmul-Rom aka Keyes kernel. +template +struct CubicCatmulRomKernel +{ + typedef T FloatType; + + static T a00() { + return 0; + } + static T a01() { + return (T) -0.5; + } + static T a02() { + return (T) 1.; + } + static T a03() { + return (T) -0.5; + } + static T a10() { + return (T) 1.; + } + static T a11() { + return 0; + } + static T a12() { + return (T) -5. / 2.; + } + static T a13() { + return (T) 3. / 2.; + } + static T a20() { + return 0; + } + static T a21() { + return (T) 0.5; + } + static T a22() { + return (T) 2.; + } + static T a23() { + return (T) -3. / 2.; + } + static T a30() { + return 0; + } + static T a31() { + return 0; + } + static T a32() { + return (T) -0.5; + } + static T a33() { + return (T) 0.5; + } +}; + +// B-spline kernel +template +struct CubicBSplineKernel +{ + typedef T FloatType; + + static T a00() { + return (T) 1. / 6.; + } + static T a01() { + return (T) -3. / 6.; + } + static T a02() { + return (T) 3. / 6.; + } + static T a03() { + return (T) -1. / 6.; + } + static T a10() { + return (T) 4. / 6.; + } + static T a11() { + return 0; + } + static T a12() { + return (T) -6. / 6.; + } + static T a13() { + return (T) 3. / 6.; + } + static T a20() { + return (T) 1. / 6.; + } + static T a21() { + return (T) 3. / 6.; + } + static T a22() { + return (T) 3. / 6.; + } + static T a23() { + return (T) -3. / 6.; + } + static T a30() { + return 0; + } + static T a31() { + return 0; + } + static T a32() { + return 0; + } + static T a33() { + return (T) 1. / 6.; + } +}; + +template +inline T clamp(T a, T lower, T upper) + { + return (a < lower) ? lower : + (a > upper) ? upper : a; +} +} + +template +struct CubicKernelWrapper +{ + typedef typename Kernel::FloatType FloatType; + + static constexpr size_t kernel_span = 4; + + static FloatType kernel(FloatType x) + { + x = fabs(x); + if (x >= (FloatType) 2.) + return 0.0f; + if (x <= (FloatType) 1.) { + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return Kernel::a10() + Kernel::a11() * x + Kernel::a12() * x2 + Kernel::a13() * x3; + } + assert(x > (FloatType )1. && x < (FloatType )2.); + x -= (FloatType) 1.; + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return Kernel::a00() + Kernel::a01() * x + Kernel::a02() * x2 + Kernel::a03() * x3; + } + + static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x) + { + const FloatType x2 = x * x; + const FloatType x3 = x * x * x; + return f0 * (Kernel::a00() + Kernel::a01() * x + Kernel::a02() * x2 + Kernel::a03() * x3) + + f1 * (Kernel::a10() + Kernel::a11() * x + Kernel::a12() * x2 + Kernel::a13() * x3) + + f2 * (Kernel::a20() + Kernel::a21() * x + Kernel::a22() * x2 + Kernel::a23() * x3) + + f3 * (Kernel::a30() + Kernel::a31() * x + Kernel::a32() * x2 + Kernel::a33() * x3); + } +}; + +// Linear splines +template +using LinearKernel = CubicKernelWrapper>; + +// Catmul-Rom splines +template +using CubicCatmulRomKernel = CubicKernelWrapper>; + +// Cubic B-splines +template +using CubicBSplineKernel = CubicKernelWrapper>; + +template +static typename KernelWrapper::FloatType cubic_interpolate(const Eigen::ArrayBase &F, + const typename KernelWrapper::FloatType pt) { + typedef typename KernelWrapper::FloatType T; + const int w = int(F.size()); + const int ix = (int) floor(pt); + const T s = pt - (T) ix; + + if (ix > 1 && ix + 2 < w) { + // Inside the fully interpolated region. + return KernelWrapper::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s); + } + // Transition region. Extend with a constant function. + auto f = [&F, w](T x) { + return F[BicubicInternal::clamp(x, 0, w - 1)]; + }; + return KernelWrapper::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s); +} + +template +static float bicubic_interpolate(const Eigen::MatrixBase &F, + const Eigen::Matrix &pt) { + typedef typename Kernel::FloatType T; + const int w = F.cols(); + const int h = F.rows(); + const int ix = (int) floor(pt[0]); + const int iy = (int) floor(pt[1]); + const T s = pt[0] - (T) ix; + const T t = pt[1] - (T) iy; + + if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) { + // Inside the fully interpolated region. + return Kernel::interpolate( + Kernel::interpolate(F(ix - 1, iy - 1), F(ix, iy - 1), F(ix + 1, iy - 1), F(ix + 2, iy - 1), s), + Kernel::interpolate(F(ix - 1, iy), F(ix, iy), F(ix + 1, iy), F(ix + 2, iy), s), + Kernel::interpolate(F(ix - 1, iy + 1), F(ix, iy + 1), F(ix + 1, iy + 1), F(ix + 2, iy + 1), s), + Kernel::interpolate(F(ix - 1, iy + 2), F(ix, iy + 2), F(ix + 1, iy + 2), F(ix + 2, iy + 2), s), t); + } + // Transition region. Extend with a constant function. + auto f = [&F, &f, w, h](int x, int y) { + return F(BicubicInternal::clamp(x, 0, w - 1), BicubicInternal::clamp(y, 0, h - 1)); + }; + return Kernel::interpolate( + Kernel::interpolate(f(ix - 1, iy - 1), f(ix, iy - 1), f(ix + 1, iy - 1), f(ix + 2, iy - 1), s), + Kernel::interpolate(f(ix - 1, iy), f(ix, iy), f(ix + 1, iy), f(ix + 2, iy), s), + Kernel::interpolate(f(ix - 1, iy + 1), f(ix, iy + 1), f(ix + 1, iy + 1), f(ix + 2, iy + 1), s), + Kernel::interpolate(f(ix - 1, iy + 2), f(ix, iy + 2), f(ix + 1, iy + 2), f(ix + 2, iy + 2), s), t); +} + +} //namespace Geometry + +} // namespace Slic3r + +#endif /* BICUBIC_HPP */ diff --git a/src/libslic3r/Geometry/Curves.cpp b/src/libslic3r/Geometry/Curves.cpp deleted file mode 100644 index fa95926be..000000000 --- a/src/libslic3r/Geometry/Curves.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include "Curves.hpp" - - -namespace Slic3r { -namespace Geometry { - -PolynomialCurve fit_polynomial(const std::vector &points, const std::vector &weights, size_t order) { - // check to make sure inputs are correct - assert(points.size() >= order + 1); - assert(points.size() == weights.size()); - - std::vector squared_weights(weights.size()); - for (size_t index = 0; index < weights.size(); ++index) { - squared_weights[index] = sqrt(weights[index]); - } - - Eigen::VectorXf V0(points.size()); - Eigen::VectorXf V1(points.size()); - Eigen::VectorXf V2(points.size()); - for (size_t index = 0; index < points.size(); index++) { - V0(index) = points[index].x() * squared_weights[index]; - V1(index) = points[index].y() * squared_weights[index]; - V2(index) = points[index].z(); - } - - // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial - Eigen::MatrixXf T(points.size(), order + 1); - // Populate the matrix - for (size_t i = 0; i < points.size(); ++i) - { - for (size_t j = 0; j < order + 1; ++j) - { - T(i, j) = pow(V2(i), j) * squared_weights[i]; - } - } - - // Solve for linear least square fit - const auto QR = T.householderQr(); - Eigen::VectorXf result0 = QR.solve(V0); - Eigen::VectorXf result1 = QR.solve(V1); - std::vector coeff { order + 1 }; - for (size_t k = 0; k < order + 1; k++) { - coeff[k] = Vec2f { result0[k], result1[k] }; - } - - return PolynomialCurve{coeff}; -} - -Vec3f PolynomialCurve::get_fitted_point(float z) const { - size_t order = this->coefficients.size() - 1; - float fitted_x = 0; - float fitted_y = 0; - for (size_t index = 0; index < order + 1; ++index) { - float z_pow = pow(z, index); - fitted_x += this->coefficients[index].x() * z_pow; - fitted_y += this->coefficients[index].y() * z_pow; - } - - return Vec3f { fitted_x, fitted_y, z }; -} - -} -} diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index 3c102f192..d221bfd1f 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -2,156 +2,257 @@ #define SRC_LIBSLIC3R_GEOMETRY_CURVES_HPP_ #include "libslic3r/Point.hpp" +#include "Bicubic.hpp" #include namespace Slic3r { namespace Geometry { +template struct PolynomialCurve { - std::vector coefficients; + std::vector> coefficients; - explicit PolynomialCurve(const std::vector &coefficients) : + explicit PolynomialCurve(std::vector> coefficients) : coefficients(coefficients) { } - Vec3f get_fitted_point(float z) const; -}; - -//https://towardsdatascience.com/least-square-polynomial-CURVES-using-c-eigen-package-c0673728bd01 -// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y -PolynomialCurve fit_polynomial(const std::vector &points, const std::vector &weights, size_t order); - -namespace CurveSmoothingKernels { -//NOTE: Kernel functions are used in range [-1,1]. - -// konstant kernel is used mainly for tests -template -struct ConstantKernel { - float operator()(NumberType w) const { - return NumberType(1); - } -}; - -//https://en.wikipedia.org/wiki/Kernel_(statistics) -template -struct EpanechnikovKernel { - float operator()(NumberType w) const { - return NumberType(0.25) * (NumberType(1) - w * w); - } -}; - -} - -template -using VecX = Eigen::Matrix; - -template -struct PiecewiseFittedCurve { - std::vector> coefficients; - Kernel kernel; - NumberType normalized_kernel_bandwidth; - NumberType length; - NumberType segment_size; - - NumberType get_segment_center(size_t segment_index) const { - return segment_size * segment_index; - } - - //t should be in normalized range [0,1] with respect to the length of the original polygon line - Vec get_fitted_point(const NumberType &t) const { - Vec result { 0 }; - for (int coeff_index = 0; coeff_index < coefficients[0].size(); ++coeff_index) { - NumberType segment_center = this->get_segment_center(coeff_index); - NumberType normalized_center_dis = (segment_center - t) - / (NumberType(0.5) * normalized_kernel_bandwidth); - if (normalized_center_dis >= NumberType(-1) && normalized_center_dis <= NumberType(1)) { - for (size_t dim = 0; dim < Dimension; ++dim) { - result[dim] += kernel(normalized_center_dis) * coefficients[dim][coeff_index]; - } + Vec3f get_fitted_value(const NumberType value) const { + Vec result = Vec::Zero(); + size_t order = this->coefficients.size() - 1; + for (size_t index = 0; index < order + 1; ++index) { + float powered = pow(value, index); + for (size_t dim = 0; dim < Dimension; ++dim) { + result(dim) += powered * this->coefficients[dim](index); } } return result; } }; -// number_of_segments: how many curve segments (kernel applications) are used. Must be at least 2, because we are centering the segments on the first and last point -// normalized_kernel_bandwidth (0,..): how spread the kernel is over the points in normalized coordinates (points are mapped to parametric range [0,1]) -// for example, 0.5 normalized_kernel_bandwidth means that one curve segment covers half of the points -template -PiecewiseFittedCurve fit_curve(const std::vector> &points, - size_t number_of_segments, const NumberType &normalized_kernel_bandwidth, - Kernel kernel) { - +//https://towardsdatascience.com/least-square-polynomial-CURVES-using-c-eigen-package-c0673728bd01 +// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y +template +PolynomialCurve fit_polynomial(const std::vector> &observations, + const std::vector &observation_points, + const std::vector &weights, size_t order) { // check to make sure inputs are correct - assert(normalized_kernel_bandwidth > 0); - assert(number_of_segments >= 2); - assert(number_of_segments <= points.size()); - NumberType length { }; - std::vector knots { }; - for (size_t point_index = 0; point_index < points.size() - 1; ++point_index) { - knots.push_back(length); - length += (points[point_index + 1] - points[point_index]).norm(); - } - //last point - knots.push_back(length); + assert(observation_points.size() >= order + 1); + assert(observation_points.size() == weights.size()); + assert(observations.size() == weights.size()); - //normalize knots - for (NumberType &knot : knots) { - knot = knot / length; + std::vector squared_weights(weights.size()); + for (size_t index = 0; index < weights.size(); ++index) { + squared_weights[index] = sqrt(weights[index]); } - PiecewiseFittedCurve result { }; - - result.kernel = kernel; - result.normalized_kernel_bandwidth = normalized_kernel_bandwidth; - result.length = length; - result.segment_size = NumberType(1) / (number_of_segments - NumberType(1)); - - std::vector> data_points(Dimension); + std::vector> data_points(Dimension); for (size_t dim = 0; dim < Dimension; ++dim) { - data_points[dim] = Eigen::Matrix(points.size()); + data_points[dim] = Eigen::Matrix( + observations.size()); } - - for (size_t index = 0; index < points.size(); index++) { + for (size_t index = 0; index < observations.size(); index++) { for (size_t dim = 0; dim < Dimension; ++dim) { - data_points[dim](index) = points[index](dim); + data_points[dim](index) = observations[index](dim) * squared_weights[index]; } } - Eigen::MatrixXf T(knots.size(), number_of_segments); - for (size_t i = 0; i < knots.size(); ++i) { - for (size_t j = 0; j < number_of_segments; ++j) { - NumberType knot_val = knots[i]; - NumberType segment_center = result.get_segment_center(j); - NumberType normalized_center_dis = (segment_center - knot_val) - / (NumberType(0.5) * normalized_kernel_bandwidth); - if (normalized_center_dis >= NumberType(-1) && normalized_center_dis <= NumberType(1)) { - T(i, j) = kernel(normalized_center_dis); - } else { - T(i, j) = 0; - } + Eigen::MatrixXf T(observation_points.size(), order + 1); + // Populate the matrix + for (size_t i = 0; i < observation_points.size(); ++i) { + for (size_t j = 0; j < order + 1; ++j) { + T(i, j) = pow(observation_points[i], j) * squared_weights[i]; } } + const auto QR = T.householderQr(); + std::vector> coefficients(Dimension); // Solve for linear least square fit - std::vector> coefficients(Dimension); - const auto QR = T.colPivHouseholderQr(); for (size_t dim = 0; dim < Dimension; ++dim) { coefficients[dim] = QR.solve(data_points[dim]); } + return PolynomialCurve(coefficients); +} + +template +struct PiecewiseFittedCurve { + std::vector> coefficients; + Kernel kernel; + NumberType start; + NumberType length; + NumberType n_segment_size; + size_t segments_count; + + NumberType get_n_segment_start(int segment_index) const { + return n_segment_size * segment_index; + } + + NumberType normalize(const NumberType &observation_point) const { + return (observation_point - start) / length; + } + + Vec get_fitted_value(const NumberType &observation_point) const { + Vec result = Vec::Zero(); + NumberType t = normalize(observation_point); + + int start_segment_idx = int(floor(t / this->n_segment_size)) - int(Kernel::kernel_span * 0.5f - 1.0f); + for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); + segment_index++) { + if (segment_index < 0 || segment_index >= int(this->segments_count)) { + continue; + } + NumberType segment_start = this->get_n_segment_start(segment_index); + NumberType normalized_segment_distance = (segment_start - t) / this->n_segment_size; + + for (size_t dim = 0; dim < Dimension; ++dim) { + result(dim) += kernel.kernel(normalized_segment_distance) * coefficients[dim](segment_index); + } + } + return result; + } +} +; + +// observations: data to be fitted by the curve +// observation points: growing sequence of points where the observations were made. +// In other words, for function f(x) = y, observations are y0...yn, and observation points are x0...xn +// weights: how important the observation is +// number_of_inner_splines: how many full inner splines are fit into the normalized valid range 0,1; +// final number of knots is Kernel::kernel_span times larger + additional segments on start and end +// Kernel: model used for the curve fitting +template +PiecewiseFittedCurve fit_curve( + const std::vector> &observations, + const std::vector &observation_points, + const std::vector &weights, + size_t number_of_inner_splines, + Kernel kernel) { + + // check to make sure inputs are correct + assert(number_of_inner_splines >= 1); + assert(observation_points.size() == observations.size()); + assert(observation_points.size() == weights.size()); + assert(number_of_inner_splines <= observations.size()); + assert(observations.size() >= 4); + + size_t extremes_repetition = Kernel::kernel_span - 1; //how many (additional) times is the first and last point repeated + + //prepare sqrt of weights, which will then be applied to both matrix T and observed data: https://en.wikipedia.org/wiki/Weighted_least_squares + std::vector sqrt_weights(weights.size() + extremes_repetition * 2); + for (size_t index = 0; index < weights.size(); ++index) { + assert(weights[index] > 0); + sqrt_weights[index + extremes_repetition] = sqrt(weights[index]); + } + //repeat weights for addtional segments + for (int index = 0; index < int(extremes_repetition); ++index) { + sqrt_weights[index] = sqrt(weights.front()); + sqrt_weights[sqrt_weights.size() - index - 1] = sqrt(weights.back()); + } + + // prepare result and compute metadata + PiecewiseFittedCurve result { }; + + NumberType original_len = observation_points.back() - observation_points.front(); + NumberType orig_segment_size = original_len / NumberType(number_of_inner_splines * Kernel::kernel_span); + result.kernel = kernel; + result.start = observation_points.front() - extremes_repetition * orig_segment_size; + result.length = observation_points.back() + extremes_repetition * orig_segment_size - result.start; + result.segments_count = number_of_inner_splines * Kernel::kernel_span + extremes_repetition * 2; + result.n_segment_size = NumberType(1) / NumberType(result.segments_count - 1); + + //normalize observations points by start and length + std::vector normalized_obs_points(observation_points.size() + extremes_repetition * 2); + for (size_t index = 0; index < observation_points.size(); ++index) { + normalized_obs_points[index + extremes_repetition] = result.normalize(observation_points[index]); + } + // create artificial values at the extremes for constant curve fitting + for (int index = 0; index < int(extremes_repetition); ++index) { + normalized_obs_points[extremes_repetition - 1 - index] = result.normalize(observation_points.front() + - index * orig_segment_size - NumberType(0.5) * orig_segment_size); + + normalized_obs_points[normalized_obs_points.size() - extremes_repetition + index] = result.normalize( + observation_points.back() + index * orig_segment_size + NumberType(0.5) * orig_segment_size); + } + + // prepare observed data + std::vector> data_points(Dimension); + for (size_t dim = 0; dim < Dimension; ++dim) { + data_points[dim] = Eigen::Matrix( + observations.size() + extremes_repetition * 2); + } + for (size_t index = 0; index < observations.size(); index++) { + for (size_t dim = 0; dim < Dimension; ++dim) { + data_points[dim](index + extremes_repetition) = observations[index](dim) + * sqrt_weights[index + extremes_repetition]; + } + } + //duplicate observed data at the start and end + for (int index = 0; index < int(extremes_repetition); index++) { + for (size_t dim = 0; dim < Dimension; ++dim) { + data_points[dim](index) = observations.front()(dim) * sqrt_weights[index]; + data_points[dim](data_points[dim].size() - index - 1) = observations.back()(dim) + * sqrt_weights[data_points[dim].size() - index - 1]; + } + } + + //Create weight matrix for each point and each segment. + Eigen::MatrixXf T(normalized_obs_points.size(), result.segments_count); + for (size_t i = 0; i < normalized_obs_points.size(); ++i) { + for (size_t j = 0; j < result.segments_count; ++j) { + T(i, j) = NumberType(0); + } + } + + for (size_t i = 0; i < normalized_obs_points.size(); ++i) { + NumberType knot_val = normalized_obs_points[i]; + int start_segment_idx = int(floor(knot_val / result.n_segment_size)) - int(Kernel::kernel_span * 0.5f - 1.0f); + for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); + segment_index++) { + if (segment_index < 0 || segment_index >= int(result.segments_count)) { + continue; + } + NumberType segment_start = result.get_n_segment_start(segment_index); + NumberType normalized_segment_distance = (segment_start - knot_val) / result.n_segment_size; + // fill in kernel value with weight applied + + T(i, segment_index) += kernel.kernel(normalized_segment_distance) * sqrt_weights[i]; + } + } + + // Solve for linear least square fit + std::vector> coefficients(Dimension); + const auto QR = T.fullPivHouseholderQr(); + for (size_t dim = 0; dim < Dimension; ++dim) { + coefficients[dim] = QR.solve(data_points[dim]); + } + + // store coefficients in result result.coefficients = coefficients; return result; } template -PiecewiseFittedCurve> -fit_epanechnikov_curve(const std::vector> &points, - size_t number_of_segments, const NumberType &normalized_kernel_bandwidth) { - return fit_curve(points, number_of_segments, normalized_kernel_bandwidth, - CurveSmoothingKernels::EpanechnikovKernel { }); +PiecewiseFittedCurve> +fit_cubic_bspline( + const std::vector> &observations, + std::vector observation_points, + std::vector weights, + size_t number_of_segments) { + return fit_curve(observations, observation_points, weights, number_of_segments, + CubicBSplineKernel { }); +} + +template +PiecewiseFittedCurve> +fit_catmul_rom_spline( + const std::vector> &observations, + std::vector observation_points, + std::vector weights, + size_t number_of_segments) { + return fit_curve(observations, observation_points, weights, number_of_segments, + CubicCatmulRomKernel { }); } } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 6b430c2fe..09ff533fe 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -28,6 +28,9 @@ using Mat = Eigen::Matrix; template using Vec = Mat; +template +using DynVec = Eigen::Matrix; + // Eigen types, to replace the Slic3r's own types in the future. // Vector types with a fixed point coordinate base type. using Vec2crd = Eigen::Matrix; diff --git a/src/libslic3r/SLA/bicubic.h b/src/libslic3r/SLA/bicubic.h deleted file mode 100644 index 870d00dbd..000000000 --- a/src/libslic3r/SLA/bicubic.h +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef BICUBIC_HPP -#define BICUBIC_HPP - -#include -#include -#include - -#include - -namespace Slic3r { - -namespace BicubicInternal { - // Linear kernel, to be able to test cubic methods with hat kernels. - template - struct LinearKernel - { - typedef T FloatType; - - static T a00() { return T(0.); } - static T a01() { return T(0.); } - static T a02() { return T(0.); } - static T a03() { return T(0.); } - static T a10() { return T(1.); } - static T a11() { return T(-1.); } - static T a12() { return T(0.); } - static T a13() { return T(0.); } - static T a20() { return T(0.); } - static T a21() { return T(1.); } - static T a22() { return T(0.); } - static T a23() { return T(0.); } - static T a30() { return T(0.); } - static T a31() { return T(0.); } - static T a32() { return T(0.); } - static T a33() { return T(0.); } - }; - - // Interpolation kernel aka Catmul-Rom aka Keyes kernel. - template - struct CubicCatmulRomKernel - { - typedef T FloatType; - - static T a00() { return 0; } - static T a01() { return (T)-0.5; } - static T a02() { return (T) 1.; } - static T a03() { return (T)-0.5; } - static T a10() { return (T) 1.; } - static T a11() { return 0; } - static T a12() { return (T)-5./2.; } - static T a13() { return (T) 3./2.; } - static T a20() { return 0; } - static T a21() { return (T) 0.5; } - static T a22() { return (T) 2.; } - static T a23() { return (T)-3./2.; } - static T a30() { return 0; } - static T a31() { return 0; } - static T a32() { return (T)-0.5; } - static T a33() { return (T) 0.5; } - }; - - // B-spline kernel - template - struct CubicBSplineKernel - { - typedef T FloatType; - - static T a00() { return (T) 1./6.; } - static T a01() { return (T) -3./6.; } - static T a02() { return (T) 3./6.; } - static T a03() { return (T) -1./6.; } - static T a10() { return (T) 4./6.; } - static T a11() { return 0; } - static T a12() { return (T) -6./6.; } - static T a13() { return (T) 3./6.; } - static T a20() { return (T) 1./6.; } - static T a21() { return (T) 3./6.; } - static T a22() { return (T) 3./6.; } - static T a23() { return (T)- 3./6.; } - static T a30() { return 0; } - static T a31() { return 0; } - static T a32() { return 0; } - static T a33() { return (T) 1./6.; } - }; - - template - inline T clamp(T a, T lower, T upper) - { - return (a < lower) ? lower : - (a > upper) ? upper : a; - } -} - -template -struct CubicKernel -{ - typedef typename KERNEL KernelInternal; - typedef typename KERNEL::FloatType FloatType; - - static FloatType kernel(FloatType x) - { - x = fabs(x); - if (x >= (FloatType)2.) - return 0.0f; - if (x <= (FloatType)1.) { - FloatType x2 = x * x; - FloatType x3 = x2 * x; - return KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3; - } - assert(x > (FloatType)1. && x < (FloatType)2.); - x -= (FloatType)1.; - FloatType x2 = x * x; - FloatType x3 = x2 * x; - return KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3; - } - - static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x) - { - const FloatType x2 = x*x; - const FloatType x3 = x*x*x; - return f0*(KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3) + - f1*(KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3) + - f2*(KERNEL::a20() + KERNEL::a21() * x + KERNEL::a22() * x2 + KERNEL::a23() * x3) + - f3*(KERNEL::a30() + KERNEL::a31() * x + KERNEL::a32() * x2 + KERNEL::a33() * x3); - } -}; - -// Linear splines -typedef CubicKernel> LinearKernelf; -typedef CubicKernel> LinearKerneld; -// Catmul-Rom splines -typedef CubicKernel> CubicCatmulRomKernelf; -typedef CubicKernel> CubicCatmulRomKerneld; -typedef CubicKernel> CubicInterpolationKernelf; -typedef CubicKernel> CubicInterpolationKerneld; -// Cubic B-splines -typedef CubicKernel> CubicBSplineKernelf; -typedef CubicKernel> CubicBSplineKerneld; - -template -static float cubic_interpolate(const Eigen::ArrayBase &F, const typename KERNEL::FloatType pt, const typename KERNEL::FloatType dx) -{ - typedef typename KERNEL::FloatType T; - const int w = int(F.size()); - const int ix = (int)floor(pt); - const T s = pt - (T)ix; - - if (ix > 1 && ix + 2 < w) { - // Inside the fully interpolated region. - return KERNEL::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s); - } - // Transition region. Extend with a constant function. - auto f = [&F, w](x) { return F[BicubicInternal::clamp(x, 0, w - 1)]; } - return KERNEL::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s); -} - -template -static float bicubic_interpolate(const Eigen::MatrixBase &F, const Eigen::Matrix &pt, const typename KERNEL::FloatType dx) -{ - typedef typename KERNEL::FloatType T; - const int w = F.cols(); - const int h = F.rows(); - const int ix = (int)floor(pt[0]); - const int iy = (int)floor(pt[1]); - const T s = pt[0] - (T)ix; - const T t = pt[1] - (T)iy; - - if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) { - // Inside the fully interpolated region. - return KERNEL::interpolate( - KERNEL::interpolate(F(ix-1,iy-1),F(ix ,iy-1),F(ix+1,iy-1),F(ix+2,iy-1),s), - KERNEL::interpolate(F(ix-1,iy ),F(ix ,iy ),F(ix+1,iy ),F(ix+2,iy ),s), - KERNEL::interpolate(F(ix-1,iy+1),F(ix ,iy+1),F(ix+1,iy+1),F(ix+2,iy+1),s), - KERNEL::interpolate(F(ix-1,iy+2),F(ix ,iy+2),F(ix+1,iy+2),F(ix+2,iy+2),s),t); - } - // Transition region. Extend with a constant function. - auto f = [&f, w, h](int x, int y) { return F(BicubicInternal::clamp(x,0,w-1),BicubicInternal::clamp(y,0,h-1)); } - return KERNEL::interpolate( - KERNEL::interpolate(f(ix-1,iy-1),f(ix ,iy-1),f(ix+1,iy-1),f(ix+2,iy-1),s), - KERNEL::interpolate(f(ix-1,iy ),f(ix ,iy ),f(ix+1,iy ),f(ix+2,iy ),s), - KERNEL::interpolate(f(ix-1,iy+1),f(ix ,iy+1),f(ix+1,iy+1),f(ix+2,iy+1),s), - KERNEL::interpolate(f(ix-1,iy+2),f(ix ,iy+2),f(ix+1,iy+2),f(ix+2,iy+2),s),t); -} - -} // namespace Slic3r - -#endif /* BICUBIC_HPP */ diff --git a/tests/libslic3r/test_curve_fitting.cpp b/tests/libslic3r/test_curve_fitting.cpp index 9968e473a..a38e9b5a0 100644 --- a/tests/libslic3r/test_curve_fitting.cpp +++ b/tests/libslic3r/test_curve_fitting.cpp @@ -2,61 +2,117 @@ #include #include +#include +#include -TEST_CASE("Curves: constant kernel fitting", "[Curves]") { +TEST_CASE("Curves: cubic b spline fit test", "[Curves]") { using namespace Slic3r; using namespace Slic3r::Geometry; - std::vector> points { Vec<1, float> { 0 }, Vec<1, float> { 2 }, Vec<1, float> { 7 } }; - size_t number_of_segments = 2; - float normalized_kernel_bandwidth = 1.0f; - auto kernel = CurveSmoothingKernels::ConstantKernel { }; - auto curve = fit_curve(points, number_of_segments, normalized_kernel_bandwidth, kernel); + auto fx = [&](size_t index) { + return float(index) / 200.0f; + }; - REQUIRE(curve.length == Approx(7.0f)); - REQUIRE(curve.coefficients[0].size() == number_of_segments); - REQUIRE(curve.coefficients[0][0] == Approx(1.0f)); - REQUIRE(curve.coefficients[0][1] == Approx(7.0f)); + auto fy = [&](size_t index) { + return 1.0f; + }; - REQUIRE(curve.get_fitted_point(0.33)[0] == Approx(1.0f)); + std::vector> observations { }; + std::vector observation_points { }; + std::vector weights { }; + for (size_t index = 0; index < 200; ++index) { + observations.push_back(Vec<1, float> { fy(index) }); + observation_points.push_back(fx(index)); + weights.push_back(1); + } + + Vec2f fmin { fx(0), fy(0) }; + Vec2f fmax { fx(200), fy(200) }; + + auto bspline = fit_cubic_bspline(observations, observation_points, weights, 1); + + Approx ap(1.0f); + ap.epsilon(0.1f); + + for (int p = 0; p < 200; ++p) { + float fitted_val = bspline.get_fitted_value(fx(p))(0); + float expected = fy(p); + + REQUIRE(fitted_val == ap(expected)); + + } } -TEST_CASE("Curves: constant kernel fitting 2", "[Curves]") { +TEST_CASE("Curves: quadratic f cubic b spline fit test", "[Curves]") { using namespace Slic3r; using namespace Slic3r::Geometry; - std::vector> points { Vec<1, float> { 0 }, Vec<1, float> { 2 }, Vec<1, float> { 2 }, - Vec<1, float> { 4 } }; - size_t number_of_segments = 2; - float normalized_kernel_bandwidth = 2.0f; - auto kernel = CurveSmoothingKernels::ConstantKernel { }; - auto curve = fit_curve(points, number_of_segments, normalized_kernel_bandwidth, kernel); + auto fx = [&](size_t index) { + return float(index) / 100.0f; + }; - REQUIRE(curve.length == Approx(4.0f)); - REQUIRE(curve.coefficients[0].size() == number_of_segments); - REQUIRE(curve.get_fitted_point(0.33)[0] == Approx(2.0f)); + auto fy = [&](size_t index) { + return (fx(index) - 1) * (fx(index) - 1); + }; + + std::vector> observations { }; + std::vector observation_points { }; + std::vector weights { }; + for (size_t index = 0; index < 200; ++index) { + observations.push_back(Vec<1, float> { fy(index) }); + observation_points.push_back(fx(index)); + weights.push_back(1); + } + + Vec2f fmin { fx(0), fy(0) }; + Vec2f fmax { fx(200), fy(200) }; + + auto bspline = fit_cubic_bspline(observations, observation_points, weights, 10); + + for (int p = 0; p < 200; ++p) { + float fitted_val = bspline.get_fitted_value(fx(p))(0); + float expected = fy(p); + + auto check = [](float a, float b) { + return abs(a - b) < 0.2f; + }; + //Note: checking is problematic, splines will not perfectly align + REQUIRE(check(fitted_val, expected)); + + } } -TEST_CASE("Curves: 2D constant kernel fitting", "[Curves]") { +TEST_CASE("Curves: polynomial fit test", "[Curves]") { using namespace Slic3r; using namespace Slic3r::Geometry; - std::vector points { Vec2f { 0, 0 }, Vec2f { 2, 1 }, Vec2f { 4, 2 }, Vec2f { 6, 3 } }; - size_t number_of_segments = 4; - float normalized_kernel_bandwidth = 0.49f; - auto kernel = CurveSmoothingKernels::ConstantKernel { }; - auto curve = fit_curve(points, number_of_segments, normalized_kernel_bandwidth, kernel); + auto fx = [&](size_t index) { + return float(index) / 100.0f; + }; - REQUIRE(curve.length == Approx(sqrt(6 * 6 + 3 * 3))); - REQUIRE(curve.coefficients.size() == 2); - REQUIRE(curve.coefficients[0].size() == number_of_segments); - REQUIRE(curve.coefficients[0][0] == Approx(0.0f)); - REQUIRE(curve.coefficients[0][1] == Approx(2.0f)); - REQUIRE(curve.coefficients[0][2] == Approx(4.0f)); - REQUIRE(curve.coefficients[0][3] == Approx(6.0f)); + auto fy = [&](size_t index) { + return (fx(index) - 1) * (fx(index) - 1); + }; - REQUIRE(curve.coefficients[1][0] == Approx(0.0f)); - REQUIRE(curve.coefficients[1][1] == Approx(1.0f)); - REQUIRE(curve.coefficients[1][2] == Approx(2.0f)); - REQUIRE(curve.coefficients[1][3] == Approx(3.0f)); + std::vector> observations { }; + std::vector observation_points { }; + std::vector weights { }; + for (size_t index = 0; index < 200; ++index) { + observations.push_back(Vec<1, float> { fy(index) }); + observation_points.push_back(fx(index)); + weights.push_back(1); + } + + Vec2f fmin { fx(0), fy(0) }; + Vec2f fmax { fx(200), fy(200) }; + + Approx ap(1.0f); + ap.epsilon(0.1f); + + auto poly = fit_polynomial(observations, observation_points, weights, 2); + + REQUIRE(poly.coefficients[0](0) == ap(1)); + REQUIRE(poly.coefficients[0](1) == ap(-2)); + REQUIRE(poly.coefficients[0](2) == ap(1)); } + From 15135ef2ed35f7d65fba6150af4bfb683e303e22 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 17 Mar 2022 17:38:21 +0100 Subject: [PATCH 41/71] fixes, central enforced point preference --- src/libslic3r/GCode/SeamPlacer.cpp | 61 ++++++++++++++++++++++++++---- src/libslic3r/GCode/SeamPlacer.hpp | 5 ++- src/libslic3r/Geometry/Curves.hpp | 15 +++++--- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index b0254762f..d466941c0 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -409,6 +409,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: std::queue oversampled_points { }; size_t orig_angle_index = 0; perimeter->start_index = result_vec.size(); + bool some_point_enforced = false; while (!orig_polygon_points.empty() || !oversampled_points.empty()) { EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; Vec3f position; @@ -427,6 +428,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: if (global_model_info.is_enforced(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { type = EnforcedBlockedSeamPoint::Enforced; + some_point_enforced = true; } if (global_model_info.is_blocked(position, SeamPlacer::enforcer_blocker_distance_tolerance)) { @@ -449,10 +451,28 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: } result_vec.emplace_back(position, perimeter, local_ccw_angle, type); - } perimeter->end_index = result_vec.size() - 1; + + // We will find first patch of enforced points (patch: continous section of enforced points) and select the middle + // point, which will have priority during alignemnt + // If there are multiple enforced patches in the perimeter, others are ignored + if (some_point_enforced) { + size_t first_enforced_idx = perimeter->start_index; + while (first_enforced_idx <= perimeter->end_index + && result_vec[first_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced) { + first_enforced_idx++; + } + size_t last_enforced_idx = first_enforced_idx; + while (last_enforced_idx < perimeter->end_index + && result_vec[last_enforced_idx + 1].type == EnforcedBlockedSeamPoint::Enforced) { + last_enforced_idx++; + } + size_t central_idx = (first_enforced_idx + last_enforced_idx) / 2; + result_vec[central_idx].central_enforcer = true; + } + } // Get index of previous and next perimeter point of the layer. Because SeamCandidates of all polygons of the given layer @@ -603,7 +623,6 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; } -//Comparator of seam points. It has two necessary methods: is_first_better and is_first_not_much_worse struct SeamComparator { SeamPosition setup; @@ -625,6 +644,15 @@ struct SeamComparator { // should return if a is better seamCandidate than b bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location = Vec2f { 0.0f, 0.0f }) const { + if (setup == SeamPosition::spAligned && a.central_enforcer != b.central_enforcer) { + if (a.central_enforcer) { + return true; + } + if (b.central_enforcer) { + return false; + } + } + // Blockers/Enforcers discrimination, top priority if (a.type > b.type) { return true; @@ -664,6 +692,15 @@ struct SeamComparator { // sema point of the perimeter, to find out if the aligned point is not much worse than the current seam bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { // Blockers/Enforcers discrimination, top priority + if (setup == SeamPosition::spAligned && a.central_enforcer != b.central_enforcer) { + if (a.central_enforcer) { + return true; + } + if (b.central_enforcer) { + return false; + } + } + if (a.type == EnforcedBlockedSeamPoint::Enforced) { return true; } @@ -972,6 +1009,13 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, SeamCandidate &next_layer_seam = m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; + if (next_layer_seam.central_enforcer + && (next_layer_seam.position - projected_position).norm() < 3 * SeamPlacer::seam_align_tolerable_dist) { + seam_string.push_back( { layer_idx, closest_point.perimeter->seam_index }); + last_point_indexes = std::pair { layer_idx, closest_point.perimeter->seam_index }; + return true; + } + auto are_similar = [&](const SeamCandidate &a, const SeamCandidate &b) { return comparator.is_first_not_much_worse(a, b) && comparator.is_first_not_much_worse(b, a); }; @@ -1027,7 +1071,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: } } - //sort them before alignment. Alignment is sensitive to intitializaion, this gives it better chance to choose something nice + //sort them before alignment. Alignment is sensitive to initializaion, this gives it better chance to choose something nice std::sort(seams.begin(), seams.end(), [&](const std::pair &left, const std::pair &right) { return comparator.is_first_better(m_perimeter_points_per_object[po][left.first][left.second], @@ -1091,7 +1135,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: }); // gather all positions of seams and their weights (weights are derived as negative penalty, they are made positive in next step) - std::vector observations(seam_string.size()); + std::vector observations(seam_string.size()); std::vector observation_points(seam_string.size()); std::vector weights(seam_string.size()); @@ -1103,7 +1147,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: for (size_t index = 0; index < seam_string.size(); ++index) { Vec3f pos = m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; - observations[index] = pos; + observations[index] = pos.head<2>(); observation_points[index] = pos.z(); weights[index] = -comparator.get_penalty( m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); @@ -1116,18 +1160,19 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: } // Curve Fitting - size_t number_of_splines = std::max(size_t(1), size_t(observations.size() / SeamPlacer::seam_align_seams_per_spline)); + size_t number_of_splines = std::max(size_t(1), + size_t(observations.size() / SeamPlacer::seam_align_seams_per_spline)); auto curve = Geometry::fit_cubic_bspline(observations, observation_points, weights, number_of_splines); // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Perimeter structure of the point; also set flag aligned to true for (const auto &pair : seam_string) { Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; - Vec3f seam_pos = curve.get_fitted_value(current_pos.z()); + Vec2f fitted_pos = curve.get_fitted_value(current_pos.z()); Perimeter *perimeter = m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); - perimeter->final_seam_position = seam_pos; + perimeter->final_seam_position = Vec3f { fitted_pos.x(), fitted_pos.y(), current_pos.z() }; perimeter->finalized = true; } diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index f42243448..939d6ec75 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -56,7 +56,7 @@ struct SeamCandidate { float local_ccw_angle, EnforcedBlockedSeamPoint type) : position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), local_ccw_angle( - local_ccw_angle), type(type) { + local_ccw_angle), type(type), central_enforcer(false){ } const Vec3f position; // pointer to Perimter loop of this point. It is shared across all points of the loop @@ -65,6 +65,7 @@ struct SeamCandidate { float overhang; float local_ccw_angle; EnforcedBlockedSeamPoint type; + bool central_enforcer; //marks this candidate as central point of enforced segment on the perimeter - important for alignment }; struct FaceVisibilityInfo { @@ -113,7 +114,7 @@ public: // minimum number of seams needed in cluster to make alignemnt happen static constexpr size_t seam_align_minimum_string_seams = 6; // points covered by spline; determines number of splines for the given string - static constexpr size_t seam_align_seams_per_spline = 10; + static constexpr size_t seam_align_seams_per_spline = 30; //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index d221bfd1f..b92ad2c36 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -119,7 +119,7 @@ struct PiecewiseFittedCurve { // In other words, for function f(x) = y, observations are y0...yn, and observation points are x0...xn // weights: how important the observation is // number_of_inner_splines: how many full inner splines are fit into the normalized valid range 0,1; -// final number of knots is Kernel::kernel_span times larger + additional segments on start and end +// final number of knots is Kernel::kernel_span times number_of_inner_splines + additional segments on start and end // Kernel: model used for the curve fitting template PiecewiseFittedCurve fit_curve( @@ -144,7 +144,7 @@ PiecewiseFittedCurve fit_curve( assert(weights[index] > 0); sqrt_weights[index + extremes_repetition] = sqrt(weights[index]); } - //repeat weights for addtional segments + //repeat weights for addtional extreme segments for (int index = 0; index < int(extremes_repetition); ++index) { sqrt_weights[index] = sqrt(weights.front()); sqrt_weights[sqrt_weights.size() - index - 1] = sqrt(weights.back()); @@ -153,8 +153,8 @@ PiecewiseFittedCurve fit_curve( // prepare result and compute metadata PiecewiseFittedCurve result { }; - NumberType original_len = observation_points.back() - observation_points.front(); - NumberType orig_segment_size = original_len / NumberType(number_of_inner_splines * Kernel::kernel_span); + NumberType orig_len = observation_points.back() - observation_points.front(); + NumberType orig_segment_size = orig_len / NumberType(number_of_inner_splines * Kernel::kernel_span); result.kernel = kernel; result.start = observation_points.front() - extremes_repetition * orig_segment_size; result.length = observation_points.back() + extremes_repetition * orig_segment_size - result.start; @@ -187,7 +187,7 @@ PiecewiseFittedCurve fit_curve( * sqrt_weights[index + extremes_repetition]; } } - //duplicate observed data at the start and end + //duplicate observed data at the extremes for (int index = 0; index < int(extremes_repetition); index++) { for (size_t dim = 0; dim < Dimension; ++dim) { data_points[dim](index) = observations.front()(dim) * sqrt_weights[index]; @@ -196,7 +196,7 @@ PiecewiseFittedCurve fit_curve( } } - //Create weight matrix for each point and each segment. + //Create weight matrix T for each point and each segment; Eigen::MatrixXf T(normalized_obs_points.size(), result.segments_count); for (size_t i = 0; i < normalized_obs_points.size(); ++i) { for (size_t j = 0; j < result.segments_count; ++j) { @@ -204,11 +204,14 @@ PiecewiseFittedCurve fit_curve( } } + //Fill the weight matrix for (size_t i = 0; i < normalized_obs_points.size(); ++i) { NumberType knot_val = normalized_obs_points[i]; + //find index of first segment that is affected by the point i; this can be deduced from kernel_span int start_segment_idx = int(floor(knot_val / result.n_segment_size)) - int(Kernel::kernel_span * 0.5f - 1.0f); for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); segment_index++) { + // skip if we overshoot segment_index - happens at the extremes if (segment_index < 0 || segment_index >= int(result.segments_count)) { continue; } From 191e788aa08ba6f57ceb5960d346e5b5490b6eb2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 18 Mar 2022 10:10:51 +0100 Subject: [PATCH 42/71] make Random seams disaligned --- src/libslic3r/GCode/SeamPlacer.cpp | 14 ++++++++------ src/libslic3r/GCode/SeamPlacer.hpp | 11 ++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index d466941c0..8070087a6 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -19,7 +19,7 @@ #include "libslic3r/Geometry/Curves.hpp" -#define DEBUG_FILES +//#define DEBUG_FILES #ifdef DEBUG_FILES #include @@ -344,13 +344,15 @@ struct GlobalModelInfo { ; //Extract perimeter polygons of the given layer -Polygons extract_perimeter_polygons(const Layer *layer) { +Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition configured_seam_preference) { Polygons polygons; for (const LayerRegion *layer_region : layer->regions()) { for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { - if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { + if (perimeter->role() == ExtrusionRole::erExternalPerimeter + || (perimeter->role() == ExtrusionRole::erPerimeter + && configured_seam_preference == spRandom)) { Points p; perimeter->collect_points(p); polygons.emplace_back(p); @@ -903,7 +905,7 @@ void pick_random_seam_point(std::vector &perimeter_points, size_t // Gather SeamCandidates of each layer into vector and build KDtree over them // Store results in the SeamPlacer varaibles m_perimeter_points_per_object and m_perimeter_points_trees_per_object void SeamPlacer::gather_seam_candidates(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { + const SeamPlacerImpl::GlobalModelInfo &global_model_info, const SeamPosition configured_seam_preference) { using namespace SeamPlacerImpl; m_perimeter_points_per_object.emplace(po, po->layer_count()); @@ -916,7 +918,7 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, m_perimeter_points_per_object[po][layer_idx]; const Layer *layer = po->get_layer(layer_idx); auto unscaled_z = layer->slice_z; - Polygons polygons = extract_perimeter_polygons(layer); + Polygons polygons = extract_perimeter_polygons(layer, configured_seam_preference); for (const auto &poly : polygons) { process_perimeter_polygon(poly, unscaled_z, layer_candidates, global_model_info); @@ -1228,7 +1230,7 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather_seam_candidates: start"; - gather_seam_candidates(po, global_model_info); + gather_seam_candidates(po, global_model_info, configured_seam_preference); BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather_seam_candidates: end"; diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 939d6ec75..d6b64c115 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -56,7 +56,7 @@ struct SeamCandidate { float local_ccw_angle, EnforcedBlockedSeamPoint type) : position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), local_ccw_angle( - local_ccw_angle), type(type), central_enforcer(false){ + local_ccw_angle), type(type), central_enforcer(false) { } const Vec3f position; // pointer to Perimter loop of this point. It is shared across all points of the loop @@ -124,17 +124,18 @@ public: void init(const Print &print); - void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point& last_pos) const; + void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const; private: - void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); + void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info, + const SeamPosition configured_seam_preference); void calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_overhangs(const PrintObject *po); - void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); + void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); bool find_next_seam_in_layer(const PrintObject *po, std::pair &last_point_indexes, - size_t layer_idx,const SeamPlacerImpl::SeamComparator &comparator, + size_t layer_idx, const SeamPlacerImpl::SeamComparator &comparator, std::vector> &seam_string); }; From 1164449d4e44451bbb9bec51f723e1336e2d22d8 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 29 Mar 2022 14:29:58 +0200 Subject: [PATCH 43/71] compute overhang distance using SDF detect embedded (inner) perimeter points and prefer them for seam placement --- src/libslic3r/GCode/SeamPlacer.cpp | 163 +++++++++++++++++------------ src/libslic3r/GCode/SeamPlacer.hpp | 8 +- 2 files changed, 102 insertions(+), 69 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 8070087a6..c9c25f519 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -127,6 +127,7 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: raycast visibility for " << triangles.indices.size() << " triangles: start"; + //prepare uniform samples of a hemisphere float step_size = 1.0f / SeamPlacer::sqr_rays_per_triangle; std::vector precomputed_sample_directions( SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); @@ -344,7 +345,8 @@ struct GlobalModelInfo { ; //Extract perimeter polygons of the given layer -Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition configured_seam_preference) { +Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition configured_seam_preference, + std::vector &corresponding_regions_out) { Polygons polygons; for (const LayerRegion *layer_region : layer->regions()) { for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { @@ -352,21 +354,24 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { if (perimeter->role() == ExtrusionRole::erExternalPerimeter || (perimeter->role() == ExtrusionRole::erPerimeter - && configured_seam_preference == spRandom)) { + && configured_seam_preference == spRandom)) { //for random seam alignment, extract all perimeters Points p; perimeter->collect_points(p); polygons.emplace_back(p); + corresponding_regions_out.push_back(layer_region); } } if (polygons.empty()) { Points p; ex_entity->collect_points(p); polygons.emplace_back(p); + corresponding_regions_out.push_back(layer_region); } } else { Points p; ex_entity->collect_points(p); polygons.emplace_back(p); + corresponding_regions_out.push_back(layer_region); } } } @@ -374,6 +379,7 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point // it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway polygons.emplace_back(std::vector { Point { 0, 0 } }); + corresponding_regions_out.push_back(nullptr); } return polygons; @@ -383,8 +389,8 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi // Compute its type (Enfrocer,Blocker), angle, and position //each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon // if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions -void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector &result_vec, - const GlobalModelInfo &global_model_info) { +void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const LayerRegion *region, + const GlobalModelInfo &global_model_info, std::vector &result_vec) { if (orig_polygon.size() == 0) { return; } @@ -411,6 +417,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std:: std::queue oversampled_points { }; size_t orig_angle_index = 0; perimeter->start_index = result_vec.size(); + perimeter->flow_width = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).width() : 0.0f; bool some_point_enforced = false; while (!orig_polygon_points.empty() || !oversampled_points.empty()) { EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; @@ -501,36 +508,6 @@ std::pair find_previous_and_next_perimeter_point(const std::vect return {size_t(prev),size_t(next)}; } -//NOTE: only rough esitmation of overhang distance -// value represents distance from edge, positive is overhang, negative is inside shape -float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b, - const SeamCandidate &under_c) { - auto p = Vec2d { point.position.x(), point.position.y() }; - auto a = Vec2d { under_a.position.x(), under_a.position.y() }; - auto b = Vec2d { under_b.position.x(), under_b.position.y() }; - auto c = Vec2d { under_c.position.x(), under_c.position.y() }; - - auto oriented_line_dist = [](const Vec2d a, const Vec2d b, const Vec2d p) { //signed distance from line - return -((b.x() - a.x()) * (a.y() - p.y()) - (a.x() - p.x()) * (b.y() - a.y())) / (a - b).norm(); - }; - - auto dist_ab = oriented_line_dist(a, b, p); - auto dist_bc = oriented_line_dist(b, c, p); - - // from angle and signed distances from the arms of the points on the previous layer, we - // can deduce if it is overhang and give estimation of the size. - // However, the size of the overhang is rough estimation, the sign is more reliable - if (under_b.local_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside - return -((p - b).norm() + dist_ab + dist_bc) / 3.0; - } - - if (under_b.local_ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside - return -((p - b).norm() + dist_ab + dist_bc) / 3.0; - } - - return ((p - b).norm() + dist_ab + dist_bc) / 3.0; -} - // Computes all global model info - transforms object, performs raycasting, // stores enforces and blockers void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { @@ -664,7 +641,15 @@ struct SeamComparator { } //avoid overhangs - if (a.overhang > 0.1f && b.overhang < a.overhang) { + if (a.overhang > 0.0f && b.overhang < a.overhang) { + return false; + } + + // prefer hidden points (more than 1 mm inside) + if (a.embedded_distance < -1.0f && b.embedded_distance > -1.0f) { + return true; + } + if (b.embedded_distance < -1.0f && a.embedded_distance > -1.0f) { return false; } @@ -719,7 +704,15 @@ struct SeamComparator { } //avoid overhangs - if (a.overhang > 0.1f && b.overhang < a.overhang) { + if (a.overhang > 0.0f && b.overhang < a.overhang) { + return false; + } + + // prefer hidden points (more than 1 mm inside) + if (a.embedded_distance < -1.0f && b.embedded_distance > -1.0f) { + return true; + } + if (b.embedded_distance < -1.0f && a.embedded_distance > -1.0f) { return false; } @@ -783,6 +776,10 @@ void debug_export_points(const std::vector())), weight_fill); + + Vec3i overhang_color = value_rgbi(-0.5, 0.5, std::clamp(point.overhang, -0.5f, 0.5f)); + std::string overhang_fill = "rgb(" + std::to_string(overhang_color.x()) + "," + + std::to_string(overhang_color.y()) + + "," + + std::to_string(overhang_color.z()) + ")"; + overhangs_svg.draw(scaled(Vec2f(point.position.head<2>())), overhang_fill); } } } @@ -899,6 +903,20 @@ void pick_random_seam_point(std::vector &perimeter_points, size_t } +EdgeGrid::Grid compute_layer_merged_edge_grid(const Layer *layer) { + static const float eps = float(scale_(layer->object()->config().slice_closing_radius.value)); + // merge with offset + ExPolygons merged = layer->merged(eps); + // ofsset back + ExPolygons layer_outline = offset_ex(merged, -eps); + + const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); + EdgeGrid::Grid result { }; + result.create(layer_outline, distance_field_resolution); + result.calculate_sdf(); + return result; +} + } // namespace SeamPlacerImpl // Parallel process and extract each perimeter polygon of the given print object. @@ -918,15 +936,16 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, m_perimeter_points_per_object[po][layer_idx]; const Layer *layer = po->get_layer(layer_idx); auto unscaled_z = layer->slice_z; - Polygons polygons = extract_perimeter_polygons(layer, configured_seam_preference); - for (const auto &poly : polygons) { - process_perimeter_polygon(poly, unscaled_z, layer_candidates, - global_model_info); + std::vector regions; + //NOTE corresponding region ptr may be null, if the layer has zero perimeters + Polygons polygons = extract_perimeter_polygons(layer, configured_seam_preference, regions); + for (size_t poly_index = 0; poly_index < polygons.size(); ++poly_index) { + process_perimeter_polygon(polygons[poly_index], unscaled_z, + regions[poly_index], global_model_info, layer_candidates); } auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( - functor, layer_candidates.size()); + m_perimeter_points_trees_per_object[po][layer_idx] = + std::make_unique(functor, layer_candidates.size()); } } ); @@ -947,36 +966,45 @@ void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, }); } -void SeamPlacer::calculate_overhangs(const PrintObject *po) { +void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) { using namespace SeamPlacerImpl; tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { + std::unique_ptr prev_layer_grid; + if (r.begin() > 0) { // previous layer exists + prev_layer_grid = std::make_unique( + compute_layer_merged_edge_grid(po->layers()[r.begin() - 1])); + } + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + bool layer_has_multiple_loops = + m_perimeter_points_per_object[po][layer_idx][0].perimeter->end_index + < m_perimeter_points_per_object[po][layer_idx].size() - 1; + std::unique_ptr current_layer_grid = std::make_unique( + compute_layer_merged_edge_grid(po->layers()[layer_idx])); + for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { - const auto calculate_layer_overhang = [&](size_t other_layer_idx) { - size_t closest_supporter = find_closest_point( - *m_perimeter_points_trees_per_object[po][other_layer_idx], - perimeter_point.position); - const SeamCandidate &supporter_point = - m_perimeter_points_per_object[po][other_layer_idx][closest_supporter]; + Point point = Point::new_scale(Vec2f { perimeter_point.position.head<2>() }); + if (prev_layer_grid.get() != nullptr) { + coordf_t overhang_dist; + prev_layer_grid->signed_distance(point, scaled(perimeter_point.perimeter->flow_width), overhang_dist); + perimeter_point.overhang = + unscale(overhang_dist) - perimeter_point.perimeter->flow_width; + } - auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter); - const SeamCandidate &prev_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.first]; - const SeamCandidate &next_point = - m_perimeter_points_per_object[po][other_layer_idx][prev_next.second]; - - return calculate_overhang(perimeter_point, prev_point, - supporter_point, next_point); - }; - - if (layer_idx > 0) { //calculate overhang - perimeter_point.overhang = calculate_layer_overhang(layer_idx-1); + if (layer_has_multiple_loops) { // search for embedded perimeter points (points hidden inside the print ,e.g. multimaterial join, best position for seam) + coordf_t layer_embedded_distance; + current_layer_grid->signed_distance(point, scaled(1.0f), + layer_embedded_distance); + perimeter_point.embedded_distance = unscale(layer_embedded_distance); } } + + prev_layer_grid.swap(current_layer_grid); } - }); + } + ); } // Estimates, if there is good seam point in the layer_idx which is close to last_point_pos @@ -1243,10 +1271,10 @@ void SeamPlacer::init(const Print &print) { } BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs : start"; - calculate_overhangs(po); + << "SeamPlacer: calculate_overhangs and layer embdedding : start"; + calculate_overhangs_and_layer_embedding(po); BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs : end"; + << "SeamPlacer: calculate_overhangs and layer embdedding: end"; BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: pick_seam_point : start"; @@ -1285,7 +1313,8 @@ void SeamPlacer::init(const Print &print) { } } -void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const { +void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, + const Point &last_pos) const { using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); //NOTE this is necessary, since layer->id() is quite unreliable diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index d6b64c115..130547ff8 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -40,6 +40,7 @@ struct Perimeter { size_t start_index; size_t end_index; //inclusive! size_t seam_index; + float flow_width; // During alignment, a final position may be stored here. In that case, finalized is set to true. // Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position @@ -55,7 +56,7 @@ struct SeamCandidate { SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type) : - position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), local_ccw_angle( + position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle( local_ccw_angle), type(type), central_enforcer(false) { } const Vec3f position; @@ -63,6 +64,9 @@ struct SeamCandidate { const std::shared_ptr perimeter; float visibility; float overhang; + // distance inside the merged layer regions, for detecting perimter points which are hidden indside the print (e.g. multimaterial join) + // Negative sign means inside the print, comes from EdgeGrid structure + float embedded_distance; float local_ccw_angle; EnforcedBlockedSeamPoint type; bool central_enforcer; //marks this candidate as central point of enforced segment on the perimeter - important for alignment @@ -131,7 +135,7 @@ private: const SeamPosition configured_seam_preference); void calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); - void calculate_overhangs(const PrintObject *po); + void calculate_overhangs_and_layer_embedding(const PrintObject *po); void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); bool find_next_seam_in_layer(const PrintObject *po, std::pair &last_point_indexes, From 965803822edabbfc0f30cdf3d02392afdbd44cdd Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 29 Mar 2022 14:49:24 +0200 Subject: [PATCH 44/71] remove invalid comment --- src/libslic3r/Geometry/Curves.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index b92ad2c36..d984c6a3a 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -31,7 +31,6 @@ struct PolynomialCurve { }; //https://towardsdatascience.com/least-square-polynomial-CURVES-using-c-eigen-package-c0673728bd01 -// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y template PolynomialCurve fit_polynomial(const std::vector> &observations, const std::vector &observation_points, From 156a60017d7ebb37490351827b7e16c344d1fc0e Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 30 Mar 2022 11:22:35 +0200 Subject: [PATCH 45/71] fixed ExPolygons dealocation while using EdgeGrid fixed warnings in Bicubic.h file --- src/libslic3r/GCode/SeamPlacer.cpp | 55 +++++++++++++++++------------- src/libslic3r/Geometry/Bicubic.hpp | 54 ++++++++++++++--------------- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index c9c25f519..c7df13c58 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -750,7 +750,7 @@ void debug_export_points(const std::vector())), fill); min_vis = std::min(min_vis, point.visibility); max_vis = std::max(max_vis, point.visibility); @@ -772,31 +772,31 @@ void debug_export_points(const std::vector())), visibility_fill); Vec3i weight_color = value_rgbi(min_weight, max_weight, -comparator.get_penalty(point)); std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) - + "," - + std::to_string(weight_color.z()) + ")"; + + "," + + std::to_string(weight_color.z()) + ")"; weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); Vec3i overhang_color = value_rgbi(-0.5, 0.5, std::clamp(point.overhang, -0.5f, 0.5f)); std::string overhang_fill = "rgb(" + std::to_string(overhang_color.x()) + "," - + std::to_string(overhang_color.y()) - + "," - + std::to_string(overhang_color.z()) + ")"; + + std::to_string(overhang_color.y()) + + "," + + std::to_string(overhang_color.z()) + ")"; overhangs_svg.draw(scaled(Vec2f(point.position.head<2>())), overhang_fill); } } @@ -903,18 +903,26 @@ void pick_random_seam_point(std::vector &perimeter_points, size_t } -EdgeGrid::Grid compute_layer_merged_edge_grid(const Layer *layer) { +struct EdgeGridWrapper { + explicit EdgeGridWrapper(ExPolygons ex_polys) : + ex_polys(ex_polys) { + + grid.create(this->ex_polys, distance_field_resolution); + grid.calculate_sdf(); + } + const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); + EdgeGrid::Grid grid; + ExPolygons ex_polys; +} +; + +EdgeGridWrapper compute_layer_merged_edge_grid(const Layer *layer) { static const float eps = float(scale_(layer->object()->config().slice_closing_radius.value)); // merge with offset ExPolygons merged = layer->merged(eps); // ofsset back ExPolygons layer_outline = offset_ex(merged, -eps); - - const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); - EdgeGrid::Grid result { }; - result.create(layer_outline, distance_field_resolution); - result.calculate_sdf(); - return result; + return EdgeGridWrapper(layer_outline); } } // namespace SeamPlacerImpl @@ -971,9 +979,9 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), [&](tbb::blocked_range r) { - std::unique_ptr prev_layer_grid; + std::unique_ptr prev_layer_grid; if (r.begin() > 0) { // previous layer exists - prev_layer_grid = std::make_unique( + prev_layer_grid = std::make_unique( compute_layer_merged_edge_grid(po->layers()[r.begin() - 1])); } @@ -981,21 +989,22 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) bool layer_has_multiple_loops = m_perimeter_points_per_object[po][layer_idx][0].perimeter->end_index < m_perimeter_points_per_object[po][layer_idx].size() - 1; - std::unique_ptr current_layer_grid = std::make_unique( + std::unique_ptr current_layer_grid = std::make_unique( compute_layer_merged_edge_grid(po->layers()[layer_idx])); for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { Point point = Point::new_scale(Vec2f { perimeter_point.position.head<2>() }); if (prev_layer_grid.get() != nullptr) { coordf_t overhang_dist; - prev_layer_grid->signed_distance(point, scaled(perimeter_point.perimeter->flow_width), overhang_dist); + prev_layer_grid->grid.signed_distance(point, scaled(perimeter_point.perimeter->flow_width), + overhang_dist); perimeter_point.overhang = unscale(overhang_dist) - perimeter_point.perimeter->flow_width; } if (layer_has_multiple_loops) { // search for embedded perimeter points (points hidden inside the print ,e.g. multimaterial join, best position for seam) coordf_t layer_embedded_distance; - current_layer_grid->signed_distance(point, scaled(1.0f), + current_layer_grid->grid.signed_distance(point, scaled(1.0f), layer_embedded_distance); perimeter_point.embedded_distance = unscale(layer_embedded_distance); } diff --git a/src/libslic3r/Geometry/Bicubic.hpp b/src/libslic3r/Geometry/Bicubic.hpp index 9dc4c0083..98c6f8bb2 100644 --- a/src/libslic3r/Geometry/Bicubic.hpp +++ b/src/libslic3r/Geometry/Bicubic.hpp @@ -78,37 +78,37 @@ struct CubicCatmulRomKernel return 0; } static T a01() { - return (T) -0.5; + return T( -0.5); } static T a02() { - return (T) 1.; + return T( 1.); } static T a03() { - return (T) -0.5; + return T( -0.5); } static T a10() { - return (T) 1.; + return T( 1.); } static T a11() { return 0; } static T a12() { - return (T) -5. / 2.; + return T( -5. / 2.); } static T a13() { - return (T) 3. / 2.; + return T( 3. / 2.); } static T a20() { return 0; } static T a21() { - return (T) 0.5; + return T( 0.5); } static T a22() { - return (T) 2.; + return T( 2.); } static T a23() { - return (T) -3. / 2.; + return T( -3. / 2.); } static T a30() { return 0; @@ -117,10 +117,10 @@ struct CubicCatmulRomKernel return 0; } static T a32() { - return (T) -0.5; + return T( -0.5); } static T a33() { - return (T) 0.5; + return T( 0.5); } }; @@ -131,40 +131,40 @@ struct CubicBSplineKernel typedef T FloatType; static T a00() { - return (T) 1. / 6.; + return T( 1. / 6.); } static T a01() { - return (T) -3. / 6.; + return T( -3. / 6.); } static T a02() { - return (T) 3. / 6.; + return T( 3. / 6.); } static T a03() { - return (T) -1. / 6.; + return T( -1. / 6.); } static T a10() { - return (T) 4. / 6.; + return T( 4. / 6.); } static T a11() { return 0; } static T a12() { - return (T) -6. / 6.; + return T( -6. / 6.); } static T a13() { - return (T) 3. / 6.; + return T( 3. / 6.); } static T a20() { - return (T) 1. / 6.; + return T( 1. / 6.); } static T a21() { - return (T) 3. / 6.; + return T( 3. / 6.); } static T a22() { - return (T) 3. / 6.; + return T( 3. / 6.); } static T a23() { - return (T) -3. / 6.; + return T( -3. / 6.); } static T a30() { return 0; @@ -176,7 +176,7 @@ struct CubicBSplineKernel return 0; } static T a33() { - return (T) 1. / 6.; + return T( 1. / 6.); } }; @@ -241,7 +241,7 @@ static typename KernelWrapper::FloatType cubic_interpolate(const Eigen::ArrayBas typedef typename KernelWrapper::FloatType T; const int w = int(F.size()); const int ix = (int) floor(pt); - const T s = pt - (T) ix; + const T s = pt - T( ix); if (ix > 1 && ix + 2 < w) { // Inside the fully interpolated region. @@ -262,8 +262,8 @@ static float bicubic_interpolate(const Eigen::MatrixBase &F, const int h = F.rows(); const int ix = (int) floor(pt[0]); const int iy = (int) floor(pt[1]); - const T s = pt[0] - (T) ix; - const T t = pt[1] - (T) iy; + const T s = pt[0] - T( ix); + const T t = pt[1] - T( iy); if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) { // Inside the fully interpolated region. @@ -274,7 +274,7 @@ static float bicubic_interpolate(const Eigen::MatrixBase &F, Kernel::interpolate(F(ix - 1, iy + 2), F(ix, iy + 2), F(ix + 1, iy + 2), F(ix + 2, iy + 2), s), t); } // Transition region. Extend with a constant function. - auto f = [&F, &f, w, h](int x, int y) { + auto f = [&F, w, h](int x, int y) { return F(BicubicInternal::clamp(x, 0, w - 1), BicubicInternal::clamp(y, 0, h - 1)); }; return Kernel::interpolate( From 7d02647ebfecddc8fb68fc0b7e731012d68a4d5c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 31 Mar 2022 12:20:46 +0200 Subject: [PATCH 46/71] Removed various Point::ccw() and Point::ccw_angle() methods, they were provided for Perl bindings and their semantic was confusing. Implemented free function angle() to measure angle between two vectors. Reworked Polygon::convex/concave_points(), changed the meaning of their angle threshold parameter. Removed some unused methods from Perl bindings and tests. Reworked the "wipe inside at the external perimeter" function after Point::ccw_angle() was removed. --- src/libslic3r/GCode.cpp | 22 +++---- src/libslic3r/Geometry.hpp | 6 +- src/libslic3r/Geometry/ConvexHull.cpp | 9 +-- src/libslic3r/Line.hpp | 1 - src/libslic3r/Point.cpp | 32 ---------- src/libslic3r/Point.hpp | 36 ++++++----- src/libslic3r/Polygon.cpp | 86 ++++++++++++++------------- src/libslic3r/Polygon.hpp | 7 ++- t/geometry.t | 59 +----------------- tests/libslic3r/test_geometry.cpp | 64 +++++++++++++++----- xs/xsp/Line.xsp | 2 +- xs/xsp/Point.xsp | 8 +-- xs/xsp/Polygon.xsp | 2 - 13 files changed, 146 insertions(+), 188 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aed2ff53f..a5aa9013e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2596,18 +2596,19 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // the side depends on the original winding order of the polygon (left for contours, right for holes) //FIXME improve the algorithm in case the loop is tiny. //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). - Point a = paths.front().polyline.points[1]; // second point - Point b = *(paths.back().polyline.points.end()-3); // second to last point + // Angle from the 2nd point to the last point. + double angle_inside = angle(paths.front().polyline.points[1] - paths.front().first_point(), + *(paths.back().polyline.points.end()-3) - paths.front().first_point()); + assert(angle_inside >= -M_PI && angle_inside <= M_PI); + // 3rd of this angle will be taken, thus make the angle monotonic before interpolation. if (was_clockwise) { - // swap points - Point c = a; a = b; b = c; + if (angle_inside > 0) + angle_inside -= 2.0 * M_PI; + } else { + if (angle_inside < 0) + angle_inside += 2.0 * M_PI; } - double angle = paths.front().first_point().ccw_angle(a, b) / 3; - - // turn left if contour, turn right if hole - if (was_clockwise) angle *= -1; - // create the destination point along the first segment and rotate it // we make sure we don't exceed the segment length because we don't know // the rotation of the second segment so we might cross the object boundary @@ -2619,7 +2620,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Shift by no more than a nozzle diameter. //FIXME Hiding the seams will not work nicely for very densely discretized contours! Point pt = ((nd * nd >= l2) ? p2 : (p1 + v * (nd / sqrt(l2)))).cast(); - pt.rotate(angle, paths.front().polyline.points.front()); + // Rotate pt inside around the seam point. + pt.rotate(angle_inside / 3., paths.front().polyline.points.front()); // generate the travel move gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); } diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index c825f8254..82ffbd8d1 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -36,9 +36,9 @@ enum Orientation static inline Orientation orient(const Point &a, const Point &b, const Point &c) { static_assert(sizeof(coord_t) * 2 == sizeof(int64_t), "orient works with 32 bit coordinates"); - int64_t u = int64_t(b(0)) * int64_t(c(1)) - int64_t(b(1)) * int64_t(c(0)); - int64_t v = int64_t(a(0)) * int64_t(c(1)) - int64_t(a(1)) * int64_t(c(0)); - int64_t w = int64_t(a(0)) * int64_t(b(1)) - int64_t(a(1)) * int64_t(b(0)); + int64_t u = int64_t(b.x()) * int64_t(c.y()) - int64_t(b.y()) * int64_t(c.x()); + int64_t v = int64_t(a.x()) * int64_t(c.y()) - int64_t(a.y()) * int64_t(c.x()); + int64_t w = int64_t(a.x()) * int64_t(b.y()) - int64_t(a.y()) * int64_t(b.x()); int64_t d = u - v + w; return (d > 0) ? ORIENTATION_CCW : ((d == 0) ? ORIENTATION_COLINEAR : ORIENTATION_CW); } diff --git a/src/libslic3r/Geometry/ConvexHull.cpp b/src/libslic3r/Geometry/ConvexHull.cpp index b1ff77f80..2e92535f2 100644 --- a/src/libslic3r/Geometry/ConvexHull.cpp +++ b/src/libslic3r/Geometry/ConvexHull.cpp @@ -1,6 +1,7 @@ #include "libslic3r.h" #include "ConvexHull.hpp" #include "BoundingBox.hpp" +#include "../Geometry.hpp" #include @@ -19,13 +20,13 @@ Polygon convex_hull(Points pts) hull.points.resize(2 * n); // Build lower hull for (int i = 0; i < n; ++ i) { - while (k >= 2 && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) + while (k >= 2 && Geometry::orient(pts[i], hull[k-2], hull[k-1]) != Geometry::ORIENTATION_CCW) -- k; hull[k ++] = pts[i]; } // Build upper hull for (int i = n-2, t = k+1; i >= 0; i--) { - while (k >= t && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) + while (k >= t && Geometry::orient(pts[i], hull[k-2], hull[k-1]) != Geometry::ORIENTATION_CCW) -- k; hull[k ++] = pts[i]; } @@ -58,7 +59,7 @@ Pointf3s convex_hull(Pointf3s points) Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); - if (p.ccw(k2, k1) <= 0) + if (Geometry::orient(p, k2, k1) != Geometry::ORIENTATION_CCW) --k; else break; @@ -76,7 +77,7 @@ Pointf3s convex_hull(Pointf3s points) Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); - if (p.ccw(k2, k1) <= 0) + if (Geometry::orient(p, k2, k1) != Geometry::ORIENTATION_CCW) --k; else break; diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 751e59458..8631ee08b 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -113,7 +113,6 @@ public: Vector vector() const { return this->b - this->a; } Vector normal() const { return Vector((this->b(1) - this->a(1)), -(this->b(0) - this->a(0))); } bool intersection(const Line& line, Point* intersection) const; - double ccw(const Point& point) const { return point.ccw(*this); } // Clip a line with a bounding box. Returns false if the line is completely outside of the bounding box. bool clip_with_bbox(const BoundingBox &bbox); // Extend the line from both sides by an offset. diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 66f125bb6..9f56f96d6 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -108,38 +108,6 @@ bool Point::nearest_point(const Points &points, Point* point) const return true; } -/* Three points are a counter-clockwise turn if ccw > 0, clockwise if - * ccw < 0, and collinear if ccw = 0 because ccw is a determinant that - * gives the signed area of the triangle formed by p1, p2 and this point. - * In other words it is the 2D cross product of p1-p2 and p1-this, i.e. - * z-component of their 3D cross product. - * We return double because it must be big enough to hold 2*max(|coordinate|)^2 - */ -double Point::ccw(const Point &p1, const Point &p2) const -{ - static_assert(sizeof(coord_t) == 4, "Point::ccw() requires a 32 bit coord_t"); - return cross2((p2 - p1).cast(), (*this - p1).cast()); -// return cross2((p2 - p1).cast(), (*this - p1).cast()); -} - -double Point::ccw(const Line &line) const -{ - return this->ccw(line.a, line.b); -} - -// returns the CCW angle between this-p1 and this-p2 -// i.e. this assumes a CCW rotation from p1 to p2 around this -double Point::ccw_angle(const Point &p1, const Point &p2) const -{ - const Point v1 = *this - p1; - const Point v2 = p2 - *this; - int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); - int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); - float angle = float(atan2(float(cross), float(dot))); - // we only want to return only positive angles - return angle <= 0 ? angle + 2*PI : angle; -} - Point Point::projection_onto(const MultiPoint &poly) const { Point running_projection = poly.first_point(); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 09ff533fe..8279a377e 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -79,24 +79,35 @@ inline const auto &identity3d = identity<3, double>; inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); } -template -int32_t cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) = delete; - -template -inline T cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) -{ - return v1.x() * v2.y() - v1.y() * v2.x(); -} - +// Cross product of two 2D vectors. +// None of the vectors may be of int32_t type as the result would overflow. template inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); + static_assert(! std::is_same::value, "cross2(): Scalar type must not be int32_t, otherwise the cross product would overflow."); static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); return v1.x() * v2.y() - v1.y() * v2.x(); } -template -inline Eigen::Matrix perp(const Eigen::MatrixBase> &v) { return Eigen::Matrix(- v.y(), v.x()); } +// 2D vector perpendicular to the argument. +template +inline Eigen::Matrix perp(const Eigen::MatrixBase &v) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector"); + return { - v.y(), v.x() }; +} + +// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>. +template +inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); + auto v1d = v1.cast(); + auto v2d = v2.cast(); + return atan2(cross2(v1d, v2d), v1d.dot(v2d)); +} template Eigen::Matrix to_2d(const Eigen::MatrixBase> &ptN) { return { ptN.x(), ptN.y() }; } @@ -168,9 +179,6 @@ public: int nearest_point_index(const PointConstPtrs &points) const; int nearest_point_index(const PointPtrs &points) const; bool nearest_point(const Points &points, Point* point) const; - double ccw(const Point &p1, const Point &p2) const; - double ccw(const Line &line) const; - double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; }; diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index c7bbe9706..09f1c393d 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -171,50 +171,56 @@ Point Polygon::centroid() const return Point(Vec2d(c / (3. * area_sum))); } -// find all concave vertices (i.e. having an internal angle greater than the supplied angle) -// (external = right side, thus we consider ccw orientation) -Points Polygon::concave_points(double angle) const +// Filter points from poly to the output with the help of FilterFn. +// filter function receives two vectors: +// v1: this_point - previous_point +// v2: next_point - this_point +// and returns true if the point is to be copied to the output. +template +Points filter_points_by_vectors(const Points &poly, FilterFn filter) { - Points points; - angle = 2. * PI - angle + EPSILON; - - // check whether first point forms a concave angle - if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle) - points.push_back(this->points.front()); - - // check whether points 1..(n-1) form concave angles - for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++ p) - if (p->ccw_angle(*(p-1), *(p+1)) <= angle) - points.push_back(*p); - - // check whether last point forms a concave angle - if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle) - points.push_back(this->points.back()); - - return points; -} + // Last point is the first point visited. + Point p1 = poly.back(); + // Previous vector to p1. + Vec2d v1 = (p1 - *(poly.end() - 2)).cast(); -// find all convex vertices (i.e. having an internal angle smaller than the supplied angle) -// (external = right side, thus we consider ccw orientation) -Points Polygon::convex_points(double angle) const -{ - Points points; - angle = 2*PI - angle - EPSILON; - - // check whether first point forms a convex angle - if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) - points.push_back(this->points.front()); - - // check whether points 1..(n-1) form convex angles - for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { - if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points.push_back(*p); + Points out; + for (Point p2 : poly) { + // p2 is next point to the currently visited point p1. + Vec2d v2 = (p2 - p1).cast(); + if (filter(v1, v2)) + out.emplace_back(p2); + v1 = v2; + p1 = p2; } - // check whether last point forms a convex angle - if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle) - points.push_back(this->points.back()); - - return points; + return out; +} + +template +Points filter_convex_concave_points_by_angle_threshold(const Points &poly, double angle_threshold, ConvexConcaveFilterFn convex_concave_filter) +{ + assert(angle_threshold >= 0.); + if (angle_threshold < EPSILON) { + double cos_angle = cos(angle_threshold); + return filter_points_by_vectors(poly, [convex_concave_filter, cos_angle](const Vec2d &v1, const Vec2d &v2){ + return convex_concave_filter(v1, v2) && v1.normalized().dot(v2.normalized()) < cos_angle; + }); + } else { + return filter_points_by_vectors(poly, [convex_concave_filter](const Vec2d &v1, const Vec2d &v2){ + return convex_concave_filter(v1, v2); + }); + } +} + +Points Polygon::convex_points(double angle_threshold) const +{ + return filter_convex_concave_points_by_angle_threshold(this->points, angle_threshold, [](const Vec2d &v1, const Vec2d &v2){ return cross2(v1, v2) > 0.; }); +} + +Points Polygon::concave_points(double angle_threshold) const +{ + return filter_convex_concave_points_by_angle_threshold(this->points, angle_threshold, [](const Vec2d &v1, const Vec2d &v2){ return cross2(v1, v2) < 0.; }); } // Projection of a point onto the polygon. diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 089820565..77f3020be 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -65,8 +65,11 @@ public: void densify(float min_length, std::vector* lengths = nullptr); void triangulate_convex(Polygons* polygons) const; Point centroid() const; - Points concave_points(double angle = PI) const; - Points convex_points(double angle = PI) const; + // Considering CCW orientation of this polygon, find all convex resp. concave points + // with the angle at the vertex larger than a threshold. + // Zero angle_threshold means to accept all convex resp. concave points. + Points convex_points(double angle_threshold = 0.) const; + Points concave_points(double angle_threshold = 0.) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; std::vector parameter_by_length() const; diff --git a/t/geometry.t b/t/geometry.t index 0f37c0aa3..bb72b22e1 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 42; +plan tests => 30; BEGIN { use FindBin; @@ -189,63 +189,6 @@ my $polygons = [ is +Slic3r::Point->new(10, 0)->distance_to_line($line), 0, 'distance_to'; } -#========================================================== - -{ - my $square = Slic3r::Polygon->new_scale( - [100,100], - [200,100], - [200,200], - [100,200], - ); - is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in ccw square'; - is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in ccw square'; - - $square->make_clockwise; - is scalar(@{$square->concave_points(PI*4/3)}), 4, 'fuor concave vertices detected in cw square'; - is scalar(@{$square->convex_points(PI*2/3)}), 0, 'no convex vertices detected in cw square'; -} - -{ - my $square = Slic3r::Polygon->new_scale( - [150,100], - [200,100], - [200,200], - [100,200], - [100,100], - ); - is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon'; - is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square'; -} - -{ - my $square = Slic3r::Polygon->new_scale( - [200,200], - [100,200], - [100,100], - [150,100], - [200,100], - ); - is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon'; - is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square'; -} - -{ - my $triangle = Slic3r::Polygon->new( - [16000170,26257364], [714223,461012], [31286371,461008], - ); - is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle'; - is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle'; -} - -{ - my $triangle = Slic3r::Polygon->new( - [16000170,26257364], [714223,461012], [20000000,461012], [31286371,461012], - ); - is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle having collinear point'; - is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle having collinear point'; -} - { my $triangle = Slic3r::Polygon->new( [16000170,26257364], [714223,461012], [31286371,461008], diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 21f9a9663..34e625e6d 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -373,7 +373,43 @@ SCENARIO("Line distances", "[Geometry]"){ } } +SCENARIO("Calculating angles", "[Geometry]") +{ + GIVEN(("Vectors 30 degrees apart")) + { + std::vector> pts { + { {1000, 0}, { 866, 500 } }, + { { 866, 500 }, { 500, 866 } }, + { { 500, 866 }, { 0, 1000 } }, + { { -500, 866 }, { -866, 500 } } + }; + + THEN("Angle detected is 30 degrees") + { + for (auto &p : pts) + REQUIRE(is_approx(angle(p.first, p.second), M_PI / 6.)); + } + } + + GIVEN(("Vectors 30 degrees apart")) + { + std::vector> pts { + { { 866, 500 }, {1000, 0} }, + { { 500, 866 }, { 866, 500 } }, + { { 0, 1000 }, { 500, 866 } }, + { { -866, 500 }, { -500, 866 } } + }; + + THEN("Angle detected is -30 degrees") + { + for (auto &p : pts) + REQUIRE(is_approx(angle(p.first, p.second), - M_PI / 6.)); + } + } +} + SCENARIO("Polygon convex/concave detection", "[Geometry]"){ + static constexpr const double angle_threshold = M_PI / 3.; GIVEN(("A Square with dimension 100")){ auto square = Slic3r::Polygon /*new_scale*/(std::vector({ Point(100,100), @@ -381,13 +417,13 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){ Point(200,200), Point(100,200)})); THEN("It has 4 convex points counterclockwise"){ - REQUIRE(square.concave_points(PI*4/3).size() == 0); - REQUIRE(square.convex_points(PI*2/3).size() == 4); + REQUIRE(square.concave_points(angle_threshold).size() == 0); + REQUIRE(square.convex_points(angle_threshold).size() == 4); } THEN("It has 4 concave points clockwise"){ square.make_clockwise(); - REQUIRE(square.concave_points(PI*4/3).size() == 4); - REQUIRE(square.convex_points(PI*2/3).size() == 0); + REQUIRE(square.concave_points(angle_threshold).size() == 4); + REQUIRE(square.convex_points(angle_threshold).size() == 0); } } GIVEN("A Square with an extra colinearvertex"){ @@ -398,8 +434,8 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){ Point(100,200), Point(100,100)})); THEN("It has 4 convex points counterclockwise"){ - REQUIRE(square.concave_points(PI*4/3).size() == 0); - REQUIRE(square.convex_points(PI*2/3).size() == 4); + REQUIRE(square.concave_points(angle_threshold).size() == 0); + REQUIRE(square.convex_points(angle_threshold).size() == 4); } } GIVEN("A Square with an extra collinear vertex in different order"){ @@ -410,8 +446,8 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){ Point(150,100), Point(200,100)})); THEN("It has 4 convex points counterclockwise"){ - REQUIRE(square.concave_points(PI*4/3).size() == 0); - REQUIRE(square.convex_points(PI*2/3).size() == 4); + REQUIRE(square.concave_points(angle_threshold).size() == 0); + REQUIRE(square.convex_points(angle_threshold).size() == 4); } } @@ -422,8 +458,8 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){ Point(31286371,461008) })); THEN("it has three convex vertices"){ - REQUIRE(triangle.concave_points(PI*4/3).size() == 0); - REQUIRE(triangle.convex_points(PI*2/3).size() == 3); + REQUIRE(triangle.concave_points(angle_threshold).size() == 0); + REQUIRE(triangle.convex_points(angle_threshold).size() == 3); } } @@ -435,8 +471,8 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){ Point(31286371,461012) })); THEN("it has three convex vertices"){ - REQUIRE(triangle.concave_points(PI*4/3).size() == 0); - REQUIRE(triangle.convex_points(PI*2/3).size() == 3); + REQUIRE(triangle.concave_points(angle_threshold).size() == 0); + REQUIRE(triangle.convex_points(angle_threshold).size() == 3); } } GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){ @@ -453,8 +489,8 @@ SCENARIO("Polygon convex/concave detection", "[Geometry]"){ Point(38092663,692699),Point(52100125,692699) })); THEN("the correct number of points are detected"){ - REQUIRE(polygon.concave_points(PI*4/3).size() == 6); - REQUIRE(polygon.convex_points(PI*2/3).size() == 10); + REQUIRE(polygon.concave_points(angle_threshold).size() == 6); + REQUIRE(polygon.convex_points(angle_threshold).size() == 10); } } } diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp index 777dc41fa..36181c3ba 100644 --- a/xs/xsp/Line.xsp +++ b/xs/xsp/Line.xsp @@ -41,7 +41,7 @@ Clone normal(); Clone vector(); double ccw(Point* point) - %code{% RETVAL = THIS->ccw(*point); %}; + %code{% RETVAL = cross2((THIS->a - *point).cast(), (THIS->b - THIS->a).cast()); %}; %{ Line* diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index beefc6249..2d70e0203 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -39,13 +39,7 @@ double perp_distance_to_line(Line* line) %code{% RETVAL = line->perp_distance_to(*THIS); %}; double ccw(Point* p1, Point* p2) - %code{% RETVAL = THIS->ccw(*p1, *p2); %}; - double ccw_angle(Point* p1, Point* p2) - %code{% RETVAL = THIS->ccw_angle(*p1, *p2); %}; - Point* projection_onto_polygon(Polygon* polygon) - %code{% RETVAL = new Point(THIS->projection_onto(*polygon)); %}; - Point* projection_onto_polyline(Polyline* polyline) - %code{% RETVAL = new Point(THIS->projection_onto(*polyline)); %}; + %code{% RETVAL = cross2((*p1 - *THIS).cast(), (*p2 - *p1).cast()); %}; Point* projection_onto_line(Line* line) %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; Point* negative() diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index a94425477..6b6d52524 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -39,8 +39,6 @@ %code{% THIS->triangulate_convex(&RETVAL); %}; Clone centroid(); Clone bounding_box(); - Points concave_points(double angle); - Points convex_points(double angle); Clone point_projection(Point* point) %code{% RETVAL = THIS->point_projection(*point); %}; Clone intersection(Line* line) From e95d19b5605df8aa7b1219d5c675ddd59d7185be Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 31 Mar 2022 12:26:50 +0200 Subject: [PATCH 47/71] WIP, FIXME: Worked around a Perl integration test failure in t/perimeters.t, see the comment FIXME skip the 1st layer in the test 'loops start on concave point if any' The failure is due to the seam smoothing, which brings the seam too far away from the external perimeter at the 1st layer, which uses significantly wider extrusion rate than the other layers. Moving the seam point from the external perimeter causes an error when projecting the seam point back to the external perimeter, where the projection lands not exactly at a corner point, creating a short segment at the end of a polygon loop. --- t/perimeters.t | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/t/perimeters.t b/t/perimeters.t index d3f96f122..463459611 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -221,9 +221,11 @@ use Slic3r::Test; if (!$loop_contains_point && $is_contour) # contour should include destination || ($loop_contains_point && $is_hole); # hole should not - if ($model eq 'cube_with_concave_hole') { + if ($model eq 'cube_with_concave_hole' + #FIXME skip the 1st layer in the test 'loops start on concave point if any' + && $self->Z > 0.36) { # check that loop starts at a concave vertex - my $ccw_angle = $loop->first_point->ccw_angle(@$loop[-2,1]); + my $ccw_angle = $loop->[-2]->ccw($loop->first_point, $loop->[1]); my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex $starts_on_convex_point = 1 if ($convex && $is_contour) || (!$convex && $is_hole); From bd8ce6fabd8809223e8b2e4e8985f8bc6857d8e3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 31 Mar 2022 13:51:09 +0200 Subject: [PATCH 48/71] Follow-up to 9cb51caead9dee5c4d12a4ef5f925b5efeb00b75 Fixing compilation on GCC --- src/libslic3r/Point.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 8279a377e..26ff59226 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -104,8 +104,8 @@ template inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); - auto v1d = v1.cast(); - auto v2d = v2.cast(); + auto v1d = v1.typename cast(); + auto v2d = v2.typename cast(); return atan2(cross2(v1d, v2d), v1d.dot(v2d)); } From 42e802c1b8db55cf132f1d00ecfa0128977dc4c5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 31 Mar 2022 16:26:04 +0200 Subject: [PATCH 49/71] Refactoring of Curves.hpp for better memory management and vectorization (replaced vector of vectors with Eigen 2D matrices). --- src/libslic3r/Geometry/Curves.hpp | 107 +++++++++---------------- tests/libslic3r/test_curve_fitting.cpp | 6 +- 2 files changed, 41 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index d984c6a3a..251f12125 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -11,21 +11,14 @@ namespace Geometry { template struct PolynomialCurve { - std::vector> coefficients; - - explicit PolynomialCurve(std::vector> coefficients) : - coefficients(coefficients) { - } + Eigen::MatrixXf coefficients; Vec3f get_fitted_value(const NumberType value) const { - Vec result = Vec::Zero(); - size_t order = this->coefficients.size() - 1; - for (size_t index = 0; index < order + 1; ++index) { - float powered = pow(value, index); - for (size_t dim = 0; dim < Dimension; ++dim) { - result(dim) += powered * this->coefficients[dim](index); - } - } + auto result = Vec::Zero(); + size_t order = this->coefficients.rows() - 1; + auto x = NumberType(1.); + for (size_t index = 0; index < order + 1; ++index, x *= value) + result += x * this->coefficients.col(index); return result; } }; @@ -36,48 +29,38 @@ PolynomialCurve fit_polynomial(const std::vector &observation_points, const std::vector &weights, size_t order) { // check to make sure inputs are correct - assert(observation_points.size() >= order + 1); + size_t cols = order + 1; + assert(observation_points.size() >= cols); assert(observation_points.size() == weights.size()); assert(observations.size() == weights.size()); - std::vector squared_weights(weights.size()); - for (size_t index = 0; index < weights.size(); ++index) { - squared_weights[index] = sqrt(weights[index]); - } - - std::vector> data_points(Dimension); - for (size_t dim = 0; dim < Dimension; ++dim) { - data_points[dim] = Eigen::Matrix( - observations.size()); - } - for (size_t index = 0; index < observations.size(); index++) { - for (size_t dim = 0; dim < Dimension; ++dim) { - data_points[dim](index) = observations[index](dim) * squared_weights[index]; - } - } - - Eigen::MatrixXf T(observation_points.size(), order + 1); - // Populate the matrix - for (size_t i = 0; i < observation_points.size(); ++i) { - for (size_t j = 0; j < order + 1; ++j) { - T(i, j) = pow(observation_points[i], j) * squared_weights[i]; - } + Eigen::MatrixXf data_points(Dimension, observations.size()); + Eigen::MatrixXf T(observations.size(), cols); + for (size_t i = 0; i < weights.size(); ++i) { + auto squared_weight = sqrt(weights[i]); + data_points.col(i) = observations[i] * squared_weight; + // Populate the matrix + auto x = squared_weight; + auto c = observation_points[i]; + for (size_t j = 0; j < cols; ++j, x *= c) + T(i, j) = x; } const auto QR = T.householderQr(); - std::vector> coefficients(Dimension); + Eigen::MatrixXf coefficients(Dimension, cols); // Solve for linear least square fit for (size_t dim = 0; dim < Dimension; ++dim) { - coefficients[dim] = QR.solve(data_points[dim]); + coefficients.row(dim) = QR.solve(data_points.row(dim).transpose()); } - return PolynomialCurve(coefficients); + return { std::move(coefficients) }; } -template +template struct PiecewiseFittedCurve { - std::vector> coefficients; - Kernel kernel; + using Kernel = KernelType; + + Eigen::MatrixXf coefficients; NumberType start; NumberType length; NumberType n_segment_size; @@ -104,14 +87,11 @@ struct PiecewiseFittedCurve { NumberType segment_start = this->get_n_segment_start(segment_index); NumberType normalized_segment_distance = (segment_start - t) / this->n_segment_size; - for (size_t dim = 0; dim < Dimension; ++dim) { - result(dim) += kernel.kernel(normalized_segment_distance) * coefficients[dim](segment_index); - } + result += Kernel::kernel(normalized_segment_distance) * coefficients.col(segment_index); } return result; } -} -; +}; // observations: data to be fitted by the curve // observation points: growing sequence of points where the observations were made. @@ -138,7 +118,7 @@ PiecewiseFittedCurve fit_curve( size_t extremes_repetition = Kernel::kernel_span - 1; //how many (additional) times is the first and last point repeated //prepare sqrt of weights, which will then be applied to both matrix T and observed data: https://en.wikipedia.org/wiki/Weighted_least_squares - std::vector sqrt_weights(weights.size() + extremes_repetition * 2); + std::vector sqrt_weights(weights.size() + extremes_repetition * 2); for (size_t index = 0; index < weights.size(); ++index) { assert(weights[index] > 0); sqrt_weights[index + extremes_repetition] = sqrt(weights[index]); @@ -154,7 +134,6 @@ PiecewiseFittedCurve fit_curve( NumberType orig_len = observation_points.back() - observation_points.front(); NumberType orig_segment_size = orig_len / NumberType(number_of_inner_splines * Kernel::kernel_span); - result.kernel = kernel; result.start = observation_points.front() - extremes_repetition * orig_segment_size; result.length = observation_points.back() + extremes_repetition * orig_segment_size - result.start; result.segments_count = number_of_inner_splines * Kernel::kernel_span + extremes_repetition * 2; @@ -175,33 +154,26 @@ PiecewiseFittedCurve fit_curve( } // prepare observed data - std::vector> data_points(Dimension); - for (size_t dim = 0; dim < Dimension; ++dim) { - data_points[dim] = Eigen::Matrix( - observations.size() + extremes_repetition * 2); - } - for (size_t index = 0; index < observations.size(); index++) { + // Eigen defaults to column major memory layout. + Eigen::MatrixXf data_points(Dimension, observations.size() + extremes_repetition * 2); + for (size_t index = 0; index < observations.size(); ++ index) { for (size_t dim = 0; dim < Dimension; ++dim) { - data_points[dim](index + extremes_repetition) = observations[index](dim) + data_points(dim, index + extremes_repetition) = observations[index](dim) * sqrt_weights[index + extremes_repetition]; } } //duplicate observed data at the extremes for (int index = 0; index < int(extremes_repetition); index++) { for (size_t dim = 0; dim < Dimension; ++dim) { - data_points[dim](index) = observations.front()(dim) * sqrt_weights[index]; - data_points[dim](data_points[dim].size() - index - 1) = observations.back()(dim) - * sqrt_weights[data_points[dim].size() - index - 1]; + data_points(dim, index) = observations.front()(dim) * sqrt_weights[index]; + data_points(dim, data_points.cols() - index - 1) = observations.back()(dim) + * sqrt_weights[data_points.cols() - index - 1]; } } //Create weight matrix T for each point and each segment; Eigen::MatrixXf T(normalized_obs_points.size(), result.segments_count); - for (size_t i = 0; i < normalized_obs_points.size(); ++i) { - for (size_t j = 0; j < result.segments_count; ++j) { - T(i, j) = NumberType(0); - } - } + T.setZero(); //Fill the weight matrix for (size_t i = 0; i < normalized_obs_points.size(); ++i) { @@ -223,15 +195,12 @@ PiecewiseFittedCurve fit_curve( } // Solve for linear least square fit - std::vector> coefficients(Dimension); + result.coefficients.resize(Dimension, result.segments_count); const auto QR = T.fullPivHouseholderQr(); for (size_t dim = 0; dim < Dimension; ++dim) { - coefficients[dim] = QR.solve(data_points[dim]); + result.coefficients.row(dim) = QR.solve(data_points.row(dim).transpose()); } - // store coefficients in result - result.coefficients = coefficients; - return result; } diff --git a/tests/libslic3r/test_curve_fitting.cpp b/tests/libslic3r/test_curve_fitting.cpp index a38e9b5a0..faf7839c7 100644 --- a/tests/libslic3r/test_curve_fitting.cpp +++ b/tests/libslic3r/test_curve_fitting.cpp @@ -111,8 +111,8 @@ TEST_CASE("Curves: polynomial fit test", "[Curves]") { auto poly = fit_polynomial(observations, observation_points, weights, 2); - REQUIRE(poly.coefficients[0](0) == ap(1)); - REQUIRE(poly.coefficients[0](1) == ap(-2)); - REQUIRE(poly.coefficients[0](2) == ap(1)); + REQUIRE(poly.coefficients(0, 0) == ap(1)); + REQUIRE(poly.coefficients(0, 1) == ap(-2)); + REQUIRE(poly.coefficients(0, 2) == ap(1)); } From c19770189fb6bd569dc22f977275bbb42384a15e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 1 Apr 2022 11:55:29 +0200 Subject: [PATCH 50/71] Follow-up to 1c9ba291fe32bc4a4c78cabbab0639b0c164f23f Refactoring of Curves.hpp for better memory management and vectorization --- src/libslic3r/Geometry/Curves.hpp | 52 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index 251f12125..5b89f3886 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -64,7 +64,8 @@ struct PiecewiseFittedCurve { NumberType start; NumberType length; NumberType n_segment_size; - size_t segments_count; + + size_t segments() const { return this->coefficients.cols(); } NumberType get_n_segment_start(int segment_index) const { return n_segment_size * segment_index; @@ -81,7 +82,7 @@ struct PiecewiseFittedCurve { int start_segment_idx = int(floor(t / this->n_segment_size)) - int(Kernel::kernel_span * 0.5f - 1.0f); for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); segment_index++) { - if (segment_index < 0 || segment_index >= int(this->segments_count)) { + if (segment_index < 0 || segment_index >= int(this->coefficients.cols())) { continue; } NumberType segment_start = this->get_n_segment_start(segment_index); @@ -100,13 +101,12 @@ struct PiecewiseFittedCurve { // number_of_inner_splines: how many full inner splines are fit into the normalized valid range 0,1; // final number of knots is Kernel::kernel_span times number_of_inner_splines + additional segments on start and end // Kernel: model used for the curve fitting -template +template PiecewiseFittedCurve fit_curve( const std::vector> &observations, const std::vector &observation_points, const std::vector &weights, - size_t number_of_inner_splines, - Kernel kernel) { + size_t number_of_inner_splines) { // check to make sure inputs are correct assert(number_of_inner_splines >= 1); @@ -124,9 +124,13 @@ PiecewiseFittedCurve fit_curve( sqrt_weights[index + extremes_repetition] = sqrt(weights[index]); } //repeat weights for addtional extreme segments - for (int index = 0; index < int(extremes_repetition); ++index) { - sqrt_weights[index] = sqrt(weights.front()); - sqrt_weights[sqrt_weights.size() - index - 1] = sqrt(weights.back()); + { + auto first = sqrt_weights[extremes_repetition]; + auto last = sqrt_weights[extremes_repetition + weights.size() - 1]; + for (int index = 0; index < int(extremes_repetition); ++index) { + sqrt_weights[index] = first; + sqrt_weights[sqrt_weights.size() - index - 1] = last; + } } // prepare result and compute metadata @@ -136,8 +140,8 @@ PiecewiseFittedCurve fit_curve( NumberType orig_segment_size = orig_len / NumberType(number_of_inner_splines * Kernel::kernel_span); result.start = observation_points.front() - extremes_repetition * orig_segment_size; result.length = observation_points.back() + extremes_repetition * orig_segment_size - result.start; - result.segments_count = number_of_inner_splines * Kernel::kernel_span + extremes_repetition * 2; - result.n_segment_size = NumberType(1) / NumberType(result.segments_count - 1); + size_t segments_count = number_of_inner_splines * Kernel::kernel_span + extremes_repetition * 2; + result.n_segment_size = NumberType(1) / NumberType(segments_count - 1); //normalize observations points by start and length std::vector normalized_obs_points(observation_points.size() + extremes_repetition * 2); @@ -157,22 +161,18 @@ PiecewiseFittedCurve fit_curve( // Eigen defaults to column major memory layout. Eigen::MatrixXf data_points(Dimension, observations.size() + extremes_repetition * 2); for (size_t index = 0; index < observations.size(); ++ index) { - for (size_t dim = 0; dim < Dimension; ++dim) { - data_points(dim, index + extremes_repetition) = observations[index](dim) - * sqrt_weights[index + extremes_repetition]; - } + data_points.col(index + extremes_repetition) = observations[index] + * sqrt_weights[index + extremes_repetition]; } //duplicate observed data at the extremes for (int index = 0; index < int(extremes_repetition); index++) { - for (size_t dim = 0; dim < Dimension; ++dim) { - data_points(dim, index) = observations.front()(dim) * sqrt_weights[index]; - data_points(dim, data_points.cols() - index - 1) = observations.back()(dim) - * sqrt_weights[data_points.cols() - index - 1]; - } + data_points.col(index) = observations.front() * sqrt_weights[index]; + data_points.col(data_points.cols() - index - 1) = observations.back() + * sqrt_weights[data_points.cols() - index - 1]; } //Create weight matrix T for each point and each segment; - Eigen::MatrixXf T(normalized_obs_points.size(), result.segments_count); + Eigen::MatrixXf T(normalized_obs_points.size(), segments_count); T.setZero(); //Fill the weight matrix @@ -183,19 +183,19 @@ PiecewiseFittedCurve fit_curve( for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); segment_index++) { // skip if we overshoot segment_index - happens at the extremes - if (segment_index < 0 || segment_index >= int(result.segments_count)) { + if (segment_index < 0 || segment_index >= int(segments_count)) { continue; } NumberType segment_start = result.get_n_segment_start(segment_index); NumberType normalized_segment_distance = (segment_start - knot_val) / result.n_segment_size; // fill in kernel value with weight applied - T(i, segment_index) += kernel.kernel(normalized_segment_distance) * sqrt_weights[i]; + T(i, segment_index) += Kernel::kernel(normalized_segment_distance) * sqrt_weights[i]; } } // Solve for linear least square fit - result.coefficients.resize(Dimension, result.segments_count); + result.coefficients.resize(Dimension, segments_count); const auto QR = T.fullPivHouseholderQr(); for (size_t dim = 0; dim < Dimension; ++dim) { result.coefficients.row(dim) = QR.solve(data_points.row(dim).transpose()); @@ -211,8 +211,7 @@ fit_cubic_bspline( std::vector observation_points, std::vector weights, size_t number_of_segments) { - return fit_curve(observations, observation_points, weights, number_of_segments, - CubicBSplineKernel { }); + return fit_curve>(observations, observation_points, weights, number_of_segments); } template @@ -222,8 +221,7 @@ fit_catmul_rom_spline( std::vector observation_points, std::vector weights, size_t number_of_segments) { - return fit_curve(observations, observation_points, weights, number_of_segments, - CubicCatmulRomKernel { }); + return fit_curveCubicCatmulRomKernel(observations, observation_points, weights, number_of_segments); } } From 396d3215bde882999c9d8790e974bbd6c75da8c1 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 1 Apr 2022 15:50:51 +0200 Subject: [PATCH 51/71] Refactoring of curve fitting algorithm: removal of artificial extension at the ends of the curve removal of observation points normalization added clamping of parameter index which compensates for under-represented spline segments added parameter for level of freedom at the ends of the curve --- src/libslic3r/GCode/SeamPlacer.cpp | 6 +- src/libslic3r/GCode/SeamPlacer.hpp | 2 +- src/libslic3r/Geometry/Curves.hpp | 149 ++++++++++++----------------- src/libslic3r/Point.hpp | 4 +- 4 files changed, 68 insertions(+), 93 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index c7df13c58..a2ae968d8 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1199,9 +1199,9 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: } // Curve Fitting - size_t number_of_splines = std::max(size_t(1), - size_t(observations.size() / SeamPlacer::seam_align_seams_per_spline)); - auto curve = Geometry::fit_cubic_bspline(observations, observation_points, weights, number_of_splines); + size_t number_of_segments = std::max(size_t(1), + size_t(observations.size() / SeamPlacer::seam_align_seams_per_segment)); + auto curve = Geometry::fit_cubic_bspline(observations, observation_points, weights, number_of_segments); // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Perimeter structure of the point; also set flag aligned to true diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 130547ff8..f81bf0155 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -118,7 +118,7 @@ public: // minimum number of seams needed in cluster to make alignemnt happen static constexpr size_t seam_align_minimum_string_seams = 6; // points covered by spline; determines number of splines for the given string - static constexpr size_t seam_align_seams_per_spline = 30; + static constexpr size_t seam_align_seams_per_segment = 8; //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index 5b89f3886..58af14797 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -6,6 +6,8 @@ #include +//#define LSQR_DEBUG + namespace Slic3r { namespace Geometry { @@ -53,7 +55,7 @@ PolynomialCurve fit_polynomial(const std::vector @@ -62,33 +64,24 @@ struct PiecewiseFittedCurve { Eigen::MatrixXf coefficients; NumberType start; - NumberType length; - NumberType n_segment_size; - - size_t segments() const { return this->coefficients.cols(); } - - NumberType get_n_segment_start(int segment_index) const { - return n_segment_size * segment_index; - } - - NumberType normalize(const NumberType &observation_point) const { - return (observation_point - start) / length; - } + NumberType segment_size; + size_t endpoints_level_of_freedom; Vec get_fitted_value(const NumberType &observation_point) const { Vec result = Vec::Zero(); - NumberType t = normalize(observation_point); - int start_segment_idx = int(floor(t / this->n_segment_size)) - int(Kernel::kernel_span * 0.5f - 1.0f); + //find corresponding segment index; expects kernels to be centered; NOTE: shift by number of additional segments, which are outside of the valid length + int middle_right_segment_index = floor((observation_point - start) / segment_size); + //find index of first segment that is affected by the point i; this can be deduced from kernel_span + int start_segment_idx = middle_right_segment_index - Kernel::kernel_span / 2 + 1; for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); segment_index++) { - if (segment_index < 0 || segment_index >= int(this->coefficients.cols())) { - continue; - } - NumberType segment_start = this->get_n_segment_start(segment_index); - NumberType normalized_segment_distance = (segment_start - t) / this->n_segment_size; + NumberType segment_start = start + segment_index * segment_size; + NumberType normalized_segment_distance = (segment_start - observation_point) / segment_size; - result += Kernel::kernel(normalized_segment_distance) * coefficients.col(segment_index); + int parameter_index = segment_index + endpoints_level_of_freedom; + parameter_index = std::clamp(parameter_index, 0, int(coefficients.cols()) - 1); + result += Kernel::kernel(normalized_segment_distance) * coefficients.col(parameter_index); } return result; } @@ -106,99 +99,77 @@ PiecewiseFittedCurve fit_curve( const std::vector> &observations, const std::vector &observation_points, const std::vector &weights, - size_t number_of_inner_splines) { + size_t segments_count, + size_t endpoints_level_of_freedom) { // check to make sure inputs are correct - assert(number_of_inner_splines >= 1); + assert(segments_count > 0); + assert(observations.size() > 0); assert(observation_points.size() == observations.size()); assert(observation_points.size() == weights.size()); - assert(number_of_inner_splines <= observations.size()); - assert(observations.size() >= 4); - - size_t extremes_repetition = Kernel::kernel_span - 1; //how many (additional) times is the first and last point repeated + assert(segments_count <= observations.size()); //prepare sqrt of weights, which will then be applied to both matrix T and observed data: https://en.wikipedia.org/wiki/Weighted_least_squares - std::vector sqrt_weights(weights.size() + extremes_repetition * 2); + std::vector sqrt_weights(weights.size()); for (size_t index = 0; index < weights.size(); ++index) { assert(weights[index] > 0); - sqrt_weights[index + extremes_repetition] = sqrt(weights[index]); - } - //repeat weights for addtional extreme segments - { - auto first = sqrt_weights[extremes_repetition]; - auto last = sqrt_weights[extremes_repetition + weights.size() - 1]; - for (int index = 0; index < int(extremes_repetition); ++index) { - sqrt_weights[index] = first; - sqrt_weights[sqrt_weights.size() - index - 1] = last; - } + sqrt_weights[index] = sqrt(weights[index]); } // prepare result and compute metadata PiecewiseFittedCurve result { }; - NumberType orig_len = observation_points.back() - observation_points.front(); - NumberType orig_segment_size = orig_len / NumberType(number_of_inner_splines * Kernel::kernel_span); - result.start = observation_points.front() - extremes_repetition * orig_segment_size; - result.length = observation_points.back() + extremes_repetition * orig_segment_size - result.start; - size_t segments_count = number_of_inner_splines * Kernel::kernel_span + extremes_repetition * 2; - result.n_segment_size = NumberType(1) / NumberType(segments_count - 1); - - //normalize observations points by start and length - std::vector normalized_obs_points(observation_points.size() + extremes_repetition * 2); - for (size_t index = 0; index < observation_points.size(); ++index) { - normalized_obs_points[index + extremes_repetition] = result.normalize(observation_points[index]); - } - // create artificial values at the extremes for constant curve fitting - for (int index = 0; index < int(extremes_repetition); ++index) { - normalized_obs_points[extremes_repetition - 1 - index] = result.normalize(observation_points.front() - - index * orig_segment_size - NumberType(0.5) * orig_segment_size); - - normalized_obs_points[normalized_obs_points.size() - extremes_repetition + index] = result.normalize( - observation_points.back() + index * orig_segment_size + NumberType(0.5) * orig_segment_size); - } + NumberType valid_length = observation_points.back() - observation_points.front(); + NumberType segment_size = valid_length / NumberType(segments_count); + result.start = observation_points.front(); + result.segment_size = segment_size; + result.endpoints_level_of_freedom = endpoints_level_of_freedom; // prepare observed data // Eigen defaults to column major memory layout. - Eigen::MatrixXf data_points(Dimension, observations.size() + extremes_repetition * 2); - for (size_t index = 0; index < observations.size(); ++ index) { - data_points.col(index + extremes_repetition) = observations[index] - * sqrt_weights[index + extremes_repetition]; - } - //duplicate observed data at the extremes - for (int index = 0; index < int(extremes_repetition); index++) { - data_points.col(index) = observations.front() * sqrt_weights[index]; - data_points.col(data_points.cols() - index - 1) = observations.back() - * sqrt_weights[data_points.cols() - index - 1]; + Eigen::MatrixXf data_points(Dimension, observations.size()); + for (size_t index = 0; index < observations.size(); ++index) { + data_points.col(index) = observations[index] * sqrt_weights[index]; } + size_t parameters_count = segments_count + 1 + 2 * endpoints_level_of_freedom; //Create weight matrix T for each point and each segment; - Eigen::MatrixXf T(normalized_obs_points.size(), segments_count); + Eigen::MatrixXf T(observation_points.size(), parameters_count); T.setZero(); - //Fill the weight matrix - for (size_t i = 0; i < normalized_obs_points.size(); ++i) { - NumberType knot_val = normalized_obs_points[i]; + for (size_t i = 0; i < observation_points.size(); ++i) { + NumberType observation_point = observation_points[i]; + //find corresponding segment index; expects kernels to be centered + int middle_right_segment_index = floor((observation_point - result.start) / result.segment_size); //find index of first segment that is affected by the point i; this can be deduced from kernel_span - int start_segment_idx = int(floor(knot_val / result.n_segment_size)) - int(Kernel::kernel_span * 0.5f - 1.0f); + int start_segment_idx = middle_right_segment_index - Kernel::kernel_span / 2 + 1; for (int segment_index = start_segment_idx; segment_index < int(start_segment_idx + Kernel::kernel_span); segment_index++) { - // skip if we overshoot segment_index - happens at the extremes - if (segment_index < 0 || segment_index >= int(segments_count)) { - continue; - } - NumberType segment_start = result.get_n_segment_start(segment_index); - NumberType normalized_segment_distance = (segment_start - knot_val) / result.n_segment_size; - // fill in kernel value with weight applied + NumberType segment_start = result.start + segment_index * result.segment_size; + NumberType normalized_segment_distance = (segment_start - observation_point) / result.segment_size; - T(i, segment_index) += Kernel::kernel(normalized_segment_distance) * sqrt_weights[i]; + int parameter_index = segment_index + endpoints_level_of_freedom; + parameter_index = std::clamp(parameter_index, 0, int(parameters_count) - 1); + T(i, parameter_index) += Kernel::kernel(normalized_segment_distance) * sqrt_weights[i]; } } +#ifdef LSQR_DEBUG + std::cout << "weight matrix: " << std::endl; + for (int obs = 0; obs < observation_points.size(); ++obs) { + std::cout << std::endl; + for (int segment = 0; segment < parameters_count; ++segment) { + std::cout << T(obs, segment) << " "; + } + } + std::cout << std::endl; +#endif + // Solve for linear least square fit - result.coefficients.resize(Dimension, segments_count); + result.coefficients.resize(Dimension, parameters_count); const auto QR = T.fullPivHouseholderQr(); for (size_t dim = 0; dim < Dimension; ++dim) { - result.coefficients.row(dim) = QR.solve(data_points.row(dim).transpose()); + result.coefficients.row(dim) = QR.solve(data_points.row(dim).transpose()); } return result; @@ -210,8 +181,10 @@ fit_cubic_bspline( const std::vector> &observations, std::vector observation_points, std::vector weights, - size_t number_of_segments) { - return fit_curve>(observations, observation_points, weights, number_of_segments); + size_t segments_count, + size_t endpoints_level_of_freedom = 0) { + return fit_curve>(observations, observation_points, weights, segments_count, + endpoints_level_of_freedom); } template @@ -220,8 +193,10 @@ fit_catmul_rom_spline( const std::vector> &observations, std::vector observation_points, std::vector weights, - size_t number_of_segments) { - return fit_curveCubicCatmulRomKernel(observations, observation_points, weights, number_of_segments); + size_t segments_count, + size_t endpoints_level_of_freedom = 0) { + return fit_curve>(observations, observation_points, weights, segments_count, + endpoints_level_of_freedom); } } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 26ff59226..84152ee9c 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -104,8 +104,8 @@ template inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); - auto v1d = v1.typename cast(); - auto v2d = v2.typename cast(); + auto v1d = v1.template cast(); + auto v2d = v2.template cast(); return atan2(cross2(v1d, v2d), v1d.dot(v2d)); } From ae89d65e3eab85783e6e8686dd8e05541674c0d0 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 1 Apr 2022 15:57:02 +0200 Subject: [PATCH 52/71] added description for the parameter count increase --- src/libslic3r/Geometry/Curves.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index 58af14797..e39e7f100 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -131,7 +131,8 @@ PiecewiseFittedCurve fit_curve( for (size_t index = 0; index < observations.size(); ++index) { data_points.col(index) = observations[index] * sqrt_weights[index]; } - + // parameters count is always increased by one to make the parametric space of the curve symmetric. + // without this fix, the end of the curve is less flexible than the beginning size_t parameters_count = segments_count + 1 + 2 * endpoints_level_of_freedom; //Create weight matrix T for each point and each segment; Eigen::MatrixXf T(observation_points.size(), parameters_count); From 47fc39a4ba625483c36a2f836ea5635be61572d8 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 1 Apr 2022 16:37:04 +0200 Subject: [PATCH 53/71] fixed misleanding or wrong comments of the fitting function --- src/libslic3r/Geometry/Curves.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Geometry/Curves.hpp b/src/libslic3r/Geometry/Curves.hpp index e39e7f100..6542cb706 100644 --- a/src/libslic3r/Geometry/Curves.hpp +++ b/src/libslic3r/Geometry/Curves.hpp @@ -70,7 +70,7 @@ struct PiecewiseFittedCurve { Vec get_fitted_value(const NumberType &observation_point) const { Vec result = Vec::Zero(); - //find corresponding segment index; expects kernels to be centered; NOTE: shift by number of additional segments, which are outside of the valid length + //find corresponding segment index; expects kernels to be centered int middle_right_segment_index = floor((observation_point - start) / segment_size); //find index of first segment that is affected by the point i; this can be deduced from kernel_span int start_segment_idx = middle_right_segment_index - Kernel::kernel_span / 2 + 1; @@ -91,9 +91,8 @@ struct PiecewiseFittedCurve { // observation points: growing sequence of points where the observations were made. // In other words, for function f(x) = y, observations are y0...yn, and observation points are x0...xn // weights: how important the observation is -// number_of_inner_splines: how many full inner splines are fit into the normalized valid range 0,1; -// final number of knots is Kernel::kernel_span times number_of_inner_splines + additional segments on start and end -// Kernel: model used for the curve fitting +// segments_count: number of segments inside the valid length of the curve +// endpoints_level_of_freedom: number of additional parameters at each end; reasonable values depend on the kernel span template PiecewiseFittedCurve fit_curve( const std::vector> &observations, From 3b8cfc62daf2057010d28139d96acbc35b3a12b6 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 4 Apr 2022 11:44:02 +0200 Subject: [PATCH 54/71] fixed drawing seams on multipart objects removed oversampling for blockers improved drawing seams over sharp features --- src/libslic3r/GCode/SeamPlacer.cpp | 31 ++++++++++++++++++++---------- src/libslic3r/GCode/SeamPlacer.hpp | 4 ++-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index a2ae968d8..5f366730a 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -447,10 +447,9 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const if (orig_point) { Vec3f pos_of_next = orig_polygon_points.empty() ? first : orig_polygon_points.front(); float distance_to_next = (position - pos_of_next).norm(); - if (global_model_info.is_enforced(position, distance_to_next) - || global_model_info.is_blocked(position, distance_to_next)) { + if (global_model_info.is_enforced(position, distance_to_next)) { Vec3f vec_to_next = (pos_of_next - position).normalized(); - float step_size = SeamPlacer::enforcer_blocker_oversampling_distance; + float step_size = SeamPlacer::enforcer_oversampling_distance; float step = step_size; while (step < distance_to_next) { oversampled_points.push(position + vec_to_next * step); @@ -473,13 +472,26 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const && result_vec[first_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced) { first_enforced_idx++; } + + // Gather also points with large angles (these are points from the original mesh, since oversampled points have zero angle) + // If there are any, the middle point will be picked from those (makes drawing over sharp corners easier) + std::vector orig_large_angle_points_indices{}; size_t last_enforced_idx = first_enforced_idx; while (last_enforced_idx < perimeter->end_index && result_vec[last_enforced_idx + 1].type == EnforcedBlockedSeamPoint::Enforced) { + if (abs(result_vec[last_enforced_idx].local_ccw_angle) > 0.4 * PI) { + orig_large_angle_points_indices.push_back(last_enforced_idx); + } last_enforced_idx++; } - size_t central_idx = (first_enforced_idx + last_enforced_idx) / 2; - result_vec[central_idx].central_enforcer = true; + + if (orig_large_angle_points_indices.empty()) { + size_t central_idx = (first_enforced_idx + last_enforced_idx) / 2; + result_vec[central_idx].central_enforcer = true; + } else { + size_t central_idx = orig_large_angle_points_indices.size() / 2; + result_vec[orig_large_angle_points_indices[central_idx]].central_enforcer = true; + } } } @@ -508,8 +520,7 @@ std::pair find_previous_and_next_perimeter_point(const std::vect return {size_t(prev),size_t(next)}; } -// Computes all global model info - transforms object, performs raycasting, -// stores enforces and blockers +// Computes all global model info - transforms object, performs raycasting void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather occlusion meshes: start"; @@ -575,7 +586,7 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; - auto obj_transform = po->trafo(); + auto obj_transform = po->trafo_centered(); for (const ModelVolume *mv : po->model_object()->volumes) { if (mv->is_seam_painted()) { @@ -1013,8 +1024,8 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) prev_layer_grid.swap(current_layer_grid); } } - ); - } + ); +} // Estimates, if there is good seam point in the layer_idx which is close to last_point_pos // uses comparator.is_first_not_much_worse method to compare current seam with the closest point diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index f81bf0155..aad783747 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -103,9 +103,9 @@ public: static constexpr float additional_angle_importance = 0.3f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer - static constexpr float enforcer_blocker_distance_tolerance = 0.3f; + static constexpr float enforcer_blocker_distance_tolerance = 0.35f; // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size - static constexpr float enforcer_blocker_oversampling_distance = 0.1f; + static constexpr float enforcer_oversampling_distance = 0.2f; // When searching for seam clusters for alignment: // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer From 8ce36e9137350e411870d9dbdbca4c41423369f7 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 5 Apr 2022 16:13:40 +0200 Subject: [PATCH 55/71] Refactoring of SeamPlacer: Replaced shared_ptr<> with deque. Merged multiple vectors into one. Refactoring using common helper functions (prev/next_idx_modulo(), angle(), ...) AABBTreeIndirect::intersect_ray_all_hits(): Reuse memory of the hits cache. --- src/libslic3r/AABBTreeIndirect.hpp | 9 +- src/libslic3r/GCode/SeamPlacer.cpp | 265 +++++++++++++---------------- src/libslic3r/GCode/SeamPlacer.hpp | 40 +++-- 3 files changed, 158 insertions(+), 156 deletions(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index d47ea5562..3ea18de8d 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -709,10 +709,15 @@ inline bool intersect_ray_all_hits( origin, dir, VectorType(dir.cwiseInverse()), eps } }; - if (! tree.empty()) { + if (tree.empty()) { + hits.clear(); + } else { + // Reusing the output memory if there is some memory already pre-allocated. + ray_intersector.hits = std::move(hits); + ray_intersector.hits.clear(); ray_intersector.hits.reserve(8); detail::intersect_ray_recursive_all_hits(ray_intersector, 0); - std::swap(hits, ray_intersector.hits); + hits = std::move(ray_intersector.hits); std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; }); } return ! hits.empty(); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 5f366730a..614ab4377 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -19,6 +19,8 @@ #include "libslic3r/Geometry/Curves.hpp" +#include "libslic3r/Utils.hpp" + //#define DEBUG_FILES #ifdef DEBUG_FILES @@ -144,7 +146,9 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< std::vector result(triangles.indices.size()); tbb::parallel_for(tbb::blocked_range(0, result.size()), - [&](tbb::blocked_range r) { + [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index, &raycasting_tree, &result](tbb::blocked_range r) { + // Maintaining hits memory outside of the loop, so it does not have to be reallocated for each query. + std::vector hits; for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { FaceVisibilityInfo &dest = result[face_index]; dest.visibility = 1.0f; @@ -174,7 +178,6 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< dest.visibility -= decrease; } } else { - std::vector hits; int in_negative = 0; if (face_index >= negative_volumes_start_index) { // if casting from negative volume face, invert direction final_ray_dir = -1.0 * final_ray_dir; @@ -219,23 +222,16 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, result[0] = 0.0f; } - auto make_idx_circular = [&](int index) { - while (index < 0) { - index += polygon.size(); - } - return index % polygon.size(); - }; - - int idx_prev = 0; - int idx_curr = 0; - int idx_next = 0; + size_t idx_prev = 0; + size_t idx_curr = 0; + size_t idx_next = 0; float distance_to_prev = 0; float distance_to_next = 0; //push idx_prev far enough back as initialization while (distance_to_prev < min_arm_length) { - idx_prev = make_idx_circular(idx_prev - 1); + idx_prev = Slic3r::prev_idx_modulo(idx_prev, polygon.size()); distance_to_prev += lengths[idx_prev]; } @@ -243,25 +239,20 @@ std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, // pull idx_prev to current as much as possible, while respecting the min_arm_length while (distance_to_prev - lengths[idx_prev] > min_arm_length) { distance_to_prev -= lengths[idx_prev]; - idx_prev = make_idx_circular(idx_prev + 1); + idx_prev = Slic3r::next_idx_modulo(idx_prev, polygon.size()); } //push idx_next forward as far as needed while (distance_to_next < min_arm_length) { distance_to_next += lengths[idx_next]; - idx_next = make_idx_circular(idx_next + 1); + idx_next = Slic3r::next_idx_modulo(idx_next, polygon.size()); } // Calculate angle between idx_prev, idx_curr, idx_next. const Point &p0 = polygon.points[idx_prev]; const Point &p1 = polygon.points[idx_curr]; const Point &p2 = polygon.points[idx_next]; - const Point v1 = p1 - p0; - const Point v2 = p2 - p1; - int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1)); - int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0)); - float angle = float(atan2(float(cross), float(dot))); - result[idx_curr] = angle; + result[idx_curr] = float(angle(p1 - p0, p2 - p1)); // increase idx_curr by one float curr_distance = lengths[idx_curr]; @@ -357,20 +348,20 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi && configured_seam_preference == spRandom)) { //for random seam alignment, extract all perimeters Points p; perimeter->collect_points(p); - polygons.emplace_back(p); + polygons.emplace_back(std::move(p)); corresponding_regions_out.push_back(layer_region); } } if (polygons.empty()) { Points p; ex_entity->collect_points(p); - polygons.emplace_back(p); + polygons.emplace_back(std::move(p)); corresponding_regions_out.push_back(layer_region); } } else { Points p; ex_entity->collect_points(p); - polygons.emplace_back(p); + polygons.emplace_back(std::move(p)); corresponding_regions_out.push_back(layer_region); } } @@ -390,7 +381,7 @@ Polygons extract_perimeter_polygons(const Layer *layer, const SeamPosition confi //each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon // if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const LayerRegion *region, - const GlobalModelInfo &global_model_info, std::vector &result_vec) { + const GlobalModelInfo &global_model_info, PrintObjectSeamData::LayerSeams &result) { if (orig_polygon.size() == 0) { return; } @@ -406,7 +397,9 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const std::vector local_angles = calculate_polygon_angles_at_vertices(polygon, lengths, SeamPlacer::polygon_local_angles_arm_distance); - std::shared_ptr perimeter = std::make_shared(); + + result.perimeters.push_back({}); + Perimeter &perimeter = result.perimeters.back(); std::queue orig_polygon_points { }; for (size_t index = 0; index < polygon.size(); ++index) { @@ -416,8 +409,8 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const Vec3f first = orig_polygon_points.front(); std::queue oversampled_points { }; size_t orig_angle_index = 0; - perimeter->start_index = result_vec.size(); - perimeter->flow_width = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).width() : 0.0f; + perimeter.start_index = result.points.size(); + perimeter.flow_width = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).width() : 0.0f; bool some_point_enforced = false; while (!orig_polygon_points.empty() || !oversampled_points.empty()) { EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; @@ -458,18 +451,18 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const } } - result_vec.emplace_back(position, perimeter, local_ccw_angle, type); + result.points.emplace_back(position, perimeter, local_ccw_angle, type); } - perimeter->end_index = result_vec.size() - 1; + perimeter.end_index = result.points.size() - 1; // We will find first patch of enforced points (patch: continous section of enforced points) and select the middle // point, which will have priority during alignemnt // If there are multiple enforced patches in the perimeter, others are ignored if (some_point_enforced) { - size_t first_enforced_idx = perimeter->start_index; - while (first_enforced_idx <= perimeter->end_index - && result_vec[first_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced) { + size_t first_enforced_idx = perimeter.start_index; + while (first_enforced_idx <= perimeter.end_index + && result.points[first_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced) { first_enforced_idx++; } @@ -477,9 +470,9 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const // If there are any, the middle point will be picked from those (makes drawing over sharp corners easier) std::vector orig_large_angle_points_indices{}; size_t last_enforced_idx = first_enforced_idx; - while (last_enforced_idx < perimeter->end_index - && result_vec[last_enforced_idx + 1].type == EnforcedBlockedSeamPoint::Enforced) { - if (abs(result_vec[last_enforced_idx].local_ccw_angle) > 0.4 * PI) { + while (last_enforced_idx < perimeter.end_index + && result.points[last_enforced_idx + 1].type == EnforcedBlockedSeamPoint::Enforced) { + if (abs(result.points[last_enforced_idx].local_ccw_angle) > 0.4 * PI) { orig_large_angle_points_indices.push_back(last_enforced_idx); } last_enforced_idx++; @@ -487,10 +480,10 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const if (orig_large_angle_points_indices.empty()) { size_t central_idx = (first_enforced_idx + last_enforced_idx) / 2; - result_vec[central_idx].central_enforcer = true; + result.points[central_idx].central_enforcer = true; } else { size_t central_idx = orig_large_angle_points_indices.size() / 2; - result_vec[orig_large_angle_points_indices[central_idx]].central_enforcer = true; + result.points[orig_large_angle_points_indices[central_idx]].central_enforcer = true; } } @@ -505,14 +498,14 @@ std::pair find_previous_and_next_perimeter_point(const std::vect int prev = point_index - 1; //for majority of points, it is true that neighbours lie behind and in front of them in the vector int next = point_index + 1; - if (point_index == current.perimeter->start_index) { + if (point_index == current.perimeter.start_index) { // if point_index is equal to start, it means that the previous neighbour is at the end - prev = current.perimeter->end_index; + prev = current.perimeter.end_index; } - if (point_index == current.perimeter->end_index) { + if (point_index == current.perimeter.end_index) { // if point_index is equal to end, than next neighbour is at the start - next = current.perimeter->start_index; + next = current.perimeter.start_index; } assert(prev >= 0); @@ -590,7 +583,7 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { for (const ModelVolume *mv : po->model_object()->volumes) { if (mv->is_seam_painted()) { - auto model_transformation = mv->get_matrix(); + auto model_transformation = obj_transform * mv->get_matrix(); indexed_triangle_set enforcers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER); its_transform(enforcers, model_transformation); @@ -601,8 +594,6 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { its_merge(result.blockers, blockers); } } - its_transform(result.enforcers, obj_transform); - its_transform(result.blockers, obj_transform); result.enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.enforcers.vertices, result.enforcers.indices); @@ -817,7 +808,7 @@ void debug_export_points(const std::vector &perimeter_points, size_t start_index, const SeamComparator &comparator) { - size_t end_index = perimeter_points[start_index].perimeter->end_index; + size_t end_index = perimeter_points[start_index].perimeter.end_index; size_t seam_index = start_index; for (size_t index = start_index; index <= end_index; ++index) { @@ -825,12 +816,12 @@ void pick_seam_point(std::vector &perimeter_points, size_t start_ seam_index = index; } } - perimeter_points[start_index].perimeter->seam_index = seam_index; + perimeter_points[start_index].perimeter.seam_index = seam_index; } size_t pick_nearest_seam_point_index(const std::vector &perimeter_points, size_t start_index, const Vec2f &preffered_location) { - size_t end_index = perimeter_points[start_index].perimeter->end_index; + size_t end_index = perimeter_points[start_index].perimeter.end_index; SeamComparator comparator { spNearest }; size_t seam_index = start_index; @@ -843,7 +834,7 @@ size_t pick_nearest_seam_point_index(const std::vector &perimeter } // picks random seam point uniformly, respecting enforcers blockers and overhang avoidance. -void pick_random_seam_point(std::vector &perimeter_points, size_t start_index) { +void pick_random_seam_point(const std::vector &perimeter_points, size_t start_index) { SeamComparator comparator { spRandom }; // algorithm keeps a list of viable points and their lengths. If it finds a point @@ -852,7 +843,7 @@ void pick_random_seam_point(std::vector &perimeter_points, size_t // in the end, the list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not // big overhang. size_t viable_example_index = start_index; - size_t end_index = perimeter_points[start_index].perimeter->end_index; + size_t end_index = perimeter_points[start_index].perimeter.end_index; std::vector viable_indices; std::vector viable_edges_lengths; std::vector viable_edges; @@ -906,11 +897,11 @@ void pick_random_seam_point(std::vector &perimeter_points, size_t point_idx++; } - Perimeter *perimeter = perimeter_points[start_index].perimeter.get(); - perimeter->seam_index = viable_indices[point_idx]; - perimeter->final_seam_position = perimeter_points[perimeter->seam_index].position + Perimeter &perimeter = perimeter_points[start_index].perimeter; + perimeter.seam_index = viable_indices[point_idx]; + perimeter.final_seam_position = perimeter_points[perimeter.seam_index].position + viable_edges[point_idx].normalized() * picked_len; - perimeter->finalized = true; + perimeter.finalized = true; } @@ -940,19 +931,18 @@ EdgeGridWrapper compute_layer_merged_edge_grid(const Layer *layer) { // Parallel process and extract each perimeter polygon of the given print object. // Gather SeamCandidates of each layer into vector and build KDtree over them -// Store results in the SeamPlacer varaibles m_perimeter_points_per_object and m_perimeter_points_trees_per_object +// Store results in the SeamPlacer variables m_seam_per_object void SeamPlacer::gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info, const SeamPosition configured_seam_preference) { using namespace SeamPlacerImpl; - m_perimeter_points_per_object.emplace(po, po->layer_count()); - m_perimeter_points_trees_per_object.emplace(po, po->layer_count()); + PrintObjectSeamData &seam_data = m_seam_per_object.emplace(po, PrintObjectSeamData{}).first->second; + seam_data.layers.resize(po->layer_count()); tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [&](tbb::blocked_range r) { + [po, configured_seam_preference, &global_model_info, &seam_data](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = - m_perimeter_points_per_object[po][layer_idx]; + PrintObjectSeamData::LayerSeams &layer_seams = seam_data.layers[layer_idx]; const Layer *layer = po->get_layer(layer_idx); auto unscaled_z = layer->slice_z; std::vector regions; @@ -960,11 +950,11 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, Polygons polygons = extract_perimeter_polygons(layer, configured_seam_preference, regions); for (size_t poly_index = 0; poly_index < polygons.size(); ++poly_index) { process_perimeter_polygon(polygons[poly_index], unscaled_z, - regions[poly_index], global_model_info, layer_candidates); + regions[poly_index], global_model_info, layer_seams); } - auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = - std::make_unique(functor, layer_candidates.size()); + auto functor = SeamCandidateCoordinateFunctor { &layer_seams.points }; + seam_data.layers[layer_idx].points_tree = + std::make_unique(functor, layer_seams.points.size()); } } ); @@ -974,10 +964,11 @@ void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info) { using namespace SeamPlacerImpl; - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { + std::vector &layers = m_seam_per_object[po].layers; + tbb::parallel_for(tbb::blocked_range(0, layers.size()), + [&layers, &global_model_info](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + for (auto &perimeter_point : layers[layer_idx].points) { perimeter_point.visibility = global_model_info.calculate_point_visibility( perimeter_point.position); } @@ -988,8 +979,9 @@ void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) { using namespace SeamPlacerImpl; - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { + std::vector &layers = m_seam_per_object[po].layers; + tbb::parallel_for(tbb::blocked_range(0, layers.size()), + [po, &layers](tbb::blocked_range r) { std::unique_ptr prev_layer_grid; if (r.begin() > 0) { // previous layer exists prev_layer_grid = std::make_unique( @@ -998,19 +990,19 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { bool layer_has_multiple_loops = - m_perimeter_points_per_object[po][layer_idx][0].perimeter->end_index - < m_perimeter_points_per_object[po][layer_idx].size() - 1; + layers[layer_idx].points[0].perimeter.end_index + < layers[layer_idx].points.size() - 1; std::unique_ptr current_layer_grid = std::make_unique( compute_layer_merged_edge_grid(po->layers()[layer_idx])); - for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) { + for (SeamCandidate &perimeter_point : layers[layer_idx].points) { Point point = Point::new_scale(Vec2f { perimeter_point.position.head<2>() }); if (prev_layer_grid.get() != nullptr) { coordf_t overhang_dist; - prev_layer_grid->grid.signed_distance(point, scaled(perimeter_point.perimeter->flow_width), + prev_layer_grid->grid.signed_distance(point, scaled(perimeter_point.perimeter.flow_width), overhang_dist); perimeter_point.overhang = - unscale(overhang_dist) - perimeter_point.perimeter->flow_width; + unscale(overhang_dist) - perimeter_point.perimeter.flow_width; } if (layer_has_multiple_loops) { // search for embedded perimeter points (points hidden inside the print ,e.g. multimaterial join, best position for seam) @@ -1040,33 +1032,31 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, std::vector> &seam_string) { using namespace SeamPlacerImpl; - const SeamCandidate &last_point = - m_perimeter_points_per_object[po][last_point_indexes.first][last_point_indexes.second]; + const std::vector &layers = m_seam_per_object[po].layers; + const SeamCandidate &last_point = layers[last_point_indexes.first].points[last_point_indexes.second]; Vec3f projected_position { last_point.position.x(), last_point.position.y(), float( po->get_layer(layer_idx)->slice_z) }; //find closest point in next layer - size_t closest_point_index = find_closest_point( - *m_perimeter_points_trees_per_object[po][layer_idx], projected_position); + size_t closest_point_index = find_closest_point(*layers[layer_idx].points_tree, projected_position); - SeamCandidate &closest_point = m_perimeter_points_per_object[po][layer_idx][closest_point_index]; + const SeamCandidate &closest_point = layers[layer_idx].points[closest_point_index]; - if (closest_point.perimeter->finalized) { //already finalized, skip + if (closest_point.perimeter.finalized) { //already finalized, skip return false; } //from the closest point, deduce index of seam in the next layer - SeamCandidate &next_layer_seam = - m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; + const SeamCandidate &next_layer_seam = layers[layer_idx].points[closest_point.perimeter.seam_index]; if (next_layer_seam.central_enforcer && (next_layer_seam.position - projected_position).norm() < 3 * SeamPlacer::seam_align_tolerable_dist) { - seam_string.push_back( { layer_idx, closest_point.perimeter->seam_index }); - last_point_indexes = std::pair { layer_idx, closest_point.perimeter->seam_index }; + seam_string.push_back( { layer_idx, closest_point.perimeter.seam_index }); + last_point_indexes = std::pair { layer_idx, closest_point.perimeter.seam_index }; return true; } - auto are_similar = [&](const SeamCandidate &a, const SeamCandidate &b) { + auto are_similar = [&comparator](const SeamCandidate &a, const SeamCandidate &b) { return comparator.is_first_not_much_worse(a, b) && comparator.is_first_not_much_worse(b, a); }; @@ -1110,22 +1100,22 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: #endif //gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer + const std::vector &layers = m_seam_per_object[po].layers; std::vector> seams; - for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; + for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { + const std::vector &layer_perimeter_points = layers[layer_idx].points; size_t current_point_index = 0; while (current_point_index < layer_perimeter_points.size()) { - seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter->seam_index); - current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1; + seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter.seam_index); + current_point_index = layer_perimeter_points[current_point_index].perimeter.end_index + 1; } } //sort them before alignment. Alignment is sensitive to initializaion, this gives it better chance to choose something nice std::sort(seams.begin(), seams.end(), - [&](const std::pair &left, const std::pair &right) { - return comparator.is_first_better(m_perimeter_points_per_object[po][left.first][left.second], - m_perimeter_points_per_object[po][right.first][right.second]); + [&comparator, &layers](const std::pair &left, const std::pair &right) { + return comparator.is_first_better(layers[left.first].points[left.second], + layers[right.first].points[right.second]); } ); @@ -1133,9 +1123,8 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: for (const std::pair &seam : seams) { size_t layer_idx = seam.first; size_t seam_index = seam.second; - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - if (layer_perimeter_points[seam_index].perimeter->finalized) { + const std::vector &layer_perimeter_points = layers[layer_idx].points; + if (layer_perimeter_points[seam_index].perimeter.finalized) { // This perimeter is already aligned, skip seam continue; } else { @@ -1148,7 +1137,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: std::vector> seam_string { std::pair(layer_idx, seam_index) }; //find seams or potential seams in forward direction; there is a budget of skips allowed - while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { + while (skips >= 0 && next_layer < int(layers.size())) { if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string)) { //String added, last_point_pos updated, nothing to be done } else { @@ -1191,16 +1180,15 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: //init min_weight by the first point float min_weight = -comparator.get_penalty( - m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]); + layers[seam_string[0].first].points[seam_string[0].second]); //gather points positions and weights, update min_weight in each step for (size_t index = 0; index < seam_string.size(); ++index) { - Vec3f pos = - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; + Vec3f pos = layers[seam_string[index].first].points[seam_string[index].second].position; observations[index] = pos.head<2>(); observation_points[index] = pos.z(); weights[index] = -comparator.get_penalty( - m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); + layers[seam_string[index].first].points[seam_string[index].second]); min_weight = std::min(min_weight, weights[index]); } @@ -1217,13 +1205,12 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Perimeter structure of the point; also set flag aligned to true for (const auto &pair : seam_string) { - Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; + Vec3f current_pos = layers[pair.first].points[pair.second].position; Vec2f fitted_pos = curve.get_fitted_value(current_pos.z()); - Perimeter *perimeter = - m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); - perimeter->final_seam_position = Vec3f { fitted_pos.x(), fitted_pos.y(), current_pos.z() }; - perimeter->finalized = true; + Perimeter &perimeter = layers[pair.first].points[pair.second].perimeter; + perimeter.final_seam_position = to_3d(fitted_pos, current_pos.z()); + perimeter.finalized = true; } #ifdef DEBUG_FILES @@ -1232,7 +1219,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: }; Vec3f color { randf(), randf(), randf() }; for (size_t i = 0; i < seam_string.size(); ++i) { - auto orig_seam = m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second]; + auto orig_seam = perimeter_points[seam_string[i].first][seam_string[i].second]; fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], orig_seam.position[1], orig_seam.position[2], color[0], color[1], @@ -1241,11 +1228,10 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: color = Vec3f { randf(), randf(), randf() }; for (size_t i = 0; i < seam_string.size(); ++i) { - Perimeter *perimeter = - m_perimeter_points_per_object[po][seam_string[i].first][seam_string[i].second].perimeter.get(); - fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter->final_seam_position[0], - perimeter->final_seam_position[1], - perimeter->final_seam_position[2], color[0], color[1], + Perimeter &perimeter = perimeter_points[seam_string[i].first][seam_string[i].second].perimeter; + fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter.final_seam_position[0], + perimeter.final_seam_position[1], + perimeter.final_seam_position[2], color[0], color[1], color[2]); } #endif @@ -1261,8 +1247,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: void SeamPlacer::init(const Print &print) { using namespace SeamPlacerImpl; - m_perimeter_points_trees_per_object.clear(); - m_perimeter_points_per_object.clear(); + m_seam_per_object.clear(); for (const PrintObject *po : print.objects()) { @@ -1299,20 +1284,16 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: pick_seam_point : start"; //pick seam point - tbb::parallel_for(tbb::blocked_range(0, m_perimeter_points_per_object[po].size()), - [&](tbb::blocked_range r) { + std::vector &layers = m_seam_per_object[po].layers; + tbb::parallel_for(tbb::blocked_range(0, layers.size()), + [&layers, configured_seam_preference, comparator](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = - m_perimeter_points_per_object[po][layer_idx]; - size_t current = 0; - while (current < layer_perimeter_points.size()) { - if (configured_seam_preference == spRandom) { + std::vector &layer_perimeter_points = layers[layer_idx].points; + for (size_t current = 0; current < layer_perimeter_points.size(); current = layer_perimeter_points[current].perimeter.end_index + 1) + if (configured_seam_preference == spRandom) pick_random_seam_point(layer_perimeter_points, current); - } else { + else pick_seam_point(layer_perimeter_points, current, comparator); - } - current = layer_perimeter_points[current].perimeter->end_index + 1; - } } }); BOOST_LOG_TRIVIAL(debug) @@ -1327,7 +1308,7 @@ void SeamPlacer::init(const Print &print) { } #ifdef DEBUG_FILES - debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), + debug_export_points(perimeter_points, po->bounding_box(), std::to_string(po->id().id), comparator); #endif } @@ -1341,29 +1322,29 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); double unscaled_z = layer->slice_z; - const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object.find(po)->second[layer_index]; - const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second[layer_index]; + const PrintObjectSeamData::LayerSeams &layer_perimeters = m_seam_per_object.find(layer->object())->second.layers[layer_index]; const Point &fp = loop.first_point(); - Vec2f unscaled_p = unscale(fp).cast(); - size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, - Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); - const Perimeter *perimeter = perimeter_points[closest_perimeter_point_index].perimeter.get(); + Vec2f unscaled_p = unscaled(fp); + size_t closest_perimeter_point_index = find_closest_point(*layer_perimeters.points_tree.get(), to_3d(unscaled_p, float(unscaled_z))); - size_t seam_index; - if (po->config().seam_position == spNearest) { - seam_index = pick_nearest_seam_point_index(perimeter_points, perimeter->start_index, - unscale(last_pos).cast()); + Vec3f seam_position; + if (const Perimeter &perimeter = layer_perimeters.points[closest_perimeter_point_index].perimeter; + perimeter.finalized) { + seam_position = perimeter.final_seam_position; } else { - seam_index = perimeter->seam_index; + size_t seam_index; + if (po->config().seam_position == spNearest) { + seam_index = pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, + unscaled(last_pos)); + } else { + seam_index = perimeter.seam_index; + } + seam_position = layer_perimeters.points[seam_index].position; } - Vec3f seam_position = perimeter_points[seam_index].position; - if (perimeter->finalized) { - seam_position = perimeter->final_seam_position; - } - Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); + auto seam_point = Point::new_scale(seam_position.x(), seam_position.y()); if (!loop.split_at_vertex(seam_point)) // The point is not in the original loop. diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index aad783747..04e6b1bc6 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -51,20 +51,20 @@ struct Perimeter { //Struct over which all processing of perimeters is done. For each perimeter point, its respective candidate is created, // then all the needed attributes are computed and finally, for each perimeter one point is chosen as seam. -// This seam position can be than further aligned +// This seam position can be then further aligned struct SeamCandidate { - SeamCandidate(const Vec3f &pos, std::shared_ptr perimeter, + SeamCandidate(const Vec3f &pos, Perimeter &perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type) : position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle( local_ccw_angle), type(type), central_enforcer(false) { } const Vec3f position; - // pointer to Perimter loop of this point. It is shared across all points of the loop - const std::shared_ptr perimeter; + // pointer to Perimeter loop of this point. It is shared across all points of the loop + Perimeter &perimeter; float visibility; float overhang; - // distance inside the merged layer regions, for detecting perimter points which are hidden indside the print (e.g. multimaterial join) + // distance inside the merged layer regions, for detecting perimeter points which are hidden indside the print (e.g. multimaterial join) // Negative sign means inside the print, comes from EdgeGrid structure float embedded_distance; float local_ccw_angle; @@ -87,10 +87,29 @@ struct SeamCandidateCoordinateFunctor { }; } // namespace SeamPlacerImpl +struct PrintObjectSeamData +{ + using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; + + struct LayerSeams + { + std::deque perimeters; + std::vector points; + std::unique_ptr points_tree; + }; + // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter + std::vector layers; + // Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD + // tree of all points of the given layer + + void clear() + { + layers.clear(); + } +}; + class SeamPlacer { public: - using SeamCandidatesTree = - KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; static constexpr size_t raycasting_decimation_target_triangle_count = 10000; static constexpr float raycasting_subdivision_target_length = 2.0f; //square of number of rays per triangle @@ -120,11 +139,8 @@ public: // points covered by spline; determines number of splines for the given string static constexpr size_t seam_align_seams_per_segment = 8; - //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: - // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer - std::unordered_map>> m_perimeter_points_per_object; - // Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD tree of all points of the given layer - std::unordered_map>> m_perimeter_points_trees_per_object; + //The following data structures hold all perimeter points for all PrintObject. + std::unordered_map m_seam_per_object; void init(const Print &print); From fb2621c03ca264fa77106b20b4965a9d6d23c5ce Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 6 Apr 2022 09:57:36 +0200 Subject: [PATCH 56/71] Negative volumes raycasting fix - normal was flipped between iterations, incorrect algorithm for hit detection Debug files export fix after refactoring --- src/libslic3r/GCode/SeamPlacer.cpp | 43 +++++++++++++++++------------- src/libslic3r/GCode/SeamPlacer.hpp | 4 +-- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 614ab4377..a53e3246e 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -146,13 +146,15 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< std::vector result(triangles.indices.size()); tbb::parallel_for(tbb::blocked_range(0, result.size()), - [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index, &raycasting_tree, &result](tbb::blocked_range r) { + [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index, + &raycasting_tree, &result](tbb::blocked_range r) { // Maintaining hits memory outside of the loop, so it does not have to be reallocated for each query. std::vector hits; for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { FaceVisibilityInfo &dest = result[face_index]; dest.visibility = 1.0f; - constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); + constexpr float decrease = 1.0f + / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); Vec3i face = triangles.indices[face_index]; Vec3f A = triangles.vertices[face.x()]; @@ -177,32 +179,35 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< if (hit) { dest.visibility -= decrease; } - } else { - int in_negative = 0; - if (face_index >= negative_volumes_start_index) { // if casting from negative volume face, invert direction + } else { //TODO improve logic for order based boolean operations - consider order of volumes + Vec3d ray_origin_d = (center + normal * 0.1).cast(); // start above surface. + if (face_index >= negative_volumes_start_index) { // if casting from negative volume face, invert direction, change start pos final_ray_dir = -1.0 * final_ray_dir; - normal = -normal; + ray_origin_d = (center - normal * 0.1).cast(); } Vec3d final_ray_dir_d = final_ray_dir.cast(); - Vec3d ray_origin_d = (center + normal * 0.1).cast(); // start above surface. bool some_hit = AABBTreeIndirect::intersect_ray_all_hits(triangles.vertices, triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hits); if (some_hit) { + int in_negative = 0; + int in_positive = 0; // NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point; // It cannot be inside model, and it cannot be inside negative volume for (int hit_index = int(hits.size()) - 1; hit_index >= 0; --hit_index) { + Vec3f face_normal = its_face_normal(triangles, hits[hit_index].id); if (hits[hit_index].id >= int(negative_volumes_start_index)) { //negative volume hit - Vec3f normal = its_face_normal(triangles, hits[hit_index].id); - in_negative += sgn(normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space + in_negative += sgn(face_normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space // which in reverse hit analysis means, that we are entering negative space :) and vice versa - } else if (in_negative <= 0) { //object hit and we are not in negative space + } else { + in_positive += sgn(face_normal.dot(final_ray_dir)); + } + if (in_positive > 0 && in_negative <= 0) { dest.visibility -= decrease; break; } } } - } } } @@ -747,9 +752,9 @@ struct SeamComparator { ; #ifdef DEBUG_FILES -void debug_export_points(const std::vector> &object_perimter_points, +void debug_export_points(const std::vector &layers, const BoundingBox &bounding_box, std::string object_name, const SeamComparator &comparator) { - for (size_t layer_idx = 0; layer_idx < object_perimter_points.size(); ++layer_idx) { + for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { std::string angles_file_name = debug_out_path( (object_name + "_angles_" + std::to_string(layer_idx) + ".svg").c_str()); SVG angles_svg {angles_file_name, bounding_box}; @@ -759,7 +764,7 @@ void debug_export_points(const std::vector::min(); float max_weight = min_weight; - for (const SeamCandidate &point : object_perimter_points[layer_idx]) { + for (const SeamCandidate &point : layers[layer_idx].points) { Vec3i color = value_rgbi(-PI, PI, point.local_ccw_angle); std::string fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," + std::to_string(color.z()) + ")"; @@ -782,7 +787,7 @@ void debug_export_points(const std::vector &left, const std::pair &right) { @@ -1219,7 +1224,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: }; Vec3f color { randf(), randf(), randf() }; for (size_t i = 0; i < seam_string.size(); ++i) { - auto orig_seam = perimeter_points[seam_string[i].first][seam_string[i].second]; + auto orig_seam = layers[seam_string[i].first].points[seam_string[i].second]; fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], orig_seam.position[1], orig_seam.position[2], color[0], color[1], @@ -1228,7 +1233,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: color = Vec3f { randf(), randf(), randf() }; for (size_t i = 0; i < seam_string.size(); ++i) { - Perimeter &perimeter = perimeter_points[seam_string[i].first][seam_string[i].second].perimeter; + const Perimeter &perimeter = layers[seam_string[i].first].points[seam_string[i].second].perimeter; fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter.final_seam_position[0], perimeter.final_seam_position[1], perimeter.final_seam_position[2], color[0], color[1], @@ -1308,7 +1313,7 @@ void SeamPlacer::init(const Print &print) { } #ifdef DEBUG_FILES - debug_export_points(perimeter_points, po->bounding_box(), std::to_string(po->id().id), + debug_export_points(layers, po->bounding_box(), std::to_string(po->id().id), comparator); #endif } diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 04e6b1bc6..15dcd702a 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -119,7 +119,7 @@ public: static constexpr float polygon_local_angles_arm_distance = 0.5f; // increases angle importance at the cost of deacreasing visibility info importance. must be > 0 - static constexpr float additional_angle_importance = 0.3f; + static constexpr float additional_angle_importance = 0.6f; // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer static constexpr float enforcer_blocker_distance_tolerance = 0.35f; @@ -134,7 +134,7 @@ public: // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. // this param limits the number of allowed skips static constexpr size_t seam_align_tolerable_skips = 4; - // minimum number of seams needed in cluster to make alignemnt happen + // minimum number of seams needed in cluster to make alignment happen static constexpr size_t seam_align_minimum_string_seams = 6; // points covered by spline; determines number of splines for the given string static constexpr size_t seam_align_seams_per_segment = 8; From f5709345ad2f9f7a024558bf1bb2dd2545a9b650 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 8 Apr 2022 16:55:55 +0200 Subject: [PATCH 57/71] GCode export: Replaced std::strings in G-code comments with string_views. Seam placer: Detecting perimeter by the pointer and size of the comment_perimeter string_view, only placing seams for perimeters. --- src/libslic3r/GCode.cpp | 69 ++++++++++++++++-------------- src/libslic3r/GCode.hpp | 10 ++--- src/libslic3r/GCode/SeamPlacer.cpp | 7 ++- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a5aa9013e..1a6ee4b80 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2336,7 +2336,7 @@ GCode::LayerResult GCode::process_layer( path.mm3_per_mm = mm3_per_mm; } //FIXME using the support_material_speed of the 1st object printed. - gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); + gcode += this->extrude_loop(loop, "skirt"sv, m_config.support_material_speed.value); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). @@ -2349,7 +2349,7 @@ GCode::LayerResult GCode::process_layer( this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); for (const ExtrusionEntity *ee : print.brim().entities) { - gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value); + gcode += this->extrude_entity(*ee, "brim"sv, m_config.support_material_speed.value); } m_brim_done = true; m_avoid_crossing_perimeters.use_external_mp(false); @@ -2542,7 +2542,13 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } -std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed) +static const auto comment_perimeter = "perimeter"sv; +// Comparing string_view pointer & length for speed. +static inline bool comment_is_perimeter(const std::string_view comment) { + return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); +} + +std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed) { // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation @@ -2553,11 +2559,11 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); - if (m_config.spiral_vase) { - loop.split_at(last_pos, false); - } - else + if (! m_config.spiral_vase && comment_is_perimeter(description)) { + assert(m_layer != nullptr); m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first, this->last_pos()); + } else + loop.split_at(last_pos, false); // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so @@ -2577,11 +2583,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // extrude along the path std::string gcode; - for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { -// description += ExtrusionLoop::role_to_string(loop.loop_role()); -// description += ExtrusionEntity::role_to_string(path->role); - path->simplify(m_scaled_resolution); - gcode += this->_extrude(*path, description, speed); + for (ExtrusionPath &path : paths) { + path.simplify(m_scaled_resolution); + gcode += this->_extrude(path, description, speed); } // reset acceleration @@ -2629,13 +2633,11 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou return gcode; } -std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed) +std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed) { // extrude along the path std::string gcode; for (ExtrusionPath path : multipath.paths) { -// description += ExtrusionLoop::role_to_string(loop.loop_role()); -// description += ExtrusionEntity::role_to_string(path->role); path.simplify(m_scaled_resolution); gcode += this->_extrude(path, description, speed); } @@ -2648,7 +2650,7 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string return gcode; } -std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed) +std::string GCode::extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed) { if (const ExtrusionPath* path = dynamic_cast(&entity)) return this->extrude_path(*path, description, speed); @@ -2661,9 +2663,8 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des return ""; } -std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) +std::string GCode::extrude_path(ExtrusionPath path, std::string_view description, double speed) { -// description += ExtrusionEntity::role_to_string(path.role()); path.simplify(m_scaled_resolution); std::string gcode = this->_extrude(path, description, speed); if (m_wipe.enable) { @@ -2684,7 +2685,7 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorextrude_entity(*ee, "perimeter", -1.); + gcode += this->extrude_entity(*ee, comment_perimeter, -1.); } return gcode; } @@ -2694,7 +2695,7 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorrole(); assert(role == erSupportMaterial || role == erSupportMaterialInterface); - const char *label = (role == erSupportMaterial) ? support_label : support_interface_label; + const auto label = (role == erSupportMaterial) ? support_label : support_interface_label; const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed; const ExtrusionPath *path = dynamic_cast(ee); if (path) @@ -2818,20 +2819,18 @@ void GCode::GCodeOutputStream::write_format(const char* format, ...) va_end(args); } -std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed) +std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view description, double speed) { std::string gcode; - - if (is_bridge(path.role())) - description += " (bridge)"; + const std::string_view description_bridge = is_bridge(path.role()) ? " (bridge)"sv : ""sv; // go to first point of extrusion path if (!m_last_pos_defined || m_last_pos != path.first_point()) { - gcode += this->travel_to( - path.first_point(), - path.role(), - "move to first " + description + " point" - ); + std::string comment = "move to first "; + comment += description; + comment += description_bridge; + comment += " point"; + gcode += this->travel_to(path.first_point(), path.role(), comment); } // compensate retraction @@ -2969,7 +2968,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += m_writer.set_speed(F, "", comment); double path_length = 0.; { - std::string comment = m_config.gcode_comments ? description : ""; + std::string comment; + if (m_config.gcode_comments) { + comment = description; + comment += description_bridge; + } Vec2d prev = this->point_to_gcode_quantized(path.polyline.points.front()); auto it = path.polyline.points.begin(); auto end = path.polyline.points.end(); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8732e797a..5afe3d915 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -274,10 +274,10 @@ private: void set_extruders(const std::vector &extruder_ids); std::string preamble(); std::string change_layer(coordf_t print_z); - std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1.); - std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1.); - std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); - std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); + std::string extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed = -1.); + std::string extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed = -1.); + std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.); + std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.); // Extruding multiple objects with soluble / non-soluble / combined supports // on a multi-material printer, trying to minimize tool switches. @@ -428,7 +428,7 @@ private: // Processor GCodeProcessor m_processor; - std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); + std::string _extrude(const ExtrusionPath &path, const std::string_view description, double speed = -1); void print_machine_envelope(GCodeOutputStream &file, Print &print); void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index a53e3246e..c729e4982 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1323,8 +1323,11 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern const Point &last_pos) const { using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); -//NOTE this is necessary, since layer->id() is quite unreliable - size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); + // Must not be called with supprot layer. + assert(dynamic_cast(layer) == nullptr); + // Object layer IDs are incremented by the number of raft layers. + assert(layer->id() >= po->slicing_parameters().raft_layers()); + size_t layer_index = layer->id() - po->slicing_parameters().raft_layers(); double unscaled_z = layer->slice_z; const PrintObjectSeamData::LayerSeams &layer_perimeters = m_seam_per_object.find(layer->object())->second.layers[layer_index]; From 853b8adf807b1c3fbef09361cd73336feebfa3d6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 11 Apr 2022 09:49:41 +0200 Subject: [PATCH 58/71] Little refactoring of SeamPlacer. Moved color mapping functions to Color.hpp Removed the "extern" keyword from Color.hpp --- src/libslic3r/Color.hpp | 62 ++++++++++++++++++----------- src/libslic3r/GCode/SeamPlacer.cpp | 63 ++++++++++++------------------ 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp index 183705c4a..bb62ffdaa 100644 --- a/src/libslic3r/Color.hpp +++ b/src/libslic3r/Color.hpp @@ -133,41 +133,57 @@ public: static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; } }; -extern ColorRGB operator * (float value, const ColorRGB& other); -extern ColorRGBA operator * (float value, const ColorRGBA& other); +ColorRGB operator * (float value, const ColorRGB& other); +ColorRGBA operator * (float value, const ColorRGBA& other); -extern ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t); -extern ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t); +ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t); +ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t); -extern ColorRGB complementary(const ColorRGB& color); -extern ColorRGBA complementary(const ColorRGBA& color); +ColorRGB complementary(const ColorRGB& color); +ColorRGBA complementary(const ColorRGBA& color); -extern ColorRGB saturate(const ColorRGB& color, float factor); -extern ColorRGBA saturate(const ColorRGBA& color, float factor); +ColorRGB saturate(const ColorRGB& color, float factor); +ColorRGBA saturate(const ColorRGBA& color, float factor); -extern ColorRGB opposite(const ColorRGB& color); -extern ColorRGB opposite(const ColorRGB& a, const ColorRGB& b); +ColorRGB opposite(const ColorRGB& color); +ColorRGB opposite(const ColorRGB& a, const ColorRGB& b); -extern bool can_decode_color(const std::string& color); +bool can_decode_color(const std::string& color); -extern bool decode_color(const std::string& color_in, ColorRGB& color_out); -extern bool decode_color(const std::string& color_in, ColorRGBA& color_out); +bool decode_color(const std::string& color_in, ColorRGB& color_out); +bool decode_color(const std::string& color_in, ColorRGBA& color_out); -extern bool decode_colors(const std::vector& colors_in, std::vector& colors_out); -extern bool decode_colors(const std::vector& colors_in, std::vector& colors_out); +bool decode_colors(const std::vector& colors_in, std::vector& colors_out); +bool decode_colors(const std::vector& colors_in, std::vector& colors_out); -extern std::string encode_color(const ColorRGB& color); -extern std::string encode_color(const ColorRGBA& color); +std::string encode_color(const ColorRGB& color); +std::string encode_color(const ColorRGBA& color); -extern ColorRGB to_rgb(const ColorRGBA& other_rgba); -extern ColorRGBA to_rgba(const ColorRGB& other_rgb); -extern ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha); +ColorRGB to_rgb(const ColorRGBA& other_rgba); +ColorRGBA to_rgba(const ColorRGB& other_rgb); +ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha); -extern ColorRGBA picking_decode(unsigned int id); -extern unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b); +// Color mapping of a value into RGB false colors. +inline Vec3f value_to_rgbf(float minimum, float maximum, float value) +{ + float ratio = 2.0f * (value - minimum) / (maximum - minimum); + float b = std::max(0.0f, (1.0f - ratio)); + float r = std::max(0.0f, (ratio - 1.0f)); + float g = 1.0f - b - r; + return Vec3f { r, g, b }; +} + +// Color mapping of a value into RGB false colors. +inline Vec3i value_to_rgbi(float minimum, float maximum, float value) +{ + return (value_to_rgbf(minimum, maximum, value) * 255).cast(); +} + +ColorRGBA picking_decode(unsigned int id); +unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b); // Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components // were not interpolated by alpha blending or multi sampling. -extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); } // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index c729e4982..738164b87 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -11,6 +11,7 @@ #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Color.hpp" #include "libslic3r/EdgeGrid.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Layer.hpp" @@ -45,18 +46,6 @@ float gauss(float value, float mean_x_coord, float mean_value, float falloff_spe return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); } -Vec3f value_to_rgbf(float minimum, float maximum, float value) { - float ratio = 2.0f * (value - minimum) / (maximum - minimum); - float b = std::max(0.0f, (1.0f - ratio)); - float r = std::max(0.0f, (ratio - 1.0f)); - float g = 1.0f - b - r; - return Vec3f { r, g, b }; -} - -Vec3i value_rgbi(float minimum, float maximum, float value) { - return (value_to_rgbf(minimum, maximum, value) * 255).cast(); -} - /// Coordinate frame class Frame { public: @@ -616,16 +605,6 @@ struct SeamComparator { setup(setup) { } - float compute_angle_penalty(float ccw_angle) const { - // This function is used: - // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) - // looks scary, but it is gaussian combined with sigmoid, - // so that concave points have much smaller penalty over convex ones - - return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + - 1.0f / (2 + std::exp(-ccw_angle)); // sigmoid, which heavily favourizes concave angles - } - // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage // should return if a is better seamCandidate than b bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location = Vec2f { 0.0f, @@ -748,8 +727,18 @@ struct SeamComparator { return (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); } -} -; + +private: + float compute_angle_penalty(float ccw_angle) const { + // This function is used: + // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) + // looks scary, but it is gaussian combined with sigmoid, + // so that concave points have much smaller penalty over convex ones + + return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + + 1.0f / (2 + std::exp(-ccw_angle)); // sigmoid, which heavily favourizes concave angles + } +}; #ifdef DEBUG_FILES void debug_export_points(const std::vector &layers, @@ -1327,28 +1316,27 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern assert(dynamic_cast(layer) == nullptr); // Object layer IDs are incremented by the number of raft layers. assert(layer->id() >= po->slicing_parameters().raft_layers()); - size_t layer_index = layer->id() - po->slicing_parameters().raft_layers(); - double unscaled_z = layer->slice_z; + const size_t layer_index = layer->id() - po->slicing_parameters().raft_layers(); + const double unscaled_z = layer->slice_z; const PrintObjectSeamData::LayerSeams &layer_perimeters = m_seam_per_object.find(layer->object())->second.layers[layer_index]; - const Point &fp = loop.first_point(); - - Vec2f unscaled_p = unscaled(fp); - size_t closest_perimeter_point_index = find_closest_point(*layer_perimeters.points_tree.get(), to_3d(unscaled_p, float(unscaled_z))); + // Find the closest perimeter in the SeamPlacer to the first point of this loop. + size_t closest_perimeter_point_index; + { + const Point &fp = loop.first_point(); + Vec2f unscaled_p = unscaled(fp); + closest_perimeter_point_index = find_closest_point(*layer_perimeters.points_tree.get(), to_3d(unscaled_p, float(unscaled_z))); + } Vec3f seam_position; if (const Perimeter &perimeter = layer_perimeters.points[closest_perimeter_point_index].perimeter; perimeter.finalized) { seam_position = perimeter.final_seam_position; } else { - size_t seam_index; - if (po->config().seam_position == spNearest) { - seam_index = pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, - unscaled(last_pos)); - } else { - seam_index = perimeter.seam_index; - } + size_t seam_index = po->config().seam_position == spNearest ? + pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, unscaled(last_pos)) : + perimeter.seam_index; seam_position = layer_perimeters.points[seam_index].position; } @@ -1361,4 +1349,3 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern } } // namespace Slic3r - From adb467286f63af795629da153d60528b136c1ce8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 11 Apr 2022 09:51:32 +0200 Subject: [PATCH 59/71] Documented the seam placement corner penalty with an image. --- doc/seam_placement/corner_penalty_function.png | Bin 0 -> 35247 bytes src/libslic3r/GCode/SeamPlacer.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 doc/seam_placement/corner_penalty_function.png diff --git a/doc/seam_placement/corner_penalty_function.png b/doc/seam_placement/corner_penalty_function.png new file mode 100644 index 0000000000000000000000000000000000000000..bf445d671b5255ec1b667b5ebaa933366b114f24 GIT binary patch literal 35247 zcmb@u1z42ZyEly6Kt<_BK)OSauB{9RNJw|5bayBy3@zQT9Y7i+hepH!VF;0u?(S~* z){Oi7&pzLY_dVD3da-4zFwZ>Cy4QXGY7zQM@x{aY7JvEwhIOZ_BYgjHxk&eDKIb|W5`0Dsd=VvO!_zy9;a|!SaOO@x~N8T zX7{gfrWJ+0#jR?`HTAvs!8VYq)8rh3A}h|1O=!CzVY0EhU`)ZlA;XmQ@G3^FqBqTK zvnx|a(HH@@jVK?vV{2+0RM&ay?KAxER`m3d(m$>2#ds$Ncqg~1?V`lv<;a^j*bzt7IhGbv?4DdiSs^2kEf z6#ENdu4*`(gdX8KKhRTTy7tEvVc4QA$xfb870r4wc~T{w%Dio#`I^_xk}Tx5Q163g zgjGPRt9&GK^lrg&>taOTb7>j$A70Kn^AmtagZkBt?K%lQ1)G*@9dOZPe!g;&Aij-V8}e$v&}TJ!ZnxJ)5XULOf5O zR=pnhBvkqke2oRsmfKD6rSJ1Hz1K!9v)qI;RGZ1MLjv~GrZ;|-n?@CZg?%$>v9`G> z=6#SqX>X~WXiOT8FyT+D4z-KB#saUwt6^wpsHxfEq^GMpp8GP%e=jcU)*o+MTU*1z z!p7|zdRAR)s;lw(mwM9RcH>8bs!Eei9h4-Tdbm=%Ve_`XK7VX#(hosBi7yexEn8&E zLr>gTY^^=woG4XIqvhywI9rk09fJ&>HJ0-|`=`rU0cWdu`%bd4YU-O?TUWc$0qJtN z?HwHgc4Mw5JF`Ur*VcE>-6$UOaaz*l@PA$O4R`V-=6Ptp;=i%47~O~mx3pA?557H_VWl2C57#((#zS<8SR^Gt@k8Ii zm`81gEh@FyiNg^vjxGFf#7g01ec9ouQT^_EOHOVxf{>T_wT_pYy#AL);ZK}029&BK ztYuLJPC^tke7#3{pE3m;GRKB99rk;Y%6i>TEqhygkMSi(2OCsRv;zgeL+0?YJ()J(Vsm!GQK$R?Me0rR! z%38JNu@%R$Sz)1dfS}U_KVD=@cP^YXK#;IvQQZzc%G&AP&@3+Yz}XMr(kOQ zPsrfa*2ndL-O=dY!NC#wTvrvj5R>djZHXdm6YlWQcYLbgItL-8pmBZq)1*L)Ug!zc zy#_r_+O;Of-Qek;KC^LyFRq;|2lk{LDm%0}yHvQzA0VEKPYIkKg8lO4;a{kY%Ka`r z{(*A+QfhEV%B+3t50e9D(&=Er}DsJ`}L+*zj64`GU(hd{O{0vg&ZEh(3B=yChMXc-x? zzgl8$Igav45(v=lH4o~a@ib{Hg+-a#|7Ze_>x=Cd|4H6H(8W}K4Q?$(t3oU(eAI|V z-R>Fmf=X$v6a$qZD@z*so=}|zhMawSX*m^<2P-U0m1j(xwLj*_Nk9GB;WtvSK!M|% zT^vZVaMF<^ab=x z5dldwUJSydJT;|(r0hKWy^Ua+7@Ora?rGciAK&9cngRy=;9Lfe?*@*n+Z8lrK!tDS zO-TeWXNEE6e)E~!c8zwod+qmYdDoJepi_R(^WrpZN?N4oBwQ^5eL+Igi8lm-|^0z72~!`-;7tASlQ zCeS@3#Kn6n*xR@j^IQ;NMC&7EWhEsHu$c0{IxV7dO~ML`tqiJRUJ1iKFYsske|>Lh z+TYMjvaj_R^@FkBZvVqSnp(*ltW-A{y6w0q#3SDB-mud+9nWxh4X+K&|5*6?Yu7IAeG>ISQYgZvmiGjB1eJZEl%BVv(@rQ&~0F3osNb)1_N{PHx>>+RIpzC0@M zW8$P_fVJsw-kg8yP~!h=+CCpuiIK3jCQ?1KB+YKasZl`u^NA8XMnisJnjxKjs!pDLpJorl{YE5`8E7KG#9JJNzDq)Z7x*02@j|<2c3Ua+j*~KaqB_4wNF6O zu=8}o!CQ`!Xh(h#Q)BWXa;zcegS()QKNG(uiSIRUJk zGBH*f33#7-pAC<@_t%o^3HhrVPUi{xN870qZY${rCRIiiyxakJqNmbfL1j3e4i?Ro zCFHoIz)|aTDqvrx!+bZetzF&IS4A#Xrrvms>_G}$?`!wqIhwqoOT(?H-Mw9s4imcU z@|b{t8Nw;CGGMs-JgBU-CKcl=4z3Hf`s*Vq@n2F(}Xtze*52 zfxBT^ti)lF_{x?ugA|lys9HO%9`H49X3^am{qjWbr5lzFKn;n*FShbq;-J!|bPYJ0 z_oaf8t+B!SyhIOyZsKS1R(;Nf*f!-Asr{PsC1}ve$G^Y(zA|wLU6wt5&2hFwzdDD&cJ0!wx=~a9l>NwKaNVkQ z-B`qAi2LjCqKAqP=Ayt1`SS)EgE5OL6Jxex5~|;m3TnXdt2a3v(XjmqUmUiI^hlXh zfavN_cRDO)I5dVZ1{13H7m0#x;B^Nzg#LWXQpkh|L?RvfJWSHc$DVLk#Wh zhjg}_E$g;8JTj+Mi}6$9aABA278Me7%LYxqi_%^9>hoQaNBbb$s}K;42Fcmv0JJ(v#Dol#I(- z-t2L4+lFV=@{BJ2I~!KE3(+qO#v13iZsYTqv}L0a zUr09GG*}2ca${(PU?M3U3=Clr%Oe8il(tIKOOMd+7RGC=th=!oEA2Y4vu-vN!L| zhHGvRXc#JYXeGaX$$Rj9T@Wg?EqdLk^}+J*uUUx+KN@#2m-FL)gQw`2)_d;3Xw?0REpkbaQ1^xGXEi zb(-;{@H-0cVcZ-QOtK+(wC=DtTi%632l^y@)Lrd8ny9C}S?;8{X znn{FKkp8_-Pg^S?gRh3FoEAqyp&s!GB5cs__HY7a0Q`?O$C(1k5GFJ*x))KnF#u2( zmg>T{JX+56n_p&`a`tYr?c?=_Eve&hPD3EOYG%hD^&#|ZqpxfsNrkAVLXMt6DFal? zjIdWvPu!RN&kmfnn4r+=G2e^4aeHm{#0(J+|D9G$z-A^E97eX>%D@E*@-lzY9#02c z1EvH(xL%Y=1&>ckP3WhrWI<0?-aN5hj^8_wYWb>n zGOP424Z~4xuiMrCPK4rG#FCKuPGA0f{9|Hb!%=YQuM?YHA8Y;eaA-}HxXR`(LI3F0 zz}xRk5W=v?`zFsj#!$BrRRD({uk>dFl4hNEFQgoyCyAgZskgCD+ToGRneC6Dxz{;_GL+$^DNj;;T6D)0RZf{0>B9XqVNjVSUfso+e}DM>h&h8K1ZS!_1)h}lU2U_tCbh)n z73d0Ie1D9Oj}Hv+3JMB7c|vnHkU6Y^-Li5t2or}j48|OGCe?4HmeJ)pKjKx;)YSBt zBFf?a@4~c*K_dT*!7{?rqFKQW}ofkRrhd! z_4#gRW}LpHK3#u|4dvS3>@82ee{!Tt;irkC$TCMD8aqznniS@4b_q%9zzZ!4f^3(!DPfOhy z4Q;HMvaYs5JVS9YctDPE=shp5D<2kG0fCfx_?`(K!!;x}oDyxKU8XObs;awyhLGuh zwwh$%{Tfg_oe7J|p65dYOLG$mTXM8I{f&kF1J5C#9pB8GrUlcE>LtRp6bA$2FH-dO zO{P*Nejo8Cq=U;>kem21Z1}bf@dyV4ZP_?dD>+S6QAw$fKuHu2J48fS82gdnXS2@O zi^NF+0%>j5iS;Avm#OgKvVIK>kx`#wUdJ$KVEp^DROK65w`h zdVSPe2|CndT}%7?vDbwpfh3Xb`>EStc#@K%B?I|3EnoTGPDs`90u@Jv+va?FPp?+; z*xuMG6-Qc+B3Tmsszw+)du8VaR~cVS=REs$?_@MV41GA5dU(9Ag_tn2CA~@Rg12df zo^AFTa&pqKS@uNynFhABmI%J$ zF$}=ysVFf>d>7O-^obu5;5Tj_HarIWds503qs2WIm0Do`NsVWV5Qq1xpSu~Z@3(@A z4v>B67tUhw>Sxs+fMst`bom2nKB|HGMloXR^vybXzi=lh^*diLojHAkM0YwNILHcv zW(#%IqQ`fsLS7J8K6zqMRjL0aD=^4Oi^%3Fk^a!&n24{j8{97HilvuP=FJmaf(B?-nTt7<`djB|;JF z=TF)nYT*YPEda938bkBWgl4$6Amu=085=hy$VJ?#k3VSup<~A}1ScyTObhLs(Ip<_ zTA^s7H|M=xoNBqc6zOYM?QbI)qbMS8GH?)0%P-y2cY;s9k>u9-ciQv&GCv#1{y@XC z2`zv7)AQx=`GUK6la&Vs(Us}Yi-Caj22Vb(v>rwPu^sf!Yl*!!vo+C(_hTsXY#8ht zBl+LU`KNM1YbXuv{fMKC4KZnmI`%eb$e__R#F`3btfxtWx!DYhg8EWj2O$OUltqb# zsCE?PUW?8ExZ}u&ZqfqWkH|FCq`Gqqw>={P^U~FC35IGF^{gMQ=}+ppeD0O=V8ENK zw%UAtjZa%IIN^8kH3UMa_)hibAr&0d0`oKUTPbB?V7RUP7gsNb$C#d)i3^fxzrIOv zSP_NNI9+xi2VjeQwE6(O!-T?Wb>g6l(>+6x07r$e!duyNPhZ=6Nvg;3!zVg4lLjaD z*ap>~TH$y~93;G`{*I(>euvozT6<|QdRV5m&zU+aW{Sdn0at^3(@A!=d_Og`%5wb% z4(~o5y*Y*V_Z|KpK-qjuxeTKmWIMx0AeNK~HY{aR%o->6NmNftIel}~f4?e*BSVp^ zNPkMicQ@{blpW@F*~BpG-RE^Zygpu*?#$Vjw+Eh}$o&cXQ;OgE4@%i-_n)jpaoqn< zQ>jUkuhba0rb*Tlk2tK-2RDar{~L2JI#$5yDu~=6#sbXS14xv)FJo{8463Nl7>fj*0JO13&AI> z*>>v|HFtd0N`JLd;COr)Gead#(hPy3-aOos4ibL_=986LK5vPpjqYf{tG#DdM~DfO`BBy^`EZ_NT52 zr*Dqm_WD8Sq0i}sKDzjUXCU1AKS?m}D=8JE+VSTh2YIGt3(i97>Miw-O^@$JAgtZA z%D$@;TKa5Fi`to`;W8!;ac*BM^wI6ktiGgHZ}2s=*5bVT+|)d=x&j5j*HmiL9mIj` z_s}0Gdj2J#xBZVuyeFFGyO2=)eZc(YNyHyWhp-bP_kz`5rfofjwRhdYTz{%qa67b5 z<$mU}LN;hB(+2y$T3W7sWQlPM1qbIGcD*h_ZDED5t`o^*Bw7&sE$pE+og6RTTix7X zDm|tp55;CH#8;3OUlE2t%(45%q}HMN6Wac6$cH1!hNDLODcZs3|2`%9sdn12rvUj0 zL{H)mj~QcCQlZ$cTX8^#X|6sqPMihQ9{ylGQ3=P?*cy2W8PuC}?)&)=@|2tce6yn5 zsv}RagBN)6E)P(v6E7^K9!&R)c8zQebPELt*5%98B3)>H&4dI0tjA(ioglnpmIii+ z+_K5HriE2r=0zKxe1I~(ySIVrN({HN=)v$FjQl1|sE)_GIwEp}i=qKK)Qq%D>#k1r zO+{=P2JocnF{su07*iEx%}#UFc@y7@D8_`3sUz^TlPF!(!neR(1UU!)M~p^Ovenl7J}j&jrL`k z33&Z#1U+G63z&@poY!|yN>o=rNvKSGu92(O@)_y{2+%Je^zV%#yBv?U-g5O$R)-Sg zYEOp}%f`lLyv~IQ?@70B!|0Y#hB%N%n+HE%^nOZ!Jmn@9bUfog5tQ=5!Dw#)uexud z8r)wC5eP{Lp@OvH0TmsL2eZY0)DNPzTDm~0SXTJk`zgKj%A8yuju&Cm1B`^f^p_rr zk$?&TVkKZC?{kWC&|RkzZ7;vQW6-E)Q-CcOO(kXxAJGZ|mFMn9JM0%aB^ z z{^msnMsIT$)F2VK{ii=hOUt7}^E(GB>ir+$t)2&gdrBq(G)GKKEGQ_5fPf(IF%-s( zbmmVJ^W6Ch&>>j9Az`HSP=UOFe@4>S_&8RqN&W8X5^$<+1l&hB*I=Efa*us*`)&O> z)hzQIzOwpklV4<$zaG%M*EgOuT7Q{R;-h#`0RAQ$i+VABqKDe@PJsi@)y00F#O1+2 zVd1BNRhE`a=>1raJJTw^XmDNxS7jk2sxP(9wMEZ|x2t5Hp1ybZmbPr0lZlzcD~?4* z1Eh5D8_MQtHa0*t**CF3w3cCqKtK(UgfxU&eGKTFtIvg~wVk`B>l&n9WaLzi8)RWlPjNWc z!!0+zbKI6cMJUa0eXOH8Vd%>90Aa-g%kTQ}4Oc_sCJ?&N1=k%An2ZZ~3qA6$u|Ea8 zsT(boXpB6pf0;6BW?yVg{}16oQ`}d%J7+OHB4G$1$SHK6+)szGw#7Wg)5pfqdjg-( z)iBSmTLgZZ4=5}sb6U@uK2mRnw09d6Jx~>Z62|Z^IuQS}b>(yKAsEYqfp5jF!RmGg zUrg))OM@OzLjKYTd5Ge$!B+8)hWdg z&crMJ1RB`m39jXeq_&$K-ck95Ys)rCL!2bHTGvbE?iG+NHyN*KhoODHya*rL#Ws@< zP`{n1vL@e++p@&R8QzuM?LJ7~*@JIZp7GTcmGNi7PQWE4Ff?Shq)6Jg&1tu!q00e? z;ltoapA7>oe0MMxAU{)UBvU^ z3{~D$k6y~pTI?FU`zvE5oO#Gjiqk}{f6D@lQLv_kncS@;5zvvva>2?@$ww}FZxoG> zO-(1LFI)?r?oIn&ZmX-P%2lk9E6q??-{pn=LqbG5kEsHZUmk#{L2?!tF|iRp@?4N~ zkSdE-;cgBA_TjEGPij0qO}MWMznlAIogkUu+&8i7L1Yk^sSi)T~qHEGaP6lAkk-!vAx1dzgO6tK9mnx2X) z!&hb}!a+PoGj?u2RMR(VmbFyT?B*C5=3t@?5L!y4$cQfMPkcIEe{|1Ib|2u8YK9)V z&bvqWV3)yy(AOoBQh&aS>*r*Dil$fZivm&fDv07qTDQWKMP(=0b|^X@F)9slaBUps z9YmKTE`j$5px)dM(U&zKi+X^p+lY5TvSlQAYhnr@{?;HZqAM?^638XGylz9hFkepRQ*rEl~ir#Y;eQ>=`GuAKy9CfW%*F7>E!p5|o- zOw=#kOPuY+S}vj-s!Y-eLY@ZyUzWG-c>j4=X_}l7!G2owKMD@5S(BOF4I&7_&XerU z8RvRbrNz$PCRXoWgj_W#sYzTL2lebi1}KnbhgYTtk75$C+G2IAmJld3w*SE(`4&1h zrc?PkEYzo+?CarwGDwzsaJkz6qFD+8P3vaH`CI|BA7G!hJR_R?EBch(=^^`8KB0Dc zr-v7eQ@|9VrDaJYBi+_ilbXJdVxl-GuuCW=#Dw!A5HiR+=vH(6R6z&TLJ?Y|pg45r zj|YXdMeu+lN2>(B&L9CF824O!OqqzsVE_ulxbrZIb}9V79r~ zdmohZANfUAKlpd2YE4}H3AX(*0QdwBW_BC8lpWEo^`U#3J@QWBbf>rNLpe* zP~|8V`KYMUzixofj@;wUPP$pM#GPWy` zJ200-o`@CrL55zB_?SI6bJWREs81RlMgblPTE=`_P;Pk4j@Eeo2DYR^z{_wYdoCx( zxR?X_A|RjyolfD1pY=vPKR<~;^M~iTj^q1OrUI!8^8@aCBamHPciJ(@^H)31Ti^iY z$`VhD!Kdw)HJ+BK$PhV)>iDm{IE&=HHK}TP7hIEul!h zMaU4<^_d7iu5w-FL}$Hg3AP1ghhwCABxGU#1h}2_NJq#4uw=*7_C1B)765v0^a#CZ zCl6i(6RF2kri_Rf(4oZFVt;7mC>ho~$Nn0q+uKpIzUJcf(({p)pe1|}M6;WiPM&xEe;9s2(2E_d7`Me_XZD5YlpbLcb#0mgH1SB3&ej3*vP;8zr$q(yQ{{!RC*S||dQaK^U>XmW= zs1jf^{9Hg?D+j2TmI?4%i3~{ujnJ8{HSNvrbG$f)ZgA4CCD?S{9B%Bzk>x<87an!m05p_ z%aT%c-M?mj)svZu7Ixg*M3GKP3ma!6))ZLNr$*$^X)|=%=Wl5E-$_0}G@r5g zGvpfdN;5x}@+K((cf~q7z#PGvgjC;RKi+NDW+jriP1jN88M^G{kgGY~ao#c<;Bb6? z?emm;<6b6w)Rsci*21<%hG)*>B+vI``tiS|f&M}M={cfM>W`6Y9QE?wt>jqKLqZx- zJQQ5=^|8`;$G9!eZ|kk(=p=N-_Iefgt)VGZ=)u9V>MbA(z}tOnR6`@3)G|2;>!@o1 z%s;wv0>T*JWv6UQ)LyYs)jVZg-K)Z|Pu1agNOXAG-te(7^l!sBEDsh|_~0E^hl*;S z6A;CdA4=`Wg3of=>_p*t1Q6Gm16>ql1LVcRX;OuD&oR?vlr_S9)KL(m?=0dv4}z#K z9%m>T?%oFEfK9WsyQRws@r2XVq{J3v2270w(=N*dF5n_*8nMHk)Kf%ZcS4y-9|oJy zG$C&RA?YouY$9I9BaEKPpinvJIe37aw*2j&36X~M9!@@0CJZ` zZeo0y_`>A|`WZ8jIv9hYR_s*uXHNFihS@HD12J;BCgs1@hKfR0uFkh5oEKySAARbp z-`d@EQdci3EQGGqjN4O?ljBP%PX{jFsO+3ru%J~~^Ai9KYf&xtQczPf(Uz2z78a_gtA|^WQ&8yY=m-l7 zLsx$0zI1Z~DMtM=m1P2O6%{`UlSFB3jQiMe4RUjmV_u-NC={*)WRHFHSNHVXHNM$R#u?|EUF6SN{9R=nj+xym|BYRoT%hNYO z$TWiA>|aI%%Y3M7BbrFsjQCvJob&2toh-ty3pP7+%;<%A<_CqFz1%%KB(6^8$K?06 zwzeWE1i+t*3R4yH%Algx7yHxKSI4RagYg|Su;&k3tpebd=RLfs5t$o8qkMyh%gw6S z$JbX4fBSXU`^VD`5+1L@0sC~8+pj$&v)bMffi zU_!|h04E%*&QFK~g&)t(zK*}h*VlK#jOR6;-HY<5sHnum#P)V+TAe0uH$%heFJIo2 z9`;H$29gLRimmeSNEg+}8AGy3H#`ySlpk&W{wZISm?5-jf*o zxN5YY6joQ?=jyEJ=0=<;J-B1Zp8{Cc^YT~pWUou@Pt!01cD`cT5q$jxq8kDZmq;+a z8tdvAVk1B>Ql2y0qZ~Yf-+@%p=j=p4XojwwyvOM~%3_3p;eawU)GR!bM8eOf9E3A6 zc`|z}@*?t7ej%;hPd&4y=LOQbsVq;U%ESxAmN+_1#?KqDGkl_<5N&d$$QQA{AG8!2 zoUSOvOW7$p7*#3hK8rN31xc@$U?eQLX|C%{bEp!UcX{%pq|eJej`r9Au;cG`ce=C| zQZ>31(l8cqebgcyhnFz;%GTA<}X?M_Wtw)_7yVw z(nqN!6}hMteD1zJY|EGwtE!%$zN$86n|hWNy1OJaKTo>3U1Q*oNu}#0A9*j((Ps!& zq2WKB0&h2=YYqLle01$j)8ZNPSqArA|BlteZI_DDKbD$iUj_@0@6YrQpvX4=*rI3< z+DES^rc0TkB8H42Vu5X>+&4+?T?H+e#b^HB!y z4B0XokTXK0Vj-mUIY$D@F2{j2Ej;SOUi6*%VaE5&PJ8W3Zp+G!%6o{y;lvT^YYek0 znw5VVMreSVgLs1CCkOF{RTl}_Tv0%(bOpLQTdF2r%=A3h8qwfiP@qeuHua3ro`T4o zSWC?*P*kQH54li!6F%glM-It(7{Gw8=s4Bnt?afyX54j_);Brg&%r>+dE;8M<&Bp(T5{#cIki7kyl%7GsoKs6@ z8rvNY0);D{3i#2-VDk;8C|LAb7>-w6wJxTKPyaa2SguN&l4Thc7zKJShSQf(#RLi#R!`4NB0X6)|tk2mysWLfdwh3_sYIDH; z3=Vl=DiowntD0KY7jX9<3TJ1#smJ3R3 zs4dAr^ygec_w)Tfxgy@b&bOT#<*}38ZkgJd-V5^^Awcst9uv-<1w2 zuO`L7(8ClT2I^EgzPa)^h7jSyE%B3Ou)ExERv`dZ;$~9{?)}eHogMcdGGZ$Tm6FaxgH4TFvh{ zsG%@xZHYY#gDicI^x7ZohYFDbeRc8~_$uZa(Xq;HU*Ex4r?qxV45sHiB>Y8K7|x`{ zz3dm@MLIg$2DMSPp$EE;;8_B*%|E5&f8M4bRlTAQm+-yQ$W-5W@`EH_5{ zUjF}+i#cf#%kU*dOO=2%PX-nS;9UVj8Mwk6c&8`UF2g1tR2Tf~@g&Ye-JpJdxdXQ-??0285MRhJkz`H#V@ee0MxPAV4#eXD( zjX141;5B^5$PNS2$VlA&3`T`($MhQ$1Z_S zme_Fn*Zw*D`q0_)4Wrmb(Srbwc!x3sUAwqUJQmA^QSb~bFCY7%zey>H_(Gub&mVa&6N{0b*R)W ze9@YLgAoC4YGk^+b)G#scP_NbNWFLq#M72AF_^W{(vdrOdDK>`o&e#9j@XSP}&g%GLM8`&GmsHyPgZ^8|gjbG3no< z%)BrSXA6zkBc`fBCF-F!N+t8Ov&S99#cgMavxIF&{)SOAY2-txNRJ)+1`+mmbkE;a zjtZ2WATc%VMIF7T5k>-d8MU4u4^!{fn#wgaRghmQ!-Fp??fF7GVLW__0^_e76r^}F z%2T%pSRNk`x3|i+{JRTz0BS8S#N!Yk6tVm2m0QF^NP%|?-1c<+Rwyvu&X=bfM5nkq z=VnWfOvPRdvm1DfeC12;0g)clg4!D2$2Fh8Gr{{+=Cg!?a`)55CNG@AR%}h9y8`om zpI;g185eM7vFXC=??~!N#U(7ZgM%vWwsb*f@`xc3Nk?p-w8T%zH#1KGN0*ST0;Dix(0NeEgvQb6fi;wCQm0 zc7&3^-$yRiWd)sF1v20cc zkXXbLGS|ASgW`oNe;@R~by%QKRpDWeV9GsE8KDFg@!g0c$_-K6h=-O$buKe%%U=pE zbZj7mL?(>=dW)sb<7ImGJyw!zu&h7+gKPKN3GG+>k>+etH|$KHY*#12WX#2o41ZKS z&?>y&Bo)_3vBk>2KY%oJtoh2gw&I2@npB=YDhgV!eqB!I;J? z%d)wD0b)624d=R}Oy5HwRCFTq?gf7S>_1z*k znfhw+O?Q52F+xy^Ajn5A+PYVE z>gjIv$!Z$%-m{qL76TfAqzCxW@8|~@e?d?%@6Brf7+V1ls!H?$ zsd~dkWJ&dechYM?c4TndtlfCc=5)*TIpERYbRb*9r1&Y;URkMD`CM+2fljUCM}yoz zr&>XHxXZaGLyRIlp#1mQ+g@+NlVy(|cpOF_+QjvRuFN?e0|X2}m-;?m&rnaebrQc< zS|~G1Zk)1BI4z z18vl*E{{HJP6?<+;H=%oINSjQy<1KYT;LQZ3oTDt48JS+|839dgf^!t>4=&gZYv}%{ zp{An+yiWGdv$FPleUB50m1Btk)e>asl!144`196F%+Zv(H76m!br@;9(*3Fi} z!l>xzspygnQ8Z;_yiKEJ#&?TGR_ zEHV`HJ=v*uTL)hfdz{dwiM_m=7r&bjFwpI=fBd?i_tWxwC8S-+9lhs|~ z<~t*}6CXV62x*Tqt%$9gYp?|g5$&GJ#UakD5pBOiV-Cj39+S#cvSE`-ZEd|(a73X0 z|5b{7Y{n8K=Z??;^2@sI!R!5yfC-Qw2g20l)b>?92!xk@ECnC4-+zdTkKgv)-`Lvn zzu4^nzp5o1CThzY8g$jwQKy1YH-6l5eW}LkhA7gc1G3jgtzP~q(Y!R;xE@GcKXMi+ z)+WZ}qiCH}c+_&_g6Yw8+;X*yyuMhthAT9rmz_)pTrM6Nwwxl9qAq9xB5kgb)OnV* zmPVr7#MI%8*{kyPZ!EK+oR(>Bd z%KLB*Ztw16U;Uv!*zl9~uO5yOi-hRt=rnmoj<4JiN7n4bFLPD2o;|duG&Qa|U4}fh zrZ5*Qddyh$qIO^P$L(h_76f$tM%P*a*IIw-21i0oRMd2X6$V!9N_PiOPRQXU-HXn6 zF)-FR>bp#|+THQXtb)Pwq{G(6pEGGOz6_zcH%^!QxXe5U~T{ki_AT>K9@* z$V<-N5|3P|wpqs=(dqvFa?jm4a1sC<;3%!Ah!yvVC@L?HSw02wBG|D_7kfw!4vxsk zNbjTv`!{UNy_gKQiXX$`Oc?pz++Gb zfbZM*g!UI5i|O(Za2U|9!6l3M+Sz3tzU2*ter7p-@awla2V~pvX1|6SLjks<+daGG6BL&LgkYq}+1;FAIf2|WrKz`DI;lyR!?Rk}TpxRT@ayOX7|5KZ zx@)l~>O~r$`@~pr6F!WD1f4Ga2$fm?(&tai>=N&j-q^QURul%aNQ-YC3VoOJ z;fdcBX%Vj+#rBLhaPoV;khRN<{#s@ZVhONT99qou<=hC zR6{>Y{*wQ%L_J?bQ3X2~AvY|7CYNR=8;>lNi5axz%wtWY2WiLbb}mT3p~_|>L%lHY zCbP>w@bABe&K1ar60K64LUhBj#Mj$wMEM%qR4wLh)|BMBDeD}G^>4AH_ zyXJ(&BD+O+vO-=Cy?w;ZFO6vM3x$HSZ3trNKjsCso%u%E>_?dkuH!V zYGUJ%TXh}V%rtrK4}KhgPTq+=IEe3=nRQu@0do3LvsKGqFej2I*eGIC;^|+;vcGmQ z@TF=QSR2kd8=vRA6h;_Ry=v4Bx-^qgPDr9iQ&VsDBVZbW|G4qL(@}~%2Jo*+`JcW-Hp zc+;lp{V1%{idh(6K?=ZJ@SYAv8xO+UCdaOa>UXW1gDai*GRe~t2Cx|{C9?xK{`FC$nApF=j6 zr!QB2-rLN$M)s5(bNDjcR*aNhI#TJ;rn-^jm^GF?ulh5^N>XRt?wq6tfeY-e3@ zd;*WQay)NXVa~woyvfalNjG1~E8V$*6)Ur+j0hg>RUlP$b()srZ8E5K7Z3 zZ%?o}=01;>Ni49u@XK&`GG@sA+tpaZzhGC3NUBVLg2PM$tFD5N_|tlY7 z`R@)tfUwabr%Q+loh-wuqwrp3mjYv4kc&S^6QUqAz#c=I{M^Kh#NlL)clT%V-q3Ul z0sy^qiR*dxwJ5AGA}T}+Gp(~Lr8v1)W~^lzgq?Xpm?Yx#vK17zsqZDT6Oyje!PvIK zz%j(o1wp*q*^;L-3n^=mZz7_N;B>EeQlBCSh%Zx9j@%72s?iQ*mdu)>>HAJbi_a67 z-ffMy>E%p&=8E4ynAF45@%gl2@wDNx{ep#*745W>2I8q0N}%0P2J1bYMPLPj6H5IS z2|lN(Ok1O${5~LpCNLZ+Q6=QE)7vLuV;%Q%QIB8VIJnx(MQF9MV18`y0D$O4=aRhh zy=i>XO*&W`b<^~(U-@qHaU-_K0gJ-r#RjKHe)9~due}7a0urBfGs{pioqJjwbna_{zOaJz;Emts7ycpyY7OSso1956yK^K~tR(S?R3cUe#^=k`*fYZnGCU3{$u>^;hHyNpm97H82~ZW6 zh9gVV6Ra2)%<7Oxe6&f8wczo%5# zTv^yY<@8w=!5lD?J5U2Io*1NF6dqW^y#dt+3DYX2B7ns!yKX--D&Y_C;&5NM^Cr-U z{Q;P;r9VIYsU;{sDK!=z}%9x{@XtK7(CuL^Gns!onWwy>RO=rk^xAm?^*z1d{r_-G_y*{hJ1T3uVI@ow{tuGl|26Z2=g zYS{Vhs@)|QdXJ9I%1v!jRRUHc6WfRDB`Ie!eyPi>6OC;pC${MbnUzvL!Q-_HbN?Q7 zcvQRjWcA*_LD*;d@J|Xh=6?_>1kPaHCVb;s;<)_K76{CC#;j+843l_*;16<~cN6Peh%o)bpP(9_K+}txip02=Z z+XhNDZ%dmx-J|CpKa5nevo$X%?I9Xl)knWNyT^xhCn-}>zS#=O2V{^R_hl~P)Hhh6 zQPgEML*SDl&v{g9jKuX$o|c9eE&dBBdyF?DTG?~uj_J@lxe6k8>`PPIrQeMH^88E= zg&O;O62^P~{(IEhjB>3AE6pC`a0D%KE~TdzUs+pqU?ar&RQgJIJkR(}LxwJ*uY$(r z&`pO*qDp3QrkIOJtT#}WHyhadRWg}1A{MPAmx{i`dsw5tqT0(y4DH8L?NFd={- ze5UzPIA(h!OyS>|n0R%1AffG*e7^0qFi(A^tU0sEqR)3)Ih6|^U zDW6?tN>qb>*}*!guwbWBylmz;6iD*OeASJOSiwF+VxSk9%nf0d&Qg$cJ6oQ ztSNY^Of!{_t;6jjK0)b24(W46km2?5zp~ztmU1`={IZI8CY{+(;W+v_;4Rj89>%Ll ztoKBlJy$vDW!i#oaCUv6F5UvOiA_tC9bxBi`nUJ+N&->KIV^ERvt`(Fy>xvZ5@(yv zM3XCizB9k^tfyJSD=7{97#3`;&Y_|Fn1~Th(bi!#C3<{zrwr(Ww)_=VJoR{Osd5e0q_m|cmrjU!}{@&P1u1n zvo-u|{@}-`bfnx!uE5OVL}8W25N94&%0~>m1ir&x+W~tWqsOD4aO`|$2&jD_3ts^X zW5_*2+^A!QkK`PB*EsDTJSP`Wl8`BzM~?oGR~QPFdHh!Ut6nnnIi5IqlaO$pn%Pm1 z#t@%m_`N4@Z@G=;YZfIGN<+d_C_*2~NkVdN#mmrt^{@FQvS&;jIU*pd93LU-0FUQ$ z6PHL*%nJz#;mGF0FIpGwaDv2Z?t7gM3mND3QXGxFCaB~h;&};w;Rk+-&|dQG=Tdh{lh5(+I0a*yuDeZFJ*AX#px$GBOH0!cZK1R#KOy*gc?%=Q z9SZ?Fw8UN;uoD|I2e^g{W%%udZEvq~Hg0Z8?GOgga-p$O?9S+h=fw zdp3w?&HWTLa`?6;!e!ARtH(kc`mepx7umgQOJt}_x)z(y_s2S>JQdwU8m3Ks$DyL z``f!<7Vkm$BL?M3T;6rH&)989Ira11yTs|SQ_Nj|e$t*!L>O&WMorjNec;f+1-Jk< z-AEZ5Xl28XWRt7=J)6+DzNL-63WEcQbkYDP??o%mZF`KnRlXii@wJ2d>y%t~tpz66 zH}?r_A@kB6homa)zuoKO-L~WMr33^jK=u9lv+7-mV@&T*No$W(sw0_bP?>pRJWE0G_HEtD1pvCC(L&hPGoT#&n<8qLe)0?`(;)Sk`n7x;?QkK}rhceLX>*G_|4Z3bW zLyO^V$@*rV9l!PMG7;-;ugoirr7C{;6K9^X`h81iJPgCvp__GddSLu9qp^pz+Atm4 z#xX0}nC#qVv=?dKacU6rKCKx#xDDOtp>WstsTnC~3>)G6&h#B%X#0;<2D4pltL>7s z!;%}VPk*G6`kc%@ofH$c53Ya5i>Pk|yaqZ`w?U*aYydV4%UCwkZx;Yd-q^jzK6ebJ{_?pPrCTjOuJVx z4)2EH2YJvrATnmpo*#``jW`UX>DK?pOT8+{zwNp8VB@ysaAZ0yN-PevO|I?3&&JzAJFT7Ru(1F|imGt+$U+Ia*2 z+AT6%6_=BFc3fg14Y@K*{8vRq*WT^`(cbAViQQJ@FXErXBfDh6R?k5tD0mdp`-UPM@Y<_hYH0cPt=sLj~@e5Id z6{w~1=|J{qv&6Y;^<+qQko5DRPYhGfrO9uFbvX0Q@E*Vm(llN7yPjvu zukYJ0EUyI!I;Q!)>s;EnX=^wLO9U4LYZ1<(pZ;%}1y*S1DQPf5!GD7?#8>)sIz3fV z2d%|apa)~;(y`I zc;U4RQig?vb;t?@h#Tl1VKC;~#_1LYH#NR5{PN92y{I^lH?DMI=YOj`in|T(1TPb4 zgC7vcPPM*zWa??pScG&bl8NE08wmJfaZsDgV*j_>2^#dv?J$Q27q`@2xwwT_R5bCv zMI|r}cCwzwr!}*6KN5-(WHLY@sqk(~*R~JKtxaYosn5Rxj#IvW1e8|5b8~!+v(YDV zMm^SJ$vZ;ir@M{D4X$6gt1W2AH>v3k_elLhJEmJ7mhz&7$!KePjW_jOf$V}iF9ic= zw#B$~XEXcchQWi)U!%>ZOANjr5)&uUcl?T$n{8;+L)LK;#m!9?w5i2w4j={gO~qzIFhze?IE9P*bs zPcJ;tD6CN%%cB6~fxvb~;`A;cqq2?ErquI*3(^tVuP#p-w;&jwNftx~)#io4k@uD$ zH|FA5UQDsF(YdyDbNj48i-$~|ox)5pZu>U2?|@3?Bz1Z_?sJ|W>dRwm+W#l9Kn0r8 z5OxGqrdX}V#&V}O=p)fx(=US=!bvsaXaZ+0er!w7U7W7s`KVo`WZLwRVlVq87o{d= z@8Npv!Qu3^`-i!|PA4Ec?x5q6s~7zbflT)5N@;@}dfd(HuS{yd+ThU=))}Ir8_&9a zGPt_T@sHddcYD~BM`r;1HPLiX6gDg>I}2kr>bG!T{gb^LSBC4vuf)t|ZT&#NjHRDzZ6l&)&Tk#t*UZc$;2r3in6`Ceo(gr+uvtFO**tCfI(CG#nR4+8M{OifKqOj_t+s!B?R_3oQ3uPnBUlCk{%pM%ES& zZZpx)(OH})=X~g~*iiq~*49>7(sa}>FccmYrR7SQBm_T}Dy91Bw@)6iaq z8ap*nc@W<5Irv|lsG^>79j<4PgTkC=Ij)I37K45I!&BDQb`#i3<~?!@#}JCEte>+! zE5dAee#C4}*F=SfH=G_!FDxv8Rv_{$H%3QCadg|}od_p;Ou41IyE_07hi!g>7A{c4 zkNMWeK_rrZMG1NsZXfQgXlo-yrR>H%>1KLe! zYeSSr-A+G;+yp%)15IaDgpwh&!v#Qx$FixSn7|com(>5h|Fak4zN7iWAhDhu>=3uL zP@}`)9R}F@M(3?5(Sz-Qz2>7%zq2FbJ7;}l*9YfZ0(c?#6%!Y#(5<|sa5KxrDSJ;VB$i@YI5BN43FJ1Op5{fHtBW@%Z43_r_d{DYeW|@nz^7$&_oA5Zr)nBFU;bxjkZHst*%zsl*)Xcsb$J8?}%8+2vozt?Wthnp=YPHYJ$wQz`yYVG-;0YmYX)iz# zqLJ^RB~wyT($Q{W^U3$ZlJcuFDpmTH?enXm9z+2*aQJSe&ii-E8)n92%l~5xe9|KA1&<* zmk_Ur^y2$CKWSH`r1bTOa>k|v;|yreI_SuiUdhG8K(9pXx4V!|qk>H-xI=V#A$ro} zr5sQ1)jHt^p_!i-i0K|q><13J)ftimwa?bz8n@!5U6@2V(+`V^@v_>S2Azn5>!D zx2;}p)N{_iYw&B%f1Ib9GYS_r+===etwa*s%@-bcpphS^#40`m$Y-D~;znff^?)~~ z)Tm;@H)PiXfO#{R2|(^>YL?V%aX=*03J;c(X?S=S6Dkc*usC=1ny``LRd@9%Zk(lX zai6ky|3saevads1wMq%^+sJ@5bNwCj=(tTak=`ERf=9@9PyQ#%*emb$aBi3JC70c=S~^Sp73EMLB;Z+{ zM3Y>HrP?IjGiAE2wwbJen`FGw=tm$hn5>WHX#YkQ^jIEx9e9027~t1J%DD^g zd9xnE4BWSG`djg%9KD*?7Qh9gAd)tW-mzUl&2(HVVFnWn$2wkS);) ziwASq`B|5Xju8J%Rx}eQF_q%PI*XqXvqqbmZ6rXk5u@&iZ$?va=sexzKcT+Yg~z>J5woE^I`*?5%%V74DRnY<4)x@lG>2W@?*cHjPfGFacvzZK!T86CTVn1nMVXVtzg`1L$I%LY-)bgvY+W(AZDO?x~`# zRzs^sR#c*oZhVrze|xzdXf#Hk+re06!HH|rE7_?E3_@R}LT;KVwUOV=(xsZuz<5V- zvAsS}n(u+&gX+yRt)JCLMrF=i90~xpB*hA~{*l60Dmakr3*>uOr6LeoP^(j2DJUQ< zDJkvadFEndR32q}b2w)#A-XlXzGjO)E)X;+uv~fE!1CnV*WdOp-MvBItY4tA7K~Rk zI0Gl95X--5Z)^L#*8(Nd$F!Lwtt6KpoiQApL4IuSP%Wmf75=$xC-`o_AzuYSUiBj5 z#l3j2*C1`3VL}~Py#D6yTN%beBjLiIFwGL$?8Vc&jpPbFB^M%2``uj~Wu91}5xo?e zX;*Go_kp4KUoBehywv-xf4qa!Qxu+*bDGAy`*rhFy!zMnOxhIxO!J4^`wCUM_jeh2 zI65_CTprR80b$qF<4w$;F-n|Xt#TTw_Q2^=D2?z?vEqWufxjS+wY1zn7wK}s6HCGD z^ifYgsmsS)el#yK7>d%9-A>9&7J5j&H$MHZn?FKR58UnYoP^$8!}~s;5dOjJ42XbzyHL;c#VJKwnMV+rKwvmW#0bjXApD4v~*wI(=G32iR8gSbdjVAl!;+hR0Jhj z<+<`@bu|ap-@R;mGrId)nVbZNTnzQSOhcZacYmblClgK%akk$@Rvj`f_0ge~l3sPi zDt9GEPtrjz;hucCyGir`=v@y z*=m~IWFXT!b{T4fIBW%UB|**bHA0QIqy~K%*jkZ#TNBNo`k6G~7U&Zw%Tk@# z|8EMu0{9BA_5-V0YBlcPk1sc+xoyd)***EoRDL0GG;uV5mblN0A1G!bwxFQcb}pxs z0QA8fsLEABQ~BV8j&e-|fDplGd4dmI-o^->alyv60#9yBs>7Hs&cg6(m6(m9TUHZX zub)7H=a4cpL$y{9Kb~A3TP`Kb7bRi%CTm3D$Em#US0uo3d}Y+eM_iR@v2-uqk=xfw9nC3m4VtABhv1Qv^tjJvJ&M7s(NblHX|!LO zdRl?xo0iG}ZYlGd?%mFDXU|fmrDH!h=I=zNKJnKIMvk@Zy*uymoHC1Rny(f zO9Uizl3tZ1B_+uNpnwAJl{bjtTbikJT6-;@A&{AcT(A^fE&JJowSmBLYpT%!fzA+{ zu6a3*sjjw-N49xQXCO+Bd;8NUveSw9imrO2Y6nu9De6Ph=ReEuE zd+6tE1QnE$^8Nk8wE7~5UdICl>-QC*NQbDhy?~cKT1rq) zQEzS!OCj;(!GUE3z1&O;lx6p;a?jU~H&EMY$22^zpVUNIRq%-?_T5{Eglpm9O+8Fl zGUWLMI_}&U{uCu8CMDHWTFS@AM@EDnVl&qSYPvSfy{Bs2D?niYh<~qvCn5;oQq?+0 zE=#IWm!t^u33G0~wAcz!QE>rj7z50pG<+v6Zc0?p&N4jxk9mYMNPQ-xqoT~ZOvOMm zX~8gnkM{z~8q1j3+WP3A**56|z}8JZ0*GWo6qK)tiE+qBSO?%9_i^Q_A?PnUAUqci zWy)mVm0$qgee2z1E30z`yi&?uls<$Q`^D|?C^1X`kt?YfpIOx7+EJMqub_WlGYRgZ z8xp0^AV_gXCEz$yM)0<+1bH0c+si6f@nycgV|O-t<*a_`izqm`Lh8YT-GgZ-z<**8 zbzzB?Q&Ls++X*v1=@f1%v+PUQTN!#*I1a+~U;F#AOwr(Dg|Y|MZ2~X~d0{9m*UG2# zo{Nh;vDj4rX@DtRY6~FL)YN=dVgxfZ1f5mDc>qZ{>qj9s*@)Wth}s)c?S2w8Adh>J#cG*{y*HoxW0yCsf(K*)0suB=#BR0w=n%U{=ADF*FkH)n?Zq*CRdUwa~M`KD>UO-B?Efwv`F2Nkj^PkZbg^h@S$Ytx>fUAqk?oJoOki~b8#6F8~_f$ob^zeWUfl;17*$8%D^s0rW({2PU~rT5JlCd0EEq&@n4G!0MfiL z&<$lO$T9@z&T+mth5XEQeQi!)OI`eXCXZY!50$Q7W8y8R zriF)J`r-;@iY|G%#2G|##|xbCq1)*ApYeX0l+Tg1^f3nTFfoHg7Q$#8yoQG+2u8Jb z1}p`^wL$0s?Cu^WkT3*%Fg(N1n;3s+)+p%KNvM@oMu`++t3<0Tw>oFiPo6QT%4+7@ zY$jQQqZP+m6&&J+6uZIE`Ul|IF*Y)g^ zm~xx+zyTYP+ulH32Fr*Cx|8Gzg}r=%80CSF-d+eleZ~|TO3w4yl~Jm-8m^EivHvcpsmEfD=s$8QyOjInl6GAMb_K#}_6^ zE?SM3x@Ko%IuJTA4!E6|xB*%VPLTU;65xWLv);zvb40qMogWA>eCr^SCS#a?!x(+D zU517Fs=k5ilVYhwe7qtyqEA9mf$ALbG&z+x7#GYnF&+7>>sQ+O5HPCCj1&NUVAS~p z6spiF2-uWbDyijcgbgnNwr_+{-W-2pFv17f2##X|k}?W{Fa&aBYR^pLf8 zeAllGr;>@jy08)2ANC~`$+@ao*pbc5|8%Y|_H+os6&4x%ZRdp`O+1S{+2zQEqCAin zEs1)Z!kScV=dq)P#f6*eIfMtV;Y__cl!ci&TD>&`^(15g-9%SD%S|SEA#vsy{OMck zd;Lx{IaQnUyP+fch*>Wodt{*M^QQmgxLp;m)VsJC-~zV2oKzrp5^)~@IbU`D8&}^# zjVNUPX86X!4{Bi11G%MM0NJRFte^ik=leg#r#SZoag$R2X0)Mta4@%Rg1P`P8;qc@ zrjE0Z$ms@5+%izWohb6{VfXTwl}1uOW#R`+&%_|@+&vFsZf7AJtBdj>gvm&1*v;Y9 zFFZ`Z94bJ=o+Tb^VGWvdtKNxlqB}G`Grz3urV=J~$e%OTgu6~%c%wjQ@uAq3D@3De z91o0_C*M`+AXl{`f)y+~?n~BtdB5hN)XxP9EFeW++r_0i`!!rAHOw)pI5pIS*QoJU z+0%}z`x(FHWlUbZwa&`%iv5mL|+pI zT;`!8!Y5$9Bu?NiXI5!-o)Q`G`Jdmw16;Bhklqefu0;1j1=CZ9*7cY;;%^;0c4(B{ z$P>B40v=*#S(VvuWgTL}DuA@r&u5y@La|n7>B-u?c$f4VUR|J<5=HuxSZHRB9#&BX zr-ZT9zTo&!kUuO7Wuhu3{o}lYB_SkpbE2?&W5;>z#Ote9`{8)D%l#H%_D_&>RbKYn zoW)i*k>jCW<~#cB{C2rza4GglYr~2!Vz-o^vo?Qgsq!%uU{cuWfiex7Y)o}CqW&ON zc!8BM^6FK^d9X{pLzF!@HT~Mu2xip0D4*7m?IE;;!^y z^Dk~rJ7wq|Ml`$N#_)K@ZKiSs1L6C2Fr*GV{Bh3!qrjJRYNaPJ&`KW@Hu5sF32>lL z^YK>wy1^&*nn3hDMy0+>6VG|l7H=8`uRK0Mo)QC;CdU?JH{q%#B_g_1**GMe#=1Us zgf@d|^?TGPNJ0s#PT>9-N|We%adCl^=S?)SdK0if#sgzK*YE&3yY_>E2PW4X_u-j>?(v z1Tqz<=_4j4osFYVge8D$h!45~TlGC~K1Gdw$*Cf>i zHPL{&eDZd>zwdVT(=c zyTEu;Fa*`pHh+Tw%U>HCERYNyly%9Wzvh_YaQyH#edHc!i?RYI~X5L+Lb=_c! z-GD$|-?#6+G&KL&ns}uo&f%zg`8&UJZIjX!@RnthVApjWfJ~V^frop6fEPWMByDSC)JH;_kA^rLe^K#PbGY{!=Ywz0 zHtze`q;CwEnZmfG7s zs~^u`pwstK<|25mWB_il{RjEKuYM zt;Wv_kxN(_T@zl9`XYg<=CCh28i%r6?ijda5X^o{IXlw_ou)j}>~tsMX02Lq!d?sW z>mO?8WaQ&ov%SyjEa-+oIo8D}y51GcPfg^zHgvwZ`eik0_K-1o7~5l2^|~P6ew@EN zbJfg<&`u|WOP@a4Jv8%K?6Pv-y?o4&<}+4VbrvR0OmeW*a_)5Z&*D~^PxML-X5?-U zl9UvDpkoaU37QDlO)b~fcikb-MZsn<>#OYbxQGrn;Gg-w^TaFcyl9A3F5~O5=*YgU z^Tea+ed0ijqvE2Y6eW!77U%0;I||H4FdNlqM&`^9{etASNji{1*E-CRw4o1$Lu;EY zQ#qQAnaT4Jw$WRd+(E^Ggs?{8rKs#MWNi-ZVwqz_@|nQKv*UsOAf8jqS;XP^pQq!^ z--+Bu5eB@GoE;TZ*4A$4(=UMKXf0~wXK7iOX?{u!3J#GIu9V?t&_mreEiWJ0SXmEP z_p5zb+&WBgPs2_PF=h1iLv&(==e?4IK2!Dnp1KqlfdTQ7#a)Hsf_28oKF-6F-Bm%r z@oV-u)jHit<n%2jm-1$}$mFA_INm-Vq_Z$}PvXnp^@t7R}5W}_v1{POVRqaYND z^DJgU^KQw0eM}dYe(CkQ?l36D^~;{G!*++~cVAS=2CrOse3$8g+$e%+Bu_8iEl@h_ z0s_~PA%k$h5@%mQH~=pG*N~87nvvW^cHVGMD!OxN#_b~GH+wa)!n|<{L>r`#OBoO4 zu3k~n2rD3K5EZlI9#Dn`C}eQh+5f<(lxd7z_Bmv)CT4u_`1Snmh}p;{JREb$m-CZB z^wPt7Ed@#fMP)W=Dbm;KF`48^_-NA*nZqwWLl?tf=c{@nxkYW-wY>UeOt-e)cMUKR7Q70=RBpPzesme zo>sf6r&I^_{-faVifBccG&ne(|0O^%KVTOOr)(;?we-DB30kME)n9+G?vuzT%%9{~uXihhwJCAr?c{1Eom z4$U44@+TGmk5nCzI?J}U^`p-z24_+1adzpMwG?Mg69q`?V@Gn7G zq7pW4FBiTgmNEz@*)%hs2Utj^q)Ft}uk2J#)VD4aBLN`&B|jb3vEbetG7j%Vk|L&0|n|!o@Z8#j@QZ5 zy-XeX>)D-abk46|micO;d$+Y*?qnc1N&8*o)vCge0hgtExrbf~+0zm|5Y*IkqP#Ua z_U5$YM9*qK8n_*K*gtN^pRJteN(G3mf712;BgbYrc=eKjG+Bg#2K3t%Db^0pNYcxH zXJbbEJ*4HJ2L;&0l)==S`o(v#7MdlK|AO>4Q1jORz3cwThW^9s{Kw1hA0$_Y21763 zXHs}`SJxJ_YW!#DG6&=MbALt&F5mdicyYml`tzZM69;AgGmI`+PJcf92q&cCLDXOS zum5W~Vv&RX|ABI9cs$fEGQZEO#qT$gIHo}td!JAJzS%7Lk2~je$8hikYoR9TF*|8 zX{W@$STZO)Wr_wV?%`HIt-I<)zOxJ_jxi_~n{i`@O6a1?R)^S(Z*Cj$FK1B`$g%ReU2DoHb zV(9q{sqYW}B<0Xd3D9x+^~1YlS8*4inU&a?W!<@lfE1&&1&A+;1HZ}|HPg>eR@TKK z{QRxepgz_^*UQfCJ2-3b>gB(2jka`kSF;4kcO;}C5I4Xk0cgzNs(XLy@ zW91174=p4$$S-GPs1eZh%cJlo?%2!1w(0C;*GT4CBk3i-@4Hsry-KXDgD9PDDxURP z=Vlra1ZQplxbm$}w%*B)AnBQAKLDkcb5$5iekY=3H~F4pYxUqZnwmFFFWJKm%vJ}L zaY{-`oaNzE6?|j3PGE$PkkD^W*l%yZ$#+z6fGz#y?vJnScVeyb3XXRBc)@Y2k!xTM zkB*LLdOZ`B_@$)I+6Z~arX}4r27f9pa3_a3p*m1rc^)`1GJg1 z?Lk_RxV73Y;$=$T3S=G#J5fM(~F+U;AD^HdRdqB zHeFE&V!#=-PO_ZUSA1XfPQQGTtm~a7e^An#D(S70V7Z+JS`(fe?txiv@P7`~VyBSF zP-D5_xtbAlwy1jcHW(itA25i7B_&UPw1b1AK+5vUBN4SQQOS#|qnegn3h0*jg z1RxM@TY_rZ_5@K(vi&#DG)UaiwqC>`*o!&r3fUTZOV5+0L9uJBigw|+vq(yh1t_DQ zpEqH-fdTY6a4?ecC|6=%LWLKj19c#KXl!gO`nHMpRQB{JOkH0+PK})ccZS;WS5m+C zax8&CK_Ph>`MOSXjjQFhNe<$=w8~&Z=GY1)8uaaakfbIGY&V5Xr28CTKreyD-`~QZ z7Sw^6>^R2Ed@5KoO4(sI0)uQpa?eGR0k>y}<0`oc=FrR>+x-Z2?fBHU5c;{P&r-cg ztfLbXHK4qt&^Y~QEO#>1LY4tu(l_9yJZd=HDiNcjJ;w)fA##2z*!zJ&-&l zmHK^`T=Ho36WH8<_Zk9~QKuLcgrBj1?!?0Z<6v^6=tw4ntxQcBqvhmcEY97loj_$> zXeM>wuu*K%`mRL!&;`g2M{>UyiqVk(6kmzeJZX7;{w_K0*k`AP9sIpLh{D7)X6+Q$+7cY7 z>~lOlr;a#6y2S`FhUFK5Jh@I@{#MR!k5PD-D& zOY`*sEzN7xAO~G?(bFEUQY^|V|8^&wIrD`CJxS1Ji2)@gB`Yf{z0c3#hj-OtK}Cwy zU9wtG3=O1Wk`?awYSf~dHFk!^Q4I8wO!b)6o1MNhv8a zphsX8d;**b%W(B_+t*S_@iyi)Fm$=x0jCh`GX@dod7x=8UAp9M`wRikLPog&h0!H_ zwj(ek;d-#4XH_$Vx0BZwE<{(Py{4GRvpG{Yn$J(Xf~v$?rmZyZBb~^}6(3#0_aC+y zn;Ot0fb(pk{EMA%b zk3#k~HMNPsv!7+v^)rSZvIKiFQwksUwn)$o6X7~eW}%sv{ack-A44~WEu5X-EqM|X z5ecR^ucA-qY-2UeE%b_SfB2A=Mt|o{JWI%e$U%}Hx3ZnfjT<4C$+@|>8jijf4oU10 z)fb6G#8L0V=|lR#4&LUvVTzbqpPpuW%O`t$qt z_Vye_bqhz5a@=NHgXJdNA{SDm^r)O1iR#s)(tiOHBNy8RbPwlMnL?7~gv#P|*mFLx z4{;)C@Hn(l&_n%=Pv1nLMFnP=1Z@&`4!puGU`)Vl%{F?k*WmyC8W45C81g&YQ-Q&l zNhN>2+usAWhJb(|m1dNlc*vw(1x!+@Enf_a&&D`3{4z=hCfbyUr;o zJ@QAmRw7`P+v)#C%PGQgB1I-IkxG0i<=^K$nKIvp-*@Y5O>rJ>)sH`zYEAm`7lEsO z=!on_V_I68V|i}9RJ9juccfJ(?qCr7$wB*s`^Z~A_>i5QT|Kobr4lhLIB>Q;Ha6zb zw3lj~?t?~pda0n$xz6A?cj@B_Y4h!vnGX4>UU}cO>_lDP<;13+_dNCllKp;0%Pj(Z z@4FBo43yy=kC`2CGDZEq_PA`ypiu*$L8uXCpsTec$8ZUE`OBKGIU;44&zl+6HxBWr_;?2 zwWvv|rXK;%j6AcKH+^YKy(1vabCD3?Q;H690*W9u{baFXNUHB-5wOIqhRSEZqH{5C$0Y_mznUnUJcBnz z)m1gLg~EUgq-0^fz*p{U0K181TaSj`CHpuH%kyQ30&dL|9QTH^U=a{Cbm76e*H1xm z+i&#?BQNEq`JDJZ$3cx8@ttsU+RWj+eVRaikd&Q*m-Lj|*Dy$W{p^lrmOeimnmB7w z=!>EOs;VpU4!ns?aWU4iZnMTrSt|+g9k85n`D%AgNb`784I4-$A9d0?6+JiItoA+y z&Wo<%!;5JETipO9TAf?}4XvX$+-kRocA17n&`vB=`9oUa1n@V&g-LP5wi>p|+t}HG zF4SeypW^#09ANcPRk`axolP!5#B`@e#uB3R)P>2*#?0VCzKRa+Q zhq7_I@%SZ`R0QT6Bn)u^UPS%|DBE-~x85|mm4Khi1IZYjbyp8rN5}~1n{zjFG*@+d zsw!+C)C-&|+Ps&#-QVB;yR<=F`g zLS#J&I;PSC%xk2m?rCULv3Vjr9i5@RKCn-f%T$6$MR^foA)K4C*G4nAb zbk2L+b@MKfEVFD@`HQZ(&Km-ew;^ltKvINrxiIGER{eH41D9-$Hd4nxY|w6lSlrSL zMCKDl-Vxxahu z)yZSf8mn|b(f^S1VQ591*}=i`6hhR_jlGqvnkv?zvY#gD4QTz?829pZ)EU^bdRvW2 zJeAxIR6rgvo5a~jWI~i}(sFTS>=w-(d+w1tz%7v0t9eN&doZOT}x=hKpsxtf$vVmd###dF&D6D1W?3f)2dQS)`!~ zIH$P;kJx6sLhZC`h_BUTVO>gj6iR;6*u)>#vv0ngh+0!#MaNnfrn^vO!z8A$DCzp+ z8_0VzE~PrpkK*dtv5!e|*1Z%_3I5ok3E-I*uSd(hy~h9yb4N!9I8f*6<;l`oQ)({p zHa;TG)JEWw6ROqemLvze)TLC-O5Q|jae%fDrr6Z#zLINiOn7@WxG2rbbOC8Uv!7e{ zm)_1ahz(-IsQ(SrwwL&e4@%PiqS7W~RnY zrydi&?npZDD{uE%Rj6byHxy0`sA)su6)?1))=})C*%~2|)R!H3BboXhK$2XC?P8Nw z6=FW0e2`3VrpvX}&(?UpUhcqyw51u^qwBiz_g|xe5M|Jn-DT;ug{&=X!ZXd<02}XK zZj3^#fwuP_ZIeJ7ucn!hu(`JWLHfP-?@XzIlIxpQLuEwOc~_?*Cbmo*6g~P|buaz> z`eD9Sd1T4DnD|PuoURPo0rSSQd;;u}DRB^#cn59uAl&3X407@xgW1E?k6ZforUR2{)x&IZ?Aa~U#Y+O*3A8Q%VMoN1d-7(o)5tvW4@w%aF$c`L?fBNW43y=6&;#G3 l|0$RIQ=k1mR2c9dpOYff`acfjn_Y15 Date: Mon, 11 Apr 2022 15:18:12 +0200 Subject: [PATCH 60/71] On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation. https://github.com/microsoft/STL/issues/147#issuecomment-1090148740 Slic3r::deque<> compiles to boost::container::deque<> on Windows, to std::deque<> on other systems. SeamPlacer newly uses Slic3r::deque<>. --- src/libslic3r/GCode/SeamPlacer.hpp | 3 ++- src/libslic3r/libslic3r.h | 17 +++++++++++++++++ src/libslic3r/pchheader.hpp | 6 ++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 15dcd702a..a918ff1b3 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -6,6 +6,7 @@ #include #include +#include "libslic3r/libslic3r.h" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/PrintConfig.hpp" @@ -93,7 +94,7 @@ struct PrintObjectSeamData struct LayerSeams { - std::deque perimeters; + Slic3r::deque perimeters; std::vector points; std::unique_ptr points_tree; }; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 5e0fb67b3..2131a9233 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -23,6 +23,13 @@ #include #include +#ifdef _WIN32 +// On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation. +// https://github.com/microsoft/STL/issues/147#issuecomment-1090148740 +// Thus it is recommended to use boost::container::deque instead. +#include +#endif // _WIN32 + #include "Technologies.hpp" #include "Semver.hpp" @@ -73,6 +80,16 @@ namespace Slic3r { extern Semver SEMVER; +// On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation. +template> +using deque = +#ifdef _WIN32 + // Use boost implementation, which allocates blocks of 512 bytes instead of blocks of 8 bytes. + boost::container::deque; +#else // _WIN32 + std::deque; +#endif // _WIN32 + template inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index e6591f574..aeb79e3d8 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -64,6 +64,12 @@ #include #include #include +#ifdef _WIN32 +// On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation. +// https://github.com/microsoft/STL/issues/147#issuecomment-1090148740 +// Thus it is recommended to use boost::container::deque instead. +#include +#endif // _WIN32 #include #include #include From 399b7f79e88c40685b820f195ff22646561b4fe2 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 12 Apr 2022 12:39:37 +0200 Subject: [PATCH 61/71] Little more refactoring of SeamPlacer. --- src/libslic3r/GCode/SeamPlacer.cpp | 154 ++++++++++++----------------- src/libslic3r/GCode/SeamPlacer.hpp | 14 +-- 2 files changed, 72 insertions(+), 96 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 78524954c..2290a82b9 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -610,25 +610,17 @@ struct SeamComparator { bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location = Vec2f { 0.0f, 0.0f }) const { if (setup == SeamPosition::spAligned && a.central_enforcer != b.central_enforcer) { - if (a.central_enforcer) { - return true; - } - if (b.central_enforcer) { - return false; - } + return a.central_enforcer; } // Blockers/Enforcers discrimination, top priority - if (a.type > b.type) { - return true; - } - if (b.type > a.type) { - return false; + if (a.type != b.type) { + return a.type > b.type; } //avoid overhangs - if (a.overhang > 0.0f && b.overhang < a.overhang) { - return false; + if (a.overhang > 0.0f || b.overhang > 0.0f) { + return a.overhang < b.overhang; } // prefer hidden points (more than 1 mm inside) @@ -647,7 +639,7 @@ struct SeamComparator { float distance_penalty_b = 1.0f; if (setup == spNearest) { distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); - distance_penalty_b = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); + distance_penalty_b = 1.1f - gauss((b.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); } //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) @@ -661,17 +653,14 @@ struct SeamComparator { return penalty_a < penalty_b; } - // Comparator used during alignment. If there is close potential aligned point, it is comapred to the current - // sema point of the perimeter, to find out if the aligned point is not much worse than the current seam + // Comparator used during alignment. If there is close potential aligned point, it is compared to the current + // seam point of the perimeter, to find out if the aligned point is not much worse than the current seam + // Also used by the random seam generator. bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { // Blockers/Enforcers discrimination, top priority if (setup == SeamPosition::spAligned && a.central_enforcer != b.central_enforcer) { - if (a.central_enforcer) { - return true; - } - if (b.central_enforcer) { - return false; - } + // Prefer centers of enforcers. + return a.central_enforcer; } if (a.type == EnforcedBlockedSeamPoint::Enforced) { @@ -682,16 +671,13 @@ struct SeamComparator { return false; } - if (a.type > b.type) { - return true; - } - if (b.type > a.type) { - return false; + if (a.type != b.type) { + return a.type > b.type; } //avoid overhangs - if (a.overhang > 0.0f && b.overhang < a.overhang) { - return false; + if (a.overhang > 0.0f || b.overhang > 0.0f) { + return a.overhang < b.overhang; } // prefer hidden points (more than 1 mm inside) @@ -716,9 +702,13 @@ struct SeamComparator { float penalty_b = (b.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(b.local_ccw_angle); - return penalty_a <= penalty_b || std::abs(penalty_a - penalty_b) < SeamPlacer::seam_align_score_tolerance; + return penalty_a <= penalty_b || penalty_a - penalty_b < SeamPlacer::seam_align_score_tolerance; } + bool are_similar(const SeamCandidate &a, const SeamCandidate &b) const { + return is_first_not_much_worse(a, b) && is_first_not_much_worse(b, a); + }; + //always nonzero, positive float get_penalty(const SeamCandidate &a) const { if (setup == SeamPosition::spRear) { @@ -838,25 +828,20 @@ void pick_random_seam_point(const std::vector &perimeter_points, // big overhang. size_t viable_example_index = start_index; size_t end_index = perimeter_points[start_index].perimeter.end_index; - std::vector viable_indices; - std::vector viable_edges_lengths; - std::vector viable_edges; + struct Viable { + // Candidate seam point index. + size_t index; + float edge_length; + Vec3f edge; + }; + std::vector viables; for (size_t index = start_index; index <= end_index; ++index) { - if (comparator.is_first_not_much_worse(perimeter_points[index], perimeter_points[viable_example_index]) && - comparator.is_first_not_much_worse(perimeter_points[viable_example_index], perimeter_points[index])) { - // index ok, push info into respective vectors - Vec3f edge_to_next; - if (index == end_index) { - edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); - } else - { - edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); - } + if (comparator.are_similar(perimeter_points[index], perimeter_points[viable_example_index])) { + // index ok, push info into viables + Vec3f edge_to_next{ perimeter_points[index == end_index ? start_index : index + 1].position - perimeter_points[index].position }; float dist_to_next = edge_to_next.norm(); - viable_indices.push_back(index); - viable_edges_lengths.push_back(dist_to_next); - viable_edges.push_back(edge_to_next); + viables.push_back({ index, dist_to_next, edge_to_next }); } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index], perimeter_points[index])) { // index is worse then viable_example_index, skip this point @@ -864,39 +849,29 @@ void pick_random_seam_point(const std::vector &perimeter_points, // index is better than viable example index, update example, clear gathered info, start again // clear up all gathered info, start from scratch, update example index viable_example_index = index; - viable_indices.clear(); - viable_edges_lengths.clear(); - viable_edges.clear(); + viables.clear(); - Vec3f edge_to_next; - if (index == end_index) { - edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); - } else { - edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); - } + Vec3f edge_to_next = (perimeter_points[index == end_index ? start_index : index + 1].position - perimeter_points[index].position); float dist_to_next = edge_to_next.norm(); - viable_indices.push_back(index); - viable_edges_lengths.push_back(dist_to_next); - viable_edges.push_back(edge_to_next); + viables.push_back({ index, dist_to_next, edge_to_next }); } } // now pick random point from the stored options - float len_sum = std::accumulate(viable_edges_lengths.begin(), viable_edges_lengths.end(), 0.0f); + float len_sum = std::accumulate(viables.begin(), viables.end(), 0.0f, [](const float acc, const Viable &v){ return acc + v.edge_length; }); float picked_len = len_sum * (rand() / (float(RAND_MAX) + 1)); size_t point_idx = 0; - while (picked_len - viable_edges_lengths[point_idx] > 0) { - picked_len = picked_len - viable_edges_lengths[point_idx]; + while (picked_len - viables[point_idx].edge_length > 0) { + picked_len = picked_len - viables[point_idx].edge_length; point_idx++; } Perimeter &perimeter = perimeter_points[start_index].perimeter; - perimeter.seam_index = viable_indices[point_idx]; + perimeter.seam_index = viables[point_idx].index; perimeter.final_seam_position = perimeter_points[perimeter.seam_index].position - + viable_edges[point_idx].normalized() * picked_len; + + viables[point_idx].edge.normalized() * picked_len; perimeter.finalized = true; - } struct EdgeGridWrapper { @@ -946,7 +921,7 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, process_perimeter_polygon(polygons[poly_index], unscaled_z, regions[poly_index], global_model_info, layer_seams); } - auto functor = SeamCandidateCoordinateFunctor { &layer_seams.points }; + auto functor = SeamCandidateCoordinateFunctor { layer_seams.points }; seam_data.layers[layer_idx].points_tree = std::make_unique(functor, layer_seams.points.size()); } @@ -1019,18 +994,18 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) // If the current chosen stream is close enough, it is stored in seam_string. returns true and updates last_point_pos // If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos // Otherwise does nothing, returns false -// sadly cannot be const because map access operator[] is not const, since it can create new object -bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, +// Used by align_seam_points(). +bool SeamPlacer::find_next_seam_in_layer( + const std::vector &layers, std::pair &last_point_indexes, - size_t layer_idx, const SeamPlacerImpl::SeamComparator &comparator, - std::vector> &seam_string) { + const size_t layer_idx, const float slice_z, + const SeamPlacerImpl::SeamComparator &comparator, + std::vector> &seam_string) const { using namespace SeamPlacerImpl; - const std::vector &layers = m_seam_per_object[po].layers; const SeamCandidate &last_point = layers[last_point_indexes.first].points[last_point_indexes.second]; - Vec3f projected_position { last_point.position.x(), last_point.position.y(), float( - po->get_layer(layer_idx)->slice_z) }; + Vec3f projected_position { last_point.position.x(), last_point.position.y(), slice_z }; //find closest point in next layer size_t closest_point_index = find_closest_point(*layers[layer_idx].points_tree, projected_position); @@ -1044,26 +1019,21 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, const SeamCandidate &next_layer_seam = layers[layer_idx].points[closest_point.perimeter.seam_index]; if (next_layer_seam.central_enforcer - && (next_layer_seam.position - projected_position).norm() < 3 * SeamPlacer::seam_align_tolerable_dist) { - seam_string.push_back( { layer_idx, closest_point.perimeter.seam_index }); + && (next_layer_seam.position - projected_position).squaredNorm() < sqr(3 * SeamPlacer::seam_align_tolerable_dist)) { last_point_indexes = std::pair { layer_idx, closest_point.perimeter.seam_index }; + seam_string.push_back(last_point_indexes); return true; } - auto are_similar = [&comparator](const SeamCandidate &a, const SeamCandidate &b) { - return comparator.is_first_not_much_worse(a, b) && comparator.is_first_not_much_worse(b, a); - }; - - if ((closest_point.position - projected_position).norm() < SeamPlacer::seam_align_tolerable_dist + if ((closest_point.position - projected_position).squaredNorm() < sqr(SeamPlacer::seam_align_tolerable_dist) && comparator.is_first_not_much_worse(closest_point, next_layer_seam) - && are_similar(last_point, closest_point)) { - seam_string.push_back( { layer_idx, closest_point_index }); + && comparator.are_similar(last_point, closest_point)) { last_point_indexes = std::pair { layer_idx, closest_point_index }; + seam_string.push_back(last_point_indexes); return true; } else { return false; } - } // clusters already chosen seam points into strings across multiple layers, and then @@ -1114,6 +1084,11 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: ); //align the seam points - start with the best, and check if they are aligned, if yes, skip, else start alignment + // Keeping the vectors outside, so with a bit of luck they will not get reallocated after couple of for loop iterations. + std::vector> seam_string; + std::vector observations; + std::vector observation_points; + std::vector weights; for (const std::pair &seam : seams) { size_t layer_idx = seam.first; size_t seam_index = seam.second; @@ -1128,11 +1103,11 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: int next_layer = layer_idx + 1; std::pair last_point_indexes = std::pair(layer_idx, seam_index); - std::vector> seam_string { std::pair(layer_idx, seam_index) }; + seam_string = { std::pair(layer_idx, seam_index) }; //find seams or potential seams in forward direction; there is a budget of skips allowed while (skips >= 0 && next_layer < int(layers.size())) { - if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string)) { + if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { //String added, last_point_pos updated, nothing to be done } else { // Layer skipped, reduce number of available skips @@ -1146,7 +1121,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: skips = SeamPlacer::seam_align_tolerable_skips / 2; last_point_indexes = std::pair(layer_idx, seam_index); while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string)) { + if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { //String added, last_point_pos updated, nothing to be done } else { // Layer skipped, reduce number of available skips @@ -1168,13 +1143,12 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: }); // gather all positions of seams and their weights (weights are derived as negative penalty, they are made positive in next step) - std::vector observations(seam_string.size()); - std::vector observation_points(seam_string.size()); - std::vector weights(seam_string.size()); + observations.resize(seam_string.size()); + observation_points.resize(seam_string.size()); + weights.resize(seam_string.size()); //init min_weight by the first point - float min_weight = -comparator.get_penalty( - layers[seam_string[0].first].points[seam_string[0].second]); + auto min_weight = std::numeric_limits::max(); //gather points positions and weights, update min_weight in each step for (size_t index = 0; index < seam_string.size(); ++index) { diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index a918ff1b3..70881d558 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -78,12 +78,12 @@ struct FaceVisibilityInfo { }; struct SeamCandidateCoordinateFunctor { - SeamCandidateCoordinateFunctor(std::vector *seam_candidates) : + SeamCandidateCoordinateFunctor(const std::vector &seam_candidates) : seam_candidates(seam_candidates) { } - std::vector *seam_candidates; + const std::vector &seam_candidates; float operator()(size_t index, size_t dim) const { - return seam_candidates->operator[](index).position[dim]; + return seam_candidates[index].position[dim]; } }; } // namespace SeamPlacerImpl @@ -154,10 +154,12 @@ private: const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_overhangs_and_layer_embedding(const PrintObject *po); void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); - bool find_next_seam_in_layer(const PrintObject *po, + bool find_next_seam_in_layer( + const std::vector &layers, std::pair &last_point_indexes, - size_t layer_idx, const SeamPlacerImpl::SeamComparator &comparator, - std::vector> &seam_string); + const size_t layer_idx, const float slice_z, + const SeamPlacerImpl::SeamComparator &comparator, + std::vector> &seam_string) const; }; } // namespace Slic3r From 2dfabb7e69c07d02fe425e4aec07708c114432e0 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 12 Apr 2022 12:49:36 +0200 Subject: [PATCH 62/71] Fixing missing include. --- src/libslic3r/Color.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp index bb62ffdaa..8044a0318 100644 --- a/src/libslic3r/Color.hpp +++ b/src/libslic3r/Color.hpp @@ -4,6 +4,8 @@ #include #include +#include "Point.hpp" + namespace Slic3r { class ColorRGB From 04d4a0d4f766b48d58bf8dda46909471f1b061e3 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 12 Apr 2022 17:09:45 +0200 Subject: [PATCH 63/71] when searching for central enforcer (for alignment purposes), find properly the first enforced segment. Fixed issue where if the enforced segment was painted over the start/end of the perimeter, part of the enforced points was not considered. --- src/libslic3r/GCode/SeamPlacer.cpp | 95 +++++++++++++++++++----------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 2290a82b9..712c8d724 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -392,7 +392,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const std::vector local_angles = calculate_polygon_angles_at_vertices(polygon, lengths, SeamPlacer::polygon_local_angles_arm_distance); - result.perimeters.push_back({}); + result.perimeters.push_back( { }); Perimeter &perimeter = result.perimeters.back(); std::queue orig_polygon_points { }; @@ -450,30 +450,43 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const perimeter.end_index = result.points.size() - 1; - // We will find first patch of enforced points (patch: continous section of enforced points) and select the middle - // point, which will have priority during alignemnt + // We will find first patch of enforced points (patch: continuous section of enforced points) and select the middle + // point, which will have priority during alignment // If there are multiple enforced patches in the perimeter, others are ignored if (some_point_enforced) { + size_t perimeter_size = perimeter.end_index - perimeter.start_index + 1; + const auto next_index = [&](size_t idx) { + return perimeter.start_index + Slic3r::next_idx_modulo(idx - perimeter.start_index, perimeter_size); + }; + size_t first_enforced_idx = perimeter.start_index; - while (first_enforced_idx <= perimeter.end_index - && result.points[first_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced) { - first_enforced_idx++; + for (int _ = 0; _ < perimeter_size; ++_) { + if (result.points[first_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced && + result.points[next_index(first_enforced_idx)].type == EnforcedBlockedSeamPoint::Enforced) { + break; + } + first_enforced_idx = next_index(first_enforced_idx); } + first_enforced_idx = next_index(first_enforced_idx); // Gather also points with large angles (these are points from the original mesh, since oversampled points have zero angle) // If there are any, the middle point will be picked from those (makes drawing over sharp corners easier) - std::vector orig_large_angle_points_indices{}; + std::vector orig_large_angle_points_indices { }; + std::vector viable_points_indices { }; size_t last_enforced_idx = first_enforced_idx; - while (last_enforced_idx < perimeter.end_index - && result.points[last_enforced_idx + 1].type == EnforcedBlockedSeamPoint::Enforced) { + for (int _ = 0; _ < perimeter_size; ++_) { + if (result.points[last_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced) { + break; + } + viable_points_indices.push_back(last_enforced_idx); if (abs(result.points[last_enforced_idx].local_ccw_angle) > 0.4 * PI) { orig_large_angle_points_indices.push_back(last_enforced_idx); } - last_enforced_idx++; + last_enforced_idx = next_index(last_enforced_idx); } - + assert(viable_points_indices.size() > 0); if (orig_large_angle_points_indices.empty()) { - size_t central_idx = (first_enforced_idx + last_enforced_idx) / 2; + size_t central_idx = viable_points_indices[viable_points_indices.size() / 2]; result.points[central_idx].central_enforcer = true; } else { size_t central_idx = orig_large_angle_points_indices.size() / 2; @@ -707,7 +720,8 @@ struct SeamComparator { bool are_similar(const SeamCandidate &a, const SeamCandidate &b) const { return is_first_not_much_worse(a, b) && is_first_not_much_worse(b, a); - }; + } + ; //always nonzero, positive float get_penalty(const SeamCandidate &a) const { @@ -831,17 +845,18 @@ void pick_random_seam_point(const std::vector &perimeter_points, struct Viable { // Candidate seam point index. size_t index; - float edge_length; - Vec3f edge; + float edge_length; + Vec3f edge; }; std::vector viables; for (size_t index = start_index; index <= end_index; ++index) { if (comparator.are_similar(perimeter_points[index], perimeter_points[viable_example_index])) { // index ok, push info into viables - Vec3f edge_to_next{ perimeter_points[index == end_index ? start_index : index + 1].position - perimeter_points[index].position }; + Vec3f edge_to_next { perimeter_points[index == end_index ? start_index : index + 1].position + - perimeter_points[index].position }; float dist_to_next = edge_to_next.norm(); - viables.push_back({ index, dist_to_next, edge_to_next }); + viables.push_back( { index, dist_to_next, edge_to_next }); } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index], perimeter_points[index])) { // index is worse then viable_example_index, skip this point @@ -851,14 +866,17 @@ void pick_random_seam_point(const std::vector &perimeter_points, viable_example_index = index; viables.clear(); - Vec3f edge_to_next = (perimeter_points[index == end_index ? start_index : index + 1].position - perimeter_points[index].position); + Vec3f edge_to_next = (perimeter_points[index == end_index ? start_index : index + 1].position + - perimeter_points[index].position); float dist_to_next = edge_to_next.norm(); - viables.push_back({ index, dist_to_next, edge_to_next }); + viables.push_back( { index, dist_to_next, edge_to_next }); } } // now pick random point from the stored options - float len_sum = std::accumulate(viables.begin(), viables.end(), 0.0f, [](const float acc, const Viable &v){ return acc + v.edge_length; }); + float len_sum = std::accumulate(viables.begin(), viables.end(), 0.0f, [](const float acc, const Viable &v) { + return acc + v.edge_length; + }); float picked_len = len_sum * (rand() / (float(RAND_MAX) + 1)); size_t point_idx = 0; @@ -905,7 +923,7 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info, const SeamPosition configured_seam_preference) { using namespace SeamPlacerImpl; - PrintObjectSeamData &seam_data = m_seam_per_object.emplace(po, PrintObjectSeamData{}).first->second; + PrintObjectSeamData &seam_data = m_seam_per_object.emplace(po, PrintObjectSeamData { }).first->second; seam_data.layers.resize(po->layer_count()); tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), @@ -923,7 +941,8 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, } auto functor = SeamCandidateCoordinateFunctor { layer_seams.points }; seam_data.layers[layer_idx].points_tree = - std::make_unique(functor, layer_seams.points.size()); + std::make_unique(functor, + layer_seams.points.size()); } } ); @@ -935,7 +954,7 @@ void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, std::vector &layers = m_seam_per_object[po].layers; tbb::parallel_for(tbb::blocked_range(0, layers.size()), - [&layers, &global_model_info](tbb::blocked_range r) { + [&layers, &global_model_info](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { for (auto &perimeter_point : layers[layer_idx].points) { perimeter_point.visibility = global_model_info.calculate_point_visibility( @@ -959,7 +978,7 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { bool layer_has_multiple_loops = - layers[layer_idx].points[0].perimeter.end_index + layers[layer_idx].points[0].perimeter.end_index < layers[layer_idx].points.size() - 1; std::unique_ptr current_layer_grid = std::make_unique( compute_layer_merged_edge_grid(po->layers()[layer_idx])); @@ -1019,7 +1038,8 @@ bool SeamPlacer::find_next_seam_in_layer( const SeamCandidate &next_layer_seam = layers[layer_idx].points[closest_point.perimeter.seam_index]; if (next_layer_seam.central_enforcer - && (next_layer_seam.position - projected_position).squaredNorm() < sqr(3 * SeamPlacer::seam_align_tolerable_dist)) { + && (next_layer_seam.position - projected_position).squaredNorm() + < sqr(3 * SeamPlacer::seam_align_tolerable_dist)) { last_point_indexes = std::pair { layer_idx, closest_point.perimeter.seam_index }; seam_string.push_back(last_point_indexes); return true; @@ -1107,7 +1127,8 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: //find seams or potential seams in forward direction; there is a budget of skips allowed while (skips >= 0 && next_layer < int(layers.size())) { - if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { + if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, + float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { //String added, last_point_pos updated, nothing to be done } else { // Layer skipped, reduce number of available skips @@ -1121,7 +1142,8 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: skips = SeamPlacer::seam_align_tolerable_skips / 2; last_point_indexes = std::pair(layer_idx, seam_index); while (skips >= 0 && next_layer >= 0) { - if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { + if (find_next_seam_in_layer(layers, last_point_indexes, next_layer, + float(po->get_layer(next_layer)->slice_z), comparator, seam_string)) { //String added, last_point_pos updated, nothing to be done } else { // Layer skipped, reduce number of available skips @@ -1257,7 +1279,8 @@ void SeamPlacer::init(const Print &print) { [&layers, configured_seam_preference, comparator](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { std::vector &layer_perimeter_points = layers[layer_idx].points; - for (size_t current = 0; current < layer_perimeter_points.size(); current = layer_perimeter_points[current].perimeter.end_index + 1) + for (size_t current = 0; current < layer_perimeter_points.size(); + current = layer_perimeter_points[current].perimeter.end_index + 1) if (configured_seam_preference == spRandom) pick_random_seam_point(layer_perimeter_points, current); else @@ -1293,24 +1316,28 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern const size_t layer_index = layer->id() - po->slicing_parameters().raft_layers(); const double unscaled_z = layer->slice_z; - const PrintObjectSeamData::LayerSeams &layer_perimeters = m_seam_per_object.find(layer->object())->second.layers[layer_index]; + const PrintObjectSeamData::LayerSeams &layer_perimeters = + m_seam_per_object.find(layer->object())->second.layers[layer_index]; // Find the closest perimeter in the SeamPlacer to the first point of this loop. size_t closest_perimeter_point_index; { const Point &fp = loop.first_point(); Vec2f unscaled_p = unscaled(fp); - closest_perimeter_point_index = find_closest_point(*layer_perimeters.points_tree.get(), to_3d(unscaled_p, float(unscaled_z))); + closest_perimeter_point_index = find_closest_point(*layer_perimeters.points_tree.get(), + to_3d(unscaled_p, float(unscaled_z))); } Vec3f seam_position; if (const Perimeter &perimeter = layer_perimeters.points[closest_perimeter_point_index].perimeter; - perimeter.finalized) { + perimeter.finalized) { seam_position = perimeter.final_seam_position; } else { - size_t seam_index = po->config().seam_position == spNearest ? - pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, unscaled(last_pos)) : - perimeter.seam_index; + size_t seam_index = + po->config().seam_position == spNearest ? + pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, + unscaled(last_pos)) : + perimeter.seam_index; seam_position = layer_perimeters.points[seam_index].position; } From 83f3ca27dcd8912fbf463e7b87fea095ef17853a Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 13 Apr 2022 14:32:09 +0200 Subject: [PATCH 64/71] reworked and improved the find_next_seam_in_layer method: Now uses find nearby_points with radius to save some computations if all points are far. From the nearby points, it finds the nearest and best point, and tries to use them in this order (preivously only nearest was considered). This helps to snap the alignment to nearby sharp corners if present. --- src/libslic3r/GCode/SeamPlacer.cpp | 69 +++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 712c8d724..07b8b8ee3 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -460,7 +460,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const }; size_t first_enforced_idx = perimeter.start_index; - for (int _ = 0; _ < perimeter_size; ++_) { + for (size_t _ = 0; _ < perimeter_size; ++_) { if (result.points[first_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced && result.points[next_index(first_enforced_idx)].type == EnforcedBlockedSeamPoint::Enforced) { break; @@ -474,7 +474,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const std::vector orig_large_angle_points_indices { }; std::vector viable_points_indices { }; size_t last_enforced_idx = first_enforced_idx; - for (int _ = 0; _ < perimeter_size; ++_) { + for (size_t _ = 0; _ < perimeter_size; ++_) { if (result.points[last_enforced_idx].type != EnforcedBlockedSeamPoint::Enforced) { break; } @@ -1025,35 +1025,72 @@ bool SeamPlacer::find_next_seam_in_layer( const SeamCandidate &last_point = layers[last_point_indexes.first].points[last_point_indexes.second]; Vec3f projected_position { last_point.position.x(), last_point.position.y(), slice_z }; - //find closest point in next layer - size_t closest_point_index = find_closest_point(*layers[layer_idx].points_tree, projected_position); + std::vector nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position, + SeamPlacer::seam_align_tolerable_dist); - const SeamCandidate &closest_point = layers[layer_idx].points[closest_point_index]; - - if (closest_point.perimeter.finalized) { //already finalized, skip + if (nearby_points_indices.empty()) { return false; } - //from the closest point, deduce index of seam in the next layer - const SeamCandidate &next_layer_seam = layers[layer_idx].points[closest_point.perimeter.seam_index]; + size_t best_nearby_point_index = nearby_points_indices[0]; + size_t nearest_point_index = nearby_points_indices[0]; + // Now find best nearby point, nearest point, and corresponding indices + for (const size_t &nearby_point_index : nearby_points_indices) { + const SeamCandidate &point = layers[layer_idx].points[nearby_point_index]; + if (point.perimeter.finalized) { + continue; // skip over finalized perimeters, try to find some that is not finalized + } + if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index], + projected_position.head<2>()) + || layers[layer_idx].points[best_nearby_point_index].perimeter.finalized) { + best_nearby_point_index = nearby_point_index; + } + if ((point.position - projected_position).squaredNorm() + < (layers[layer_idx].points[nearest_point_index].position - projected_position).squaredNorm() + || layers[layer_idx].points[nearest_point_index].perimeter.finalized) { + nearest_point_index = nearby_point_index; + } + } + + const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index]; + const SeamCandidate &nearest_point = layers[layer_idx].points[nearest_point_index]; + + if (nearest_point.perimeter.finalized) { + //all points are from already finalized perimeter, skip + return false; + } + + //from the nearest_point, deduce index of seam in the next layer + const SeamCandidate &next_layer_seam = layers[layer_idx].points[nearest_point.perimeter.seam_index]; + + // First try to pick central enforcer if any present if (next_layer_seam.central_enforcer && (next_layer_seam.position - projected_position).squaredNorm() < sqr(3 * SeamPlacer::seam_align_tolerable_dist)) { - last_point_indexes = std::pair { layer_idx, closest_point.perimeter.seam_index }; + last_point_indexes = std::pair { layer_idx, nearest_point.perimeter.seam_index }; seam_string.push_back(last_point_indexes); return true; } - if ((closest_point.position - projected_position).squaredNorm() < sqr(SeamPlacer::seam_align_tolerable_dist) - && comparator.is_first_not_much_worse(closest_point, next_layer_seam) - && comparator.are_similar(last_point, closest_point)) { - last_point_indexes = std::pair { layer_idx, closest_point_index }; + // Next compare nearest and nearby point. If they are similar pick nearest, Otherwise expect curvy lines on smooth surfaces like chimney of benchy model + // We also compare it to the last point, to detect sharp changes in the scoring - that points to change in the model geometry and string should be ended. + if (comparator.are_similar(nearest_point, best_nearby_point) + && comparator.is_first_not_much_worse(nearest_point, next_layer_seam) + && comparator.are_similar(last_point, nearest_point)) { + last_point_indexes = std::pair { layer_idx, nearest_point_index }; seam_string.push_back(last_point_indexes); return true; - } else { - return false; } + // If nearest point is not good enough, try it with the best nearby point. + if (comparator.is_first_not_much_worse(best_nearby_point, next_layer_seam) + && comparator.are_similar(last_point, nearest_point)) { + last_point_indexes = std::pair { layer_idx, best_nearby_point_index }; + seam_string.push_back(last_point_indexes); + return true; + } + + return false; } // clusters already chosen seam points into strings across multiple layers, and then From 137fa352381243eb9104540437108b332ee5624b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 13 Apr 2022 15:00:18 +0200 Subject: [PATCH 65/71] Postpone seam picking for spNearest configuration to the place_seam method. --- src/libslic3r/GCode/SeamPlacer.cpp | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 07b8b8ee3..85826fc68 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1308,24 +1308,26 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: calculate_overhangs and layer embdedding: end"; - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : start"; - //pick seam point - std::vector &layers = m_seam_per_object[po].layers; - tbb::parallel_for(tbb::blocked_range(0, layers.size()), - [&layers, configured_seam_preference, comparator](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = layers[layer_idx].points; - for (size_t current = 0; current < layer_perimeter_points.size(); - current = layer_perimeter_points[current].perimeter.end_index + 1) - if (configured_seam_preference == spRandom) - pick_random_seam_point(layer_perimeter_points, current); - else - pick_seam_point(layer_perimeter_points, current, comparator); - } - }); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : end"; + if (configured_seam_preference != spNearest) { // For spNearest, the seam is picked in the place_seam method with actual nozzle position information + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: pick_seam_point : start"; + //pick seam point + std::vector &layers = m_seam_per_object[po].layers; + tbb::parallel_for(tbb::blocked_range(0, layers.size()), + [&layers, configured_seam_preference, comparator](tbb::blocked_range r) { + for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { + std::vector &layer_perimeter_points = layers[layer_idx].points; + for (size_t current = 0; current < layer_perimeter_points.size(); + current = layer_perimeter_points[current].perimeter.end_index + 1) + if (configured_seam_preference == spRandom) + pick_random_seam_point(layer_perimeter_points, current); + else + pick_seam_point(layer_perimeter_points, current, comparator); + } + }); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: pick_seam_point : end"; + } if (configured_seam_preference == spAligned) { BOOST_LOG_TRIVIAL(debug) From 68cf4db58e63334523f8f926b79fa7b3a20c325b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 13 Apr 2022 15:46:40 +0200 Subject: [PATCH 66/71] interpolate fitted and original position during b spline alignment - push points with large weight more towards their original position --- src/libslic3r/GCode/SeamPlacer.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 85826fc68..3c9009702 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1206,10 +1206,8 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: observation_points.resize(seam_string.size()); weights.resize(seam_string.size()); - //init min_weight by the first point auto min_weight = std::numeric_limits::max(); - - //gather points positions and weights, update min_weight in each step + //gather points positions and weights (negative value of penalty), update min_weight in each step for (size_t index = 0; index < seam_string.size(); ++index) { Vec3f pos = layers[seam_string[index].first].points[seam_string[index].second].position; observations[index] = pos.head<2>(); @@ -1219,10 +1217,11 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: min_weight = std::min(min_weight, weights[index]); } - //makes all weights positive + //make all weights positive for (float &w : weights) { w = w - min_weight + 0.01; } + float max_weight = -min_weight + 0.01; // Curve Fitting size_t number_of_segments = std::max(size_t(1), @@ -1231,12 +1230,17 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Perimeter structure of the point; also set flag aligned to true - for (const auto &pair : seam_string) { + for (size_t index = 0; index < seam_string.size(); ++index) { + const auto& pair = seam_string[index]; + const float t = weights[index]/max_weight; Vec3f current_pos = layers[pair.first].points[pair.second].position; Vec2f fitted_pos = curve.get_fitted_value(current_pos.z()); + //interpolate between current and fitted position, prefer current pos for large weights. + Vec3f final_position = t*current_pos + (1-t)*to_3d(fitted_pos, current_pos.z()); + Perimeter &perimeter = layers[pair.first].points[pair.second].perimeter; - perimeter.final_seam_position = to_3d(fitted_pos, current_pos.z()); + perimeter.final_seam_position = final_position; perimeter.finalized = true; } From 43d91663826ddbd6eb558f01eea75061ea3a0f09 Mon Sep 17 00:00:00 2001 From: Godrak Date: Wed, 13 Apr 2022 19:07:35 +0200 Subject: [PATCH 67/71] nomralize weights of points before curve fitting and fitted value interpolation --- src/libslic3r/GCode/SeamPlacer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 3c9009702..480b35c08 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -721,7 +721,6 @@ struct SeamComparator { bool are_similar(const SeamCandidate &a, const SeamCandidate &b) const { return is_first_not_much_worse(a, b) && is_first_not_much_worse(b, a); } - ; //always nonzero, positive float get_penalty(const SeamCandidate &a) const { @@ -1207,6 +1206,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: weights.resize(seam_string.size()); auto min_weight = std::numeric_limits::max(); + auto max_weight = -std::numeric_limits::max(); //gather points positions and weights (negative value of penalty), update min_weight in each step for (size_t index = 0; index < seam_string.size(); ++index) { Vec3f pos = layers[seam_string[index].first].points[seam_string[index].second].position; @@ -1215,13 +1215,13 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: weights[index] = -comparator.get_penalty( layers[seam_string[index].first].points[seam_string[index].second]); min_weight = std::min(min_weight, weights[index]); + max_weight = std::max(max_weight, weights[index]); } - //make all weights positive + //normalize weights (ensure nonzero) for (float &w : weights) { - w = w - min_weight + 0.01; + w = 0.01 + (w - min_weight) / (max_weight - min_weight); } - float max_weight = -min_weight + 0.01; // Curve Fitting size_t number_of_segments = std::max(size_t(1), @@ -1232,7 +1232,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // Perimeter structure of the point; also set flag aligned to true for (size_t index = 0; index < seam_string.size(); ++index) { const auto& pair = seam_string[index]; - const float t = weights[index]/max_weight; + const float t = weights[index]; Vec3f current_pos = layers[pair.first].points[pair.second].position; Vec2f fitted_pos = curve.get_fitted_value(current_pos.z()); From e377e58cd2bb141ed948c17f74d17b26a5bef1b3 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 14 Apr 2022 11:53:00 +0200 Subject: [PATCH 68/71] Updated weights for curve fitting, ensured snapping to sharp corners Fixed debug exports after refactorings. --- src/libslic3r/GCode/SeamPlacer.cpp | 46 +++++++++++------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 480b35c08..65bd29e63 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -722,16 +722,6 @@ struct SeamComparator { return is_first_not_much_worse(a, b) && is_first_not_much_worse(b, a); } - //always nonzero, positive - float get_penalty(const SeamCandidate &a) const { - if (setup == SeamPosition::spRear) { - return a.position.y(); - } - - return (a.visibility + SeamPlacer::additional_angle_importance) * compute_angle_penalty(a.local_ccw_angle); - } - -private: float compute_angle_penalty(float ccw_angle) const { // This function is used: // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) @@ -757,15 +747,15 @@ void debug_export_points(const std::vector &la float max_weight = min_weight; for (const SeamCandidate &point : layers[layer_idx].points) { - Vec3i color = value_rgbi(-PI, PI, point.local_ccw_angle); + Vec3i color = value_to_rgbi(-PI, PI, point.local_ccw_angle); std::string fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," + std::to_string(color.z()) + ")"; angles_svg.draw(scaled(Vec2f(point.position.head<2>())), fill); min_vis = std::min(min_vis, point.visibility); max_vis = std::max(max_vis, point.visibility); - min_weight = std::min(min_weight, -comparator.get_penalty(point)); - max_weight = std::max(max_weight, -comparator.get_penalty(point)); + min_weight = std::min(min_weight, -comparator.compute_angle_penalty(point.local_ccw_angle)); + max_weight = std::max(max_weight, -comparator.compute_angle_penalty(point.local_ccw_angle)); } @@ -780,18 +770,18 @@ void debug_export_points(const std::vector &la SVG overhangs_svg {overhangs_file_name, bounding_box}; for (const SeamCandidate &point : layers[layer_idx].points) { - Vec3i color = value_rgbi(min_vis, max_vis, point.visibility); + Vec3i color = value_to_rgbi(min_vis, max_vis, point.visibility); std::string visibility_fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," + std::to_string(color.z()) + ")"; visibility_svg.draw(scaled(Vec2f(point.position.head<2>())), visibility_fill); - Vec3i weight_color = value_rgbi(min_weight, max_weight, -comparator.get_penalty(point)); + Vec3i weight_color = value_to_rgbi(min_weight, max_weight, -comparator.compute_angle_penalty(point.local_ccw_angle)); std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + "," + std::to_string(weight_color.z()) + ")"; weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); - Vec3i overhang_color = value_rgbi(-0.5, 0.5, std::clamp(point.overhang, -0.5f, 0.5f)); + Vec3i overhang_color = value_to_rgbi(-0.5, 0.5, std::clamp(point.overhang, -0.5f, 0.5f)); std::string overhang_fill = "rgb(" + std::to_string(overhang_color.x()) + "," + std::to_string(overhang_color.y()) + "," @@ -1205,22 +1195,18 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: observation_points.resize(seam_string.size()); weights.resize(seam_string.size()); - auto min_weight = std::numeric_limits::max(); - auto max_weight = -std::numeric_limits::max(); - //gather points positions and weights (negative value of penalty), update min_weight in each step + //gather points positions and weights + // The algorithm uses only angle to compute penalty, to enforce snapping to sharp corners, if they are present + // after several experiments approach that gives best results is to snap the weight to one for sharp corners, and + // leave it small for others. However, this can result in non-smooth line over area with a lot of unaligned sharp corners. for (size_t index = 0; index < seam_string.size(); ++index) { Vec3f pos = layers[seam_string[index].first].points[seam_string[index].second].position; observations[index] = pos.head<2>(); observation_points[index] = pos.z(); - weights[index] = -comparator.get_penalty( - layers[seam_string[index].first].points[seam_string[index].second]); - min_weight = std::min(min_weight, weights[index]); - max_weight = std::max(max_weight, weights[index]); - } - - //normalize weights (ensure nonzero) - for (float &w : weights) { - w = 0.01 + (w - min_weight) / (max_weight - min_weight); + weights[index] = + (comparator.compute_angle_penalty( + layers[seam_string[index].first].points[seam_string[index].second].local_ccw_angle) + < comparator.compute_angle_penalty(0.4f * PI)) ? 1.0f : 0.1f; } // Curve Fitting @@ -1231,13 +1217,13 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Perimeter structure of the point; also set flag aligned to true for (size_t index = 0; index < seam_string.size(); ++index) { - const auto& pair = seam_string[index]; + const auto &pair = seam_string[index]; const float t = weights[index]; Vec3f current_pos = layers[pair.first].points[pair.second].position; Vec2f fitted_pos = curve.get_fitted_value(current_pos.z()); //interpolate between current and fitted position, prefer current pos for large weights. - Vec3f final_position = t*current_pos + (1-t)*to_3d(fitted_pos, current_pos.z()); + Vec3f final_position = t * current_pos + (1 - t) * to_3d(fitted_pos, current_pos.z()); Perimeter &perimeter = layers[pair.first].points[pair.second].perimeter; perimeter.final_seam_position = final_position; From b5a5926bbed99278327d68691a55bc377b7ed65b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 20 Apr 2022 13:46:40 +0200 Subject: [PATCH 69/71] Implemented alignment of inner seams, especially in concave angles where the perpendicular projection is suboptimal. --- src/libslic3r/GCode/SeamPlacer.cpp | 46 ++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 65bd29e63..004a42a53 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1226,6 +1226,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: Vec3f final_position = t * current_pos + (1 - t) * to_3d(fitted_pos, current_pos.z()); Perimeter &perimeter = layers[pair.first].points[pair.second].perimeter; + perimeter.seam_index = pair.second; perimeter.final_seam_position = final_position; perimeter.finalized = true; } @@ -1358,11 +1359,13 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern } Vec3f seam_position; + size_t seam_index; if (const Perimeter &perimeter = layer_perimeters.points[closest_perimeter_point_index].perimeter; perimeter.finalized) { seam_position = perimeter.final_seam_position; + seam_index = perimeter.seam_index; } else { - size_t seam_index = + seam_index = po->config().seam_position == spNearest ? pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, unscaled(last_pos)) : @@ -1370,12 +1373,45 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern seam_position = layer_perimeters.points[seam_index].position; } - auto seam_point = Point::new_scale(seam_position.x(), seam_position.y()); + Point seam_point = Point::new_scale(seam_position.x(), seam_position.y()); - if (!loop.split_at_vertex(seam_point)) -// The point is not in the original loop. -// Insert it. + if (const SeamCandidate &perimeter_point = layer_perimeters.points[seam_index]; + (po->config().seam_position == spNearest || po->config().seam_position == spAligned) && + loop.role() == ExtrusionRole::erPerimeter && //Hopefully internal perimeter + (seam_position - perimeter_point.position).squaredNorm() < 4.0f && // seam is on perimeter point + perimeter_point.local_ccw_angle < -EPSILON // In concave angles + ) { // In this case, we are at internal perimeter, where the external perimeter has seam in concave angle. We want to align + // the internal seam into the concave corner, and not on the perpendicular projection on the closest edge (which is what the split_at function does) + size_t index_of_prev = + seam_index == perimeter_point.perimeter.start_index ? + perimeter_point.perimeter.end_index : + seam_index - 1; + size_t index_of_next = + seam_index == perimeter_point.perimeter.end_index ? + perimeter_point.perimeter.start_index : + seam_index + 1; + + Vec2f dir_to_middle = + ((perimeter_point.position - layer_perimeters.points[index_of_prev].position).head<2>().normalized() + + (perimeter_point.position - layer_perimeters.points[index_of_next].position).head<2>().normalized()) + * 0.5; + + auto [_, projected_point] = loop.get_closest_path_and_point(seam_point, true); + //get closest projected point, determine depth of the seam point. + float depth = (float) unscale(Point(seam_point - projected_point)).norm(); + float angle_factor = cos(-perimeter_point.local_ccw_angle / 2.0f); // There are some nice geometric identities in determination of the correct depth of new seam point. + //overshoot the target depth, in concave angles it will correctly snap to the corner; TODO: find out why such big overshoot is needed. + constexpr float sq2 = sqrt(2.0f); + Vec2f final_pos = perimeter_point.position.head<2>() + (sq2 * depth / angle_factor) * dir_to_middle; + seam_point = Point::new_scale(final_pos.x(), final_pos.y()); + } + + if (!loop.split_at_vertex(seam_point)) { + // The point is not in the original loop. + // Insert it. loop.split_at(seam_point, true); + } + } } // namespace Slic3r From 77b5885f7d450167f83a5836b3c26b1c8116a163 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 21 Apr 2022 10:02:20 +0200 Subject: [PATCH 70/71] fix build problems --- src/libslic3r/GCode/SeamPlacer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 004a42a53..6e0968145 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1206,7 +1206,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: weights[index] = (comparator.compute_angle_penalty( layers[seam_string[index].first].points[seam_string[index].second].local_ccw_angle) - < comparator.compute_angle_penalty(0.4f * PI)) ? 1.0f : 0.1f; + < comparator.compute_angle_penalty(0.4f * float(PI))) ? 1.0f : 0.1f; } // Curve Fitting @@ -1401,8 +1401,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern float depth = (float) unscale(Point(seam_point - projected_point)).norm(); float angle_factor = cos(-perimeter_point.local_ccw_angle / 2.0f); // There are some nice geometric identities in determination of the correct depth of new seam point. //overshoot the target depth, in concave angles it will correctly snap to the corner; TODO: find out why such big overshoot is needed. - constexpr float sq2 = sqrt(2.0f); - Vec2f final_pos = perimeter_point.position.head<2>() + (sq2 * depth / angle_factor) * dir_to_middle; + Vec2f final_pos = perimeter_point.position.head<2>() + (1.4142 * depth / angle_factor) * dir_to_middle; seam_point = Point::new_scale(final_pos.x(), final_pos.y()); } From 7cccb736dec4620f58797e773bd0184fd219c4b2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 25 Apr 2022 17:34:22 +0200 Subject: [PATCH 71/71] Reverted perl perimeter test bypass --- t/perimeters.t | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/t/perimeters.t b/t/perimeters.t index 463459611..adc2a6cec 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -221,9 +221,7 @@ use Slic3r::Test; if (!$loop_contains_point && $is_contour) # contour should include destination || ($loop_contains_point && $is_hole); # hole should not - if ($model eq 'cube_with_concave_hole' - #FIXME skip the 1st layer in the test 'loops start on concave point if any' - && $self->Z > 0.36) { + if ($model eq 'cube_with_concave_hole') { # check that loop starts at a concave vertex my $ccw_angle = $loop->[-2]->ccw($loop->first_point, $loop->[1]); my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex