diff --git a/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp index c82ce54d29..d88e9cb684 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp +++ b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp @@ -76,6 +76,9 @@ struct SampleConfig // NOTE: Slice of Cylinder bottom has tip of trinagles on contour // (neighbor coordinate - create issue in voronoi) double simplification_tolerance = scale_(0.05 /*mm*/); + + // Only for debug purposes + std::string path = ""; // when set to empty string, no debug output is generated }; } // namespace Slic3r::sla #endif // slic3r_SLA_SuppotstIslands_SampleConfig_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp index 294f53c1d3..a3e466add1 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp +++ b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp @@ -124,6 +124,15 @@ SupportIslandPoints SampleIslandUtils::uniform_cover_island( ) { ExPolygon simplified_island = get_simplified(island, config); + if (!config.path.empty()) { + static int counter = 0; + std::string path = replace_first(config.path, "<>", std::to_string(++counter)); + SVG svg(path, BoundingBox{island.contour.points}); + svg.draw_original(island); + svg.draw(island, "lightgray"); + svg.draw(simplified_island, "gray"); + } + // 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)) { diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 01909354ef..5027627ccf 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -19,6 +19,7 @@ #include "libslic3r/Line.hpp" #include "libslic3r/Surface.hpp" #include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" namespace Slic3r { @@ -110,6 +111,19 @@ void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_op this->path(d, true, 0, fill_opacity); } +void SVG::draw_original(const ExPolygon &expolygon) { + std::ostringstream d; + auto write_d = [&d](const Points &pts) { + d << "M "; + for (const Point& p: pts) + d << p.x() << " " << p.y() << " "; + d << "z "; // closed path + }; + for (const Polygon &p : to_polygons(expolygon)) + write_d(p.points); + path(d.str(), false /*fill*/, 1 /*stroke_width*/, 0.f /*fill opacity*/); +} + void SVG::draw_outline(const ExPolygon &expolygon, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width) { draw_outline(expolygon.contour, stroke_outer, stroke_width); diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index 1de7f495c6..8d32b07604 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -96,6 +96,9 @@ public: void draw_text(const Point &pt, const char *text, const char *color, coordf_t font_size = 20.f); void draw_legend(const Point &pt, const char *text, const char *color, coordf_t font_size = 10.f); + // Draw no scaled expolygon coordinates + void draw_original(const ExPolygon &exPoly); + void Close(); private: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 651291b336..c2e9e469fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -23,7 +23,7 @@ #include "libslic3r/SLAPrint.hpp" #include "libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp" - +#include "imgui/imgui_stdlib.h" // string input for ImGui static const double CONE_RADIUS = 0.25; static const double CONE_HEIGHT = 0.75; @@ -686,85 +686,8 @@ RENDER_AGAIN: 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(); - } + draw_island_config(); + ImGui::Text("Distribution depends on './resources/data/sla_support.svg'\ninstruction for edit are in file"); @@ -886,6 +809,102 @@ RENDER_AGAIN: m_parent.set_as_dirty(); } +void GLGizmoSlaSupports::draw_island_config() { + if (!ImGui::TreeNode("Support islands:")) + return; // no need to draw configuration for islands + sla::SampleConfig &sample_config = sla::SampleConfigFactory::get_sample_config(); + bool store_islands = !sample_config.path.empty(); + if (ImGui::Checkbox("StoreIslands", &store_islands)) { + if (store_islands = true) + sample_config.path = "C:/data/temp/island<>.svg"; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Store islands in file\n<> is replaced by island order number"); + if (store_islands) { + ImGui::SameLine(); + std::string path; + ImGui::InputText("path", &sample_config.path); + } + + 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); + } + + // end of tree node + ImGui::TreePop(); +} + bool GLGizmoSlaSupports::on_is_activable() const { const Selection& selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 13087bf0fe..52560e13cb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -91,6 +91,8 @@ private: void unregister_point_raycasters_for_picking(); void update_point_raycasters_for_picking_transform(); + void draw_island_config(); + bool m_lock_unique_islands = false; bool m_editing_mode = false; // Is editing mode active? float m_new_point_head_diameter; // Size of a new point. diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index fa29ee4d9a..f221d337e4 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -12,7 +12,7 @@ #include #include #include - +#include "nanosvg/nanosvg.h" // load SVG file #include "sla_test_utils.hpp" using namespace Slic3r; @@ -318,11 +318,45 @@ ExPolygon load_frog(){ return slices.front()[1]; } +ExPolygon load_svg(const std::string& svg_filepath) { + struct NSVGimage *image = nsvgParseFromFile(svg_filepath.c_str(), "px", 96); + ScopeGuard sg_image([&image] { nsvgDelete(image); }); + + auto to_polygon = [](NSVGpath *path) { + Polygon r; + r.points.reserve(path->npts); + for (size_t i = 0; i < path->npts; i++) + r.points.push_back(Point(path->pts[2 * i], path->pts[2 * i + 1])); + return r; + }; + + for (NSVGshape *shape_ptr = image->shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next) { + const NSVGshape &shape = *shape_ptr; + if (!(shape.flags & NSVG_FLAGS_VISIBLE)) continue; // is visible + if (shape.fill.type != NSVG_PAINT_NONE) continue; // is not used fill + if (shape.stroke.type == NSVG_PAINT_NONE) continue; // exist stroke + //if (shape.strokeWidth < 1e-5f) continue; // is visible stroke width + //if (shape.stroke.color != 4278190261) continue; // is red + ExPolygon result; + for (NSVGpath *path = shape.paths; path != NULL; path = path->next) { + // Path order is reverse to path in file + if (path->next == NULL) // last path is contour + result.contour = to_polygon(path); + else + result.holes.push_back(to_polygon(path)); + } + return result; + } + REQUIRE(false); + return {}; +} + ExPolygons createTestIslands(double size) { bool useFrogLeg = false; // need post reorganization of longest path ExPolygons result = { + load_svg("C:/Users/Filip Sykala/Downloads/lm_issue.svg"), // one support point ExPolygon(PolygonUtils::create_equilateral_triangle(size)), ExPolygon(PolygonUtils::create_square(size)),