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