From a6540b73dabc3af766643657b226f942ef05656c Mon Sep 17 00:00:00 2001 From: jaseg Date: Sat, 30 Jan 2021 14:17:42 +0100 Subject: Multi-layer module export working --- include/gerbolyze.hpp | 33 +++++++++++++++----------- src/main.cpp | 15 ++++++++---- src/out_flattener.cpp | 24 ++++++++++++------- src/out_sexp.cpp | 24 ++++++++++++++++++- src/svg_doc.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 126 insertions(+), 34 deletions(-) diff --git a/include/gerbolyze.hpp b/include/gerbolyze.hpp index af25571..a48c3bf 100644 --- a/include/gerbolyze.hpp +++ b/include/gerbolyze.hpp @@ -37,11 +37,17 @@ namespace gerbolyze { GRB_POL_DARK }; + class LayerNameToken { + public: + std::string m_name; + }; + class PolygonSink { public: virtual ~PolygonSink() {} virtual void header(d2p origin, d2p size) {(void) origin; (void) size;} virtual PolygonSink &operator<<(const Polygon &poly) = 0; + virtual PolygonSink &operator<<(const LayerNameToken &) { return *this; }; virtual PolygonSink &operator<<(GerberPolarityToken pol) = 0; virtual void footer() {} }; @@ -53,11 +59,13 @@ namespace gerbolyze { virtual ~Flattener(); virtual void header(d2p origin, d2p size); virtual Flattener &operator<<(const Polygon &poly); + virtual Flattener &operator<<(const LayerNameToken &layer_name); virtual Flattener &operator<<(GerberPolarityToken pol); virtual void footer(); private: void render_out_clear_polys(); + void flush_polys_to_sink(); PolygonSink &m_sink; GerberPolarityToken m_current_polarity = GRB_POL_DARK; Flattener_D *d; @@ -78,17 +86,20 @@ namespace gerbolyze { std::ostream &m_out; }; + extern const std::vector kicad_default_layers; + class ElementSelector { public: - virtual bool match(const pugi::xml_node &node, bool included) const = 0; + virtual bool match(const pugi::xml_node &node, bool included, bool is_root) const = 0; }; class IDElementSelector : public ElementSelector { public: - virtual bool match(const pugi::xml_node &node, bool included) const; + virtual bool match(const pugi::xml_node &node, bool included, bool is_root) const; std::vector include; std::vector exclude; + const std::vector *layers = nullptr; }; class ImageVectorizer { @@ -144,7 +155,7 @@ namespace gerbolyze { const ClipperLib::Paths *lookup_clip_path(const pugi::xml_node &node); Pattern *lookup_pattern(const std::string id); - void export_svg_group(const RenderSettings &rset, const pugi::xml_node &group, ClipperLib::Paths &parent_clip_path, const ElementSelector *sel=nullptr, bool included=true); + void export_svg_group(const RenderSettings &rset, const pugi::xml_node &group, ClipperLib::Paths &parent_clip_path, const ElementSelector *sel=nullptr, bool included=true, bool is_root=false); void export_svg_path(const RenderSettings &rset, const pugi::xml_node &node, ClipperLib::Paths &clip_path); void setup_debug_output(std::string filename=""); void setup_viewport_clip(); @@ -225,27 +236,21 @@ namespace gerbolyze { KicadSexpOutput(std::ostream &out, std::string mod_name, std::string layer, bool only_polys=false, std::string m_ref_text="", std::string m_val_text="G*****", d2p ref_pos={0,10}, d2p val_pos={0,-10}); virtual ~KicadSexpOutput() {} virtual KicadSexpOutput &operator<<(const Polygon &poly); + virtual KicadSexpOutput &operator<<(const LayerNameToken &layer_name); virtual KicadSexpOutput &operator<<(GerberPolarityToken pol); virtual void header_impl(d2p origin, d2p size); virtual void footer_impl(); + void set_export_layers(const std::vector &layers) { m_export_layers = &layers; } + private: + const std::vector *m_export_layers = &kicad_default_layers; std::string m_mod_name; std::string m_layer; + bool m_auto_layer; std::string m_ref_text; std::string m_val_text; d2p m_ref_pos; d2p m_val_pos; }; - - - /* TODO - class SExpOutput : public StreamPolygonSink { - public: - virtual SExpOutput &operator<<(const Polygon &poly); - virtual SExpOutput &operator<<(GerberPolarityToken pol); - virtual void header_impl(d2p origin, d2p size); - virtual void footer_impl(); - } - */ } diff --git a/src/main.cpp b/src/main.cpp index 52110b6..45e2cfb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,7 +71,7 @@ int main(int argc, char **argv) { "Module name for KiCAD S-Exp output", 1}, {"sexp_layer", {"--sexp-layer"}, - "Layer for KiCAD S-Exp output", + "Layer for KiCAD S-Exp output. Defaults to auto-detect layers from SVG layer/top-level group names", 1}, {"preserve_aspect_ratio", {"-a", "--preserve-aspect-ratio"}, "Bitmap mode only: Preserve aspect ratio of image. Allowed values are meet, slice. Can also parse full SVG preserveAspectRatio syntax.", @@ -160,6 +160,8 @@ int main(int argc, char **argv) { string fmt = args["ofmt"] ? args["ofmt"] : "gerber"; transform(fmt.begin(), fmt.end(), fmt.begin(), [](unsigned char c){ return std::tolower(c); }); /* c++ yeah */ + string sexp_layer = args["sexp_layer"] ? args["sexp_layer"].as() : "auto"; + bool force_flatten = false; PolygonSink *sink = nullptr; PolygonSink *flattener = nullptr; @@ -172,14 +174,14 @@ int main(int argc, char **argv) { sink = new SimpleGerberOutput(*out_f, only_polys, 4, precision); } else if (fmt == "s-exp" || fmt == "sexp" || fmt == "kicad") { - if (!args["sexp_mod_name"] || !args["sexp_layer"]) { - cerr << "--sexp-mod-name and --sexp-layer must be given for sexp export" << endl; + if (!args["sexp_mod_name"]) { + cerr << "--sexp-mod-name must be given for sexp export" << endl; argagg::fmt_ostream fmt(cerr); fmt << usage.str() << argparser; return EXIT_FAILURE; } - sink = new KicadSexpOutput(*out_f, args["sexp_mod_name"], args["sexp_layer"], only_polys); + sink = new KicadSexpOutput(*out_f, args["sexp_mod_name"], sexp_layer, only_polys); force_flatten = true; } else { @@ -206,6 +208,9 @@ int main(int argc, char **argv) { id_match(args["only_groups"], sel.include); if (args["exclude_groups"]) id_match(args["exclude_groups"], sel.exclude); + if (sexp_layer == "auto") { + sel.layers = &gerbolyze::kicad_default_layers; + } string vectorizer = args["vectorizer"] ? args["vectorizer"] : "poisson-disc"; /* Check argument */ @@ -320,7 +325,7 @@ int main(int argc, char **argv) { } else { cerr << "calling usvg on " << barf << " and " << frob << endl; - const char *command_line[] = {"usvg", barf.c_str(), frob.c_str(), NULL}; + const char *command_line[] = {"usvg", "--keep-named-groups", barf.c_str(), frob.c_str(), NULL}; struct subprocess_s subprocess; int rc = subprocess_create(command_line, subprocess_option_inherit_environment, &subprocess); if (rc) { diff --git a/src/out_flattener.cpp b/src/out_flattener.cpp index 5816f88..8868ca2 100644 --- a/src/out_flattener.cpp +++ b/src/out_flattener.cpp @@ -144,15 +144,19 @@ Flattener &Flattener::operator<<(GerberPolarityToken pol) { return *this; } -Flattener &Flattener::operator<<(const Polygon &poly) { - static int i=0, j=0; +Flattener &Flattener::operator<<(const LayerNameToken &layer_name) { + flush_polys_to_sink(); + m_sink << layer_name; + cerr << "Flattener forwarding layer name to sink: \"" << layer_name.m_name << "\"" << endl; + + return *this; +} +Flattener &Flattener::operator<<(const Polygon &poly) { if (m_current_polarity == GRB_POL_DARK) { d->add_dark_polygon(poly); - cerr << "dark primitive " << i++ << endl; } else { /* clear */ - cerr << "clear primitive " << j++ << endl; d->add_clear_polygon(poly); render_out_clear_polys(); } @@ -160,10 +164,8 @@ Flattener &Flattener::operator<<(const Polygon &poly) { return *this; } -void Flattener::footer() { - if (m_current_polarity == GRB_POL_CLEAR) { - render_out_clear_polys(); - } +void Flattener::flush_polys_to_sink() { + *this << GRB_POL_DARK; /* force render */ m_sink << GRB_POL_DARK; for (auto &poly : d->dark_polys) { @@ -174,6 +176,12 @@ void Flattener::footer() { m_sink << poly_out; } + d->clear_polys.clear(); + d->dark_polys.clear(); +} + +void Flattener::footer() { + flush_polys_to_sink(); m_sink.footer(); } diff --git a/src/out_sexp.cpp b/src/out_sexp.cpp index 90adef2..9a04416 100644 --- a/src/out_sexp.cpp +++ b/src/out_sexp.cpp @@ -32,7 +32,8 @@ using namespace std; KicadSexpOutput::KicadSexpOutput(ostream &out, string mod_name, string layer, bool only_polys, string ref_text, string val_text, d2p ref_pos, d2p val_pos) : StreamPolygonSink(out, only_polys), m_mod_name(mod_name), - m_layer(layer), + m_layer(layer == "auto" ? "unknown" : layer), + m_auto_layer(layer == "auto"), m_val_text(val_text), m_ref_pos(ref_pos), m_val_pos(val_pos) @@ -63,7 +64,28 @@ KicadSexpOutput &KicadSexpOutput::operator<<(GerberPolarityToken pol) { return *this; } +KicadSexpOutput &KicadSexpOutput::operator<<(const LayerNameToken &layer_name) { + if (!m_auto_layer) + return *this; + + cerr << "Setting S-Exp export layer to \"" << layer_name.m_name << "\"" << endl; + if (!layer_name.m_name.empty()) { + m_layer = layer_name.m_name; + } else { + m_layer = "unknown"; + } + + return *this; +} + KicadSexpOutput &KicadSexpOutput::operator<<(const Polygon &poly) { + if (m_auto_layer) { + if (std::find(m_export_layers->begin(), m_export_layers->end(), m_layer) == m_export_layers->end()) { + cerr << "Rejecting S-Exp export layer \"" << m_layer << "\"" << endl; + return *this; + } + } + if (poly.size() < 3) { cerr << "Warning: " << poly.size() << "-element polygon passed to KicadSexpOutput" << endl; return *this; diff --git a/src/svg_doc.cpp b/src/svg_doc.cpp index 39c5724..21af576 100644 --- a/src/svg_doc.cpp +++ b/src/svg_doc.cpp @@ -117,12 +117,21 @@ double gerbolyze::SVGDocument::doc_units_to_mm(double px) const { return px / (vb_w / page_w_mm); } -bool IDElementSelector::match(const pugi::xml_node &node, bool included) const { +bool IDElementSelector::match(const pugi::xml_node &node, bool included, bool is_root) const { + string id = node.attribute("id").value(); + if (is_root && layers) { + bool layer_match = std::find(layers->begin(), layers->end(), id) != layers->end(); + if (!layer_match) { + cerr << "Rejecting layer \"" << id << "\"" << endl; + return false; + } + } + if (include.empty() && exclude.empty()) return true; - bool include_match = std::find(include.begin(), include.end(), node.attribute("id").value()) != include.end(); - bool exclude_match = std::find(exclude.begin(), exclude.end(), node.attribute("id").value()) != exclude.end(); + bool include_match = std::find(include.begin(), include.end(), id) != include.end(); + bool exclude_match = std::find(exclude.begin(), exclude.end(), id) != exclude.end(); if (exclude_match || (!included && !include_match)) { return false; @@ -132,7 +141,7 @@ bool IDElementSelector::match(const pugi::xml_node &node, bool included) const { } /* Recursively export all SVG elements in the given group. */ -void gerbolyze::SVGDocument::export_svg_group(const RenderSettings &rset, const pugi::xml_node &group, Paths &parent_clip_path, const ElementSelector *sel, bool included) { +void gerbolyze::SVGDocument::export_svg_group(const RenderSettings &rset, const pugi::xml_node &group, Paths &parent_clip_path, const ElementSelector *sel, bool included, bool is_root) { /* Enter the group's coordinate system */ cairo_save(cr); apply_cairo_transform_from_svg(cr, group.attribute("transform").value()); @@ -162,12 +171,23 @@ void gerbolyze::SVGDocument::export_svg_group(const RenderSettings &rset, const /* Iterate over the group's children, exporting them one by one. */ for (const auto &node : group.children()) { - if (sel && !sel->match(node, included)) + if (sel && !sel->match(node, included, is_root)) continue; string name(node.name()); if (name == "g") { + if (is_root) { /* Treat top-level groups as "layers" like inkscape does. */ + cerr << "Forwarding layer name to sink: \"" << node.attribute("id").value() << "\"" << endl; + LayerNameToken tok { node.attribute("id").value() }; + *polygon_sink << tok; + } + export_svg_group(rset, node, clip_path, sel, true); + + if (is_root) { + LayerNameToken tok {""}; + *polygon_sink << tok; + } } else if (name == "path") { export_svg_path(rset, node, clip_path); @@ -379,7 +399,7 @@ void gerbolyze::SVGDocument::render(const RenderSettings &rset, PolygonSink &sin c.AddPaths(vb_paths, ptSubject, /* closed */ true); ClipperLib::IntRect bbox = c.GetBounds(); cerr << "document viewbox clip: bbox={" << bbox.left << ", " << bbox.top << "} - {" << bbox.right << ", " << bbox.bottom << "}" << endl; - export_svg_group(rset, root_elem, vb_paths, sel, false); + export_svg_group(rset, root_elem, vb_paths, sel, false, true); sink.footer(); } @@ -484,3 +504,35 @@ void gerbolyze::SVGDocument::load_clips() { } } +/* Note: These values come from KiCAD's common/lset.cpp. KiCAD uses *multiple different names* for the same layer in + * different places, and not all of them are stable. Sometimes, these names change without notice. If this list isn't + * up-to-date, it's not my fault. Still, please file an issue. */ +const std::vector gerbolyze::kicad_default_layers ({ + /* Copper */ + "F.Cu", + "In1.Cu", "In2.Cu", "In3.Cu", "In4.Cu", "In5.Cu", "In6.Cu", "In7.Cu", "In8.Cu", + "In9.Cu", "In10.Cu", "In11.Cu", "In12.Cu", "In13.Cu", "In14.Cu", "In15.Cu", "In16.Cu", + "In17.Cu", "In18.Cu", "In19.Cu", "In20.Cu", "In21.Cu", "In22.Cu", "In23.Cu", + "In24.Cu", "In25.Cu", "In26.Cu", "In27.Cu", "In28.Cu", "In29.Cu", "In30.Cu", + "B.Cu", + + /* Technical layers */ + "B.Adhes", "F.Adhes", + "B.Paste", "F.Paste", + "B.SilkS", "F.SilkS", + "B.Mask", "F.Mask", + + /* User layers */ + "Dwgs.User", + "Cmts.User", + "Eco1.User", "Eco2.User", + "Edge.Cuts", + "Margin", + + /* Footprint layers */ + "F.CrtYd", "B.CrtYd", + "F.Fab", "B.Fab", + + /* Layers for user scripting etc. */ + "User.1", "User.2", "User.3", "User.4", "User.5", "User.6", "User.7", "User.8", "User.9", + }); -- cgit