kopia lustrzana https://github.com/jaseg/gerbolyze
svg-flatten: Finish direct interpolation optimization
rodzic
2fc5d1d929
commit
d3204b1ede
|
@ -46,7 +46,9 @@ namespace gerbolyze {
|
|||
|
||||
class ApertureToken {
|
||||
public:
|
||||
ApertureToken(double size=0.0) : m_size(size) {}
|
||||
ApertureToken() : m_has_aperture(false) {}
|
||||
ApertureToken(double size) : m_has_aperture(true), m_size(size) {}
|
||||
bool m_has_aperture = false;
|
||||
double m_size = 0.0;
|
||||
};
|
||||
|
||||
|
@ -317,7 +319,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, bool outline_mode=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);
|
||||
virtual ~SimpleGerberOutput() {}
|
||||
virtual SimpleGerberOutput &operator<<(const Polygon &poly);
|
||||
virtual SimpleGerberOutput &operator<<(GerberPolarityToken pol);
|
||||
|
@ -336,8 +338,8 @@ namespace gerbolyze {
|
|||
d2p m_offset;
|
||||
double m_scale;
|
||||
bool m_flip_pol;
|
||||
bool m_outline_mode;
|
||||
double m_current_aperture;
|
||||
bool m_aperture_set;
|
||||
bool m_macro_aperture;
|
||||
unsigned int m_aperture_num;
|
||||
};
|
||||
|
|
|
@ -246,8 +246,7 @@ int main(int argc, char **argv) {
|
|||
cerr << "Info: Loading scaled input @scale=" << scale << endl;
|
||||
}
|
||||
|
||||
sink = new SimpleGerberOutput(
|
||||
*out_f, only_polys, 4, precision, scale, {0,0}, args["flip_gerber_polarity"], outline_mode);
|
||||
sink = new SimpleGerberOutput(*out_f, only_polys, 4, precision, scale, {0,0}, args["flip_gerber_polarity"]);
|
||||
|
||||
} else if (fmt == "s-exp" || fmt == "sexp" || fmt == "kicad") {
|
||||
if (!args["sexp_mod_name"]) {
|
||||
|
|
|
@ -27,15 +27,15 @@
|
|||
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, bool outline_mode)
|
||||
SimpleGerberOutput::SimpleGerberOutput(ostream &out, bool only_polys, int digits_int, int digits_frac, double scale, d2p offset, bool flip_polarity)
|
||||
: 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_outline_mode(outline_mode),
|
||||
m_current_aperture(0.0),
|
||||
m_aperture_set(false),
|
||||
m_macro_aperture(false),
|
||||
m_aperture_num(10) /* See gerber standard */
|
||||
{
|
||||
|
@ -63,28 +63,27 @@ void SimpleGerberOutput::header_impl(d2p origin, d2p size) {
|
|||
}
|
||||
|
||||
SimpleGerberOutput& SimpleGerberOutput::operator<<(const ApertureToken &ap) {
|
||||
if (!m_macro_aperture && ap.m_size == m_current_aperture) {
|
||||
if (m_aperture_set && !m_macro_aperture && ap.m_size == m_current_aperture) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
m_aperture_set = ap.m_has_aperture;
|
||||
m_macro_aperture = false;
|
||||
m_current_aperture = ap.m_size;
|
||||
m_aperture_num += 1;
|
||||
|
||||
double size = (ap.m_size > 0.0) ? ap.m_size : 0.05;
|
||||
m_out << "%ADD" << m_aperture_num << "C," << size << "*%" << endl;
|
||||
m_out << "D" << m_aperture_num << "*" << endl;
|
||||
if (m_aperture_set) {
|
||||
m_current_aperture = ap.m_size;
|
||||
m_aperture_num += 1;
|
||||
|
||||
double size = (ap.m_size > 0.0) ? ap.m_size : 0.05;
|
||||
m_out << "%ADD" << m_aperture_num << "C," << size << "*%" << endl;
|
||||
m_out << "D" << m_aperture_num << "*" << endl;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -94,15 +93,15 @@ SimpleGerberOutput& SimpleGerberOutput::operator<<(GerberPolarityToken pol) {
|
|||
return *this;
|
||||
}
|
||||
SimpleGerberOutput& SimpleGerberOutput::operator<<(const Polygon &poly) {
|
||||
if (poly.size() < 3 && !m_outline_mode) {
|
||||
cerr << "Warning: " << poly.size() << "-element polygon passed to SimpleGerberOutput" << endl;
|
||||
if (poly.size() < 3 && !m_aperture_set) {
|
||||
cerr << "Warning: " << poly.size() << "-element polygon passed to SimpleGerberOutput in region mode" << endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
if (!m_outline_mode) {
|
||||
if (!m_aperture_set) {
|
||||
m_out << "G36*" << endl;
|
||||
}
|
||||
|
||||
|
@ -119,7 +118,7 @@ SimpleGerberOutput& SimpleGerberOutput::operator<<(const Polygon &poly) {
|
|||
<< "D01*" << endl;
|
||||
}
|
||||
|
||||
if (!m_outline_mode) {
|
||||
if (!m_aperture_set) {
|
||||
m_out << "G37*" << endl;
|
||||
}
|
||||
|
||||
|
@ -132,6 +131,8 @@ void SimpleGerberOutput::footer_impl() {
|
|||
|
||||
|
||||
SimpleGerberOutput &SimpleGerberOutput::operator<<(const FlashToken &tok) {
|
||||
assert(m_aperture_set);
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -50,7 +50,10 @@ PolygonScaler &PolygonScaler::operator<<(GerberPolarityToken pol) {
|
|||
}
|
||||
|
||||
PolygonScaler &PolygonScaler::operator<<(const ApertureToken &tok) {
|
||||
m_sink << ApertureToken(tok.m_size * m_scale);
|
||||
if (tok.m_has_aperture)
|
||||
m_sink << ApertureToken(tok.m_size * m_scale);
|
||||
else
|
||||
m_sink << tok;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -350,10 +350,6 @@ void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml
|
|||
stroke_clip.StrictlySimple(true);
|
||||
stroke_clip.AddPaths(ctx.clip(), ptClip, /* closed */ true);
|
||||
|
||||
ClipperOffset offx;
|
||||
offx.ArcTolerance = 0.01 * clipper_scale; /* 10µm; TODO: Make this configurable */
|
||||
offx.MiterLimit = stroke_miterlimit;
|
||||
|
||||
/* We forward strokes as regular gerber interpolations instead of tracing their outline using clipper when one
|
||||
* of these is true:
|
||||
*
|
||||
|
@ -362,107 +358,129 @@ void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml
|
|||
*
|
||||
* We have to ignore patterned strokes since then we recursively call down to the pattern renderer. The checks
|
||||
* in (2) are to make sure that the semantics of our source SVG align with gerber's aperture semantics. Gerber
|
||||
* cannot express anything other than "round" joins and ends. If a clip is set, the clipped line ends would not
|
||||
* be round so we have to exclude that as well. A possible future optimization would be to check if we actually
|
||||
* did clip the stroke, but that is too expensive since then we'd have to outline it first to account for
|
||||
* stroke thickness and end caps.
|
||||
* cannot express anything other than "round" joins and ends. If any part of the path is clipped, the clipped
|
||||
* line ends would not be round so we have to exclude that as well. In case of outline mode, we accept this
|
||||
* inaccuracies for usability (the only alternative would be to halt and catch fire).
|
||||
*/
|
||||
bool local_outline_mode =
|
||||
stroke_color != GRB_PATTERN_FILL && (
|
||||
ctx.settings().outline_mode || (
|
||||
ctx.clip().empty() &&
|
||||
end_type == ClipperLib::etOpenRound &&
|
||||
join_type == ClipperLib::jtRound));
|
||||
|
||||
/* For stroking we have to separately handle open and closed paths */
|
||||
for (auto &poly : stroke_closed) {
|
||||
if (poly.empty())
|
||||
continue;
|
||||
|
||||
/* Special case: A closed path becomes a number of open paths when it is dashed. */
|
||||
if (dasharray.empty()) {
|
||||
|
||||
if (local_outline_mode) {
|
||||
stroke_clip.AddPath(poly, ptSubject, /* closed */ true);
|
||||
} 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, stroke_dashoffset);
|
||||
|
||||
if (local_outline_mode) {
|
||||
stroke_clip.AddPaths(out, ptSubject, /* closed */ false);
|
||||
} else {
|
||||
offx.AddPaths(out, join_type, end_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &poly : stroke_open) {
|
||||
Paths out;
|
||||
dash_path(poly, out, dasharray, stroke_dashoffset);
|
||||
|
||||
if (ctx.settings().outline_mode) {
|
||||
stroke_clip.AddPaths(out, ptSubject, /* closed */ false);
|
||||
} else {
|
||||
offx.AddPaths(out, join_type, end_type);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.settings().outline_mode) {
|
||||
stroke_clip.Execute(ctIntersection, ptree, pftNonZero, pftNonZero);
|
||||
Paths outline_paths;
|
||||
ctx.sink() << ApertureToken(stroke_width);
|
||||
|
||||
ClosedPathsFromPolyTree(ptree, outline_paths);
|
||||
for (auto &path : outline_paths) {
|
||||
/* we have to connect the last and first point here */
|
||||
if (path.empty())
|
||||
|
||||
/* Calculate out dashes: A closed path becomes a number of open paths when it is dashed. */
|
||||
if (!dasharray.empty()) {
|
||||
for (auto &poly : stroke_closed) {
|
||||
if (poly.empty()) {
|
||||
continue;
|
||||
path.push_back(path[0]);
|
||||
ctx.sink() << path;
|
||||
}
|
||||
|
||||
poly.push_back(poly[0]);
|
||||
dash_path(poly, stroke_open, dasharray, stroke_dashoffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (stroke_color != GRB_PATTERN_FILL) {
|
||||
cerr << "Analyzing direct conversion of stroke" << endl;
|
||||
cerr << " stroke_closed.size() = " << stroke_closed.size() << endl;
|
||||
cerr << " stroke_open.size() = " << stroke_open.size() << endl;
|
||||
ctx.sink() << (stroke_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR);
|
||||
|
||||
ClipperOffset offx;
|
||||
offx.ArcTolerance = 0.01 * clipper_scale; /* see below. */
|
||||
offx.MiterLimit = 10;
|
||||
offx.AddPaths(ctx.clip(), jtRound, etClosedPolygon);
|
||||
PolyTree clip_ptree;
|
||||
offx.Execute(clip_ptree, -0.5 * stroke_width * clipper_scale);
|
||||
|
||||
Paths dilated_clip;
|
||||
ClosedPathsFromPolyTree(clip_ptree, dilated_clip);
|
||||
|
||||
Clipper stroke_clip;
|
||||
stroke_clip.StrictlySimple(true);
|
||||
stroke_clip.AddPaths(dilated_clip, ptClip, /* closed */ true);
|
||||
stroke_clip.AddPaths(stroke_closed, ptSubject, /* closed */ true);
|
||||
stroke_clip.AddPaths(stroke_open, ptSubject, /* closed */ false);
|
||||
stroke_clip.Execute(ctDifference, ptree, pftNonZero, pftNonZero);
|
||||
|
||||
/* Did any part of the path clip the clip path (which defaults to the document border)? */
|
||||
bool nothing_clipped = ptree.Total() == 0;
|
||||
|
||||
/* Can all joins be mapped? True if either jtRound, or if there are no joins. */
|
||||
bool joins_can_be_mapped = true;
|
||||
if (join_type != ClipperLib::jtRound) {
|
||||
for (auto &p : stroke_closed) {
|
||||
if (p.size() > 2) {
|
||||
joins_can_be_mapped = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OpenPathsFromPolyTree(ptree, outline_paths);
|
||||
ctx.sink() << outline_paths;
|
||||
/* Can all ends be mapped? True if either etOpenRound or if there are no ends (we only have closed paths) */
|
||||
bool ends_can_be_mapped = (end_type == ClipperLib::etOpenRound) || (stroke_open.size() == 0);
|
||||
/* Can gerber losslessly express this path? */
|
||||
bool gerber_lossless = nothing_clipped && ends_can_be_mapped && joins_can_be_mapped;
|
||||
|
||||
cerr << " nothing_clipped = " << nothing_clipped << endl;
|
||||
cerr << " ends_can_be_mapped = " << ends_can_be_mapped << endl;
|
||||
cerr << " joins_can_be_mapped = " << joins_can_be_mapped << endl;
|
||||
/* Accept loss of precision in outline mode. */
|
||||
if (ctx.settings().outline_mode || gerber_lossless ) {
|
||||
cerr << " -> converting directly" << endl;
|
||||
ctx.sink() << ApertureToken(stroke_width);
|
||||
for (auto &path : stroke_closed) {
|
||||
if (path.empty()) {
|
||||
continue;
|
||||
}
|
||||
/* We have to manually close these here. */
|
||||
path.push_back(path[0]);
|
||||
ctx.sink() << path;
|
||||
}
|
||||
ctx.sink() << stroke_open;
|
||||
return;
|
||||
}
|
||||
cerr << " -> NOT converting directly" << endl;
|
||||
/* else fall through to normal processing */
|
||||
}
|
||||
|
||||
ClipperOffset offx;
|
||||
offx.ArcTolerance = 0.01 * clipper_scale; /* 10µm; TODO: Make this configurable */
|
||||
offx.MiterLimit = stroke_miterlimit;
|
||||
|
||||
/* For stroking we have to separately handle open and closed paths since coincident start and end points may
|
||||
* render differently than joined start and end points. */
|
||||
offx.AddPaths(stroke_closed, join_type, etClosedLine);
|
||||
offx.AddPaths(stroke_open, join_type, end_type);
|
||||
/* Execute clipper offset operation to generate stroke outlines */
|
||||
offx.Execute(ptree, 0.5 * stroke_width * clipper_scale);
|
||||
|
||||
/* Clip. Note that (outside of outline mode) after the clipper outline operation, all we have is closed paths as
|
||||
* any open path's stroke outline is itself a closed path. */
|
||||
if (!ctx.clip().empty()) {
|
||||
Paths outline_paths;
|
||||
PolyTreeToPaths(ptree, outline_paths);
|
||||
Clipper stroke_clip;
|
||||
stroke_clip.StrictlySimple(true);
|
||||
stroke_clip.AddPaths(ctx.clip(), ptClip, /* closed */ true);
|
||||
stroke_clip.AddPaths(outline_paths, ptSubject, /* closed */ true);
|
||||
/* fill rules are nonzero since both subject and clip have already been normalized by clipper. */
|
||||
stroke_clip.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;
|
||||
|
||||
} else {
|
||||
Paths clip;
|
||||
PolyTreeToPaths(ptree, clip);
|
||||
RenderContext local_ctx(ctx, xform2d(), clip, true);
|
||||
pattern->tile(local_ctx);
|
||||
}
|
||||
|
||||
} else {
|
||||
/* Execute clipper offset operation to generate stroke outlines */
|
||||
offx.Execute(ptree, 0.5 * stroke_width * clipper_scale);
|
||||
|
||||
/* Clip. Note that (outside of outline mode) after the clipper outline operation, all we have is closed paths as
|
||||
* any open path's stroke outline is itself a closed path. */
|
||||
if (!ctx.clip().empty()) {
|
||||
Paths outline_paths;
|
||||
PolyTreeToPaths(ptree, outline_paths);
|
||||
stroke_clip.AddPaths(outline_paths, ptSubject, /* closed */ true);
|
||||
/* fill rules are nonzero since both subject and clip have already been normalized by clipper. */
|
||||
stroke_clip.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;
|
||||
|
||||
} else {
|
||||
Paths clip;
|
||||
PolyTreeToPaths(ptree, clip);
|
||||
RenderContext local_ctx(ctx, xform2d(), clip, true);
|
||||
pattern->tile(local_ctx);
|
||||
}
|
||||
|
||||
} else {
|
||||
Paths s_polys;
|
||||
dehole_polytree(ptree, s_polys);
|
||||
ctx.sink() << (stroke_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << ApertureToken() << s_polys;
|
||||
}
|
||||
Paths s_polys;
|
||||
dehole_polytree(ptree, s_polys);
|
||||
/* color has alredy been pushed above. */
|
||||
ctx.sink() << ApertureToken() << s_polys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue