1 #include "imp_board.hpp"
2 #include "3d/3d_view.hpp"
3 #include "canvas/canvas_gl.hpp"
4 #include "canvas/canvas_pads.hpp"
5 #include "fab_output_window.hpp"
6 #include "step_export_window.hpp"
7 #include "pool/part.hpp"
8 #include "rules/rules_window.hpp"
9 #include "widgets/board_display_options.hpp"
10 #include "widgets/layer_box.hpp"
11 #include "tuning_window.hpp"
12 #include "util/selection_util.hpp"
13 #include "util/util.hpp"
14 #include "util/geom_util.hpp"
15 #include "util/str_util.hpp"
16 #include "canvas/annotation.hpp"
17 #include "export_pdf/export_pdf_board.hpp"
18 #include "board/board_layers.hpp"
19 #include "pdf_export_window.hpp"
20 #include "widgets/unplaced_box.hpp"
21 #include "pnp_export_window.hpp"
22 #include "airwire_filter_window.hpp"
23 #include "core/tool_id.hpp"
24 #include "widgets/action_button.hpp"
25 #include "parts_window.hpp"
26 #include "actions.hpp"
27 #include "in_tool_action.hpp"
28 
29 namespace horizon {
ImpBoard(const std::string & board_filename,const std::string & block_filename,const std::string & pictures_dir,const PoolParams & pool_params)30 ImpBoard::ImpBoard(const std::string &board_filename, const std::string &block_filename,
31                    const std::string &pictures_dir, const PoolParams &pool_params)
32     : ImpLayer(pool_params), core_board(board_filename, block_filename, pictures_dir, *pool, *pool_caching),
33       project_dir(Glib::path_get_dirname(board_filename)), searcher(core_board)
34 {
35     core = &core_board;
36     core_board.signal_tool_changed().connect(sigc::mem_fun(*this, &ImpBase::handle_tool_change));
37     load_meta();
38 }
39 
canvas_update()40 void ImpBoard::canvas_update()
41 {
42     canvas->update(core_board.get_canvas_data());
43     warnings_box->update(core_board.get_board()->warnings);
44     apply_net_colors();
45     update_highlights();
46     tuning_window->update();
47     update_text_owner_annotation();
48 }
49 
update_airwire_annotation()50 void ImpBoard::update_airwire_annotation()
51 {
52     airwire_annotation->clear();
53     for (const auto &[net, airwires] : core_board.get_board()->airwires) {
54         if (!airwire_filter_window->airwire_is_visible(net))
55             continue;
56         const bool highlight = highlights.count(ObjectRef(ObjectType::NET, net));
57         uint8_t color2 = 0;
58         if (net_color_map.count(net))
59             color2 = net_color_map.at(net);
60         if (core_board.tool_is_active() && !highlight && highlights.size())
61             continue;
62         for (const auto &airwire : airwires) {
63             airwire_annotation->draw_line(airwire.from.get_position(), airwire.to.get_position(), ColorP::AIRWIRE, 0,
64                                           highlight, color2);
65         }
66     }
67 }
68 
update_highlights()69 void ImpBoard::update_highlights()
70 {
71     canvas->set_flags_all(0, TriangleInfo::FLAG_HIGHLIGHT);
72     canvas->set_highlight_enabled(highlights.size());
73     for (const auto &it : highlights) {
74         if (it.type == ObjectType::NET) {
75             for (const auto &it_track : core_board.get_board()->tracks) {
76                 if (it_track.second.net.uuid == it.uuid) {
77                     ObjectRef ref(ObjectType::TRACK, it_track.first);
78                     canvas->set_flags(ref, TriangleInfo::FLAG_HIGHLIGHT, 0);
79                 }
80             }
81             for (const auto &it_via : core_board.get_board()->vias) {
82                 if (it_via.second.junction->net.uuid == it.uuid) {
83                     ObjectRef ref(ObjectType::VIA, it_via.first);
84                     canvas->set_flags(ref, TriangleInfo::FLAG_HIGHLIGHT, 0);
85                 }
86             }
87             for (const auto &it_pkg : core_board.get_board()->packages) {
88                 for (const auto &it_pad : it_pkg.second.package.pads) {
89                     if (it_pad.second.net.uuid == it.uuid) {
90                         ObjectRef ref(ObjectType::PAD, it_pad.first, it_pkg.first);
91                         canvas->set_flags(ref, TriangleInfo::FLAG_HIGHLIGHT, 0);
92                     }
93                 }
94             }
95         }
96         if (it.type == ObjectType::COMPONENT) {
97             for (const auto &it_pkg : core_board.get_board()->packages) {
98                 if (it_pkg.second.component->uuid == it.uuid) {
99                     {
100                         ObjectRef ref(ObjectType::BOARD_PACKAGE, it_pkg.first);
101                         canvas->set_flags(ref, TriangleInfo::FLAG_HIGHLIGHT, 0);
102                     }
103                     for (const auto text : it_pkg.second.texts) {
104                         ObjectRef ref(ObjectType::TEXT, text->uuid);
105                         canvas->set_flags(ref, TriangleInfo::FLAG_HIGHLIGHT, 0);
106                     }
107                 }
108             }
109         }
110         else {
111             canvas->set_flags(it, TriangleInfo::FLAG_HIGHLIGHT, 0);
112         }
113     }
114     update_airwire_annotation();
115 }
116 
apply_net_colors()117 void ImpBoard::apply_net_colors()
118 {
119     canvas->reset_color2();
120     for (const auto &[net, color] : net_color_map) {
121         for (const auto &it_track : core_board.get_board()->tracks) {
122             if (it_track.second.net.uuid == net) {
123                 ObjectRef ref(ObjectType::TRACK, it_track.first);
124                 canvas->set_color2(ref, color);
125             }
126         }
127         for (const auto &it_via : core_board.get_board()->vias) {
128             if (it_via.second.junction->net.uuid == net) {
129                 ObjectRef ref(ObjectType::VIA, it_via.first);
130                 canvas->set_color2(ref, color);
131             }
132         }
133         for (const auto &it_pkg : core_board.get_board()->packages) {
134             for (const auto &it_pad : it_pkg.second.package.pads) {
135                 if (it_pad.second.net.uuid == net) {
136                     ObjectRef ref(ObjectType::PAD, it_pad.first, it_pkg.first);
137                     canvas->set_color2(ref, color);
138                 }
139             }
140         }
141         for (const auto &it_plane : core_board.get_board()->planes) {
142             if (it_plane.second.net.uuid == net) {
143                 ObjectRef ref(ObjectType::PLANE, it_plane.first);
144                 canvas->set_color2(ref, color);
145             }
146         }
147     }
148 }
149 
handle_broadcast(const json & j)150 bool ImpBoard::handle_broadcast(const json &j)
151 {
152     if (!ImpBase::handle_broadcast(j)) {
153         std::string op = j.at("op");
154         guint32 timestamp = j.value("time", 0);
155         if (op == "highlight" && cross_probing_enabled && !core->tool_is_active()) {
156             highlights.clear();
157             const json &o = j["objects"];
158             for (auto it = o.cbegin(); it != o.cend(); ++it) {
159                 auto type = static_cast<ObjectType>(it.value().at("type").get<int>());
160                 UUID uu(it.value().at("uuid").get<std::string>());
161                 highlights.emplace(type, uu);
162             }
163             update_highlights();
164         }
165         else if (op == "place") {
166             force_end_tool();
167             main_window->present(timestamp);
168             std::set<SelectableRef> components;
169             const json &o = j["components"];
170             for (auto it = o.cbegin(); it != o.cend(); ++it) {
171                 auto type = static_cast<ObjectType>(it.value().at("type").get<int>());
172                 if (type == ObjectType::COMPONENT) {
173                     UUID uu(it.value().at("uuid").get<std::string>());
174                     components.emplace(uu, type);
175                 }
176             }
177             tool_begin(ToolID::MAP_PACKAGE, true, components);
178         }
179         else if (op == "reload-netlist") {
180             force_end_tool();
181             main_window->present(timestamp);
182             trigger_action(ActionID::RELOAD_NETLIST);
183         }
184         else if (op == "reload-netlist-hint" && !core->tool_is_active()) {
185             reload_netlist_delay_conn = Glib::signal_timeout().connect(
186                     [this] {
187 #if GTK_CHECK_VERSION(3, 22, 0)
188                         reload_netlist_popover->popup();
189 #else
190                         reload_netlist_popover->show();
191 #endif
192                         return false;
193                     },
194                     500);
195         }
196     }
197     return true;
198 }
199 
handle_selection_cross_probe()200 void ImpBoard::handle_selection_cross_probe()
201 {
202     json j;
203     j["op"] = "board-select";
204     j["selection"] = nullptr;
205     std::set<UUID> pkgs;
206     for (const auto &it : canvas->get_selection()) {
207         json k;
208         ObjectType type = ObjectType::INVALID;
209         UUID uu;
210         auto board = core_board.get_board();
211         switch (it.type) {
212         case ObjectType::TRACK: {
213             auto &track = board->tracks.at(it.uuid);
214             if (track.net) {
215                 type = ObjectType::NET;
216                 uu = track.net->uuid;
217             }
218         } break;
219         case ObjectType::VIA: {
220             auto &via = board->vias.at(it.uuid);
221             if (via.junction->net) {
222                 type = ObjectType::NET;
223                 uu = via.junction->net->uuid;
224             }
225         } break;
226         case ObjectType::JUNCTION: {
227             auto &ju = board->junctions.at(it.uuid);
228             if (ju.net) {
229                 type = ObjectType::NET;
230                 uu = ju.net->uuid;
231             }
232         } break;
233         case ObjectType::BOARD_PACKAGE: {
234             auto &pkg = board->packages.at(it.uuid);
235             type = ObjectType::COMPONENT;
236             uu = pkg.component->uuid;
237             pkgs.insert(pkg.uuid);
238         } break;
239         default:;
240         }
241 
242         if (type != ObjectType::INVALID) {
243             k["type"] = static_cast<int>(type);
244             k["uuid"] = (std::string)uu;
245             j["selection"].push_back(k);
246         }
247     }
248     view_3d_window->set_highlights(pkgs);
249     send_json(j);
250 }
251 
transform_path(ClipperLib::Path & path,const Placement & tr)252 void transform_path(ClipperLib::Path &path, const Placement &tr)
253 {
254     for (auto &it : path) {
255         Coordi p(it.X, it.Y);
256         auto q = tr.transform(p);
257         it.X = q.x;
258         it.Y = q.y;
259     }
260 }
261 
update_action_sensitivity()262 void ImpBoard::update_action_sensitivity()
263 {
264     auto sel = canvas->get_selection();
265     bool have_tracks = std::any_of(sel.begin(), sel.end(), [](const auto &x) { return x.type == ObjectType::TRACK; });
266     int n_pkgs =
267             std::count_if(sel.begin(), sel.end(), [](const auto &x) { return x.type == ObjectType::BOARD_PACKAGE; });
268     set_action_sensitive(make_action(ActionID::TUNING_ADD_TRACKS), have_tracks);
269     set_action_sensitive(make_action(ActionID::TUNING_ADD_TRACKS_ALL), have_tracks);
270 
271     bool can_select_more = std::any_of(sel.begin(), sel.end(), [](const auto &x) {
272         switch (x.type) {
273         case ObjectType::TRACK:
274         case ObjectType::JUNCTION:
275         case ObjectType::VIA:
276             return true;
277 
278         default:
279             return false;
280         }
281     });
282 
283     set_action_sensitive(make_action(ActionID::HIGHLIGHT_NET), can_select_more);
284     set_action_sensitive(make_action(ActionID::HIGHLIGHT_NET_CLASS), can_select_more);
285     set_action_sensitive(make_action(ActionID::SELECT_MORE), can_select_more);
286     set_action_sensitive(make_action(ActionID::SELECT_MORE_NO_VIA), can_select_more);
287     set_action_sensitive(make_action(ActionID::FILTER_AIRWIRES), can_select_more || n_pkgs);
288 
289     set_action_sensitive(make_action(ActionID::GO_TO_SCHEMATIC), sockets_connected);
290     set_action_sensitive(make_action(ActionID::SHOW_IN_POOL_MANAGER), n_pkgs == 1 && sockets_connected);
291 
292     ImpBase::update_action_sensitivity();
293 }
294 
apply_preferences()295 void ImpBoard::apply_preferences()
296 {
297     if (view_3d_window) {
298         view_3d_window->apply_preferences(preferences);
299     }
300     canvas->set_highlight_on_top(preferences.board.highlight_on_top);
301     canvas->show_text_in_tracks = preferences.board.show_text_in_tracks;
302     canvas->show_text_in_vias = preferences.board.show_text_in_vias;
303     ImpLayer::apply_preferences();
304     canvas_update_from_pp();
305 }
306 
rgba_from_color(const Color & c)307 static Gdk::RGBA rgba_from_color(const Color &c)
308 {
309     Gdk::RGBA r;
310     r.set_rgba(c.r, c.g, c.b);
311     return r;
312 }
313 
color_from_rgba(const Gdk::RGBA & r)314 static Color color_from_rgba(const Gdk::RGBA &r)
315 {
316     return {r.get_red(), r.get_green(), r.get_blue()};
317 }
318 
get_schematic_pid()319 int ImpBoard::get_schematic_pid()
320 {
321     json j;
322     j["op"] = "get-schematic-pid";
323     return this->send_json(j);
324 }
325 
serialize_connector(const Track::Connection & conn)326 static json serialize_connector(const Track::Connection &conn)
327 {
328     if (conn.is_pad() && conn.pad->net == nullptr) {
329         const auto comp = conn.package->component;
330         auto pm = comp->part->pad_map.at(conn.pad->uuid);
331         UUIDPath<2> path(pm.gate->uuid, pm.pin->uuid);
332         json j;
333         j["component"] = (std::string)comp->uuid;
334         j["path"] = (std::string)path;
335         return j;
336     }
337     else if (conn.is_pad() && conn.pad->net) {
338         json j;
339         j["net"] = (std::string)conn.pad->net->uuid;
340         return j;
341     }
342     else if (conn.is_junc() && conn.junc->net) {
343         json j;
344         j["net"] = (std::string)conn.junc->net->uuid;
345         return j;
346     }
347     else {
348         return nullptr;
349     }
350 }
351 
handle_select_more(const ActionConnection & conn)352 void ImpBoard::handle_select_more(const ActionConnection &conn)
353 {
354     const bool no_via = conn.action_id == ActionID::SELECT_MORE_NO_VIA;
355     std::map<const Junction *, std::set<const Track *>> junction_connections;
356     const auto brd = core_board.get_board();
357     for (const auto &it : brd->tracks) {
358         for (const auto &it_ft : {it.second.from, it.second.to}) {
359             if (it_ft.is_junc()) {
360                 junction_connections[it_ft.junc].insert(&it.second);
361             }
362         }
363     }
364     std::set<const Junction *> junctions;
365     std::set<const Track *> tracks;
366 
367     auto sel = canvas->get_selection();
368 
369     for (const auto &it : sel) {
370         if (it.type == ObjectType::TRACK) {
371             tracks.insert(&brd->tracks.at(it.uuid));
372         }
373         else if (it.type == ObjectType::JUNCTION) {
374             junctions.insert(&brd->junctions.at(it.uuid));
375         }
376         else if (it.type == ObjectType::VIA) {
377             junctions.insert(brd->vias.at(it.uuid).junction);
378         }
379     }
380     bool inserted = true;
381     while (inserted) {
382         inserted = false;
383         for (const auto it : tracks) {
384             for (const auto &it_ft : {it->from, it->to}) {
385                 if (it_ft.is_junc()) {
386                     if (!no_via || (no_via && !it_ft.junc->has_via))
387                         if (junctions.insert(it_ft.junc).second)
388                             inserted = true;
389                 }
390             }
391         }
392         for (const auto it : junctions) {
393             if (junction_connections.count(it)) {
394                 for (const auto &it_track : junction_connections.at(it)) {
395                     if (tracks.insert(it_track).second)
396                         inserted = true;
397                 }
398             }
399         }
400     }
401 
402     std::set<SelectableRef> new_sel;
403 
404     for (const auto it : junctions) {
405         new_sel.emplace(it->uuid, ObjectType::JUNCTION);
406     }
407     for (const auto it : tracks) {
408         new_sel.emplace(it->uuid, ObjectType::TRACK);
409     }
410     canvas->set_selection(new_sel);
411     canvas->set_selection_mode(CanvasGL::SelectionMode::NORMAL);
412 }
413 
construct()414 void ImpBoard::construct()
415 {
416     ImpLayer::construct_layer_box(false);
417 
418     state_store = std::make_unique<WindowStateStore>(main_window, "imp-board");
419 
420     auto view_3d_button = Gtk::manage(new Gtk::Button("3D"));
421     main_window->header->pack_start(*view_3d_button);
422     view_3d_button->show();
423     view_3d_button->signal_clicked().connect([this] {
424         view_3d_window->update();
425         view_3d_window->present();
426     });
427 
428     hamburger_menu->append("Fabrication output", "win.fab_out");
429     main_window->add_action("fab_out", [this] { trigger_action(ActionID::FAB_OUTPUT_WINDOW); });
430 
431     hamburger_menu->append("PDF Export", "win.export_pdf");
432     main_window->add_action("export_pdf", [this] { trigger_action(ActionID::PDF_EXPORT_WINDOW); });
433 
434     hamburger_menu->append("Stackup…", "win.edit_stackup");
435     add_tool_action(ToolID::EDIT_STACKUP, "edit_stackup");
436 
437     hamburger_menu->append("Update all planes", "win.update_all_planes");
438     add_tool_action(ToolID::UPDATE_ALL_PLANES, "update_all_planes");
439 
440     hamburger_menu->append("Clear all planes", "win.clear_all_planes");
441     add_tool_action(ToolID::CLEAR_ALL_PLANES, "clear_all_planes");
442 
443     hamburger_menu->append("Import DXF", "win.import_dxf");
444     add_tool_action(ToolID::IMPORT_DXF, "import_dxf");
445 
446     hamburger_menu->append("Export STEP", "win.export_step");
447     main_window->add_action("export_step", [this] { trigger_action(ActionID::STEP_EXPORT_WINDOW); });
448 
449     hamburger_menu->append("Export Pick & place", "win.export_pnp");
450     main_window->add_action("export_pnp", [this] { trigger_action(ActionID::PNP_EXPORT_WINDOW); });
451 
452     hamburger_menu->append("Length tuning", "win.tuning");
453     main_window->add_action("tuning", [this] { trigger_action(ActionID::TUNING); });
454 
455     add_tool_action(ActionID::AIRWIRE_FILTER_WINDOW, "airwire_filter");
456     view_options_menu_append_action("Nets…", "win.airwire_filter");
457 
458     view_options_menu_append_action("Bottom view", "win.bottom_view");
459     add_view_angle_actions();
460 
461     if (sockets_connected) {
462         hamburger_menu->append("Cross probing", "win.cross_probing");
463         auto cp_action = main_window->add_action_bool("cross_probing", true);
464         cross_probing_enabled = true;
465         cp_action->signal_change_state().connect([this, cp_action](const Glib::VariantBase &v) {
466             cross_probing_enabled = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(v).get();
467             g_simple_action_set_state(cp_action->gobj(), g_variant_new_boolean(cross_probing_enabled));
468             if (!cross_probing_enabled && !core_board.tool_is_active()) {
469                 highlights.clear();
470                 update_highlights();
471             }
472         });
473 
474         connect_action(ActionID::GO_TO_SCHEMATIC, [this](const auto &conn) {
475             json j;
476             j["time"] = gtk_get_current_event_time();
477             j["op"] = "present-schematic";
478             auto sch_pid = this->get_schematic_pid();
479             if (sch_pid != -1)
480                 allow_set_foreground_window(sch_pid);
481             this->send_json(j);
482         });
483 
484         connect_action(ActionID::SHOW_IN_POOL_MANAGER, [this](const auto &conn) {
485             for (const auto &it : canvas->get_selection()) {
486                 auto board = core_board.get_board();
487                 if (it.type == ObjectType::BOARD_PACKAGE) {
488                     const auto &pkg = board->packages.at(it.uuid);
489                     if (pkg.component->part) {
490                         json j;
491                         j["op"] = "show-in-pool-mgr";
492                         j["type"] = "part";
493                         UUID pool_uuid;
494                         pool->get_filename(ObjectType::PART, pkg.component->part->uuid, &pool_uuid);
495                         j["pool_uuid"] = (std::string)pool_uuid;
496                         j["uuid"] = (std::string)pkg.component->part->uuid;
497                         this->send_json(j);
498                         break;
499                     }
500                 }
501             }
502         });
503         set_action_sensitive(make_action(ActionID::SHOW_IN_POOL_MANAGER), false);
504 
505         connect_action(ActionID::BACKANNOTATE_CONNECTION_LINES, [this](const auto &conn) {
506             json j;
507             j["time"] = gtk_get_current_event_time();
508             j["op"] = "backannotate";
509             allow_set_foreground_window(this->get_schematic_pid());
510             json a;
511             for (const auto &it : core_board.get_board()->connection_lines) {
512                 json x;
513                 x["from"] = serialize_connector(it.second.from);
514                 x["to"] = serialize_connector(it.second.to);
515                 if (!x["from"].is_null() && !x["to"].is_null())
516                     a.push_back(x);
517             }
518             j["connections"] = a;
519             allow_set_foreground_window(this->get_schematic_pid());
520             this->send_json(j);
521         });
522     }
523 
524     connect_action(ActionID::RELOAD_NETLIST, [this](const ActionConnection &c) {
525         reload_netlist_delay_conn.disconnect();
526 #if GTK_CHECK_VERSION(3, 22, 0)
527         reload_netlist_popover->popdown();
528 #else
529         reload_netlist_popover->hide();
530 #endif
531         core_board.reload_netlist();
532         core_board.set_needs_save();
533         canvas_update();
534         airwire_filter_window->update_nets();
535         parts_window->update();
536     });
537 
538     {
539         auto button = create_action_button(make_action(ActionID::RELOAD_NETLIST));
540         button->show();
541         main_window->header->pack_end(*button);
542 
543         reload_netlist_popover = Gtk::manage(new Gtk::Popover);
544         reload_netlist_popover->set_modal(false);
545         reload_netlist_popover->set_relative_to(*button);
546 
547         auto la = Gtk::manage(
548                 new Gtk::Label("Netlist has changed.\nReload the netlist to update the board to the latest netlist."));
549         la->show();
550         reload_netlist_popover->add(*la);
551         la->set_line_wrap(true);
552         la->set_max_width_chars(20);
553         la->property_margin() = 10;
554     }
555 
556     fab_output_window = FabOutputWindow::create(main_window, core_board, project_dir);
557     core->signal_tool_changed().connect([this](ToolID t) { fab_output_window->set_can_generate(t == ToolID::NONE); });
558     core->signal_rebuilt().connect([this] { fab_output_window->reload_layers(); });
559     fab_output_window->signal_changed().connect([this] { core_board.set_needs_save(); });
560     connect_action(ActionID::FAB_OUTPUT_WINDOW, [this](const auto &c) { fab_output_window->present(); });
561     connect_action(ActionID::GEN_FAB_OUTPUT, [this](const auto &c) {
562         fab_output_window->present();
563         fab_output_window->generate();
564     });
565 
566     pdf_export_window =
567             PDFExportWindow::create(main_window, core_board, core_board.get_pdf_export_settings(), project_dir);
568     pdf_export_window->signal_changed().connect([this] { core_board.set_needs_save(); });
569     core->signal_rebuilt().connect([this] { pdf_export_window->reload_layers(); });
570     connect_action(ActionID::PDF_EXPORT_WINDOW, [this](const auto &c) { pdf_export_window->present(); });
571     connect_action(ActionID::EXPORT_PDF, [this](const auto &c) {
572         pdf_export_window->present();
573         pdf_export_window->generate();
574     });
575 
576     view_3d_window = View3DWindow::create(*core_board.get_board(), *pool_caching, View3DWindow::Mode::BOARD);
577     view_3d_window->set_solder_mask_color(rgba_from_color(core_board.get_colors().solder_mask));
578     view_3d_window->set_silkscreen_color(rgba_from_color(core_board.get_colors().silkscreen));
579     view_3d_window->set_substrate_color(rgba_from_color(core_board.get_colors().substrate));
580     view_3d_window->signal_changed().connect([this] {
581         core_board.get_colors().solder_mask = color_from_rgba(view_3d_window->get_solder_mask_color());
582         core_board.get_colors().silkscreen = color_from_rgba(view_3d_window->get_silkscreen_color());
583         core_board.get_colors().substrate = color_from_rgba(view_3d_window->get_substrate_color());
584         core_board.set_needs_save();
585     });
586     view_3d_window->signal_present_imp().connect([this] { main_window->present(); });
587     view_3d_window->signal_package_select().connect([this](const auto &uu) {
588         if (!core->tool_is_active() && canvas->get_selection_mode() == CanvasGL::SelectionMode::NORMAL) {
589             if (uu)
590                 canvas->set_selection({{uu, ObjectType::BOARD_PACKAGE}});
591             else
592                 canvas->set_selection({});
593         }
594     });
595 
596     step_export_window = StepExportWindow::create(main_window, core_board, project_dir);
597     step_export_window->signal_changed().connect([this] { core_board.set_needs_save(); });
598     connect_action(ActionID::STEP_EXPORT_WINDOW, [this](const auto &c) { step_export_window->present(); });
599     connect_action(ActionID::EXPORT_STEP, [this](const auto &c) {
600         step_export_window->present();
601         step_export_window->generate();
602     });
603 
604     tuning_window = new TuningWindow(*core_board.get_board());
605     tuning_window->set_transient_for(*main_window);
606     imp_interface->signal_request_length_tuning_ref().connect([this] { return tuning_window->get_ref_length(); });
607 
608     rules_window->signal_goto().connect([this](Coordi location, UUID sheet) { canvas->center_and_zoom(location); });
609 
610     connect_action(ActionID::VIEW_3D, [this](const auto &a) {
611         view_3d_window->update();
612         view_3d_window->present();
613     });
614 
615     connect_action(ActionID::TUNING, [this](const auto &a) { tuning_window->present(); });
616     connect_action(ActionID::TUNING_ADD_TRACKS, sigc::mem_fun(*this, &ImpBoard::handle_measure_tracks));
617     connect_action(ActionID::TUNING_ADD_TRACKS_ALL, sigc::mem_fun(*this, &ImpBoard::handle_measure_tracks));
618 
619     parts_window = new PartsWindow(*core_board.get_board());
620     parts_window->set_transient_for(*main_window);
621     connect_action(ActionID::PARTS_WINDOW, [this](const auto &a) { parts_window->present(); });
622     parts_window->update();
623     parts_window->signal_selected().connect([this](const auto &comps) {
624         highlights.clear();
625         std::set<UUID> pkgs;
626         for (const auto &[uu, pkg] : core_board.get_board()->packages) {
627             if (comps.count(pkg.component->uuid)) {
628                 highlights.emplace(ObjectType::BOARD_PACKAGE, uu);
629                 pkgs.insert(uu);
630             }
631         }
632         update_highlights();
633         view_3d_window->set_highlights(pkgs);
634     });
635     if (m_meta.count("parts"))
636         parts_window->load_from_json(m_meta.at("parts"));
637 
638     connect_action(ActionID::HIGHLIGHT_NET, [this](const auto &a) {
639         highlights.clear();
640         for (const auto &it : canvas->get_selection()) {
641             if (auto uu = net_from_selectable(it)) {
642                 highlights.emplace(ObjectType::NET, uu);
643             }
644         }
645         this->update_highlights();
646     });
647 
648     connect_action(ActionID::HIGHLIGHT_NET_CLASS, [this](const auto &a) {
649         highlights.clear();
650         for (const auto &it : canvas->get_selection()) {
651             if (auto uu = net_from_selectable(it)) {
652                 const auto &net_sel = core_board.get_block()->nets.at(uu);
653                 for (const auto &[net_uu, net] : core_board.get_block()->nets) {
654                     if (net.net_class == net_sel.net_class)
655                         highlights.emplace(ObjectType::NET, net_uu);
656                 }
657             }
658         }
659         this->update_highlights();
660     });
661 
662     connect_action(ActionID::SELECT_MORE, sigc::mem_fun(*this, &ImpBoard::handle_select_more));
663     connect_action(ActionID::SELECT_MORE_NO_VIA, sigc::mem_fun(*this, &ImpBoard::handle_select_more));
664 
665     auto *display_control_notebook = Gtk::manage(new Gtk::Notebook);
666     display_control_notebook->append_page(*layer_box, "Layers");
667     layer_box->show();
668     layer_box->get_style_context()->add_class("background");
669 
670     board_display_options_box = Gtk::manage(new BoardDisplayOptionsBox(core_board.get_layer_provider()));
671     {
672         auto fbox = Gtk::manage(new Gtk::Box());
673         fbox->pack_start(*board_display_options_box, true, true, 0);
674         fbox->get_style_context()->add_class("background");
675         fbox->show();
676         display_control_notebook->append_page(*fbox, "Options");
677         board_display_options_box->show();
678     }
679 
680     board_display_options_box->signal_set_layer_display().connect([this](int index, const LayerDisplay &lda) {
681         LayerDisplay ld = canvas->get_layer_display(index);
682         ld.types_visible = lda.types_visible;
683         canvas->set_layer_display(index, ld);
684     });
685     canvas->set_layer_display(10000, LayerDisplay(true, LayerDisplay::Mode::OUTLINE));
686     core->signal_rebuilt().connect([this] { board_display_options_box->update(); });
687     if (m_meta.count("board_display_options"))
688         board_display_options_box->load_from_json(m_meta.at("board_display_options"));
689 
690     canvas->signal_motion_notify_event().connect([this](GdkEventMotion *ev) {
691         if (target_drag_begin.type != ObjectType::INVALID) {
692             handle_drag();
693         }
694         return false;
695     });
696 
697     canvas->signal_button_release_event().connect([this](GdkEventButton *ev) {
698         target_drag_begin = Target();
699         return false;
700     });
701 
702     text_owner_annotation = canvas->create_annotation();
703     text_owner_annotation->set_visible(true);
704     text_owner_annotation->set_display(LayerDisplay(true, LayerDisplay::Mode::OUTLINE));
705 
706     airwire_annotation = canvas->create_annotation();
707     airwire_annotation->set_visible(true);
708     airwire_annotation->set_display(LayerDisplay(true, LayerDisplay::Mode::OUTLINE));
709     airwire_annotation->use_highlight = true;
710 
711     core_board.signal_rebuilt().connect(sigc::mem_fun(*this, &ImpBoard::update_text_owners));
712     canvas->signal_hover_selection_changed().connect(sigc::mem_fun(*this, &ImpBoard::update_text_owner_annotation));
713     canvas->signal_selection_changed().connect(sigc::mem_fun(*this, &ImpBoard::update_text_owner_annotation));
714     update_text_owners();
715 
716     core_board.signal_rebuilt().connect([this] { selection_filter_dialog->update_layers(); });
717 
718     main_window->left_panel->pack_start(*display_control_notebook, false, false);
719 
720     unplaced_box = Gtk::manage(new UnplacedBox("Package"));
721     unplaced_box->show();
722     main_window->left_panel->pack_end(*unplaced_box, true, true, 0);
723     unplaced_box->signal_place().connect([this](const auto &items) {
724         std::set<SelectableRef> components;
725         for (const auto &it : items) {
726             components.emplace(it.at(0), ObjectType::COMPONENT);
727         }
728         this->tool_begin(ToolID::MAP_PACKAGE, true, components);
729     });
730     core_board.signal_rebuilt().connect(sigc::mem_fun(*this, &ImpBoard::update_unplaced));
731     update_unplaced();
732     core_board.signal_tool_changed().connect(
733             [this](ToolID tool_id) { unplaced_box->set_sensitive(tool_id == ToolID::NONE); });
734 
735 
736     pnp_export_window = PnPExportWindow::create(main_window, *core_board.get_board(),
737                                                 core_board.get_pnp_export_settings(), project_dir);
738 
739     connect_action(ActionID::PNP_EXPORT_WINDOW, [this](const auto &c) {
740         pnp_export_window->update();
741         pnp_export_window->present();
742     });
743     connect_action(ActionID::EXPORT_PNP, [this](const auto &c) {
744         pnp_export_window->update();
745         pnp_export_window->present();
746         pnp_export_window->generate();
747     });
748 
749     pnp_export_window->signal_changed().connect([this] { core_board.set_needs_save(); });
750     core->signal_tool_changed().connect([this](ToolID t) { pnp_export_window->set_can_export(t == ToolID::NONE); });
751     core->signal_rebuilt().connect([this] {
752         if (pnp_export_window->get_visible())
753             pnp_export_window->update();
754     });
755 
756     airwire_filter_window = AirwireFilterWindow::create(main_window, *core_board.get_board());
757     airwire_filter_window->update_nets();
758     airwire_filter_window->signal_changed().connect([this] {
759         update_net_colors();
760         apply_net_colors();
761         update_airwire_annotation();
762         update_view_hints();
763     });
764     airwire_filter_window->signal_selection_changed().connect([this](auto nets) {
765         highlights.clear();
766         for (const auto &net : nets) {
767             highlights.emplace(ObjectType::NET, net);
768         }
769         update_highlights();
770     });
771     connect_action(ActionID::AIRWIRE_FILTER_WINDOW, [this](const auto &a) { airwire_filter_window->present(); });
772     core_board.signal_rebuilt().connect(sigc::mem_fun(*this, &ImpBoard::update_airwires));
773     connect_action(ActionID::RESET_AIRWIRE_FILTER, [this](const auto &a) { airwire_filter_window->set_all(true); });
774     connect_action(ActionID::FILTER_AIRWIRES, [this](const auto &a) {
775         std::set<UUID> nets;
776         const auto board = core_board.get_board();
777         for (const auto &it : canvas->get_selection()) {
778             switch (it.type) {
779             case ObjectType::TRACK: {
780                 auto &track = board->tracks.at(it.uuid);
781                 if (track.net) {
782                     nets.emplace(track.net->uuid);
783                 }
784             } break;
785             case ObjectType::VIA: {
786                 auto &via = board->vias.at(it.uuid);
787                 if (via.junction->net) {
788                     nets.emplace(via.junction->net->uuid);
789                 }
790             } break;
791             case ObjectType::JUNCTION: {
792                 auto &ju = board->junctions.at(it.uuid);
793                 if (ju.net) {
794                     nets.emplace(ju.net->uuid);
795                 }
796             } break;
797             case ObjectType::BOARD_PACKAGE: {
798                 auto &pkg = board->packages.at(it.uuid);
799                 for (const auto &it_pad : pkg.package.pads) {
800                     if (it_pad.second.net) {
801                         nets.emplace(it_pad.second.net->uuid);
802                     }
803                 }
804             } break;
805             default:;
806             }
807         }
808 
809         airwire_filter_window->set_only(nets);
810     });
811 
812     if (m_meta.count("nets"))
813         airwire_filter_window->load_from_json(m_meta.at("nets"));
814 
815     {
816         auto &b = add_action_button(make_action(ToolID::ROUTE_TRACK_INTERACTIVE));
817         b.add_action(make_action(ToolID::ROUTE_DIFFPAIR_INTERACTIVE));
818     }
819     add_action_button_polygon();
820     {
821         auto &b = add_action_button(make_action(ToolID::PLACE_BOARD_HOLE));
822         b.add_action(make_action(ToolID::PLACE_VIA));
823     }
824     add_action_button_line();
825 
826     add_action_button(make_action(ToolID::PLACE_TEXT));
827     add_action_button(make_action(ToolID::DRAW_DIMENSION));
828 
829     update_monitor();
830 
831     display_control_notebook->show();
832 
833     set_view_angle(0);
834 }
835 
net_from_selectable(const SelectableRef & sr)836 UUID ImpBoard::net_from_selectable(const SelectableRef &sr)
837 {
838     const auto &board = *core_board.get_board();
839     switch (sr.type) {
840     case ObjectType::TRACK: {
841         auto &track = board.tracks.at(sr.uuid);
842         if (track.net) {
843             return track.net->uuid;
844         }
845     } break;
846     case ObjectType::VIA: {
847         auto &via = board.vias.at(sr.uuid);
848         if (via.junction->net) {
849             return via.junction->net->uuid;
850         }
851     } break;
852     case ObjectType::JUNCTION: {
853         auto &ju = board.junctions.at(sr.uuid);
854         if (ju.net) {
855             return ju.net->uuid;
856         }
857     } break;
858     default:;
859     }
860     return UUID();
861 }
862 
update_airwires()863 void ImpBoard::update_airwires()
864 {
865     airwire_filter_window->update_from_board();
866 }
867 
update_text_owners()868 void ImpBoard::update_text_owners()
869 {
870     auto brd = core_board.get_board();
871     for (const auto &it_pkg : brd->packages) {
872         for (const auto &itt : it_pkg.second.texts) {
873             text_owners[itt->uuid] = it_pkg.first;
874         }
875     }
876     update_text_owner_annotation();
877 }
878 
update_text_owner_annotation()879 void ImpBoard::update_text_owner_annotation()
880 {
881     text_owner_annotation->clear();
882     auto sel = canvas->get_selection();
883     const auto brd = core_board.get_board();
884     for (const auto &it : sel) {
885         if (it.type == ObjectType::TEXT) {
886             if (brd->texts.count(it.uuid)) {
887                 const auto &text = brd->texts.at(it.uuid);
888                 if (text_owners.count(text.uuid))
889                     text_owner_annotation->draw_line(text.placement.shift,
890                                                      brd->packages.at(text_owners.at(text.uuid)).placement.shift,
891                                                      ColorP::FROM_LAYER, 0);
892             }
893         }
894         else if (it.type == ObjectType::BOARD_PACKAGE) {
895             if (brd->packages.count(it.uuid)) {
896                 const auto &pkg = brd->packages.at(it.uuid);
897                 for (const auto &text : pkg.texts) {
898                     if (canvas->layer_is_visible(text->layer))
899                         text_owner_annotation->draw_line(text->placement.shift, pkg.placement.shift, ColorP::FROM_LAYER,
900                                                          0);
901                 }
902             }
903         }
904     }
905 }
906 
get_hud_text(std::set<SelectableRef> & sel)907 std::string ImpBoard::get_hud_text(std::set<SelectableRef> &sel)
908 {
909     std::string s;
910     if (sel_count_type(sel, ObjectType::TRACK)) {
911         auto n = sel_count_type(sel, ObjectType::TRACK);
912         s += "\n\n<b>" + std::to_string(n) + " " + object_descriptions.at(ObjectType::TRACK).get_name_for_n(n)
913              + "</b>\n";
914         std::set<int> layers;
915         std::set<const Net *> nets;
916         std::vector<const Track *> tracks;
917         int64_t length = 0;
918         const Track *the_track = nullptr;
919         for (const auto &it : sel) {
920             if (it.type == ObjectType::TRACK) {
921                 const auto &tr = core_board.get_board()->tracks.at(it.uuid);
922                 the_track = &tr;
923                 tracks.emplace_back(the_track);
924                 layers.insert(tr.layer);
925                 length += (tr.from.get_position() - tr.to.get_position()).magd();
926                 if (tr.net)
927                     nets.insert(tr.net);
928             }
929         }
930         s += "Layers: ";
931         for (auto layer : layers) {
932             s += core->get_layer_provider().get_layers().at(layer).name + " ";
933         }
934         s += "\nTotal length: " + dim_to_string(length, false);
935 
936         if (n == 2) {
937             // Show spacing between 2 parallel tracks
938             const auto t1 = *tracks.at(0);
939             const auto t2 = *tracks.at(1);
940             if (t1.is_parallel_to(t2)) {
941                 const Coordd u = t1.to.get_position() - t1.from.get_position();
942                 const Coordd v = u.normalize();
943                 const Coordd w = t2.from.get_position() - t1.from.get_position();
944                 const int64_t offset = v.cross(w);
945                 s += "\nSpacing: " + dim_to_string(offset, false);
946             }
947         }
948 
949         if (n == 1) {
950             s += "\n" + get_hud_text_for_net(the_track->net);
951         }
952         else {
953             s += "\n" + std::to_string(nets.size()) + " "
954                  + object_descriptions.at(ObjectType::NET).get_name_for_n(nets.size());
955         }
956         sel_erase_type(sel, ObjectType::TRACK);
957     }
958     trim(s);
959     if (auto it_sel = sel_find_exactly_one(sel, ObjectType::BOARD_PACKAGE)) {
960         const auto &pkg = core_board.get_board()->packages.at(it_sel->uuid);
961         s += "\n\n<b>Package " + pkg.component->refdes + "</b>";
962         if (pkg.fixed) {
963             s += " (not movable)";
964         }
965         s += "\n";
966         s += get_hud_text_for_component(pkg.component);
967         sel_erase_type(sel, ObjectType::BOARD_PACKAGE);
968     }
969     else if (sel_count_type(sel, ObjectType::BOARD_PACKAGE) > 1) {
970         auto n = sel_count_type(sel, ObjectType::BOARD_PACKAGE);
971         s += "\n\n<b>" + std::to_string(n) + " " + object_descriptions.at(ObjectType::BOARD_PACKAGE).get_name_for_n(n)
972              + "</b>\n";
973         size_t n_pads = 0;
974         for (const auto &it : sel) {
975             if (it.type == ObjectType::BOARD_PACKAGE) {
976                 const auto &pkg = core_board.get_board()->packages.at(it.uuid);
977                 n_pads += pkg.package.pads.size();
978             }
979         }
980         s += "Total pads: " + std::to_string(n_pads);
981 
982         // When n == 2 selection is removed when showing delta below
983         if (n != 2) {
984             sel_erase_type(sel, ObjectType::BOARD_PACKAGE);
985         }
986     }
987     trim(s);
988     if (sel_count_type(sel, ObjectType::POLYGON_VERTEX) == 1 || sel_count_type(sel, ObjectType::POLYGON_EDGE) == 1) {
989         const auto &brd = *core_board.get_board();
990         const Polygon *poly = nullptr;
991         if (sel_count_type(sel, ObjectType::POLYGON_VERTEX))
992             poly = &brd.polygons.at(sel_find_one(sel, ObjectType::POLYGON_VERTEX).uuid);
993         if (sel_count_type(sel, ObjectType::POLYGON_EDGE))
994             poly = &brd.polygons.at(sel_find_one(sel, ObjectType::POLYGON_EDGE).uuid);
995         if (poly) {
996             if (auto plane = dynamic_cast<const Plane *>(poly->usage.ptr)) {
997                 s += "\n\n<b>Plane " + plane->net->name + "</b>\n";
998                 s += "Fill order: " + std::to_string(plane->priority) + "\n";
999                 s += "Layer: ";
1000                 s += core_board.get_layer_provider().get_layers().at(poly->layer).name + "\n";
1001             }
1002         }
1003     }
1004     trim(s);
1005 
1006     // Display the delta if two items of these types are selected
1007     for (const ObjectType type : {ObjectType::BOARD_PACKAGE, ObjectType::BOARD_HOLE, ObjectType::VIA}) {
1008         if (sel_count_type(sel, type) == 2) {
1009             // Already added for packages
1010             if (type != ObjectType::BOARD_PACKAGE) {
1011                 s += "\n\n<b>2 " + object_descriptions.at(type).name_pl + "</b>";
1012             }
1013             std::vector<Coordi> positions;
1014             const auto &brd = *core_board.get_board();
1015             for (const auto &it : sel) {
1016                 if (it.type == type) {
1017                     if (type == ObjectType::BOARD_HOLE) {
1018                         const auto &hole = &brd.holes.at(it.uuid);
1019                         positions.push_back(hole->placement.shift);
1020                     }
1021                     else if (type == ObjectType::VIA) {
1022                         const auto &via = &brd.vias.at(it.uuid);
1023                         positions.push_back(via->junction->position);
1024                     }
1025                     else if (type == ObjectType::BOARD_PACKAGE) {
1026                         const auto &package = &brd.packages.at(it.uuid);
1027                         positions.push_back(package->placement.shift);
1028                     }
1029                     else {
1030                         assert(false); // unreachable
1031                     }
1032                 }
1033             }
1034             assert(positions.size() == 2);
1035             const auto delta = positions.at(1) - positions.at(0);
1036             s += "\n" + coord_to_string(delta, true);
1037             sel_erase_type(sel, type);
1038         }
1039     }
1040     trim(s);
1041 
1042     return s;
1043 }
1044 
handle_measure_tracks(const ActionConnection & a)1045 void ImpBoard::handle_measure_tracks(const ActionConnection &a)
1046 {
1047     auto sel = canvas->get_selection();
1048     std::set<UUID> tracks;
1049     for (const auto &it : sel) {
1050         if (it.type == ObjectType::TRACK) {
1051             tracks.insert(it.uuid);
1052         }
1053     }
1054     tuning_window->add_tracks(tracks, a.action_id == ActionID::TUNING_ADD_TRACKS_ALL);
1055     tuning_window->present();
1056 }
1057 
get_tool_for_drag_move(bool ctrl,const std::set<SelectableRef> & sel) const1058 ToolID ImpBoard::get_tool_for_drag_move(bool ctrl, const std::set<SelectableRef> &sel) const
1059 {
1060     if (preferences.board.move_using_router && sel.size() == 1 && sel_count_type(sel, ObjectType::TRACK) == 1)
1061         return ToolID::DRAG_TRACK_INTERACTIVE;
1062     else
1063         return ImpBase::get_tool_for_drag_move(ctrl, sel);
1064 }
1065 
handle_maybe_drag(bool ctrl)1066 void ImpBoard::handle_maybe_drag(bool ctrl)
1067 {
1068     if (!preferences.board.drag_start_track) {
1069         ImpBase::handle_maybe_drag(ctrl);
1070         return;
1071     }
1072     auto target = canvas->get_current_target();
1073     const auto brd = core_board.get_board();
1074     if (target.type == ObjectType::PAD) {
1075         auto &pkg = brd->packages.at(target.path.at(0));
1076         auto &pad = pkg.package.pads.at(target.path.at(1));
1077         if (pad.padstack.type == Padstack::Type::MECHANICAL || pad.is_nc) {
1078             ImpBase::handle_maybe_drag(ctrl);
1079             return;
1080         }
1081     }
1082     else if (target.type == ObjectType::JUNCTION) {
1083         const auto &ju = brd->junctions.at(target.path.at(0));
1084         if (!ju.net) {
1085             ImpBase::handle_maybe_drag(ctrl);
1086             return;
1087         }
1088     }
1089     else {
1090         ImpBase::handle_maybe_drag(ctrl);
1091         return;
1092     }
1093     canvas->inhibit_drag_selection();
1094     target_drag_begin = target;
1095     cursor_pos_drag_begin = canvas->get_cursor_pos_win();
1096 }
1097 
handle_drag()1098 void ImpBoard::handle_drag()
1099 {
1100     auto pos = canvas->get_cursor_pos_win();
1101     auto delta = pos - cursor_pos_drag_begin;
1102     auto brd = core_board.get_board();
1103     bool have_net = false;
1104     if (target_drag_begin.type == ObjectType::PAD) {
1105         auto &pkg = brd->packages.at(target_drag_begin.path.at(0));
1106         auto &pad = pkg.package.pads.at(target_drag_begin.path.at(1));
1107         have_net = pad.net;
1108     }
1109     else if (target_drag_begin.type == ObjectType::JUNCTION) {
1110         have_net = brd->junctions.at(target_drag_begin.path.at(0)).net;
1111     }
1112 
1113     if (delta.mag_sq() > (50 * 50)) {
1114         if (have_net) {
1115             {
1116                 highlights.clear();
1117                 update_highlights();
1118                 ToolArgs args;
1119                 args.coords = target_drag_begin.p;
1120                 ToolResponse r = core->tool_begin(ToolID::ROUTE_TRACK_INTERACTIVE, args, imp_interface.get());
1121                 tool_process(r);
1122             }
1123             {
1124                 ToolArgs args;
1125                 args.type = ToolEventType::ACTION;
1126                 args.coords = target_drag_begin.p;
1127                 args.action = InToolActionID::LMB;
1128                 args.target = target_drag_begin;
1129                 args.work_layer = canvas->property_work_layer();
1130                 ToolResponse r = core->tool_update(args);
1131                 tool_process(r);
1132             }
1133         }
1134         else {
1135             {
1136                 highlights.clear();
1137                 update_highlights();
1138                 ToolArgs args;
1139                 args.coords = target_drag_begin.p;
1140                 ToolResponse r = core->tool_begin(ToolID::DRAW_CONNECTION_LINE, args, imp_interface.get());
1141                 tool_process(r);
1142             }
1143             {
1144                 ToolArgs args;
1145                 args.type = ToolEventType::ACTION;
1146                 args.coords = target_drag_begin.p;
1147                 args.action = InToolActionID::LMB;
1148                 args.target = target_drag_begin;
1149                 args.work_layer = canvas->property_work_layer();
1150                 ToolResponse r = core->tool_update(args);
1151                 tool_process(r);
1152             }
1153         }
1154         target_drag_begin = Target();
1155     }
1156 }
1157 
get_doubleclick_action(ObjectType type,const UUID & uu)1158 ActionToolID ImpBoard::get_doubleclick_action(ObjectType type, const UUID &uu)
1159 {
1160     auto a = ImpBase::get_doubleclick_action(type, uu);
1161     if (a.first != ActionID::NONE)
1162         return a;
1163     switch (type) {
1164     case ObjectType::BOARD_HOLE:
1165         return make_action(ToolID::EDIT_BOARD_HOLE);
1166         break;
1167     case ObjectType::VIA:
1168         return make_action(ToolID::EDIT_VIA);
1169         break;
1170     case ObjectType::TRACK:
1171         return make_action(ActionID::SELECT_MORE);
1172         break;
1173     case ObjectType::POLYGON:
1174     case ObjectType::POLYGON_EDGE:
1175     case ObjectType::POLYGON_VERTEX: {
1176         auto poly = core_board.get_polygon(uu);
1177         if (poly->usage) {
1178             switch (poly->usage->get_type()) {
1179             case PolygonUsage::Type::PLANE:
1180                 return make_action(ToolID::EDIT_PLANE);
1181 
1182             case PolygonUsage::Type::KEEPOUT:
1183                 return make_action(ToolID::EDIT_KEEPOUT);
1184 
1185             default:
1186                 return {ActionID::NONE, ToolID::NONE};
1187             }
1188         }
1189         else {
1190             return {ActionID::NONE, ToolID::NONE};
1191         }
1192     } break;
1193     default:
1194         return {ActionID::NONE, ToolID::NONE};
1195     }
1196 }
1197 
1198 
append_bottom_layers(std::vector<int> & layers)1199 static void append_bottom_layers(std::vector<int> &layers)
1200 {
1201     std::vector<int> bottoms;
1202     bottoms.reserve(layers.size());
1203     for (auto it = layers.rbegin(); it != layers.rend(); it++) {
1204         if (*it >= 0 && *it != BoardLayers::L_OUTLINE && *it != BoardLayers::OUTLINE_NOTES)
1205             bottoms.push_back(-100 - *it);
1206     }
1207     layers.insert(layers.end(), bottoms.begin(), bottoms.end());
1208 }
1209 
get_selection_filter_info() const1210 std::map<ObjectType, ImpBase::SelectionFilterInfo> ImpBoard::get_selection_filter_info() const
1211 {
1212     std::vector<int> inner_layers;
1213     for (unsigned i = 0; i < core_board.get_board()->get_n_inner_layers(); i++) {
1214         inner_layers.push_back(-i - 1);
1215     }
1216     std::vector<int> layers_line = {BoardLayers::OUTLINE_NOTES, BoardLayers::TOP_ASSEMBLY, BoardLayers::TOP_SILKSCREEN,
1217                                     BoardLayers::TOP_MASK, BoardLayers::TOP_COPPER};
1218     append_bottom_layers(layers_line);
1219 
1220     std::vector<int> layers_polygon = {BoardLayers::L_OUTLINE, BoardLayers::TOP_SILKSCREEN, BoardLayers::TOP_MASK,
1221                                        BoardLayers::TOP_COPPER};
1222     layers_polygon.insert(layers_polygon.end(), inner_layers.begin(), inner_layers.end());
1223     append_bottom_layers(layers_polygon);
1224 
1225     std::vector<int> layers_package = {BoardLayers::TOP_COPPER, BoardLayers::BOTTOM_COPPER};
1226 
1227     std::vector<int> layers_track = {BoardLayers::TOP_COPPER};
1228     layers_track.insert(layers_track.end(), inner_layers.begin(), inner_layers.end());
1229     append_bottom_layers(layers_track);
1230 
1231     using Flag = ImpBase::SelectionFilterInfo::Flag;
1232     std::map<ObjectType, ImpBase::SelectionFilterInfo> r = {
1233             {ObjectType::BOARD_PACKAGE, {layers_package, Flag::DEFAULT}},
1234             {ObjectType::TRACK, {layers_track, Flag::DEFAULT}},
1235             {ObjectType::VIA, {{}, Flag::WORK_LAYER_ONLY_ENABLED}},
1236             {ObjectType::BOARD_DECAL, {}},
1237             {ObjectType::POLYGON, {layers_polygon, Flag::HAS_OTHERS}},
1238             {ObjectType::TEXT, {layers_line, Flag::HAS_OTHERS}},
1239             {ObjectType::LINE, {layers_line, Flag::HAS_OTHERS}},
1240             {ObjectType::JUNCTION, {layers_track, Flag::HAS_OTHERS}},
1241             {ObjectType::ARC, {layers_line, Flag::HAS_OTHERS}},
1242             {ObjectType::DIMENSION, {}},
1243             {ObjectType::BOARD_HOLE, {{}, Flag::WORK_LAYER_ONLY_ENABLED}},
1244             {ObjectType::CONNECTION_LINE, {}},
1245             {ObjectType::BOARD_PANEL, {}},
1246             {ObjectType::PICTURE, {}},
1247     };
1248     return r;
1249 }
1250 
update_unplaced()1251 void ImpBoard::update_unplaced()
1252 {
1253     std::map<UUIDPath<2>, std::string> components;
1254     const auto brd = core_board.get_board();
1255     for (const auto &it : brd->block->components) {
1256         if (it.second.part)
1257             components.emplace(std::piecewise_construct, std::forward_as_tuple(it.second.uuid),
1258                                std::forward_as_tuple(it.second.refdes));
1259     }
1260 
1261     for (auto &it : brd->packages) {
1262         components.erase(it.second.component->uuid);
1263     }
1264     unplaced_box->update(components);
1265 }
1266 
get_save_meta(json & j)1267 void ImpBoard::get_save_meta(json &j)
1268 {
1269     ImpLayer::get_save_meta(j);
1270     j["board_display_options"] = board_display_options_box->serialize();
1271     j["nets"] = airwire_filter_window->serialize();
1272     j["parts"] = parts_window->serialize();
1273 }
1274 
get_view_hints()1275 std::vector<std::string> ImpBoard::get_view_hints()
1276 {
1277     auto r = ImpLayer::get_view_hints();
1278 
1279     if (airwire_filter_window->get_filtered())
1280         r.emplace_back("airwires filtered");
1281 
1282     return r;
1283 }
1284 
update_net_colors()1285 void ImpBoard::update_net_colors()
1286 {
1287     auto &net_colors = airwire_filter_window->get_net_colors();
1288     net_color_map.clear();
1289     if (net_colors.size()) {
1290         std::vector<ColorI> t;
1291         t.emplace_back(ColorI{0, 0, 0});
1292 
1293         std::map<ColorI, int> colors_idxs;
1294         auto get_or_create_color = [&t, &colors_idxs](ColorI c) -> int {
1295             if (colors_idxs.count(c)) {
1296                 return colors_idxs.at(c);
1297             }
1298             else {
1299                 t.emplace_back(c);
1300                 const auto i = t.size() - 1;
1301                 colors_idxs.emplace(c, i);
1302                 return i;
1303             }
1304         };
1305 
1306         for (const auto &[net, color] : net_colors) {
1307             net_color_map[net] = get_or_create_color(color);
1308         }
1309         canvas->set_colors2(t);
1310     }
1311 }
1312 
1313 
update_monitor()1314 void ImpBoard::update_monitor()
1315 {
1316     ItemSet mon_items = core_board.get_block()->get_pool_items_used();
1317     {
1318         ItemSet items = core_board.get_board()->get_pool_items_used();
1319         mon_items.insert(items.begin(), items.end());
1320     }
1321     set_monitor_items(mon_items);
1322 }
1323 
~ImpBoard()1324 ImpBoard::~ImpBoard()
1325 {
1326     reload_netlist_delay_conn.disconnect();
1327     delete view_3d_window;
1328 }
1329 
1330 } // namespace horizon
1331