1 #include "BedShapeDialog.hpp"
2 #include "GUI_App.hpp"
3 #include "OptionsGroup.hpp"
4
5 #include <wx/wx.h>
6 #include <wx/numformatter.h>
7 #include <wx/sizer.h>
8 #include <wx/statbox.h>
9 #include <wx/tooltip.h>
10
11 #include "libslic3r/BoundingBox.hpp"
12 #include "libslic3r/Model.hpp"
13 #include "libslic3r/Polygon.hpp"
14
15 #include "boost/nowide/iostream.hpp"
16 #include <boost/algorithm/string/predicate.hpp>
17 #include <boost/filesystem.hpp>
18
19 #include <algorithm>
20
21 namespace Slic3r {
22 namespace GUI {
23
BedShape(const ConfigOptionPoints & points)24 BedShape::BedShape(const ConfigOptionPoints& points)
25 {
26 auto polygon = Polygon::new_scale(points.values);
27
28 // is this a rectangle ?
29 if (points.size() == 4) {
30 auto lines = polygon.lines();
31 if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
32 // okay, it's a rectangle
33 // find origin
34 coordf_t x_min, x_max, y_min, y_max;
35 x_max = x_min = points.values[0](0);
36 y_max = y_min = points.values[0](1);
37 for (auto pt : points.values)
38 {
39 x_min = std::min(x_min, pt(0));
40 x_max = std::max(x_max, pt(0));
41 y_min = std::min(y_min, pt(1));
42 y_max = std::max(y_max, pt(1));
43 }
44
45 m_type = Type::Rectangular;
46 m_rectSize = Vec2d(x_max - x_min, y_max - y_min);
47 m_rectOrigin = Vec2d(-x_min, -y_min);
48
49 return;
50 }
51 }
52
53 // is this a circle ?
54 {
55 // Analyze the array of points.Do they reside on a circle ?
56 auto center = polygon.bounding_box().center();
57 std::vector<double> vertex_distances;
58 double avg_dist = 0;
59 for (auto pt : polygon.points)
60 {
61 double distance = (pt - center).cast<double>().norm();
62 vertex_distances.push_back(distance);
63 avg_dist += distance;
64 }
65
66 avg_dist /= vertex_distances.size();
67 bool defined_value = true;
68 for (auto el : vertex_distances)
69 {
70 if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
71 defined_value = false;
72 break;
73 }
74 if (defined_value) {
75 // all vertices are equidistant to center
76 m_type = Type::Circular;
77 m_diameter = unscale<double>(avg_dist * 2);
78
79 return;
80 }
81 }
82
83 if (points.size() < 3)
84 return;
85
86 // This is a custom bed shape, use the polygon provided.
87 m_type = Type::Custom;
88 }
89
get_option_label(BedShape::Parameter param)90 static std::string get_option_label(BedShape::Parameter param)
91 {
92 switch (param) {
93 case BedShape::Parameter::RectSize : return L("Size");
94 case BedShape::Parameter::RectOrigin: return L("Origin");
95 case BedShape::Parameter::Diameter : return L("Diameter");
96 default: return "";
97 }
98 }
99
append_option_line(ConfigOptionsGroupShp optgroup,Parameter param)100 void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter param)
101 {
102 ConfigOptionDef def;
103
104 if (param == Parameter::RectSize) {
105 def.type = coPoints;
106 def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) });
107 def.min = 0;
108 def.max = 1200;
109 def.label = get_option_label(param);
110 def.tooltip = L("Size in X and Y of the rectangular plate.");
111
112 Option option(def, "rect_size");
113 optgroup->append_single_option_line(option);
114 }
115 else if (param == Parameter::RectOrigin) {
116 def.type = coPoints;
117 def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) });
118 def.min = -600;
119 def.max = 600;
120 def.label = get_option_label(param);
121 def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
122
123 Option option(def, "rect_origin");
124 optgroup->append_single_option_line(option);
125 }
126 else if (param == Parameter::Diameter) {
127 def.type = coFloat;
128 def.set_default_value(new ConfigOptionFloat(200));
129 def.sidetext = L("mm");
130 def.label = get_option_label(param);
131 def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
132
133 Option option(def, "diameter");
134 optgroup->append_single_option_line(option);
135 }
136 }
137
get_name(Type type)138 wxString BedShape::get_name(Type type)
139 {
140 switch (type) {
141 case Type::Rectangular : return _L("Rectangular");
142 case Type::Circular : return _L("Circular");
143 case Type::Custom : return _L("Custom");
144 case Type::Invalid :
145 default : return _L("Invalid");
146 }
147 }
148
get_type()149 size_t BedShape::get_type()
150 {
151 return static_cast<size_t>(m_type == Type::Invalid ? Type::Rectangular : m_type);
152 }
153
get_full_name_with_params()154 wxString BedShape::get_full_name_with_params()
155 {
156 wxString out = _L("Shape") + ": " + get_name(m_type);
157
158 if (m_type == Type::Rectangular) {
159 out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(m_rectSize).serialize() + "]";
160 out += "\n" + _(get_option_label(Parameter::RectOrigin))+ ": [" + ConfigOptionPoint(m_rectOrigin).serialize() + "]";
161 }
162 else if (m_type == Type::Circular)
163 out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(m_diameter) + "]";
164
165 return out;
166 }
167
apply_optgroup_values(ConfigOptionsGroupShp optgroup)168 void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup)
169 {
170 if (m_type == Type::Rectangular || m_type == Type::Invalid) {
171 optgroup->set_value("rect_size" , new ConfigOptionPoints{ m_rectSize });
172 optgroup->set_value("rect_origin" , new ConfigOptionPoints{ m_rectOrigin });
173 }
174 else if (m_type == Type::Circular)
175 optgroup->set_value("diameter", double_to_string(m_diameter));
176 }
177
build_dialog(const ConfigOptionPoints & default_pt,const ConfigOptionString & custom_texture,const ConfigOptionString & custom_model)178 void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
179 {
180 SetFont(wxGetApp().normal_font());
181 m_panel = new BedShapePanel(this);
182 m_panel->build_panel(default_pt, custom_texture, custom_model);
183
184 auto main_sizer = new wxBoxSizer(wxVERTICAL);
185 main_sizer->Add(m_panel, 1, wxEXPAND);
186 main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
187
188 SetSizer(main_sizer);
189 SetMinSize(GetSize());
190 main_sizer->SetSizeHints(this);
191
192 this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) {
193 EndModal(wxID_CANCEL);
194 }));
195 }
196
on_dpi_changed(const wxRect & suggested_rect)197 void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect)
198 {
199 const int& em = em_unit();
200 m_panel->m_shape_options_book->SetMinSize(wxSize(25 * em, -1));
201
202 for (auto og : m_panel->m_optgroups)
203 og->msw_rescale();
204
205 const wxSize& size = wxSize(50 * em, -1);
206
207 SetMinSize(size);
208 SetSize(size);
209
210 Refresh();
211 }
212
213 const std::string BedShapePanel::NONE = "None";
214 const std::string BedShapePanel::EMPTY_STRING = "";
215
build_panel(const ConfigOptionPoints & default_pt,const ConfigOptionString & custom_texture,const ConfigOptionString & custom_model)216 void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
217 {
218 m_shape = default_pt.values;
219 m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value;
220 m_custom_model = custom_model.value.empty() ? NONE : custom_model.value;
221
222 auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape")));
223 sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font());
224
225 // shape options
226 m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP);
227 sbsizer->Add(m_shape_options_book);
228
229 auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular));
230 BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize);
231 BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin);
232 activate_options_page(optgroup);
233
234 optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular));
235 BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter);
236 activate_options_page(optgroup);
237
238 optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom));
239
240 Line line{ "", "" };
241 line.full_width = 1;
242 line.widget = [this](wxWindow* parent) {
243 wxButton* shape_btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL..."));
244 wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
245 shape_sizer->Add(shape_btn, 1, wxEXPAND);
246
247 wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
248 sizer->Add(shape_sizer, 1, wxEXPAND);
249
250 shape_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
251 load_stl();
252 });
253
254 return sizer;
255 };
256 optgroup->append_line(line);
257 activate_options_page(optgroup);
258
259 wxPanel* texture_panel = init_texture_panel();
260 wxPanel* model_panel = init_model_panel();
261
262 Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e)
263 {
264 update_shape();
265 }));
266
267 // right pane with preview canvas
268 m_canvas = new Bed_2D(this);
269 m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); });
270 m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); });
271
272 wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL);
273 left_sizer->Add(sbsizer, 0, wxEXPAND);
274 left_sizer->Add(texture_panel, 1, wxEXPAND);
275 left_sizer->Add(model_panel, 1, wxEXPAND);
276
277 wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
278 top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10);
279 top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10);
280
281 SetSizerAndFit(top_sizer);
282
283 set_shape(default_pt);
284 update_preview();
285 }
286
287 // Called from the constructor.
288 // Create a panel for a rectangular / circular / custom bed shape.
init_shape_options_page(const wxString & title)289 ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title)
290 {
291 wxPanel* panel = new wxPanel(m_shape_options_book);
292 ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
293
294 optgroup->label_width = 10;
295 optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
296 update_shape();
297 };
298
299 m_optgroups.push_back(optgroup);
300 // panel->SetSizerAndFit(optgroup->sizer);
301 m_shape_options_book->AddPage(panel, title);
302
303 return optgroup;
304 }
305
activate_options_page(ConfigOptionsGroupShp options_group)306 void BedShapePanel::activate_options_page(ConfigOptionsGroupShp options_group)
307 {
308 options_group->activate();
309 options_group->parent()->SetSizerAndFit(options_group->sizer);
310 }
311
init_texture_panel()312 wxPanel* BedShapePanel::init_texture_panel()
313 {
314 wxPanel* panel = new wxPanel(this);
315 ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Texture")));
316
317 optgroup->label_width = 10;
318 optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
319 update_shape();
320 };
321
322 Line line{ "", "" };
323 line.full_width = 1;
324 line.widget = [this](wxWindow* parent) {
325 wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
326 wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
327 load_sizer->Add(load_btn, 1, wxEXPAND);
328
329 wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
330 wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL);
331 filename_sizer->Add(filename_lbl, 1, wxEXPAND);
332
333 wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
334 wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
335 remove_sizer->Add(remove_btn, 1, wxEXPAND);
336
337 wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
338 sizer->Add(filename_sizer, 1, wxEXPAND);
339 sizer->Add(load_sizer, 1, wxEXPAND);
340 sizer->Add(remove_sizer, 1, wxEXPAND);
341
342 load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
343 {
344 load_texture();
345 }));
346
347 remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
348 {
349 m_custom_texture = NONE;
350 update_shape();
351 }));
352
353 filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
354 {
355 e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string()));
356 wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
357 if (lbl != nullptr)
358 {
359 bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture);
360 lbl->SetForegroundColour(exists ? wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) : wxColor(*wxRED));
361
362 wxString tooltip_text = "";
363 if (m_custom_texture != NONE)
364 {
365 if (!exists)
366 tooltip_text += _(L("Not found:")) + " ";
367
368 tooltip_text += _(m_custom_texture);
369 }
370
371 wxToolTip* tooltip = lbl->GetToolTip();
372 if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
373 lbl->SetToolTip(tooltip_text);
374 }
375 }));
376
377 remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
378 {
379 e.Enable(m_custom_texture != NONE);
380 }));
381
382 return sizer;
383 };
384 optgroup->append_line(line);
385 optgroup->activate();
386
387 panel->SetSizerAndFit(optgroup->sizer);
388
389 return panel;
390 }
391
init_model_panel()392 wxPanel* BedShapePanel::init_model_panel()
393 {
394 wxPanel* panel = new wxPanel(this);
395 ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Model")));
396
397 optgroup->label_width = 10;
398 optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
399 update_shape();
400 };
401
402 Line line{ "", "" };
403 line.full_width = 1;
404 line.widget = [this](wxWindow* parent) {
405 wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
406 wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
407 load_sizer->Add(load_btn, 1, wxEXPAND);
408
409 wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
410 wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL);
411 filename_sizer->Add(filename_lbl, 1, wxEXPAND);
412
413 wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
414 wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
415 remove_sizer->Add(remove_btn, 1, wxEXPAND);
416
417 wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
418 sizer->Add(filename_sizer, 1, wxEXPAND);
419 sizer->Add(load_sizer, 1, wxEXPAND);
420 sizer->Add(remove_sizer, 1, wxEXPAND);
421
422 load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
423 {
424 load_model();
425 }));
426
427 remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
428 {
429 m_custom_model = NONE;
430 update_shape();
431 }));
432
433 filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
434 {
435 e.SetText(_(boost::filesystem::path(m_custom_model).filename().string()));
436 wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
437 if (lbl != nullptr)
438 {
439 bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model);
440 lbl->SetForegroundColour(exists ? wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) : wxColor(*wxRED));
441
442 wxString tooltip_text = "";
443 if (m_custom_model != NONE)
444 {
445 if (!exists)
446 tooltip_text += _(L("Not found:")) + " ";
447
448 tooltip_text += _(m_custom_model);
449 }
450
451 wxToolTip* tooltip = lbl->GetToolTip();
452 if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
453 lbl->SetToolTip(tooltip_text);
454 }
455 }));
456
457 remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
458 {
459 e.Enable(m_custom_model != NONE);
460 }));
461
462 return sizer;
463 };
464 optgroup->append_line(line);
465 optgroup->activate();
466
467 panel->SetSizerAndFit(optgroup->sizer);
468
469 return panel;
470 }
471
472 // Called from the constructor.
473 // Set the initial bed shape from a list of points.
474 // Deduce the bed shape type(rect, circle, custom)
475 // This routine shall be smart enough if the user messes up
476 // with the list of points in the ini file directly.
set_shape(const ConfigOptionPoints & points)477 void BedShapePanel::set_shape(const ConfigOptionPoints& points)
478 {
479 BedShape shape(points);
480
481 m_shape_options_book->SetSelection(shape.get_type());
482 shape.apply_optgroup_values(m_optgroups[shape.get_type()]);
483
484 // Copy the polygon to the canvas, make a copy of the array, if custom shape is selected
485 if (shape.is_custom())
486 m_loaded_shape = points.values;
487
488 update_shape();
489
490 return;
491 }
492
update_preview()493 void BedShapePanel::update_preview()
494 {
495 if (m_canvas) m_canvas->Refresh();
496 Refresh();
497 }
498
499 // Update the bed shape from the dialog fields.
update_shape()500 void BedShapePanel::update_shape()
501 {
502 auto page_idx = m_shape_options_book->GetSelection();
503 auto opt_group = m_optgroups[page_idx];
504
505 BedShape::Type page_type = static_cast<BedShape::Type>(page_idx);
506
507 if (page_type == BedShape::Type::Rectangular) {
508 Vec2d rect_size(Vec2d::Zero());
509 Vec2d rect_origin(Vec2d::Zero());
510
511 try { rect_size = boost::any_cast<Vec2d>(opt_group->get_value("rect_size")); }
512 catch (const std::exception& /* e */) { return; }
513
514 try { rect_origin = boost::any_cast<Vec2d>(opt_group->get_value("rect_origin")); }
515 catch (const std::exception & /* e */) { return; }
516
517 auto x = rect_size(0);
518 auto y = rect_size(1);
519 // empty strings or '-' or other things
520 if (x == 0 || y == 0) return;
521 double x0 = 0.0;
522 double y0 = 0.0;
523 double x1 = x;
524 double y1 = y;
525
526 auto dx = rect_origin(0);
527 auto dy = rect_origin(1);
528
529 x0 -= dx;
530 x1 -= dx;
531 y0 -= dy;
532 y1 -= dy;
533 m_shape = { Vec2d(x0, y0),
534 Vec2d(x1, y0),
535 Vec2d(x1, y1),
536 Vec2d(x0, y1) };
537 }
538 else if (page_type == BedShape::Type::Circular) {
539 double diameter;
540 try { diameter = boost::any_cast<double>(opt_group->get_value("diameter")); }
541 catch (const std::exception & /* e */) { return; }
542
543 if (diameter == 0.0) return ;
544 auto r = diameter / 2;
545 auto twopi = 2 * PI;
546 auto edges = 72;
547 std::vector<Vec2d> points;
548 for (int i = 1; i <= edges; ++i) {
549 auto angle = i * twopi / edges;
550 points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
551 }
552 m_shape = points;
553 }
554 else if (page_type == BedShape::Type::Custom)
555 m_shape = m_loaded_shape;
556
557 update_preview();
558 }
559
560 // Loads an stl file, projects it to the XY plane and calculates a polygon.
load_stl()561 void BedShapePanel::load_stl()
562 {
563 wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
564 if (dialog.ShowModal() != wxID_OK)
565 return;
566
567 std::string file_name = dialog.GetPath().ToUTF8().data();
568 if (!boost::algorithm::iends_with(file_name, ".stl"))
569 {
570 show_error(this, _(L("Invalid file format.")));
571 return;
572 }
573
574 wxBusyCursor wait;
575
576 Model model;
577 try {
578 model = Model::read_from_file(file_name);
579 }
580 catch (std::exception &) {
581 show_error(this, _(L("Error! Invalid model")));
582 return;
583 }
584
585 auto mesh = model.mesh();
586 auto expolygons = mesh.horizontal_projection();
587
588 if (expolygons.size() == 0) {
589 show_error(this, _(L("The selected file contains no geometry.")));
590 return;
591 }
592 if (expolygons.size() > 1) {
593 show_error(this, _(L("The selected file contains several disjoint areas. This is not supported.")));
594 return;
595 }
596
597 auto polygon = expolygons[0].contour;
598 std::vector<Vec2d> points;
599 for (auto pt : polygon.points)
600 points.push_back(unscale(pt));
601
602 m_loaded_shape = points;
603 update_shape();
604 }
605
load_texture()606 void BedShapePanel::load_texture()
607 {
608 wxFileDialog dialog(this, _(L("Choose a file to import bed texture from (PNG/SVG):")), "", "",
609 file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
610
611 if (dialog.ShowModal() != wxID_OK)
612 return;
613
614 m_custom_texture = NONE;
615
616 std::string file_name = dialog.GetPath().ToUTF8().data();
617 if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg"))
618 {
619 show_error(this, _(L("Invalid file format.")));
620 return;
621 }
622
623 wxBusyCursor wait;
624
625 m_custom_texture = file_name;
626 update_shape();
627 }
628
load_model()629 void BedShapePanel::load_model()
630 {
631 wxFileDialog dialog(this, _(L("Choose an STL file to import bed model from:")), "", "",
632 file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
633
634 if (dialog.ShowModal() != wxID_OK)
635 return;
636
637 m_custom_model = NONE;
638
639 std::string file_name = dialog.GetPath().ToUTF8().data();
640 if (!boost::algorithm::iends_with(file_name, ".stl"))
641 {
642 show_error(this, _(L("Invalid file format.")));
643 return;
644 }
645
646 wxBusyCursor wait;
647
648 m_custom_model = file_name;
649 update_shape();
650 }
651
652 } // GUI
653 } // Slic3r
654