From b5e3b96764e6238e73b23c2484ecd942b0e5ab56 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 10 Jan 2025 09:13:10 +0800 Subject: [PATCH] ENH: add Clipper2::Union and fix a command line hang issue jira: STUDIO-9623 Change-Id: I8b40f66fed0fc9e5b13f0f10337267065fef1056 --- .../include/clipper2/clipper.engine.h | 3 +- src/libslic3r/Arachne/WallToolPaths.cpp | 4 +- src/libslic3r/Clipper2Utils.cpp | 77 ++++ src/libslic3r/Clipper2Utils.hpp | 4 +- src/libslic3r/SVG.cpp | 365 +++++++++++++++++- src/libslic3r/SVG.hpp | 8 + src/libslic3r/Support/TreeSupport.cpp | 16 +- 7 files changed, 461 insertions(+), 16 deletions(-) diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h index 67383f213..7a149233b 100644 --- a/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h @@ -295,7 +295,8 @@ namespace Clipper2Lib { typedef typename std::vector::const_iterator pp64_itor; public: PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} - PolyPath64* operator [] (size_t index) { return static_cast(childs_[index]); } + PolyPath64 *operator[](size_t index) { return static_cast(childs_[index]); } + PolyPath64 *Childs(size_t index) const { return static_cast(childs_[index]); } pp64_itor begin() const { return childs_.cbegin(); } pp64_itor end() const { return childs_.cend(); } diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp index 362cc7615..9cb90f3eb 100644 --- a/src/libslic3r/Arachne/WallToolPaths.cpp +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -781,7 +781,7 @@ const Polygons& WallToolPaths::getInnerContour() } return inner_contour; } - +Polygons EmptyPolygons; const Polygons& WallToolPaths::getFirstWallContour() { if (!toolpaths_generated && inset_count > 0) @@ -790,7 +790,7 @@ const Polygons& WallToolPaths::getFirstWallContour() } else if(inset_count == 0) { - return {}; + return EmptyPolygons; } return first_wall_contour; } diff --git a/src/libslic3r/Clipper2Utils.cpp b/src/libslic3r/Clipper2Utils.cpp index 92acd5e38..53b074fe7 100644 --- a/src/libslic3r/Clipper2Utils.cpp +++ b/src/libslic3r/Clipper2Utils.cpp @@ -1,4 +1,6 @@ #include "Clipper2Utils.hpp" +#include "libslic3r.h" +#include "clipper2/clipper.h" namespace Slic3r { @@ -33,6 +35,66 @@ Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const std::vector& in) return out; } +Points Path64ToPoints(const Clipper2Lib::Path64& path64) +{ + Points points; + points.reserve(path64.size()); + for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y))); + return points; +} + +static ExPolygons PolyTreeToExPolygons(Clipper2Lib::PolyTree64 &&polytree) +{ + struct Inner + { + static void PolyTreeToExPolygonsRecursive(Clipper2Lib::PolyTree64 &&polynode, ExPolygons *expolygons) + { + size_t cnt = expolygons->size(); + expolygons->resize(cnt + 1); + (*expolygons)[cnt].contour.points = Path64ToPoints(polynode.Polygon()); + + (*expolygons)[cnt].holes.resize(polynode.Count()); + for (int i = 0; i < polynode.Count(); ++i) { + (*expolygons)[cnt].holes[i].points = Path64ToPoints(polynode[i]->Polygon()); + // Add outer polygons contained by (nested within) holes. + for (int j = 0; j < polynode[i]->Count(); ++j) PolyTreeToExPolygonsRecursive(std::move(*polynode[i]->Childs(j)), expolygons); + } + } + + static size_t PolyTreeCountExPolygons(const Clipper2Lib::PolyPath64& polynode) + { + size_t cnt = 1; + for (size_t i = 0; i < polynode.Count(); ++i) { + for (size_t j = 0; j < polynode.Childs(i)->Count(); ++j) cnt += PolyTreeCountExPolygons(*polynode.Childs(i)->Childs(j)); + } + return cnt; + } + }; + + ExPolygons retval; + size_t cnt = 0; + for (int i = 0; i < polytree.Count(); ++i) cnt += Inner::PolyTreeCountExPolygons(*polytree[i]); + retval.reserve(cnt); + for (int i = 0; i < polytree.Count(); ++i) Inner::PolyTreeToExPolygonsRecursive(std::move(*polytree[i]), &retval); + return retval; +} + +Clipper2Lib::Paths64 Slic3rExPolygons_to_Paths64(const ExPolygons& in) +{ + Clipper2Lib::Paths64 out; + out.reserve(in.size()); + for (const ExPolygon& expolygon : in) { + for (size_t i = 0; i < expolygon.num_contours(); i++) { + const auto &poly = expolygon.contour_or_hole(i); + Clipper2Lib::Path64 path; + path.reserve(poly.points.size()); + for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y()))); + out.emplace_back(std::move(path)); + } + } + return out; +} + Polylines _clipper2_pl_open(Clipper2Lib::ClipType clipType, const Slic3r::Polylines& subject, const Slic3r::Polygons& clip) { Clipper2Lib::Clipper64 c; @@ -57,4 +119,19 @@ Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip) { return _clipper2_pl_open(Clipper2Lib::ClipType::Difference, subject, clip); } +ExPolygons union_ex2(const ExPolygons& expolygons) +{ + Clipper2Lib::Clipper64 c; + c.AddSubject(Slic3rExPolygons_to_Paths64(expolygons)); + + Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::Union; + Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero; + Clipper2Lib::PolyTree64 solution; + c.Execute(ct, fr, solution); + + ExPolygons results = PolyTreeToExPolygons(std::move(solution)); + + return results; +} + } \ No newline at end of file diff --git a/src/libslic3r/Clipper2Utils.hpp b/src/libslic3r/Clipper2Utils.hpp index f69421711..a612512f7 100644 --- a/src/libslic3r/Clipper2Utils.hpp +++ b/src/libslic3r/Clipper2Utils.hpp @@ -1,8 +1,6 @@ #ifndef slic3r_Clipper2Utils_hpp_ #define slic3r_Clipper2Utils_hpp_ -#include "libslic3r.h" -#include "clipper2/clipper.h" #include "Polygon.hpp" #include "Polyline.hpp" @@ -10,7 +8,7 @@ namespace Slic3r { Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); - +ExPolygons union_ex2(const ExPolygons &expolygons); } #endif diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index c951380f6..f50cdccfa 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -1,7 +1,8 @@ #include "SVG.hpp" #include - +// #include "pugixml/pugixml.hpp" #include +#include "nlohmann/json.hpp" namespace Slic3r { @@ -86,7 +87,7 @@ void SVG::draw(const Lines &lines, std::string stroke, coordf_t stroke_width) void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_opacity) { this->fill = fill; - + std::string d; for (const Polygon &p : to_polygons(expolygon)) d += this->get_path_d(p, true) + " "; @@ -201,7 +202,7 @@ void SVG::draw(const Point &point, std::string fill, coord_t iradius) svg << " "; - + fprintf(this->f, "%s\n", svg.str().c_str()); } @@ -247,7 +248,7 @@ void SVG::path(const std::string &d, bool fill, coordf_t stroke_width, const flo d.c_str(), fill ? this->fill.c_str() : "none", this->stroke.c_str(), - lineWidth, + lineWidth, (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : "", fill_opacity ); @@ -331,6 +332,132 @@ void SVG::add_comment(const std::string comment) fprintf(this->f, "\n", comment.c_str()); } +// Function to parse the SVG path data +Points ParseSVGPath(const std::string &pathData) +{ + Points points; + Vec2d currentPoint = {0, 0}; + char command = 0; + std::istringstream stream(pathData); + + while (stream) { + // Read the command or continue with the previous command + if (!std::isdigit(stream.peek()) && stream.peek() != '-' && stream.peek() != '.') { stream >> command; } + + if (command == 'M' || command == 'm') { // Move to + double x, y; + stream >> x; + stream.ignore(1, ','); // Skip the comma, if present + stream >> y; + + if (command == 'm') { // Relative + currentPoint.x() += x; + currentPoint.y() += y; + } else { // Absolute + currentPoint.x() = x; + currentPoint.y() = y; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'L' || command == 'l') { // Line to + double x, y; + stream >> x; + stream.ignore(1, ','); // Skip the comma, if present + stream >> y; + + if (command == 'l') { // Relative + currentPoint.x() += x; + currentPoint.y() += y; + } else { // Absolute + currentPoint.x() = x; + currentPoint.y() = y; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'Z' || command == 'z') { // Close path + if (!points.empty()) { + points.push_back(points.front()); // Close the polygon by returning to the start + } + } else if (command == 'H' || command == 'h') { // Horizontal line + double x; + stream >> x; + + if (command == 'h') { // Relative + currentPoint.x() += x; + } else { // Absolute + currentPoint.x() = x; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'V' || command == 'v') { // Vertical line + double y; + stream >> y; + + if (command == 'v') { // Relative + currentPoint.y() += y; + } else { // Absolute + currentPoint.y() = y; + } + points.push_back(scaled(currentPoint)); + } else if (command == 'z') { + if (!points.empty()) { + points.push_back(points.front()); // Close path + } + } else { + stream.ignore(1); // Skip invalid commands or extra spaces + } + } + + return points; +} + +// Convert SVG path to ExPolygon +ExPolygon ConvertToExPolygon(const std::vector &svgPaths) +{ + ExPolygon exPolygon; + + for (const auto &pathData : svgPaths) { + auto points = ParseSVGPath(pathData); + if (exPolygon.contour.empty()) { + exPolygon.contour.points = points; // First path is outer + } else { + exPolygon.holes.emplace_back(points); // Subsequent paths are holes + } + } + + return exPolygon; +} + +// Function to load SVG and convert paths to ExPolygons +std::vector SVG::load(const std::string &svgFilePath) +{ + std::vector polygons; +/* pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(svgFilePath.c_str()); + if (!result) { + std::cerr << "Failed to load SVG file: " << result.description() << "\n"; + return polygons; + } + + + // Find the root element + pugi::xml_node svgNode = doc.child("svg"); + if (!svgNode) { + std::cerr << "No element found in file.\n"; + return polygons; + } + + // Iterate over elements + for (pugi::xml_node pathNode : svgNode.children("path")) { + const char *pathData = pathNode.attribute("d").value(); + if (pathData) { + std::vector paths = {std::string(pathData)}; // For simplicity, assuming one path per element. You could extract more complex paths if necessary. + ExPolygon exPolygon = ConvertToExPolygon(paths); + polygons.push_back(exPolygon); + } + } +*/ + return polygons; +} + + void SVG::Close() { fprintf(this->f, "\n"); @@ -411,4 +538,234 @@ void SVG::export_expolygons(const char *path, const std::vector(); + p.y() = j[1].get(); + } else { + throw std::runtime_error("Invalid Point JSON format. Expected [x, y]."); + } +} + +// Serialization for Polygon +void to_json(nlohmann::json &j, const Polygon &polygon) +{ + j = nlohmann::json::array(); + for (const auto &point : polygon.points) { + j.push_back(point); // Push each point (serialized as [x, y]) + } +} + +void from_json(const nlohmann::json &j, Polygon &polygon) +{ + if (j.is_array()) { + polygon.clear(); + for (const auto &item : j) { polygon.append(item.get()); } + } else { + throw std::runtime_error("Invalid Polygon JSON format. Expected array of points."); + } +} + + +// Serialization for ExPolygon +void to_json(nlohmann::json &j, const ExPolygon &exPolygon) { + j = nlohmann::json{{"contour", exPolygon.contour}, {"holes", exPolygon.holes}}; +} + +void from_json(const nlohmann::json &j, ExPolygon &exPolygon) +{ + if (j.contains("contour")) { + j.at("contour").get_to(exPolygon.contour); + if (j.contains("holes")) { + j.at("holes").get_to(exPolygon.holes); + } + } else { + throw std::runtime_error("Invalid ExPolygon JSON format. Missing 'contour' or 'holes'."); + } +} + +// Serialization for ExPolygons +void to_json(nlohmann::json &j, const std::vector &exPolygons) +{ + j = nlohmann::json::array(); + for (const auto &exPolygon : exPolygons) { + j.push_back(exPolygon); // Serialize each ExPolygon + } +} + +void from_json(const nlohmann::json& j, std::vector& exPolygons) +{ + if (j.is_array()) { + exPolygons.clear(); + for (const auto& item : j) { + exPolygons.push_back(item.get()); + } + } + else { + throw std::runtime_error("Invalid ExPolygons JSON format. Expected array of ExPolygons."); + } +} + +// Function to dump ExPolygons to JSON +void dumpExPolygonToJson(const ExPolygon &exPolygon, const std::string &filePath) +{ + nlohmann::json j = exPolygon; + + // Write JSON to a file + std::ofstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for writing: " << filePath << "\n"; + return; + } + file << j.dump(4); // Pretty print with 4 spaces of indentation + file.close(); + + std::cout << "ExPolygons dumped to " << filePath << "\n"; +} + +// Function to dump ExPolygons to JSON +void dumpExPolygonsToJson(const std::vector &exPolygons, const std::string &filePath) +{ + nlohmann::json j = exPolygons; + + // Write JSON to a file + std::ofstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for writing: " << filePath << "\n"; + return; + } + file << j.dump(4); // Pretty print with 4 spaces of indentation + file.close(); + + std::cout << "ExPolygons dumped to " << filePath << "\n"; +} + +// Function to load ExPolygons from JSON +std::vector loadExPolygonsFromJson(const std::string &filePath) +{ + std::vector exPolygons; + + std::ifstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for reading: " << filePath << "\n"; + return exPolygons; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); // Read entire file into string + + nlohmann::json j; + try { + j = nlohmann::json::parse(content); + //file >> j; // Parse JSON from file + } catch (const nlohmann::json::parse_error &e) { + std::cerr << "JSON parsing error: " << e.what() << std::endl; + return exPolygons; // Return empty vector on failure + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + file.close(); + return exPolygons; + } + file.close(); + + // Deserialize JSON to std::vector + //exPolygons = j.get>(); + if (j.is_array()) { + for (const auto& item : j) { + exPolygons.push_back(item.get()); + } + } else if (j.is_object()) { + exPolygons.push_back(j.get()); + } + else { + throw std::runtime_error("Invalid ExPolygons JSON format. Expected array of ExPolygons."); + } + + return exPolygons; +} + +// Save ExPolygons to a file +void dumpExPolygonsToTxt(const std::vector &exPolygons, const std::string &filePath) +{ + std::ofstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for writing: " << filePath << std::endl; + return; + } + + for (size_t i = 0; i < exPolygons.size(); ++i) { + const auto &exPolygon = exPolygons[i]; + file << "# ExPolygon " << i + 1 << "\n"; + + // Save the outer contour + file << "contour:"; + for (const auto &point : exPolygon.contour) { file << " " << point.x() << " " << point.y(); } + file << "\n"; + + // Save the holes + for (const auto &hole : exPolygon.holes) { + file << "hole:"; + for (const auto &point : hole) { file << " " << point.x() << " " << point.y(); } + file << "\n"; + } + } + + file.close(); + std::cout << "ExPolygons saved to " << filePath << std::endl; +} + +// Load ExPolygons from a file +std::vector loadExPolygonsFromTxt(const std::string &filePath) +{ + std::vector exPolygons; + + std::ifstream file(filePath); + if (!file) { + std::cerr << "Error: Cannot open file for reading: " << filePath << std::endl; + return exPolygons; + } + + std::string line; + ExPolygon currentPolygon; + while (std::getline(file, line)) { + if (line.empty() || line[0] == '#') { + // Start of a new polygon + if (!currentPolygon.contour.empty() || !currentPolygon.holes.empty()) { + exPolygons.push_back(currentPolygon); + currentPolygon = ExPolygon(); + } + continue; + } + + std::istringstream stream(line); + std::string keyword; + stream >> keyword; + + if (keyword == "contour:") { + currentPolygon.contour.clear(); + coord_t x, y; + while (stream >> x >> y) { currentPolygon.contour.append({x, y}); } + } else if (keyword == "hole:") { + Polygon hole; + coord_t x, y; + while (stream >> x >> y) { hole.append({x, y}); } + currentPolygon.holes.push_back(hole); + } + } + + // Add the last polygon if any + if (!currentPolygon.contour.empty() || !currentPolygon.holes.empty()) { exPolygons.push_back(currentPolygon); } + + file.close(); + std::cout << "Loaded " << exPolygons.size() << " ExPolygons from " << filePath << std::endl; + return exPolygons; +} + } diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index 5f5796105..85e3fae50 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -80,6 +80,8 @@ public: void draw_grid(const BoundingBox& bbox, const std::string& stroke = "black", coordf_t stroke_width = scale_(0.05), coordf_t step=scale_(1.0)); void add_comment(const std::string comment); + static ExPolygons load(const std::string& filename); + void Close(); private: @@ -177,6 +179,12 @@ private: float to_svg_y(float x) const throw() { return flipY ? this->height - to_svg_coord(x) : to_svg_coord(x); } }; +void dumpExPolygonToJson(const ExPolygon &exPolygon, const std::string &filePath); +void dumpExPolygonsToJson(const std::vector &exPolygons, const std::string &filePath); +std::vector loadExPolygonsFromJson(const std::string &filePath); + +void dumpExPolygonsToTxt(const std::vector &exPolygons, const std::string &filePath); +std::vector loadExPolygonsFromTxt(const std::string &filePath); } #endif diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 47038bdcb..ea7671850 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -3,6 +3,7 @@ #include "format.hpp" #include "ClipperUtils.hpp" +#include "Clipper2Utils.hpp" #include "Fill/FillBase.hpp" #include "I18N.hpp" #include "Layer.hpp" @@ -629,6 +630,7 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p SVG svg(debug_out_path("machine_boarder.svg"), m_object->bounding_box()); if (svg.is_opened()) svg.draw(m_machine_border, "yellow"); #endif + BOOST_LOG_TRIVIAL(debug) << "tree support construct finish"; } void add_overhang(Layer *layer, const ExPolygon &overhang, int type) @@ -3871,14 +3873,16 @@ TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance m_max_move_distances.resize(object.layers().size(), 0); m_layer_outlines.resize(object.layers().size()); m_layer_outlines_below.resize(object.layer_count()); - for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr) - { + for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr) { + // BOOST_LOG_TRIVIAL(debug) << "TreeSupportData construct "<< layer_nr<<"/"<height * branch_scale_factor; ExPolygons &outline = m_layer_outlines[layer_nr]; - for (const ExPolygon& poly : layer->lslices) { - poly.simplify(scale_(m_radius_sample_resolution), &outline); - } + outline.clear(); + outline.reserve(layer->lslices.size()); + for (const ExPolygon &poly : layer->lslices) { append(outline, to_expolygons( poly.simplify_p(scale_(m_radius_sample_resolution)))); } + if (layer_nr % 10 == 0) + outline = union_ex(outline); if (layer_nr == 0) m_layer_outlines_below[layer_nr] = outline; @@ -3886,7 +3890,7 @@ TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance m_layer_outlines_below[layer_nr] = m_layer_outlines_below[layer_nr - 1]; m_layer_outlines_below[layer_nr].insert(m_layer_outlines_below[layer_nr].end(), outline.begin(), outline.end()); if (layer_nr%10==0) - m_layer_outlines_below[layer_nr] = union_ex(m_layer_outlines_below[layer_nr]); + m_layer_outlines_below[layer_nr] = union_ex2(m_layer_outlines_below[layer_nr]); } } }