1 #include "model_editor.hpp"
2 #include "imp/imp_package.hpp"
3 #include <glm/gtx/transform.hpp>
4 #include "widgets/spin_button_angle.hpp"
5 #include "widgets/spin_button_dim.hpp"
6 #include "3d_view.hpp"
7 #include "canvas3d/canvas3d.hpp"
8 #include "place_model_box.hpp"
9 #include "util/gtk_util.hpp"
10 #include "util/geom_util.hpp"
11 
12 namespace horizon {
13 
mat_from_model(const Package::Model & model,double scale)14 glm::dmat4 mat_from_model(const Package::Model &model, double scale)
15 {
16     glm::dmat4 mat = glm::dmat4(1);
17     mat = glm::translate(mat, glm::dvec3(model.x, model.y, model.z) * scale);
18     mat = glm::rotate(mat, angle_to_rad(model.roll), glm::dvec3(-1, 0, 0));
19     mat = glm::rotate(mat, angle_to_rad(model.pitch), glm::dvec3(0, -1, 0));
20     mat = glm::rotate(mat, angle_to_rad(model.yaw), glm::dvec3(0, 0, -1));
21     return mat;
22 }
23 
24 
make_label(const std::string & text)25 static Gtk::Label *make_label(const std::string &text)
26 {
27     auto la = Gtk::manage(new Gtk::Label(text));
28     la->get_style_context()->add_class("dim-label");
29     la->set_halign(Gtk::ALIGN_END);
30     return la;
31 }
32 
make_current()33 void ModelEditor::make_current()
34 {
35     if (imp.current_model != uu) {
36         imp.current_model = uu;
37         imp.view_3d_window->update();
38     }
39     imp.update_model_editors();
40 }
41 
ModelEditor(ImpPackage & iimp,const UUID & iuu)42 ModelEditor::ModelEditor(ImpPackage &iimp, const UUID &iuu)
43     : Gtk::Box(Gtk::ORIENTATION_VERTICAL, 5), uu(iuu), imp(iimp), model(imp.core_package.models.at(uu))
44 {
45     property_margin() = 10;
46     auto entry = Gtk::manage(new Gtk::Entry);
47     pack_start(*entry, false, false, 0);
48     entry->show();
49     entry->set_width_chars(45);
50     entry->signal_focus_in_event().connect([this](GdkEventFocus *ev) {
51         imp.current_model = uu;
52         imp.view_3d_window->update();
53         imp.update_model_editors();
54         return false;
55     });
56     bind_widget(entry, model.filename);
57     entry->signal_changed().connect([this] { s_signal_changed.emit(); });
58 
59     {
60         auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 5));
61         auto delete_button = Gtk::manage(new Gtk::Button);
62         delete_button->set_image_from_icon_name("list-remove-symbolic", Gtk::ICON_SIZE_BUTTON);
63         delete_button->signal_clicked().connect([this] {
64             imp.core_package.models.erase(uu);
65             if (imp.core_package.default_model == uu) {
66                 if (imp.core_package.models.size()) {
67                     imp.core_package.default_model = imp.core_package.models.begin()->first;
68                     imp.current_model = imp.core_package.default_model;
69                 }
70                 else {
71                     imp.core_package.default_model = UUID();
72                 }
73             }
74             s_signal_changed.emit();
75             imp.view_3d_window->update();
76             imp.update_model_editors();
77             delete this->get_parent();
78         });
79         box->pack_end(*delete_button, false, false, 0);
80 
81         auto browse_button = Gtk::manage(new Gtk::Button("Browse…"));
82         browse_button->signal_clicked().connect([this, entry] {
83             Package::Model *model2 = nullptr;
84             if (imp.core_package.models.count(uu)) {
85                 model2 = &imp.core_package.models.at(uu);
86             }
87             auto mfn = imp.ask_3d_model_filename(model2 ? model2->filename : "");
88             if (mfn.size()) {
89                 entry->set_text(mfn);
90                 imp.view_3d_window->update(true);
91             }
92         });
93         box->pack_end(*browse_button, false, false, 0);
94 
95         default_cb = Gtk::manage(new Gtk::CheckButton("Default"));
96         if (imp.core_package.default_model == uu) {
97             default_cb->set_active(true);
98         }
99         default_cb->signal_toggled().connect([this] {
100             if (default_cb->get_active()) {
101                 imp.core_package.default_model = uu;
102                 make_current();
103             }
104             else {
105                 imp.update_model_editors();
106             }
107             s_signal_changed.emit();
108         });
109         box->pack_start(*default_cb, false, false, 0);
110 
111         current_label = Gtk::manage(new Gtk::Label("Current"));
112         current_label->get_style_context()->add_class("dim-label");
113         current_label->set_no_show_all(true);
114         box->pack_start(*current_label, false, false, 0);
115 
116         box->show_all();
117         pack_start(*box, false, false, 0);
118     }
119 
120     {
121         auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 5));
122         {
123             auto place_button = Gtk::manage(new Gtk::MenuButton());
124             place_button->set_label("Place…");
125             place_button->signal_clicked().connect(sigc::mem_fun(*this, &ModelEditor::make_current));
126             auto place_menu = Gtk::manage(new Gtk::Menu);
127             place_button->set_menu(*place_menu);
128 
129             {
130                 auto it = Gtk::manage(new Gtk::MenuItem("Center"));
131                 it->show();
132                 place_menu->append(*it);
133                 it->signal_activate().connect([this] {
134                     const auto &filename = model.filename;
135                     auto bb = imp.view_3d_window->get_canvas().get_model_bbox(filename);
136                     origin_cb->set_active(false);
137                     for (unsigned int ax = 0; ax < 3; ax++) {
138                         sp_angle.get(ax)->set_value(0);
139                     }
140                     sp_shift.x->set_value((bb.xh + bb.xl) / -2 * 1e6);
141                     sp_shift.y->set_value((bb.yh + bb.yl) / -2 * 1e6);
142                     sp_shift.z->set_value((bb.zh + bb.zl) / -2 * 1e6);
143                     origin_cb->set_active(true);
144                 });
145                 widgets_insenstive_without_model.push_back(it);
146             }
147 
148             {
149                 auto it = Gtk::manage(new Gtk::MenuItem("Align…"));
150                 it->show();
151                 place_menu->append(*it);
152                 it->signal_activate().connect([this] {
153                     imp.view_3d_stack->set_visible_child("place");
154                     imp.place_model_box->init(model);
155                 });
156                 widgets_insenstive_without_model.push_back(it);
157             }
158 
159             {
160                 auto it = Gtk::manage(new Gtk::MenuItem("Round to 1 µm"));
161                 it->show();
162                 place_menu->append(*it);
163                 it->signal_activate().connect([this] {
164                     for (unsigned int ax = 0; ax < 3; ax++) {
165                         sp_shift.get(ax)->set_value(round_multiple(sp_shift.get(ax)->get_value_as_int(), 1000));
166                     }
167                 });
168             }
169 
170             {
171                 auto it = Gtk::manage(new Gtk::MenuItem("Reset"));
172                 it->show();
173                 place_menu->append(*it);
174                 it->signal_activate().connect([this] {
175                     origin_cb->set_active(false);
176                     for (unsigned int ax = 0; ax < 3; ax++) {
177                         sp_shift.get(ax)->set_value(0);
178                         sp_angle.get(ax)->set_value(0);
179                     }
180                 });
181             }
182 
183             box->pack_start(*place_button, false, false, 0);
184         }
185         {
186             auto project_button = Gtk::manage(new Gtk::Button("Project"));
187             project_button->signal_clicked().connect([this] { imp.project_model(model); });
188             box->pack_start(*project_button, false, false, 0);
189             widgets_insenstive_without_model.push_back(project_button);
190         }
191 
192         origin_cb = Gtk::manage(new Gtk::CheckButton("Rotate around package origin"));
193         box->pack_end(*origin_cb, false, false, 0);
194 
195         box->show_all();
196         pack_start(*box, false, false, 0);
197     }
198 
199     update_widgets_insenstive();
200     imp.view_3d_window->get_canvas().signal_models_loading().connect(
201             sigc::track_obj([this](auto a, auto b) { update_widgets_insenstive(); }, *this));
202 
203     auto placement_grid = Gtk::manage(new Gtk::Grid);
204     placement_grid->set_hexpand_set(true);
205     placement_grid->set_row_spacing(5);
206     placement_grid->set_column_spacing(5);
207     std::set<Gtk::SpinButton *> placement_spin_buttons;
208     static const std::array<std::string, 3> s_xyz = {"X", "Y", "Z"};
209     for (unsigned int ax = 0; ax < 3; ax++) {
210         placement_grid->attach(*make_label(s_xyz.at(ax)), 0, ax, 1, 1);
211         auto sp = Gtk::manage(new SpinButtonDim);
212         sp->set_range(-1000_mm, 1000_mm);
213         placement_grid->attach(*sp, 1, ax, 1, 1);
214         sp->set_value(model.get_shift(ax));
215         placement_spin_buttons.insert(sp);
216         sp_shift.set(ax, sp);
217     }
218 
219     static const std::array<std::string, 3> s_rpy = {"Roll", "Pitch", "Yaw"};
220 
221     for (unsigned int ax = 0; ax < 3; ax++) {
222         auto la = make_label(s_rpy.at(ax));
223         la->set_hexpand(true);
224         placement_grid->attach(*la, 2, ax, 1, 1);
225         auto sp = Gtk::manage(new SpinButtonAngle);
226         placement_grid->attach(*sp, 3, ax, 1, 1);
227         sp->set_value(model.get_rotation(ax));
228         placement_spin_buttons.insert(sp);
229         sp_angle.set(ax, sp);
230     }
231 
232     for (auto sp : placement_spin_buttons) {
233         auto conn = sp->signal_value_changed().connect([this, sp] {
234             if (sp == sp_shift.x || sp == sp_shift.y || sp == sp_shift.z) {
235                 for (unsigned int ax = 0; ax < 3; ax++) {
236                     model.set_shift(ax, sp_shift.get(ax)->get_value_as_int());
237                 }
238             }
239             else {
240                 const auto oldmat = mat_from_model(model);
241                 const auto p = glm::inverse(oldmat) * glm::dvec4(0, 0, 0, 1);
242 
243                 for (unsigned int ax = 0; ax < 3; ax++) {
244                     model.set_rotation(ax, sp_angle.get(ax)->get_value_as_int());
245                 }
246 
247                 if (origin_cb->get_active()) {
248                     const auto newmat = mat_from_model(model);
249                     const auto delta = newmat * p;
250 
251                     for (auto &cn : sp_connections) {
252                         cn.block();
253                     }
254                     for (unsigned int ax = 0; ax < 3; ax++) {
255                         sp_shift.get(ax)->set_value(model.get_shift(ax) - delta[ax]);
256                         model.set_shift(ax, sp_shift.get(ax)->get_value_as_int());
257                     }
258                     for (auto &cn : sp_connections) {
259                         cn.unblock();
260                     }
261                 }
262             }
263 
264             imp.update_fake_board();
265             imp.view_3d_window->get_canvas().update_packages();
266             s_signal_changed.emit();
267         });
268         sp_connections.push_back(conn);
269     }
270 
271     placement_grid->show_all();
272     pack_start(*placement_grid, false, false, 0);
273 
274     imp.update_model_editors();
275 }
276 
update_widgets_insenstive()277 void ModelEditor::update_widgets_insenstive()
278 {
279     const auto has_model = imp.view_3d_window->get_canvas().model_is_loaded(model.filename);
280     for (auto w : widgets_insenstive_without_model) {
281         w->set_sensitive(has_model);
282     }
283 }
284 
set_is_current(const UUID & iuu)285 void ModelEditor::set_is_current(const UUID &iuu)
286 {
287     current_label->set_visible(iuu == uu);
288 }
289 
290 
set_is_default(const UUID & iuu)291 void ModelEditor::set_is_default(const UUID &iuu)
292 {
293     default_cb->set_active(iuu == uu);
294 }
295 
reload()296 void ModelEditor::reload()
297 {
298     for (auto &cn : sp_connections) {
299         cn.block();
300     }
301     for (unsigned int ax = 0; ax < 3; ax++) {
302         sp_shift.get(ax)->set_value(model.get_shift(ax));
303     }
304     for (auto &cn : sp_connections) {
305         cn.unblock();
306     }
307 }
308 
309 } // namespace horizon
310