mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' into fs_QuadricEdgeCollapse
# Conflicts: # src/slic3r/GUI/NotificationManager.hpp
This commit is contained in:
		
						commit
						790d445420
					
				
					 33 changed files with 771 additions and 378 deletions
				
			
		|  | @ -595,7 +595,7 @@ bool GLVolume::is_sinking() const | |||
| 
 | ||||
| bool GLVolume::is_below_printbed() const | ||||
| { | ||||
|     return transformed_convex_hull_bounding_box().max(2) < 0.0; | ||||
|     return transformed_convex_hull_bounding_box().max.z() < 0.0; | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SINKING_CONTOURS | ||||
|  |  | |||
|  | @ -87,6 +87,11 @@ | |||
| #include <boost/nowide/fstream.hpp> | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
 | ||||
| 
 | ||||
| // Needed for forcing menu icons back under gtk2 and gtk3
 | ||||
| #if defined(__WXGTK20__) || defined(__WXGTK3__) | ||||
|     #include <gtk/gtk.h> | ||||
| #endif | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
|  | @ -799,6 +804,14 @@ bool GUI_App::OnInit() | |||
| 
 | ||||
| bool GUI_App::on_init_inner() | ||||
| { | ||||
|     // Forcing back menu icons under gtk2 and gtk3. Solution is based on:
 | ||||
|     // https://docs.gtk.org/gtk3/class.Settings.html
 | ||||
|     // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
 | ||||
|     // TODO: Find workaround for GTK4
 | ||||
| #if defined(__WXGTK20__) || defined(__WXGTK3__) | ||||
|     g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); | ||||
| #endif | ||||
| 
 | ||||
|     // Verify resources path
 | ||||
|     const wxString resources_dir = from_u8(Slic3r::resources_dir()); | ||||
|     wxCHECK_MSG(wxDirExists(resources_dir), false, | ||||
|  |  | |||
|  | @ -1049,7 +1049,7 @@ void ObjectList::key_event(wxKeyEvent& event) | |||
|         || event.GetKeyCode() == WXK_BACK | ||||
| #endif //__WXOSX__
 | ||||
|         ) { | ||||
|         remove(); | ||||
|         wxGetApp().plater()->remove_selected(); | ||||
|     } | ||||
|     else if (event.GetKeyCode() == WXK_F5) | ||||
|         wxGetApp().plater()->reload_all_from_disk(); | ||||
|  | @ -1920,16 +1920,15 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con | |||
|             if (vol->is_model_part()) | ||||
|                 ++solid_cnt; | ||||
|         if (volume->is_model_part() && solid_cnt == 1) { | ||||
|             Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last solid part from object."))); | ||||
|             Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object.")); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         take_snapshot(_(L("Delete Subobject"))); | ||||
|         take_snapshot(_L("Delete Subobject")); | ||||
| 
 | ||||
|         object->delete_volume(idx); | ||||
| 
 | ||||
|         if (object->volumes.size() == 1) | ||||
|         { | ||||
|         if (object->volumes.size() == 1) { | ||||
|             const auto last_volume = object->volumes[0]; | ||||
|             if (!last_volume->config.empty()) { | ||||
|                 object->config.apply(last_volume->config); | ||||
|  | @ -1948,11 +1947,11 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con | |||
|     } | ||||
|     else if (type == itInstance) { | ||||
|         if (object->instances.size() == 1) { | ||||
|             Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted."))); | ||||
|             Slic3r::GUI::show_error(nullptr, _L("Last instance of an object cannot be deleted.")); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         take_snapshot(_(L("Delete Instance"))); | ||||
|         take_snapshot(_L("Delete Instance")); | ||||
|         object->delete_instance(idx); | ||||
|     } | ||||
|     else | ||||
|  |  | |||
|  | @ -14,6 +14,31 @@ | |||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/property_tree/ini_parser.hpp> | ||||
| #include <map> | ||||
| #include <cereal/archives/binary.hpp> | ||||
| #include <cereal/types/string.hpp> | ||||
| #include <cereal/types/vector.hpp> | ||||
| 
 | ||||
| #define HINTS_CEREAL_VERSION 1 | ||||
| // structure for writing used hints into binary file with version
 | ||||
| struct HintsCerealData | ||||
| { | ||||
| 	std::vector<std::string> my_data; | ||||
| 	// cereal will supply the version automatically when loading or saving
 | ||||
| 	// The version number comes from the CEREAL_CLASS_VERSION macro
 | ||||
| 	template<class Archive> | ||||
| 	void serialize(Archive& ar, std::uint32_t const version) | ||||
| 	{ | ||||
| 		// You can choose different behaviors depending on the version
 | ||||
| 		// This is useful if you need to support older variants of your codebase
 | ||||
| 		// interacting with newer ones
 | ||||
| 		if (version > HINTS_CEREAL_VERSION) | ||||
| 			throw Slic3r::IOError("Version of hints.cereal is higher than current version."); | ||||
| 		else | ||||
| 			ar(my_data); | ||||
| 	} | ||||
| }; | ||||
| // version of used hints binary file
 | ||||
| CEREAL_CLASS_VERSION(HintsCerealData, HINTS_CEREAL_VERSION); | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
|  | @ -31,6 +56,41 @@ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, f | |||
| 	else | ||||
| 		ImGui::PushStyleColor(idx, col); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void write_used_binary(const std::vector<std::string>& ids) | ||||
| { | ||||
| 	boost::filesystem::ofstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"), std::ios::binary); | ||||
| 	cereal::BinaryOutputArchive archive(file); | ||||
| 		HintsCerealData cd { ids }; | ||||
| 	try | ||||
| 	{ | ||||
| 		archive(cd); | ||||
| 	} | ||||
| 	catch (const std::exception& ex) | ||||
| 	{ | ||||
| 		BOOST_LOG_TRIVIAL(error) << "Failed to write to hints.cereal. " << ex.what(); | ||||
| 	} | ||||
| } | ||||
| void read_used_binary(std::vector<std::string>& ids) | ||||
| { | ||||
| 	boost::filesystem::ifstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal")); | ||||
| 	cereal::BinaryInputArchive archive(file); | ||||
| 	HintsCerealData cd; | ||||
| 	try | ||||
| 	{ | ||||
| 		archive(cd); | ||||
| 	} | ||||
| 	catch (const std::exception& ex) | ||||
| 	{ | ||||
| 		BOOST_LOG_TRIVIAL(error) << "Failed to load to hints.cereal. " << ex.what(); | ||||
| 		return; | ||||
| 	} | ||||
| 	ids = cd.my_data; | ||||
| } | ||||
| enum TagCheckResult | ||||
| { | ||||
| 	TagCheckAffirmative, | ||||
|  | @ -180,16 +240,16 @@ void launch_browser_if_allowed(const std::string& url) | |||
| 		wxLaunchDefaultBrowser(url); | ||||
| } | ||||
| } //namespace
 | ||||
| 
 | ||||
| HintDatabase::~HintDatabase() | ||||
| { | ||||
| 	if (m_initialized) { | ||||
| 		write_used_binary(m_used_ids); | ||||
| 	} | ||||
| } | ||||
| void HintDatabase::init() | ||||
| { | ||||
| 		 | ||||
| 	load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini")); | ||||
| 		 | ||||
| 	const AppConfig* app_config = wxGetApp().app_config; | ||||
| 	m_hint_id = std::atoi(app_config->get("last_hint").c_str()); | ||||
|     m_initialized = true; | ||||
| 
 | ||||
| } | ||||
| void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) | ||||
| { | ||||
|  | @ -210,15 +270,22 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) | |||
| 			for (const auto& data : section.second) { | ||||
| 				dict.emplace(data.first, data.second.data()); | ||||
| 			} | ||||
| 			 | ||||
| 			//unescaping and translating all texts and saving all data common for all hint types 
 | ||||
| 			// unique id string [hint:id] (trim "hint:")
 | ||||
| 			std::string id_string = section.first.substr(5); | ||||
| 			id_string = std::to_string(std::hash<std::string>{}(id_string)); | ||||
| 			// unescaping and translating all texts and saving all data common for all hint types 
 | ||||
| 			std::string fulltext; | ||||
| 			std::string text1; | ||||
| 			std::string hypertext_text; | ||||
| 			std::string follow_text; | ||||
| 			// tags
 | ||||
| 			std::string disabled_tags; | ||||
| 			std::string enabled_tags; | ||||
| 			// optional link to documentation (accessed from button)
 | ||||
| 			std::string documentation_link; | ||||
| 			// randomized weighted order variables
 | ||||
| 			size_t      weight = 1; | ||||
| 			bool		was_displayed = is_used(id_string); | ||||
| 			//unescape text1
 | ||||
| 			unescape_string_cstyle(_utf8(dict["text"]), fulltext); | ||||
| 			// replace <b> and </b> for imgui markers
 | ||||
|  | @ -276,37 +343,41 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) | |||
| 				documentation_link = dict["documentation_link"]; | ||||
| 			} | ||||
| 
 | ||||
| 			if (dict.find("weight") != dict.end()) { | ||||
| 				weight = (size_t)std::max(1, std::atoi(dict["weight"].c_str())); | ||||
| 			} | ||||
| 
 | ||||
| 			// create HintData
 | ||||
| 			if (dict.find("hypertext_type") != dict.end()) { | ||||
| 				//link to internet
 | ||||
| 				if(dict["hypertext_type"] == "link") { | ||||
| 					std::string	hypertext_link = dict["hypertext_link"]; | ||||
| 					HintData	hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); }  }; | ||||
| 					HintData	hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); }  }; | ||||
| 					m_loaded_hints.emplace_back(hint_data); | ||||
| 				// highlight settings
 | ||||
