From f2c891533f5179bf3d8a1625b1fe490a0cd06a42 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 25 Apr 2021 14:03:16 +0200 Subject: svg-flatten: Add outline/edge layer mode --- svg-flatten/src/main.cpp | 16 ++++-- svg-flatten/src/out_gerber.cpp | 24 ++++++--- svg-flatten/src/svg_doc.cpp | 115 +++++++++++++++++++++++++---------------- 3 files changed, 100 insertions(+), 55 deletions(-) (limited to 'svg-flatten/src') diff --git a/svg-flatten/src/main.cpp b/svg-flatten/src/main.cpp index 722b356..3093d6a 100644 --- a/svg-flatten/src/main.cpp +++ b/svg-flatten/src/main.cpp @@ -28,7 +28,7 @@ int main(int argc, char **argv) { "Print version and exit", 0}, {"ofmt", {"-o", "--format"}, - "Output format. Supported: gerber, svg, s-exp (KiCAD S-Expression)", + "Output format. Supported: gerber, gerber-outline (for board outline layer), svg, s-exp (KiCAD S-Expression)", 1}, {"precision", {"-p", "--precision"}, "Number of decimal places use for exported coordinates (gerber: 1-9, SVG: 0-*)", @@ -181,6 +181,7 @@ int main(int argc, char **argv) { bool force_flatten = false; bool is_sexp = false; + bool outline_mode = false; PolygonSink *sink = nullptr; PolygonSink *flattener = nullptr; PolygonSink *dilater = nullptr; @@ -189,10 +190,16 @@ int main(int argc, char **argv) { string clear_color = args["svg_clear_color"] ? args["svg_clear_color"].as() : "#ffffff"; sink = new SimpleSVGOutput(*out_f, only_polys, precision, dark_color, clear_color); - } else if (fmt == "gbr" || fmt == "grb" || fmt == "gerber") { + } else if (fmt == "gbr" || fmt == "grb" || fmt == "gerber" || fmt == "gerber-outline") { + outline_mode = fmt == "gerber-outline"; + double scale = args["scale"].as(1.0); - cerr << "loading @scale=" << scale << endl; - sink = new SimpleGerberOutput(*out_f, only_polys, 4, precision, scale, {0,0}, args["flip_gerber_polarity"]); + if (scale != 1.0) { + cerr << "loading @scale=" << scale << endl; + } + + sink = new SimpleGerberOutput( + *out_f, only_polys, 4, precision, scale, {0,0}, args["flip_gerber_polarity"], outline_mode); } else if (fmt == "s-exp" || fmt == "sexp" || fmt == "kicad") { if (!args["sexp_mod_name"]) { @@ -417,6 +424,7 @@ int main(int argc, char **argv) { min_feature_size, curve_tolerance, vec_sel, + outline_mode, }; SVGDocument doc; diff --git a/svg-flatten/src/out_gerber.cpp b/svg-flatten/src/out_gerber.cpp index 9513c0b..94c01de 100644 --- a/svg-flatten/src/out_gerber.cpp +++ b/svg-flatten/src/out_gerber.cpp @@ -27,13 +27,14 @@ using namespace gerbolyze; using namespace std; -SimpleGerberOutput::SimpleGerberOutput(ostream &out, bool only_polys, int digits_int, int digits_frac, double scale, d2p offset, bool flip_polarity) +SimpleGerberOutput::SimpleGerberOutput(ostream &out, bool only_polys, int digits_int, int digits_frac, double scale, d2p offset, bool flip_polarity, bool outline_mode) : StreamPolygonSink(out, only_polys), m_digits_int(digits_int), m_digits_frac(digits_frac), m_offset(offset), m_scale(scale), - m_flip_pol(flip_polarity) + m_flip_pol(flip_polarity), + m_outline_mode(outline_mode) { assert(1 <= digits_int && digits_int <= 9); assert(0 <= digits_frac && digits_frac <= 9); @@ -61,16 +62,20 @@ void SimpleGerberOutput::header_impl(d2p origin, d2p size) { SimpleGerberOutput& SimpleGerberOutput::operator<<(GerberPolarityToken pol) { assert(pol == GRB_POL_DARK || pol == GRB_POL_CLEAR); + if (m_outline_mode) { + assert(pol == GRB_POL_DARK); + } + if ((pol == GRB_POL_DARK) != m_flip_pol) { m_out << "%LPD*%" << endl; - } else if (pol == GRB_POL_CLEAR) { + } else { m_out << "%LPC*%" << endl; } return *this; } SimpleGerberOutput& SimpleGerberOutput::operator<<(const Polygon &poly) { - if (poly.size() < 3) { + if (poly.size() < 3 && !m_outline_mode) { cerr << "Warning: " << poly.size() << "-element polygon passed to SimpleGerberOutput" << endl; return *this; } @@ -78,11 +83,15 @@ SimpleGerberOutput& SimpleGerberOutput::operator<<(const Polygon &poly) { /* NOTE: Clipper and gerber both have different fixed-point scales. We get points in double mm. */ double x = round((poly[0][0] * m_scale + m_offset[0]) * m_gerber_scale); double y = round((m_height - poly[0][1] * m_scale + m_offset[1]) * m_gerber_scale); - m_out << "G36*" << endl; + if (!m_outline_mode) { + m_out << "G36*" << endl; + } + 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 << "D02*" << endl; m_out << "G01*" << endl; + for (size_t i=1; i 0.0; + + /* Skip filling for transparent fills. In outline mode, skip filling if a stroke is also set to avoid double lines. + */ + if (fill_color && !(rset.outline_mode && has_stroke)) { /* Clip paths. Consider all paths closed for filling. */ if (!clip_path.empty()) { Clipper c; @@ -262,6 +266,10 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings } } else { /* solid fill */ + if (rset.outline_mode) { + fill_color = GRB_DARK; + } + Paths f_polys; /* Important for gerber spec compliance and also for reliable rendering results irrespective of board house * and gerber viewer. */ @@ -274,81 +282,98 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings out.push_back(std::array{ ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale }); + + /* In outline mode, manually close polys */ + if (rset.outline_mode && !out.empty()) + out.push_back(out[0]); + *polygon_sink << (fill_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << out; } } } - if (stroke_color && stroke_width > 0.0) { + if (has_stroke) { ClipperOffset offx; offx.ArcTolerance = 0.01 * clipper_scale; /* 10µm; TODO: Make this configurable */ /* For stroking we have to separately handle open and closed paths */ - for (const auto &poly : closed_paths) { - if (poly.empty()) /* do we need this? */ + for (auto &poly : closed_paths) { + if (poly.empty()) continue; /* Special case: A closed path becomes a number of open paths when it is dashed. */ if (dasharray.empty()) { - offx.AddPath(poly, join_type, etClosedLine); + + if (rset.outline_mode) { + /* In outline mode, manually close polys */ + poly.push_back(poly[0]); + *polygon_sink << poly; + + } else { + offx.AddPath(poly, join_type, etClosedLine); + } } else { Path poly_copy(poly); poly_copy.push_back(poly[0]); Paths out; dash_path(poly_copy, out, dasharray); - offx.AddPaths(out, join_type, end_type); + + if (rset.outline_mode) { + *polygon_sink << out; + } else { + offx.AddPaths(out, join_type, end_type); + } } } for (const auto &poly : open_paths) { Paths out; dash_path(poly, out, dasharray); - offx.AddPaths(out, join_type, end_type); - } - /* Execute clipper offset operation to generate stroke outlines */ - offx.Execute(ptree, 0.5 * stroke_width * clipper_scale); + if (rset.outline_mode) { + *polygon_sink << out; + } else { + offx.AddPaths(out, join_type, end_type); + } + } - /* Clip. Note that after the outline, all we have is closed paths as any open path's stroke outline is itself - * a closed path. */ - if (!clip_path.empty()) { - Clipper c; + if (!rset.outline_mode) { + /* Execute clipper offset operation to generate stroke outlines */ + offx.Execute(ptree, 0.5 * stroke_width * clipper_scale); + + /* Clip. Note that after the outline, all we have is closed paths as any open path's stroke outline is itself + * a closed path. */ + if (!clip_path.empty()) { + Clipper c; + + Paths outline_paths; + PolyTreeToPaths(ptree, outline_paths); + c.AddPaths(outline_paths, ptSubject, /* closed */ true); + c.AddPaths(clip_path, ptClip, /* closed */ true); + c.StrictlySimple(true); + /* fill rules are nonzero since both subject and clip have already been normalized by clipper. */ + c.Execute(ctIntersection, ptree, pftNonZero, pftNonZero); + } - Paths outline_paths; - PolyTreeToPaths(ptree, outline_paths); - c.AddPaths(outline_paths, ptSubject, /* closed */ true); - c.AddPaths(clip_path, ptClip, /* closed */ true); - c.StrictlySimple(true); - /* fill rules are nonzero since both subject and clip have already been normalized by clipper. */ - c.Execute(ctIntersection, ptree, pftNonZero, pftNonZero); - } + /* Call out to pattern tiler for pattern strokes. The stroke's outline becomes the clip here. */ + if (stroke_color == GRB_PATTERN_FILL) { + string stroke_pattern_id = usvg_id_url(node.attribute("stroke").value()); + Pattern *pattern = lookup_pattern(stroke_pattern_id); + if (!pattern) { + cerr << "Warning: Fill pattern with id \"" << stroke_pattern_id << "\" not found." << endl; - /* Call out to pattern tiler for pattern strokes. The stroke's outline becomes the clip here. */ - if (stroke_color == GRB_PATTERN_FILL) { - string stroke_pattern_id = usvg_id_url(node.attribute("stroke").value()); - Pattern *pattern = lookup_pattern(stroke_pattern_id); - if (!pattern) { - cerr << "Warning: Fill pattern with id \"" << stroke_pattern_id << "\" not found." << endl; + } else { + Paths clip; + PolyTreeToPaths(ptree, clip); + pattern->tile(local_xf, rset, clip); + } } else { - Paths clip; - PolyTreeToPaths(ptree, clip); - pattern->tile(local_xf, rset, clip); - } + Paths s_polys; + dehole_polytree(ptree, s_polys); - } else { - Paths s_polys; - dehole_polytree(ptree, s_polys); - - /* export gerber */ - for (const auto &poly : s_polys) { - vector> out; - for (const auto &p : poly) - out.push_back(std::array{ - ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale - }); - *polygon_sink << (stroke_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << out; + *polygon_sink << (stroke_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << s_polys; } } } -- cgit