From 9e9cc2bc019f0e67720ad5da16315e7cd6781e78 Mon Sep 17 00:00:00 2001
From: jaseg <git@jaseg.de>
Date: Mon, 20 Jun 2022 16:19:53 +0200
Subject: svg-flatten: add export of patterns via aperture macros

---
 svg-flatten/src/main.cpp        | 17 ++++++++++++++++-
 svg-flatten/src/out_gerber.cpp  | 38 +++++++++++++++++++++++++++++++++++++-
 svg-flatten/src/out_scaler.cpp  | 23 +++++++++++++++++++++++
 svg-flatten/src/out_sexp.cpp    |  2 +-
 svg-flatten/src/out_svg.cpp     |  2 +-
 svg-flatten/src/svg_doc.cpp     | 11 +++++++++++
 svg-flatten/src/svg_pattern.cpp | 20 ++++++++++++++++++--
 svg-flatten/src/svg_pattern.h   |  2 +-
 8 files changed, 108 insertions(+), 7 deletions(-)

(limited to 'svg-flatten/src')

diff --git a/svg-flatten/src/main.cpp b/svg-flatten/src/main.cpp
index 35fcbfb..812db5d 100644
--- a/svg-flatten/src/main.cpp
+++ b/svg-flatten/src/main.cpp
@@ -76,6 +76,9 @@ int main(int argc, char **argv) {
             {"pattern_complete_tiles_only", {"--pattern-complete-tiles-only"},
                 "Break SVG spec by only rendering complete pattern tiles, i.e. pattern tiles that entirely fit the target area, instead of performing clipping.",
                 0},
+            {"use_apertures_for_patterns", {"--use-apertures-for-patterns"},
+                "Try to use apertures to represent svg patterns where possible.",
+                0},
             {"min_feature_size", {"-d", "--trace-space"},
                 "Minimum feature size of elements in vectorized graphics (trace/space) in mm. Default: 0.1mm.",
                 1},
@@ -85,6 +88,12 @@ int main(int argc, char **argv) {
             {"drill_test_polsby_popper_tolerance", {"--drill-test-tolerance"},
                 "Tolerance for identifying circles as drills in outline mode",
                 1},
+            {"aperture_circle_test_tolerance", {"--circle-test-tolerance"},
+                "Tolerance for identifying circles as apertures in patterns (--use-apertures-for-patterns)",
+                1},
+            {"aperture_rect_test_tolerance", {"--rect-test-tolerance"},
+                "Tolerance for identifying rectangles as apertures in patterns (--use-apertures-for-patterns)",
+                1},
             {"no_header", {"--no-header"},
                 "Do not export output format header/footer, only export the primitives themselves",
                 0},
@@ -297,7 +306,9 @@ int main(int argc, char **argv) {
 
     double min_feature_size = args["min_feature_size"].as<double>(0.1); /* mm */
     double curve_tolerance = args["curve_tolerance"].as<double>(0.1); /* mm */
-    double drill_test_polsby_popper_tolerance = args["drill_test_polsby_popper_tolerance"].as<double>(0.1); /* mm */
+    double drill_test_polsby_popper_tolerance = args["drill_test_polsby_popper_tolerance"].as<double>(0.1);
+    double aperture_rect_test_tolerance = args["aperture_rect_test_tolerance"].as<double>(0.1);
+    double aperture_circle_test_tolerance = args["aperture_circle_test_tolerance"].as<double>(0.1);
 
     string ending = "";
     auto idx = in_f_name.rfind(".");
@@ -427,15 +438,19 @@ int main(int argc, char **argv) {
     VectorizerSelectorizer vec_sel(vectorizer, args["vectorizer_map"] ? args["vectorizer_map"].as<string>() : "");
     bool flip_svg_colors = args["flip_svg_color_interpretation"];
     bool pattern_complete_tiles_only = args["pattern_complete_tiles_only"];
+    bool use_apertures_for_patterns = args["use_apertures_for_patterns"];
 
     RenderSettings rset {
         min_feature_size,
         curve_tolerance,
         drill_test_polsby_popper_tolerance,
+        aperture_circle_test_tolerance,
+        aperture_rect_test_tolerance,
         vec_sel,
         outline_mode,
         flip_svg_colors,
         pattern_complete_tiles_only,
+        use_apertures_for_patterns,
     };
 
     SVGDocument doc;
diff --git a/svg-flatten/src/out_gerber.cpp b/svg-flatten/src/out_gerber.cpp
index c43d07d..0b0a6bf 100644
--- a/svg-flatten/src/out_gerber.cpp
+++ b/svg-flatten/src/out_gerber.cpp
@@ -36,6 +36,7 @@ SimpleGerberOutput::SimpleGerberOutput(ostream &out, bool only_polys, int digits
     m_flip_pol(flip_polarity),
     m_outline_mode(outline_mode),
     m_current_aperture(0.0),
+    m_macro_aperture(false),
     m_aperture_num(10) /* See gerber standard */
 {
     assert(1 <= digits_int && digits_int <= 9);
@@ -62,10 +63,11 @@ void SimpleGerberOutput::header_impl(d2p origin, d2p size) {
 }
 
 SimpleGerberOutput& SimpleGerberOutput::operator<<(const ApertureToken &ap) {
-    if (ap.m_size == m_current_aperture) {
+    if (!m_macro_aperture && ap.m_size == m_current_aperture) {
         return *this;
     }
 
+    m_macro_aperture = false;
     m_current_aperture = ap.m_size;
     m_aperture_num += 1;
 
@@ -139,3 +141,37 @@ void SimpleGerberOutput::footer_impl() {
     m_out << "M02*" << endl;
 }
 
+
+SimpleGerberOutput &SimpleGerberOutput::operator<<(const FlashToken &tok) {
+    double x = round((tok.m_offset[0] * m_scale + m_offset[0]) * m_gerber_scale);
+    double y = round((m_height - tok.m_offset[1] * m_scale + m_offset[1]) * m_gerber_scale);
+
+    m_out << "X" << setw(m_digits_int + m_digits_frac) << setfill('0') << std::internal /* isn't C++ a marvel of engineering? */ << (long long int)x
+          << "Y" << setw(m_digits_int + m_digits_frac) << setfill('0') << std::internal << (long long int)y
+          << "D03*" << endl;
+
+    return *this;
+}
+
+SimpleGerberOutput &SimpleGerberOutput::operator<<(const PatternToken &tok) {
+    m_macro_aperture = true;
+    m_aperture_num += 1;
+
+    m_out << "%AMmacro" << m_aperture_num << "*" << endl;
+
+    for (auto &pair : tok.m_polys) {
+        int exposure = (pair.second == GRB_POL_DARK) ? 1 : 0;
+        m_out << 4 << "," << exposure << "," << pair.first.size();
+        for (auto &pt : pair.first) {
+            m_out << "," << pt[0] << "," << pt[1];
+        }
+        m_out << "," << pair.first.back()[0] << "," << pair.first.back()[1] << "*" << endl;
+    }
+
+    m_out << "%" << endl;
+    m_out << "%ADD" << m_aperture_num << "macro" << m_aperture_num << "*%" << endl;
+    m_out << "D" << m_aperture_num << "*" << endl;
+
+    return *this;
+}
+
diff --git a/svg-flatten/src/out_scaler.cpp b/svg-flatten/src/out_scaler.cpp
index 0320d69..fb54145 100644
--- a/svg-flatten/src/out_scaler.cpp
+++ b/svg-flatten/src/out_scaler.cpp
@@ -69,3 +69,26 @@ PolygonScaler &PolygonScaler::operator<<(const DrillToken &tok) {
     m_sink << DrillToken(new_center);
     return *this;
 }
+
+PolygonScaler &PolygonScaler::operator<<(const FlashToken &tok) {
+    d2p new_offset = { tok.m_offset[0] * m_scale, tok.m_offset[1] * m_scale};
+    m_sink << FlashToken(new_offset);
+    return *this;
+}
+
+PolygonScaler &PolygonScaler::operator<<(const PatternToken &tok) {
+    vector<pair<Polygon, GerberPolarityToken>> new_polys;
+    for (size_t i=0; i<tok.m_polys.size(); i++) {
+        Polygon poly(tok.m_polys[i].first.size());
+        for (size_t j=0; j<poly.size(); j++) {
+            d2p new_point = tok.m_polys[i].first[j];
+            new_point[0] *= m_scale;
+            new_point[1] *= m_scale;
+            poly[j] = new_point;
+        }
+        new_polys.emplace_back(pair<Polygon, GerberPolarityToken>{poly, tok.m_polys[i].second});
+    }
+    m_sink << PatternToken(new_polys);
+    return *this;
+}
+
diff --git a/svg-flatten/src/out_sexp.cpp b/svg-flatten/src/out_sexp.cpp
index 6760920..f872b90 100644
--- a/svg-flatten/src/out_sexp.cpp
+++ b/svg-flatten/src/out_sexp.cpp
@@ -135,7 +135,7 @@ KicadSexpOutput &KicadSexpOutput::operator<<(const Polygon &poly) {
     return *this;
 }
 
-KicadSexpOutput &KicadSexpOutput::operator<<(const DrillToken &tok) {
+KicadSexpOutput &KicadSexpOutput::operator<<(const DrillToken &) {
     return *this;
 }
 
diff --git a/svg-flatten/src/out_svg.cpp b/svg-flatten/src/out_svg.cpp
index 4ecd979..66f0c94 100644
--- a/svg-flatten/src/out_svg.cpp
+++ b/svg-flatten/src/out_svg.cpp
@@ -77,7 +77,7 @@ SimpleSVGOutput &SimpleSVGOutput::operator<<(const Polygon &poly) {
     return *this;
 }
 
-SimpleSVGOutput &SimpleSVGOutput::operator<<(const DrillToken &tok) {
+SimpleSVGOutput &SimpleSVGOutput::operator<<(const DrillToken &) {
     return *this;
 }
 
diff --git a/svg-flatten/src/svg_doc.cpp b/svg-flatten/src/svg_doc.cpp
index e8c02db..7716a2e 100644
--- a/svg-flatten/src/svg_doc.cpp
+++ b/svg-flatten/src/svg_doc.cpp
@@ -557,3 +557,14 @@ gerbolyze::RenderContext::RenderContext(RenderContext &parent, xform2d transform
     m_mat.transform(transform);
 }
 
+gerbolyze::RenderContext::RenderContext(RenderContext &parent, PolygonSink &sink, ClipperLib::Paths &clip) :
+    m_sink(sink),
+    m_settings(parent.settings()),
+    m_mat(parent.mat()),
+    m_root(false),
+    m_included(false),
+    m_sel(parent.sel()),
+    m_clip(clip)
+{
+}
+
diff --git a/svg-flatten/src/svg_pattern.cpp b/svg-flatten/src/svg_pattern.cpp
index fc74d73..e8fb851 100644
--- a/svg-flatten/src/svg_pattern.cpp
+++ b/svg-flatten/src/svg_pattern.cpp
@@ -25,7 +25,7 @@
 
 using namespace std;
 
-gerbolyze::Pattern::Pattern(const pugi::xml_node &node, SVGDocument &doc) : _node(node), doc(&doc) {
+gerbolyze::Pattern::Pattern(const pugi::xml_node &node, SVGDocument &doc) : m_node(node), doc(&doc) {
     /* Read pattern attributes from SVG node */
     x = usvg_double_attr(node, "x");
     y = usvg_double_attr(node, "y");
@@ -87,6 +87,17 @@ void gerbolyze::Pattern::tile (gerbolyze::RenderContext &ctx) {
     /* Switch to pattern coordinates */
     RenderContext pat_ctx(ctx, patternTransform);
 
+    if (ctx.settings().use_apertures_for_patterns) {
+        vector<pair<Polygon, GerberPolarityToken>> out;
+        LambdaPolygonSink list_sink([&out](const Polygon &poly, GerberPolarityToken pol) {
+                out.emplace_back(pair<Polygon, GerberPolarityToken>{poly, pol});
+            });
+        ClipperLib::Paths empty_clip;
+        RenderContext macro_ctx(pat_ctx, list_sink, empty_clip);
+        doc->export_svg_group(macro_ctx, m_node);
+        pat_ctx.sink() << PatternToken(out);
+    }
+
     /* Iterate over all pattern tiles in pattern coordinates */
     for (double inst_off_x = fmod(inst_x, inst_w) - 2*inst_w;
             inst_off_x < bx + bw + 2*inst_w;
@@ -131,7 +142,12 @@ void gerbolyze::Pattern::tile (gerbolyze::RenderContext &ctx) {
                 }
             }
 
-            doc->export_svg_group(elem_ctx, _node);
+            if (ctx.settings().use_apertures_for_patterns) {
+                /* use inst_h offset to compensate for gerber <-> svg "y" coordinate spaces */
+                elem_ctx.sink() << FlashToken(elem_ctx.mat().doc2phys({0, inst_h}));
+            } else {
+                doc->export_svg_group(elem_ctx, m_node);
+            }
         }
     }
 }
diff --git a/svg-flatten/src/svg_pattern.h b/svg-flatten/src/svg_pattern.h
index 73efe5a..368087a 100644
--- a/svg-flatten/src/svg_pattern.h
+++ b/svg-flatten/src/svg_pattern.h
@@ -47,7 +47,7 @@ private:
     xform2d patternTransform_inv;
     enum RelativeUnits patternUnits;
     enum RelativeUnits patternContentUnits;
-    const pugi::xml_node _node;
+    const pugi::xml_node m_node;
     SVGDocument *doc = nullptr;
 };
 
-- 
cgit