ENH: add Clipper2::Union and fix a command line hang issue

jira: STUDIO-9623

Change-Id: I8b40f66fed0fc9e5b13f0f10337267065fef1056
This commit is contained in:
Arthur 2025-01-10 09:13:10 +08:00 committed by lane.wei
parent 4f1ad8016e
commit b5e3b96764
7 changed files with 461 additions and 16 deletions

View File

@ -295,7 +295,8 @@ namespace Clipper2Lib {
typedef typename std::vector<PolyPath64*>::const_iterator pp64_itor;
public:
PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
PolyPath64* operator [] (size_t index) { return static_cast<PolyPath64*>(childs_[index]); }
PolyPath64 *operator[](size_t index) { return static_cast<PolyPath64 *>(childs_[index]); }
PolyPath64 *Childs(size_t index) const { return static_cast<PolyPath64 *>(childs_[index]); }
pp64_itor begin() const { return childs_.cbegin(); }
pp64_itor end() const { return childs_.cend(); }

View File

@ -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;
}

View File

@ -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<T>& 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;
}
}

View File

@ -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

View File

@ -1,7 +1,8 @@
#include "SVG.hpp"
#include <iostream>
// #include "pugixml/pugixml.hpp"
#include <boost/nowide/cstdio.hpp>
#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 << " <circle cx=\"" << to_svg_x(point(0) - origin(0)) << "\" cy=\"" << to_svg_y(point(1) - origin(1))
<< "\" r=\"" << radius << "\" "
<< "style=\"stroke: none; fill: " << fill << "\" />";
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, "<!-- %s -->\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<coord_t>(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<coord_t>(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<coord_t>(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<coord_t>(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<std::string> &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<ExPolygon> SVG::load(const std::string &svgFilePath)
{
std::vector<ExPolygon> 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 <svg> element
pugi::xml_node svgNode = doc.child("svg");
if (!svgNode) {
std::cerr << "No <svg> element found in file.\n";
return polygons;
}
// Iterate over <path> elements
for (pugi::xml_node pathNode : svgNode.children("path")) {
const char *pathData = pathNode.attribute("d").value();
if (pathData) {
std::vector<std::string> 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, "</svg>\n");
@ -411,4 +538,234 @@ void SVG::export_expolygons(const char *path, const std::vector<std::pair<Slic3r
svg.Close();
}
// JSON serialization for Point using compact format [x, y]
void to_json(nlohmann::json &j, const Point &p) { j = nlohmann::json{p.x(), p.y()}; }
void from_json(const nlohmann::json &j, Point &p)
{
if (j.is_array() && j.size() == 2) {
p.x() = j[0].get<coord_t>();
p.y() = j[1].get<coord_t>();
} 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<Point>()); }
} 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<ExPolygon> &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<ExPolygon>& exPolygons)
{
if (j.is_array()) {
exPolygons.clear();
for (const auto& item : j) {
exPolygons.push_back(item.get<ExPolygon>());
}
}
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<ExPolygon> &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<ExPolygon> loadExPolygonsFromJson(const std::string &filePath)
{
std::vector<ExPolygon> 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<ExPolygon>
//exPolygons = j.get<std::vector<ExPolygon>>();
if (j.is_array()) {
for (const auto& item : j) {
exPolygons.push_back(item.get<ExPolygon>());
}
} else if (j.is_object()) {
exPolygons.push_back(j.get<ExPolygon>());
}
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<ExPolygon> &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<ExPolygon> loadExPolygonsFromTxt(const std::string &filePath)
{
std::vector<ExPolygon> 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;
}
}

View File

@ -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<ExPolygon> &exPolygons, const std::string &filePath);
std::vector<ExPolygon> loadExPolygonsFromJson(const std::string &filePath);
void dumpExPolygonsToTxt(const std::vector<ExPolygon> &exPolygons, const std::string &filePath);
std::vector<ExPolygon> loadExPolygonsFromTxt(const std::string &filePath);
}
#endif

View File

@ -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<<"/"<<object.layer_count();
const Layer* layer = object.get_layer(layer_nr);
m_max_move_distances[layer_nr] = layer->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]);
}
}
}