From eac89409b80c8f61efc98a20b4d742c878cde637 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sat, 30 Jan 2021 00:27:43 +0100 Subject: Add KiCAD sexp output --- Makefile | 7 +++-- README.md | 57 ++++++++++++++++++++++++++++++++++ include/gerbolyze.hpp | 19 ++++++++++++ src/main.cpp | 25 +++++++++++++-- src/out_sexp.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 README.md create mode 100644 src/out_sexp.cpp diff --git a/Makefile b/Makefile index a188e87..520100b 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ SOURCES := src/svg_color.cpp \ src/main.cpp \ src/out_svg.cpp \ src/out_gerber.cpp \ + src/out_sexp.cpp \ src/out_flattener.cpp \ src/lambda_sink.cpp \ @@ -37,7 +38,9 @@ CXXFLAGS += $(shell $(PKG_CONFIG) --cflags pangocairo pugixml opencv4) LDFLAGS := -lm -lc -lstdc++ LDFLAGS += $(shell $(PKG_CONFIG) --libs pangocairo pugixml opencv4) -all: $(BUILDDIR)/svg-render +TARGET := svg-render + +all: $(BUILDDIR)/$(TARGET) test.gbr test.svg &: render ./render test.svg > test.gbr @@ -46,7 +49,7 @@ $(BUILDDIR)/%.o: %.cpp @mkdir -p $(dir $@) $(CXX) -c $(CXXFLAGS) $(CXXFLAGS) $(INCLUDES) -o $@ $^ -$(BUILDDIR)/svg-render: $(SOURCES:%.cpp=$(BUILDDIR)/%.o) $(BUILDDIR)/upstream/cpp-base64/base64.o +$(BUILDDIR)/$(TARGET): $(SOURCES:%.cpp=$(BUILDDIR)/%.o) $(BUILDDIR)/upstream/cpp-base64/base64.o @mkdir -p $(dir $@) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fa711d --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +build/svg-render 2.0 + +Usage: build/svg-render [options]... [input_file] [output_file] + +Specify "-" for stdin/stdout. + + -h, --help + Print help and exit + -v, --version + Print version and exit + -o, --format + Output format. Supported: gerber, svg, s-exp (KiCAD S-Expression) + -p, --precision + Number of decimal places use for exported coordinates (gerber: 1-9, + SVG: 0-*) + --clear-color + SVG color to use for "clear" areas (default: white) + --dark-color + SVG color to use for "dark" areas (default: black) + -d, --trace-space + Minimum feature size of elements in vectorized graphics + (trace/space) in mm. Default: 0.1mm. + --no-header + Do not export output format header/footer, only export the + primitives themselves + --flatten + Flatten output so it only consists of non-overlapping white + polygons. This perform composition at the vector level. Potentially slow. + --no-flatten + Disable automatic flattening for KiCAD S-Exp export + -g, --only-groups + Comma-separated list of group IDs to export. + -b, --vectorizer + Vectorizer to use for bitmap images. One of poisson-disc (default), + hex-grid, square-grid, binary-contours, dev-null. + --vectorizer-map + Map from image element id to vectorizer. Overrides --vectorizer. + Format: id1=vectorizer,id2=vectorizer,... + --force-svg + Force SVG input irrespective of file name + --force-png + Force bitmap graphics input irrespective of file name + -s, --size + Bitmap mode only: Physical size of output image in mm. Format: 12.34x56.78 + --sexp-mod-name + Module name for KiCAD S-Exp output + --sexp-layer + Layer for KiCAD S-Exp output + -a, --preserve-aspect-ratio + Bitmap mode only: Preserve aspect ratio of image. Allowed values + are meet, slice. Can also parse full SVG preserveAspectRatio syntax. + --no-usvg + Do not preprocess input using usvg (do not use unless you know + *exactly* what you're doing) + -e, --exclude-groups + Comma-separated list of group IDs to exclude from export. Takes + precedence over --only-groups. diff --git a/include/gerbolyze.hpp b/include/gerbolyze.hpp index 1d4f9bd..af25571 100644 --- a/include/gerbolyze.hpp +++ b/include/gerbolyze.hpp @@ -220,6 +220,25 @@ namespace gerbolyze { d2p m_offset; }; + class KicadSexpOutput : public StreamPolygonSink { + public: + 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<<(GerberPolarityToken pol); + virtual void header_impl(d2p origin, d2p size); + virtual void footer_impl(); + + private: + std::string m_mod_name; + std::string m_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: diff --git a/src/main.cpp b/src/main.cpp index 915b71f..52110b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,9 @@ int main(int argc, char **argv) { {"flatten", {"--flatten"}, "Flatten output so it only consists of non-overlapping white polygons. This perform composition at the vector level. Potentially slow.", 0}, + {"no_flatten", {"--no-flatten"}, + "Disable automatic flattening for KiCAD S-Exp export", + 0}, {"only_groups", {"-g", "--only-groups"}, "Comma-separated list of group IDs to export.", 1}, @@ -64,6 +67,12 @@ int main(int argc, char **argv) { {"size", {"-s", "--size"}, "Bitmap mode only: Physical size of output image in mm. Format: 12.34x56.78", 1}, + {"sexp_mod_name", {"--sexp-mod-name"}, + "Module name for KiCAD S-Exp output", + 1}, + {"sexp_layer", {"--sexp-layer"}, + "Layer for KiCAD S-Exp output", + 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.", 1}, @@ -151,6 +160,7 @@ 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 */ + bool force_flatten = false; PolygonSink *sink = nullptr; PolygonSink *flattener = nullptr; if (fmt == "svg") { @@ -161,6 +171,17 @@ int main(int argc, char **argv) { } else if (fmt == "gbr" || fmt == "grb" || fmt == "gerber") { 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; + 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); + force_flatten = true; + } else { cerr << "Unknown output format \"" << fmt << "\"" << endl; argagg::fmt_ostream fmt(cerr); @@ -168,7 +189,7 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - if (args["flatten"]) { + if (args["flatten"] || (force_flatten && !args["no_flatten"])) { flattener = new Flattener(*sink); } @@ -294,7 +315,7 @@ int main(int argc, char **argv) { } if (args["skip_usvg"]) { - cerr << "skippint usvg" << endl; + cerr << "skipping usvg" << endl; frob = barf; } else { diff --git a/src/out_sexp.cpp b/src/out_sexp.cpp new file mode 100644 index 0000000..90adef2 --- /dev/null +++ b/src/out_sexp.cpp @@ -0,0 +1,86 @@ +/* + * This file is part of gerbolyze, a vector image preprocessing toolchain + * Copyright (C) 2021 Jan Sebastian Götte + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace gerbolyze; +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_val_text(val_text), + m_ref_pos(ref_pos), + m_val_pos(val_pos) +{ + if (ref_text.empty()) { + m_ref_text = mod_name; + } else { + m_ref_text = ref_text; + } +} + +void KicadSexpOutput::header_impl(d2p, d2p) { + auto tedit = std::time(0); + m_out << "(module " << m_mod_name << " (layer F.Cu) (tedit " << std::hex << std::setfill('0') << std::setw(8) << tedit << ")" << endl; + m_out << " (fp_text reference " << m_ref_text << " (at " << m_ref_pos[0] << " " << m_ref_pos[1] << ") (layer F.SilkS) hide" << endl; + m_out << " (effects (font (size 1 1) (thickness 0.15)))" << endl; + m_out << " )" << endl; + m_out << " (fp_text value " << m_val_text << " (at " << m_val_pos[0] << " " << m_val_pos[1] << ") (layer F.SilkS) hide" << endl; + m_out << " (effects (font (size 1 1) (thickness 0.15)))" << endl; + m_out << " )" << endl; +} + +KicadSexpOutput &KicadSexpOutput::operator<<(GerberPolarityToken pol) { + if (pol == GRB_POL_CLEAR) { + cerr << "Warning: clear polarity not supported since KiCAD manages to have an even worse graphics model than gerber, except it can't excuse itself by its age..... -.-" << endl; + } + + return *this; +} + +KicadSexpOutput &KicadSexpOutput::operator<<(const Polygon &poly) { + if (poly.size() < 3) { + cerr << "Warning: " << poly.size() << "-element polygon passed to KicadSexpOutput" << endl; + return *this; + } + + m_out << " (fp_poly (pts"; + for (auto &p : poly) { + m_out << " (xy " << p[0] << " " << p[1] << ")"; + } + m_out << ")"; + m_out << " (layer " << m_layer << ") (width 0))" << endl; + + return *this; +} + +void KicadSexpOutput::footer_impl() { + m_out << ")" << endl; +} + + -- cgit