1 #include "imp_package.hpp"
2 #include "3d/3d_view.hpp"
3 #include "board/board_layers.hpp"
4 #include "canvas/canvas_gl.hpp"
5 #include "footprint_generator/footprint_generator_window.hpp"
6 #include "header_button.hpp"
7 #include "parameter_window.hpp"
8 #include "pool/part.hpp"
9 #include "util/gtk_util.hpp"
10 #include "util/geom_util.hpp"
11 #include "util/pool_completion.hpp"
12 #include "util/changeable.hpp"
13 #include "util/str_util.hpp"
14 #include "widgets/pool_browser_button.hpp"
15 #include "widgets/pool_browser.hpp"
16 #include "widgets/spin_button_dim.hpp"
17 #include "widgets/parameter_set_editor.hpp"
18 #include "widgets/tag_entry.hpp"
19 #include "widgets/layer_box.hpp"
20 #include "widgets/layer_help_box.hpp"
21 #include "widgets/spin_button_angle.hpp"
22 #include "util/selection_util.hpp"
23 #include "widgets/action_button.hpp"
24 #include <iomanip>
25 #include "core/tool_id.hpp"
26 #include "actions.hpp"
27 
28 namespace horizon {
ImpPackage(const std::string & package_filename,const std::string & pool_path)29 ImpPackage::ImpPackage(const std::string &package_filename, const std::string &pool_path)
30     : ImpLayer(pool_path), core_package(package_filename, *pool), package(core_package.get_package()),
31       searcher(core_package), fake_block(UUID::random()), fake_board(UUID::random(), fake_block)
32 {
33     core = &core_package;
34     core_package.signal_tool_changed().connect(sigc::mem_fun(*this, &ImpBase::handle_tool_change));
35     load_meta();
36 }
37 
canvas_update()38 void ImpPackage::canvas_update()
39 {
40     canvas->update(core_package.get_canvas_data());
41     warnings_box->update(package.warnings);
42     if (projection_annotation->get_visible()) {
43         for (const auto &it : projection_targets) {
44             canvas->append_target(Target(UUID(), ObjectType::MODEL_3D, it));
45         }
46     }
47     update_highlights();
48 }
49 
apply_preferences()50 void ImpPackage::apply_preferences()
51 {
52     if (view_3d_window)
53         view_3d_window->apply_preferences(preferences);
54     ImpLayer::apply_preferences();
55 }
56 
get_hud_text(std::set<SelectableRef> & sel)57 std::string ImpPackage::get_hud_text(std::set<SelectableRef> &sel)
58 {
59     std::string s;
60     if (auto it_sel = sel_find_exactly_one(sel, ObjectType::PAD)) {
61         const auto &pad = package.pads.at(it_sel->uuid);
62         s += "<b>Pad " + pad.name + "</b>\n";
63         for (const auto &it : pad.parameter_set) {
64             s += parameter_id_to_name(it.first) + ": " + dim_to_string(it.second, true) + "\n";
65         }
66         sel_erase_type(sel, ObjectType::PAD);
67     }
68     else if (sel_count_type(sel, ObjectType::PAD) == 2) {
69         std::vector<const Pad *> pads;
70         for (const auto &it : sel) {
71             if (it.type == ObjectType::PAD) {
72                 pads.push_back(&package.pads.at(it.uuid));
73             }
74         }
75         assert(pads.size() == 2);
76         s += "<b>2 Pads</b>\n";
77         s += "ΔX:" + dim_to_string(std::abs(pads.at(0)->placement.shift.x - pads.at(1)->placement.shift.x)) + "\n";
78         s += "ΔY:" + dim_to_string(std::abs(pads.at(0)->placement.shift.y - pads.at(1)->placement.shift.y));
79         sel_erase_type(sel, ObjectType::PAD);
80     }
81     trim(s);
82     return s;
83 }
84 
85 
update_action_sensitivity()86 void ImpPackage::update_action_sensitivity()
87 {
88     auto sel = canvas->get_selection();
89     set_action_sensitive(make_action(ActionID::EDIT_PADSTACK),
90                          sockets_connected && std::any_of(sel.begin(), sel.end(), [](const auto &x) {
91                              return x.type == ObjectType::PAD;
92                          }));
93 
94     ImpBase::update_action_sensitivity();
95 }
96 
update_monitor()97 void ImpPackage::update_monitor()
98 {
99     ItemSet items;
100     for (const auto &it : package.pads) {
101         items.emplace(ObjectType::PADSTACK, it.second.pool_padstack->uuid);
102     }
103     set_monitor_items(items);
104 }
105 
check_alt_pkg()106 void ImpPackage::check_alt_pkg()
107 {
108     const UUID alt_uuid = browser_alt_button->property_selected_uuid();
109     if (!alt_uuid)
110         return;
111     const auto pkg_uuid = package.uuid;
112     if (pkg_uuid == alt_uuid) {
113         browser_alt_button->property_selected_uuid() = UUID();
114         return;
115     }
116     pool->db.execute("DROP VIEW IF EXISTS pkg_deps");
117     {
118         pool->db.execute(
119                         "CREATE TEMPORARY view pkg_deps AS SELECT uuid, dep_uuid FROM dependencies WHERE "
120                         "type = 'package' AND dep_type = 'package' AND uuid != '" + (std::string)pkg_uuid + "' "
121                         "UNION SELECT '" + (std::string)pkg_uuid + "', '" + (std::string) alt_uuid+ "'");
122     }
123     {
124         SQLite::Query q(pool->db,
125                         "WITH FindRoot AS ( "
126                         "SELECT uuid, dep_uuid, uuid as path, 0 AS Distance "
127                         "FROM pkg_deps WHERE uuid = $pkg "
128                         "UNION ALL "
129                         "SELECT C.uuid, P.dep_uuid, C.path || ' > ' || P.uuid, C.Distance + 1 "
130                         "FROM pkg_deps AS P "
131                         " JOIN FindRoot AS C "
132                         " ON C.dep_uuid = P.uuid AND P.dep_uuid != P.uuid AND C.dep_uuid != C.uuid "
133                         ") "
134                         "SELECT * "
135                         "FROM FindRoot AS R "
136                         "WHERE R.uuid = R.dep_uuid AND R.Distance > 0");
137         q.bind("$pkg", pkg_uuid);
138         while (q.step()) {
139             Gtk::MessageDialog md(*main_window, "Can't set as alt. package", false /* use_markup */, Gtk::MESSAGE_ERROR,
140                                   Gtk::BUTTONS_OK);
141             md.set_secondary_text("Cyclic dependency");
142             md.run();
143             browser_alt_button->property_selected_uuid() = UUID();
144             break;
145         }
146     }
147 
148     pool->db.execute("DROP VIEW IF EXISTS pkg_deps");
149 }
150 
construct()151 void ImpPackage::construct()
152 {
153     ImpLayer::construct_layer_box();
154 
155     state_store = std::make_unique<WindowStateStore>(main_window, "imp-package");
156 
157     construct_3d();
158 
159     connect_action(ActionID::EDIT_PADSTACK, [this](const auto &a) {
160         auto sel = canvas->get_selection();
161         if (sel_count_type(sel, ObjectType::PAD)) {
162             auto uu = sel_find_one(sel, ObjectType::PAD).uuid;
163             this->edit_pool_item(ObjectType::PADSTACK, package.pads.at(uu).pool_padstack->uuid);
164         }
165     });
166 
167     footprint_generator_window = FootprintGeneratorWindow::create(main_window, core_package);
168     footprint_generator_window->signal_generated().connect(sigc::mem_fun(*this, &ImpBase::canvas_update_from_pp));
169 
170     parameter_window =
171             new ParameterWindow(main_window, &core_package.parameter_program_code, &core_package.parameter_set);
172     parameter_window->signal_changed().connect([this] { core_package.set_needs_save(); });
173     {
174         auto button = Gtk::manage(new Gtk::Button("Parameters…"));
175         main_window->header->pack_start(*button);
176         button->show();
177         button->signal_clicked().connect([this] { parameter_window->present(); });
178     }
179     parameter_window_add_polygon_expand(parameter_window);
180     {
181         auto button = Gtk::manage(new Gtk::Button("Insert courtyard program"));
182         parameter_window->add_button(button);
183         button->signal_clicked().connect([this] {
184             const Polygon *poly = nullptr;
185             for (const auto &it : package.polygons) {
186                 if (it.second.vertices.size() == 4 && !it.second.has_arcs()
187                     && it.second.parameter_class == "courtyard") {
188                     poly = &it.second;
189                     break;
190                 }
191             }
192             if (poly) {
193                 parameter_window->set_error_message("");
194                 Coordi a = poly->vertices.at(0).position;
195                 Coordi b = a;
196                 for (const auto &v : poly->vertices) {
197                     a = Coordi::min(a, v.position);
198                     b = Coordi::max(b, v.position);
199                 }
200                 auto c = (a + b) / 2;
201                 auto d = b - a;
202                 std::stringstream ss;
203                 ss.imbue(std::locale::classic());
204                 ss << std::fixed << std::setprecision(3);
205                 ss << d.x / 1e6 << "mm " << d.y / 1e6 << "mm\n";
206                 ss << "get-parameter [ courtyard_expansion ]\n2 * "
207                       "+xy\nset-polygon [ courtyard rectangle ";
208                 ss << c.x / 1e6 << "mm " << c.y / 1e6 << "mm ]";
209                 parameter_window->insert_text(ss.str());
210                 parameter_window->get_parameter_set_editor()->add_or_set_parameter(ParameterID::COURTYARD_EXPANSION,
211                                                                                    0.25_mm);
212                 parameter_window->signal_apply().emit();
213             }
214             else {
215                 parameter_window->set_error_message(
216                         "no courtyard polygon found: needs to have 4 vertices "
217                         "and the parameter class 'courtyard'");
218             }
219         });
220     }
221     parameter_window->signal_apply().connect([this] {
222         if (core->tool_is_active())
223             return;
224 
225         if (auto r = package.parameter_program.set_code(core_package.parameter_program_code)) {
226             parameter_window->set_error_message("<b>Compile error:</b>" + r.value());
227             return;
228         }
229         else {
230             parameter_window->set_error_message("");
231         }
232         package.parameter_set = core_package.parameter_set;
233 
234         if (auto r = package.parameter_program.run(package.parameter_set)) {
235             parameter_window->set_error_message("<b>Run error:</b>" + r.value());
236             return;
237         }
238         else {
239             parameter_window->set_error_message("");
240         }
241         core_package.rebuild();
242         canvas_update();
243     });
244 
245     layer_help_box = Gtk::manage(new LayerHelpBox(*pool.get()));
246     layer_help_box->show();
247     main_window->left_panel->pack_end(*layer_help_box, true, true, 0);
248     layer_box->property_work_layer().signal_changed().connect(
249             [this] { layer_help_box->set_layer(layer_box->property_work_layer()); });
250     layer_help_box->set_layer(layer_box->property_work_layer());
251     layer_help_box->signal_trigger_action().connect([this](auto a) { this->trigger_action(a); });
252 
253 
254     header_button = Gtk::manage(new HeaderButton);
255     main_window->header->set_custom_title(*header_button);
256     header_button->show();
257     header_button->signal_closed().connect(sigc::mem_fun(*this, &ImpPackage::update_header));
258 
259     entry_name = header_button->add_entry("Name");
260     entry_name->signal_activate().connect(sigc::mem_fun(*this, &ImpPackage::update_header));
261 
262     auto entry_manufacturer = header_button->add_entry("Manufacturer");
263     entry_manufacturer->set_completion(create_pool_manufacturer_completion(*pool.get()));
264 
265     auto entry_tags = Gtk::manage(new TagEntry(*pool.get(), ObjectType::PACKAGE, true));
266     entry_tags->show();
267     header_button->add_widget("Tags", entry_tags);
268 
269     browser_alt_button = Gtk::manage(new PoolBrowserButton(ObjectType::PACKAGE, *pool.get()));
270     browser_alt_button->get_browser().set_show_none(true);
271     header_button->add_widget("Alternate for", browser_alt_button);
272 
273     {
274         entry_name->set_text(package.name);
275         entry_manufacturer->set_text(package.manufacturer);
276         std::stringstream s;
277         entry_tags->set_tags(package.tags);
278         if (package.alternate_for)
279             browser_alt_button->property_selected_uuid() = package.alternate_for->uuid;
280     }
281 
282     entry_name->signal_changed().connect([this] { core_package.set_needs_save(); });
283     entry_manufacturer->signal_changed().connect([this] { core_package.set_needs_save(); });
284     entry_tags->signal_changed().connect([this] { core_package.set_needs_save(); });
285 
286     browser_alt_button->property_selected_uuid().signal_changed().connect([this] {
287         check_alt_pkg();
288         core_package.set_needs_save();
289     });
290 
291     if (package.name.size() == 0) { // new package
292         entry_name->set_text(Glib::path_get_basename(Glib::path_get_dirname(core_package.get_filename())));
293     }
294 
295     hamburger_menu->append("Import DXF", "win.import_dxf");
296     add_tool_action(ToolID::IMPORT_DXF, "import_dxf");
297 
298     hamburger_menu->append("Import KiCad package", "win.import_kicad");
299     add_tool_action(ToolID::IMPORT_KICAD_PACKAGE, "import_kicad");
300 
301     hamburger_menu->append("Reload pool", "win.reload_pool");
302     main_window->add_action("reload_pool", [this] { trigger_action(ActionID::RELOAD_POOL); });
303 
304     view_options_menu_append_action("Bottom view", "win.bottom_view");
305     view_options_menu_append_action("3D projection", "win.show_projection");
306 
307     add_view_angle_actions();
308 
309     connect_action(ActionID::FOOTPRINT_GENERATOR, [this](auto &a) {
310         footprint_generator_window->present();
311         footprint_generator_window->show_all();
312     });
313 
314     update_monitor();
315 
316     core_package.signal_save().connect([this, entry_manufacturer, entry_tags] {
317         package.tags = entry_tags->get_tags();
318         package.name = entry_name->get_text();
319         package.manufacturer = entry_manufacturer->get_text();
320         UUID alt_uuid = browser_alt_button->property_selected_uuid();
321         if (alt_uuid) {
322             package.alternate_for = pool->get_package(alt_uuid);
323         }
324         else {
325             package.alternate_for = nullptr;
326         }
327     });
328 
329     add_action_button(make_action(ToolID::PLACE_PAD));
330     add_action_button_line();
331     add_action_button_polygon();
332     add_action_button(make_action(ToolID::PLACE_TEXT));
333     add_action_button(make_action(ToolID::DRAW_DIMENSION));
334 
335     {
336         auto &b = add_action_button_menu("action-generate-symbolic");
337         b.set_margin_top(5);
338         b.add_action(make_action(ActionID::FOOTPRINT_GENERATOR));
339         b.add_action(make_action(ToolID::GENERATE_COURTYARD));
340         b.add_action(make_action(ToolID::GENERATE_SILKSCREEN));
341         b.set_tooltip_text("Generate…");
342     }
343 
344     update_header();
345     set_view_angle(0);
346 }
347 
348 
update_highlights()349 void ImpPackage::update_highlights()
350 {
351     canvas->set_flags_all(0, TriangleInfo::FLAG_HIGHLIGHT);
352     canvas->set_highlight_enabled(highlights.size());
353     for (const auto &it : highlights) {
354         if (it.type == ObjectType::PAD) {
355             ObjectRef ref(ObjectType::PAD, it.uuid);
356             canvas->set_flags(ref, TriangleInfo::FLAG_HIGHLIGHT, 0);
357         }
358 
359         else {
360             canvas->set_flags(it, TriangleInfo::FLAG_HIGHLIGHT, 0);
361         }
362     }
363 }
364 
365 
get_doubleclick_action(ObjectType type,const UUID & uu)366 ActionToolID ImpPackage::get_doubleclick_action(ObjectType type, const UUID &uu)
367 {
368     auto a = ImpBase::get_doubleclick_action(type, uu);
369     if (a.first != ActionID::NONE)
370         return a;
371     switch (type) {
372     case ObjectType::PAD:
373         return make_action(ToolID::EDIT_PAD_PARAMETER_SET);
374         break;
375     default:
376         return {ActionID::NONE, ToolID::NONE};
377     }
378 }
379 
append_bottom_layers(std::vector<int> & layers)380 static void append_bottom_layers(std::vector<int> &layers)
381 {
382     std::vector<int> bottoms;
383     bottoms.reserve(layers.size());
384     for (auto it = layers.rbegin(); it != layers.rend(); it++) {
385         bottoms.push_back(-100 - *it);
386     }
387     layers.insert(layers.end(), bottoms.begin(), bottoms.end());
388 }
389 
get_selection_filter_info() const390 std::map<ObjectType, ImpBase::SelectionFilterInfo> ImpPackage::get_selection_filter_info() const
391 {
392     std::vector<int> layers_line = {BoardLayers::TOP_ASSEMBLY, BoardLayers::TOP_PACKAGE, BoardLayers::TOP_SILKSCREEN};
393     append_bottom_layers(layers_line);
394     std::vector<int> layers_text = {BoardLayers::TOP_ASSEMBLY, BoardLayers::TOP_SILKSCREEN};
395     append_bottom_layers(layers_text);
396     std::vector<int> layers_polygon = {BoardLayers::TOP_COURTYARD, BoardLayers::TOP_ASSEMBLY, BoardLayers::TOP_PACKAGE,
397                                        BoardLayers::TOP_SILKSCREEN};
398     append_bottom_layers(layers_polygon);
399 
400     using Flag = ImpBase::SelectionFilterInfo::Flag;
401     std::map<ObjectType, ImpBase::SelectionFilterInfo> r = {
402             {ObjectType::PAD, {{}, Flag::WORK_LAYER_ONLY_ENABLED}},
403             {ObjectType::LINE, {layers_line, Flag::HAS_OTHERS}},
404             {ObjectType::TEXT, {layers_text, Flag::HAS_OTHERS}},
405             {ObjectType::JUNCTION, {layers_line, Flag::HAS_OTHERS}},
406             {ObjectType::POLYGON, {layers_polygon, Flag::HAS_OTHERS}},
407             {ObjectType::DIMENSION, {}},
408             {ObjectType::PICTURE, {}},
409             {ObjectType::ARC, {layers_line, Flag::HAS_OTHERS}},
410     };
411     return r;
412 }
413 
414 
update_header()415 void ImpPackage::update_header()
416 {
417     const auto &name = entry_name->get_text();
418     header_button->set_label(name);
419     set_window_title(name);
420     parameter_window->set_subtitle(name);
421 }
422 
get_view_hints()423 std::vector<std::string> ImpPackage::get_view_hints()
424 {
425     auto r = ImpLayer::get_view_hints();
426 
427     if (projection_annotation->get_visible())
428         r.emplace_back("3D projection");
429 
430     return r;
431 }
432 
~ImpPackage()433 ImpPackage::~ImpPackage()
434 {
435     delete view_3d_window;
436 }
437 
438 } // namespace horizon
439