| 				} else if (dict["hypertext_type"] == "settings") { | ||||
| 					std::string		opt = dict["hypertext_settings_opt"]; | ||||
| 					Preset::Type	type = static_cast<Preset::Type>(std::atoi(dict["hypertext_settings_type"].c_str())); | ||||
| 					std::wstring	category = boost::nowide::widen(dict["hypertext_settings_category"]); | ||||
| 					HintData		hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } }; | ||||
| 					HintData		hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } }; | ||||
| 					m_loaded_hints.emplace_back(hint_data); | ||||
| 				// open preferences
 | ||||
| 				} else if(dict["hypertext_type"] == "preferences") { | ||||
| 					int			page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str())); | ||||
| 					HintData	hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } }; | ||||
| 					HintData	hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } }; | ||||
| 					m_loaded_hints.emplace_back(hint_data); | ||||
| 
 | ||||
| 				} else if (dict["hypertext_type"] == "plater") { | ||||
| 					std::string	item = dict["hypertext_plater_item"]; | ||||
| 					HintData	hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } }; | ||||
| 					HintData	hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } }; | ||||
| 					m_loaded_hints.emplace_back(hint_data); | ||||
| 				} else if (dict["hypertext_type"] == "gizmo") { | ||||
| 					std::string	item = dict["hypertext_gizmo_item"]; | ||||
| 					HintData	hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } }; | ||||
| 					HintData	hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } }; | ||||
| 					m_loaded_hints.emplace_back(hint_data); | ||||
| 				} | ||||
| 				else if (dict["hypertext_type"] == "gallery") { | ||||
| 					HintData	hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() {   | ||||
| 					HintData	hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() { | ||||
| 						// Deselect all objects, otherwise gallery wont show.
 | ||||
| 						wxGetApp().plater()->canvas3D()->deselect_all(); | ||||
| 						wxGetApp().obj_list()->load_shape_object_from_gallery(); } }; | ||||
|  | @ -314,17 +385,17 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) | |||
| 				} | ||||
| 			} else { | ||||
| 				// plain text without hypertext
 | ||||
| 				HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link }; | ||||
| 				HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link }; | ||||
| 				m_loaded_hints.emplace_back(hint_data); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| HintData* HintDatabase::get_hint(bool up) | ||||
| HintData* HintDatabase::get_hint(bool new_hint/* = true*/) | ||||
| { | ||||
|     if (! m_initialized) { | ||||
|         init(); | ||||
|         //return false;
 | ||||
| 		new_hint = true; | ||||
|     } | ||||
| 	if (m_loaded_hints.empty()) | ||||
| 	{ | ||||
|  | @ -332,23 +403,97 @@ HintData* HintDatabase::get_hint(bool up) | |||
| 		return nullptr; | ||||
| 	} | ||||
| 
 | ||||
|     // shift id
 | ||||
|     m_hint_id = (up ? m_hint_id + 1 : m_hint_id ); | ||||
|     m_hint_id %= m_loaded_hints.size(); | ||||
| 	try | ||||
| 	{ | ||||
| 		if (new_hint) | ||||
| 			m_hint_id = get_next(); | ||||
| 	} | ||||
| 	catch (const std::exception&) | ||||
| 	{ | ||||
| 		return nullptr; | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	AppConfig* app_config = wxGetApp().app_config; | ||||
| 	app_config->set("last_hint", std::to_string(m_hint_id)); | ||||
| 
 | ||||
| 	//data = &m_loaded_hints[m_hint_id];
 | ||||
| 	/*
 | ||||
|     data.text = m_loaded_hints[m_hint_id].text; | ||||
|     data.hypertext = m_loaded_hints[m_hint_id].hypertext; | ||||
| 	data.follow_text = m_loaded_hints[m_hint_id].follow_text; | ||||
|     data.callback = m_loaded_hints[m_hint_id].callback; | ||||
| 	*/ | ||||
|     return &m_loaded_hints[m_hint_id]; | ||||
| } | ||||
| 
 | ||||
| size_t HintDatabase::get_next() | ||||
| { | ||||
| 	if (!m_sorted_hints) | ||||
| 	{ | ||||
| 		auto compare_wieght = [](const HintData& a, const HintData& b){ return a.weight < b.weight; }; | ||||
| 		std::sort(m_loaded_hints.begin(), m_loaded_hints.end(), compare_wieght); | ||||
| 		m_sorted_hints = true; | ||||
| 		srand(time(NULL)); | ||||
| 	} | ||||
| 	std::vector<size_t> candidates; // index in m_loaded_hints
 | ||||
| 	// total weight
 | ||||
| 	size_t total_weight = 0; | ||||
| 	for (size_t i = 0; i < m_loaded_hints.size(); i++) { | ||||
| 		if (!m_loaded_hints[i].was_displayed && tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) { | ||||
| 			candidates.emplace_back(i); | ||||
| 			total_weight += m_loaded_hints[i].weight; | ||||
| 		} | ||||
| 	} | ||||
| 	// all were shown
 | ||||
| 	if (total_weight == 0) { | ||||
| 		clear_used(); | ||||
| 	 	for (size_t i = 0; i < m_loaded_hints.size(); i++) { | ||||
| 			m_loaded_hints[i].was_displayed = false; | ||||
| 			if (tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) { | ||||
| 				candidates.emplace_back(i); | ||||
| 				total_weight += m_loaded_hints[i].weight; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if (total_weight == 0) { | ||||
| 		BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed. No suitable hint was found."; | ||||
| 		throw std::exception(); | ||||
| 	} | ||||
| 	size_t random_number = rand() % total_weight + 1; | ||||
| 	size_t current_weight = 0; | ||||
| 	for (size_t i = 0; i < candidates.size(); i++) { | ||||
| 		current_weight += m_loaded_hints[candidates[i]].weight; | ||||
| 		if (random_number <= current_weight) { | ||||
| 			set_used(m_loaded_hints[candidates[i]].id_string); | ||||
| 			m_loaded_hints[candidates[i]].was_displayed = true; | ||||
| 			return candidates[i]; | ||||
| 		} | ||||
| 	} | ||||
| 	BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed."; | ||||
| 	throw std::exception(); | ||||
| } | ||||
| 
 | ||||
| bool HintDatabase::is_used(const std::string& id) | ||||
| { | ||||
| 	// load used ids from file
 | ||||
| 	if (!m_used_ids_loaded) { | ||||
| 		read_used_binary(m_used_ids); | ||||
| 		m_used_ids_loaded = true; | ||||
| 	} | ||||
| 	// check if id is in used
 | ||||
| 	for (const std::string& used_id : m_used_ids) { | ||||
| 		if (used_id == id) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| void HintDatabase::set_used(const std::string& id) | ||||
| { | ||||
| 	// check needed?
 | ||||
| 	if (!is_used(id)) | ||||
| 	{ | ||||
| 		m_used_ids.emplace_back(id); | ||||
| 	} | ||||
| } | ||||
| void HintDatabase::clear_used() | ||||
| { | ||||
| 	m_used_ids.clear(); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::HintNotification::count_spaces() | ||||
| { | ||||
| 	//determine line width 
 | ||||
|  | @ -844,23 +989,12 @@ void NotificationManager::HintNotification::open_documentation() | |||
| 		launch_browser_if_allowed(m_documentation_link); | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::HintNotification::retrieve_data(int recursion_counter) | ||||
| void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true*/) | ||||
| { | ||||
|     HintData* hint_data = HintDatabase::get_instance().get_hint(recursion_counter >= 0 ? true : false); | ||||
|     HintData* hint_data = HintDatabase::get_instance().get_hint(new_hint); | ||||
| 	if (hint_data == nullptr) | ||||
| 		 close(); | ||||
| 
 | ||||
| 	if (hint_data != nullptr && !tags_check(hint_data->disabled_tags, hint_data->enabled_tags)) | ||||
| 	{ | ||||
| 		// Content for different user - retrieve another
 | ||||
| 		size_t count = HintDatabase::get_instance().get_count(); | ||||
| 		if ((int)count < recursion_counter) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << "Hint notification failed to load data due to recursion counter."; | ||||
| 		} else { | ||||
| 			retrieve_data(recursion_counter + 1); | ||||
| 		} | ||||
| 		return; | ||||
| 	} | ||||
| 	if(hint_data != nullptr) | ||||
|     { | ||||
|         NotificationData nd { NotificationType::DidYouKnowHint, | ||||
|  |  | |||
|  | @ -9,7 +9,10 @@ namespace GUI { | |||
| // Database of hints updatable
 | ||||
| struct HintData | ||||
| { | ||||
| 	std::string        id_string; | ||||
| 	std::string        text; | ||||
| 	size_t			   weight; | ||||
| 	bool               was_displayed; | ||||
| 	std::string        hypertext; | ||||
| 	std::string		   follow_text; | ||||
| 	std::string		   disabled_tags; | ||||
|  | @ -33,11 +36,12 @@ private: | |||
| 		: m_hint_id(0) | ||||
| 	{} | ||||
| public: | ||||
| 	~HintDatabase(); | ||||
| 	HintDatabase(HintDatabase const&) = delete; | ||||
| 	void operator=(HintDatabase const&) = delete; | ||||
| 
 | ||||
| 	// return true if HintData filled;
 | ||||
| 	HintData* get_hint(bool up = true); | ||||
| 	HintData* get_hint(bool new_hint = true); | ||||
| 	size_t    get_count() {  | ||||
| 		if (!m_initialized)  | ||||
| 			return 0; | ||||
|  | @ -46,10 +50,17 @@ public: | |||
| private: | ||||
| 	void	init(); | ||||
| 	void	load_hints_from_file(const boost::filesystem::path& path); | ||||
| 	bool    is_used(const std::string& id); | ||||
| 	void    set_used(const std::string& id); | ||||
| 	void    clear_used(); | ||||
| 	// Returns position in m_loaded_hints with next hint chosed randomly with weights
 | ||||
| 	size_t  get_next(); | ||||
| 	size_t						m_hint_id; | ||||
| 	bool						m_initialized { false }; | ||||
| 	std::vector<HintData>       m_loaded_hints; | ||||
| 
 | ||||
| 	bool						m_sorted_hints { false }; | ||||
| 	std::vector<std::string>    m_used_ids; | ||||
| 	bool                        m_used_ids_loaded { false }; | ||||
| }; | ||||
| // Notification class - shows current Hint ("Did you know") 
 | ||||
| class NotificationManager::HintNotification : public NotificationManager::PopNotification | ||||
|  | @ -58,10 +69,10 @@ public: | |||
| 	HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint) | ||||
| 		: PopNotification(n, id_provider, evt_handler) | ||||
| 	{ | ||||
| 		retrieve_data(new_hint ? 0 : -1); | ||||
| 		retrieve_data(new_hint); | ||||
| 	} | ||||
| 	virtual void	init() override; | ||||
| 	void			open_next() { retrieve_data(0); } | ||||
| 	void			open_next() { retrieve_data(); } | ||||
| protected: | ||||
| 	virtual void	set_next_window_size(ImGuiWrapper& imgui) override; | ||||
| 	virtual void	count_spaces() override; | ||||
|  | @ -87,7 +98,7 @@ protected: | |||
| 								const float win_size_x, const float win_size_y, | ||||
| 								const float win_pos_x, const float win_pos_y); | ||||
| 	// recursion counter -1 tells to retrieve same hint as last time
 | ||||
| 	void			retrieve_data(int recursion_counter = 0); | ||||
| 	void			retrieve_data(bool new_hint = true); | ||||
| 	void			open_documentation(); | ||||
| 
 | ||||
| 	bool						m_has_hint_data { false }; | ||||
|  |  | |||
|  | @ -147,26 +147,28 @@ void FillBedJob::finalize() | |||
| 
 | ||||
|     size_t inst_cnt = model_object->instances.size(); | ||||
| 
 | ||||
|     for (ArrangePolygon &ap : m_selected) { | ||||
|         if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0)) | ||||
|             ap.apply(); | ||||
|     } | ||||
|     int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, [](int s, auto &ap) { | ||||
|         return s + int(ap.priority == 0 && ap.bed_idx == 0); | ||||
|     }); | ||||
| 
 | ||||
|     model_object->ensure_on_bed(); | ||||
|     if (added_cnt > 0) { | ||||
|         for (ArrangePolygon &ap : m_selected) { | ||||
|             if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0)) | ||||
|                 ap.apply(); | ||||
|         } | ||||
| 
 | ||||
|     m_plater->update(); | ||||
|         model_object->ensure_on_bed(); | ||||
| 
 | ||||
|     int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, | ||||
|                                      [](int s, auto &ap) { | ||||
|                                          return s + int(ap.priority == 0 && ap.bed_idx == 0); | ||||
|                                      }); | ||||
|         m_plater->update(); | ||||
| 
 | ||||
|     // FIXME: somebody explain why this is needed for increase_object_instances
 | ||||
|     if (inst_cnt == 1) added_cnt++; | ||||
|         // FIXME: somebody explain why this is needed for increase_object_instances
 | ||||
|         if (inst_cnt == 1) added_cnt++; | ||||
| 
 | ||||
|     if (added_cnt > 0) | ||||
|         m_plater->sidebar() | ||||
|             .obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt)); | ||||
|     } | ||||
| 
 | ||||
|     Job::finalize(); | ||||
| } | ||||
| 
 | ||||
