1 #include "fab_output_window.hpp"
2 #include "board/board.hpp"
3 #include "export_gerber/gerber_export.hpp"
4 #include "util/gtk_util.hpp"
5 #include "widgets/spin_button_dim.hpp"
6 #include "document/idocument_board.hpp"
7 #include "rules/rules_with_core.hpp"
8 #include "rules/cache.hpp"
9
10 namespace horizon {
11
12 class GerberLayerEditor : public Gtk::Box, public Changeable {
13 public:
14 GerberLayerEditor(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, FabOutputWindow &pa,
15 FabOutputSettings::GerberLayer &la);
16 static GerberLayerEditor *create(FabOutputWindow &pa, FabOutputSettings::GerberLayer &la);
17 FabOutputWindow &parent;
18
19 private:
20 Gtk::CheckButton *gerber_layer_checkbutton = nullptr;
21 Gtk::Entry *gerber_layer_filename_entry = nullptr;
22
23 FabOutputSettings::GerberLayer &layer;
24 };
25
GerberLayerEditor(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,FabOutputWindow & pa,FabOutputSettings::GerberLayer & la)26 GerberLayerEditor::GerberLayerEditor(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, FabOutputWindow &pa,
27 FabOutputSettings::GerberLayer &la)
28 : Gtk::Box(cobject), parent(pa), layer(la)
29 {
30 GET_WIDGET(gerber_layer_checkbutton);
31 GET_WIDGET(gerber_layer_filename_entry);
32 parent.sg_layer_name->add_widget(*gerber_layer_checkbutton);
33
34 gerber_layer_checkbutton->set_label(parent.brd.get_layers().at(layer.layer).name);
35 bind_widget(gerber_layer_checkbutton, layer.enabled);
36 gerber_layer_checkbutton->signal_toggled().connect([this] { s_signal_changed.emit(); });
37 bind_widget(gerber_layer_filename_entry, layer.filename);
38 gerber_layer_filename_entry->signal_changed().connect([this] { s_signal_changed.emit(); });
39 }
40
create(FabOutputWindow & pa,FabOutputSettings::GerberLayer & la)41 GerberLayerEditor *GerberLayerEditor::create(FabOutputWindow &pa, FabOutputSettings::GerberLayer &la)
42 {
43 GerberLayerEditor *w;
44 Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
45 x->add_from_resource("/org/horizon-eda/horizon/imp/fab_output.ui", "gerber_layer_editor");
46 x->get_widget_derived("gerber_layer_editor", w, pa, la);
47 w->reference();
48 return w;
49 }
50
FabOutputWindow(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,IDocumentBoard & c,const std::string & project_dir)51 FabOutputWindow::FabOutputWindow(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, IDocumentBoard &c,
52 const std::string &project_dir)
53 : Gtk::Window(cobject), core(c), brd(*core.get_board()), settings(core.get_fab_output_settings()),
54 state_store(this, "imp-fab-output")
55 {
56 GET_WIDGET(gerber_layers_box);
57 GET_WIDGET(prefix_entry);
58 GET_WIDGET(directory_entry);
59 GET_WIDGET(npth_filename_entry);
60 GET_WIDGET(pth_filename_entry);
61 GET_WIDGET(npth_filename_label);
62 GET_WIDGET(pth_filename_label);
63 GET_WIDGET(generate_button);
64 GET_WIDGET(directory_button);
65 GET_WIDGET(drill_mode_combo);
66 GET_WIDGET(log_textview);
67 GET_WIDGET(zip_output_switch);
68
69 export_filechooser.attach(directory_entry, directory_button, this);
70 export_filechooser.set_project_dir(project_dir);
71 export_filechooser.bind_filename(settings.output_directory);
72 export_filechooser.signal_changed().connect([this] {
73 s_signal_changed.emit();
74 update_export_button();
75 });
76 export_filechooser.set_action(GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
77
78 bind_widget(prefix_entry, settings.prefix, [this](std::string &) {
79 s_signal_changed.emit();
80 update_export_button();
81 });
82 prefix_entry->signal_changed().connect([this] { s_signal_changed.emit(); });
83 bind_widget(npth_filename_entry, settings.drill_npth_filename, [this](std::string &) {
84 s_signal_changed.emit();
85 update_export_button();
86 });
87 bind_widget(pth_filename_entry, settings.drill_pth_filename, [this](std::string &) {
88 s_signal_changed.emit();
89 update_export_button();
90 });
91 bind_widget(zip_output_switch, settings.zip_output);
92 zip_output_switch->property_active().signal_changed().connect([this] { s_signal_changed.emit(); });
93
94 drill_mode_combo->set_active_id(FabOutputSettings::mode_lut.lookup_reverse(settings.drill_mode));
95 drill_mode_combo->signal_changed().connect([this] {
96 settings.drill_mode = FabOutputSettings::mode_lut.lookup(drill_mode_combo->get_active_id());
97 update_drill_visibility();
98 s_signal_changed.emit();
99 update_export_button();
100 });
101 update_drill_visibility();
102
103 generate_button->signal_clicked().connect(sigc::mem_fun(*this, &FabOutputWindow::generate));
104
105 outline_width_sp = Gtk::manage(new SpinButtonDim());
106 outline_width_sp->set_range(.01_mm, 10_mm);
107 outline_width_sp->show();
108 bind_widget(outline_width_sp, settings.outline_width);
109 outline_width_sp->signal_value_changed().connect([this] { s_signal_changed.emit(); });
110 {
111 Gtk::Box *b = nullptr;
112 x->get_widget("gerber_outline_width_box", b);
113 b->pack_start(*outline_width_sp, true, true, 0);
114 }
115
116 sg_layer_name = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
117
118 reload_layers();
119 update_export_button();
120 }
121
reload_layers()122 void FabOutputWindow::reload_layers()
123 {
124 if (n_layers == settings.layers.size())
125 return;
126 else
127 n_layers = settings.layers.size();
128
129 {
130 auto children = gerber_layers_box->get_children();
131 for (auto ch : children)
132 delete ch;
133 }
134 std::vector<FabOutputSettings::GerberLayer *> layers_sorted;
135 layers_sorted.reserve(settings.layers.size());
136 for (auto &la : settings.layers) {
137 layers_sorted.push_back(&la.second);
138 }
139 std::sort(layers_sorted.begin(), layers_sorted.end(),
140 [](const auto a, const auto b) { return b->layer < a->layer; });
141
142 for (auto la : layers_sorted) {
143 auto ed = GerberLayerEditor::create(*this, *la);
144 ed->signal_changed().connect([this] {
145 s_signal_changed.emit();
146 update_export_button();
147 });
148 gerber_layers_box->add(*ed);
149 ed->show();
150 ed->unreference();
151 }
152 }
153
update_drill_visibility()154 void FabOutputWindow::update_drill_visibility()
155 {
156 if (settings.drill_mode == FabOutputSettings::DrillMode::INDIVIDUAL) {
157 npth_filename_entry->set_visible(true);
158 npth_filename_label->set_visible(true);
159 pth_filename_label->set_text("PTH suffix");
160 }
161 else {
162 npth_filename_entry->set_visible(false);
163 npth_filename_label->set_visible(false);
164 pth_filename_label->set_text("Drill suffix");
165 }
166 }
167
cb_nop(const std::string &)168 static void cb_nop(const std::string &)
169 {
170 }
171
generate()172 void FabOutputWindow::generate()
173 {
174 if (!generate_button->get_sensitive())
175 return;
176
177 RulesCheckCache cache(&core);
178 const auto r = rules_check(*core.get_rules(), RuleID::PREFLIGHT_CHECKS, core, cache, &cb_nop);
179 const auto r_plane = rules_check(*core.get_rules(), RuleID::PLANE, core, cache, &cb_nop);
180 if (r.level != RulesCheckErrorLevel::PASS || r_plane.level != RulesCheckErrorLevel::PASS) {
181 Gtk::MessageDialog md(*this, "Preflight or plane checks didn't pass", false /* use_markup */,
182 Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE);
183 md.set_secondary_text("This might be due to unfilled planes or overlapping planes of identical priority.");
184 md.add_button("Ignore", Gtk::RESPONSE_ACCEPT);
185 md.add_button("Cancel", Gtk::RESPONSE_CANCEL);
186 md.set_default_response(Gtk::RESPONSE_CANCEL);
187 if (md.run() != Gtk::RESPONSE_ACCEPT) {
188 return;
189 }
190 }
191
192 try {
193 FabOutputSettings my_settings = settings;
194 my_settings.output_directory = export_filechooser.get_filename_abs();
195 my_settings.zip_output = zip_output_switch->get_active();
196 GerberExporter ex(brd, my_settings);
197 ex.generate();
198 log_textview->get_buffer()->set_text(ex.get_log());
199 }
200 catch (const std::exception &e) {
201 log_textview->get_buffer()->set_text(std::string("Error: ") + e.what());
202 }
203 catch (const Gio::Error &e) {
204 log_textview->get_buffer()->set_text(std::string("Error: ") + e.what());
205 }
206 catch (...) {
207 log_textview->get_buffer()->set_text("Other error");
208 }
209 }
210
set_can_generate(bool v)211 void FabOutputWindow::set_can_generate(bool v)
212 {
213 can_export = v;
214 update_export_button();
215 }
216
update_export_button()217 void FabOutputWindow::update_export_button()
218 {
219 std::string txt;
220 if (can_export) {
221 if (settings.output_directory.size()) {
222 if (settings.prefix.size()) {
223 if (settings.drill_mode == FabOutputSettings::DrillMode::INDIVIDUAL) {
224 if (settings.drill_pth_filename.size() == 0 || settings.drill_npth_filename.size() == 0) {
225 txt = "drill filenames not set";
226 }
227 }
228 else {
229 if (settings.drill_pth_filename.size() == 0) {
230 txt = "drill filename not set";
231 }
232 }
233 if (txt.size() == 0) { // still okay
234 for (const auto &it : settings.layers) {
235 if (it.second.enabled && it.second.filename.size() == 0) {
236 txt = brd.get_layers().at(it.first).name + " filename not set";
237 break;
238 }
239 }
240 }
241 }
242 else {
243 txt = "prefix not set";
244 }
245 }
246 else {
247 txt = "output directory not set";
248 }
249 }
250 else {
251 txt = "tool is active";
252 }
253 widget_set_insensitive_tooltip(*generate_button, txt);
254 }
255
create(Gtk::Window * p,IDocumentBoard & c,const std::string & project_dir)256 FabOutputWindow *FabOutputWindow::create(Gtk::Window *p, IDocumentBoard &c, const std::string &project_dir)
257 {
258 FabOutputWindow *w;
259 Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
260 x->add_from_resource("/org/horizon-eda/horizon/imp/fab_output.ui");
261 x->get_widget_derived("window", w, c, project_dir);
262
263 w->set_transient_for(*p);
264
265 return w;
266 }
267 } // namespace horizon
268