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