| }} // namespace Slic3r::GUI
 | ||||
|  |  | |||
|  | @ -621,7 +621,7 @@ void MainFrame::update_title() | |||
|         if (!dirty_marker.empty() || !project.empty()) { | ||||
|             if (!dirty_marker.empty() && project.empty()) | ||||
|                 project = _("Untitled"); | ||||
|                 title = dirty_marker + project + " - "; | ||||
|             title = dirty_marker + project + " - "; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -545,7 +545,6 @@ private: | |||
| 		NotificationType::PlaterWarning,  | ||||
| 		NotificationType::ProgressBar,  | ||||
| 		NotificationType::PrintHostUpload,  | ||||
| 		NotificationType::UpdatedItemsInfo, | ||||
|         NotificationType::SimplifySuggestion | ||||
| 	}; | ||||
| 	//prepared (basic) notifications
 | ||||
|  |  | |||
|  | @ -396,6 +396,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr | |||
|         m_optgroup->append_line(cafile_hint); | ||||
|     } | ||||
|     else { | ||||
|          | ||||
|         Line line{ "", "" }; | ||||
|         line.full_width = 1; | ||||
| 
 | ||||
|  | @ -411,7 +412,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr | |||
|             sizer->Add(txt, 1, wxEXPAND); | ||||
|             return sizer; | ||||
|         }; | ||||
| 
 | ||||
