1 #include "padstack.hpp"
2 #include "board/board_layers.hpp"
3 #include "common/lut.hpp"
4 #include "util/util.hpp"
5 #include "nlohmann/json.hpp"
6 #include "util/bbox_accumulator.hpp"
7 
8 namespace horizon {
9 
10 const LutEnumStr<Padstack::Type> Padstack::type_lut = {
11         {"top", Padstack::Type::TOP}, {"bottom", Padstack::Type::BOTTOM}, {"through", Padstack::Type::THROUGH},
12         {"via", Padstack::Type::VIA}, {"hole", Padstack::Type::HOLE},     {"mechanical", Padstack::Type::MECHANICAL}};
13 
MyParameterProgram(Padstack * p,const std::string & c)14 Padstack::MyParameterProgram::MyParameterProgram(Padstack *p, const std::string &c) : ParameterProgramPolygon(c), ps(p)
15 {
16 }
17 
set_shape(const TokenCommand & cmd)18 std::optional<std::string> Padstack::MyParameterProgram::set_shape(const TokenCommand &cmd)
19 {
20     if (cmd.arguments.size() < 2 || cmd.arguments.at(0)->type != Token::Type::STR
21         || cmd.arguments.at(1)->type != Token::Type::STR)
22         return "not enough arguments";
23 
24     const auto &pclass = dynamic_cast<TokenString &>(*cmd.arguments.at(0).get()).string;
25     const auto &form = dynamic_cast<TokenString &>(*cmd.arguments.at(1).get()).string;
26 
27     if (form == "rectangle") {
28         int64_t width, height;
29         if (stack_pop(height) || stack_pop(width))
30             return "empty stack";
31         for (auto &it : ps->shapes) {
32             if (it.second.parameter_class == pclass) {
33                 it.second.form = Shape::Form::RECTANGLE;
34                 it.second.params = {width, height};
35             }
36         }
37     }
38     else if (form == "circle") {
39         int64_t diameter;
40         if (stack_pop(diameter))
41             return "empty stack";
42         for (auto &it : ps->shapes) {
43             if (it.second.parameter_class == pclass) {
44                 it.second.form = Shape::Form::CIRCLE;
45                 it.second.params = {diameter};
46             }
47         }
48     }
49     else if (form == "obround") {
50         int64_t width, height;
51         if (stack_pop(height) || stack_pop(width))
52             return "empty stack";
53         for (auto &it : ps->shapes) {
54             if (it.second.parameter_class == pclass) {
55                 it.second.form = Shape::Form::OBROUND;
56                 it.second.params = {width, height};
57             }
58         }
59     }
60     else if (form == "position") {
61         int64_t x, y;
62         if (stack_pop(y) || stack_pop(x))
63             return "empty stack";
64         for (auto &it : ps->shapes) {
65             if (it.second.parameter_class == pclass) {
66                 it.second.placement.shift = {x, y};
67             }
68         }
69     }
70 
71     else {
72         return "unknown form " + form;
73     }
74 
75     return {};
76 }
77 
set_hole(const TokenCommand & cmd)78 std::optional<std::string> Padstack::MyParameterProgram::set_hole(const TokenCommand &cmd)
79 {
80     if (cmd.arguments.size() < 2 || cmd.arguments.at(0)->type != Token::Type::STR
81         || cmd.arguments.at(1)->type != Token::Type::STR)
82         return "not enough arguments";
83 
84     const auto &pclass = dynamic_cast<TokenString &>(*cmd.arguments.at(0).get()).string;
85     const auto &shape = dynamic_cast<TokenString &>(*cmd.arguments.at(1).get()).string;
86 
87     if (shape == "round") {
88         int64_t diameter;
89         if (stack_pop(diameter))
90             return "empty stack";
91         for (auto &it : ps->holes) {
92             if (it.second.parameter_class == pclass) {
93                 it.second.shape = Hole::Shape::ROUND;
94                 it.second.diameter = diameter;
95             }
96         }
97     }
98     else if (shape == "slot") {
99         int64_t diameter, length;
100         if (stack_pop(length) || stack_pop(diameter))
101             return "empty stack";
102         for (auto &it : ps->holes) {
103             if (it.second.parameter_class == pclass) {
104                 it.second.shape = Hole::Shape::SLOT;
105                 it.second.diameter = diameter;
106                 it.second.length = length;
107             }
108         }
109     }
110 
111     else {
112         return "unknown shape " + shape;
113     }
114 
115     return {};
116 }
117 
get_polygons()118 std::map<UUID, Polygon> &Padstack::MyParameterProgram::get_polygons()
119 {
120     return ps->polygons;
121 }
122 
123 
get_command(const std::string & cmd)124 ParameterProgram::CommandHandler Padstack::MyParameterProgram::get_command(const std::string &cmd)
125 {
126     using namespace std::placeholders;
127     if (auto r = ParameterProgramPolygon::get_command(cmd)) {
128         return r;
129     }
130     else if (cmd == "set-shape") {
131         return static_cast<CommandHandler>(&Padstack::MyParameterProgram::set_shape);
132     }
133     else if (cmd == "set-hole") {
134         return static_cast<CommandHandler>(&Padstack::MyParameterProgram::set_hole);
135     }
136     return nullptr;
137 }
138 
Padstack(const Padstack & ps)139 Padstack::Padstack(const Padstack &ps)
140     : uuid(ps.uuid), name(ps.name), type(ps.type), polygons(ps.polygons), holes(ps.holes), shapes(ps.shapes),
141       parameter_set(ps.parameter_set), parameters_required(ps.parameters_required),
142       parameter_program(ps.parameter_program), version(ps.version)
143 {
144     update_refs();
145 }
146 
operator =(Padstack const & ps)147 void Padstack::operator=(Padstack const &ps)
148 {
149     uuid = ps.uuid;
150     name = ps.name;
151     type = ps.type;
152     polygons = ps.polygons;
153     holes = ps.holes;
154     shapes = ps.shapes;
155     parameter_set = ps.parameter_set;
156     parameters_required = ps.parameters_required;
157     parameter_program = ps.parameter_program;
158     version = ps.version;
159     update_refs();
160 }
161 
update_refs()162 void Padstack::update_refs()
163 {
164     parameter_program.ps = this;
165 }
166 
167 static const unsigned int app_version = 0;
168 
get_app_version()169 unsigned int Padstack::get_app_version()
170 {
171     return app_version;
172 }
173 
Padstack(const UUID & uu,const json & j)174 Padstack::Padstack(const UUID &uu, const json &j)
175     : uuid(uu), name(j.at("name").get<std::string>()), well_known_name(j.value("well_known_name", "")),
176       parameter_program(this, j.value("parameter_program", "")), version(app_version, j)
177 {
178     check_object_type(j, ObjectType::PADSTACK);
179     version.check(ObjectType::PADSTACK, name, uuid);
180     {
181         const json &o = j["polygons"];
182         for (auto it = o.cbegin(); it != o.cend(); ++it) {
183             auto u = UUID(it.key());
184             polygons.emplace(std::make_pair(u, Polygon(u, it.value())));
185         }
186     }
187     map_erase_if(polygons, [](const auto &a) { return a.second.vertices.size() == 0; });
188     {
189         const json &o = j["holes"];
190         for (auto it = o.cbegin(); it != o.cend(); ++it) {
191             auto u = UUID(it.key());
192             holes.emplace(std::make_pair(u, Hole(u, it.value())));
193         }
194     }
195     if (j.count("shapes")) {
196         const json &o = j["shapes"];
197         for (auto it = o.cbegin(); it != o.cend(); ++it) {
198             auto u = UUID(it.key());
199             shapes.emplace(std::make_pair(u, Shape(u, it.value())));
200         }
201     }
202     if (j.count("padstack_type")) {
203         type = type_lut.lookup(j.at("padstack_type"));
204     }
205     if (j.count("parameter_set")) {
206         parameter_set = parameter_set_from_json(j.at("parameter_set"));
207     }
208     if (j.count("parameters_required")) {
209         const json &o = j["parameters_required"];
210         for (auto it = o.cbegin(); it != o.cend(); ++it) {
211             parameters_required.insert(parameter_id_from_string(it.value()));
212         }
213     }
214 } // namespace horizon
215 
new_from_file(const std::string & filename)216 Padstack Padstack::new_from_file(const std::string &filename)
217 {
218     auto j = load_json_from_file(filename);
219     return Padstack(UUID(j.at("uuid").get<std::string>()), j);
220 }
221 
Padstack(const UUID & uu)222 Padstack::Padstack(const UUID &uu) : uuid(uu), parameter_program(this, ""), version(app_version)
223 {
224 }
225 
serialize() const226 json Padstack::serialize() const
227 {
228     json j;
229     version.serialize(j);
230     j["uuid"] = (std::string)uuid;
231     j["type"] = "padstack";
232     j["name"] = name;
233     j["well_known_name"] = well_known_name;
234     j["padstack_type"] = type_lut.lookup_reverse(type);
235     j["parameter_program"] = parameter_program.get_code();
236     j["parameter_set"] = parameter_set_serialize(parameter_set);
237     j["polygons"] = json::object();
238     for (const auto &it : polygons) {
239         j["polygons"][(std::string)it.first] = it.second.serialize();
240     }
241     j["holes"] = json::object();
242     for (const auto &it : holes) {
243         j["holes"][(std::string)it.first] = it.second.serialize();
244     }
245     j["shapes"] = json::object();
246     for (const auto &it : shapes) {
247         j["shapes"][(std::string)it.first] = it.second.serialize();
248     }
249     j["parameters_required"] = json::array();
250     for (const auto &it : parameters_required) {
251         j["parameters_required"].push_back(parameter_id_to_string(it));
252     }
253     return j;
254 }
255 
get_uuid() const256 UUID Padstack::get_uuid() const
257 {
258     return uuid;
259 }
260 
get_bbox(bool copper_only) const261 std::pair<Coordi, Coordi> Padstack::get_bbox(bool copper_only) const
262 {
263     BBoxAccumulator<Coordi::type> acc;
264     for (const auto &it : polygons) {
265         if (!copper_only || BoardLayers::is_copper(it.second.layer)) {
266             acc.accumulate(it.second.get_bbox());
267         }
268     }
269     for (const auto &it : shapes) {
270         if (!copper_only || BoardLayers::is_copper(it.second.layer)) {
271             const auto bb = it.second.placement.transform_bb(it.second.get_bbox());
272             acc.accumulate(bb);
273         }
274     }
275     return acc.get_or_0();
276 }
277 
get_layers() const278 const std::map<int, Layer> &Padstack::get_layers() const
279 {
280     static const std::map<int, Layer> layers = {{30, {30, "Top Paste"}},
281                                                 {10, {10, "Top Mask"}},
282                                                 {0, {0, "Top Copper", false, true}},
283                                                 {-1, {-1, "Inner", false, true}},
284                                                 {-100, {-100, "Bottom Copper", true, true}},
285                                                 {-110, {-110, "Bottom Mask", true}},
286                                                 {-130, {-130, "Bottom Paste"}}};
287     return layers;
288 }
289 
copy_param(ParameterSet & dest,const ParameterSet & src,ParameterID id)290 static void copy_param(ParameterSet &dest, const ParameterSet &src, ParameterID id)
291 {
292     if (src.count(id))
293         dest[id] = src.at(id);
294 }
295 
copy_param(ParameterSet & dest,const ParameterSet & src,const std::set<ParameterID> & ids)296 static void copy_param(ParameterSet &dest, const ParameterSet &src, const std::set<ParameterID> &ids)
297 {
298     for (const auto id : ids) {
299         copy_param(dest, src, id);
300     }
301 }
302 
apply_parameter_set(const ParameterSet & ps)303 std::optional<std::string> Padstack::apply_parameter_set(const ParameterSet &ps)
304 {
305     auto ps_this = parameter_set;
306     copy_param(ps_this, ps,
307                {ParameterID::PAD_HEIGHT, ParameterID::PAD_WIDTH, ParameterID::PAD_DIAMETER,
308                 ParameterID::SOLDER_MASK_EXPANSION, ParameterID::PASTE_MASK_CONTRACTION, ParameterID::HOLE_DIAMETER,
309                 ParameterID::HOLE_LENGTH, ParameterID::VIA_DIAMETER, ParameterID::HOLE_SOLDER_MASK_EXPANSION,
310                 ParameterID::VIA_SOLDER_MASK_EXPANSION, ParameterID::HOLE_ANNULAR_RING, ParameterID::CORNER_RADIUS});
311     return parameter_program.run(ps_this);
312 }
313 
expand_inner(unsigned int n_inner)314 void Padstack::expand_inner(unsigned int n_inner)
315 {
316     if (n_inner == 0) {
317         for (auto it = shapes.begin(); it != shapes.end();) {
318             if (it->second.layer == -1) {
319                 shapes.erase(it++);
320             }
321             else {
322                 it++;
323             }
324         }
325         for (auto it = polygons.begin(); it != polygons.end();) {
326             if (it->second.layer == -1) {
327                 polygons.erase(it++);
328             }
329             else {
330                 it++;
331             }
332         }
333     }
334     std::map<UUID, Polygon> new_polygons;
335     for (const auto &it : polygons) {
336         if (it.second.layer == -1) {
337             for (unsigned int i = 0; i < (n_inner - 1); i++) {
338                 auto uu = UUID::random();
339                 auto &np = new_polygons.emplace(uu, it.second).first->second;
340                 np.uuid = uu;
341                 np.layer = -2 - i;
342             }
343         }
344     }
345     polygons.insert(new_polygons.begin(), new_polygons.end());
346 
347     std::map<UUID, Shape> new_shapes;
348     for (const auto &it : shapes) {
349         if (it.second.layer == -1) {
350             for (unsigned int i = 0; i < (n_inner - 1); i++) {
351                 auto uu = UUID::random();
352                 auto &np = new_shapes.emplace(uu, it.second).first->second;
353                 np.uuid = uu;
354                 np.layer = -2 - i;
355             }
356         }
357     }
358     shapes.insert(new_shapes.begin(), new_shapes.end());
359 }
360 } // namespace horizon
361