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