|         m_optgroup->append_line(line); | ||||
|     } | ||||
| 
 | ||||
|  | @ -421,6 +421,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr | |||
|         m_optgroup->append_single_option_line(option); | ||||
|     } | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
|     option = m_optgroup->get_option("printhost_ssl_ignore_revoke"); | ||||
|     option.opt.width = Field::def_width_wider(); | ||||
|     m_optgroup->append_single_option_line(option); | ||||
| #endif | ||||
| 
 | ||||
|     m_optgroup->activate(); | ||||
| 
 | ||||
|     Field* printhost_field = m_optgroup->get_field("print_host"); | ||||
|  |  | |||
|  | @ -1643,7 +1643,7 @@ struct Plater::priv | |||
|     BoundingBox scaled_bed_shape_bb() const; | ||||
| 
 | ||||
|     std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false); | ||||
|     std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool force_center_on_bed = false); | ||||
|     std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); | ||||
| 
 | ||||
|     wxString get_export_file(GUI::FileType file_type); | ||||
| 
 | ||||
|  | @ -1658,6 +1658,7 @@ struct Plater::priv | |||
|     void deselect_all(); | ||||
|     void remove(size_t obj_idx); | ||||
|     void delete_object_from_model(size_t obj_idx); | ||||
|     void delete_all_objects_from_model(); | ||||
|     void reset(); | ||||
|     void mirror(Axis axis); | ||||
|     void split_object(); | ||||
|  | @ -1944,7 +1945,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|         // 3DScene/Toolbar:
 | ||||
