diff --git a/resources/data/sla_support.svg b/resources/data/sla_support.svg index d2f4e02c38..7fef16f080 100644 --- a/resources/data/sla_support.svg +++ b/resources/data/sla_support.svg @@ -26,13 +26,13 @@ showgrid="false" showborder="true" borderlayer="true" - inkscape:zoom="3.3638608" - inkscape:cx="92.601929" - inkscape:cy="110.88449" - inkscape:window-width="1920" - inkscape:window-height="1129" - inkscape:window-x="1912" - inkscape:window-y="-8" + inkscape:zoom="19.02887" + inkscape:cx="14.845863" + inkscape:cy="132.29897" + inkscape:window-width="2400" + inkscape:window-height="1261" + inkscape:window-x="-9" + inkscape:window-y="-9" inkscape:window-maximized="1" inkscape:current-layer="layer1" /> + id="tspan9676">Last virtual point of curve is [last.x, ∞] + style="stroke-width:2.5" /> diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index a19cc5d058..b0021a0012 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -473,6 +473,7 @@ set(SLIC3R_SOURCES SLA/SupportIslands/PostProcessNeighbors.cpp SLA/SupportIslands/PostProcessNeighbors.hpp SLA/SupportIslands/SampleConfig.hpp + SLA/SupportIslands/SampleConfigFactory.cpp SLA/SupportIslands/SampleConfigFactory.hpp SLA/SupportIslands/SampleIslandUtils.cpp SLA/SupportIslands/SampleIslandUtils.hpp diff --git a/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp index eb40320bad..c82ce54d29 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp +++ b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp @@ -13,12 +13,12 @@ struct SampleConfig { // Every point on island has at least one support point in maximum distance // MUST be bigger than zero - coord_t max_distance = 2; - coord_t half_distance = 1; // has to be half od max_distance + coord_t max_distance = static_cast(scale_(5.)); + coord_t half_distance = static_cast(scale_(2.5)); // has to be half od max_distance // Support point head radius // MUST be bigger than zero - coord_t head_radius = 1; // [nano meter] + coord_t head_radius = static_cast(scale_(.4)); // [nano meter] // When it is possible, there will be this minimal distance from outline. // zero when head should be on outline @@ -27,7 +27,7 @@ struct SampleConfig // Measured as sum of VD edge length from outline // Used only when there is no space for outline offset on first/last point // Must be bigger than minimal_distance_from_outline - coord_t maximal_distance_from_outline = 1.;// [nano meter] + coord_t maximal_distance_from_outline = static_cast(scale_(1.));// [nano meter] // When angle on outline is smaller than max_interesting_angle // than create unmovable support point. @@ -44,23 +44,23 @@ struct SampleConfig // Maximal length of longest path in voronoi diagram to be island // supported only by one single support point this point will be in center of path. - coord_t max_length_for_one_support_point = 1.; + coord_t max_length_for_one_support_point = static_cast(scale_(1.)); // Maximal length of island supported by 2 points - coord_t max_length_for_two_support_points = 1.; + coord_t max_length_for_two_support_points = static_cast(scale_(1.)); // Maximal width of line island supported in the middle of line // Must be greater or equal to min_width_for_outline_support - coord_t max_width_for_center_support_line = 1.; + coord_t max_width_for_center_support_line = static_cast(scale_(1.)); // Minimal width to be supported by outline // Must be smaller or equal to max_width_for_center_support_line - coord_t min_width_for_outline_support = 1.; + coord_t min_width_for_outline_support = static_cast(scale_(1.)); // Term criteria for end of alignment // Minimal change in manhatn move of support position before termination - coord_t minimal_move = 1000; // in nanometers, devide from print resolution to quater pixel + coord_t minimal_move = static_cast(scale_(.01)); // devide from print resolution to quater pixel // Maximal count of align iteration size_t count_iteration = 100; @@ -75,7 +75,7 @@ struct SampleConfig // There is no need to calculate with precisse island // NOTE: Slice of Cylinder bottom has tip of trinagles on contour // (neighbor coordinate - create issue in voronoi) - double simplification_tolerance = 1e4; // [nm] + double simplification_tolerance = scale_(0.05 /*mm*/); }; } // namespace Slic3r::sla #endif // slic3r_SLA_SuppotstIslands_SampleConfig_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp b/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp new file mode 100644 index 0000000000..0bbbdc9abb --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp @@ -0,0 +1,104 @@ +#include "SampleConfigFactory.hpp" + +using namespace Slic3r::sla; + +bool SampleConfigFactory::verify(SampleConfig &cfg) { + auto verify_max = [](coord_t &c, coord_t max) { + assert(c <= max); + if (c > max) { + c = max; + return false; + } + return true; + }; + auto verify_min = [](coord_t &c, coord_t min) { + assert(c >= min); + if (c < min) { + c = min; + return false; + } + return true; + }; + auto verify_min_max = [](coord_t &min, coord_t &max) { + // min must be smaller than max + assert(min < max); + if (min > max) { + std::swap(min, max); + return false; + } else if (min == max) { + min /= 2; // cut in half + return false; + } + return true; + }; + bool res = true; + res &= verify_min_max(cfg.max_length_for_one_support_point, cfg.max_length_for_two_support_points); + res &= verify_min_max(cfg.min_width_for_outline_support, cfg.max_width_for_center_support_line); // check histeresis + res &= verify_max(cfg.max_length_for_one_support_point, + 2 * cfg.max_distance + + 2 * cfg.head_radius + + 2 * cfg.minimal_distance_from_outline); + res &= verify_min(cfg.max_length_for_one_support_point, + 2 * cfg.head_radius + 2 * cfg.minimal_distance_from_outline); + res &= verify_max(cfg.max_length_for_two_support_points, + 2 * cfg.max_distance + + 2 * 2 * cfg.head_radius + + 2 * cfg.minimal_distance_from_outline); + res &= verify_min(cfg.max_width_for_center_support_line, + 2 * cfg.head_radius + 2 * cfg.minimal_distance_from_outline); + res &= verify_max(cfg.max_width_for_center_support_line, + 2 * cfg.max_distance + 2 * cfg.head_radius); + if (!res) while (!verify(cfg)); + return res; +} + +SampleConfig SampleConfigFactory::create(float support_head_diameter_in_mm) +{ + coord_t head_diameter = static_cast(scale_(support_head_diameter_in_mm)); + coord_t minimal_distance = head_diameter * 7; + coord_t min_distance = head_diameter / 2 + minimal_distance; + coord_t max_distance = 3 * min_distance; + + // TODO: find valid params !!!! + SampleConfig result; + result.max_distance = max_distance; + result.half_distance = result.max_distance / 2; + result.head_radius = head_diameter / 2; + result.minimal_distance_from_outline = result.head_radius; + result.maximal_distance_from_outline = result.max_distance/3; + assert(result.minimal_distance_from_outline < result.maximal_distance_from_outline); + result.minimal_support_distance = result.minimal_distance_from_outline + + result.half_distance; + + result.min_side_branch_length = 2 * result.minimal_distance_from_outline + result.max_distance/4; + result.max_length_for_one_support_point = + max_distance / 3 + + 2 * result.minimal_distance_from_outline + + head_diameter; + result.max_length_for_two_support_points = + result.max_length_for_one_support_point + max_distance / 2; + result.max_width_for_center_support_line = + 2 * head_diameter + 2 * result.minimal_distance_from_outline + + max_distance / 2; + result.min_width_for_outline_support = result.max_width_for_center_support_line - 2 * head_diameter; + result.outline_sample_distance = 3*result.max_distance/4; + + // Align support points + // TODO: propagate print resolution + result.minimal_move = scale_(0.1); // 0.1 mm is enough + // [in nanometers --> 0.01mm ], devide from print resolution to quater pixel is too strict + result.count_iteration = 30; // speed VS precission + result.max_align_distance = result.max_distance / 2; + + verify(result); + return result; +} + +std::optional SampleConfigFactory::gui_sample_config_opt; +SampleConfig &SampleConfigFactory::get_sample_config() { + // init config + if (!gui_sample_config_opt.has_value()) + // create default configuration + gui_sample_config_opt = sla::SampleConfigFactory::create(.4f); + return *gui_sample_config_opt; +} diff --git a/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp b/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp index 1f046364c2..762798a0b6 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp +++ b/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp @@ -1,8 +1,9 @@ #ifndef slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_ #define slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_ +#include #include "SampleConfig.hpp" -#include "../SupportPointGenerator.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r::sla { @@ -14,78 +15,15 @@ class SampleConfigFactory public: SampleConfigFactory() = delete; - // factory method to iniciate config - static SampleConfig create(const SupportPointGeneratorConfig &config) - { - coord_t head_diameter = scale_((double)config.head_diameter.min); - coord_t minimal_distance = head_diameter * 7; - coord_t min_distance = head_diameter / 2 + minimal_distance; - coord_t max_distance = 3 * min_distance; - - // TODO: find valid params !!!! - SampleConfig result; - result.max_distance = max_distance; - result.half_distance = result.max_distance / 2; - result.head_radius = head_diameter / 2; - result.minimal_distance_from_outline = result.head_radius; - result.maximal_distance_from_outline = result.max_distance/3; - assert(result.minimal_distance_from_outline < result.maximal_distance_from_outline); - result.minimal_support_distance = result.minimal_distance_from_outline + - result.half_distance; + static bool verify(SampleConfig &cfg); + static SampleConfig create(float support_head_diameter_in_mm); - result.min_side_branch_length = 2 * result.minimal_distance_from_outline; - - result.max_length_for_one_support_point = - 2 * result.minimal_distance_from_outline + - head_diameter; - coord_t max_length_for_one_support_point = - 2 * max_distance + - head_diameter + - 2 * result.minimal_distance_from_outline; - if (result.max_length_for_one_support_point > max_length_for_one_support_point) - result.max_length_for_one_support_point = max_length_for_one_support_point; - coord_t min_length_for_one_support_point = - 2 * head_diameter + - 2 * result.minimal_distance_from_outline; - if (result.max_length_for_one_support_point < min_length_for_one_support_point) - result.max_length_for_one_support_point = min_length_for_one_support_point; - - result.max_length_for_two_support_points = - 2 * max_distance + 2 * head_diameter + - 2 * result.minimal_distance_from_outline; - coord_t max_length_for_two_support_points = - 2 * max_distance + - 2 * head_diameter + - 2 * result.minimal_distance_from_outline; - if (result.max_length_for_two_support_points > max_length_for_two_support_points) - result.max_length_for_two_support_points = max_length_for_two_support_points; - assert(result.max_length_for_two_support_points > result.max_length_for_one_support_point); - - result.max_width_for_center_support_line = - 2 * head_diameter + 2 * result.minimal_distance_from_outline + - max_distance / 2; - coord_t min_width_for_center_support_line = head_diameter + 2 * result.minimal_distance_from_outline; - if (result.max_width_for_center_support_line < min_width_for_center_support_line) - result.max_width_for_center_support_line = min_width_for_center_support_line; - coord_t max_width_for_center_support_line = 2 * max_distance + head_diameter; - if (result.max_width_for_center_support_line > max_width_for_center_support_line) - result.max_width_for_center_support_line = max_width_for_center_support_line; - - result.min_width_for_outline_support = result.max_width_for_center_support_line - 2 * head_diameter; - assert(result.min_width_for_outline_support <= result.max_width_for_center_support_line); - - result.outline_sample_distance = 3*result.max_distance/4; - - // Align support points - // TODO: propagate print resolution - result.minimal_move = scale_(0.1); // 0.1 mm is enough - // [in nanometers --> 0.01mm ], devide from print resolution to quater pixel is too strict - result.count_iteration = 30; // speed VS precission - result.max_align_distance = result.max_distance / 2; - - return result; - } +private: + // TODO: REMOVE IT. Do not use in production + // Global variable to temporary set configuration from GUI into SLA print steps + static std::optional gui_sample_config_opt; +public: + static SampleConfig &get_sample_config(); }; - } // namespace Slic3r::sla #endif // slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp index 7346773871..670bc71080 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp +++ b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp @@ -57,26 +57,84 @@ const ExPolygon &get_expolygon_with_biggest_contour(const ExPolygons &expolygons } return *biggest; } + +/// +/// When radius of all points is smaller than max radius set output center and return true +/// +/// +/// +/// +/// True when Bounding box of points is smaller than max radius +bool get_center(const Points &points, coord_t max_radius, Point& output_center){ + if (points.size()<=2) + return false; + auto it = points.begin(); + Point min = *it; + Point max = *it; + for (++it; it != points.end(); ++it) { + if (min.x() > it->x()) { + min.x() = it->x(); + if (max.x() - min.x() > max_radius) + return false; + } else if(max.x() < it->x()) { + max.x() = it->x(); + if (max.x() - min.x() > max_radius) + return false; + } + if (min.y() > it->y()) { + min.y() = it->y(); + if (max.y() - min.y() > max_radius) + return false; + } else if (max.y() < it->y()) { + max.y() = it->y(); + if (max.y() - min.y() > max_radius) + return false; + } + } + + // prevent overflow of point range, no care about 1 size + output_center = min/2 + max/2; + return true; +} + +/// +/// Decrease level of detail +/// +/// Polygon to reduce count of points +/// Define progressivness of reduction +/// Simplified island +ExPolygon get_simplified(const ExPolygon &island, const SampleConfig &config) { + //// closing similar to FDM arachne do before voronoi inspiration in make_expolygons inside TriangleMeshSlicer + //float closing_radius = scale_(0.0499f); + //float offset_out = closing_radius; + //float offset_in = -closing_radius; + //ExPolygons closed_expolygons = offset2_ex({island}, offset_out, offset_in); // mitter + //ExPolygon closed_expolygon = get_expolygon_with_biggest_contour(closed_expolygons); + //// "Close" operation still create neighbor pixel for sharp triangle tip - cause VD issues + + ExPolygons simplified_expolygons = island.simplify(config.simplification_tolerance); + return simplified_expolygons.empty() ? + island : get_expolygon_with_biggest_contour(simplified_expolygons); +} + } // namespace SupportIslandPoints SampleIslandUtils::uniform_cover_island( const ExPolygon &island, const SampleConfig &config ) { - // closing similar to FDM arachne do before voronoi - // inspired by make_expolygons inside TriangleMeshSlicer - float closing_radius = scale_(0.0499f); - float offset_out = closing_radius; - float offset_in = -closing_radius; - ExPolygons closed_expolygons = offset2_ex({island}, offset_out, offset_in); // mitter - ExPolygon closed_expolygon = get_expolygon_with_biggest_contour(closed_expolygons); + ExPolygon simplified_island = get_simplified(island, config); - // "Close" operation create neighbor pixel for sharp triangle tip - double tolerance = scale_(0.05); - ExPolygons simplified_expolygons = island.simplify(tolerance); - ExPolygon simplified_expolygon = get_expolygon_with_biggest_contour(simplified_expolygons); + // When island is smaller than minimal-head diameter, + // it will be supported whole by support poin in center + if (Point center; get_center(simplified_island.contour.points, config.head_radius, center)) { + SupportIslandPoints result; + result.push_back(std::make_unique( + center, SupportIslandInnerPoint::Type::one_bb_center_point)); + return result; + } Slic3r::Geometry::VoronoiDiagram vd; - Lines lines = to_lines(simplified_expolygon); + Lines lines = to_lines(simplified_island); vd.construct_voronoi(lines.begin(), lines.end()); Slic3r::Voronoi::annotate_inside_outside(vd, lines); VoronoiGraph skeleton = VoronoiGraphUtils::create_skeleton(vd, lines); @@ -976,7 +1034,6 @@ SupportIslandPoints SampleIslandUtils::sample_voronoi_graph( // every island has to have a point on contour assert(start_node != nullptr); longest_path = VoronoiGraphUtils::create_longest_path(start_node); - // longest_path = create_longest_path_recursive(start_node); #ifdef SLA_SAMPLE_ISLAND_UTILS_STORE_VORONOI_GRAPH_TO_SVG_PATH { diff --git a/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp b/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp index bad9b628f3..2348b84b5f 100644 --- a/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp +++ b/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp @@ -34,6 +34,7 @@ public: outline, // keep position align with island outline inner, // point inside wide part, without restriction on move + one_bb_center_point, // for island smaller than head radius undefined }; diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 99c8506a12..66236df764 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -10,7 +10,6 @@ #include "libslic3r/KDTreeIndirect.hpp" // SupportIslands -#include "libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp" #include "libslic3r/SLA/SupportIslands/SampleIslandUtils.hpp" using namespace Slic3r; @@ -232,7 +231,6 @@ void support_part_overhangs( (const LayerSupportPoint &support_point, const Point &p) -> bool { // Debug visualization of all sampled outline //return false; - coord_t r = support_point.current_radius; Point dp = support_point.position_on_layer - p; if (std::abs(dp.x()) > r) return false; @@ -269,9 +267,8 @@ void support_part_overhangs( /// z coordinate of part /// void support_island(const LayerPart &part, NearPoints& near_points, float part_z, - const SupportPointGeneratorConfig &cfg) { - SampleConfig sample_cfg = SampleConfigFactory::create(cfg); - SupportIslandPoints samples = SampleIslandUtils::uniform_cover_island(*part.shape, sample_cfg); + const SupportPointGeneratorConfig &cfg) { + SupportIslandPoints samples = SampleIslandUtils::uniform_cover_island(*part.shape, cfg.island_configuration); //samples = {std::make_unique(island.contour.centroid())}; for (const SupportIslandPointPtr &sample : samples) near_points.add(LayerSupportPoint{ @@ -443,6 +440,12 @@ Points sample_overhangs(const LayerPart& part, double dist2) { void prepare_supports_for_layer(LayerSupportPoints &supports, float layer_z, const SupportPointGeneratorConfig &config) { + auto set_radius = [&config](LayerSupportPoint &support, float radius) { + if (!is_approx(config.density_relative, 1.f, 1e-4f)) // exist relative density + radius /= config.density_relative; + support.current_radius = static_cast(scale_(radius)); + }; + const std::vector& curve = config.support_curve; // calculate support area for each support point as radius // IMPROVE: use some offsets of previous supported island @@ -458,7 +461,7 @@ void prepare_supports_for_layer(LayerSupportPoints &supports, float layer_z, if ((index+1) >= curve.size()) { // set maximal radius - support.current_radius = static_cast(scale_(curve.back().x())); + set_radius(support, curve.back().x()); continue; } // interpolate radius on input curve @@ -467,7 +470,7 @@ void prepare_supports_for_layer(LayerSupportPoints &supports, float layer_z, assert(a.y() <= diff_z && diff_z <= b.y()); float t = (diff_z - a.y()) / (b.y() - a.y()); assert(0 <= t && t <= 1); - support.current_radius = static_cast(scale_(a.x() + t * (b.x() - a.x()))); + set_radius(support, a.x() + t * (b.x() - a.x())); } } diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index ed310c7ca9..9abd0c9962 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -13,6 +13,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/ExPolygon.hpp" #include "libslic3r/SLA/SupportPoint.hpp" +#include "libslic3r/SLA/SupportIslands/SampleConfig.hpp" namespace Slic3r::sla { @@ -31,7 +32,7 @@ struct SupportPointGeneratorConfig{ /// /// Size range for support point interface (head) /// - MinMax head_diameter = {0.2f, 0.6f}; // [in mm] + float head_diameter = 0.4f; // [in mm] // FIXME: calculate actual pixel area from printer config: // const float pixel_area = @@ -46,6 +47,9 @@ struct SupportPointGeneratorConfig{ // y axis .. mean difference of height(Z) // Points of lines [in mm] std::vector support_curve; + + // Configuration for sampling island + SampleConfig island_configuration; }; struct LayerPart; // forward decl. diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index ec5d55719e..5cf8f98aaf 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -53,6 +53,7 @@ #include "libslic3r/SLA/RasterBase.hpp" #include "libslic3r/SLA/SupportTree.hpp" #include "libslic3r/SLA/SupportTreeStrategies.hpp" +#include "libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -627,99 +628,104 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) BOOST_LOG_TRIVIAL(debug) << "Support point count " << mo.sla_support_points.size(); - // Unless the user modified the points or we already did the calculation, - // we will do the autoplacement. Otherwise we will just blindly copy the - // frontend data into the backend cache. - if (mo.sla_points_status != sla::PointsStatus::UserModified) { - throw_if_canceled(); - sla::SupportPointGeneratorConfig config; - const SLAPrintObjectConfig& cfg = po.config(); - - // the density config value is in percents: - config.density_relative = float(cfg.support_points_density_relative / 100.f); - - switch (cfg.support_tree_type) { - case sla::SupportTreeType::Default: - case sla::SupportTreeType::Organic: - config.head_diameter = {float(cfg.support_head_front_diameter), .0}; - break; - case sla::SupportTreeType::Branching: - config.head_diameter = {float(cfg.branchingsupport_head_front_diameter), .0}; - break; - } - - // scaling for the sub operations - double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; - double init = current_status(); - - auto statuscb = [this, d, init](unsigned st) - { - double current = init + st * d; - if(std::round(current_status()) < std::round(current)) - report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); - }; - - // Construction of this object does the calculation. - throw_if_canceled(); - - // TODO: filter small unprintable islands in slices - // (Island with area smaller than 1 pixel was skipped in support generator) - - std::vector slices = po.get_model_slices(); // copy - const std::vector& heights = po.m_model_height_levels; - sla::ThrowOnCancel cancel = [this]() { throw_if_canceled(); }; - sla::StatusFunction status = statuscb; - sla::SupportPointGeneratorData data = - sla::prepare_generator_data(std::move(slices), heights, cancel, status); - - sla::LayerSupportPoints layer_support_points = - sla::generate_support_points(data, config, cancel, status); - - const AABBMesh& emesh = po.m_supportdata->input.emesh; - // Maximal move of support point to mesh surface, - // no more than height of layer - assert(po.m_model_height_levels.size() > 1); - double allowed_move = (po.m_model_height_levels[1] - po.m_model_height_levels[0]) + - std::numeric_limits::epsilon(); - sla::SupportPoints support_points = - sla::move_on_mesh_surface(layer_support_points, emesh, allowed_move, cancel); - - throw_if_canceled(); - - MeshSlicingParamsEx params; - params.closing_radius = float(po.config().slice_closing_radius.value); - std::vector blockers = - slice_volumes(po.model_object()->volumes, - po.m_model_height_levels, po.trafo(), params, - [](const ModelVolume *vol) { - return vol->is_support_blocker(); - }); - - std::vector enforcers = - slice_volumes(po.model_object()->volumes, - po.m_model_height_levels, po.trafo(), params, - [](const ModelVolume *vol) { - return vol->is_support_enforcer(); - }); - - SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()}; - filter_support_points_by_modifiers(support_points, mask, po.m_model_height_levels); - - po.m_supportdata->input.pts = support_points; - - BOOST_LOG_TRIVIAL(debug) - << "Automatic support points: " - << po.m_supportdata->input.pts.size(); - - // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass - // the update status to GLGizmoSlaSupports - report_status(-1, _u8L("Generating support points"), - SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); - } else { + if (mo.sla_points_status == sla::PointsStatus::UserModified) { // There are either some points on the front-end, or the user // removed them on purpose. No calculation will be done. po.m_supportdata->input.pts = po.transformed_support_points(); + return; } + // Unless the user modified the points or we already did the calculation, + // we will do the autoplacement. Otherwise we will just blindly copy the + // frontend data into the backend cache. + // if (mo.sla_points_status != sla::PointsStatus::UserModified) + + throw_if_canceled(); + const SLAPrintObjectConfig& cfg = po.config(); + + // the density config value is in percents: + sla::SupportPointGeneratorConfig config; + config.density_relative = float(cfg.support_points_density_relative / 100.f); + + switch (cfg.support_tree_type) { + case sla::SupportTreeType::Default: + case sla::SupportTreeType::Organic: + config.head_diameter = float(cfg.support_head_front_diameter); + break; + case sla::SupportTreeType::Branching: + config.head_diameter = float(cfg.branchingsupport_head_front_diameter); + break; + } + + // copy current configuration for sampling islands + config.island_configuration = sla::SampleConfigFactory::get_sample_config(); + + // scaling for the sub operations + double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; + double init = current_status(); + + auto statuscb = [this, d, init](unsigned st) + { + double current = init + st * d; + if(std::round(current_status()) < std::round(current)) + report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); + }; + + // Construction of this object does the calculation. + throw_if_canceled(); + + // TODO: filter small unprintable islands in slices + // (Island with area smaller than 1 pixel was skipped in support generator) + + std::vector slices = po.get_model_slices(); // copy + const std::vector& heights = po.m_model_height_levels; + sla::ThrowOnCancel cancel = [this]() { throw_if_canceled(); }; + sla::StatusFunction status = statuscb; + sla::SupportPointGeneratorData data = + sla::prepare_generator_data(std::move(slices), heights, cancel, status); + + sla::LayerSupportPoints layer_support_points = + sla::generate_support_points(data, config, cancel, status); + + const AABBMesh& emesh = po.m_supportdata->input.emesh; + // Maximal move of support point to mesh surface, + // no more than height of layer + assert(po.m_model_height_levels.size() > 1); + double allowed_move = (po.m_model_height_levels[1] - po.m_model_height_levels[0]) + + std::numeric_limits::epsilon(); + sla::SupportPoints support_points = + sla::move_on_mesh_surface(layer_support_points, emesh, allowed_move, cancel); + + throw_if_canceled(); + + MeshSlicingParamsEx params; + params.closing_radius = float(po.config().slice_closing_radius.value); + std::vector blockers = + slice_volumes(po.model_object()->volumes, + po.m_model_height_levels, po.trafo(), params, + [](const ModelVolume *vol) { + return vol->is_support_blocker(); + }); + + std::vector enforcers = + slice_volumes(po.model_object()->volumes, + po.m_model_height_levels, po.trafo(), params, + [](const ModelVolume *vol) { + return vol->is_support_enforcer(); + }); + + SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()}; + filter_support_points_by_modifiers(support_points, mask, po.m_model_height_levels); + + po.m_supportdata->input.pts = support_points; + + BOOST_LOG_TRIVIAL(debug) + << "Automatic support points: " + << po.m_supportdata->input.pts.size(); + + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass + // the update status to GLGizmoSlaSupports + report_status(-1, _u8L("Generating support points"), + SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } void SLAPrint::Steps::support_tree(SLAPrintObject &po) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index ebcbe1747c..3ec1b2679c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -22,6 +22,8 @@ #include "libslic3r/PresetBundle.hpp" #include "libslic3r/SLAPrint.hpp" +#include "libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp" + static const double CONE_RADIUS = 0.25; static const double CONE_HEIGHT = 0.75; @@ -677,6 +679,92 @@ RENDER_AGAIN: } else { // not in editing mode: m_imgui->disabled_begin(!is_input_enabled()); + if (int density = static_cast(get_config_options({"support_points_density_relative"})[0])->value; + ImGui::SliderInt("points_density", &density, 0, 200, "%d \%")) { + mo->config.set("support_points_density_relative", density); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Divider for the supported radius\nSmaller mean less point(75% -> supported radius is enlaged to 133%, for 50% it is 200% of radius)\nLarger mean more points(125% -> supported radius is reduced to 80%, for value 150% it is 66% of radius, for 200% -> 50%)"); + } + + if (ImGui::TreeNode("Support islands:")) { + sla::SampleConfig &sample_config = sla::SampleConfigFactory::get_sample_config(); + bool exist_change = false; + if (float simplification_tolerance = unscale(sample_config.simplification_tolerance); // [in mm] + ImGui::InputFloat("input simplify", &simplification_tolerance, .1f, 1.f, "%.2f mm")) { + sample_config.simplification_tolerance = scale_(simplification_tolerance); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("There is no need to calculate with precisse island\nNOTE: Slice of Cylinder bottom has tip of trinagles on contour\n(neighbor coordinate -> create issue in boost::voronoi)"); + if (float max_distance = unscale(sample_config.max_distance); // [in mm] + ImGui::InputFloat("Max dist", &max_distance, .1f, 1.f, "%.2f mm")) { + sample_config.max_distance = scale_(max_distance); + sample_config.half_distance = sample_config.max_distance / 2; + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Every support point on island has at least one support point in maximum distance\nMUST be bigger than zero"); + ImGui::SameLine(); ImGui::Text("half is %.2f", unscale(sample_config.half_distance)); + if (float minimal_distance_from_outline = unscale(sample_config.minimal_distance_from_outline); // [in mm] + ImGui::InputFloat("from outline", &minimal_distance_from_outline, .1f, 1.f, "%.2f mm")) { + sample_config.minimal_distance_from_outline = scale_(minimal_distance_from_outline); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("When it is possible, there will be this minimal distance from outline.\nZERO when head center should be on outline\nSHOULD be positive number"); + ImGui::SameLine(); + if (float maximal_distance_from_outline = unscale(sample_config.maximal_distance_from_outline); // [in mm] + ImGui::InputFloat("max from outline", &maximal_distance_from_outline, .1f, 1.f, "%.2f mm")) { + sample_config.maximal_distance_from_outline = scale_(maximal_distance_from_outline); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Measured as sum of VD edge length from outline\nUsed only when there is no space for outline offset on first/last point\nMust be bigger than minimal_distance_from_outline"); + ImGui::Text("max_interesting_angle is %.0f", float(sample_config.max_interesting_angle*180/M_PI)); + if (ImGui::IsItemHovered()) ImGui::SetTooltip(" When angle on outline is smaller than max_interesting_angle\nthan create unmovable support point.\nShould be in range from 90 to 180"); + if (float minimal_support_distance = unscale(sample_config.minimal_support_distance); // [in mm] + ImGui::InputFloat("Thin dist", &minimal_support_distance, .1f, 1.f, "%.2f mm")) { + sample_config.minimal_support_distance = scale_(minimal_support_distance); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Distinguish when to add support point on Voronoi Diagram\nMUST be bigger than minimal_distance_from_outline\nSmaller -> more supports AND Larger -> less amount"); + if (float min_side_branch_length = unscale(sample_config.min_side_branch_length); // [in mm] + ImGui::InputFloat("min_side_branch_length", &min_side_branch_length, .1f, 1.f, "%.2f mm")) { + sample_config.min_side_branch_length = scale_(min_side_branch_length); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("minimal length of side branch to be sampled\nit is used for sampling in center only"); + if (float max_for_one = unscale(sample_config.max_length_for_one_support_point); // [in mm] + ImGui::InputFloat("Max len for one", &max_for_one, .1f, 1.f, "%.2f mm")) { + sample_config.max_length_for_one_support_point = scale_(max_for_one); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Maximal island length (longest voronoi path) for support by point in path center"); + if (float max_for_two = unscale(sample_config.max_length_for_two_support_points); // [in mm] + ImGui::InputFloat("Max len for two", &max_for_two, .1f, 1.f, "%.2f mm")) { + sample_config.max_length_for_two_support_points = scale_(max_for_two); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Maximal island length (longest voronoi path)\n for support by 2 points on path sides"); + if (float max_width_for_center_support_line = unscale(sample_config.max_width_for_center_support_line); // [in mm] + ImGui::InputFloat("thin max width", &max_width_for_center_support_line, .1f, 1.f, "%.2f mm")) { + sample_config.max_width_for_center_support_line = scale_(max_width_for_center_support_line); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Maximal width of line island supported in the middle of line\nMust be greater or equal to thick min width(to make hysteresis)"); + if (float min_width_for_outline_support = unscale(sample_config.min_width_for_outline_support); // [in mm] + ImGui::InputFloat("thick min width", &min_width_for_outline_support, .1f, 1.f, "%.2f mm")) { + sample_config.min_width_for_outline_support = scale_(min_width_for_outline_support); + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Minimal width to be supported by outline\nMust be smaller or equal to thin max width(to make hysteresis)"); + + ImGui::Text("head radius is set to %.2f", unscale(sample_config.head_radius)); + ImGui::Text("Alignment stop criteria: min_move(%.0f um), iter(%d x), max_VD_move(%.2f mm)", unscale(sample_config.minimal_move)*1000, sample_config.count_iteration, + unscale(sample_config.max_align_distance) + ); + + if (exist_change){ + sla::SampleConfigFactory::verify(sample_config); + } + ImGui::TreePop(); + } ImGui::Text("Distribution depends on './resources/data/sla_support.svg'\ninstruction for edit are in file"); @@ -720,9 +808,7 @@ RENDER_AGAIN: // wxGetApp().obj_list()->update_and_show_object_settings_item(); //} - bool generate = ImGuiPureWrap::button(m_desc.at("auto_generate")); - - if (generate) + if (ImGuiPureWrap::button(m_desc.at("auto_generate"))) auto_generate(); ImGui::Separator(); diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index bcc8e388df..470c1a6ce3 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -17,7 +18,7 @@ using namespace Slic3r; using namespace Slic3r::sla; -#define STORE_SAMPLE_INTO_SVG_FILES +//#define STORE_SAMPLE_INTO_SVG_FILES TEST_CASE("Overhanging point should be supported", "[SupGen]") { @@ -304,9 +305,23 @@ ExPolygon create_mountains(double size) { {size / 7, size}}); } +/// Neighbor points create trouble for voronoi - test of neccessary offseting(closing) of contour +ExPolygon create_cylinder_bottom_slice() { + indexed_triangle_set its_cylinder = its_make_cylinder(6.6551999999999998, 11.800000000000001); + MeshSlicingParams param; + Polygons polygons = slice_mesh(its_cylinder, 0.0125000002, param); + return ExPolygon{polygons.front()}; +} + +ExPolygon load_frog(){ + TriangleMesh mesh = load_model("frog_legs.obj"); + std::vector slices = slice_mesh_ex(mesh.its, {0.1f}); + return slices.front()[1]; +} + ExPolygons createTestIslands(double size) { - bool useFrogLeg = false; + bool useFrogLeg = false; // need post reorganization of longest path ExPolygons result = { // one support point @@ -338,6 +353,9 @@ ExPolygons createTestIslands(double size) create_tiny_wide_test_2(3 * size, 2 / 3. * size), create_tiny_between_holes(3 * size, 2 / 3. * size), + ExPolygon(PolygonUtils::create_equilateral_triangle(scale_(18.6))), + create_cylinder_bottom_slice(), + // still problem // three support points ExPolygon(PolygonUtils::create_equilateral_triangle(3 * size)), @@ -347,14 +365,9 @@ ExPolygons createTestIslands(double size) create_trinagle_with_hole(size), create_square_with_hole(size, size / 2), create_square_with_hole(size, size / 3) - }; - - if (useFrogLeg) { - TriangleMesh mesh = load_model("frog_legs.obj"); - std::vector slices = slice_mesh_ex(mesh.its, {0.1f}); - ExPolygon frog_leg = slices.front()[1]; - result.push_back(frog_leg); - } + }; + if (useFrogLeg) + result.push_back(load_frog()); return result; } @@ -403,7 +416,7 @@ Points rasterize(const ExPolygon &island, double distance) { SupportIslandPoints test_island_sampling(const ExPolygon & island, const SampleConfig &config) { - auto points = SupportPointGenerator::uniform_cover_island(island, config); + auto points = SampleIslandUtils::uniform_cover_island(island, config); Points chck_points = rasterize(island, config.head_radius); // TODO: Use resolution of printer bool is_ok = true; @@ -447,7 +460,7 @@ SupportIslandPoints test_island_sampling(const ExPolygon & island, } } CHECK(!points.empty()); - //CHECK(is_ok); + CHECK(is_ok); // all points must be inside of island for (const auto &point : points) { CHECK(island.contains(point->point)); } @@ -539,12 +552,6 @@ TEST_CASE("speed sampling", "[hide], [SupGen]") { size_t count = 1; - std::vector> result1; - result1.reserve(islands.size()*count); - for (size_t i = 0; i> result2; result2.reserve(islands.size()*count); for (size_t i = 0; i < count; ++i) @@ -560,7 +567,7 @@ TEST_CASE("speed sampling", "[hide], [SupGen]") { #ifdef STORE_SAMPLE_INTO_SVG_FILES - for (size_t i = 0; i < result1.size(); ++i) { + for (size_t i = 0; i < result2.size(); ++i) { size_t island_index = i % islands.size(); ExPolygon &island = islands[island_index]; @@ -568,9 +575,6 @@ TEST_CASE("speed sampling", "[hide], [SupGen]") { std::string name = "sample_" + std::to_string(i) + ".svg"; SVG svg(name, LineUtils::create_bounding_box(lines)); svg.draw(island, "lightgray"); - svg.draw_text({0, 0}, ("random samples " + std::to_string(result1[i].size())).c_str(), "blue"); - for (Vec2f &p : result1[i]) - svg.draw((p * 1e6).cast(), "blue", 1e6); svg.draw_text({0., 5e6}, ("uniform samples " + std::to_string(result2[i].size())).c_str(), "green"); for (Vec2f &p : result2[i]) svg.draw((p * 1e6).cast(), "green", 1e6); @@ -580,16 +584,17 @@ TEST_CASE("speed sampling", "[hide], [SupGen]") { /// /// Check for correct sampling of island -/// /// TEST_CASE("Small islands should be supported in center", "[SupGen], [VoronoiSkeleton]") { - double size = 3e7; - SampleConfig cfg = create_sample_config(size); - ExPolygons islands = createTestIslands(size); + float head_diameter = .4f; + SampleConfig cfg = SampleConfigFactory::create(head_diameter); + ExPolygons islands = createTestIslands(21 * scale_(head_diameter)); for (ExPolygon &island : islands) { // information for debug which island cause problem [[maybe_unused]] size_t debug_index = &island - &islands.front(); + + // TODO: index 17 - create field again auto points = test_island_sampling(island, cfg); double angle = 3.14 / 3; // cca 60 degree @@ -601,46 +606,31 @@ TEST_CASE("Small islands should be supported in center", "[SupGen], [VoronoiSkel } } -std::vector sample_old(const ExPolygon &island) -{ - // Create the support point generator - static TriangleMesh mesh; - static AABBMesh emesh(mesh); - static sla::SupportPointGenerator::Config autogencfg; - //autogencfg.minimal_distance = 8.f; - static sla::SupportPointGenerator generator{emesh, autogencfg, [] {}, [](int) {}}; - - // tear preasure - float tp = autogencfg.tear_pressure(); - size_t layer_id = 13; - coordf_t print_z = 11.f; - SupportPointGenerator::MyLayer layer(layer_id, print_z); - ExPolygon poly = island; - BoundingBox bbox(island.contour.points); - Vec2f centroid; - float area = island.area(); - float h = 17.f; - sla::SupportPointGenerator::Structure s(layer, poly, bbox, centroid,area,h); - auto flag = sla::SupportPointGenerator::IslandCoverageFlags( - sla::SupportPointGenerator::icfIsNew | sla::SupportPointGenerator::icfWithBoundary); - SupportPointGenerator::PointGrid3D grid3d; - generator.uniformly_cover({island}, s, s.area * tp, grid3d, flag); - - std::vector result; - result.reserve(grid3d.grid.size()); - for (auto g : grid3d.grid) { - const Vec3f &p = g.second.position; - Vec2f p2f(p.x(), p.y()); - result.emplace_back(scale_(p2f)); - } - return result; -} +//TEST_CASE("Cell polygon check", "") { +// coord_t max_distance = 9; +// Points points{Point{0,0}, Point{10,0}}; +// using VD = Slic3r::Geometry::VoronoiDiagram; +// VD vd; +// vd.construct_voronoi(points.begin(), points.end()); +// assert(points.size() == vd.cells().size()); +// Polygons cells(points.size()); +// for (const VD::cell_type &cell : vd.cells()) +// cells[cell.source_index()] = VoronoiGraphUtils::to_polygon(cell, points, max_distance); +// +// REQUIRE(cells[0].size() >= 3); +// REQUIRE(cells[1].size() >= 3); +// Polygons cell_overlaps = intersection(cells[0], cells[1]); +// double area = 0; +// for (const Polygon &cell_overlap : cell_overlaps) +// area += cell_overlap.area(); +// CHECK(area < 1); +//} #include std::vector sample_filip(const ExPolygon &island) { static SampleConfig cfg = create_sample_config(1e6); - SupportIslandPoints points = SupportPointGenerator::uniform_cover_island(island, cfg); + SupportIslandPoints points = SampleIslandUtils::uniform_cover_island(island, cfg); std::vector result; result.reserve(points.size()); @@ -675,16 +665,8 @@ void store_sample(const std::vector &samples, const ExPolygon& island) } TEST_CASE("Compare sampling test", "[hide]") -{ - enum class Sampling { - old, - filip - } sample_type = Sampling::old; - - std::function(const ExPolygon &)> sample = - (sample_type == Sampling::old) ? sample_old : - (sample_type == Sampling::filip) ? sample_filip : - nullptr; +{ + std::function(const ExPolygon &)> sample = sample_filip; ExPolygons islands = createTestIslands(1e6); ExPolygons islands_big = createTestIslands(3e6); islands.insert(islands.end(), islands_big.begin(), islands_big.end()); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 94d77e1dff..17331335da 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -129,8 +129,7 @@ void test_supports(const std::string &obj_filename, // Create the support point generator sla::SupportPointGeneratorConfig autogencfg; - float head_diam = 2 * supportcfg.head_front_radius_mm; - autogencfg.head_diameter = {head_diam, head_diam}; + autogencfg.head_diameter = 2 * supportcfg.head_front_radius_mm; sla::ThrowOnCancel cancel = []() {}; sla::StatusFunction status = [](int) {}; sla::SupportPointGeneratorData gen_data = sla::prepare_generator_data(std::move(out.model_slices), out.slicegrid, cancel, status);