mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge remote-tracking branch 'origin/dev' into ys_manipulation_panel_rw
This commit is contained in:
		
						commit
						e34ef1dd07
					
				
					 13 changed files with 415 additions and 199 deletions
				
			
		|  | @ -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); | ||||
|  |  | |||
|  | @ -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<const ExtrusionEntityCollection*>(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<const ExtrusionEntityCollection*>(entity)); | ||||
| 				else | ||||
| 					out.append(*entity); | ||||
| 		} | ||||
| 	} flatten; | ||||
| 	flatten.recursive_do(*this); | ||||
|     return flatten.out; | ||||
| } | ||||
| 
 | ||||
| double ExtrusionEntityCollection::min_mm3_per_mm() const | ||||
|  |  | |||
|  | @ -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; } | ||||
|  |  | |||
|  | @ -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<LayerRangeEditor*>(e.GetWindow()); | ||||
|  |  | |||
|  | @ -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<size_t> 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<double>(), -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(); | ||||
|  |  | |||
|  | @ -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<std::pair<wxString, bool>> &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); | ||||
|  |  | |||
|  | @ -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<double>()); | ||||
|  | @ -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<Vec3f, Vec3f>& 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<int, 4>& 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<igl::Hit> 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<Vec3f> hits; | ||||
|     std::vector<Vec3f> 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<float>(), (point2-point1).cast<float>(), 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; i<hits.size(); ++i) { | ||||
|         igl::Hit& hit = hits[i]; | ||||
|         int fid = hit.id;   // facet id
 | ||||
|         bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
 | ||||
|         a = (m_its->vertices[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<double>())) | ||||
|             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<hits.size(); ++i) | ||||
|             if (! is_point_clipped(hits[i].cast<double>())) | ||||
|                 break; | ||||
|     } | ||||
| 
 | ||||
|     if (i==hits.size() || (hits.size()-i) % 2 != 0) { | ||||
|  | @ -443,7 +410,7 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec | |||
|     } | ||||
| 
 | ||||
|     // Calculate and return both the point and the facet normal.
 | ||||
|     pos_and_normal = std::make_pair(result, a.cross(b)); | ||||
|     pos_and_normal = std::make_pair(hits[i], normals[i]); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
|  | @ -504,76 +471,28 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|             GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); | ||||
| 
 | ||||
|             // First collect positions of all the points in world coordinates.
 | ||||
|             const Transform3d& instance_matrix = m_model_object->instances[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<Vec3d> points; | ||||
|             for (unsigned int i=0; i<m_editing_cache.size(); ++i) { | ||||
|                 const sla::SupportPoint &support_point = m_editing_cache[i].support_point; | ||||
|                 points.push_back(instance_matrix * support_point.pos.cast<double>()); | ||||
|                 points.back()(2) += m_z_shift; | ||||
|             } | ||||
|             for (unsigned int i=0; i<m_editing_cache.size(); ++i) | ||||
|                 points.push_back(trafo.get_matrix() * m_editing_cache[i].support_point.pos.cast<double>()); | ||||
| 
 | ||||
|             // Now ask the rectangle which of the points are inside.
 | ||||
|             const Camera& camera = m_parent.get_camera(); | ||||
|             std::vector<unsigned int> selected_idxs = m_selection_rectangle.stop_dragging(m_parent, points); | ||||
|             std::vector<Vec3f> points_inside; | ||||
|             std::vector<unsigned int> 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<float>()); | ||||
| 
 | ||||