|         view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); | ||||
|         view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); | ||||
|         view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); | ||||
|         view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { delete_all_objects_from_model(); }); | ||||
| //        view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
 | ||||
|         view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); | ||||
|         view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); | ||||
|         view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); | ||||
|  | @ -2426,7 +2428,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
|             } | ||||
| 
 | ||||
|             if (one_by_one) { | ||||
|                 auto loaded_idxs = load_model_objects(model.objects, is_project_file, !is_project_file); | ||||
|                 if (type_3mf && !is_project_file) | ||||
|                     model.center_instances_around_point(bed_shape_bb().center()); | ||||
|                 auto loaded_idxs = load_model_objects(model.objects, is_project_file); | ||||
|                 obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); | ||||
|             } else { | ||||
|                 // This must be an .stl or .obj file, which may contain a maximum of one volume.
 | ||||
|  | @ -2481,7 +2485,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
| 
 | ||||
| // #define AUTOPLACEMENT_ON_LOAD
 | ||||
| 
 | ||||
| std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z, bool force_center_on_bed) | ||||
| std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z) | ||||
| { | ||||
|     const BoundingBoxf bed_shape = bed_shape_bb(); | ||||
|     const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones(); | ||||
|  | @ -2541,9 +2545,6 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode | |||
|         object->ensure_on_bed(allow_negative_z); | ||||
|     } | ||||
| 
 | ||||
