1 #include <regex>
2 #include "display.hpp"
3 #include "widgets/preview_canvas.hpp"
4 #include "widgets/spin_button_dim.hpp"
5 #include "board/board_layers.hpp"
6 #include "logger/logger.hpp"
7 #include "util/str_util.hpp"
8 #include "document/idocument_package.hpp"
9 #include "pool/ipool.hpp"
10 #include "canvas/canvas_gl.hpp"
11 
12 namespace horizon {
13 static const auto PLUSMINUS = std::string("\u00B1");
14 
create(IDocumentPackage & c,enum footag_type type)15 FootagDisplay *FootagDisplay::create(IDocumentPackage &c, enum footag_type type)
16 {
17     FootagDisplay *w;
18     Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
19     x->add_from_resource("/org/horizon-eda/horizon/imp/footprint_generator/footag/footag.ui");
20     x->get_widget_derived("box", w, c, type);
21     w->reference();
22     return w;
23 }
24 
FootagDisplay(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,IDocumentPackage & c,enum footag_type type)25 FootagDisplay::FootagDisplay(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, IDocumentPackage &c,
26                              enum footag_type type)
27     : Gtk::Box(cobject), core(c), ppkg(UUID::random())
28 {
29     ctx = footag_open(type);
30     if (!ctx) {
31         return;
32     }
33 
34     params = footag_get_param(ctx);
35 
36     canvas_package = Gtk::manage(new PreviewCanvas(core.get_pool(), true));
37     canvas_package->set_size_request(400, 500);
38     canvas_package->signal_size_allocate().connect([this](auto &alloc) {
39         if (autofit->get_active() && !(alloc == old_alloc)) {
40             this->display();
41             old_alloc = alloc;
42         }
43     });
44     canvas_package->set_hexpand(true);
45     canvas_package->set_vexpand(true);
46 
47     x->get_widget("reference_title", reference_title);
48     x->get_widget("reference_label", reference_label);
49     x->get_widget("autofit", autofit);
50     x->get_widget("hint_label", hint_label);
51     autofit->signal_clicked().connect([this] {
52         if (autofit->get_active()) {
53             display();
54         }
55     });
56 
57     Gtk::Box *canvas_box;
58     x->get_widget("canvas_box", canvas_box);
59     canvas_package->show();
60     canvas_box->pack_start(*canvas_package, true, true, 0);
61 
62     {
63         Gtk::Label *la;
64         x->get_widget("package_label", la);
65         auto ti = footag_get_typeinfo(type);
66         la->set_label(ti->brief);
67 
68         Gtk::Label *la_desc;
69         x->get_widget("package_description", la_desc);
70         if (ti->desc) {
71             la_desc->set_label(ti->desc);
72         }
73         else {
74             la_desc->hide();
75             la_desc->set_no_show_all(true);
76         }
77     }
78 
79     Gtk::Box *content_box;
80     x->get_widget("content_box", content_box);
81 
82     Gtk::Box *current_box = nullptr;
83     Gtk::Grid *grid = nullptr;
84 
85     auto sg_name = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
86     auto sg_abbr = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
87     auto sg_switch = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
88 
89     unsigned int i = 0;
90     for (struct footag_param *p = params; p->id != FOOTAG_PARAM_DONE; p++) {
91         if (p->id == FOOTAG_PARAM_IGNORE) {
92             continue;
93         }
94         if (p->id == FOOTAG_PARAM_TOPIC) {
95             current_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 5));
96             {
97                 auto la = Gtk::manage(new Gtk::Label());
98                 la->set_markup("<b>" + std::string(p->name) + "</b>");
99                 la->set_halign(Gtk::ALIGN_START);
100                 current_box->pack_start(*la, false, false, 0);
101                 la->show();
102 
103                 grid = Gtk::manage(new Gtk::Grid);
104                 grid->set_row_spacing(5);
105                 grid->set_column_spacing(5);
106                 grid->set_margin_start(5);
107                 current_box->pack_start(*grid, false, false, 0);
108                 grid->show();
109                 i = 0;
110             }
111             content_box->pack_start(*current_box, false, false, 0);
112             continue;
113         }
114 
115         {
116             const bool use_abbr = true;
117             auto la = Gtk::manage(new Gtk::Label());
118             la->set_label(p->name);
119             la->set_xalign(1);
120             la->get_style_context()->add_class("dim-label");
121             sg_name->add_widget(*la);
122             grid->attach(*la, 0, i, 1, 1);
123 
124             auto la_abbr = Gtk::manage(new Gtk::Label());
125             if (use_abbr && (std::string)p->abbr != "-") {
126                 la_abbr->set_markup("<b>" + std::string(p->abbr) + "</b>");
127             }
128             la_abbr->set_xalign(0);
129             la_abbr->get_style_context()->add_class("dim-label");
130             sg_abbr->add_widget(*la_abbr);
131             grid->attach(*la_abbr, 1, i, 1, 1);
132         }
133 
134         if (p->item.type == FOOTAG_DATA_BOOL) {
135             auto bb = &p->item.data.b;
136             auto wi = Gtk::manage(new Gtk::Switch());
137             wi->property_active().signal_changed().connect([this, bb, wi] {
138                 *bb = wi->get_active();
139                 calc_and_display();
140             });
141             wi->set_active(*bb);
142             wi->signal_grab_focus().connect([this, p] {
143                 /* NOTE: this does not work */
144                 help(p);
145             });
146             auto rbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
147             rbox->add(*wi);
148             grid->attach(*rbox, 2, i, 1, 1);
149         }
150         else if (p->item.type == FOOTAG_DATA_INTEGER) {
151             struct footag_integer *ii = &p->item.data.i;
152             auto wi = Gtk::manage(new Gtk::SpinButton());
153             wi->set_numeric(true);
154             wi->set_range(ii->min, ii->max);
155             wi->set_increments(ii->step, ii->step);
156             wi->set_snap_to_ticks(true);
157             wi->set_value(ii->val);
158             wi->signal_value_changed().connect([this, ii, wi] {
159                 ii->val = wi->get_value_as_int();
160                 calc_and_display();
161             });
162             wi->signal_grab_focus().connect([this, p] { help(p); });
163             grid->attach(*wi, 2, i, 1, 1);
164         }
165         else if (p->item.type == FOOTAG_DATA_LENGTH) {
166             auto wi = Gtk::manage(new SpinButtonDim());
167             wi->set_range(0, 200_mm);
168             wi->set_increments(0.05_mm, 0.1_mm);
169             wi->set_value(p->item.data.l * 1_mm);
170             wi->signal_value_changed().connect([this, p, wi] {
171                 p->item.data.l = wi->get_value() / 1_mm;
172                 calc_and_display();
173             });
174             wi->signal_grab_focus().connect([this, p] { help(p); });
175             grid->attach(*wi, 2, i, 1, 1);
176         }
177         else if (p->item.type == FOOTAG_DATA_ENUM) {
178             struct footag_enum *e = &p->item.data.e;
179             /* if few options then radio buttons, else combo */
180             if (e->num <= 4) {
181                 auto rbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
182                 rbox->get_style_context()->add_class("linked");
183                 rbox->set_halign(Gtk::ALIGN_START);
184                 auto first = Gtk::manage(new Gtk::RadioButton(e->strs[0]));
185                 Gtk::RadioButton *wi = nullptr;
186                 for (unsigned int j = 0; j < e->num; j++) {
187                     if (j == 0) {
188                         wi = first;
189                     }
190                     else {
191                         wi = Gtk::manage(new Gtk::RadioButton(e->strs[j]));
192                         wi->join_group(*first);
193                     }
194                     wi->set_mode(false);
195                     wi->set_active(e->val == j);
196                     wi->signal_toggled().connect([this, wi, e, j] {
197                         if (wi->get_active()) {
198                             e->val = j;
199                             calc_and_display();
200                         }
201                     });
202                     wi->signal_grab_focus().connect([this, p] { help(p); });
203                     rbox->pack_start(*wi, true, true, 0);
204                 }
205                 grid->attach(*rbox, 2, i, 3, 1);
206                 sg_switch->add_widget(*rbox);
207             }
208             else {
209                 auto wi = Gtk::manage(new Gtk::ComboBoxText());
210                 for (unsigned int j = 0; j < e->num; j++) {
211                     wi->append(e->strs[j]);
212                 }
213                 wi->set_active(e->val);
214                 wi->signal_changed().connect([this, e, wi] {
215                     e->val = wi->get_active_row_number();
216                     calc_and_display();
217                 });
218                 wi->signal_grab_focus().connect([this, p] {
219                     /* NOTE: this does not work */
220                     help(p);
221                 });
222                 grid->attach(*wi, 2, i, 1, 1);
223                 sg_switch->add_widget(*wi);
224             }
225         }
226         else if (p->item.type == FOOTAG_DATA_BITMASK) {
227             struct footag_bitmask *m = &p->item.data.m;
228             auto rbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
229             rbox->get_style_context()->add_class("linked");
230             rbox->set_halign(Gtk::ALIGN_START);
231             for (unsigned int j = 0; j < m->num; j++) {
232                 auto wi = Gtk::manage(new Gtk::ToggleButton(m->strs[j]));
233                 unsigned long themask = 1 << j;
234                 wi->set_active(m->val & themask);
235                 wi->signal_toggled().connect([this, wi, m, themask] {
236                     if (wi->get_active()) {
237                         m->val |= themask;
238                     }
239                     else {
240                         m->val &= ~themask;
241                     }
242                     calc_and_display();
243                 });
244                 wi->signal_grab_focus().connect([this, p] { help(p); });
245                 rbox->pack_start(*wi, true, true, 0);
246             }
247             grid->attach(*rbox, 2, i, 3, 1);
248             sg_switch->add_widget(*rbox);
249         }
250         else if (p->item.type == FOOTAG_DATA_TOL) {
251             struct footol *t = &p->item.data.t;
252 
253             auto wi0 = Gtk::manage(new SpinButtonDim());
254             auto wi1 = Gtk::manage(new SpinButtonDim());
255             wi0->set_range(0, 200_mm);
256             wi1->set_range(0, 200_mm);
257             wi0->set_increments(0.05_mm, 0.1_mm);
258             wi1->set_increments(0.05_mm, 0.1_mm);
259             wi0->set_value(t->nom * 1.0_mm);
260             wi1->set_value(t->max * 0.5_mm - t->min * 0.5_mm);
261 
262             /* button selects "nominal, plusminus" or "min, max" */
263             auto pm = Gtk::manage(new Gtk::Button(PLUSMINUS));
264             pm->signal_clicked().connect([t, pm, wi0, wi1] {
265                 if (pm->get_label() == "to") {
266                     int64_t v0 = t->nom * 1.0_mm;
267                     int64_t v1 = t->max * 0.5_mm - t->min * 0.5_mm;
268                     pm->set_label(PLUSMINUS);
269                     wi0->set_value(v0);
270                     wi1->set_value(v1);
271                 }
272                 else {
273                     pm->set_label("to");
274                     wi0->set_value(t->min * 1_mm);
275                     wi1->set_value(t->max * 1_mm);
276                 }
277             });
278 
279             wi0->signal_value_changed().connect([this, t, pm, wi0, wi1] {
280                 if (pm->get_label() == "to") {
281                     int64_t min = wi0->get_value_as_int();
282                     int64_t max = t->max * 1_mm;
283                     int64_t newmax = max;
284                     if (max < min) {
285                         newmax = min;
286                     }
287                     footol_minmax(t, 1.0 * min / 1_mm, 1.0 * newmax / 1_mm);
288                     if (max != newmax) {
289                         wi1->set_value(newmax);
290                     }
291                 }
292                 else {
293                     int64_t nom = wi0->get_value_as_int();
294                     int64_t diff = t->max * 0.5_mm - t->min * 0.5_mm;
295                     footol_pm(t, 1.0 * nom / 1_mm, 1.0 * diff / 1_mm);
296                 }
297                 calc_and_display();
298             });
299 
300             wi1->signal_value_changed().connect([this, t, pm, wi0, wi1] {
301                 if (pm->get_label() == "to") {
302                     int64_t min = t->min * 1_mm;
303                     int64_t max = wi1->get_value_as_int();
304                     int64_t newmin = min;
305                     if (max < min) {
306                         newmin = max;
307                     }
308                     footol_minmax(t, 1.0 * newmin / 1_mm, 1.0 * max / 1_mm);
309                     if (min != newmin) {
310                         wi0->set_value(newmin);
311                     }
312                 }
313                 else {
314                     int64_t nom = t->nom * 1_mm;
315                     int64_t diff = wi1->get_value_as_int();
316                     footol_pm(t, 1.0 * nom / 1_mm, 1.0 * diff / 1_mm);
317                 }
318                 calc_and_display();
319             });
320             wi0->signal_grab_focus().connect([this, p] { help(p); });
321             wi1->signal_grab_focus().connect([this, p] { help(p); });
322             pm->signal_grab_focus().connect([this, p] { help(p); });
323             grid->attach(*wi0, 2, i, 1, 1);
324             grid->attach(*pm, 3, i, 1, 1);
325             grid->attach(*wi1, 4, i, 1, 1);
326         }
327 
328         i++;
329     }
330 
331     calc_and_display();
332 }
333 
334 static const char *padstack_names[] = {
335         [FOOTAG_PADSTACK_SMD_RECT] = "smd rectangular",     [FOOTAG_PADSTACK_SMD_RRECT] = "smd rectangular rounded",
336         [FOOTAG_PADSTACK_SMD_CIRC] = "smd circular",        [FOOTAG_PADSTACK_SMD_OBLONG] = "",
337         [FOOTAG_PADSTACK_SMD_DSHAPE] = "smd half-obround",  [FOOTAG_PADSTACK_TH_ROUND] = "th circular",
338         [FOOTAG_PADSTACK_TH_ROUND_RPAD] = "th rectangular", [FOOTAG_PADSTACK_NONE] = "",
339 };
340 
341 static_assert(sizeof(padstack_names) / sizeof(padstack_names[0]) == FOOTAG_PADSTACK_NUM, "sizes dont match");
342 
getpadstack(IPool & pool,enum footag_padstack stack)343 static const Padstack *getpadstack(IPool &pool, enum footag_padstack stack)
344 {
345     const std::string ps_name = padstack_names[stack];
346     auto ps = pool.get_well_known_padstack(ps_name);
347     if (!ps) {
348         Logger::get().log_warning("Well-known padstack '" + ps_name + "' not found in pool", Logger::Domain::IMP);
349     }
350     return ps;
351 }
352 
make_rlimit_rect(Polygon & poly,const struct footag_rlimit * r,enum BoardLayers::Layer layer)353 static void make_rlimit_rect(Polygon &poly, const struct footag_rlimit *r, enum BoardLayers::Layer layer)
354 {
355     poly.vertices.clear();
356     poly.append_vertex(Coordi(r->minx * 1_mm, -r->maxy * 1_mm));
357     poly.append_vertex(Coordi(r->maxx * 1_mm, -r->maxy * 1_mm));
358     poly.append_vertex(Coordi(r->maxx * 1_mm, -r->miny * 1_mm));
359     poly.append_vertex(Coordi(r->minx * 1_mm, -r->miny * 1_mm));
360     poly.layer = layer;
361 }
362 
calc(Package & pkg,const struct footag_spec * s)363 void FootagDisplay::calc(Package &pkg, const struct footag_spec *s)
364 {
365     for (int i = 0; i < s->npads; i++) {
366         auto uu = UUID::random();
367         auto p = &s->pads[i];
368         if (p->stack == FOOTAG_PADSTACK_NONE) {
369             continue;
370         }
371         auto ps = getpadstack(core.get_pool(), p->stack);
372         if (!ps) {
373             continue;
374         }
375         auto &pad = pkg.pads.emplace(uu, Pad(uu, ps)).first->second;
376         pad.placement.shift.x = p->x * 1_mm;
377         pad.placement.shift.y = p->y * 1_mm;
378         pad.name = p->name;
379         pad.placement.set_angle(p->angle);
380         if (p->stack == FOOTAG_PADSTACK_SMD_CIRC || p->stack == FOOTAG_PADSTACK_TH_ROUND) {
381             pad.parameter_set[ParameterID::PAD_DIAMETER] = p->diam * 1_mm;
382         }
383         else {
384             pad.parameter_set[ParameterID::PAD_HEIGHT] = p->h * 1_mm;
385             pad.parameter_set[ParameterID::PAD_WIDTH] = p->w * 1_mm;
386             if (p->stack == FOOTAG_PADSTACK_SMD_RRECT) {
387                 pad.parameter_set[ParameterID::CORNER_RADIUS] = p->param * 1_mm;
388             }
389         }
390         if (p->stack == FOOTAG_PADSTACK_TH_ROUND || p->stack == FOOTAG_PADSTACK_TH_ROUND_RPAD) {
391             pad.parameter_set[ParameterID::HOLE_DIAMETER] = p->holediam * 1_mm;
392         }
393     }
394 
395     {
396         auto uu = UUID::random();
397         auto &poly = pkg.polygons.emplace(uu, uu).first->second;
398         make_rlimit_rect(poly, &s->body, BoardLayers::TOP_PACKAGE);
399     }
400 
401     {
402         auto uu = UUID::random();
403         auto &poly = pkg.polygons.emplace(uu, uu).first->second;
404         make_rlimit_rect(poly, &s->courtyard, BoardLayers::TOP_COURTYARD);
405         poly.parameter_class = "courtyard";
406     }
407 }
408 
calc_and_display(void)409 void FootagDisplay::calc_and_display(void)
410 {
411     ppkg = Package(UUID::random());
412     auto s = footag_get_spec(ctx);
413     if (!s) {
414         display();
415         return;
416     }
417 
418     calc(ppkg, s);
419 
420     display();
421     if (s->ref.doc) {
422         reference_title->set_label(std::string("as per ") + s->ref.doc + ", " + s->ref.where);
423         reference_label->set_label(s->ref.what);
424     }
425 }
426 
display(void)427 void FootagDisplay::display(void)
428 {
429     canvas_package->load(ppkg, autofit->get_active());
430     for (const auto &la : ppkg.get_layers()) {
431         auto ld = LayerDisplay::Mode::FILL_ONLY;
432         auto visible = false;
433         if (la.second.copper) {
434             visible = true;
435         }
436         if (la.first == BoardLayers::TOP_PACKAGE) {
437             visible = true;
438         }
439         if (la.first == BoardLayers::TOP_COURTYARD) {
440             visible = true;
441             ld = LayerDisplay::Mode::OUTLINE;
442         }
443         canvas_package->get_canvas().set_layer_display(la.first, LayerDisplay(visible, ld));
444     }
445 }
446 
generate(void)447 bool FootagDisplay::generate(void)
448 {
449     auto &pkg = core.get_package();
450     auto s = footag_get_spec(ctx);
451     if (!s) {
452         return false;
453     }
454 
455     calc(pkg, s);
456     return true;
457 }
458 
isopen(void)459 bool FootagDisplay::isopen(void)
460 {
461     return ctx != NULL;
462 }
463 
help(const struct footag_param * p)464 void FootagDisplay::help(const struct footag_param *p)
465 {
466 
467     std::string str;
468     str = footag_hint(ctx, p);
469     if (!str.size()) {
470         str = "<big>" + std::string(p->name) + "</big>";
471     }
472     trim(str);
473     hint_label->set_markup(str);
474 }
475 
~FootagDisplay()476 FootagDisplay::~FootagDisplay()
477 {
478     if (ctx) {
479         footag_close(ctx);
480     }
481 }
482 } // namespace horizon
483