mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-30 12:11:15 -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