kopia lustrzana https://github.com/jaseg/gerbolyze
398 wiersze
15 KiB
C++
398 wiersze
15 KiB
C++
/*
|
|
* This file is part of gerbolyze, a vector image preprocessing toolchain
|
|
* Copyright (C) 2021 Jan Sebastian Götte <gerbolyze@jaseg.de>
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <map>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <array>
|
|
#include <cstdint>
|
|
|
|
#include <pugixml.hpp>
|
|
|
|
#include "svg_pattern.h"
|
|
#include "geom2d.hpp"
|
|
|
|
namespace gerbolyze {
|
|
|
|
constexpr char lib_version[] = "3.1.7";
|
|
|
|
typedef std::function<std::vector<d2p> *(double, double, double)> sampling_fun;
|
|
|
|
enum GerberPolarityToken {
|
|
GRB_POL_CLEAR,
|
|
GRB_POL_DARK
|
|
};
|
|
|
|
class LayerNameToken {
|
|
public:
|
|
std::string m_name;
|
|
};
|
|
|
|
class ApertureToken {
|
|
public:
|
|
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;
|
|
};
|
|
|
|
class PatternToken {
|
|
public:
|
|
PatternToken(vector<pair<Polygon, GerberPolarityToken>> &polys) : m_polys(polys) {}
|
|
vector<pair<Polygon, GerberPolarityToken>> &m_polys;
|
|
};
|
|
|
|
class FlashToken {
|
|
public:
|
|
FlashToken(d2p offset) : m_offset(offset) {}
|
|
d2p m_offset;
|
|
};
|
|
|
|
class PolygonSink {
|
|
public:
|
|
virtual ~PolygonSink() {}
|
|
virtual void header(d2p origin, d2p size) {(void) origin; (void) size;}
|
|
virtual bool can_do_apertures() { return false; }
|
|
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 PolygonSink &operator<<(const ApertureToken &) { return *this; };
|
|
virtual PolygonSink &operator<<(const FlashToken &) { return *this; };
|
|
virtual PolygonSink &operator<<(const PatternToken &) {
|
|
cerr << "Error: pattern to aperture mapping is not supporte for this output." << endl;
|
|
return *this;
|
|
};
|
|
virtual void footer() {}
|
|
};
|
|
|
|
class Flattener_D;
|
|
class Flattener : public PolygonSink {
|
|
public:
|
|
Flattener(PolygonSink &sink);
|
|
virtual ~Flattener();
|
|
virtual void header(d2p origin, d2p size);
|
|
virtual Flattener &operator<<(const Polygon &poly);
|
|
virtual Flattener &operator<<(const LayerNameToken &layer_name);
|
|
virtual Flattener &operator<<(GerberPolarityToken pol);
|
|
virtual Flattener &operator<<(const ApertureToken &tok);
|
|
virtual Flattener &operator<<(const FlashToken &tok);
|
|
virtual void footer();
|
|
|
|
private:
|
|
void render_out_clear_polys();
|
|
void flush_polys_to_sink();
|
|
PolygonSink &m_sink;
|
|
GerberPolarityToken m_current_polarity = GRB_POL_DARK;
|
|
Flattener_D *d;
|
|
};
|
|
|
|
class Dilater : public PolygonSink {
|
|
public:
|
|
Dilater(PolygonSink &sink, double dilation) : m_sink(sink), m_dilation(dilation) {}
|
|
virtual void header(d2p origin, d2p size);
|
|
virtual Dilater &operator<<(const Polygon &poly);
|
|
virtual Dilater &operator<<(const LayerNameToken &layer_name);
|
|
virtual Dilater &operator<<(GerberPolarityToken pol);
|
|
virtual Dilater &operator<<(const ApertureToken &ap);
|
|
virtual Dilater &operator<<(const FlashToken &tok);
|
|
virtual void footer();
|
|
|
|
private:
|
|
PolygonSink &m_sink;
|
|
double m_dilation;
|
|
GerberPolarityToken m_current_polarity = GRB_POL_DARK;
|
|
};
|
|
|
|
class PolygonScaler : public PolygonSink {
|
|
public:
|
|
PolygonScaler(PolygonSink &sink, double scale=1.0) : m_sink(sink), m_scale(scale) {}
|
|
virtual void header(d2p origin, d2p size);
|
|
virtual bool can_do_apertures();
|
|
virtual PolygonScaler &operator<<(const Polygon &poly);
|
|
virtual PolygonScaler &operator<<(const LayerNameToken &layer_name);
|
|
virtual PolygonScaler &operator<<(GerberPolarityToken pol);
|
|
virtual PolygonScaler &operator<<(const ApertureToken &tok);
|
|
virtual PolygonScaler &operator<<(const FlashToken &tok);
|
|
virtual PolygonScaler &operator<<(const PatternToken &tok);
|
|
virtual void footer();
|
|
|
|
private:
|
|
PolygonSink &m_sink;
|
|
double m_scale;
|
|
};
|
|
|
|
class StreamPolygonSink : public PolygonSink {
|
|
public:
|
|
StreamPolygonSink(std::ostream &out, bool only_polys=false) : m_only_polys(only_polys), m_out(out) {}
|
|
virtual ~StreamPolygonSink() {}
|
|
virtual void header(d2p origin, d2p size) { if (!m_only_polys) header_impl(origin, size); }
|
|
virtual void footer() { if (!m_only_polys) { footer_impl(); } m_out.flush(); }
|
|
|
|
protected:
|
|
virtual void header_impl(d2p origin, d2p size) = 0;
|
|
virtual void footer_impl() = 0;
|
|
|
|
bool m_only_polys = false;
|
|
std::ostream &m_out;
|
|
};
|
|
|
|
extern const std::vector<std::string> kicad_default_layers;
|
|
|
|
class ElementSelector {
|
|
public:
|
|
virtual bool match(const pugi::xml_node &node, bool is_toplevel, bool parent_include) const {
|
|
(void) node, (void) is_toplevel, (void) parent_include;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class IDElementSelector : public ElementSelector {
|
|
public:
|
|
virtual bool match(const pugi::xml_node &node, bool is_toplevel, bool parent_include) const;
|
|
|
|
std::vector<std::string> include;
|
|
std::vector<std::string> exclude;
|
|
const std::vector<std::string> *layers = nullptr;
|
|
};
|
|
|
|
class ImageVectorizer {
|
|
public:
|
|
virtual ~ImageVectorizer() {};
|
|
virtual void vectorize_image(RenderContext &ctx, const pugi::xml_node &node, double min_feature_size_px) = 0;
|
|
};
|
|
|
|
ImageVectorizer *makeVectorizer(const std::string &name);
|
|
|
|
class VectorizerSelectorizer {
|
|
public:
|
|
VectorizerSelectorizer(const std::string default_vectorizer="dev-null", const std::string defs="");
|
|
|
|
ImageVectorizer *select(const pugi::xml_node &img);
|
|
|
|
private:
|
|
std::string m_default;
|
|
std::map<std::string, std::string> m_map;
|
|
};
|
|
|
|
class RenderSettings {
|
|
public:
|
|
double m_minimum_feature_size_mm = 0.1;
|
|
double geometric_tolerance_mm = 0.1;
|
|
double stroke_width_cutoff = 0.01;
|
|
double drill_test_polsby_popper_tolerance = 0.01;
|
|
double aperture_circle_test_tolerance = 0.01;
|
|
double aperture_rect_test_tolerance = 0.01;
|
|
VectorizerSelectorizer &m_vec_sel;
|
|
bool outline_mode = false;
|
|
bool flip_color_interpretation = false;
|
|
bool pattern_complete_tiles_only = false;
|
|
bool use_apertures_for_patterns = false;
|
|
bool do_gerber_interpolation = true;
|
|
};
|
|
|
|
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,
|
|
bool included);
|
|
RenderContext(RenderContext &parent,
|
|
PolygonSink &sink,
|
|
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_root, m_included);
|
|
}
|
|
|
|
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) {}
|
|
|
|
/* true -> load successful */
|
|
bool load(std::istream &in, double scale=1.0);
|
|
bool load(std::string filename, double scale=1.0);
|
|
/* true -> load successful */
|
|
bool valid() const { return _valid; }
|
|
operator bool() const { return valid(); }
|
|
|
|
double mm_to_doc_units(double) const;
|
|
double doc_units_to_mm(double) const;
|
|
|
|
double width() const { return page_w_mm; }
|
|
double height() const { return page_h_mm; }
|
|
|
|
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;
|
|
|
|
const ClipperLib::Paths *lookup_clip_path(const pugi::xml_node &node);
|
|
Pattern *lookup_pattern(const std::string id);
|
|
|
|
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();
|
|
|
|
bool _valid;
|
|
pugi::xml_document svg_doc;
|
|
pugi::xml_node root_elem;
|
|
pugi::xml_node defs_node;
|
|
double vb_x, vb_y, vb_w, vb_h;
|
|
double page_w, page_h;
|
|
double page_w_mm, page_h_mm;
|
|
std::map<std::string, Pattern> pattern_map;
|
|
std::map<std::string, ClipperLib::Paths> clip_path_map;
|
|
ClipperLib::Paths vb_paths; /* viewport clip rect */
|
|
|
|
static constexpr double dbg_fill_alpha = 0.8;
|
|
static constexpr double dbg_stroke_alpha = 1.0;
|
|
static constexpr double assumed_usvg_dpi = 96.0;
|
|
};
|
|
|
|
typedef std::function<void (const Polygon &, GerberPolarityToken)> lambda_sink_fun;
|
|
class LambdaPolygonSink : public PolygonSink {
|
|
public:
|
|
LambdaPolygonSink(lambda_sink_fun lambda) : m_lambda(lambda) {}
|
|
|
|
virtual LambdaPolygonSink &operator<<(const Polygon &poly);
|
|
virtual LambdaPolygonSink &operator<<(GerberPolarityToken pol);
|
|
private:
|
|
GerberPolarityToken m_currentPolarity = GRB_POL_DARK;
|
|
lambda_sink_fun m_lambda;
|
|
};
|
|
|
|
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);
|
|
virtual ~SimpleGerberOutput() {}
|
|
virtual SimpleGerberOutput &operator<<(const Polygon &poly);
|
|
virtual SimpleGerberOutput &operator<<(GerberPolarityToken pol);
|
|
virtual SimpleGerberOutput &operator<<(const ApertureToken &ap);
|
|
virtual SimpleGerberOutput &operator<<(const FlashToken &tok);
|
|
virtual SimpleGerberOutput &operator<<(const PatternToken &tok);
|
|
virtual bool can_do_apertures() { return true; }
|
|
virtual void header_impl(d2p origin, d2p size);
|
|
virtual void footer_impl();
|
|
|
|
private:
|
|
int m_digits_int;
|
|
int m_digits_frac;
|
|
double m_width;
|
|
double m_height;
|
|
long long int m_gerber_scale;
|
|
d2p m_offset;
|
|
double m_scale;
|
|
bool m_flip_pol;
|
|
double m_current_aperture;
|
|
bool m_aperture_set;
|
|
bool m_macro_aperture;
|
|
unsigned int m_aperture_num;
|
|
};
|
|
|
|
class SimpleSVGOutput : public StreamPolygonSink {
|
|
public:
|
|
SimpleSVGOutput(std::ostream &out, bool only_polys=false, int digits_frac=6, std::string dark_color="#000000", std::string clear_color="#ffffff");
|
|
virtual ~SimpleSVGOutput() {}
|
|
virtual SimpleSVGOutput &operator<<(const Polygon &poly);
|
|
virtual SimpleSVGOutput &operator<<(GerberPolarityToken pol);
|
|
virtual SimpleSVGOutput &operator<<(const ApertureToken &ap);
|
|
virtual SimpleSVGOutput &operator<<(const FlashToken &tok);
|
|
virtual bool can_do_apertures() { return true; }
|
|
virtual void header_impl(d2p origin, d2p size);
|
|
virtual void footer_impl();
|
|
|
|
private:
|
|
int m_digits_frac;
|
|
std::string m_dark_color;
|
|
std::string m_clear_color;
|
|
std::string m_current_color;
|
|
double m_stroke_width;
|
|
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<<(const LayerNameToken &layer_name);
|
|
virtual KicadSexpOutput &operator<<(const FlashToken &tok);
|
|
virtual KicadSexpOutput &operator<<(GerberPolarityToken pol);
|
|
virtual void header_impl(d2p origin, d2p size);
|
|
virtual void footer_impl();
|
|
|
|
void set_export_layers(const std::vector<std::string> &layers) { m_export_layers = &layers; }
|
|
|
|
private:
|
|
const std::vector<std::string> *m_export_layers = &kicad_default_layers;
|
|
std::string m_mod_name;
|
|
std::string m_layer;
|
|
bool m_auto_layer;
|
|
std::string m_ref_text;
|
|
std::string m_val_text;
|
|
d2p m_ref_pos;
|
|
d2p m_val_pos;
|
|
};
|
|
}
|