|     if (force_center_on_bed) | ||||
|         model.center_instances_around_point(bed_shape.center()); | ||||
| 
 | ||||
| #ifdef AUTOPLACEMENT_ON_LOAD | ||||
|     // FIXME distance should be a config value /////////////////////////////////
 | ||||
|     auto min_obj_distance = static_cast<coord_t>(6/SCALING_FACTOR); | ||||
|  | @ -2756,6 +2757,32 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) | |||
|     object_list_changed(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::delete_all_objects_from_model() | ||||
| { | ||||
|     Plater::TakeSnapshot snapshot(q, _L("Delete All Objects")); | ||||
| 
 | ||||
|     if (view3D->is_layers_editing_enabled()) | ||||
|         view3D->enable_layers_editing(false); | ||||
| 
 | ||||
|     reset_gcode_toolpaths(); | ||||
|     gcode_result.reset(); | ||||
| 
 | ||||
|     view3D->get_canvas3d()->reset_sequential_print_clearance(); | ||||
| 
 | ||||
|     // Stop and reset the Print content.
 | ||||
|     background_process.reset(); | ||||
|     model.clear_objects(); | ||||
|     update(); | ||||
|     // Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
 | ||||
|     sidebar->obj_list()->delete_all_objects_from_list(); | ||||
|     object_list_changed(); | ||||
| 
 | ||||
|     // The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
 | ||||
|     sidebar->show_sliced_info_sizer(false); | ||||
| 
 | ||||
|     model.custom_gcode_per_print_z.gcodes.clear(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::reset() | ||||
| { | ||||
|     Plater::TakeSnapshot snapshot(q, _L("Reset Project")); | ||||
|  | @ -2947,6 +2974,9 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool | |||
|     if (view3D->is_layers_editing_enabled()) | ||||
|         view3D->get_wxglcanvas()->Refresh(); | ||||
| 
 | ||||
|     if (background_process.empty()) | ||||
|         view3D->get_canvas3d()->reset_sequential_print_clearance(); | ||||
| 
 | ||||
|     if (invalidated == Print::APPLY_STATUS_INVALIDATED) { | ||||
|         // Some previously calculated data on the Print was invalidated.
 | ||||
|         // Hide the slicing results, as the current slicing status is no more valid.
 | ||||
|  | @ -4759,10 +4789,8 @@ void Plater::load_project(const wxString& filename) | |||
|     std::vector<fs::path> input_paths; | ||||
|     input_paths.push_back(into_path(filename)); | ||||
| 
 | ||||
|     std::vector<size_t> res = load_files(input_paths); | ||||
| 
 | ||||
|     // if res is empty no data has been loaded
 | ||||
|     if (!res.empty()) { | ||||
|     if (! load_files(input_paths).empty()) { | ||||
|         // At least one file was loaded.
 | ||||
|         p->set_project_filename(filename); | ||||
|         reset_project_dirty_initial_presets(); | ||||
|         update_project_dirty_from_presets(); | ||||
|  | @ -4797,8 +4825,7 @@ void Plater::add_model(bool imperial_units/* = false*/) | |||
|     } | ||||
| 
 | ||||
|     Plater::TakeSnapshot snapshot(this, snapshot_label); | ||||
|     std::vector<size_t> res = load_files(paths, true, false, imperial_units); | ||||
|     if (!res.empty()) | ||||
|     if (! load_files(paths, true, false, imperial_units).empty()) | ||||
|         wxGetApp().mainframe->update_title(); | ||||
| } | ||||
| 
 | ||||
|  | @ -4933,7 +4960,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename) | |||
|         _L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10); | ||||
| 
 | ||||
|     m_action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), | ||||
|         static_cast<int>(LoadType::OpenProject) - 1, static_cast<int>(LoadType::LoadConfig)) - 1; | ||||
|         static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1; | ||||
| 
 | ||||
|     wxStaticBox* action_stb = new wxStaticBox(this, wxID_ANY, _L("Action")); | ||||
|     if (!wxOSX) action_stb->SetBackgroundStyle(wxBG_STYLE_PAINT); | ||||
|  | @ -6228,8 +6255,7 @@ void Plater::changed_object(int obj_idx) | |||
|     if (obj_idx < 0) | ||||
|         return; | ||||
|     // recenter and re - align to Z = 0
 | ||||
|     auto model_object = p->model.objects[obj_idx]; | ||||
|     model_object->ensure_on_bed(this->p->printer_technology != ptSLA); | ||||
|     p->model.objects[obj_idx]->ensure_on_bed(p->printer_technology != ptSLA); | ||||
|     if (this->p->printer_technology == ptSLA) { | ||||
|         // Update the SLAPrint from the current Model, so that the reload_scene()
 | ||||
|         // pulls the correct data, update the 3D scene.
 | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac | |||
|     const size_t active_snapshot_time = stack.active_snapshot_time(); | ||||
|     const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time)); | ||||
|     const int idx = it - snapshots.begin() - 1; | ||||
|     const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && (size_t)idx < snapshots.size() - 1) ? | ||||
|     const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && idx < int(snapshots.size()) - 1) ? | ||||
|         &snapshots[idx] : nullptr; | ||||
| 
 | ||||
