diff options
Diffstat (limited to 'svg-flatten')
-rw-r--r-- | svg-flatten/include/gerbolyze.hpp | 63 | ||||
-rw-r--r-- | svg-flatten/src/main.cpp | 2 | ||||
-rw-r--r-- | svg-flatten/src/out_scaler.cpp | 10 | ||||
-rw-r--r-- | svg-flatten/src/out_sexp.cpp | 35 | ||||
-rw-r--r-- | svg-flatten/src/svg_doc.cpp | 192 | ||||
-rw-r--r-- | svg-flatten/src/svg_geom.cpp | 11 | ||||
-rw-r--r-- | svg-flatten/src/svg_pattern.cpp | 18 | ||||
-rw-r--r-- | svg-flatten/src/svg_pattern.h | 3 | ||||
-rw-r--r-- | svg-flatten/src/vec_core.cpp | 60 | ||||
-rw-r--r-- | svg-flatten/src/vec_core.h | 8 |
10 files changed, 229 insertions, 173 deletions
diff --git a/svg-flatten/include/gerbolyze.hpp b/svg-flatten/include/gerbolyze.hpp index 7b70501..2c21173 100644 --- a/svg-flatten/include/gerbolyze.hpp +++ b/svg-flatten/include/gerbolyze.hpp @@ -110,13 +110,13 @@ namespace gerbolyze { GerberPolarityToken m_current_polarity = GRB_POL_DARK; }; - class Scaler : public PolygonSink { + class PolygonScaler : public PolygonSink { public: - Scaler(PolygonSink &sink, double scale=1.0) : m_sink(sink), m_scale(scale) {} + PolygonScaler(PolygonSink &sink, double scale=1.0) : m_sink(sink), m_scale(scale) {} virtual void header(d2p origin, d2p size); - virtual Scaler &operator<<(const Polygon &poly); - virtual Scaler &operator<<(const LayerNameToken &layer_name); - virtual Scaler &operator<<(GerberPolarityToken pol); + virtual PolygonScaler &operator<<(const Polygon &poly); + virtual PolygonScaler &operator<<(const LayerNameToken &layer_name); + virtual PolygonScaler &operator<<(GerberPolarityToken pol); virtual void footer(); private: @@ -143,7 +143,10 @@ namespace gerbolyze { class ElementSelector { public: - virtual bool match(const pugi::xml_node &node, bool included, bool is_root) const = 0; + virtual bool match(const pugi::xml_node &node, bool included, bool is_root) const { + (void) node, (void) included, (void) is_root; + return true; + } }; class IDElementSelector : public ElementSelector { @@ -158,7 +161,7 @@ namespace gerbolyze { class ImageVectorizer { public: virtual ~ImageVectorizer() {}; - virtual void vectorize_image(xform2d &mat, const pugi::xml_node &node, ClipperLib::Paths &clip_path, PolygonSink &sink, double min_feature_size_px) = 0; + virtual void vectorize_image(RenderContext &ctx, const pugi::xml_node &node, double min_feature_size_px) = 0; }; ImageVectorizer *makeVectorizer(const std::string &name); @@ -183,6 +186,42 @@ namespace gerbolyze { bool flip_color_interpretation = false; }; + class RenderContext { + public: + RenderContext(const RenderSettings &settings, + PolygonSink &sink, + const ElementSelector &sel, + ClipperLib::Paths &clip); + RenderContext(RenderContext &parent, + xform2d transform); + RenderContext(RenderContext &parent, + xform2d transform, + ClipperLib::Paths &clip); + + PolygonSink &sink() { return m_sink; } + const ElementSelector &sel() { return m_sel; } + const RenderSettings &settings() { return m_settings; } + xform2d &mat() { return m_mat; } + bool root() const { return m_root; } + bool included() const { return m_included; } + ClipperLib::Paths &clip() { return m_clip; } + void transform(xform2d &transform) { + m_mat.transform(transform); + } + bool match(const pugi::xml_node &node) { + return m_sel.match(node, m_included, m_root); + } + + private: + PolygonSink &m_sink; + const RenderSettings &m_settings; + xform2d m_mat; + bool m_root; + bool m_included; /* TODO: refactor name */ + const ElementSelector &m_sel; + ClipperLib::Paths &m_clip; + }; + class SVGDocument { public: SVGDocument() : _valid(false) {} @@ -200,8 +239,8 @@ namespace gerbolyze { double width() const { return page_w_mm; } double height() const { return page_h_mm; } - void render(const RenderSettings &rset, PolygonSink &sink, const ElementSelector *sel=nullptr); - void render_to_list(const RenderSettings &rset, std::vector<std::pair<Polygon, GerberPolarityToken>> &out, const ElementSelector *sel=nullptr); + void render(const RenderSettings &rset, PolygonSink &sink, const ElementSelector &sel=ElementSelector()); + void render_to_list(const RenderSettings &rset, std::vector<std::pair<Polygon, GerberPolarityToken>> &out, const ElementSelector &sel=ElementSelector()); private: friend class Pattern; @@ -209,8 +248,8 @@ namespace gerbolyze { const ClipperLib::Paths *lookup_clip_path(const pugi::xml_node &node); Pattern *lookup_pattern(const std::string id); - void export_svg_group(xform2d &mat, 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(xform2d &mat, const RenderSettings &rset, const pugi::xml_node &node, ClipperLib::Paths &clip_path); + void export_svg_group(RenderContext &ctx, const pugi::xml_node &group); + void export_svg_path(RenderContext &ctx, const pugi::xml_node &node); void setup_viewport_clip(); void load_clips(const RenderSettings &rset); void load_patterns(); @@ -226,8 +265,6 @@ namespace gerbolyze { std::map<std::string, ClipperLib::Paths> clip_path_map; ClipperLib::Paths vb_paths; /* viewport clip rect */ - PolygonSink *polygon_sink = nullptr; - static constexpr double dbg_fill_alpha = 0.8; static constexpr double dbg_stroke_alpha = 1.0; static constexpr double assumed_usvg_dpi = 96.0; diff --git a/svg-flatten/src/main.cpp b/svg-flatten/src/main.cpp index bacfa71..54ce896 100644 --- a/svg-flatten/src/main.cpp +++ b/svg-flatten/src/main.cpp @@ -377,7 +377,7 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - doc.render(rset, *top_sink, &sel); + doc.render(rset, *top_sink, sel); remove(frob.c_str()); remove(barf.c_str()); diff --git a/svg-flatten/src/out_scaler.cpp b/svg-flatten/src/out_scaler.cpp index 983dffe..9b7de4e 100644 --- a/svg-flatten/src/out_scaler.cpp +++ b/svg-flatten/src/out_scaler.cpp @@ -29,27 +29,27 @@ using namespace gerbolyze; using namespace std; -void Scaler::header(d2p origin, d2p size) { +void PolygonScaler::header(d2p origin, d2p size) { m_sink.header({origin[0] * m_scale, origin[1] * m_scale}, {size[0] * m_scale, size[1] * m_scale}); } -void Scaler::footer() { +void PolygonScaler::footer() { m_sink.footer(); } -Scaler &Scaler::operator<<(const LayerNameToken &layer_name) { +PolygonScaler &PolygonScaler::operator<<(const LayerNameToken &layer_name) { m_sink << layer_name; return *this; } -Scaler &Scaler::operator<<(GerberPolarityToken pol) { +PolygonScaler &PolygonScaler::operator<<(GerberPolarityToken pol) { m_sink << pol; return *this; } -Scaler &Scaler::operator<<(const Polygon &poly) { +PolygonScaler &PolygonScaler::operator<<(const Polygon &poly) { Polygon new_poly; for (auto &p : poly) { new_poly.push_back({ p[0] * m_scale, p[1] * m_scale }); diff --git a/svg-flatten/src/out_sexp.cpp b/svg-flatten/src/out_sexp.cpp index 9a04416..1a7f1d0 100644 --- a/svg-flatten/src/out_sexp.cpp +++ b/svg-flatten/src/out_sexp.cpp @@ -29,6 +29,40 @@ using namespace gerbolyze; using namespace std; +/* 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<std::string> 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", + }); + + 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), @@ -105,4 +139,3 @@ void KicadSexpOutput::footer_impl() { m_out << ")" << endl; } - diff --git a/svg-flatten/src/svg_doc.cpp b/svg-flatten/src/svg_doc.cpp index 639d7fa..d90e00d 100644 --- a/svg-flatten/src/svg_doc.cpp +++ b/svg-flatten/src/svg_doc.cpp @@ -132,14 +132,7 @@ bool IDElementSelector::match(const pugi::xml_node &node, bool included, bool is } /* Recursively export all SVG elements in the given group. */ -void gerbolyze::SVGDocument::export_svg_group(xform2d &mat, const RenderSettings &rset, const pugi::xml_node &group, Paths &parent_clip_path, const ElementSelector *sel, bool included, bool is_root) { - - /* Load clip paths from defs given bezier flattening tolerance from rset */ - load_clips(rset); - - /* Enter the group's coordinate system */ - xform2d local_xf(mat); - local_xf.transform(xform2d(group.attribute("transform").value())); +void gerbolyze::SVGDocument::export_svg_group(RenderContext &ctx, const pugi::xml_node &group) { /* Fetch clip path from global registry and transform it into document coordinates. */ Paths clip_path; @@ -152,50 +145,56 @@ void gerbolyze::SVGDocument::export_svg_group(xform2d &mat, const RenderSettings } else { clip_path = *lookup; - local_xf.transform_paths(clip_path); + ctx.mat().transform_paths(clip_path); } /* Clip against parent's clip path (both are now in document coordinates) */ - if (!parent_clip_path.empty()) { + if (!ctx.clip().empty()) { if (!clip_path.empty()) { - combine_clip_paths(parent_clip_path, clip_path, clip_path); + Clipper c; + c.StrictlySimple(true); + c.AddPaths(ctx.clip(), ptClip, /* closed */ true); + c.AddPaths(clip_path, ptSubject, /* closed */ true); + /* Nonzero fill since both input clip paths must already have been preprocessed by clipper. */ + c.Execute(ctIntersection, clip_path, pftNonZero); } else { - clip_path = parent_clip_path; + clip_path = ctx.clip(); } } /* Iterate over the group's children, exporting them one by one. */ for (const auto &node : group.children()) { - if (sel && !sel->match(node, included, is_root)) + if (!ctx.match(node)) continue; string name(node.name()); + RenderContext elem_ctx(ctx, xform2d(node.attribute("transform").value()), clip_path); if (name == "g") { - if (is_root) { /* Treat top-level groups as "layers" like inkscape does. */ + if (ctx.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; + elem_ctx.sink() << tok; } - export_svg_group(local_xf, rset, node, clip_path, sel, true); + export_svg_group(elem_ctx, node); - if (is_root) { + if (ctx.root()) { LayerNameToken tok {""}; - *polygon_sink << tok; + elem_ctx.sink() << tok; } } else if (name == "path") { - export_svg_path(local_xf, rset, node, clip_path); + export_svg_path(elem_ctx, node); } else if (name == "image") { - ImageVectorizer *vec = rset.m_vec_sel.select(node); + ImageVectorizer *vec = ctx.settings().m_vec_sel.select(node); if (!vec) { cerr << "Cannot resolve vectorizer for node \"" << node.attribute("id").value() << "\"" << endl; continue; } - double min_feature_size_px = mm_to_doc_units(rset.m_minimum_feature_size_mm); - vec->vectorize_image(local_xf, node, clip_path, *polygon_sink, min_feature_size_px); + double min_feature_size_px = mm_to_doc_units(ctx.settings().m_minimum_feature_size_mm); + vec->vectorize_image(elem_ctx, node, min_feature_size_px); delete vec; } else if (name == "defs") { @@ -207,9 +206,9 @@ void gerbolyze::SVGDocument::export_svg_group(xform2d &mat, const RenderSettings } /* Export an SVG path element to gerber. Apply patterns and clip on the fly. */ -void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings &rset, const pugi::xml_node &node, Paths &clip_path) { - enum gerber_color fill_color = gerber_fill_color(node, rset); - enum gerber_color stroke_color = gerber_stroke_color(node, rset); +void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml_node &node) { + enum gerber_color fill_color = gerber_fill_color(node, ctx.settings()); + enum gerber_color stroke_color = gerber_stroke_color(node, ctx.settings()); double stroke_width = usvg_double_attr(node, "stroke-width", /* default */ 1.0); assert(stroke_width > 0.0); @@ -225,15 +224,13 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings } /* Load path from SVG path data and transform into document units. */ - xform2d local_xf(mat); - local_xf.transform(xform2d(node.attribute("transform").value())); /* FIXME transform stroke width here? */ - stroke_width = local_xf.doc2phys_dist(stroke_width); + stroke_width = ctx.mat().doc2phys_dist(stroke_width); Paths stroke_open, stroke_closed; PolyTree ptree_fill; PolyTree ptree; - load_svg_path(local_xf, node, stroke_open, stroke_closed, ptree_fill, rset.curve_tolerance_mm); + load_svg_path(ctx.mat(), node, stroke_open, stroke_closed, ptree_fill, ctx.settings().curve_tolerance_mm); Paths fill_paths; PolyTreeToPaths(ptree_fill, fill_paths); @@ -243,17 +240,16 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings /* 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)) { + if (has_fill && !(ctx.settings().outline_mode && has_stroke)) { /* Clip paths. Consider all paths closed for filling. */ - if (!clip_path.empty()) { + if (!ctx.clip().empty()) { Clipper c; c.AddPaths(fill_paths, ptSubject, /* closed */ true); - c.AddPaths(clip_path, ptClip, /* closed */ true); + c.AddPaths(ctx.clip(), 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_fill, pftNonZero, pftNonZero); - PolyTreeToPaths(ptree_fill, fill_paths); } /* Call out to pattern tiler for pattern fills. The path becomes the clip here. */ @@ -264,11 +260,13 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings cerr << "Warning: Fill pattern with id \"" << fill_pattern_id << "\" not found." << endl; } else { - pattern->tile(local_xf, rset, fill_paths); + PolyTreeToPaths(ptree_fill, fill_paths); + RenderContext local_ctx(ctx, xform2d(), fill_paths); + pattern->tile(local_ctx); } } else { /* solid fill */ - if (rset.outline_mode) { + if (ctx.settings().outline_mode) { fill_color = GRB_DARK; } @@ -286,10 +284,10 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings }); /* In outline mode, manually close polys */ - if (rset.outline_mode && !out.empty()) + if (ctx.settings().outline_mode && !out.empty()) out.push_back(out[0]); - *polygon_sink << (fill_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << out; + ctx.sink() << (fill_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << out; } } } @@ -307,10 +305,10 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings /* Special case: A closed path becomes a number of open paths when it is dashed. */ if (dasharray.empty()) { - if (rset.outline_mode && stroke_color != GRB_PATTERN_FILL) { + if (ctx.settings().outline_mode && stroke_color != GRB_PATTERN_FILL) { /* In outline mode, manually close polys */ poly.push_back(poly[0]); - *polygon_sink << ApertureToken() << poly; + ctx.sink() << ApertureToken() << poly; } else { offx.AddPath(poly, join_type, etClosedLine); @@ -322,8 +320,8 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings Paths out; dash_path(poly_copy, out, dasharray, stroke_dashoffset); - if (rset.outline_mode && stroke_color != GRB_PATTERN_FILL) { - *polygon_sink << ApertureToken(stroke_width) << out; + if (ctx.settings().outline_mode && stroke_color != GRB_PATTERN_FILL) { + ctx.sink() << ApertureToken(stroke_width) << out; } else { offx.AddPaths(out, join_type, end_type); } @@ -334,8 +332,8 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings Paths out; dash_path(poly, out, dasharray, stroke_dashoffset); - if (rset.outline_mode && stroke_color != GRB_PATTERN_FILL) { - *polygon_sink << ApertureToken(stroke_width) << out; + if (ctx.settings().outline_mode && stroke_color != GRB_PATTERN_FILL) { + ctx.sink() << ApertureToken(stroke_width) << out; } else { offx.AddPaths(out, join_type, end_type); } @@ -346,13 +344,13 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings /* 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()) { + if (!ctx.clip().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.AddPaths(ctx.clip(), 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); @@ -368,20 +366,21 @@ void gerbolyze::SVGDocument::export_svg_path(xform2d &mat, const RenderSettings } else { Paths clip; PolyTreeToPaths(ptree, clip); - pattern->tile(local_xf, rset, clip); + RenderContext local_ctx(ctx, xform2d(), clip); + pattern->tile(local_ctx); } - } else if (!rset.outline_mode) { + } else if (!ctx.settings().outline_mode) { Paths s_polys; dehole_polytree(ptree, s_polys); - *polygon_sink << ApertureToken() << (stroke_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << s_polys; + ctx.sink() << ApertureToken() << (stroke_color == GRB_DARK ? GRB_POL_DARK : GRB_POL_CLEAR) << s_polys; } } - *polygon_sink << ApertureToken(); + ctx.sink() << ApertureToken(); } -void gerbolyze::SVGDocument::render(const RenderSettings &rset, PolygonSink &sink, const ElementSelector *sel) { +void gerbolyze::SVGDocument::render(const RenderSettings &rset, PolygonSink &sink, const ElementSelector &sel) { assert(_valid); /* Export the actual SVG document. We do this as we go, i.e. we immediately process each element to gerber as we * encounter it instead of first rendering everything to a giant list of gerber primitives and then serializing @@ -389,21 +388,18 @@ void gerbolyze::SVGDocument::render(const RenderSettings &rset, PolygonSink &sin */ /* Scale document pixels to mm for sinks */ - Scaler scaler(sink, doc_units_to_mm(1.0)); + PolygonScaler scaler(sink, doc_units_to_mm(1.0)); + RenderContext ctx(rset, scaler, sel, vb_paths); + + /* Load clip paths from defs with given bezier flattening tolerance and unit scale */ + load_clips(rset); - polygon_sink = &scaler; scaler.header({vb_x, vb_y}, {vb_w, vb_h}); - ClipperLib::Clipper c; - 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; - xform2d xf; - export_svg_group(xf, rset, root_elem, vb_paths, sel, false, true); + export_svg_group(ctx, root_elem); scaler.footer(); - polygon_sink = nullptr; } -void gerbolyze::SVGDocument::render_to_list(const RenderSettings &rset, vector<pair<Polygon, GerberPolarityToken>> &out, const ElementSelector *sel) { +void gerbolyze::SVGDocument::render_to_list(const RenderSettings &rset, vector<pair<Polygon, GerberPolarityToken>> &out, const ElementSelector &sel) { LambdaPolygonSink sink([&out](const Polygon &poly, GerberPolarityToken pol) { out.emplace_back(pair<Polygon, GerberPolarityToken>{poly, pol}); }); @@ -413,14 +409,14 @@ void gerbolyze::SVGDocument::render_to_list(const RenderSettings &rset, vector<p void gerbolyze::SVGDocument::setup_viewport_clip() { /* Set up view port clip path */ Path vb_path; - for (auto &elem : vector<pair<double, double>> {{vb_x, vb_y}, {vb_x+vb_w, vb_y}, {vb_x+vb_w, vb_y+vb_h}, {vb_x, vb_y+vb_h}}) { - double x = elem.first, y = elem.second; - vb_path.push_back({ (cInt)round(x * clipper_scale), (cInt)round(y * clipper_scale) }); + for (d2p &p : vector<d2p> { + {vb_x, vb_y}, + {vb_x+vb_w, vb_y}, + {vb_x+vb_w, vb_y+vb_h}, + {vb_x, vb_y+vb_h}}) { + vb_path.push_back({ (cInt)round(p[0] * clipper_scale), (cInt)round(p[1] * clipper_scale) }); } vb_paths.push_back(vb_path); - - ClipperLib::Clipper c; - c.AddPaths(vb_paths, ptSubject, /* closed */ true); } void gerbolyze::SVGDocument::load_patterns() { @@ -478,35 +474,35 @@ void gerbolyze::SVGDocument::load_clips(const RenderSettings &rset) { } } -/* 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<std::string> 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", - }); + +gerbolyze::RenderContext::RenderContext(const RenderSettings &settings, + PolygonSink &sink, + const ElementSelector &sel, + ClipperLib::Paths &clip) : + m_sink(sink), + m_settings(settings), + m_mat(), + m_root(true), + m_included(false), + m_sel(sel), + m_clip(clip) +{ +} + +gerbolyze::RenderContext::RenderContext(RenderContext &parent, xform2d transform) : + RenderContext(parent, transform, parent.clip()) +{ +} + +gerbolyze::RenderContext::RenderContext(RenderContext &parent, xform2d transform, ClipperLib::Paths &clip) : + m_sink(parent.sink()), + m_settings(parent.settings()), + m_mat(parent.mat()), + m_root(false), + m_included(parent.included()), + m_sel(parent.sel()), + m_clip(clip) +{ + m_mat.transform(transform); +} + diff --git a/svg-flatten/src/svg_geom.cpp b/svg-flatten/src/svg_geom.cpp index f836567..a36c881 100644 --- a/svg-flatten/src/svg_geom.cpp +++ b/svg-flatten/src/svg_geom.cpp @@ -147,14 +147,3 @@ void gerbolyze::dehole_polytree(PolyTree &ptree, Paths &out) { } } - -/* Intersect two clip paths. Both must share a coordinate system. */ -void gerbolyze::combine_clip_paths(Paths &in_a, Paths &in_b, Paths &out) { - Clipper c; - c.StrictlySimple(true); - c.AddPaths(in_a, ptClip, /* closed */ true); - c.AddPaths(in_b, ptSubject, /* closed */ true); - /* Nonzero fill since both input clip paths must already have been preprocessed by clipper. */ - c.Execute(ctIntersection, out, pftNonZero); -} - diff --git a/svg-flatten/src/svg_pattern.cpp b/svg-flatten/src/svg_pattern.cpp index cbdf4dd..b68bf3a 100644 --- a/svg-flatten/src/svg_pattern.cpp +++ b/svg-flatten/src/svg_pattern.cpp @@ -54,17 +54,17 @@ gerbolyze::Pattern::Pattern(const pugi::xml_node &node, SVGDocument &doc) : _nod /* Tile pattern into gerber. Note that this function may be called several times in case the pattern is * referenced from multiple places, so we must not clobber any of the object's state. */ -void gerbolyze::Pattern::tile (xform2d &mat, const gerbolyze::RenderSettings &rset, ClipperLib::Paths &clip) { +void gerbolyze::Pattern::tile (gerbolyze::RenderContext &ctx) { assert(doc); /* Transform x, y, w, h from pattern coordinate space into parent coordinates by applying the inverse * patternTransform. This is necessary so we iterate over the correct bounds when tiling below */ - d2p pos_xf = mat.doc2phys(d2p{x, y}); + d2p pos_xf = ctx.mat().doc2phys(d2p{x, y}); double inst_x = pos_xf[0], inst_y = pos_xf[1]; - double inst_w = mat.doc2phys_dist(w); - double inst_h = mat.doc2phys_dist(h); + double inst_w = ctx.mat().doc2phys_dist(w); + double inst_h = ctx.mat().doc2phys_dist(h); - ClipperLib::IntRect clip_bounds = get_paths_bounds(clip); + ClipperLib::IntRect clip_bounds = get_paths_bounds(ctx.clip()); double bx = clip_bounds.left / clipper_scale; double by = clip_bounds.top / clipper_scale; double bw = (clip_bounds.right - clip_bounds.left) / clipper_scale; @@ -86,8 +86,7 @@ void gerbolyze::Pattern::tile (xform2d &mat, const gerbolyze::RenderSettings &rs } /* Switch to pattern coordinates */ - xform2d local_xf(mat); - local_xf.transform(patternTransform); + RenderContext pat_ctx(ctx, patternTransform); /* Iterate over all pattern tiles in pattern coordinates */ for (double inst_off_x = fmod(inst_x, inst_w) - 2*inst_w; @@ -98,7 +97,7 @@ void gerbolyze::Pattern::tile (xform2d &mat, const gerbolyze::RenderSettings &rs inst_off_y < bh + 2*inst_h; inst_off_y += inst_h) { - xform2d elem_xf(local_xf); + xform2d elem_xf; /* Change into this individual tile's coordinate system */ elem_xf.translate(inst_off_x, inst_off_y); if (has_vb) { @@ -109,7 +108,8 @@ void gerbolyze::Pattern::tile (xform2d &mat, const gerbolyze::RenderSettings &rs } /* Export the pattern tile's content like a group */ - doc->export_svg_group(elem_xf, rset, _node, clip); + RenderContext elem_ctx(pat_ctx, elem_xf); + doc->export_svg_group(elem_ctx, _node); } } } diff --git a/svg-flatten/src/svg_pattern.h b/svg-flatten/src/svg_pattern.h index 56444dd..73efe5a 100644 --- a/svg-flatten/src/svg_pattern.h +++ b/svg-flatten/src/svg_pattern.h @@ -30,13 +30,14 @@ namespace gerbolyze { class SVGDocument; class RenderSettings; +class RenderContext; class Pattern { public: Pattern() {} Pattern(const pugi::xml_node &node, SVGDocument &doc); - void tile (xform2d &mat, const gerbolyze::RenderSettings &rset, ClipperLib::Paths &clip); + void tile (RenderContext &ctx); private: double x, y, w, h; diff --git a/svg-flatten/src/vec_core.cpp b/svg-flatten/src/vec_core.cpp index 86641dd..1b36887 100644 --- a/svg-flatten/src/vec_core.cpp +++ b/svg-flatten/src/vec_core.cpp @@ -95,15 +95,19 @@ template<typename T> nopencv::Image<T> *img_from_node(const pugi::xml_node &node return img; } -void gerbolyze::draw_bg_rect(xform2d &mat, double width, double height, ClipperLib::Paths &clip_path, PolygonSink &sink) { +void gerbolyze::draw_bg_rect(RenderContext &ctx, double width, double height) { /* For our output to look correct, we have to paint the image's bounding box in black as background for our halftone * blobs. We cannot simply draw a rect here, though. Instead we have to first intersect the bounding box with the * clip path we get from the caller. * * First, setup the bounding box rectangle in our local px coordinate space. */ ClipperLib::Path rect_path; - for (auto &elem : vector<pair<double, double>> {{0, 0}, {width, 0}, {width, height}, {0, height}}) { - d2p xf(mat.doc2phys(d2p{elem.first, elem.second})); + for (auto &elem : vector<pair<double, double>> { + {0, 0}, + {width, 0}, + {width, height}, + {0, height}}) { + d2p xf(ctx.mat().doc2phys(d2p{elem.first, elem.second})); rect_path.push_back({ (ClipperLib::cInt)round(xf[0] * clipper_scale), (ClipperLib::cInt)round(xf[1] * clipper_scale) @@ -113,8 +117,8 @@ void gerbolyze::draw_bg_rect(xform2d &mat, double width, double height, ClipperL /* Intersect the bounding box with the caller's clip path */ ClipperLib::Clipper c; c.AddPath(rect_path, ClipperLib::ptSubject, /* closed */ true); - if (!clip_path.empty()) { - c.AddPaths(clip_path, ClipperLib::ptClip, /* closed */ true); + if (!ctx.clip().empty()) { + c.AddPaths(ctx.clip(), ClipperLib::ptClip, /* closed */ true); } ClipperLib::Paths rect_out; @@ -128,7 +132,7 @@ void gerbolyze::draw_bg_rect(xform2d &mat, double width, double height, ClipperL out.push_back(std::array<double, 2>{ ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale }); - sink << GRB_POL_CLEAR << out; + ctx.sink() << GRB_POL_CLEAR << out; } } @@ -152,7 +156,7 @@ void gerbolyze::draw_bg_rect(xform2d &mat, double width, double height, ClipperL * 4. It scales each of these voronoi cell polygons to match the input images brightness at the spot covered by this * cell. */ -void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml_node &node, ClipperLib::Paths &clip_path, PolygonSink &sink, double min_feature_size_px) { +void gerbolyze::VoronoiVectorizer::vectorize_image(RenderContext &ctx, const pugi::xml_node &node, double min_feature_size_px) { double x, y, width, height; parse_img_meta(node, x, y, width, height); nopencv::Image32f *img = img_from_node<float>(node); @@ -160,10 +164,8 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml return; /* Set up target transform using SVG transform and x/y attributes */ - xform2d local_xf(mat); - local_xf.transform(xform2d(node.attribute("transform").value())); - local_xf.translate(x, y); - cerr << "voronoi vectorizer: local_xf = " << local_xf.dbg_str() << endl; + RenderContext img_ctx(ctx, xform2d(1, 0, 0, 1, x, y)); + cerr << "voronoi vectorizer: local_xf = " << ctx.mat().dbg_str() << endl; double orig_rows = img->rows(); double orig_cols = img->cols(); @@ -176,10 +178,10 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml cerr << "aspect " << scale_x << ", " << scale_y << " / " << off_x << ", " << off_y << endl; /* Adjust minimum feature size given in mm and translate into px document units in our local coordinate system. */ - min_feature_size_px = local_xf.doc2phys_dist(min_feature_size_px); + min_feature_size_px = img_ctx.mat().doc2phys_dist(min_feature_size_px); cerr << " min_feature_size_px = " << min_feature_size_px << endl; - draw_bg_rect(local_xf, width, height, clip_path, sink); + draw_bg_rect(img_ctx, width, height); /* Set up a poisson-disc sampled point "grid" covering the image. Calculate poisson disc parameters from given * minimum feature size. */ @@ -306,7 +308,7 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml if (last_fill_factor != fill_factor) { /* Fill factor was adjusted since last edge, so generate one extra point so we have a nice radial * "step". */ - d2p p = local_xf.doc2phys(d2p{ + d2p p = img_ctx.mat().doc2phys(d2p{ off_x + center.x + (e->pos[0].x - center.x) * fill_factor, off_y + center.y + (e->pos[0].y - center.y) * fill_factor }); @@ -318,7 +320,7 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml } /* Emit endpoint of current edge */ - d2p p = local_xf.doc2phys(d2p{ + d2p p = img_ctx.mat().doc2phys(d2p{ off_x + center.x + (e->pos[1].x - center.x) * fill_factor, off_y + center.y + (e->pos[1].y - center.y) * fill_factor }); @@ -339,8 +341,8 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml ClipperLib::Paths polys; ClipperLib::Clipper c; c.AddPath(cell_path, ClipperLib::ptSubject, /* closed */ true); - if (!clip_path.empty()) { - c.AddPaths(clip_path, ClipperLib::ptClip, /* closed */ true); + if (!img_ctx.clip().empty()) { + c.AddPaths(img_ctx.clip(), ClipperLib::ptClip, /* closed */ true); } c.StrictlySimple(true); c.Execute(ClipperLib::ctIntersection, polys, ClipperLib::pftNonZero, ClipperLib::pftNonZero); @@ -352,7 +354,7 @@ void gerbolyze::VoronoiVectorizer::vectorize_image(xform2d &mat, const pugi::xml out.push_back(std::array<double, 2>{ ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale }); - sink << GRB_POL_DARK << out; + img_ctx.sink() << GRB_POL_DARK << out; } } @@ -413,7 +415,7 @@ void gerbolyze::handle_aspect_ratio(string spec, double &scale_x, double &scale_ } -void gerbolyze::OpenCVContoursVectorizer::vectorize_image(xform2d &mat, const pugi::xml_node &node, ClipperLib::Paths &clip_path, PolygonSink &sink, double min_feature_size_px) { +void gerbolyze::OpenCVContoursVectorizer::vectorize_image(RenderContext &ctx, const pugi::xml_node &node, double min_feature_size_px) { (void) min_feature_size_px; /* unused by this vectorizer */ double x, y, width, height; parse_img_meta(node, x, y, width, height); @@ -422,9 +424,7 @@ void gerbolyze::OpenCVContoursVectorizer::vectorize_image(xform2d &mat, const pu return; /* Set up target transform using SVG transform and x/y attributes */ - xform2d local_xf(mat); - local_xf.transform(xform2d(node.attribute("transform").value())); - local_xf.translate(x, y); + RenderContext img_ctx(ctx, xform2d(1, 0, 0, 1, x, y)); double scale_x = (double)width / (double)img->cols(); double scale_y = (double)height / (double)img->rows(); @@ -433,24 +433,24 @@ void gerbolyze::OpenCVContoursVectorizer::vectorize_image(xform2d &mat, const pu handle_aspect_ratio(node.attribute("preserveAspectRatio").value(), scale_x, scale_y, off_x, off_y, img->cols(), img->rows()); - draw_bg_rect(local_xf, width, height, clip_path, sink); + draw_bg_rect(img_ctx, width, height); img->binarize(128); nopencv::find_contours(*img, nopencv::simplify_contours_douglas_peucker( - [&sink, &local_xf, &clip_path, off_x, off_y, scale_x, scale_y](Polygon_i& poly, nopencv::ContourPolarity pol) { + [&img_ctx, off_x, off_y, scale_x, scale_y](Polygon_i& poly, nopencv::ContourPolarity pol) { if (pol == nopencv::CP_HOLE) { std::reverse(poly.begin(), poly.end()); - sink << GRB_POL_CLEAR; + img_ctx.sink() << GRB_POL_CLEAR; } else { - sink << GRB_POL_DARK; + img_ctx.sink() << GRB_POL_DARK; } ClipperLib::Path out; for (const auto &p : poly) { - d2p q = local_xf.doc2phys(d2p{ + d2p q = img_ctx.mat().doc2phys(d2p{ off_x + (double)p[0] * scale_x, off_y + (double)p[1] * scale_y }); @@ -462,8 +462,8 @@ void gerbolyze::OpenCVContoursVectorizer::vectorize_image(xform2d &mat, const pu ClipperLib::Clipper c; c.AddPath(out, ClipperLib::ptSubject, /* closed */ true); - if (!clip_path.empty()) { - c.AddPaths(clip_path, ClipperLib::ptClip, /* closed */ true); + if (!img_ctx.clip().empty()) { + c.AddPaths(img_ctx.clip(), ClipperLib::ptClip, /* closed */ true); } c.StrictlySimple(true); ClipperLib::Paths polys; @@ -476,7 +476,7 @@ void gerbolyze::OpenCVContoursVectorizer::vectorize_image(xform2d &mat, const pu out.push_back(std::array<double, 2>{ ((double)p.X) / clipper_scale, ((double)p.Y) / clipper_scale }); - sink << out; + img_ctx.sink() << out; } })); } diff --git a/svg-flatten/src/vec_core.h b/svg-flatten/src/vec_core.h index ce25807..024d98f 100644 --- a/svg-flatten/src/vec_core.h +++ b/svg-flatten/src/vec_core.h @@ -29,7 +29,7 @@ namespace gerbolyze { public: VoronoiVectorizer(grid_type grid, bool relax=true) : m_relax(relax), m_grid_type(grid) {} - virtual void vectorize_image(xform2d &mat, const pugi::xml_node &node, ClipperLib::Paths &clip_path, PolygonSink &sink, double min_feature_size_px); + virtual void vectorize_image(RenderContext &ctx, const pugi::xml_node &node, double min_feature_size_px); private: double m_relax; grid_type m_grid_type; @@ -39,18 +39,18 @@ namespace gerbolyze { public: OpenCVContoursVectorizer() {} - virtual void vectorize_image(xform2d &mat, const pugi::xml_node &node, ClipperLib::Paths &clip_path, PolygonSink &sink, double min_feature_size_px); + virtual void vectorize_image(RenderContext &ctx, const pugi::xml_node &node, double min_feature_size_px); }; class DevNullVectorizer : public ImageVectorizer { public: DevNullVectorizer() {} - virtual void vectorize_image(xform2d &, const pugi::xml_node &, ClipperLib::Paths &, PolygonSink &, double) {} + virtual void vectorize_image(RenderContext &, const pugi::xml_node &, double) {} }; void parse_img_meta(const pugi::xml_node &node, double &x, double &y, double &width, double &height); - void draw_bg_rect(xform2d &mat, double width, double height, ClipperLib::Paths &clip_path, PolygonSink &sink); + void draw_bg_rect(RenderContext &ctx, double width, double height); void handle_aspect_ratio(std::string spec, double &scale_x, double &scale_y, double &off_x, double &off_y, double cols, double rows); } |