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