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/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/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();