aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-04-25 14:03:16 +0200
committerjaseg <git@jaseg.de>2021-04-25 14:03:16 +0200
commitf2c891533f5179bf3d8a1625b1fe490a0cd06a42 (patch)
treed64bc014aa247d425c5f1f6c64e70144956b8317
parent1180ebdc1f18044a74f22f17b4d500ce7d6543fa (diff)
downloadgerbolyze-f2c891533f5179bf3d8a1625b1fe490a0cd06a42.tar.gz
gerbolyze-f2c891533f5179bf3d8a1625b1fe490a0cd06a42.tar.bz2
gerbolyze-f2c891533f5179bf3d8a1625b1fe490a0cd06a42.zip
svg-flatten: Add outline/edge layer mode
-rw-r--r--svg-flatten/include/gerbolyze.hpp19
-rw-r--r--svg-flatten/src/main.cpp16
-rw-r--r--svg-flatten/src/out_gerber.cpp24
-rw-r--r--svg-flatten/src/svg_doc.cpp115
4 files changed, 118 insertions, 56 deletions
diff --git a/svg-flatten/include/gerbolyze.hpp b/svg-flatten/include/gerbolyze.hpp
index d1d5f85..0b4f03e 100644
--- a/svg-flatten/include/gerbolyze.hpp
+++ b/svg-flatten/include/gerbolyze.hpp
@@ -49,6 +49,21 @@ namespace gerbolyze {
virtual ~PolygonSink() {}
virtual void header(d2p origin, d2p size) {(void) origin; (void) size;}
virtual PolygonSink &operator<<(const Polygon &poly) = 0;
+ virtual PolygonSink &operator<<(const ClipperLib::Paths paths) {
+ for (const auto &poly : paths) {
+ *this << poly;
+ }
+ return *this;
+ };
+ virtual PolygonSink &operator<<(const ClipperLib::Path poly) {
+ vector<array<double, 2>> out;
+ for (const auto &p : poly) {
+ out.push_back(std::array<double, 2>{
+ ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale
+ });
+ }
+ return *this << out;
+ };
virtual PolygonSink &operator<<(const LayerNameToken &) { return *this; };
virtual PolygonSink &operator<<(GerberPolarityToken pol) = 0;
virtual void footer() {}
@@ -143,6 +158,7 @@ namespace gerbolyze {
double m_minimum_feature_size_mm = 0.1;
double curve_tolerance_mm;
VectorizerSelectorizer &m_vec_sel;
+ bool outline_mode = false;
};
class SVGDocument {
@@ -209,7 +225,7 @@ namespace gerbolyze {
class SimpleGerberOutput : public StreamPolygonSink {
public:
- SimpleGerberOutput(std::ostream &out, bool only_polys=false, int digits_int=4, int digits_frac=6, double scale=1.0, d2p offset={0,0}, bool flip_polarity=false);
+ SimpleGerberOutput(std::ostream &out, bool only_polys=false, int digits_int=4, int digits_frac=6, double scale=1.0, d2p offset={0,0}, bool flip_polarity=false, bool outline_mode=false);
virtual ~SimpleGerberOutput() {}
virtual SimpleGerberOutput &operator<<(const Polygon &poly);
virtual SimpleGerberOutput &operator<<(GerberPolarityToken pol);
@@ -225,6 +241,7 @@ namespace gerbolyze {
d2p m_offset;
double m_scale;
bool m_flip_pol;
+ bool m_outline_mode;
};
class SimpleSVGOutput : public StreamPolygonSink {
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<string>() : "#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<double>(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<poly.size(); i++) {
double x = round((poly[i][0] * m_scale + m_offset[0]) * m_gerber_scale);
double y = round((m_height - poly[i][1] * m_scale + m_offset[1]) * m_gerber_scale);
@@ -90,7 +99,10 @@ SimpleGerberOutput& SimpleGerberOutput::operator<<(const Polygon &poly) {
<< "Y" << setw(m_digits_int + m_digits_frac) << setfill('0') << std::internal << (long long int)y
<< "D01*" << endl;
}
- m_out << "G37*" << endl;
+
+ if (!m_outline_mode) {
+ m_out << "G37*" << endl;
+ }
return *this;
}
diff --git a/svg-flatten/src/svg_doc.cpp b/svg-flatten/src/svg_doc.cpp
index b7ae61b..48ee56d 100644
--- a/svg-flatten/src/svg_doc.cpp
+++ b/svg-flatten/src/svg_doc.cpp
@@ -237,8 +237,12 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings
ClosedPathsFromPolyTree(ptree_stroke, closed_paths);
PolyTreeToPaths(ptree_fill, fill_paths);
- /* Skip filling for transparent fills */
- if (fill_color) {
+ bool has_fill = fill_color;
+ bool has_stroke = stroke_color && stroke_width > 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, 2>{
((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<array<double, 2>> out;
- for (const auto &p : poly)
- out.push_back(std::array<double, 2>{
- ((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;
}
}
}