diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index af9bb4f980..ddf377c781 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -151,8 +151,8 @@ bool stl_write_binary(stl_file *stl, const char *file, const char *label) memcpy(buffer, &stl->stats.number_of_facets, 4); stl_internal_reverse_quads(buffer, 4); fwrite(buffer, 4, 1, fp); - for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { - memcpy(buffer, stl->facet_start + i, 50); + for (const stl_facet &facet : stl->facet_start) { + memcpy(buffer, &facet, 50); // Convert to little endian. stl_internal_reverse_quads(buffer, 48); fwrite(buffer, SIZEOF_STL_FACET, 1, fp); diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 9ae116c476..70c2348afa 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -11,7 +11,7 @@ ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths this->append(paths); } -ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const ExtrusionEntityCollection &other) +ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionEntityCollection &other) { this->entities = other.entities; for (size_t i = 0; i < this->entities.size(); ++i) @@ -175,20 +175,20 @@ size_t ExtrusionEntityCollection::items_count() const } // Returns a single vector of pointers to all non-collection items contained in this one. -void ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const -{ - for (const ExtrusionEntity *entity : this->entities) - if (entity->is_collection()) - retval->append(static_cast(entity)->flatten().entities); - else - retval->append(*entity); -} - ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const { - ExtrusionEntityCollection coll; - this->flatten(&coll); - return coll; + struct Flatten { + ExtrusionEntityCollection out; + void recursive_do(const ExtrusionEntityCollection &collection) { + for (const ExtrusionEntity* entity : collection.entities) + if (entity->is_collection()) + this->recursive_do(*static_cast(entity)); + else + out.append(*entity); + } + } flatten; + flatten.recursive_do(*this); + return flatten.out; } double ExtrusionEntityCollection::min_mm3_per_mm() const diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 4fe964ee10..221afc4536 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -85,7 +85,6 @@ public: Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } size_t items_count() const; - void flatten(ExtrusionEntityCollection* retval) const; ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 9053cdd42d..d209214ae6 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -284,6 +284,9 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER) { this->SetFont(wxGetApp().normal_font()); + + // Reset m_enter_pressed flag to _false_, when value is editing + this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId()); this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) { @@ -307,7 +310,7 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, if (!m_enter_pressed) { #ifndef __WXGTK__ /* Update data for next editor selection. - * But under GTK it lucks like there is no information about selected control at e.GetWindow(), + * But under GTK it looks like there is no information about selected control at e.GetWindow(), * so we'll take it from wxEVT_LEFT_DOWN event * */ LayerRangeEditor* new_editor = dynamic_cast(e.GetWindow()); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c67f0d304f..8f5f783840 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -279,6 +279,7 @@ void ObjectList::create_popup_menus() create_part_popupmenu(&m_menu_part); create_sla_object_popupmenu(&m_menu_sla_object); create_instance_popupmenu(&m_menu_instance); + create_default_popupmenu(&m_menu_default); } void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/) @@ -783,18 +784,34 @@ void ObjectList::OnChar(wxKeyEvent& event) void ObjectList::OnContextMenu(wxDataViewEvent&) { - list_manipulation(); + list_manipulation(true); } -void ObjectList::list_manipulation() +void ObjectList::list_manipulation(bool evt_context_menu/* = false*/) { wxDataViewItem item; wxDataViewColumn* col = nullptr; const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); - if (!item || col == nullptr) { - return; + /* Note: Under OSX right click doesn't send "selection changed" event. + * It means that Selection() will be return still previously selected item. + * Thus under OSX we should force UnselectAll(), when item and col are nullptr, + * and select new item otherwise. + */ + + if (!item) { + if (wxOSX && col == nullptr) + UnselectAll(); + if (evt_context_menu) { + show_context_menu(evt_context_menu); + return; + } + } + + if (wxOSX && item && col) { + UnselectAll(); + Select(item); } const wxString title = col->GetTitle(); @@ -802,7 +819,7 @@ void ObjectList::list_manipulation() if (title == " ") toggle_printable_state(item); else if (title == _("Editing")) - show_context_menu(); + show_context_menu(evt_context_menu); else if (title == _("Name")) { int obj_idx, vol_idx; @@ -818,7 +835,7 @@ void ObjectList::list_manipulation() #endif //__WXMSW__ } -void ObjectList::show_context_menu() +void ObjectList::show_context_menu(const bool evt_context_menu) { if (multiple_selection()) { @@ -831,22 +848,26 @@ void ObjectList::show_context_menu() } const auto item = GetSelection(); + wxMenu* menu {nullptr}; if (item) { const ItemType type = m_objects_model->GetItemType(item); if (!(type & (itObject | itVolume | itLayer | itInstance))) return; - wxMenu* menu = type & itInstance ? &m_menu_instance : + menu = type & itInstance ? &m_menu_instance : type & itLayer ? &m_menu_layer : m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part : printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; if (!(type & itInstance)) append_menu_item_settings(menu); - - wxGetApp().plater()->PopupMenu(menu); } + else if (evt_context_menu) + menu = &m_menu_default; + + if (menu) + wxGetApp().plater()->PopupMenu(menu); } void ObjectList::copy() @@ -1286,13 +1307,16 @@ void ObjectList::show_settings(const wxDataViewItem settings_item) wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { auto sub_menu = new wxMenu; - if (wxGetApp().get_mode() == comExpert) { + if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) { append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "", [this, type](wxCommandEvent&) { load_subobject(type); }, "", menu); sub_menu->AppendSeparator(); } - for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) { + for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) + { + if (type == ModelVolumeType::INVALID && item == "Slab") + continue; append_menu_item(sub_menu, wxID_ANY, _(item), "", [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu); } @@ -1579,6 +1603,12 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu) }, m_menu_item_split_instances->GetId()); } +void ObjectList::create_default_popupmenu(wxMenu*menu) +{ + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID); + append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part"); +} + wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) { wxMenu *menu = new wxMenu; @@ -1667,6 +1697,10 @@ void ObjectList::load_subobject(ModelVolumeType type) if (sel_item) select_item(sel_item); + +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::load_part( ModelObject* model_object, @@ -1713,8 +1747,38 @@ void ObjectList::load_part( ModelObject* model_object, } +static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb) +{ + TriangleMesh mesh; + + const double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); + + if (type_name == "Box") + // Sitting on the print bed, left front front corner at (0, 0). + mesh = make_cube(side, side, side); + else if (type_name == "Cylinder") + // Centered around 0, sitting on the print bed. + // The cylinder has the same volume as the box above. + mesh = make_cylinder(0.564 * side, side); + else if (type_name == "Sphere") + // Centered around 0, half the sphere below the print bed, half above. + // The sphere has the same volume as the box above. + mesh = make_sphere(0.62 * side, PI / 18); + else if (type_name == "Slab") + // Sitting on the print bed, left front front corner at (0, 0). + mesh = make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5); + mesh.repair(); + + return mesh; +} + void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) { + if (type == ModelVolumeType::INVALID) { + load_shape_object(type_name); + return; + } + const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; @@ -1737,26 +1801,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // Bounding box of the selected instance in world coordinate system including the translation, without modifiers. BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx); - const wxString name = _(L("Generic")) + "-" + _(type_name); - TriangleMesh mesh; - - double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); - - if (type_name == "Box") - // Sitting on the print bed, left front front corner at (0, 0). - mesh = make_cube(side, side, side); - else if (type_name == "Cylinder") - // Centered around 0, sitting on the print bed. - // The cylinder has the same volume as the box above. - mesh = make_cylinder(0.564 * side, side); - else if (type_name == "Sphere") - // Centered around 0, half the sphere below the print bed, half above. - // The sphere has the same volume as the box above. - mesh = make_sphere(0.62 * side, PI / 18); - else if (type_name == "Slab") - // Sitting on the print bed, left front front corner at (0, 0). - mesh = make_cube(instance_bb.size().x()*1.5, instance_bb.size().y()*1.5, instance_bb.size().z()*0.5); - mesh.repair(); + TriangleMesh mesh = create_mesh(type_name, instance_bb); // Mesh will be centered when loading. ModelVolume *new_volume = model_object.add_volume(std::move(mesh)); @@ -1778,6 +1823,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); } + const wxString name = _(L("Generic")) + "-" + _(type_name); new_volume->name = into_u8(name); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -1795,6 +1841,57 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode #endif //no __WXOSX__ //__WXMSW__ } +void ObjectList::load_shape_object(const std::string& type_name) +{ + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + assert(selection.get_object_idx() == -1); // Add nothing is something is selected on 3DScene + if (selection.get_object_idx() != -1) + return; + + const int obj_idx = m_objects->size(); + if (obj_idx < 0) + return; + + take_snapshot(_(L("Add Shape"))); + + // Create mesh + BoundingBoxf3 bb; + TriangleMesh mesh = create_mesh(type_name, bb); + + // Add mesh to model as a new object + Model& model = wxGetApp().plater()->model(); + const wxString name = _(L("Shape")) + "-" + _(type_name); + +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + std::vector object_idxs; + ModelObject* new_object = model.add_object(); + new_object->name = into_u8(name); + new_object->add_instance(); // each object should have at list one instance + + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = into_u8(name); + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + new_object->invalidate_bounding_box(); + + const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); + new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -new_object->origin_translation(2))); + + object_idxs.push_back(model.objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ +} + void ObjectList::del_object(const int obj_idx) { wxGetApp().plater()->delete_object_from_model(obj_idx); @@ -3582,7 +3679,8 @@ void ObjectList::msw_rescale() &m_menu_part, &m_menu_sla_object, &m_menu_instance, - &m_menu_layer }) + &m_menu_layer, + &m_menu_default}) msw_rescale_menu(menu); Layout(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 13d1106fc6..4dd618a90b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -132,6 +132,7 @@ private: MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_instance; MenuWithSeparators m_menu_layer; + MenuWithSeparators m_menu_default; wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; @@ -208,7 +209,7 @@ public: void set_tooltip_for_item(const wxPoint& pt); void selection_changed(); - void show_context_menu(); + void show_context_menu(const bool evt_context_menu); #ifndef __WXOSX__ void key_event(wxKeyEvent& event); #endif /* __WXOSX__ */ @@ -240,6 +241,7 @@ public: void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); void create_instance_popupmenu(wxMenu*menu); + void create_default_popupmenu(wxMenu *menu); wxMenu* create_settings_popupmenu(wxMenu *parent_menu); void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true); @@ -248,6 +250,7 @@ public: void load_subobject(ModelVolumeType type); void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); + void load_shape_object(const std::string &type_name); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); @@ -362,7 +365,7 @@ private: // void OnChar(wxKeyEvent& event); #endif /* __WXOSX__ */ void OnContextMenu(wxDataViewEvent &event); - void list_manipulation(); + void list_manipulation(bool evt_context_menu = false); void OnBeginDrag(wxDataViewEvent &event); void OnDropPossible(wxDataViewEvent &event); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 80afb29b17..992f4912a1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -296,8 +296,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) // Matrices set, we can render the point mark now. // If in editing mode, we'll also render a cone pointing to the sphere. if (m_editing_mode) { + // in case the normal is not yet cached, find and cache it if (m_editing_cache[i].normal == Vec3f::Zero()) - update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it + m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); @@ -366,13 +367,8 @@ void GLGizmoSlaSupports::update_mesh() m_its = &m_mesh->its; // If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it. - if (m_model_object_id != m_model_object->id() || (m_AABB.m_left == NULL && m_AABB.m_right == NULL)) - { - m_AABB.deinit(); - m_AABB.init( - MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), - MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); - } + if (m_model_object_id != m_model_object->id() || ! m_mesh_raycaster) + m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh)); m_model_object_id = m_model_object->id(); disable_editing_mode(); @@ -385,55 +381,26 @@ void GLGizmoSlaSupports::update_mesh() bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: - if (m_its == nullptr) + if (! m_mesh_raycaster) update_mesh(); const Camera& camera = m_parent.get_camera(); - const std::array& viewport = camera.get_viewport(); - const Transform3d& modelview_matrix = camera.get_view_matrix(); - const Transform3d& projection_matrix = camera.get_projection_matrix(); - - Vec3d point1; - Vec3d point2; - ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); - ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); - - std::vector hits; - const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = volume->get_instance_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift)); - point1(2) -= m_z_shift; - point2(2) -= m_z_shift; + // The raycaster query + std::vector hits; + std::vector normals; + m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, &hits, &normals); - Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); - - point1 = inv * point1; - point2 = inv * point2; - - if (!m_AABB.intersect_ray( - MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), - MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), - point1.cast(), (point2-point1).cast(), hits)) - return false; // no intersection found - - std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); - - // Now let's iterate through the points and find the first that is not clipped: - unsigned int i=0; - Vec3f bc; - Vec3f a; - Vec3f b; - Vec3f result; - for (i=0; ivertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); - b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); - result = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; - if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast())) - break; + // We must also take care of the clipping plane (if active) + unsigned i = 0; + if (m_clipping_plane_distance != 0.f) { + for (i=0; i())) + break; } if (i==hits.size() || (hits.size()-i) % 2 != 0) { @@ -443,7 +410,7 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pairinstances[m_active_instance]->get_transformation().get_matrix(); + Geometry::Transformation trafo = m_model_object->instances[m_active_instance]->get_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift)); std::vector points; - for (unsigned int i=0; i()); - points.back()(2) += m_z_shift; - } + for (unsigned int i=0; i()); + // Now ask the rectangle which of the points are inside. - const Camera& camera = m_parent.get_camera(); - std::vector selected_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + std::vector points_inside; + std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + for (size_t idx : points_idxs) + points_inside.push_back((trafo.get_matrix() * points[idx]).cast()); - // we'll recover current look direction (in world coords) and transform it to model coords. - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true); - Vec3f direction_to_camera = -camera.get_dir_forward().cast(); - Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); - Vec3f scaling = volume->get_instance_scaling_factor().cast(); - direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); - - // Iterate over all points in the rectangle and check that they are neither clipped by the - // clipping plane nor obscured by the mesh. - for (const unsigned int i : selected_idxs) { - const sla::SupportPoint &support_point = m_editing_cache[i].support_point; - if (!is_point_clipped(support_point.pos.cast())) { - bool is_obscured = false; - // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; - // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. - if (m_AABB.intersect_ray( - MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), - MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), - support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { - std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); - - if (m_clipping_plane_distance != 0.f) { - // If the closest hit facet normal points in the same direction as the ray, - // we are looking through the mesh and should therefore discard the point: - int fid = hits.front().id; // facet id - Vec3f a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); - Vec3f b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); - if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f) - is_obscured = true; - - // Eradicate all hits that are on clipped surfaces: - for (unsigned int j=0; jvertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; - if (is_point_clipped(hit_pos.cast())) { - hits.erase(hits.begin()+j); - --j; - } - } - } - - // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. - // Also, the threshold is in mesh coordinates, not in actual dimensions. - if (!hits.empty()) - is_obscured = true; - } - - if (!is_obscured) { - if (rectangle_status == GLSelectionRectangle::Deselect) - unselect_point(i); - else - select_point(i); - } + // Only select/deselect points that are actually visible + for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, + [this](const Vec3f& pt) { return is_point_clipped(pt.cast()); })) + { + const sla::SupportPoint &support_point = m_editing_cache[points_idxs[idx]].support_point; + if (! is_point_clipped(support_point.pos.cast())) { + if (rectangle_status == GLSelectionRectangle::Deselect) + unselect_point(points_idxs[idx]); + else + select_point(points_idxs[idx]); } } return true; @@ -731,23 +650,6 @@ std::vector GLGizmoSlaSupports::get_config_options(const st } -void GLGizmoSlaSupports::update_cache_entry_normal(size_t i) const -{ - int idx = 0; - Eigen::Matrix pp = m_editing_cache[i].support_point.pos; - Eigen::Matrix cc; - m_AABB.squared_distance( - MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), - MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), - pp, idx, cc); - Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]); - Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]); - m_editing_cache[i].normal = a.cross(b); -} - - - - ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const { if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f) @@ -1100,11 +1002,11 @@ void GLGizmoSlaSupports::on_set_state() m_parent.toggle_model_objects_visibility(true); m_normal_cache.clear(); m_clipping_plane_distance = 0.f; - // Release triangle mesh slicer and the AABB spatial search structure. - m_AABB.deinit(); + // Release clippers and the AABB raycaster. m_its = nullptr; m_object_clipper.reset(); m_supports_clipper.reset(); + m_mesh_raycaster.reset(); } } m_old_state = m_state; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 7bef33e1bd..cf5245f194 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -4,11 +4,6 @@ #include "GLGizmoBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" -// There is an L function in igl that would be overridden by our localization macro - let's undefine it... -#undef L -#include -#include "slic3r/GUI/I18N.hpp" // ...and redefine again when we are done with the igl code - #include "libslic3r/SLA/SLACommon.hpp" #include @@ -20,6 +15,7 @@ namespace GUI { class ClippingPlane; class MeshClipper; +class MeshRaycaster; enum class SLAGizmoEventType : unsigned char; class GLGizmoSlaSupports : public GLGizmoBase @@ -37,7 +33,8 @@ private: GLUquadricObj* m_quadric; typedef Eigen::Map> MapMatrixXfUnaligned; typedef Eigen::Map> MapMatrixXiUnaligned; - igl::AABB m_AABB; + + std::unique_ptr m_mesh_raycaster; const TriangleMesh* m_mesh; const indexed_triangle_set* m_its; mutable const TriangleMesh* m_supports_mesh; @@ -98,7 +95,6 @@ private: void render_clipping_plane(const Selection& selection) const; bool is_mesh_update_necessary() const; void update_mesh(); - void update_cache_entry_normal(size_t i) const; bool unsaved_changes() const; bool m_lock_unique_islands = false; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 9542f0b1fc..e559020e97 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -3,6 +3,15 @@ #include "libslic3r/Tesselate.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "slic3r/GUI/Camera.hpp" + +// There is an L function in igl that would be overridden by our localization macro. +#undef L +#include + +#include + + namespace Slic3r { namespace GUI { @@ -90,6 +99,168 @@ void MeshClipper::recalculate_triangles() } +class MeshRaycaster::AABBWrapper { +public: + AABBWrapper(const TriangleMesh* mesh); + ~AABBWrapper() { m_AABB.deinit(); } + + typedef Eigen::Map> MapMatrixXfUnaligned; + typedef Eigen::Map> MapMatrixXiUnaligned; + igl::AABB m_AABB; + + Vec3f get_hit_pos(const igl::Hit& hit) const; + Vec3f get_hit_normal(const igl::Hit& hit) const; + +private: + const TriangleMesh* m_mesh; +}; + +MeshRaycaster::AABBWrapper::AABBWrapper(const TriangleMesh* mesh) + : m_mesh(mesh) +{ + m_AABB.init( + MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), + MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3)); +} + + +MeshRaycaster::MeshRaycaster(const TriangleMesh& mesh) + : m_AABB_wrapper(new AABBWrapper(&mesh)), m_mesh(&mesh) +{ +} + +MeshRaycaster::~MeshRaycaster() +{ + delete m_AABB_wrapper; +} + +Vec3f MeshRaycaster::AABBWrapper::get_hit_pos(const igl::Hit& hit) const +{ + const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id]; + return Vec3f((1-hit.u-hit.v) * m_mesh->its.vertices[indices(0)] + + hit.u * m_mesh->its.vertices[indices(1)] + + hit.v * m_mesh->its.vertices[indices(2)]); +} + + +Vec3f MeshRaycaster::AABBWrapper::get_hit_normal(const igl::Hit& hit) const +{ + const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id]; + Vec3f a(m_mesh->its.vertices[indices(1)] - m_mesh->its.vertices[indices(0)]); + Vec3f b(m_mesh->its.vertices[indices(2)] - m_mesh->its.vertices[indices(0)]); + return Vec3f(a.cross(b)); +} + + +bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, + const Camera& camera, std::vector* positions, std::vector* normals) const +{ + const std::array& viewport = camera.get_viewport(); + const Transform3d& model_mat = camera.get_view_matrix(); + const Transform3d& proj_mat = camera.get_projection_matrix(); + + Vec3d pt1; + Vec3d pt2; + ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0., model_mat.data(), proj_mat.data(), viewport.data(), &pt1(0), &pt1(1), &pt1(2)); + ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1., model_mat.data(), proj_mat.data(), viewport.data(), &pt2(0), &pt2(1), &pt2(2)); + + std::vector hits; + + Transform3d inv = trafo.inverse(); + + pt1 = inv * pt1; + pt2 = inv * pt2; + + if (! m_AABB_wrapper->m_AABB.intersect_ray( + AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), + AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3), + pt1.cast(), (pt2-pt1).cast(), hits)) + return false; // no intersection found + + std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); + + // Now stuff the points in the provided vector and calculate normals if asked about them: + if (positions != nullptr) { + positions->clear(); + if (normals != nullptr) + normals->clear(); + for (const igl::Hit& hit : hits) { + positions->push_back(m_AABB_wrapper->get_hit_pos(hit)); + + if (normals != nullptr) + normals->push_back(m_AABB_wrapper->get_hit_normal(hit)); + } + } + + return true; +} + + +std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, + std::function fn_ignore_hit) const +{ + std::vector out; + + const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); + Vec3f direction_to_camera = -camera.get_dir_forward().cast(); + Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); + Vec3f scaling = trafo.get_scaling_factor().cast(); + direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); + + for (size_t i=0; i hits; + // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. + if (m_AABB_wrapper->m_AABB.intersect_ray( + AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), + AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3), + pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) { + + std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); + // If the closest hit facet normal points in the same direction as the ray, + // we are looking through the mesh and should therefore discard the point: + if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f) + is_obscured = true; + + // Eradicate all hits that the caller wants to ignore + for (unsigned j=0; jget_hit_pos(hit))) { + hits.erase(hits.begin()+j); + --j; + } + } + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. + // Also, the threshold is in mesh coordinates, not in actual dimensions. + if (! hits.empty()) + is_obscured = true; + } + if (! is_obscured) + out.push_back(i); + } + return out; +} + + +Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const +{ + int idx = 0; + Eigen::Matrix closest_point; + m_AABB_wrapper->m_AABB.squared_distance( + AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), + AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3), + point, idx, closest_point); + if (normal) { + igl::Hit imag_hit; + imag_hit.id = idx; + *normal = m_AABB_wrapper->get_hit_normal(imag_hit); + } + return closest_point; +} + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index f97003a918..a2be2677bd 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -14,6 +14,8 @@ class TriangleMeshSlicer; namespace GUI { +struct Camera; + class ClippingPlane @@ -86,6 +88,30 @@ private: }; + + +class MeshRaycaster { +public: + MeshRaycaster(const TriangleMesh& mesh); + ~MeshRaycaster(); + void set_transformation(const Geometry::Transformation& trafo); + void set_camera(const Camera& camera); + + bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + std::vector* positions = nullptr, std::vector* normals = nullptr) const; + + std::vector get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, + const std::vector& points, std::function fn_ignore_hit) const; + + Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const; + +private: + // PIMPL wrapper around igl::AABB so I don't have to include the header-only IGL here + class AABBWrapper; + AABBWrapper* m_AABB_wrapper; + const TriangleMesh* m_mesh = nullptr; +}; + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c5aad48a9e..524034ea9c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -522,7 +522,11 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : const std::vector &init_extruders = (project_config.option("wiping_volumes_extruders"))->values; const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; - const std::vector &extruder_colours = (config->option("extruder_colour"))->values; + std::vector extruder_colours = (config->option("extruder_colour"))->values; + const std::vector& filament_colours = (wxGetApp().plater()->get_plater_config()->option("filament_colour"))->values; + for (size_t i=0; i(init_matrix), cast(init_extruders), extruder_colours); @@ -2514,6 +2518,10 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) if (output_file.empty()) // Find the file name of the first printable object. output_file = this->model.propose_export_file_name_and_path(); + + if (output_file.empty() && !model.objects.empty()) + // Find the file name of the first object. + output_file = this->model.objects[0]->get_export_filename(); } wxString dlg_title; @@ -4838,6 +4846,11 @@ void Plater::on_activate() this->p->show_delayed_error_message(); } +const DynamicPrintConfig* Plater::get_plater_config() const +{ + return p->config; +} + wxString Plater::get_project_filename(const wxString& extension) const { return p->get_project_filename(extension); @@ -4868,6 +4881,11 @@ GLCanvas3D* Plater::canvas3D() return p->view3D->get_canvas3d(); } +BoundingBoxf Plater::bed_shape_bb() const +{ + return p->bed_shape_bb(); +} + PrinterTechnology Plater::printer_technology() const { return p->printer_technology; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 6b488fef14..26dcb5ac32 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -213,6 +213,7 @@ public: void on_config_change(const DynamicPrintConfig &config); // On activating the parent window. void on_activate(); + const DynamicPrintConfig* get_plater_config() const; void update_object_menu(); @@ -224,6 +225,7 @@ public: int get_selected_object_idx(); bool is_single_full_object_selection() const; GLCanvas3D* canvas3D(); + BoundingBoxf bed_shape_bb() const; PrinterTechnology printer_technology() const; void set_printer_technology(PrinterTechnology printer_technology); diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index a868ea6bb0..1f16ae3d4b 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -31,13 +31,11 @@ ExtrusionEntityCollection* flatten() %code{% RETVAL = new ExtrusionEntityCollection(); - THIS->flatten(RETVAL); + *RETVAL = THIS->flatten(); %}; double min_mm3_per_mm(); bool empty() %code{% RETVAL = THIS->entities.empty(); %}; - std::vector orig_indices() - %code{% RETVAL = THIS->orig_indices; %}; Polygons polygons_covered_by_width(); Polygons polygons_covered_by_spacing(); %{