|     assert(ret != nullptr); | ||||
|  | @ -195,8 +195,7 @@ void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type) | |||
| void ProjectDirtyStateManager::update_from_presets() | ||||
| { | ||||
|     m_state.presets = false; | ||||
|     std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets(); | ||||
|     for (const auto& [type, name] : selected_presets) { | ||||
|     for (const auto& [type, name] : wxGetApp().get_selected_presets()) { | ||||
|         m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; | ||||
|     } | ||||
|     m_state.presets |= wxGetApp().has_unsaved_preset_changes(); | ||||
|  | @ -214,6 +213,7 @@ void ProjectDirtyStateManager::reset_after_save() | |||
|         m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0; | ||||
|     } | ||||
|     else { | ||||
|         // Gizmo is active with its own Undo / Redo stack (for example the SLA support point editing gizmo).
 | ||||
|         const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); | ||||
|         if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { | ||||
|             if (m_state.gizmos.current) | ||||
|  | @ -231,8 +231,7 @@ void ProjectDirtyStateManager::reset_after_save() | |||
| void ProjectDirtyStateManager::reset_initial_presets() | ||||
| { | ||||
|     m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>(); | ||||
|     std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets(); | ||||
|     for (const auto& [type, name] : selected_presets) { | ||||
|     for (const auto& [type, name] : wxGetApp().get_selected_presets()) { | ||||
|         m_initial_presets[type] = name; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -683,7 +683,8 @@ void Selection::translate(const Vec3d& displacement, bool local) | |||
|         synchronize_unselected_volumes(); | ||||
| #endif // !DISABLE_INSTANCES_SYNCH
 | ||||
| 
 | ||||
|     this->set_bounding_boxes_dirty(); | ||||
|     ensure_not_below_bed(); | ||||
|     set_bounding_boxes_dirty(); | ||||
| } | ||||
| 
 | ||||
| // Rotate an object around one of the axes. Only one rotation component is expected to be changing.
 | ||||
|  | @ -1148,6 +1149,7 @@ void Selection::erase() | |||
|         } | ||||
| 
 | ||||
|         wxGetApp().obj_list()->delete_from_model_and_list(items); | ||||
|         ensure_not_below_bed(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -1712,7 +1714,7 @@ void Selection::calc_unscaled_instance_bounding_box() const | |||
|             if (volume.is_modifier) | ||||
|                 continue; | ||||
|             Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); | ||||
|             trafo.translation()(2) += volume.get_sla_shift_z(); | ||||
|             trafo.translation().z() += volume.get_sla_shift_z(); | ||||
|             unscaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo)); | ||||
|         } | ||||
|     } | ||||
|  | @ -1729,7 +1731,7 @@ void Selection::calc_scaled_instance_bounding_box() const | |||
|             if (volume.is_modifier) | ||||
|                 continue; | ||||
|             Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); | ||||
|             trafo.translation()(2) += volume.get_sla_shift_z(); | ||||
|             trafo.translation().z() += volume.get_sla_shift_z(); | ||||
|             scaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo)); | ||||
|         } | ||||
|     } | ||||
|  | @ -2134,6 +2136,32 @@ void Selection::ensure_on_bed() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void Selection::ensure_not_below_bed() | ||||
| { | ||||
|     typedef std::map<std::pair<int, int>, double> InstancesToZMap; | ||||
|     InstancesToZMap instances_max_z; | ||||
| 
 | ||||
|     for (size_t i = 0; i < m_volumes->size(); ++i) { | ||||
|         GLVolume* volume = (*m_volumes)[i]; | ||||
|         if (!volume->is_wipe_tower && !volume->is_modifier) { | ||||
|             const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); | ||||
|             std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx()); | ||||
|             InstancesToZMap::iterator it = instances_max_z.find(instance); | ||||
|             if (it == instances_max_z.end()) | ||||
|                 it = instances_max_z.insert(InstancesToZMap::value_type(instance, -DBL_MAX)).first; | ||||
| 
 | ||||
|             it->second = std::max(it->second, max_z); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (GLVolume* volume : *m_volumes) { | ||||
|         std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx()); | ||||
|         InstancesToZMap::iterator it = instances_max_z.find(instance); | ||||
|         if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD) | ||||
|             volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const | ||||
| { | ||||
|     struct SameInstance | ||||
|  |  | |||
|  | @ -385,6 +385,7 @@ public: | |||
| 
 | ||||
| private: | ||||
|     void ensure_on_bed(); | ||||
|     void ensure_not_below_bed(); | ||||
|     bool is_from_fully_selected_instance(unsigned int volume_idx) const; | ||||
| 
 | ||||
|     void paste_volumes_from_clipboard(); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Filip Sykala
						Filip Sykala