|             // 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<float>(); | ||||
|             Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval(); | ||||
|             Vec3f scaling = volume->get_instance_scaling_factor().cast<float>(); | ||||
|             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<double>())) { | ||||
|                     bool is_obscured = false; | ||||
|                     // Cast a ray in the direction of the camera and look for intersection with the mesh:
 | ||||
|                     std::vector<igl::Hit> 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; j<hits.size(); ++j) { | ||||
|                                 const igl::Hit& hit = hits[j]; | ||||
|                                 int fid = hit.id;   // facet id
 | ||||
| 
 | ||||
|                                 Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
 | ||||
|                                 Vec3f hit_pos = 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 (is_point_clipped(hit_pos.cast<double>())) { | ||||
|                                     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<double>()); })) | ||||
|             { | ||||
|                 const sla::SupportPoint &support_point = m_editing_cache[points_idxs[idx]].support_point; | ||||
|                 if (! is_point_clipped(support_point.pos.cast<double>())) { | ||||
|                     if (rectangle_status == GLSelectionRectangle::Deselect) | ||||
|                         unselect_point(points_idxs[idx]); | ||||
|                     else | ||||
|                         select_point(points_idxs[idx]); | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|  | @ -731,23 +650,6 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoSlaSupports::update_cache_entry_normal(size_t i) const | ||||
| { | ||||
|     int idx = 0; | ||||
|     Eigen::Matrix<float, 1, 3> pp = m_editing_cache[i].support_point.pos; | ||||
|     Eigen::Matrix<float, 1, 3> 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; | ||||
|  |  | |||
|  | @ -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 <igl/AABB.h> | ||||
| #include "slic3r/GUI/I18N.hpp"  // ...and redefine again when we are done with the igl code
 | ||||
| 
 | ||||
| #include "libslic3r/SLA/SLACommon.hpp" | ||||
| #include <wx/dialog.h> | ||||
| 
 | ||||
|  | @ -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<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned; | ||||
|     typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned; | ||||
|     igl::AABB<MapMatrixXfUnaligned, 3> m_AABB; | ||||
| 
 | ||||
|     std::unique_ptr<MeshRaycaster> 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; | ||||
|  |  | |||
|  | @ -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 <igl/AABB.h> | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
| 
 | ||||
| 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<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned; | ||||
|     typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned; | ||||
|     igl::AABB<MapMatrixXfUnaligned, 3> 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<Vec3f>* positions, std::vector<Vec3f>* normals) const | ||||
| { | ||||
|     const std::array<int, 4>& 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<igl::Hit> 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<float>(), (pt2-pt1).cast<float>(), 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<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points, | ||||
|                                                        std::function<bool(const Vec3f&)> fn_ignore_hit) const | ||||
| { | ||||
|     std::vector<unsigned> out; | ||||
| 
 | ||||
|     const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); | ||||
|     Vec3f direction_to_camera = -camera.get_dir_forward().cast<float>(); | ||||
|     Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval(); | ||||
|     Vec3f scaling = trafo.get_scaling_factor().cast<float>(); | ||||
|     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<points.size(); ++i) { | ||||
|         const Vec3f& pt = points[i]; | ||||
|         bool is_obscured = false; | ||||
|         // Cast a ray in the direction of the camera and look for intersection with the mesh:
 | ||||
|         std::vector<igl::Hit> 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; j<hits.size(); ++j) { | ||||
|                 const igl::Hit& hit = hits[j]; | ||||
|                 if (fn_ignore_hit(m_AABB_wrapper->get_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<float, 1, 3> 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
 | ||||
|  |  | |||
|  | @ -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<Vec3f>* positions = nullptr, std::vector<Vec3f>* normals = nullptr) const; | ||||
| 
 | ||||
|     std::vector<unsigned> get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, | ||||
|                                               const std::vector<Vec3f>& points, std::function<bool(const Vec3f&)> 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
 | ||||
|  |  | |||
|  | @ -522,7 +522,11 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : | |||
|             const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values; | ||||
| 
 | ||||
|             const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; | ||||
|             const std::vector<std::string> &extruder_colours = (config->option<ConfigOptionStrings>("extruder_colour"))->values; | ||||
|             std::vector<std::string> extruder_colours = (config->option<ConfigOptionStrings>("extruder_colour"))->values; | ||||
|             const std::vector<std::string>& filament_colours = (wxGetApp().plater()->get_plater_config()->option<ConfigOptionStrings>("filament_colour"))->values; | ||||
|             for (size_t i=0; i<extruder_colours.size(); ++i) | ||||
|                 if (extruder_colours[i] == "" && i < filament_colours.size()) | ||||
|                     extruder_colours[i] = filament_colours[i]; | ||||
| 
 | ||||
|             WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(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; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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<size_t> orig_indices() | ||||
|         %code{% RETVAL = THIS->orig_indices; %}; | ||||
|     Polygons polygons_covered_by_width(); | ||||
|     Polygons polygons_covered_by_spacing(); | ||||
| %{ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 YuSanka
						YuSanka