diff --git a/resources/icons/thumb_left.svg b/resources/icons/thumb_left.svg
new file mode 100644
index 000000000..ef78bd141
--- /dev/null
+++ b/resources/icons/thumb_left.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/resources/icons/thumb_right.svg b/resources/icons/thumb_right.svg
new file mode 100644
index 000000000..f3748525d
--- /dev/null
+++ b/resources/icons/thumb_right.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 90c9d0357..9f0d515b3 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -450,6 +450,31 @@ void Model::convert_multipart_object(unsigned int max_extruders)
this->objects.push_back(object);
}
+bool Model::looks_like_imperial_units() const
+{
+ if (this->objects.size() == 0)
+ return false;
+
+ stl_vertex size = this->objects[0]->get_object_stl_stats().size;
+
+ for (ModelObject* o : this->objects) {
+ auto sz = o->get_object_stl_stats().size;
+
+ if (size[0] < sz[0]) size[0] = sz[0];
+ if (size[1] < sz[1]) size[1] = sz[1];
+ if (size[2] < sz[2]) size[2] = sz[2];
+ }
+
+ return (size[0] < 3 && size[1] < 3 && size[2] < 3);
+}
+
+void Model::convert_from_imperial_units()
+{
+ double in_to_mm = 25.4;
+ for (ModelObject* o : this->objects)
+ o->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
+}
+
void Model::adjust_min_z()
{
if (objects.empty())
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 7b56030c7..07bd86aea 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -851,6 +851,8 @@ public:
bool looks_like_multipart_object() const;
void convert_multipart_object(unsigned int max_extruders);
+ bool looks_like_imperial_units() const;
+ void convert_from_imperial_units();
// Ensures that the min z of the model is not negative
void adjust_min_z();
diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp
index a6c9a5c77..617368081 100644
--- a/src/slic3r/GUI/DoubleSlider.cpp
+++ b/src/slic3r/GUI/DoubleSlider.cpp
@@ -64,8 +64,13 @@ Control::Control( wxWindow *parent,
if (!is_osx)
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
+#if ENABLE_GCODE_VIEWER
+ m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up"));
+ m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left") : ScalableBitmap(this, "thumb_down"));
+#else
m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up"));
m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down"));
+#endif // ENABLE_GCODE_VIEWER
m_thumb_size = m_bmp_thumb_lower.GetBmpSize();
m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add");
@@ -576,6 +581,10 @@ void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider
void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection)
{
+#if ENABLE_GCODE_VIEWER
+ wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x);
+ wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y);
+#else
wxCoord x_draw, y_draw;
if (selection == ssLower) {
if (is_horizontal()) {
@@ -587,7 +596,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider
y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
- else{
+ else {
if (is_horizontal()) {
x_draw = pos.x;
y_draw = pos.y - int(0.5*m_thumb_size.y);
@@ -597,6 +606,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider
y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
+#endif // ENABLE_GCODE_VIEWER
dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw);
// Update thumb rect
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index ea23f8daa..411b6aeec 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -613,7 +613,7 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const
const float icon_sc = m_em_unit * 0.1f;
#endif // __APPLE__
- int int_val = std::min(int(scale / icon_sc * 100), 100);
+ long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100);
std::string val = std::to_string(int_val);
app_config->set("auto_toolbar_size", val);
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
index 36293525a..471369a00 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -17,6 +17,8 @@ namespace Slic3r
namespace GUI
{
+const double ObjectManipulation::in_to_mm = 25.4;
+const double ObjectManipulation::mm_to_in = 0.0393700787;
// Helper function to be used by drop to bed button. Returns lowest point of this
// volume in world coordinate system.
@@ -121,6 +123,8 @@ static void set_font_and_background_style(wxWindow* win, const wxFont& font)
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
OG_Settings(parent, true)
{
+ m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
+
m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation");
// Load bitmaps to be used for the mirroring buttons:
@@ -314,15 +318,15 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
};
// add Units
- auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit)
+ auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit, wxStaticText** unit_text)
{
- wxStaticText* unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
- set_font_and_background_style(unit_text, wxGetApp().normal_font());
+ *unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
+ set_font_and_background_style(*unit_text, wxGetApp().normal_font());
// Unit text should be the same height as labels
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->SetMinSize(wxSize(-1, height));
- sizer->Add(unit_text, 0, wxALIGN_CENTER_VERTICAL);
+ sizer->Add(*unit_text, 0, wxALIGN_CENTER_VERTICAL);
editors_grid_sizer->Add(sizer);
m_rescalable_sizers.push_back(sizer);
@@ -330,7 +334,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("position", axis_idx);
- add_unit_text(L("mm"));
+ add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_position_unit);
// Add drop to bed button
m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
@@ -356,7 +360,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("rotation", axis_idx);
- add_unit_text("°");
+ wxStaticText* rotation_unit{ nullptr };
+ add_unit_text("°", &rotation_unit);
// Add reset rotation button
m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
@@ -390,7 +395,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("scale", axis_idx);
- add_unit_text("%");
+ wxStaticText* scale_unit{ nullptr };
+ add_unit_text("%", &scale_unit);
// Add reset scale button
m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
@@ -405,11 +411,20 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("size", axis_idx);
- add_unit_text("mm");
+ add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_size_unit);
editors_grid_sizer->AddStretchSpacer(1);
m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND);
+ m_check_inch = new wxCheckBox(parent, wxID_ANY, "Inches");
+ m_check_inch->SetValue(m_imperial_units);
+ m_check_inch->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
+ wxGetApp().app_config->set("use_inches", m_check_inch->GetValue() ? "1" : "0");
+ wxGetApp().sidebar().update_ui_from_settings();
+ });
+
+ m_main_grid_sizer->Add(m_check_inch, 1, wxEXPAND);
+
m_og->sizer->Clear(true);
m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border);
}
@@ -452,6 +467,32 @@ void ObjectManipulation::UpdateAndShow(const bool show)
OG_Settings::UpdateAndShow(show);
}
+void ObjectManipulation::update_ui_from_settings()
+{
+ if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) {
+ m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
+
+ auto update_unit_text = [](const wxString& new_unit_text, wxStaticText* widget) {
+ widget->SetLabel(new_unit_text);
+ if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font());
+ };
+ update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_position_unit);
+ update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_size_unit);
+
+ for (int i = 0; i < 3; ++i) {
+ auto update = [this, i](/*ManipulationEditorKey*/int key_id, const Vec3d& new_value) {
+ wxString new_text = double_to_string(m_imperial_units ? new_value(i) * mm_to_in : new_value(i), 2);
+ const int id = key_id * 3 + i;
+ if (id >= 0) m_editors[id]->set_value(new_text);
+ };
+ update(0/*mePosition*/, m_new_position);
+ update(3/*meSize*/, m_new_size);
+ }
+ }
+
+ m_check_inch->SetValue(m_imperial_units);
+}
+
void ObjectManipulation::update_settings_value(const Selection& selection)
{
m_new_move_label_string = L("Position");
@@ -562,6 +603,8 @@ void ObjectManipulation::update_if_dirty()
if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
cached_rounded(i) = new_rounded;
const int id = key_id*3+i;
+ if (m_imperial_units && (key_id == mePosition || key_id == meSize))
+ new_text = double_to_string(new_value(i)*mm_to_in, 2);
if (id >= 0) m_editors[id]->set_value(new_text);
}
cached(i) = new_value(i);
@@ -851,6 +894,9 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double
if (!m_cache.is_valid())
return;
+ if (m_imperial_units && (opt_key == "position" || opt_key == "size"))
+ new_value *= in_to_mm;
+
if (opt_key == "position")
change_position_value(axis, new_value);
else if (opt_key == "rotation")
@@ -929,6 +975,9 @@ void ObjectManipulation::msw_rescale()
for (ManipulationEditor* editor : m_editors)
editor->msw_rescale();
+ // rescale "inches" checkbox
+ m_check_inch->SetMinSize(wxSize(-1, int(1.5f * m_check_inch->GetFont().GetPixelSize().y + 0.5f)));
+
get_og()->msw_rescale();
}
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
index e6f99ab2a..64a59ac9b 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
@@ -10,6 +10,7 @@ class wxBitmapComboBox;
class wxStaticText;
class LockButton;
class wxStaticBitmap;
+class wxCheckBox;
namespace Slic3r {
namespace GUI {
@@ -41,6 +42,11 @@ private:
class ObjectManipulation : public OG_Settings
{
+public:
+ static const double in_to_mm;
+ static const double mm_to_in;
+
+private:
struct Cache
{
Vec3d position;
@@ -76,6 +82,10 @@ class ObjectManipulation : public OG_Settings
wxStaticText* m_scale_Label = nullptr;
wxStaticText* m_rotate_Label = nullptr;
+ bool m_imperial_units { false };
+ wxStaticText* m_position_unit { nullptr };
+ wxStaticText* m_size_unit { nullptr };
+
wxStaticText* m_item_name = nullptr;
wxStaticText* m_empty_str = nullptr;
@@ -84,6 +94,8 @@ class ObjectManipulation : public OG_Settings
ScalableButton* m_reset_rotation_button = nullptr;
ScalableButton* m_drop_to_bed_button = nullptr;
+ wxCheckBox* m_check_inch {nullptr};
+
// Mirroring buttons and their current state
enum MirrorButtonState {
mbHidden,
@@ -138,6 +150,7 @@ public:
void Show(const bool show) override;
bool IsShown() override;
void UpdateAndShow(const bool show) override;
+ void update_ui_from_settings();
void set_dirty() { m_dirty = true; }
// Called from the App to update the UI if dirty.
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index bf694b445..1784dbccc 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -184,8 +184,8 @@ Preview::Preview(
: m_canvas_widget(nullptr)
, m_canvas(nullptr)
#if ENABLE_GCODE_VIEWER
- , m_bottom_toolbar_sizer(nullptr)
, m_layers_slider_sizer(nullptr)
+ , m_bottom_toolbar_panel(nullptr)
#else
, m_double_slider_sizer(nullptr)
#endif // ENABLE_GCODE_VIEWER
@@ -194,6 +194,7 @@ Preview::Preview(
, m_label_show(nullptr)
, m_combochecklist_features(nullptr)
#if ENABLE_GCODE_VIEWER
+ , m_combochecklist_features_pos(0)
, m_combochecklist_options(nullptr)
#else
, m_checkbox_travel(nullptr)
@@ -251,14 +252,20 @@ bool Preview::init(wxWindow* parent, Model* model)
#if ENABLE_GCODE_VIEWER
m_layers_slider_sizer = create_layers_slider_sizer();
+
+ m_bottom_toolbar_panel = new wxPanel(this);
+
+ m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View"));
+
+ m_choice_view_type = new wxChoice(m_bottom_toolbar_panel, wxID_ANY);
#else
m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL);
create_double_slider();
-#endif // ENABLE_GCODE_VIEWER
m_label_view_type = new wxStaticText(this, wxID_ANY, _L("View"));
m_choice_view_type = new wxChoice(this, wxID_ANY);
+#endif // ENABLE_GCODE_VIEWER
m_choice_view_type->Append(_L("Feature type"));
m_choice_view_type->Append(_L("Height"));
m_choice_view_type->Append(_L("Width"));
@@ -269,10 +276,18 @@ bool Preview::init(wxWindow* parent, Model* model)
m_choice_view_type->Append(_L("Color Print"));
m_choice_view_type->SetSelection(0);
+#if ENABLE_GCODE_VIEWER
+ m_label_show = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("Show"));
+#else
m_label_show = new wxStaticText(this, wxID_ANY, _L("Show"));
+#endif // ENABLE_GCODE_VIEWER
m_combochecklist_features = new wxComboCtrl();
+#if ENABLE_GCODE_VIEWER
+ m_combochecklist_features->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
+#else
m_combochecklist_features->Create(this, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
+#endif // ENABLE_GCODE_VIEWER
std::string feature_items = GUI::into_u8(
#if ENABLE_GCODE_VIEWER
_L("Unknown") + "|1|" +
@@ -296,7 +311,7 @@ bool Preview::init(wxWindow* parent, Model* model)
#if ENABLE_GCODE_VIEWER
m_combochecklist_options = new wxComboCtrl();
- m_combochecklist_options->Create(this, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
+ m_combochecklist_options->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
std::string options_items = GUI::into_u8(
get_option_type_string(OptionType::Travel) + "|0|" +
get_option_type_string(OptionType::Retractions) + "|0|" +
@@ -328,20 +343,23 @@ bool Preview::init(wxWindow* parent, Model* model)
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
- m_moves_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL);
+ m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 4 * GetTextExtent("m").y), wxSL_HORIZONTAL);
m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView);
- m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
- m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL);
- m_bottom_toolbar_sizer->AddSpacer(5);
- m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
- m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0);
- m_bottom_toolbar_sizer->AddSpacer(5);
- m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
- m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0);
- m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
- m_bottom_toolbar_sizer->AddSpacer(5);
- m_bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 5);
+ wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL);
+ bottom_toolbar_sizer->AddSpacer(5);
+ bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
+ bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0);
+ bottom_toolbar_sizer->AddSpacer(5);
+ bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
+ bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0);
+ // change the following number if editing the layout of the bottom toolbar sizer. It is used into update_bottom_toolbar()
+ m_combochecklist_features_pos = 6;
+ bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
+ bottom_toolbar_sizer->Hide(m_combochecklist_features);
+ bottom_toolbar_sizer->AddSpacer(5);
+ bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0);
+ m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer);
#else
wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
@@ -364,8 +382,8 @@ bool Preview::init(wxWindow* parent, Model* model)
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0);
#if ENABLE_GCODE_VIEWER
- main_sizer->Add(m_bottom_toolbar_sizer, 0, wxALL | wxEXPAND, 0);
- main_sizer->Hide(m_bottom_toolbar_sizer);
+ main_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0);
+ main_sizer->Hide(m_bottom_toolbar_panel);
#else
main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0);
#endif // ENABLE_GCODE_VIEWER
@@ -565,6 +583,7 @@ void Preview::bind_event_handlers()
m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
#if ENABLE_GCODE_VIEWER
m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this);
+ m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
#else
m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
@@ -581,6 +600,7 @@ void Preview::unbind_event_handlers()
m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
#if ENABLE_GCODE_VIEWER
m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this);
+ m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
#else
m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
@@ -773,10 +793,33 @@ void Preview::update_bottom_toolbar()
combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags());
combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags());
- m_bottom_toolbar_sizer->Show(m_combochecklist_features,
- !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType);
- m_bottom_toolbar_sizer->Layout();
- Refresh();
+ // updates visibility of features combobox
+ if (m_bottom_toolbar_panel->IsShown())
+ {
+ wxSizer* sizer = m_bottom_toolbar_panel->GetSizer();
+ bool show = !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType;
+
+ if (show)
+ {
+ if (sizer->GetItem(m_combochecklist_features) == nullptr)
+ {
+ sizer->Insert(m_combochecklist_features_pos, m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
+ sizer->Show(m_combochecklist_features);
+ sizer->Layout();
+ Refresh();
+ }
+ }
+ else
+ {
+ if (sizer->GetItem(m_combochecklist_features) != nullptr)
+ {
+ sizer->Hide(m_combochecklist_features);
+ sizer->Detach(m_combochecklist_features);
+ sizer->Layout();
+ Refresh();
+ }
+ }
+ }
}
#endif // ENABLE_GCODE_VIEWER
@@ -1243,8 +1286,9 @@ void Preview::load_print_as_fff(bool keep_z_range)
#if ENABLE_GCODE_VIEWER
m_canvas->load_gcode_preview(*m_gcode_result);
m_canvas->refresh_gcode_preview(*m_gcode_result, colors);
- GetSizer()->Show(m_bottom_toolbar_sizer);
+ GetSizer()->Show(m_bottom_toolbar_panel);
GetSizer()->Layout();
+ Refresh();
zs = m_canvas->get_gcode_layers_zs();
#else
m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
@@ -1254,8 +1298,9 @@ void Preview::load_print_as_fff(bool keep_z_range)
// Load the initial preview based on slices, not the final G-code.
m_canvas->load_preview(colors, color_print_values);
#if ENABLE_GCODE_VIEWER
- GetSizer()->Hide(m_bottom_toolbar_sizer);
+ GetSizer()->Hide(m_bottom_toolbar_panel);
GetSizer()->Layout();
+ Refresh();
zs = m_canvas->get_volumes_print_zs(true);
#endif // ENABLE_GCODE_VIEWER
}
@@ -1316,8 +1361,9 @@ void Preview::load_print_as_sla()
{
m_canvas->load_sla_preview();
#if ENABLE_GCODE_VIEWER
- GetSizer()->Hide(m_bottom_toolbar_sizer);
+ GetSizer()->Hide(m_bottom_toolbar_panel);
GetSizer()->Layout();
+ Refresh();
#else
show_hide_ui_elements("none");
#endif // ENABLE_GCODE_VIEWER
diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp
index 9cf694ece..64aa8f7e3 100644
--- a/src/slic3r/GUI/GUI_Preview.hpp
+++ b/src/slic3r/GUI/GUI_Preview.hpp
@@ -84,7 +84,7 @@ class Preview : public wxPanel
GLCanvas3D* m_canvas;
#if ENABLE_GCODE_VIEWER
wxBoxSizer* m_layers_slider_sizer;
- wxBoxSizer* m_bottom_toolbar_sizer;
+ wxPanel* m_bottom_toolbar_panel;
#else
wxBoxSizer* m_double_slider_sizer;
#endif // ENABLE_GCODE_VIEWER
@@ -93,6 +93,7 @@ class Preview : public wxPanel
wxStaticText* m_label_show;
wxComboCtrl* m_combochecklist_features;
#if ENABLE_GCODE_VIEWER
+ size_t m_combochecklist_features_pos;
wxComboCtrl* m_combochecklist_options;
#else
wxCheckBox* m_checkbox_travel;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index b85fe57f0..41361eaa9 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -15,6 +15,8 @@
namespace Slic3r {
namespace GUI {
+static constexpr size_t MaxVertexBuffers = 50;
+
GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_quadric(nullptr)
@@ -123,10 +125,9 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
// Now render both enforcers and blockers.
for (int i=0; i<2; ++i) {
- if (m_ivas[mesh_id][i].has_VBOs()) {
- glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f));
- m_ivas[mesh_id][i].render();
- }
+ glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f));
+ for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i])
+ iva.render();
}
glsafe(::glPopMatrix());
}
@@ -205,8 +206,14 @@ void GLGizmoFdmSupports::update_from_model_object()
++num_of_volumes;
m_selected_facets.resize(num_of_volumes);
m_neighbors.resize(num_of_volumes);
+
m_ivas.clear();
m_ivas.resize(num_of_volumes);
+ for (size_t i=0; ivolumes) {
@@ -226,7 +233,8 @@ void GLGizmoFdmSupports::update_from_model_object()
for (int i : list)
m_selected_facets[volume_id][i] = type;
}
- update_vertex_buffers(mv, volume_id, true, true);
+ update_vertex_buffers(mesh, volume_id, FacetSupportType::ENFORCER);
+ update_vertex_buffers(mesh, volume_id, FacetSupportType::BLOCKER);
m_neighbors[volume_id].resize(3 * mesh->its.indices.size());
@@ -325,7 +333,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
Vec3f closest_hit = Vec3f::Zero();
double closest_hit_squared_distance = std::numeric_limits::max();
size_t closest_facet = 0;
- size_t closest_hit_mesh_id = size_t(-1);
+ int closest_hit_mesh_id = -1;
// Transformations of individual meshes
std::vector trafo_matrices;
@@ -368,17 +376,22 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
// We now know where the ray hit, let's save it and cast another ray
if (closest_hit_mesh_id != size_t(-1)) // only if there is at least one hit
- hit_positions_and_facet_ids[closest_hit_mesh_id].emplace_back(closest_hit, closest_facet);
+ some_mesh_was_hit = true;
+ if (some_mesh_was_hit) {
+ // Now propagate the hits
+ mesh_id = -1;
+ const TriangleMesh* mesh = nullptr;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+ ++mesh_id;
+ if (mesh_id == closest_hit_mesh_id) {
+ mesh = &mv->mesh();
+ break;
+ }
+ }
- // Now propagate the hits
- mesh_id = -1;
- for (const ModelVolume* mv : mo->volumes) {
-
- if (! mv->is_model_part())
- continue;
-
- ++mesh_id;
bool update_both = false;
const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
@@ -389,89 +402,96 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
const float limit = pow(m_cursor_radius/avg_scaling , 2.f);
- // For all hits on this mesh...
- for (const std::pair& hit_and_facet : hit_positions_and_facet_ids[mesh_id]) {
- some_mesh_was_hit = true;
- const TriangleMesh* mesh = &mv->mesh();
- std::vector& neighbors = m_neighbors[mesh_id];
+ const std::pair& hit_and_facet = { closest_hit, closest_facet };
- // Calculate direction from camera to the hit (in mesh coords):
- Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - hit_and_facet.first).normalized();
+ const std::vector& neighbors = m_neighbors[mesh_id];
- // A lambda to calculate distance from the centerline:
- auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f point) -> float {
- Vec3f diff = hit_and_facet.first - point;
- return (diff - diff.dot(dir) * dir).squaredNorm();
- };
+ // Calculate direction from camera to the hit (in mesh coords):
+ Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - hit_and_facet.first).normalized();
- // A lambda to determine whether this facet is potentionally visible (still can be obscured)
- auto faces_camera = [&dir](const ModelVolume* mv, const size_t& facet) -> bool {
- return (mv->mesh().stl.facet_start[facet].normal.dot(dir) > 0.);
- };
- // Now start with the facet the pointer points to and check all adjacent facets. neighbors vector stores
- // pairs of vertex_idx - facet_idx and is sorted with respect to the former. Neighboring facet index can be
- // quickly found by finding a vertex in the list and read the respective facet ids.
- std::vector facets_to_select{hit_and_facet.second};
- NeighborData vertex = std::make_pair(0, 0);
- std::vector visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed
- size_t facet_idx = 0; // index into facets_to_select
- auto it = neighbors.end();
- while (facet_idx < facets_to_select.size()) {
- size_t facet = facets_to_select[facet_idx];
- if (! visited[facet]) {
- // check all three vertices and in case they're close enough, find the remaining facets
- // and add them to the list to be proccessed later
- for (size_t i=0; i<3; ++i) {
- vertex.first = mesh->its.indices[facet](i); // vertex index
- float dist = squared_distance_from_line(mesh->its.vertices[vertex.first]);
- if (dist < limit) {
- it = std::lower_bound(neighbors.begin(), neighbors.end(), vertex);
- while (it != neighbors.end() && it->first == vertex.first) {
- if (it->second != facet && faces_camera(mv, it->second))
- facets_to_select.push_back(it->second);
- ++it;
- }
+ // A lambda to calculate distance from the centerline:
+ auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f& point) -> float {
+ Vec3f diff = hit_and_facet.first - point;
+ return (diff - diff.dot(dir) * dir).squaredNorm();
+ };
+
+ // A lambda to determine whether this facet is potentionally visible (still can be obscured)
+ auto faces_camera = [&dir, &mesh](const size_t& facet) -> bool {
+ return (mesh->stl.facet_start[facet].normal.dot(dir) > 0.);
+ };
+ // Now start with the facet the pointer points to and check all adjacent facets. neighbors vector stores
+ // pairs of vertex_idx - facet_idx and is sorted with respect to the former. Neighboring facet index can be
+ // quickly found by finding a vertex in the list and read the respective facet ids.
+ std::vector facets_to_select{hit_and_facet.second};
+ NeighborData vertex = std::make_pair(0, 0);
+ std::vector visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed
+ size_t facet_idx = 0; // index into facets_to_select
+ auto it = neighbors.end();
+ while (facet_idx < facets_to_select.size()) {
+ size_t facet = facets_to_select[facet_idx];
+ if (! visited[facet]) {
+ // check all three vertices and in case they're close enough, find the remaining facets
+ // and add them to the list to be proccessed later
+ for (size_t i=0; i<3; ++i) {
+ vertex.first = mesh->its.indices[facet](i); // vertex index
+ float dist = squared_distance_from_line(mesh->its.vertices[vertex.first]);
+ if (dist < limit) {
+ it = std::lower_bound(neighbors.begin(), neighbors.end(), vertex);
+ while (it != neighbors.end() && it->first == vertex.first) {
+ if (it->second != facet && faces_camera(it->second))
+ facets_to_select.push_back(it->second);
+ ++it;
}
}
- visited[facet] = true;
}
- ++facet_idx;
+ visited[facet] = true;
}
+ ++facet_idx;
+ }
- // Now just select all facets that passed.
- for (size_t next_facet : facets_to_select) {
- FacetSupportType& facet = m_selected_facets[mesh_id][next_facet];
+ std::vector new_facets;
+ new_facets.reserve(facets_to_select.size());
- if (facet != new_state && facet != FacetSupportType::NONE) {
+ // Now just select all facets that passed and remember which
+ // ones have really changed state.
+ for (size_t next_facet : facets_to_select) {
+ FacetSupportType& facet = m_selected_facets[mesh_id][next_facet];
+
+ if (facet != new_state) {
+ if (facet != FacetSupportType::NONE) {
// this triangle is currently in the other VBA.
// Both VBAs need to be refreshed.
update_both = true;
}
facet = new_state;
+ new_facets.push_back(next_facet);
+ }
+ }
+
+ if (! new_facets.empty()) {
+ if (new_state != FacetSupportType::NONE) {
+ // append triangles into the respective VBA
+ update_vertex_buffers(mesh, mesh_id, new_state, &new_facets);
+ if (update_both) {
+ auto other = new_state == FacetSupportType::ENFORCER
+ ? FacetSupportType::BLOCKER
+ : FacetSupportType::ENFORCER;
+ update_vertex_buffers(mesh, mesh_id, other); // regenerate the other VBA
+ }
+ }
+ else {
+ update_vertex_buffers(mesh, mesh_id, FacetSupportType::ENFORCER);
+ update_vertex_buffers(mesh, mesh_id, FacetSupportType::BLOCKER);
}
}
- update_vertex_buffers(mv, mesh_id,
- new_state == FacetSupportType::ENFORCER || update_both,
- new_state == FacetSupportType::BLOCKER || update_both
- );
- }
- if (some_mesh_was_hit)
- {
if (m_button_down == Button::None)
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
- // Force rendering. In case the user is dragging, the queue can be
- // flooded by wxEVT_MOVING event and rendering would be skipped.
- m_parent.render();
return true;
}
- if (action == SLAGizmoEventType::Dragging && m_button_down != Button::None) {
- // Same as above. We don't want the cursor to freeze when we
- // leave the mesh while painting.
- m_parent.render();
+ if (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)
return true;
- }
}
if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
@@ -493,34 +513,54 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
-void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv,
+void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh,
int mesh_id,
- bool update_enforcers,
- bool update_blockers)
+ FacetSupportType type,
+ const std::vector* new_facets)
{
- const TriangleMesh* mesh = &mv->mesh();
+ std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1];
- for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) {
- if ((type == FacetSupportType::ENFORCER && ! update_enforcers)
- || (type == FacetSupportType::BLOCKER && ! update_blockers))
- continue;
+ // lambda to push facet into vertex buffer
+ auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) {
+ for (int i=0; i<3; ++i)
+ iva.push_geometry(
+ mesh->its.vertices[mesh->its.indices[idx](i)].cast(),
+ m_c->raycaster()->raycasters()[mesh_id]->get_triangle_normal(idx).cast()
+ );
+ size_t num = iva.triangle_indices_size;
+ iva.push_triangle(num, num+1, num+2);
+ };
- GLIndexedVertexArray& iva = m_ivas[mesh_id][type==FacetSupportType::ENFORCER ? 0 : 1];
- iva.release_geometry();
- size_t triangle_cnt=0;
+
+ if (ivas.size() == MaxVertexBuffers || ! new_facets) {
+ // If there are too many or they should be regenerated, make one large
+ // GLVertexBufferArray.
+ ivas.clear(); // destructors release geometry
+ ivas.push_back(GLIndexedVertexArray());
+
+ bool pushed = false;
for (size_t facet_idx=0; facet_idxits.vertices[mesh->its.indices[facet_idx](i)].cast(),
- MeshRaycaster::get_triangle_normal(mesh->its, facet_idx).cast());
- iva.push_triangle(3*triangle_cnt, 3*triangle_cnt+1, 3*triangle_cnt+2);
- ++triangle_cnt;
+ if (m_selected_facets[mesh_id][facet_idx] == type) {
+ push_facet(facet_idx, ivas.back());
+ pushed = true;
+ }
}
- if (! m_selected_facets[mesh_id].empty())
- iva.finalize_geometry(true);
+ if (pushed)
+ ivas.back().finalize_geometry(true);
+ else
+ ivas.pop_back();
+ } else {
+ // we are only appending - let's make new vertex array and let the old ones live
+ ivas.push_back(GLIndexedVertexArray());
+ for (size_t facet_idx : *new_facets)
+ push_facet(facet_idx, ivas.back());
+
+ if (! new_facets->empty())
+ ivas.back().finalize_geometry(true);
+ else
+ ivas.pop_back();
}
+
}
@@ -553,7 +593,8 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr
? FacetSupportType::BLOCKER
: FacetSupportType::ENFORCER;
}
- update_vertex_buffers(mv, mesh_id, true, true);
+ update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::ENFORCER);
+ update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER);
}
Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
@@ -623,7 +664,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (mv->is_model_part()) {
m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE);
mv->m_supported_facets.clear();
- update_vertex_buffers(mv, idx, true, true);
+ update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::ENFORCER);
+ update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER);
m_parent.set_as_dirty();
}
}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
index bed6d00a0..e7f3a2063 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
@@ -33,14 +33,16 @@ private:
// individual facets (one of the enum values above).
std::vector> m_selected_facets;
- // Store two vertex buffer arrays (for enforcers/blockers)
- // for each model-part volume.
- std::vector> m_ivas;
+ // Vertex buffer arrays for each model-part volume. There is a vector of
+ // arrays so that adding triangles can be done without regenerating all
+ // other triangles. Enforcers and blockers are of course separate.
+ std::vector, 2>> m_ivas;
- void update_vertex_buffers(const ModelVolume* mv,
+ void update_vertex_buffers(const TriangleMesh* mesh,
int mesh_id,
- bool update_enforcers,
- bool update_blockers);
+ FacetSupportType type, // enforcers / blockers
+ const std::vector* new_facets = nullptr); // nullptr -> regenerate all
+
public:
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 2b5f8429f..8c268ed00 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -623,6 +623,10 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
+ append_menu_item(import_menu, wxID_ANY, _L("Import STL (imperial units)"), _L("Load an model saved with imperial units"),
+ [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
+ [this](){return m_plater != nullptr; }, this);
+
append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
index 6c4f5e49d..18526d8ce 100644
--- a/src/slic3r/GUI/MeshUtils.cpp
+++ b/src/slic3r/GUI/MeshUtils.cpp
@@ -95,11 +95,9 @@ void MeshClipper::recalculate_triangles()
}
-Vec3f MeshRaycaster::get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx)
+Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const
{
- Vec3f a(its.vertices[its.indices[facet_idx](1)] - its.vertices[its.indices[facet_idx](0)]);
- Vec3f b(its.vertices[its.indices[facet_idx](2)] - its.vertices[its.indices[facet_idx](0)]);
- return Vec3f(a.cross(b)).normalized();
+ return m_normals[facet_idx];
}
void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
@@ -218,12 +216,9 @@ Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
int idx = 0;
Vec3d closest_point;
m_emesh.squared_distance(point.cast(), idx, closest_point);
- if (normal) {
- auto indices = m_emesh.F().row(idx);
- Vec3d a(m_emesh.V().row(indices(1)) - m_emesh.V().row(indices(0)));
- Vec3d b(m_emesh.V().row(indices(2)) - m_emesh.V().row(indices(0)));
- *normal = Vec3f(a.cross(b).cast());
- }
+ if (normal)
+ *normal = m_normals[idx];
+
return closest_point.cast();
}
diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp
index 92f444f55..8e321730c 100644
--- a/src/slic3r/GUI/MeshUtils.hpp
+++ b/src/slic3r/GUI/MeshUtils.hpp
@@ -108,7 +108,11 @@ public:
// The pointer can be invalidated after constructor returns.
MeshRaycaster(const TriangleMesh& mesh)
: m_emesh(mesh)
- {}
+ {
+ m_normals.reserve(mesh.stl.facet_start.size());
+ for (const stl_facet& facet : mesh.stl.facet_start)
+ m_normals.push_back(facet.normal);
+ }
void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3d& point, Vec3d& direction) const;
@@ -140,10 +144,11 @@ public:
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
- static Vec3f get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx);
+ Vec3f get_triangle_normal(size_t facet_idx) const;
private:
sla::EigenMesh3D m_emesh;
+ std::vector m_normals;
};
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index cbd040b33..6588e7eb8 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -1170,12 +1170,15 @@ void Sidebar::show_info_sizer()
return;
}
+ bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
+ double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f;
+
auto size = model_object->bounding_box().size();
- p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2)));
+ p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef));
p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count())));
const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats;
- p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume));
+ p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3)));
p->object_info->info_facets->SetLabel(wxString::Format(_L("%d (%d shells)"), static_cast(model_object->facets_count()), stats.number_of_parts));
int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
@@ -1253,18 +1256,24 @@ void Sidebar::update_sliced_info_sizer()
const PrintStatistics& ps = p->plater->fff_print().print_statistics();
const bool is_wipe_tower = ps.total_wipe_tower_filament > 0;
- wxString new_label = _L("Used Filament (m)");
+ bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
+ double koef = imperial_units ? ObjectManipulation::in_to_mm : 1000.0;
+
+ wxString new_label = imperial_units ? _L("Used Filament (in)") : _L("Used Filament (m)");
if (is_wipe_tower)
new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower"));
wxString info_text = is_wipe_tower ?
- wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000,
- (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000,
- ps.total_wipe_tower_filament / 1000) :
- wxString::Format("%.2f", ps.total_used_filament / 1000);
+ wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef,
+ (ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef,
+ ps.total_wipe_tower_filament / /*1000*/koef) :
+ wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef);
p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label);
- p->sliced_info->SetTextAndShow(siFilament_mm3, wxString::Format("%.2f", ps.total_extruded_volume));
+ koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f;
+ new_label = imperial_units ? _L("Used Filament (in³)") : _L("Used Filament (mm³)");
+ info_text = wxString::Format("%.2f", imperial_units ? ps.total_extruded_volume * koef : ps.total_extruded_volume);
+ p->sliced_info->SetTextAndShow(siFilament_mm3, info_text, new_label);
p->sliced_info->SetTextAndShow(siFilament_g, ps.total_weight == 0.0 ? "N/A" : wxString::Format("%.2f", ps.total_weight));
new_label = _L("Cost");
@@ -1414,6 +1423,13 @@ void Sidebar::collapse(bool collapse)
}
+void Sidebar::update_ui_from_settings()
+{
+ p->object_manipulation->update_ui_from_settings();
+ show_info_sizer();
+ update_sliced_info_sizer();
+}
+
std::vector& Sidebar::combos_filament()
{
return p->combos_filament;
@@ -1653,7 +1669,7 @@ struct Plater::priv
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
- std::vector load_files(const std::vector& input_files, bool load_model, bool load_config);
+ std::vector load_files(const std::vector& input_files, bool load_model, bool load_config, bool used_inches = false);
std::vector load_model_objects(const ModelObjectPtrs &model_objects);
wxString get_export_file(GUI::FileType file_type);
@@ -2134,6 +2150,8 @@ void Plater::priv::update_ui_from_settings()
view3D->get_canvas3d()->update_ui_from_settings();
preview->get_canvas3d()->update_ui_from_settings();
+
+ sidebar->update_ui_from_settings();
}
// Called after the print technology was changed.
@@ -2166,7 +2184,7 @@ BoundingBox Plater::priv::scaled_bed_shape_bb() const
return bed_shape.bounding_box();
}
-std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config)
+std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/)
{
if (input_files.empty()) { return std::vector(); }
@@ -2263,6 +2281,23 @@ std::vector Plater::priv::load_files(const std::vector& input_
{
// The model should now be initialized
+ auto convert_from_imperial_units = [](Model& model) {
+ model.convert_from_imperial_units();
+ wxGetApp().app_config->set("use_inches", "1");
+ wxGetApp().sidebar().update_ui_from_settings();
+ };
+
+ if (imperial_units)
+ convert_from_imperial_units(model);
+ else if (model.looks_like_imperial_units()) {
+ wxMessageDialog msg_dlg(q, _L(
+ "This model looks like saved in inches.\n"
+ "Should I consider this model as a saved in inches and convert it?") + "\n",
+ _L("Saved in inches object detected"), wxICON_WARNING | wxYES | wxNO);
+ if (msg_dlg.ShowModal() == wxID_YES)
+ convert_from_imperial_units(model);
+ }
+
if (! is_project_file) {
if (model.looks_like_multipart_object()) {
wxMessageDialog msg_dlg(q, _L(
@@ -4317,7 +4352,7 @@ void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& lab
// Plater / Public
Plater::Plater(wxWindow *parent, MainFrame *main_frame)
- : wxPanel(parent)
+ : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(76 * wxGetApp().em_unit(), 49 * wxGetApp().em_unit()))
, p(new priv(this, main_frame))
{
// Initialization performed in the private c-tor
@@ -4369,7 +4404,7 @@ void Plater::load_project(const wxString& filename)
p->set_project_filename(filename);
}
-void Plater::add_model()
+void Plater::add_model(bool imperial_units/* = false*/)
{
wxArrayString input_files;
wxGetApp().import_model(this, input_files);
@@ -4397,7 +4432,7 @@ void Plater::add_model()
}
Plater::TakeSnapshot snapshot(this, snapshot_label);
- load_files(paths, true, false);
+ load_files(paths, true, false, imperial_units);
}
void Plater::import_sl1_archive()
@@ -4418,16 +4453,16 @@ void Plater::extract_config_from_project()
load_files(input_paths, false, true);
}
-std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config) { return p->load_files(input_files, load_model, load_config); }
+std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); }
// To be called when providing a list of files to the GUI slic3r on command line.
-std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config)
+std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/)
{
std::vector paths;
paths.reserve(input_files.size());
for (const std::string& path : input_files)
paths.emplace_back(path);
- return p->load_files(paths, load_model, load_config);
+ return p->load_files(paths, load_model, load_config, imperial_units);
}
void Plater::update() { p->update(); }
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 21eba8ad1..eb35750d3 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -133,6 +133,7 @@ public:
bool is_collapsed();
void collapse(bool collapse);
void update_searcher();
+ void update_ui_from_settings();
std::vector& combos_filament();
Search::OptionsSearcher& get_searcher();
@@ -165,13 +166,13 @@ public:
void new_project();
void load_project();
void load_project(const wxString& filename);
- void add_model();
+ void add_model(bool imperial_units = false);
void import_sl1_archive();
void extract_config_from_project();
- std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true);
+ std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);
// To be called when providing a list of files to the GUI slic3r on command line.
- std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true);
+ std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);
void update();
void stop_jobs();
diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp
index 25609b166..bb189cf26 100644
--- a/src/slic3r/GUI/Preferences.cpp
+++ b/src/slic3r/GUI/Preferences.cpp
@@ -120,7 +120,16 @@ void PreferencesDialog::build()
option = Option (def, "use_retina_opengl");
m_optgroup_general->append_single_option_line(option);
#endif
-
+/* // ysFIXME THis part is temporary commented
+ // The using of inches is implemented just for object's size and position
+
+ def.label = L("Use inches instead of millimeters");
+ def.type = coBool;
+ def.tooltip = L("Use inches instead of millimeters for the object's size");
+ def.set_default_value(new ConfigOptionBool{ app_config->get("use_inches") == "1" });
+ option = Option(def, "use_inches");
+ m_optgroup_general->append_single_option_line(option);
+*/
m_optgroup_camera = std::make_shared(this, _(L("Camera")));
m_optgroup_camera->label_width = 40;
m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp
index 9fcd9ec9f..22ac7b70f 100644
--- a/src/slic3r/GUI/Search.cpp
+++ b/src/slic3r/GUI/Search.cpp
@@ -460,7 +460,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
check_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Use for search") + ":"), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
check_sizer->Add(check_category, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
- if (GUI::wxGetApp().is_localized())
+ if (check_english)
check_sizer->Add(check_english, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
check_sizer->AddStretchSpacer(border);
check_sizer->Add(cancel_btn, 0, wxALIGN_CENTER_VERTICAL);
@@ -484,7 +484,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
#endif //__WXMSW__
check_category->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
- if (GUI::wxGetApp().is_localized())
+ if (check_english)
check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this);
@@ -505,7 +505,8 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
const OptionViewParameters& params = searcher->view_params;
check_category->SetValue(params.category);
- check_english->SetValue(params.english);
+ if (check_english)
+ check_english->SetValue(params.english);
this->SetPosition(position);
this->ShowModal();
@@ -594,6 +595,9 @@ void SearchDialog::OnSelect(wxDataViewEvent& event)
void SearchDialog::update_list()
{
+ // Under OSX model->Clear invoke wxEVT_DATAVIEW_SELECTION_CHANGED, so
+ // set prevent_list_events to true already here
+ prevent_list_events = true;
search_list_model->Clear();
const std::vector& filters = searcher->found_options();
@@ -601,7 +605,6 @@ void SearchDialog::update_list()
search_list_model->Prepend(item.label);
// select first item
- prevent_list_events = true;
search_list->Select(search_list_model->GetItem(0));
prevent_list_events = false;
}
@@ -609,7 +612,8 @@ void SearchDialog::update_list()
void SearchDialog::OnCheck(wxCommandEvent& event)
{
OptionViewParameters& params = searcher->view_params;
- params.english = check_english->GetValue();
+ if (check_english)
+ params.english = check_english->GetValue();
params.category = check_category->GetValue();
searcher->search();