Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2021-05-13 14:10:42 +02:00
commit 6bddacee44
90 changed files with 4276 additions and 4185 deletions

View file

@ -189,6 +189,10 @@ set(SLIC3R_GUI_SOURCES
GUI/UnsavedChangesDialog.hpp
GUI/ExtraRenderers.cpp
GUI/ExtraRenderers.hpp
GUI/ProjectDirtyStateManager.hpp
GUI/ProjectDirtyStateManager.cpp
GUI/DesktopIntegrationDialog.cpp
GUI/DesktopIntegrationDialog.hpp
Utils/Http.cpp
Utils/Http.hpp
Utils/FixModelByWin10.cpp

View file

@ -23,6 +23,9 @@
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
#include "libslic3r/PresetBundle.hpp"
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
#include <stdio.h>
#include <stdlib.h>
@ -345,9 +348,16 @@ void GLVolume::set_render_color(const float* rgba, unsigned int size)
void GLVolume::set_render_color()
{
if (force_native_color || force_neutral_color)
{
#if ENABLE_ALLOW_NEGATIVE_Z
bool outside = is_outside || is_below_printbed();
#endif // ENABLE_ALLOW_NEGATIVE_Z
if (force_native_color || force_neutral_color) {
#if ENABLE_ALLOW_NEGATIVE_Z
if (outside && shader_outside_printer_detection_enabled)
#else
if (is_outside && shader_outside_printer_detection_enabled)
#endif // ENABLE_ALLOW_NEGATIVE_Z
set_render_color(OUTSIDE_COLOR, 4);
else {
if (force_native_color)
@ -362,17 +372,24 @@ void GLVolume::set_render_color()
else if (hover == HS_Deselect)
set_render_color(HOVER_DESELECT_COLOR, 4);
else if (selected)
#if ENABLE_ALLOW_NEGATIVE_Z
set_render_color(outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4);
#else
set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4);
#endif // ENABLE_ALLOW_NEGATIVE_Z
else if (disabled)
set_render_color(DISABLED_COLOR, 4);
#if ENABLE_ALLOW_NEGATIVE_Z
else if (outside && shader_outside_printer_detection_enabled)
#else
else if (is_outside && shader_outside_printer_detection_enabled)
#endif // ENABLE_ALLOW_NEGATIVE_Z
set_render_color(OUTSIDE_COLOR, 4);
else
set_render_color(color, 4);
}
if (!printable)
{
if (!printable) {
render_color[0] /= 4;
render_color[1] /= 4;
render_color[2] /= 4;
@ -504,6 +521,25 @@ void GLVolume::render() const
bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); }
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); }
#if ENABLE_ALLOW_NEGATIVE_Z
bool GLVolume::is_sinking() const
{
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
if (is_modifier || GUI::wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA)
#else
if (is_modifier)
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
return false;
const BoundingBoxf3& box = transformed_convex_hull_bounding_box();
return box.min(2) < -EPSILON && box.max(2) >= -EPSILON;
}
bool GLVolume::is_below_printbed() const
{
return transformed_convex_hull_bounding_box().max(2) < 0.0;
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
std::vector<int> GLVolumeCollection::load_object(
const ModelObject *model_object,
int obj_idx,
@ -778,6 +814,9 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix());
shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower);
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
#if ENABLE_ALLOW_NEGATIVE_Z
shader->set_uniform("sinking", volume.first->is_sinking());
#endif // ENABLE_ALLOW_NEGATIVE_Z
volume.first->render();
}

View file

@ -453,6 +453,11 @@ public:
bool is_sla_support() const;
bool is_sla_pad() const;
#if ENABLE_ALLOW_NEGATIVE_Z
bool is_sinking() const;
bool is_below_printbed() const;
#endif // ENABLE_ALLOW_NEGATIVE_Z
// Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const {
//FIXME what to do wih m_convex_hull?

View file

@ -361,7 +361,7 @@ bool BackgroundSlicingProcess::stop()
// m_print->state_mutex() shall NOT be held. Unfortunately there is no interface to test for it.
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// this->m_export_path.clear();
// m_export_path.clear();
return false;
}
// assert(this->running());
@ -379,7 +379,7 @@ bool BackgroundSlicingProcess::stop()
m_state = STATE_IDLE;
m_print->set_cancel_callback([](){});
}
// this->m_export_path.clear();
// m_export_path.clear();
return true;
}
@ -496,7 +496,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
assert(config.opt_enum<PrinterTechnology>("printer_technology") == m_print->technology());
Print::ApplyStatus invalidated = m_print->apply(model, config);
if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF &&
!this->m_fff_print->is_step_done(psGCodeExport)) {
!m_fff_print->is_step_done(psGCodeExport)) {
// Some FFF status was invalidated, and the G-code was not exported yet.
// Let the G-code preview UI know that the final G-code preview is not valid.
// In addition, this early memory deallocation reduces memory footprint.
@ -621,7 +621,7 @@ ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParam
{
ThumbnailsList thumbnails;
if (m_thumbnail_cb)
this->execute_ui_task([this, &params, &thumbnails](){ thumbnails = this->m_thumbnail_cb(params); });
this->execute_ui_task([this, &params, &thumbnails](){ thumbnails = m_thumbnail_cb(params); });
return thumbnails;
}

View file

@ -91,6 +91,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
DynamicPrintConfig new_conf = *config;
auto answer = dialog.ShowModal();
bool support = true;
if (!is_global_config || answer == wxID_YES) {
new_conf.set_key_value("perimeters", new ConfigOptionInt(1));
new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0));
@ -100,13 +101,17 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(true));
new_conf.set_key_value("thin_walls", new ConfigOptionBool(false));
fill_density = 0;
support = false;
}
else {
new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false));
}
apply(config, &new_conf);
if (cb_value_change)
if (cb_value_change) {
cb_value_change("fill_density", fill_density);
if (!support)
cb_value_change("support_material", false);
}
}
if (config->opt_bool("wipe_tower") && config->opt_bool("support_material") &&

View file

@ -26,14 +26,18 @@
#include <wx/wupdlock.h>
#include <wx/debug.h>
#include "libslic3r/Platform.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Config.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Utils.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "Field.hpp"
#include "DesktopIntegrationDialog.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "format.hpp"
#if defined(__linux__) && defined(__WXGTK3__)
#define wxLinux_gtk3 true
@ -450,7 +454,6 @@ void ConfigWizardPage::append_spacer(int space)
content->AddSpacer(space);
}
// Wizard pages
PageWelcome::PageWelcome(ConfigWizard *parent)
@ -469,9 +472,21 @@ PageWelcome::PageWelcome(ConfigWizard *parent)
, cbox_reset(append(
new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)"))
))
, cbox_integrate(append(
new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system)."))
))
{
welcome_text->Hide();
cbox_reset->Hide();
#ifdef __linux__
if (!DesktopIntegrationDialog::is_integrated())
cbox_integrate->Show(true);
else
cbox_integrate->Hide();
#else
cbox_integrate->Hide();
#endif
}
void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
@ -479,6 +494,14 @@ void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
welcome_text->Show(data_empty);
cbox_reset->Show(!data_empty);
#ifdef __linux__
if (!DesktopIntegrationDialog::is_integrated())
cbox_integrate->Show(true);
else
cbox_integrate->Hide();
#else
cbox_integrate->Hide();
#endif
}
@ -1361,20 +1384,39 @@ void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model));
}
static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value)
{
e.Skip();
wxString str = ctrl->GetValue();
// Replace the first occurence of comma in decimal number.
bool was_replace = str.Replace(",", ".", false) > 0;
double val = 0.0;
if (!str.ToCDouble(&val)) {
if (val == 0.0)
val = def_value;
ctrl->SetValue(double_to_string(val));
show_error(nullptr, _L("Invalid numeric input."));
ctrl->SetFocus();
}
else if (was_replace)
ctrl->SetValue(double_to_string(val));
}
PageDiameters::PageDiameters(ConfigWizard *parent)
: ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1)
, spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY))
, spin_filam(new wxSpinCtrlDouble(this, wxID_ANY))
, diam_nozzle(new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord)))
, diam_filam (new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord)))
{
spin_nozzle->SetDigits(2);
spin_nozzle->SetIncrement(0.1);
auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value<ConfigOptionFloats>();
spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
diam_nozzle->SetValue(value);
spin_filam->SetDigits(2);
spin_filam->SetIncrement(0.25);
auto *default_filam = print_config_def.get("filament_diameter")->get_default_value<ConfigOptionFloats>();
spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
diam_filam->SetValue(value);
diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId());
diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId());
append_text(_L("Enter the diameter of your printer's hot end nozzle."));
@ -1383,7 +1425,7 @@ PageDiameters::PageDiameters(ConfigWizard *parent)
auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm"));
sizer_nozzle->AddGrowableCol(0, 1);
sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
sizer_nozzle->Add(spin_nozzle);
sizer_nozzle->Add(diam_nozzle);
sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_nozzle);
@ -1397,16 +1439,21 @@ PageDiameters::PageDiameters(ConfigWizard *parent)
auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm"));
sizer_filam->AddGrowableCol(0, 1);
sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
sizer_filam->Add(spin_filam);
sizer_filam->Add(diam_filam);
sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_filam);
}
void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
{
auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue());
double val = 0.0;
diam_nozzle->GetValue().ToCDouble(&val);
auto *opt_nozzle = new ConfigOptionFloats(1, val);
config.set_key_value("nozzle_diameter", opt_nozzle);
auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue());
val = 0.0;
diam_filam->GetValue().ToCDouble(&val);
auto * opt_filam = new ConfigOptionFloats(1, val);
config.set_key_value("filament_diameter", opt_filam);
auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) {
@ -2373,6 +2420,12 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
}
}
#ifdef __linux__
// Desktop integration on Linux
if (page_welcome->integrate_desktop())
DesktopIntegrationDialog::perform_desktop_integration();
#endif
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
bool snapshot = true;
Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE;
@ -2490,7 +2543,6 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
}
void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
{
const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;

View file

@ -45,7 +45,6 @@ public:
bool run(RunReason reason, StartPage start_page = SP_WELCOME);
static const wxString& name(const bool from_menu = false);
protected:
void on_dpi_changed(const wxRect &suggested_rect) override ;

View file

@ -227,10 +227,12 @@ struct PageWelcome: ConfigWizardPage
{
wxStaticText *welcome_text;
wxCheckBox *cbox_reset;
wxCheckBox *cbox_integrate;
PageWelcome(ConfigWizard *parent);
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
bool integrate_desktop() const { return cbox_integrate != nullptr ? cbox_integrate->GetValue() : false; }
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
};
@ -449,8 +451,8 @@ struct PageBedShape: ConfigWizardPage
struct PageDiameters: ConfigWizardPage
{
wxSpinCtrlDouble *spin_nozzle;
wxSpinCtrlDouble *spin_filam;
wxTextCtrl *diam_nozzle;
wxTextCtrl *diam_filam;
PageDiameters(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
@ -615,7 +617,9 @@ struct ConfigWizard::priv
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
#ifdef __linux__
void perform_desktop_integration() const;
#endif
bool check_fff_selected(); // Used to decide whether to display Filaments page
bool check_sla_selected(); // Used to decide whether to display SLA Materials page

View file

@ -0,0 +1,408 @@
#ifdef __linux__
#include "DesktopIntegrationDialog.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include "NotificationManager.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Platform.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <wx/filename.h>
#include <wx/stattext.h>
namespace Slic3r {
namespace GUI {
namespace integrate_desktop_internal{
// Disects path strings stored in system variable divided by ':' and adds into vector
static void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
{
wxString wxdirs;
if (! wxGetEnv(boost::nowide::widen(var), &wxdirs) || wxdirs.empty() )
return;
std::string dirs = boost::nowide::narrow(wxdirs);
for (size_t i = dirs.find(':'); i != std::string::npos; i = dirs.find(':'))
{
paths.push_back(dirs.substr(0, i));
if (dirs.size() > i+1)
dirs = dirs.substr(i+1);
}
paths.push_back(dirs);
}
// Return true if directory in path p+dir_name exists
static bool contains_path_dir(const std::string& p, const std::string& dir_name)
{
if (p.empty() || dir_name.empty())
return false;
boost::filesystem::path path(p + (p[p.size()-1] == '/' ? "" : "/") + dir_name);
if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)) {
//BOOST_LOG_TRIVIAL(debug) << path.string() << " " << std::oct << boost::filesystem::status(path).permissions();
return true; //boost::filesystem::status(path).permissions() & boost::filesystem::owner_write;
} else
BOOST_LOG_TRIVIAL(debug) << path.string() << " doesnt exists";
return false;
}
// Creates directory in path if not exists yet
static void create_dir(const boost::filesystem::path& path)
{
if (boost::filesystem::exists(path))
return;
BOOST_LOG_TRIVIAL(debug)<< "creating " << path.string();
boost::system::error_code ec;
boost::filesystem::create_directory(path, ec);
if (ec)
BOOST_LOG_TRIVIAL(error)<< "create directory failed: " << ec.message();
}
// Starts at basic_path (excluded) and creates all directories in dir_path
static void create_path(const std::string& basic_path, const std::string& dir_path)
{
if (basic_path.empty() || dir_path.empty())
return;
boost::filesystem::path path(basic_path);
std::string dirs = dir_path;
for (size_t i = dirs.find('/'); i != std::string::npos; i = dirs.find('/'))
{
std::string dir = dirs.substr(0, i);
path = boost::filesystem::path(path.string() +"/"+ dir);
create_dir(path);
dirs = dirs.substr(i+1);
}
path = boost::filesystem::path(path.string() +"/"+ dirs);
create_dir(path);
}
// Calls our internal copy_file function to copy file at icon_path to dest_path
static bool copy_icon(const std::string& icon_path, const std::string& dest_path)
{
BOOST_LOG_TRIVIAL(debug) <<"icon from "<< icon_path;
BOOST_LOG_TRIVIAL(debug) <<"icon to "<< dest_path;
std::string error_message;
auto cfr = copy_file(icon_path, dest_path, error_message, false);
if (cfr) {
BOOST_LOG_TRIVIAL(debug) << "Copy icon fail(" << cfr << "): " << error_message;
return false;
}
BOOST_LOG_TRIVIAL(debug) << "Copy icon success.";
return true;
}
// Creates new file filled with data.
static bool create_desktop_file(const std::string& path, const std::string& data)
{
BOOST_LOG_TRIVIAL(debug) <<".desktop to "<< path;
std::ofstream output(path);
output << data;
struct stat buffer;
if (stat(path.c_str(), &buffer) == 0)
{
BOOST_LOG_TRIVIAL(debug) << "Desktop file created.";
return true;
}
BOOST_LOG_TRIVIAL(debug) << "Desktop file NOT created.";
return false;
}
} // namespace integratec_desktop_internal
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
bool DesktopIntegrationDialog::is_integrated()
{
const char *appimage_env = std::getenv("APPIMAGE");
if (!appimage_env)
return false;
const AppConfig *app_config = wxGetApp().app_config;
std::string path(app_config->get("desktop_integration_app_path"));
BOOST_LOG_TRIVIAL(debug) << "Desktop integration desktop file path: " << path;
if (path.empty())
return false;
// confirmation that PrusaSlicer.desktop exists
struct stat buffer;
return (stat (path.c_str(), &buffer) == 0);
}
bool DesktopIntegrationDialog::integration_possible()
{
const char *appimage_env = std::getenv("APPIMAGE");
if (!appimage_env)
return false;
return true;
}
void DesktopIntegrationDialog::perform_desktop_integration()
{
BOOST_LOG_TRIVIAL(debug) << "performing desktop integration";
// Path to appimage
const char *appimage_env = std::getenv("APPIMAGE");
std::string appimage_path;
if (appimage_env) {
try {
appimage_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
} catch (std::exception &) {
}
} else {
// not appimage - not performing
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - not Appimage executable.";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
return;
}
// Find directories icons and applications
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
std::vector<std::string>target_candidates;
integrate_desktop_internal::resolve_path_from_var("XDG_DATA_HOME", target_candidates);
integrate_desktop_internal::resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
AppConfig *app_config = wxGetApp().app_config;
// suffix string to create different desktop file for alpha, beta.
std::string version_suffix;
std::string name_suffix;
std::string version(SLIC3R_VERSION);
if (version.find("alpha") != std::string::npos)
{
version_suffix = "-alpha";
name_suffix = " - alpha";
}else if (version.find("beta") != std::string::npos)
{
version_suffix = "-beta";
name_suffix = " - beta";
}
// theme path to icon destination
std::string icon_theme_path;
std::string icon_theme_dirs;
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
icon_theme_path = "hicolor/96x96/apps/";
icon_theme_dirs = "/hicolor/96x96/apps";
}
std::string target_dir_icons;
std::string target_dir_desktop;
// slicer icon
// iterate thru target_candidates to find icons folder
for (size_t i = 0; i < target_candidates.size(); ++i) {
// Copy icon PrusaSlicer.png from resources_dir()/icons to target_dir_icons/icons/
if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "icons")) {
target_dir_icons = target_candidates[i];
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (integrate_desktop_internal::copy_icon(icon_path, dest_path))
break; // success
else
target_dir_icons.clear(); // copying failed
// if all failed - try creating default home folder
if (i == target_candidates.size() - 1) {
// create $HOME/.local/share
integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
// copy icon
target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (!integrate_desktop_internal::contains_path_dir(target_dir_icons, "icons")
|| !integrate_desktop_internal::copy_icon(icon_path, dest_path)) {
// every attempt failed - icon wont be present
target_dir_icons.clear();
}
}
}
}
if(target_dir_icons.empty()) {
BOOST_LOG_TRIVIAL(error) << "Copying PrusaSlicer icon to icons directory failed.";
} else
// save path to icon
app_config->set("desktop_integration_icon_slicer_path", GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix));
// desktop file
// iterate thru target_candidates to find applications folder
for (size_t i = 0; i < target_candidates.size(); ++i)
{
if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
std::string desktop_file = GUI::format(
"[Desktop Entry]\n"
"Name=PrusaSlicer%1%\n"
"GenericName=3D Printing Software\n"
"Icon=PrusaSlicer%2%\n"
"Exec=%3% %%F\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
"Categories=Graphics;3DGraphics;Engineering;\n"
"Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
"StartupNotify=false\n"
"StartupWMClass=prusa-slicer", name_suffix, version_suffix, appimage_path);
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
if (integrate_desktop_internal::create_desktop_file(path, desktop_file)){
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicer.desktop file installation success.";
break;
} else {
// write failed - try another path
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer.desktop file installation failed.";
target_dir_desktop.clear();
}
// if all failed - try creating default home folder
if (i == target_candidates.size() - 1) {
// create $HOME/.local/share
integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
// create desktop file
target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
if (integrate_desktop_internal::contains_path_dir(target_dir_desktop, "applications")) {
if (!integrate_desktop_internal::create_desktop_file(path, desktop_file)) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file";
return;
}
} else {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
return;
}
}
}
}
if(target_dir_desktop.empty()) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
return;
}
// save path to desktop file
app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix));
// Repeat for Gcode viewer - use same paths as for slicer files
// Icon
if (!target_dir_icons.empty())
{
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer-gcodeviewer_192px.png",resources_dir());
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
if (integrate_desktop_internal::copy_icon(icon_path, dest_path))
// save path to icon
app_config->set("desktop_integration_icon_viewer_path", dest_path);
else
BOOST_LOG_TRIVIAL(error) << "Copying Gcode Viewer icon to icons directory failed.";
}
// Desktop file
std::string desktop_file = GUI::format(
"[Desktop Entry]\n"
"Name=Prusa Gcode Viewer%1%\n"
"GenericName=3D Printing Software\n"
"Icon=PrusaSlicer-gcodeviewer%2%\n"
"Exec=%3% --gcodeviwer %%F\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=text/x.gcode;\n"
"Categories=Graphics;3DGraphics;\n"
"Keywords=3D;Printing;Slicer;\n"
"StartupNotify=false", name_suffix, version_suffix, appimage_path);
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix);
if (integrate_desktop_internal::create_desktop_file(desktop_path, desktop_file))
// save path to desktop file
app_config->set("desktop_integration_app_viewer_path", desktop_path);
else {
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could create gcode viewer desktop file";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_desktop_intgration()
{
const char *appimage_env = std::getenv("APPIMAGE");
if (!appimage_env) {
BOOST_LOG_TRIVIAL(error) << "Undo desktop integration failed - not Appimage executable.";
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationFail);
return;
}
const AppConfig *app_config = wxGetApp().app_config;
// slicer .desktop
std::string path = std::string(app_config->get("desktop_integration_app_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// slicer icon
path = std::string(app_config->get("desktop_integration_icon_slicer_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// gcode viwer .desktop
path = std::string(app_config->get("desktop_integration_app_viewer_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// gcode viewer icon
path = std::string(app_config->get("desktop_integration_icon_viewer_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
}
DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
: wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
bool can_undo = DesktopIntegrationDialog::is_integrated();
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
wxString text = _L("Desktop Integration sets this binary to be searchable by the system.\n\nPress \"Perform\" to proceed.");
if (can_undo)
text += "\nPress \"Undo\" to remove previous integration.";
vbox->Add(
new wxStaticText( this, wxID_ANY, text),
// , wxDefaultPosition, wxSize(100,50), wxTE_MULTILINE),
1, // make vertically stretchable
wxEXPAND | // make horizontally stretchable
wxALL, // and make border all around
10 ); // set border width to 10
wxBoxSizer *btn_szr = new wxBoxSizer(wxHORIZONTAL);
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform"));
btn_szr->Add(btn_perform, 0, wxALL, 10);
btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); });
if (can_undo){
wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo"));
btn_szr->Add(btn_undo, 0, wxALL, 10);
btn_undo->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::undo_desktop_intgration(); EndModal(wxID_ANY); });
}
wxButton *btn_cancel = new wxButton(this, wxID_ANY, _L("Cancel"));
btn_szr->Add(btn_cancel, 0, wxALL, 10);
btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_ANY); });
vbox->Add(btn_szr, 0, wxALIGN_CENTER);
SetSizerAndFit(vbox);
}
DesktopIntegrationDialog::~DesktopIntegrationDialog()
{
}
} // namespace GUI
} // namespace Slic3r
#endif // __linux__

View file

@ -0,0 +1,39 @@
#ifdef __linux__
#ifndef slic3r_DesktopIntegrationDialog_hpp_
#define slic3r_DesktopIntegrationDialog_hpp_
#include <wx/dialog.h>
namespace Slic3r {
namespace GUI {
class DesktopIntegrationDialog : public wxDialog
{
public:
DesktopIntegrationDialog(wxWindow *parent);
DesktopIntegrationDialog(DesktopIntegrationDialog &&) = delete;
DesktopIntegrationDialog(const DesktopIntegrationDialog &) = delete;
DesktopIntegrationDialog &operator=(DesktopIntegrationDialog &&) = delete;
DesktopIntegrationDialog &operator=(const DesktopIntegrationDialog &) = delete;
~DesktopIntegrationDialog();
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
// returns true if path to PrusaSlicer.desktop is stored in App Config and existence of desktop file.
// Does not check if desktop file leads to this binary or existence of icons and viewer desktop file.
static bool is_integrated();
// true if appimage
static bool integration_possible();
// Creates Desktop files and icons for both PrusaSlicer and GcodeViewer.
// Stores paths into App Config.
// Rewrites if files already existed.
static void perform_desktop_integration();
// Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config.
static void undo_desktop_intgration();
private:
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_DesktopIntegrationDialog_hpp_
#endif // __linux__

View file

@ -326,10 +326,10 @@ double Control::get_double_value(const SelectedSlider& selection)
return m_values[selection == ssLower ? m_lower_value : m_higher_value];
}
int Control::get_tick_from_value(double value)
int Control::get_tick_from_value(double value, bool force_lower_bound/* = false*/)
{
std::vector<double>::iterator it;
if (m_is_wipe_tower)
if (m_is_wipe_tower && !force_lower_bound)
it = std::find_if(m_values.begin(), m_values.end(),
[value](const double & val) { return fabs(value - val) <= epsilon(); });
else
@ -575,7 +575,10 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_
else
is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3;
dc.DrawBitmap(*icon, x_draw, y_draw);
if (m_draw_mode == dmSequentialFffPrint)
dc.DrawBitmap(create_scaled_bitmap("colorchange_add", nullptr, 16, true), x_draw, y_draw);
else
dc.DrawBitmap(*icon, x_draw, y_draw);
//update rect of the tick action icon
m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim);
@ -591,7 +594,7 @@ void Control::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const Selec
dc.DrawLine(pt_beg, pt_end);
//draw action icon
if (m_draw_mode == dmRegular)
if (m_draw_mode == dmRegular || m_draw_mode == dmSequentialFffPrint)
draw_action_icon(dc, pt_beg, pt_end);
}
}
@ -1377,6 +1380,10 @@ wxString Control::get_tooltip(int tick/*=-1*/)
if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist
{
if (m_draw_mode == dmSequentialFffPrint)
return _L("The sequential print is on.\n"
"It's impossible to apply any custom G-code for objects printing sequentually.\n");
// Show mode as a first string of tooltop
tooltip = " " + _L("Print mode") + ": ";
tooltip += (m_mode == SingleExtruder ? SingleExtruderMode :
@ -2388,7 +2395,7 @@ void Control::edit_extruder_sequence()
extruder = 0;
if (m_extruders_sequence.is_mm_intervals) {
value += m_extruders_sequence.interval_by_mm;
tick = get_tick_from_value(value);
tick = get_tick_from_value(value, true);
if (tick < 0)
break;
}

View file

@ -322,7 +322,7 @@ private:
wxSize get_size() const;
void get_size(int* w, int* h) const;
double get_double_value(const SelectedSlider& selection);
int get_tick_from_value(double value);
int get_tick_from_value(double value, bool force_lower_bound = false);
wxString get_tooltip(int tick = -1);
int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange);

File diff suppressed because it is too large Load diff

View file

@ -23,17 +23,11 @@ namespace GUI {
class GCodeViewer
{
#if ENABLE_SPLITTED_VERTEX_BUFFER
using IBufferType = unsigned short;
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
using Color = std::array<float, 3>;
using VertexBuffer = std::vector<float>;
#if ENABLE_SPLITTED_VERTEX_BUFFER
using MultiVertexBuffer = std::vector<VertexBuffer>;
using IndexBuffer = std::vector<IBufferType>;
#else
using IndexBuffer = std::vector<unsigned int>;
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
using MultiIndexBuffer = std::vector<IndexBuffer>;
static const std::vector<Color> Extrusion_Role_Colors;
@ -69,24 +63,17 @@ class GCodeViewer
};
EFormat format{ EFormat::Position };
#if ENABLE_SPLITTED_VERTEX_BUFFER
// vbos id
std::vector<unsigned int> vbos;
// sizes of the buffers, in bytes, used in export to obj
std::vector<size_t> sizes;
#else
// vbo id
unsigned int id{ 0 };
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
// count of vertices, updated after data are sent to gpu
size_t count{ 0 };
size_t data_size_bytes() const { return count * vertex_size_bytes(); }
#if ENABLE_SPLITTED_VERTEX_BUFFER
// We set 65536 as max count of vertices inside a vertex buffer to allow
// to use unsigned short in place of unsigned int for indices in the index buffer, to save memory
size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); }
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); }
size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); }
@ -116,15 +103,10 @@ class GCodeViewer
// ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type
struct IBuffer
{
#if ENABLE_SPLITTED_VERTEX_BUFFER
// id of the associated vertex buffer
unsigned int vbo{ 0 };
// ibo id
unsigned int ibo{ 0 };
#else
// ibo id
unsigned int id{ 0 };
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
// count of indices, updated after data are sent to gpu
size_t count{ 0 };
@ -148,7 +130,6 @@ class GCodeViewer
Vec3f position{ Vec3f::Zero() };
};
#if ENABLE_SPLITTED_VERTEX_BUFFER
struct Sub_Path
{
Endpoint first;
@ -158,14 +139,9 @@ class GCodeViewer
return first.s_id <= s_id && s_id <= last.s_id;
}
};
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
EMoveType type{ EMoveType::Noop };
ExtrusionRole role{ erNone };
#if !ENABLE_SPLITTED_VERTEX_BUFFER
Endpoint first;
Endpoint last;
#endif // !ENABLE_SPLITTED_VERTEX_BUFFER
float delta_extruder{ 0.0f };
float height{ 0.0f };
float width{ 0.0f };
@ -175,12 +151,9 @@ class GCodeViewer
float volumetric_rate{ 0.0f };
unsigned char extruder_id{ 0 };
unsigned char cp_color_id{ 0 };
#if ENABLE_SPLITTED_VERTEX_BUFFER
std::vector<Sub_Path> sub_paths;
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
bool matches(const GCodeProcessor::MoveVertex& move) const;
#if ENABLE_SPLITTED_VERTEX_BUFFER
size_t vertices_count() const {
return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1;
}
@ -202,19 +175,13 @@ class GCodeViewer
Endpoint endpoint = { b_id, i_id, s_id, move.position };
sub_paths.push_back({ endpoint , endpoint });
}
#else
size_t vertices_count() const { return last.s_id - first.s_id + 1; }
bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; }
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
};
// Used to batch the indices needed to render the paths
struct RenderPath
{
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
// Index of the parent tbuffer
unsigned char tbuffer_id;
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
// Render path property
Color color;
// Index of the buffer in TBuffer::indices
@ -224,7 +191,6 @@ class GCodeViewer
unsigned int path_id;
std::vector<unsigned int> sizes;
std::vector<size_t> offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements())
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
bool contains(size_t offset) const {
for (size_t i = 0; i < offsets.size(); ++i) {
if (offsets[i] <= offset && offset <= offsets[i] + static_cast<size_t>(sizes[i] * sizeof(IBufferType)))
@ -232,7 +198,6 @@ class GCodeViewer
}
return false;
}
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
};
// // for unordered_set implementation of render_paths
// struct RenderPathPropertyHash {
@ -244,10 +209,8 @@ class GCodeViewer
// };
struct RenderPathPropertyLower {
bool operator() (const RenderPath &l, const RenderPath &r) const {
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
if (l.tbuffer_id < r.tbuffer_id)
return true;
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
for (int i = 0; i < 3; ++i) {
if (l.color[i] < r.color[i])
return true;
@ -259,11 +222,7 @@ class GCodeViewer
};
struct RenderPathPropertyEqual {
bool operator() (const RenderPath &l, const RenderPath &r) const {
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color;
#else
return l.color == r.color && l.ibuffer_id == r.ibuffer_id;
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
}
};
@ -295,7 +254,6 @@ class GCodeViewer
// s_id index of first vertex contained in this->vertices
void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id);
#if ENABLE_SPLITTED_VERTEX_BUFFER
unsigned int max_vertices_per_segment() const {
switch (render_primitive_type)
{
@ -308,24 +266,16 @@ class GCodeViewer
size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast<size_t>(max_vertices_per_segment()); }
size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); }
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
unsigned int indices_per_segment() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point: { return 1; }
case ERenderPrimitiveType::Line: { return 2; }
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles
#else
case ERenderPrimitiveType::Triangle: { return 42; } // 3 indices x 14 triangles
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
default: { return 0; }
}
}
#if ENABLE_SPLITTED_VERTEX_BUFFER
size_t indices_per_segment_size_bytes() const { return static_cast<size_t>(indices_per_segment() * sizeof(IBufferType)); }
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
unsigned int max_indices_per_segment() const {
switch (render_primitive_type)
{
@ -336,26 +286,10 @@ class GCodeViewer
}
}
size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); }
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
#if ENABLE_SPLITTED_VERTEX_BUFFER
bool has_data() const {
return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
}
#else
unsigned int start_segment_vertex_offset() const { return 0; }
unsigned int end_segment_vertex_offset() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point: { return 0; }
case ERenderPrimitiveType::Line: { return 1; }
case ERenderPrimitiveType::Triangle: { return 36; } // 1st vertex of 13th triangle
default: { return 0; }
}
}
bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; }
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
};
// helper to render shells
@ -434,11 +368,9 @@ class GCodeViewer
size_t first{ 0 };
size_t last{ 0 };
#if ENABLE_SPLITTED_VERTEX_BUFFER
bool operator == (const Endpoints& other) const {
return first == other.first && last == other.last;
}
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
};
private:
@ -464,7 +396,6 @@ class GCodeViewer
double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; }
Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); }
#if ENABLE_SPLITTED_VERTEX_BUFFER
bool operator != (const Layers& other) const {
if (m_zs != other.m_zs)
return true;
@ -473,10 +404,8 @@ class GCodeViewer
return false;
}
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
};
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
// used to render the toolpath caps of the current sequential range
// (i.e. when sliding on the horizontal slider)
struct SequentialRangeCap
@ -491,7 +420,6 @@ class GCodeViewer
void reset();
size_t indices_count() const { return 6; }
};
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
#if ENABLE_GCODE_VIEWER_STATISTICS
struct Statistics
@ -508,9 +436,7 @@ class GCodeViewer
int64_t gl_multi_points_calls_count{ 0 };
int64_t gl_multi_lines_calls_count{ 0 };
int64_t gl_multi_triangles_calls_count{ 0 };
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
int64_t gl_triangles_calls_count{ 0 };
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
// memory
int64_t results_size{ 0 };
int64_t total_vertices_gpu_size{ 0 };
@ -547,9 +473,7 @@ class GCodeViewer
gl_multi_points_calls_count = 0;
gl_multi_lines_calls_count = 0;
gl_multi_triangles_calls_count = 0;
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
gl_triangles_calls_count = 0;
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
}
void reset_sizes() {
@ -700,16 +624,14 @@ private:
Shells m_shells;
EViewType m_view_type{ EViewType::FeatureType };
bool m_legend_enabled{ true };
PrintEstimatedTimeStatistics m_time_statistics;
PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal };
PrintEstimatedStatistics m_print_statistics;
PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal };
#if ENABLE_GCODE_VIEWER_STATISTICS
Statistics m_statistics;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
std::array<float, 2> m_detected_point_sizes = { 0.0f, 0.0f };
GCodeProcessor::Result::SettingsIds m_settings_ids;
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
std::array<SequentialRangeCap, 2> m_sequential_range_caps;
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
public:
GCodeViewer();
@ -726,9 +648,7 @@ public:
void render() const;
bool has_data() const { return !m_roles.empty(); }
#if ENABLE_SPLITTED_VERTEX_BUFFER
bool can_export_toolpaths() const;
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; }
const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; }

View file

@ -134,27 +134,9 @@ void Size::set_scale_factor(int scale_factor)
m_scale_factor = scale_factor;
}
GLCanvas3D::LayersEditing::LayersEditing()
: m_enabled(false)
, m_z_texture_id(0)
, m_model_object(nullptr)
, m_object_max_z(0.f)
, m_slicing_parameters(nullptr)
, m_layer_height_profile_modified(false)
, m_adaptive_quality(0.5f)
, state(Unknown)
, band_width(2.0f)
, strength(0.005f)
, last_object_id(-1)
, last_z(0.0f)
, last_action(LAYER_HEIGHT_EDIT_ACTION_INCREASE)
{
}
GLCanvas3D::LayersEditing::~LayersEditing()
{
if (m_z_texture_id != 0)
{
if (m_z_texture_id != 0) {
glsafe(::glDeleteTextures(1, &m_z_texture_id));
m_z_texture_id = 0;
}
@ -186,11 +168,18 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id)
{
const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr;
#if ENABLE_ALLOW_NEGATIVE_Z
// Maximum height of an object changes when the object gets rotated or scaled.
// Changing maximum height of an object will invalidate the layer heigth editing profile.
// m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently.
const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast<float>(model_object_new->bounding_box().max.z());
#else
// Maximum height of an object changes when the object gets rotated or scaled.
// Changing maximum height of an object will invalidate the layer heigth editing profile.
// m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently.
float new_max_z = (model_object_new == nullptr) ? 0.f : model_object_new->raw_bounding_box().size().z();
if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z ||
#endif // ENABLE_ALLOW_NEGATIVE_Z
if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z ||
(model_object_new != nullptr && m_model_object->id() != model_object_new->id())) {
m_layer_height_profile.clear();
m_layer_height_profile_modified = false;
@ -218,7 +207,7 @@ void GLCanvas3D::LayersEditing::set_enabled(bool enabled)
m_enabled = is_allowed() && enabled;
}
float GLCanvas3D::LayersEditing::s_overelay_window_width;
float GLCanvas3D::LayersEditing::s_overlay_window_width;
void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
{
@ -302,7 +291,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
if (imgui.button(_L("Reset")))
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
GLCanvas3D::LayersEditing::s_overelay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/;
GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/;
imgui.end();
const Rect& bar_rect = get_bar_rect_viewport(canvas);
@ -319,7 +308,7 @@ float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
float t = rect.get_top();
float b = rect.get_bottom();
return ((rect.get_left() <= x) && (x <= rect.get_right()) && (t <= y) && (y <= b)) ?
return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ?
// Inside the bar.
(b - y - 1.0f) / (b - t - 1.0f) :
// Outside the bar.
@ -329,7 +318,7 @@ float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y)
{
const Rect& rect = get_bar_rect_screen(canvas);
return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom();
}
Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
@ -338,7 +327,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
float w = (float)cnv_size.get_width();
float h = (float)cnv_size.get_height();
return Rect(w - thickness_bar_width(canvas), 0.0f, w, h);
return { w - thickness_bar_width(canvas), 0.0f, w, h };
}
Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
@ -347,7 +336,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
float half_w = 0.5f * (float)cnv_size.get_width();
float half_h = 0.5f * (float)cnv_size.get_height();
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom };
}
bool GLCanvas3D::LayersEditing::is_initialized() const
@ -365,11 +354,12 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con
float h = 0.0f;
for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) {
float zi = m_layer_height_profile[i];
float zi_1 = m_layer_height_profile[i - 2];
const float zi = static_cast<float>(m_layer_height_profile[i]);
const float zi_1 = static_cast<float>(m_layer_height_profile[i - 2]);
if (zi_1 <= z && z <= zi) {
float dz = zi - zi_1;
h = (dz != 0.0f) ? lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz) : m_layer_height_profile[i + 1];
h = (dz != 0.0f) ? static_cast<float>(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) :
static_cast<float>(m_layer_height_profile[i + 1]);
break;
}
}
@ -398,10 +388,10 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3
glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
// Render the color bar
float l = bar_rect.get_left();
float r = bar_rect.get_right();
float t = bar_rect.get_top();
float b = bar_rect.get_bottom();
const float l = bar_rect.get_left();
const float r = bar_rect.get_right();
const float t = bar_rect.get_top();
const float b = bar_rect.get_bottom();
::glBegin(GL_QUADS);
::glNormal3f(0.0f, 0.0f, 1.0f);
@ -564,7 +554,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas)
{
if (last_object_id >= 0) {
if (m_layer_height_profile_modified) {
wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Manual edit")));
wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit"));
const_cast<ModelObject*>(m_model_object)->layer_height_profile.set(m_layer_height_profile);
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
wxGetApp().obj_list()->update_info_items(last_object_id);
@ -577,7 +567,7 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters()
{
if (m_slicing_parameters == nullptr) {
m_slicing_parameters = new SlicingParameters();
*m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z);
*m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z);
}
}
@ -614,278 +604,6 @@ GLCanvas3D::Mouse::Mouse()
{
}
#if !ENABLE_WARNING_TEXTURE_REMOVAL
const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 120, 120, 120 };//{ 9, 91, 134 };
const unsigned char GLCanvas3D::WarningTexture::Opacity = 255;
GLCanvas3D::WarningTexture::WarningTexture()
: GUI::GLTexture()
, m_original_width(0)
, m_original_height(0)
{
}
void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas)
{
// Since we have NotificationsManager.hpp the warning textures are no loger needed.
// However i have left the infrastructure here and only commented the rendering.
// The plater warning / error notifications are added and closed from here.
std::string text;
bool error = false;
switch (warning) {
case ObjectOutside: text = _u8L("An object outside the print area was detected."); break;
case ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = true; break;
case SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = true; break;
case SomethingNotShown: text = _u8L("Some objects are not visible."); break;
case ObjectClashed:
text = _u8L( "An object outside the print area was detected.\n"
"Resolve the current problem to continue slicing.");
error = true;
break;
}
BOOST_LOG_TRIVIAL(error) << state << " : " << text ;
auto &notification_manager = *wxGetApp().plater()->get_notification_manager();
if (state) {
if(error)
notification_manager.push_plater_error_notification(text);
else
notification_manager.push_plater_warning_notification(text);
} else {
if (error)
notification_manager.close_plater_error_notification(text);
else
notification_manager.close_plater_warning_notification(text);
}
/*
auto it = std::find(m_warnings.begin(), m_warnings.end(), warning);
if (state) {
if (it != m_warnings.end()) // this warning is already set to be shown
return;
m_warnings.push_back(warning);
std::sort(m_warnings.begin(), m_warnings.end());
}
else {
if (it == m_warnings.end()) // deactivating something that is not active is an easy task
return;
m_warnings.erase(it);
if (m_warnings.empty()) { // nothing remains to be shown
reset();
m_msg_text = "";// save information for rescaling
return;
}
}
// Look at the end of our vector and generate proper texture.
std::string text;
bool red_colored = false;
switch (m_warnings.back()) {
case ObjectOutside : text = L("An object outside the print area was detected"); break;
case ToolpathOutside : text = L("A toolpath outside the print area was detected"); break;
case SlaSupportsOutside : text = L("SLA supports outside the print area were detected"); break;
case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break;
case ObjectClashed: {
text = L("An object outside the print area was detected\n"
"Resolve the current problem to continue slicing");
red_colored = true;
break;
}
}
generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...)
// save information for rescaling
m_msg_text = text;
m_is_colored_red = red_colored;
*/
}
#ifdef __WXMSW__
static bool is_font_cleartype(const wxFont &font)
{
// Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon.
wxString font_desc = font.GetNativeFontInfoDesc();
// Find the quality field.
wxString sep(";");
size_t startpos = 0;
for (size_t i = 0; i < 12; ++ i)
startpos = font_desc.find(sep, startpos + 1);
++ startpos;
size_t endpos = font_desc.find(sep, startpos);
int quality = wxAtoi(font_desc(startpos, endpos - startpos));
return quality == CLEARTYPE_QUALITY;
}
// ClearType produces renders, which are difficult to convert into an alpha blended OpenGL texture.
// Therefore it is better to disable it, though Vojtech found out, that the font returned with ClearType
// disabled is signifcantly thicker than the default ClearType font.
// This function modifies the font provided.
static void msw_disable_cleartype(wxFont &font)
{
// Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon.
wxString font_desc = font.GetNativeFontInfoDesc();
// Find the quality field.
wxString sep(";");
size_t startpos_weight = 0;
for (size_t i = 0; i < 5; ++ i)
startpos_weight = font_desc.find(sep, startpos_weight + 1);
++ startpos_weight;
size_t endpos_weight = font_desc.find(sep, startpos_weight);
// Parse the weight field.
unsigned int weight = wxAtoi(font_desc(startpos_weight, endpos_weight - startpos_weight));
size_t startpos = endpos_weight;
for (size_t i = 0; i < 6; ++ i)
startpos = font_desc.find(sep, startpos + 1);
++ startpos;
size_t endpos = font_desc.find(sep, startpos);
int quality = wxAtoi(font_desc(startpos, endpos - startpos));
if (quality == CLEARTYPE_QUALITY) {
// Replace the weight with a smaller value to compensate the weight of non ClearType font.
wxString sweight = std::to_string(weight * 2 / 4);
size_t len_weight = endpos_weight - startpos_weight;
wxString squality = std::to_string(ANTIALIASED_QUALITY);
font_desc.replace(startpos_weight, len_weight, sweight);
font_desc.replace(startpos + sweight.size() - len_weight, endpos - startpos, squality);
font.SetNativeFontInfo(font_desc);
wxString font_desc2 = font.GetNativeFontInfoDesc();
}
wxString font_desc2 = font.GetNativeFontInfoDesc();
}
#endif /* __WXMSW__ */
bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLCanvas3D& canvas, bool compress, bool red_colored/* = false*/)
{
reset();
if (msg_utf8.empty())
return false;
wxString msg = _(msg_utf8);
wxMemoryDC memDC;
#ifdef __WXMSW__
// set scaled application normal font as default font
wxFont font = wxGetApp().normal_font();
#else
// select default font
const float scale = canvas.get_canvas_size().get_scale_factor();
#if ENABLE_RETINA_GL
// For non-visible or non-created window getBackingScaleFactor function return 0.0 value.
// And using of the zero scale causes a crash, when we trying to draw text to the (0,0) rectangle
// https://github.com/prusa3d/PrusaSlicer/issues/3916
if (scale <= 0.0f)
return false;
#endif
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale);
#endif
font.MakeLarger();
font.MakeBold();
memDC.SetFont(font);
// calculates texture size
wxCoord w, h;
memDC.GetMultiLineTextExtent(msg, &w, &h);
m_original_width = (int)w;
m_original_height = (int)h;
m_width = (int)next_highest_power_of_2((uint32_t)w);
m_height = (int)next_highest_power_of_2((uint32_t)h);
// generates bitmap
wxBitmap bitmap(m_width, m_height);
memDC.SelectObject(bitmap);
memDC.SetBackground(wxBrush(*wxBLACK));
memDC.Clear();
// draw message
memDC.SetTextForeground(*wxRED);
memDC.DrawLabel(msg, wxRect(0,0, m_original_width, m_original_height), wxALIGN_CENTER);
memDC.SelectObject(wxNullBitmap);
// Convert the bitmap into a linear data ready to be loaded into the GPU.
wxImage image = bitmap.ConvertToImage();
// prepare buffer
std::vector<unsigned char> data(4 * m_width * m_height, 0);
const unsigned char *src = image.GetData();
for (int h = 0; h < m_height; ++h) {
unsigned char* dst = data.data() + 4 * h * m_width;
for (int w = 0; w < m_width; ++w) {
*dst++ = 255;
if (red_colored) {
*dst++ = 72; // 204
*dst++ = 65; // 204
} else {
*dst++ = 255;
*dst++ = 255;
}
*dst++ = (unsigned char)std::min<int>(255, *src);
src += 3;
}
}
// sends buffer to gpu
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
return true;
}
void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const
{
if (m_warnings.empty())
return;
if (m_id > 0 && m_original_width > 0 && m_original_height > 0 && m_width > 0 && m_height > 0) {
const Size& cnv_size = canvas.get_canvas_size();
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
float left = (-0.5f * (float)m_original_width) * inv_zoom;
float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom;
float right = left + (float)m_original_width * inv_zoom;
float bottom = top - (float)m_original_height * inv_zoom;
float uv_left = 0.0f;
float uv_top = 0.0f;
float uv_right = (float)m_original_width / (float)m_width;
float uv_bottom = (float)m_original_height / (float)m_height;
GLTexture::Quad_UVs uvs;
uvs.left_top = { uv_left, uv_top };
uvs.left_bottom = { uv_left, uv_bottom };
uvs.right_bottom = { uv_right, uv_bottom };
uvs.right_top = { uv_right, uv_top };
GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs);
}
}
void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas)
{
if (m_msg_text.empty())
return;
generate(m_msg_text, canvas, true, m_is_colored_red);
}
#endif // !ENABLE_WARNING_TEXTURE_REMOVAL
void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_instances) const
{
if (!m_enabled || !is_shown())
@ -1153,8 +871,6 @@ void GLCanvas3D::SequentialPrintClearance::render() const
}
#endif // ENABLE_SEQUENTIAL_LIMITS
float GLCanvas3D::Slope::s_window_width;
wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
@ -1388,11 +1104,7 @@ void GLCanvas3D::reset_volumes()
m_volumes.clear();
m_dirty = true;
#if ENABLE_WARNING_TEXTURE_REMOVAL
_set_warning_notification(EWarning::ObjectOutside, false);
#else
_set_warning_texture(WarningTexture::ObjectOutside, false);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
}
int GLCanvas3D::check_volumes_outside_state() const
@ -1446,19 +1158,11 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
if (visible && !mo)
toggle_sla_auxiliaries_visibility(true, mo, instance_idx);
#if ENABLE_WARNING_TEXTURE_REMOVAL
if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1))
_set_warning_notification(EWarning::SomethingNotShown, true);
if (!mo && visible)
_set_warning_notification(EWarning::SomethingNotShown, false);
#else
if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1))
_set_warning_texture(WarningTexture::SomethingNotShown, true);
if (!mo && visible)
_set_warning_texture(WarningTexture::SomethingNotShown, false);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
}
void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx)
@ -1812,6 +1516,11 @@ void GLCanvas3D::render()
}
#endif // ENABLE_RENDER_STATISTICS
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown())
wxGetApp().plater()->render_project_state_debug_window();
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
#if ENABLE_CAMERA_STATISTICS
camera.debug_render();
#endif // ENABLE_CAMERA_STATISTICS
@ -2045,7 +1754,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh;
PrinterTechnology printer_technology = m_process->current_printer_technology();
PrinterTechnology printer_technology = current_printer_technology();
int volume_idx_wipe_tower_old = -1;
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
@ -2335,31 +2044,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
bool fullyOut = false;
const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut);
#if ENABLE_WARNING_TEXTURE_REMOVAL
_set_warning_notification(EWarning::ObjectClashed, partlyOut);
_set_warning_notification(EWarning::ObjectOutside, fullyOut);
if (printer_technology != ptSLA || !contained_min_one)
_set_warning_notification(EWarning::SlaSupportsOutside, false);
#else
_set_warning_texture(WarningTexture::ObjectClashed, partlyOut);
_set_warning_texture(WarningTexture::ObjectOutside, fullyOut);
if(printer_technology != ptSLA || !contained_min_one)
_set_warning_texture(WarningTexture::SlaSupportsOutside, false);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS,
contained_min_one && !m_model->objects.empty() && !partlyOut));
}
else {
#if ENABLE_WARNING_TEXTURE_REMOVAL
_set_warning_notification(EWarning::ObjectOutside, false);
_set_warning_notification(EWarning::ObjectClashed, false);
_set_warning_notification(EWarning::SlaSupportsOutside, false);
#else
_set_warning_texture(WarningTexture::ObjectOutside, false);
_set_warning_texture(WarningTexture::ObjectClashed, false);
_set_warning_texture(WarningTexture::SlaSupportsOutside, false);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
}
@ -2402,11 +2098,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result)
if (wxGetApp().is_editor()) {
m_gcode_viewer.update_shells_color_by_extruder(m_config);
#if ENABLE_WARNING_TEXTURE_REMOVAL
_set_warning_notification_if_needed(EWarning::ToolpathOutside);
#else
_show_warning_texture_if_needed(WarningTexture::ToolpathOutside);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
}
}
@ -2433,11 +2125,7 @@ void GLCanvas3D::load_sla_preview()
this->reset_volumes();
_load_sla_shells();
_update_sla_shells_outside_state();
#if ENABLE_WARNING_TEXTURE_REMOVAL
_set_warning_notification_if_needed(EWarning::SlaSupportsOutside);
#else
_show_warning_texture_if_needed(WarningTexture::SlaSupportsOutside);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
}
}
@ -2458,11 +2146,7 @@ void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, c
_load_print_object_toolpaths(*object, str_tool_colors, color_print_values);
_update_toolpath_volumes_outside_state();
#if ENABLE_WARNING_TEXTURE_REMOVAL
_set_warning_notification_if_needed(EWarning::ToolpathOutside);
#else
_show_warning_texture_if_needed(WarningTexture::ToolpathOutside);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
}
void GLCanvas3D::bind_event_handlers()
@ -2608,9 +2292,19 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
#ifdef _WIN32
if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") {
#endif //_WIN32
#ifdef __APPLE__
// On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog"
if ((evt.GetModifiers() & shiftMask) != 0) {
#endif // __APPLE__
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
m_dirty = true;
#ifdef __APPLE__
}
else
// and Cmd+M to minimize application
wxGetApp().mainframe->Iconize();
#endif // __APPLE__
#ifdef _WIN32
}
#endif //_WIN32
@ -3246,7 +2940,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
if (m_gizmos.on_mouse(evt)) {
if (wxWindow::FindFocus() != this->m_canvas)
if (wxWindow::FindFocus() != m_canvas)
// Grab keyboard focus for input in gizmo dialogs.
m_canvas->SetFocus();
@ -3269,7 +2963,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.set_move_start_threshold_position_2D_as_invalid();
}
if (evt.ButtonDown() && wxWindow::FindFocus() != this->m_canvas)
if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas)
// Grab keyboard focus on any mouse click event.
m_canvas->SetFocus();
@ -3662,14 +3356,38 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
wipe_tower_origin = v->get_volume_offset();
}
#if ENABLE_ALLOW_NEGATIVE_Z
// Fixes flying instances
#else
// Fixes sinking/flying instances
#endif // ENABLE_ALLOW_NEGATIVE_Z
for (const std::pair<int, int>& i : done) {
ModelObject* m = m_model->objects[i.first];
#if ENABLE_ALLOW_NEGATIVE_Z
double shift_z = m->get_instance_min_z(i.second);
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
if (current_printer_technology() == ptSLA || shift_z > 0.0) {
#else
if (shift_z > 0.0) {
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
Vec3d shift(0.0, 0.0, -shift_z);
#else
Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second));
#endif // ENABLE_ALLOW_NEGATIVE_Z
m_selection.translate(i.first, i.second, shift);
m->translate_instance(i.second, shift);
#if ENABLE_ALLOW_NEGATIVE_Z
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
#if ENABLE_ALLOW_NEGATIVE_Z
// if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running
// similar to void Plater::priv::selection_changed()
if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled())
post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
#endif // ENABLE_ALLOW_NEGATIVE_Z
if (object_moved)
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED));
@ -3687,18 +3405,30 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
if (!snapshot_type.empty())
wxGetApp().plater()->take_snapshot(_(snapshot_type));
#if ENABLE_ALLOW_NEGATIVE_Z
// stores current min_z of instances
std::map<std::pair<int, int>, double> min_zs;
if (!snapshot_type.empty()) {
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
const ModelObject* obj = m_model->objects[i];
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
min_zs[{ i, j }] = obj->instance_bounding_box(j).min(2);
}
}
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
std::set<std::pair<int, int>> done; // keeps track of modified instances
Selection::EMode selection_mode = m_selection.get_mode();
for (const GLVolume* v : m_volumes.volumes)
{
for (const GLVolume* v : m_volumes.volumes) {
int object_idx = v->object_idx();
if (object_idx == 1000) { // the wipe tower
Vec3d offset = v->get_volume_offset();
post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2))));
}
if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx))
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
continue;
int instance_idx = v->instance_idx();
@ -3708,15 +3438,12 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
// Rotate instances/volumes.
ModelObject* model_object = m_model->objects[object_idx];
if (model_object != nullptr)
{
if (selection_mode == Selection::Instance)
{
if (model_object != nullptr) {
if (selection_mode == Selection::Instance) {
model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation());
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
}
else if (selection_mode == Selection::Volume)
{
else if (selection_mode == Selection::Volume) {
model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation());
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
}
@ -3725,12 +3452,21 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
}
// Fixes sinking/flying instances
for (const std::pair<int, int>& i : done)
{
for (const std::pair<int, int>& i : done) {
ModelObject* m = m_model->objects[i.first];
#if ENABLE_ALLOW_NEGATIVE_Z
double shift_z = m->get_instance_min_z(i.second);
// leave sinking instances as sinking
if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= 0.0 || shift_z > 0.0) {
Vec3d shift(0.0, 0.0, -shift_z);
#else
Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second));
m_selection.translate(i.first, i.second, shift);
m->translate_instance(i.second, shift);
#endif // ENABLE_ALLOW_NEGATIVE_Z
m_selection.translate(i.first, i.second, shift);
m->translate_instance(i.second, shift);
#if ENABLE_ALLOW_NEGATIVE_Z
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
if (!done.empty())
@ -3747,14 +3483,26 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
if (!snapshot_type.empty())
wxGetApp().plater()->take_snapshot(_(snapshot_type));
#if ENABLE_ALLOW_NEGATIVE_Z
// stores current min_z of instances
std::map<std::pair<int, int>, double> min_zs;
if (!snapshot_type.empty()) {
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
const ModelObject* obj = m_model->objects[i];
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
min_zs[{ i, j }] = obj->instance_bounding_box(j).min(2);
}
}
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
std::set<std::pair<int, int>> done; // keeps track of modified instances
Selection::EMode selection_mode = m_selection.get_mode();
for (const GLVolume* v : m_volumes.volumes)
{
for (const GLVolume* v : m_volumes.volumes) {
int object_idx = v->object_idx();
if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx))
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
continue;
int instance_idx = v->instance_idx();
@ -3764,15 +3512,12 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
// Rotate instances/volumes
ModelObject* model_object = m_model->objects[object_idx];
if (model_object != nullptr)
{
if (selection_mode == Selection::Instance)
{
if (model_object != nullptr) {
if (selection_mode == Selection::Instance) {
model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor());
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
}
else if (selection_mode == Selection::Volume)
{
else if (selection_mode == Selection::Volume) {
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor());
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
@ -3782,16 +3527,25 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
}
// Fixes sinking/flying instances
for (const std::pair<int, int>& i : done)
{
for (const std::pair<int, int>& i : done) {
ModelObject* m = m_model->objects[i.first];
#if ENABLE_ALLOW_NEGATIVE_Z
double shift_z = m->get_instance_min_z(i.second);
// leave sinking instances as sinking
if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= 0.0 || shift_z > 0.0) {
Vec3d shift(0.0, 0.0, -shift_z);
#else
Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second));
#endif // ENABLE_ALLOW_NEGATIVE_Z
m_selection.translate(i.first, i.second, shift);
m->translate_instance(i.second, shift);
#if ENABLE_ALLOW_NEGATIVE_Z
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
if (!done.empty())
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED));
m_dirty = true;
}
@ -3950,16 +3704,13 @@ void GLCanvas3D::set_cursor(ECursorType type)
void GLCanvas3D::msw_rescale()
{
#if !ENABLE_WARNING_TEXTURE_REMOVAL
m_warning_texture.msw_rescale(*this);
#endif // !ENABLE_WARNING_TEXTURE_REMOVAL
}
void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar()
{
std::string new_tooltip = _u8L("Switch to Settings") +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ;
m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip);
@ -3967,11 +3718,7 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar()
bool GLCanvas3D::has_toolpaths_to_export() const
{
#if ENABLE_SPLITTED_VERTEX_BUFFER
return m_gcode_viewer.can_export_toolpaths();
#else
return m_gcode_viewer.has_data();
#endif // ENABLE_SPLITTED_VERTEX_BUFFER
}
void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const
@ -4113,7 +3860,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x)
ArrangeSettings &settings_out = get_arrange_settings();
auto &appcfg = wxGetApp().app_config;
PrinterTechnology ptech = m_process->current_printer_technology();
PrinterTechnology ptech = current_printer_technology();
bool settings_changed = false;
float dist_min = 0.f;
@ -4680,7 +4427,7 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "settings";
item.icon_filename = "settings.svg";
item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ;
item.sprite_id = 10;
item.enabling_callback = GLToolbarItem::Default_Enabling_Callback;
@ -4722,7 +4469,7 @@ bool GLCanvas3D::_init_main_toolbar()
item.sprite_id = 12;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
item.visibility_callback = [this]()->bool {
bool res = m_process->current_printer_technology() == ptFFF;
bool res = current_printer_technology() == ptFFF;
// turns off if changing printer technology
if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting"))
force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting"));
@ -4870,7 +4617,7 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
return;
auto *imgui = wxGetApp().imgui();
imgui->set_display_size((float)w, (float)h);
imgui->set_display_size(static_cast<float>(w), static_cast<float>(h));
const float font_size = 1.5f * wxGetApp().em_unit();
#if ENABLE_RETINA_GL
imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor());
@ -4878,6 +4625,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f);
#endif
#if ENABLE_SCROLLABLE_LEGEND
this->request_extra_frame();
#endif // ENABLE_SCROLLABLE_LEGEND
// ensures that this canvas is current
_set_current();
}
@ -4918,8 +4669,7 @@ void GLCanvas3D::_update_camera_zoom(double zoom)
void GLCanvas3D::_refresh_if_shown_on_screen()
{
if (_is_shown_on_screen())
{
if (_is_shown_on_screen()) {
const Size& cnv_size = get_canvas_size();
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
@ -5304,9 +5054,6 @@ void GLCanvas3D::_render_overlays() const
_check_and_update_toolbar_icon_scale();
_render_gizmos_overlay();
#if !ENABLE_WARNING_TEXTURE_REMOVAL
_render_warning_texture();
#endif // !ENABLE_WARNING_TEXTURE_REMOVAL
// main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed
// to correctly place them
@ -5344,13 +5091,6 @@ void GLCanvas3D::_render_overlays() const
glsafe(::glPopMatrix());
}
#if !ENABLE_WARNING_TEXTURE_REMOVAL
void GLCanvas3D::_render_warning_texture() const
{
m_warning_texture.render(*this);
}
#endif // !ENABLE_WARNING_TEXTURE_REMOVAL
void GLCanvas3D::_render_volumes_for_picking() const
{
static const GLfloat INV_255 = 1.0f / 255.0f;
@ -6031,7 +5771,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
{
if (layerm->slices.surfaces.empty())
continue;
const PrintRegionConfig& cfg = layerm->region()->config();
const PrintRegionConfig& cfg = layerm->region().config();
if (cfg.perimeter_extruder.value == m_selected_extruder ||
cfg.infill_extruder.value == m_selected_extruder ||
cfg.solid_infill_extruder.value == m_selected_extruder ) {
@ -6054,7 +5794,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
for (const LayerRegion *layerm : layer->regions()) {
if (is_selected_separate_extruder)
{
const PrintRegionConfig& cfg = layerm->region()->config();
const PrintRegionConfig& cfg = layerm->region().config();
if (cfg.perimeter_extruder.value != m_selected_extruder ||
cfg.infill_extruder.value != m_selected_extruder ||
cfg.solid_infill_extruder.value != m_selected_extruder)
@ -6062,7 +5802,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
}
if (ctxt.has_perimeters)
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
volume(idx_layer, layerm->region()->config().perimeter_extruder.value, 0));
volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0));
if (ctxt.has_infill) {
for (const ExtrusionEntity *ee : layerm->fills.entities) {
// fill represents infill extrusions of a single island.
@ -6071,8 +5811,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
_3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy,
volume(idx_layer,
is_solid_infill(fill->entities.front()->role()) ?
layerm->region()->config().solid_infill_extruder :
layerm->region()->config().infill_extruder,
layerm->region().config().solid_infill_extruder :
layerm->region().config().infill_extruder,
1));
}
}
@ -6297,7 +6037,7 @@ void GLCanvas3D::_load_sla_shells()
#else
v.indexed_vertex_array.load_mesh(mesh);
#endif // ENABLE_SMOOTH_NORMALS
v.indexed_vertex_array.finalize_geometry(this->m_initialized);
v.indexed_vertex_array.finalize_geometry(m_initialized);
v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled;
v.composite_id.volume_id = volume_id;
v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0));
@ -6347,7 +6087,6 @@ void GLCanvas3D::_update_sla_shells_outside_state()
}
}
#if ENABLE_WARNING_TEXTURE_REMOVAL
void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning)
{
_set_current();
@ -6364,24 +6103,6 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning)
}
_set_warning_notification(warning, show);
}
#else
void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning)
{
_set_current();
bool show = false;
if (!m_volumes.empty())
show = _is_any_volume_outside();
else {
if (wxGetApp().is_editor()) {
BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box();
if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0)
show = !test_volume.contains(paths_volume);
}
}
_set_warning_texture(warning, show);
}
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)
{
@ -6405,7 +6126,6 @@ std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& col
return output;
}
#if ENABLE_WARNING_TEXTURE_REMOVAL
void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
{
enum ErrorType{
@ -6451,12 +6171,6 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
break;
}
}
#else
void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state)
{
m_warning_texture.activate(warning, state, *this);
}
#endif // !ENABLE_WARNING_TEXTURE_REMOVAL
bool GLCanvas3D::_is_any_volume_outside() const
{

View file

@ -154,53 +154,50 @@ class GLCanvas3D
static const float THICKNESS_BAR_WIDTH;
private:
bool m_enabled;
unsigned int m_z_texture_id;
bool m_enabled{ false };
unsigned int m_z_texture_id{ 0 };
// Not owned by LayersEditing.
const DynamicPrintConfig *m_config;
const DynamicPrintConfig *m_config{ nullptr };
// ModelObject for the currently selected object (Model::objects[last_object_id]).
const ModelObject *m_model_object;
const ModelObject *m_model_object{ nullptr };
// Maximum z of the currently selected object (Model::objects[last_object_id]).
float m_object_max_z;
float m_object_max_z{ 0.0f };
// Owned by LayersEditing.
SlicingParameters *m_slicing_parameters;
SlicingParameters *m_slicing_parameters{ nullptr };
std::vector<double> m_layer_height_profile;
bool m_layer_height_profile_modified;
bool m_layer_height_profile_modified{ false };
mutable float m_adaptive_quality;
mutable float m_adaptive_quality{ 0.5f };
mutable HeightProfileSmoothingParams m_smooth_params;
static float s_overelay_window_width;
static float s_overlay_window_width;
class LayersTexture
struct LayersTexture
{
public:
LayersTexture() : width(0), height(0), levels(0), cells(0), valid(false) {}
// Texture data
std::vector<char> data;
// Width of the texture, top level.
size_t width;
size_t width{ 0 };
// Height of the texture, top level.
size_t height;
size_t height{ 0 };
// For how many levels of detail is the data allocated?
size_t levels;
size_t levels{ 0 };
// Number of texture cells allocated for the height texture.
size_t cells;
size_t cells{ 0 };
// Does it need to be refreshed?
bool valid;
bool valid{ false };
};
LayersTexture m_layers_texture;
public:
EState state;
float band_width;
float strength;
int last_object_id;
float last_z;
LayerHeightEditActionType last_action;
EState state{ Unknown };
float band_width{ 2.0f };
float strength{ 0.005f };
int last_object_id{ -1 };
float last_z{ 0.0f };
LayerHeightEditActionType last_action{ LAYER_HEIGHT_EDIT_ACTION_INCREASE };
LayersEditing();
LayersEditing() = default;
~LayersEditing();
void init();
@ -226,7 +223,7 @@ class GLCanvas3D
static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
static float get_overlay_window_width() { return LayersEditing::s_overelay_window_width; }
static float get_overlay_window_width() { return LayersEditing::s_overlay_window_width; }
float object_max_z() const { return m_object_max_z; }
@ -298,7 +295,6 @@ class GLCanvas3D
bool matches(double z) const { return this->z == z; }
};
#if ENABLE_WARNING_TEXTURE_REMOVAL
enum class EWarning {
ObjectOutside,
ToolpathOutside,
@ -306,46 +302,6 @@ class GLCanvas3D
SomethingNotShown,
ObjectClashed
};
#else
class WarningTexture : public GUI::GLTexture
{
public:
WarningTexture();
enum Warning {
ObjectOutside,
ToolpathOutside,
SlaSupportsOutside,
SomethingNotShown,
ObjectClashed
};
// Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously,
// only the last one is shown (decided by the order in the enum above).
void activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas);
void render(const GLCanvas3D& canvas) const;
// function used to get an information for rescaling of the warning
void msw_rescale(const GLCanvas3D& canvas);
private:
static const unsigned char Background_Color[3];
static const unsigned char Opacity;
int m_original_width;
int m_original_height;
// information for rescaling of the warning legend
std::string m_msg_text = "";
bool m_is_colored_red{false};
// Information about which warnings are currently active.
std::vector<Warning> m_warnings;
// Generates the texture with given text.
bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false);
};
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
#if ENABLE_RENDER_STATISTICS
class RenderStats
@ -401,7 +357,6 @@ class GLCanvas3D
{
bool m_enabled{ false };
GLVolumeCollection& m_volumes;
static float s_window_width;
public:
Slope(GLVolumeCollection& volumes) : m_volumes(volumes) {}
@ -412,7 +367,6 @@ class GLCanvas3D
void set_normal_angle(float angle_in_deg) const {
m_volumes.set_slope_normal_z(-::cos(Geometry::deg2rad(90.0f - angle_in_deg)));
}
static float get_window_width() { return s_window_width; };
};
class RenderTimer : public wxTimer {
@ -443,9 +397,6 @@ private:
std::unique_ptr<RetinaHelper> m_retina_helper;
#endif
bool m_in_render;
#if !ENABLE_WARNING_TEXTURE_REMOVAL
WarningTexture m_warning_texture;
#endif // !ENABLE_WARNING_TEXTURE_REMOVAL
wxTimer m_timer;
LayersEditing m_layers_editing;
Mouse m_mouse;
@ -836,9 +787,6 @@ private:
#endif // ENABLE_RENDER_SELECTION_CENTER
void _check_and_update_toolbar_icon_scale() const;
void _render_overlays() const;
#if !ENABLE_WARNING_TEXTURE_REMOVAL
void _render_warning_texture() const;
#endif // !ENABLE_WARNING_TEXTURE_REMOVAL
void _render_volumes_for_picking() const;
void _render_current_gizmo() const;
void _render_gizmos_overlay() const;
@ -891,17 +839,10 @@ private:
void _load_sla_shells();
void _update_toolpath_volumes_outside_state();
void _update_sla_shells_outside_state();
#if ENABLE_WARNING_TEXTURE_REMOVAL
void _set_warning_notification_if_needed(EWarning warning);
// generates a warning notification containing the given message
void _set_warning_notification(EWarning warning, bool state);
#else
void _show_warning_texture_if_needed(WarningTexture::Warning warning);
// generates a warning texture containing the given message
void _set_warning_texture(WarningTexture::Warning warning, bool state);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
bool _is_any_volume_outside() const;

View file

@ -11,6 +11,7 @@
#include <exception>
#include <cstdlib>
#include <regex>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
@ -68,6 +69,7 @@
#include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp"
#include "PrintHostDialogs.hpp"
#include "DesktopIntegrationDialog.hpp"
#include "BitmapCache.hpp"
@ -633,8 +635,17 @@ void GUI_App::post_init()
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
this->mainframe->load_config_file(this->init_params->load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (! this->init_params->input_files.empty())
this->plater()->load_files(this->init_params->input_files, true, true);
if (!this->init_params->input_files.empty()) {
const std::vector<size_t> res = this->plater()->load_files(this->init_params->input_files, true, true);
if (!res.empty() && this->init_params->input_files.size() == 1) {
// Update application titlebar when opening a project file
const std::string& filename = this->init_params->input_files.front();
if (boost::algorithm::iends_with(filename, ".amf") ||
boost::algorithm::iends_with(filename, ".amf.xml") ||
boost::algorithm::iends_with(filename, ".3mf"))
this->plater()->set_project_filename(filename);
}
}
if (! this->init_params->extra_config.empty())
this->mainframe->load_config(this->init_params->extra_config);
}
@ -905,6 +916,14 @@ bool GUI_App::on_init_inner()
}
else
load_current_presets();
#if ENABLE_PROJECT_DIRTY_STATE
if (plater_ != nullptr) {
plater_->reset_project_dirty_initial_presets();
plater_->update_project_dirty_from_presets();
}
#endif // ENABLE_PROJECT_DIRTY_STATE
mainframe->Show(true);
obj_list()->set_min_height();
@ -1624,6 +1643,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for updates"), _L("Check for configuration updates"));
#ifdef __linux__
if (DesktopIntegrationDialog::integration_possible())
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
#endif
local_menu->AppendSeparator();
}
local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots +
@ -1664,9 +1687,18 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
case ConfigMenuUpdate:
check_updates(true);
break;
#ifdef __linux__
case ConfigMenuDesktopIntegration:
show_desktop_integration_dialog();
break;
#endif
case ConfigMenuTakeSnapshot:
// Take a configuration snapshot.
#if ENABLE_PROJECT_DIRTY_STATE
if (check_and_save_current_preset_changes()) {
#else
if (check_unsaved_changes()) {
#endif // ENABLE_PROJECT_DIRTY_STATE
wxTextEntryDialog dlg(nullptr, _L("Taking configuration snapshot"), _L("Snapshot name"));
// set current normal font for dialog children,
@ -1681,7 +1713,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
}
break;
case ConfigMenuSnapshots:
#if ENABLE_PROJECT_DIRTY_STATE
if (check_and_save_current_preset_changes()) {
#else
if (check_unsaved_changes()) {
#endif // ENABLE_PROJECT_DIRTY_STATE
std::string on_snapshot;
if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
on_snapshot = app_config->get("on_snapshot");
@ -1782,8 +1818,57 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
menu->Append(local_menu, _L("&Configuration"));
}
#if ENABLE_PROJECT_DIRTY_STATE
bool GUI_App::has_unsaved_preset_changes() const
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (const Tab* const tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty())
return true;
}
return false;
}
bool GUI_App::has_current_preset_changes() const
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (const Tab* const tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
return true;
}
return false;
}
void GUI_App::update_saved_preset_from_current_preset()
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology))
tab->update_saved_preset_from_current_preset();
}
}
std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets() const
{
std::vector<std::pair<unsigned int, std::string>> ret;
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology)) {
const PresetCollection* presets = tab->get_presets();
ret.push_back({ static_cast<unsigned int>(presets->type()), presets->get_selected_preset_name() });
}
}
return ret;
}
#endif // ENABLE_PROJECT_DIRTY_STATE
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
#if ENABLE_PROJECT_DIRTY_STATE
bool GUI_App::check_and_save_current_preset_changes(const wxString& header)
{
if (this->plater()->model().objects.empty() && has_current_preset_changes()) {
#else
bool GUI_App::check_unsaved_changes(const wxString &header)
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
@ -1795,8 +1880,8 @@ bool GUI_App::check_unsaved_changes(const wxString &header)
break;
}
if (has_unsaved_changes)
{
if (has_unsaved_changes) {
#endif // ENABLE_PROJECT_DIRTY_STATE
UnsavedChangesDialog dlg(header);
if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL)
return false;
@ -2056,6 +2141,15 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
return res;
}
void GUI_App::show_desktop_integration_dialog()
{
#ifdef __linux__
//wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
DesktopIntegrationDialog dialog(mainframe);
dialog.ShowModal();
#endif //__linux__
}
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
void GUI_App::gcode_thumbnails_debug()
{

View file

@ -76,6 +76,7 @@ enum ConfigMenuIDs {
ConfigMenuSnapshots,
ConfigMenuTakeSnapshot,
ConfigMenuUpdate,
ConfigMenuDesktopIntegration,
ConfigMenuPreferences,
ConfigMenuModeSimple,
ConfigMenuModeAdvanced,
@ -209,7 +210,15 @@ public:
void update_mode();
void add_config_menu(wxMenuBar *menu);
bool check_unsaved_changes(const wxString &header = wxString());
#if ENABLE_PROJECT_DIRTY_STATE
bool has_unsaved_preset_changes() const;
bool has_current_preset_changes() const;
void update_saved_preset_from_current_preset();
std::vector<std::pair<unsigned int, std::string>> get_selected_presets() const;
bool check_and_save_current_preset_changes(const wxString& header = wxString());
#else
bool check_unsaved_changes(const wxString& header = wxString());
#endif // ENABLE_PROJECT_DIRTY_STATE
bool check_print_host_queue();
bool checked_tab(Tab* tab);
void load_current_presets(bool check_printer_presets = true);
@ -268,6 +277,7 @@ public:
void open_web_page_localized(const std::string &http_address);
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
void show_desktop_integration_dialog();
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
// temporary and debug only -> extract thumbnails from selected gcode and save them as png files

View file

@ -142,7 +142,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(editor);
auto temp = new wxStaticText(m_parent, wxID_ANY, _(L("mm")));
auto temp = new wxStaticText(m_parent, wxID_ANY, _L("mm"));
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
temp->SetFont(wxGetApp().normal_font());
sizer->Add(temp, 0, wxLEFT, wxGetApp().em_unit());
@ -154,15 +154,14 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus
void ObjectLayers::create_layers_list()
{
for (const auto &layer : m_object->layer_config_ranges)
{
for (const auto &layer : m_object->layer_config_ranges) {
const t_layer_height_range& range = layer.first;
auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range);
del_btn->SetToolTip(_(L("Remove layer range")));
del_btn->SetToolTip(_L("Remove layer range"));
auto add_btn = new PlusMinusButton(m_parent, m_bmp_add, range);
wxString tooltip = wxGetApp().obj_list()->can_add_new_range_after_current(range);
add_btn->SetToolTip(tooltip.IsEmpty() ? _(L("Add layer range")) : tooltip);
add_btn->SetToolTip(tooltip.IsEmpty() ? _L("Add layer range") : tooltip);
add_btn->Enable(tooltip.IsEmpty());
auto sizer = create_layer(range, del_btn, add_btn);
@ -242,11 +241,9 @@ void ObjectLayers::msw_rescale()
// rescale edit-boxes
const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount();
for (int i = 0; i < cells_cnt; i++)
{
for (int i = 0; i < cells_cnt; ++i) {
const wxSizerItem* item = m_grid_sizer->GetItem(i);
if (item->IsWindow())
{
if (item->IsWindow()) {
LayerRangeEditor* editor = dynamic_cast<LayerRangeEditor*>(item->GetWindow());
if (editor != nullptr)
editor->msw_rescale();
@ -283,8 +280,7 @@ void ObjectLayers::sys_color_changed()
// rescale edit-boxes
const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount();
for (int i = 0; i < cells_cnt; i++)
{
for (int i = 0; i < cells_cnt; ++i) {
const wxSizerItem* item = m_grid_sizer->GetItem(i);
if (item->IsSizer()) {// case when we have editor with buttons
const std::vector<size_t> btns = {2, 3}; // del_btn, add_btn
@ -405,11 +401,9 @@ coordf_t LayerRangeEditor::get_value()
str.Replace(",", ".", false);
if (str == ".")
layer_height = 0.0;
else
{
if (!str.ToCDouble(&layer_height) || layer_height < 0.0f)
{
show_error(m_parent, _(L("Invalid numeric input.")));
else {
if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) {
show_error(m_parent, _L("Invalid numeric input."));
SetValue(double_to_string(layer_height));
}
}

View file

@ -7,6 +7,9 @@
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#if ENABLE_PROJECT_DIRTY_STATE
#include "MainFrame.hpp"
#endif // ENABLE_PROJECT_DIRTY_STATE
#include "OptionsGroup.hpp"
#include "Tab.hpp"
@ -18,6 +21,7 @@
#include <boost/algorithm/string.hpp>
#include <wx/progdlg.h>
#include <wx/numformatter.h>
#include "slic3r/Utils/FixModelByWin10.hpp"
@ -143,18 +147,28 @@ ObjectList::ObjectList(wxWindow* parent) :
// Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
{
// Accelerators
wxAcceleratorEntry entries[10];
entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY);
entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT);
entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE);
entries[3].Set(wxACCEL_CTRL, (int) 'A', wxID_SELECTALL);
entries[4].Set(wxACCEL_CTRL, (int) 'Z', wxID_UNDO);
entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO);
wxAcceleratorEntry entries[33];
entries[0].Set(wxACCEL_CTRL, (int)'C', wxID_COPY);
entries[1].Set(wxACCEL_CTRL, (int)'X', wxID_CUT);
entries[2].Set(wxACCEL_CTRL, (int)'V', wxID_PASTE);
entries[3].Set(wxACCEL_CTRL, (int)'A', wxID_SELECTALL);
entries[4].Set(wxACCEL_CTRL, (int)'Z', wxID_UNDO);
entries[5].Set(wxACCEL_CTRL, (int)'Y', wxID_REDO);
entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE);
entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE);
entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD);
entries[9].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE);
wxAcceleratorTable accel(10, entries);
entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE);
entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD);
entries[9].Set(wxACCEL_NORMAL, WXK_NUMPAD_ADD, wxID_ADD);
entries[10].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE);
entries[11].Set(wxACCEL_NORMAL, WXK_NUMPAD_SUBTRACT, wxID_REMOVE);
entries[12].Set(wxACCEL_NORMAL, int('p'), wxID_PRINT);
int numbers_cnt = 1;
for (auto char_number : { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }) {
entries[12 + numbers_cnt].Set(wxACCEL_NORMAL, int(char_number), wxID_LAST + numbers_cnt);
entries[22 + numbers_cnt].Set(wxACCEL_NORMAL, WXK_NUMPAD0 + numbers_cnt - 1, wxID_LAST + numbers_cnt);
numbers_cnt++;
}
wxAcceleratorTable accel(33, entries);
SetAcceleratorTable(accel);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY);
@ -165,6 +179,13 @@ ObjectList::ObjectList(wxWindow* parent) :
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instances(); }, wxID_ADD);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instances(); }, wxID_REMOVE);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->toggle_printable_state(); }, wxID_PRINT);
for (int i = 0; i < 10; i++)
this->Bind(wxEVT_MENU, [this, i](wxCommandEvent &evt) {
if (extruders_count() > 1 && i <= extruders_count())
this->set_extruder_for_selected_items(i);
}, wxID_LAST+i+1);
}
#else //__WXOSX__
Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
@ -1031,6 +1052,20 @@ void ObjectList::key_event(wxKeyEvent& event)
increase_instances();
else if (event.GetUnicodeKey() == '-')
decrease_instances();
else if (event.GetUnicodeKey() == 'p')
toggle_printable_state();
else if (extruders_count() > 1) {
std::vector<wxChar> numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
wxChar key_char = event.GetUnicodeKey();
if (std::find(numbers.begin(), numbers.end(), key_char) != numbers.end()) {
long extruder_number;
if (wxNumberFormatter::FromString(wxString(key_char), &extruder_number) &&
extruders_count() >= extruder_number)
set_extruder_for_selected_items(int(extruder_number));
}
else
event.Skip();
}
else
event.Skip();
}
@ -1457,12 +1492,15 @@ void ObjectList::load_shape_object(const std::string& type_name)
if (obj_idx < 0)
return;
take_snapshot(_(L("Add Shape")));
take_snapshot(_L("Add Shape"));
// Create mesh
BoundingBoxf3 bb;
TriangleMesh mesh = create_mesh(type_name, bb);
load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name));
load_mesh_object(mesh, _L("Shape") + "-" + _(type_name));
#if ENABLE_PROJECT_DIRTY_STATE
wxGetApp().mainframe->update_title();
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center)
@ -2113,18 +2151,15 @@ void ObjectList::part_selection_changed()
const auto item = GetSelection();
if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot ))
{
og_name = _(L("Group manipulation"));
if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) {
og_name = _L("Group manipulation");
const Selection& selection = scene_selection();
// don't show manipulation panel for case of all Object's parts selection
update_and_show_manipulations = !selection.is_single_full_instance();
}
else
{
if (item)
{
else {
if (item) {
const ItemType type = m_objects_model->GetItemType(item);
const wxDataViewItem parent = m_objects_model->GetParent(item);
const ItemType parent_type = m_objects_model->GetItemType(parent);
@ -2132,7 +2167,7 @@ void ObjectList::part_selection_changed()
if (parent == wxDataViewItem(nullptr)
|| type == itInfo) {
og_name = _(L("Object manipulation"));
og_name = _L("Object manipulation");
m_config = &(*m_objects)[obj_idx]->config;
update_and_show_manipulations = true;
@ -2152,35 +2187,35 @@ void ObjectList::part_selection_changed()
else {
if (type & itSettings) {
if (parent_type & itObject) {
og_name = _(L("Object Settings to modify"));
og_name = _L("Object Settings to modify");
m_config = &(*m_objects)[obj_idx]->config;
}
else if (parent_type & itVolume) {
og_name = _(L("Part Settings to modify"));
og_name = _L("Part Settings to modify");
volume_id = m_objects_model->GetVolumeIdByItem(parent);
m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config;
}
else if (parent_type & itLayer) {
og_name = _(L("Layer range Settings to modify"));
og_name = _L("Layer range Settings to modify");
m_config = &get_item_config(parent);
}
update_and_show_settings = true;
}
else if (type & itVolume) {
og_name = _(L("Part manipulation"));
og_name = _L("Part manipulation");
volume_id = m_objects_model->GetVolumeIdByItem(item);
m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config;
update_and_show_manipulations = true;
}
else if (type & itInstance) {
og_name = _(L("Instance manipulation"));
og_name = _L("Instance manipulation");
update_and_show_manipulations = true;
// fill m_config by object's values
m_config = &(*m_objects)[obj_idx]->config;
}
else if (type & (itLayerRoot|itLayer)) {
og_name = type & itLayerRoot ? _(L("Height ranges")) : _(L("Settings for height range"));
og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range");
update_and_show_layers = true;
if (type & itLayer)
@ -2809,7 +2844,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay
const int obj_idx = m_selected_object_id;
if (obj_idx < 0) return false;
take_snapshot(_(L("Edit Height Range")));
take_snapshot(_L("Edit Height Range"));
const ItemType sel_type = m_objects_model->GetItemType(GetSelection());
@ -3787,33 +3822,6 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event)
plater->set_current_canvas_as_dirty();
}
void ObjectList::extruder_selection()
{
wxArrayString choices;
choices.Add(_(L("default")));
for (int i = 1; i <= extruders_count(); ++i)
choices.Add(wxString::Format("%d", i));
const wxString& selected_extruder = wxGetSingleChoice(_(L("Select extruder number:")),
_(L("This extruder will be set for selected items")),
choices, 0, this);
if (selected_extruder.IsEmpty())
return;
const int extruder_num = selected_extruder == _(L("default")) ? 0 : atoi(selected_extruder.c_str());
// /* Another variant for an extruder selection */
// extruder_num = wxGetNumberFromUser(_(L("Attention!!! \n"
// "It's a possibile to set an extruder number \n"
// "for whole Object(s) and/or object Part(s), \n"
// "not for an Instance. ")),
// _(L("Enter extruder number:")),
// _(L("This extruder will be set for selected items")),
// 1, 1, 5, this);
set_extruder_for_selected_items(extruder_num);
}
void ObjectList::set_extruder_for_selected_items(const int extruder) const
{
wxDataViewItemArray sels;
@ -3906,6 +3914,8 @@ void ObjectList::toggle_printable_state()
{
wxDataViewItemArray sels;
GetSelections(sels);
if (sels.IsEmpty())
return;
wxDataViewItem frst_item = sels[0];
@ -3918,10 +3928,10 @@ void ObjectList::toggle_printable_state()
int inst_idx = type == itObject ? 0 : m_objects_model->GetInstanceIdByItem(frst_item);
bool printable = !object(obj_idx)->instances[inst_idx]->printable;
const wxString snapshot_text = sels.Count() > 1 ? (printable ? _L("Set Printable group") : _L("Set Unprintable group")) :
object(obj_idx)->instances.size() == 1 ? from_u8((boost::format("%1% %2%")
% (printable ? _L("Set Printable") : _L("Set Unprintable"))
% object(obj_idx)->name).str()) :
const wxString snapshot_text = sels.Count() > 1 ?
(printable ? _L("Set Printable group") : _L("Set Unprintable group")) :
object(obj_idx)->instances.size() == 1 ?
format_wxstr("%1% %2%", (printable ? _L("Set Printable") : _L("Set Unprintable")), from_u8(object(obj_idx)->name)) :
(printable ? _L("Set Printable Instance") : _L("Set Unprintable Instance"));
take_snapshot(snapshot_text);

View file

@ -390,7 +390,6 @@ private:
void OnEditingStarted(wxDataViewEvent &event);
#endif /* __WXMSW__ */
void OnEditingDone(wxDataViewEvent &event);
void extruder_selection();
};

View file

@ -26,21 +26,26 @@ const double ObjectManipulation::mm_to_in = 0.0393700787;
// Helper function to be used by drop to bed button. Returns lowest point of this
// volume in world coordinate system.
static double get_volume_min_z(const GLVolume* volume)
static double get_volume_min_z(const GLVolume& volume)
{
const Transform3f& world_matrix = volume->world_matrix().cast<float>();
#if ENABLE_ALLOW_NEGATIVE_Z
return volume.transformed_convex_hull_bounding_box().min.z();
#else
const Transform3f& world_matrix = volume.world_matrix().cast<float>();
// need to get the ModelVolume pointer
const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id];
const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id];
const ModelObject* mo = wxGetApp().model().objects[volume.composite_id.object_id];
const ModelVolume* mv = mo->volumes[volume.composite_id.volume_id];
const TriangleMesh& hull = mv->get_convex_hull();
float min_z = std::numeric_limits<float>::max();
for (const stl_facet& facet : hull.stl.facet_start) {
for (int i = 0; i < 3; ++ i)
for (int i = 0; i < 3; ++i)
min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i]));
}
return min_z;
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
@ -341,13 +346,27 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const Geometry::Transformation& instance_trafo = volume->get_instance_transformation();
Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume));
const Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(*volume));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
}
#if ENABLE_ALLOW_NEGATIVE_Z
else if (selection.is_single_full_instance()) {
const ModelObjectPtrs& objects = wxGetApp().model().objects;
const int idx = selection.get_object_idx();
if (0 <= idx && idx < static_cast<int>(objects.size())) {
const ModelObject* mo = wxGetApp().model().objects[idx];
const double min_z = mo->bounding_box().min.z();
if (std::abs(min_z) > EPSILON) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(2, m_cache.position.z() - min_z);
}
}
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
});
editors_grid_sizer->Add(m_drop_to_bed_button);
@ -671,11 +690,15 @@ void ObjectManipulation::update_reset_buttons_visibility()
if (selection.is_single_full_instance()) {
rotation = volume->get_instance_rotation();
scale = volume->get_instance_scaling_factor();
#if ENABLE_ALLOW_NEGATIVE_Z
min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z();
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
else {
rotation = volume->get_volume_rotation();
scale = volume->get_volume_scaling_factor();
min_z = get_volume_min_z(volume);
min_z = get_volume_min_z(*volume);
}
show_rotation = !rotation.isApprox(Vec3d::Zero());
show_scale = !scale.isApprox(Vec3d::Ones());

View file

@ -643,7 +643,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
if (sla_print_technology)
m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times);
else {
auto print_mode_stat = m_gcode_result->time_statistics.modes.front();
auto print_mode_stat = m_gcode_result->print_statistics.modes.front();
m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time);
}

View file

@ -32,6 +32,42 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic
#if ENABLE_PROJECT_DIRTY_STATE
// port of 948bc382655993721d93d3b9fce9b0186fcfb211
void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate)
{
Plater* plater = wxGetApp().plater();
// Following is needed to prevent taking an extra snapshot when the activation of
// the internal stack happens when the gizmo is already active (such as open gizmo,
// close gizmo, undo, start painting). The internal stack does not activate on the
// undo, because that would obliterate all future of the main stack (user would
// have to close the gizmo himself, he has no access to main undo/redo after the
// internal stack opens). We don't want the "entering" snapshot taken in this case,
// because there already is one.
std::string last_snapshot_name;
plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name);
if (activate && !m_internal_stack_active) {
std::string str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS
? _u8L("Entering Paint-on supports")
: _u8L("Entering Seam painting");
if (last_snapshot_name != str)
Plater::TakeSnapshot(plater, str);
plater->enter_gizmos_stack();
m_internal_stack_active = true;
}
if (!activate && m_internal_stack_active) {
plater->leave_gizmos_stack();
std::string str = get_painter_type() == PainterGizmoType::SEAM
? _u8L("Leaving Seam painting")
: _u8L("Leaving Paint-on supports");
if (last_snapshot_name != str)
Plater::TakeSnapshot(plater, str);
m_internal_stack_active = false;
}
}
#else
void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate)
{
if (activate && ! m_internal_stack_active) {
@ -51,6 +87,7 @@ void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate)
m_internal_stack_active = false;
}
}
#endif // ENABLE_PROJECT_DIRTY_STATE

View file

@ -45,18 +45,18 @@ bool GLGizmoSlaSupports::on_init()
{
m_shortcut_key = WXK_CONTROL_L;
m_desc["head_diameter"] = _(L("Head diameter")) + ": ";
m_desc["lock_supports"] = _(L("Lock supports under new islands"));
m_desc["remove_selected"] = _(L("Remove selected points"));
m_desc["remove_all"] = _(L("Remove all points"));
m_desc["apply_changes"] = _(L("Apply changes"));
m_desc["discard_changes"] = _(L("Discard changes"));
m_desc["minimal_distance"] = _(L("Minimal points distance")) + ": ";
m_desc["points_density"] = _(L("Support points density")) + ": ";
m_desc["auto_generate"] = _(L("Auto-generate points"));
m_desc["manual_editing"] = _(L("Manual editing"));
m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": ";
m_desc["reset_direction"] = _(L("Reset direction"));
m_desc["head_diameter"] = _L("Head diameter") + ": ";
m_desc["lock_supports"] = _L("Lock supports under new islands");
m_desc["remove_selected"] = _L("Remove selected points");
m_desc["remove_all"] = _L("Remove all points");
m_desc["apply_changes"] = _L("Apply changes");
m_desc["discard_changes"] = _L("Discard changes");
m_desc["minimal_distance"] = _L("Minimal points distance") + ": ";
m_desc["points_density"] = _L("Support points density") + ": ";
m_desc["auto_generate"] = _L("Auto-generate points");
m_desc["manual_editing"] = _L("Manual editing");
m_desc["clipping_of_view"] = _L("Clipping of view")+ ": ";
m_desc["reset_direction"] = _L("Reset direction");
return true;
}
@ -372,7 +372,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (m_selection_empty) {
std::pair<Vec3f, Vec3f> pos_and_normal;
if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add support point")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point"));
m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second);
m_parent.set_as_dirty();
m_wait_for_up_event = true;
@ -512,7 +512,7 @@ void GLGizmoSlaSupports::delete_selected_points(bool force)
std::abort();
}
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete support point")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point"));
for (unsigned int idx=0; idx<m_editing_cache.size(); ++idx) {
if (m_editing_cache[idx].selected && (!m_editing_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) {
@ -692,7 +692,7 @@ RENDER_AGAIN:
cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f;
float backup = m_new_point_head_diameter;
m_new_point_head_diameter = m_old_point_head_diameter;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change point head diameter")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter"));
m_new_point_head_diameter = backup;
for (auto& cache_entry : m_editing_cache)
if (cache_entry.selected)
@ -760,7 +760,7 @@ RENDER_AGAIN:
if (slider_released) {
mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash);
mo->config.set("support_points_density_relative", (int)m_density_stash);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change"));
mo->config.set("support_points_minimal_distance", minimal_point_distance);
mo->config.set("support_points_density_relative", (int)density);
wxGetApp().obj_list()->update_and_show_object_settings_item();
@ -867,10 +867,9 @@ bool GLGizmoSlaSupports::on_is_selectable() const
std::string GLGizmoSlaSupports::on_get_name() const
{
return (_(L("SLA Support Points")) + " [L]").ToUTF8().data();
return (_L("SLA Support Points") + " [L]").ToUTF8().data();
}
CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const
{
return CommonGizmosDataID(
@ -895,7 +894,11 @@ void GLGizmoSlaSupports::on_set_state()
// data are not yet available, the CallAfter will postpone taking the
// snapshot until they are. No, it does not feel right.
wxGetApp().CallAfter([]() {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on")));
#if ENABLE_PROJECT_DIRTY_STATE
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Entering SLA gizmo"));
#else
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned on"));
#endif // ENABLE_PROJECT_DIRTY_STATE
});
}
@ -909,8 +912,8 @@ void GLGizmoSlaSupports::on_set_state()
wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem
// on OSX with the wxMessageDialog being shown several times when clicked into.
wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually "
"edited support points?")) + "\n",_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO);
wxMessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
"edited support points?") + "\n",_L("Save changes?"), wxICON_QUESTION | wxYES | wxNO);
if (dlg.ShowModal() == wxID_YES)
editing_mode_apply_changes();
else
@ -922,7 +925,11 @@ void GLGizmoSlaSupports::on_set_state()
else {
// we are actually shutting down
disable_editing_mode(); // so it is not active next time the gizmo opens
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off")));
#if ENABLE_PROJECT_DIRTY_STATE
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Leaving SLA gizmo"));
#else
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned off"));
#endif // ENABLE_PROJECT_DIRTY_STATE
m_normal_cache.clear();
m_old_mo_id = -1;
}
@ -953,7 +960,7 @@ void GLGizmoSlaSupports::on_stop_dragging()
&& backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected
{
m_editing_cache[m_hover_id] = m_point_before_drag;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move support point")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point"));
m_editing_cache[m_hover_id] = backup;
}
}
@ -1046,7 +1053,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken
if (unsaved_changes()) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support points edit")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit"));
m_normal_cache.clear();
for (const CacheEntry& ce : m_editing_cache)
@ -1125,14 +1132,14 @@ void GLGizmoSlaSupports::get_data_from_backend()
void GLGizmoSlaSupports::auto_generate()
{
wxMessageDialog dlg(GUI::wxGetApp().plater(),
_(L("Autogeneration will erase all manually edited points.")) + "\n\n" +
_(L("Are you sure you want to do it?")) + "\n",
_(L("Warning")), wxICON_WARNING | wxYES | wxNO);
_L("Autogeneration will erase all manually edited points.") + "\n\n" +
_L("Are you sure you want to do it?") + "\n",
_L("Warning"), wxICON_WARNING | wxYES | wxNO);
ModelObject* mo = m_c->selection_info()->model_object();
if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points"));
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
mo->sla_points_status = sla::PointsStatus::Generating;
}
@ -1180,7 +1187,7 @@ bool GLGizmoSlaSupports::unsaved_changes() const
}
SlaGizmoHelpDialog::SlaGizmoHelpDialog()
: wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
: wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
const wxString ctrl = GUI::shortkey_ctrl_prefix();
@ -1191,7 +1198,7 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog()
const wxFont& font = wxGetApp().small_font();
const wxFont& bold_font = wxGetApp().bold_font();
auto note_text = new wxStaticText(this, wxID_ANY, _(L("Note: some shortcuts work in (non)editing mode only.")));
auto note_text = new wxStaticText(this, wxID_ANY, _L("Note: some shortcuts work in (non)editing mode only."));
note_text->SetFont(font);
auto vsizer = new wxBoxSizer(wxVERTICAL);
@ -1209,21 +1216,21 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog()
vsizer->AddSpacer(20);
std::vector<std::pair<wxString, wxString>> shortcuts;
shortcuts.push_back(std::make_pair(_(L("Left click")), _(L("Add point"))));
shortcuts.push_back(std::make_pair(_(L("Right click")), _(L("Remove point"))));
shortcuts.push_back(std::make_pair(_(L("Drag")), _(L("Move point"))));
shortcuts.push_back(std::make_pair(ctrl+_(L("Left click")), _(L("Add point to selection"))));
shortcuts.push_back(std::make_pair(alt+_(L("Left click")), _(L("Remove point from selection"))));
shortcuts.push_back(std::make_pair(wxString("Shift+")+_(L("Drag")), _(L("Select by rectangle"))));
shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _(L("Deselect by rectangle"))));
shortcuts.push_back(std::make_pair(ctrl+"A", _(L("Select all points"))));
shortcuts.push_back(std::make_pair("Delete", _(L("Remove selected points"))));
shortcuts.push_back(std::make_pair(ctrl+_(L("Mouse wheel")), _(L("Move clipping plane"))));
shortcuts.push_back(std::make_pair("R", _(L("Reset clipping plane"))));
shortcuts.push_back(std::make_pair("Enter", _(L("Apply changes"))));
shortcuts.push_back(std::make_pair("Esc", _(L("Discard changes"))));
shortcuts.push_back(std::make_pair("M", _(L("Switch to editing mode"))));
shortcuts.push_back(std::make_pair("A", _(L("Auto-generate points"))));
shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point")));
shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point")));
shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point")));
shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection")));
shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection")));
shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle")));
shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle")));
shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points")));
shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points")));
shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane")));
shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane")));
shortcuts.push_back(std::make_pair("Enter", _L("Apply changes")));
shortcuts.push_back(std::make_pair("Esc", _L("Discard changes")));
shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode")));
shortcuts.push_back(std::make_pair("A", _L("Auto-generate points")));
for (const auto& pair : shortcuts) {
auto shortcut = new wxStaticText(this, wxID_ANY, pair.first);

View file

@ -488,8 +488,7 @@ bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool (
int i=0;
const char* item_text;
while (items_getter(is_undo, i, &item_text))
{
while (items_getter(is_undo, i, &item_text)) {
ImGui::Selectable(item_text, i < hovered);
if (ImGui::IsItemHovered()) {

View file

@ -109,7 +109,11 @@ void KBShortcutsDialog::fill_shortcuts()
{ "0-6", L("Camera view") },
{ "E", L("Show/Hide object/instance labels") },
// Configuration
#ifdef __APPLE__
{ ctrl + ",", L("Preferences") },
#else
{ ctrl + "P", L("Preferences") },
#endif
// Help
{ "?", L("Show keyboard shortcuts list") }
};
@ -149,8 +153,13 @@ void KBShortcutsDialog::fill_shortcuts()
{ "Shift+Tab", L("Collapse/Expand the sidebar") },
#ifdef _WIN32
{ ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog, if enabled") },
#else
#ifdef __APPLE__
{ ctrl + "Shift+M", L("Show/Hide 3Dconnexion devices settings dialog") },
{ ctrl + "M", L("Minimize application") },
#else
{ ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") },
#endif // __APPLE__
#endif // _WIN32
#if ENABLE_RENDER_PICKING_PASS
// Don't localize debugging texts.
@ -171,6 +180,14 @@ void KBShortcutsDialog::fill_shortcuts()
};
m_full_shortcuts.push_back({ { _L("Gizmos"), _L("The following shortcuts are applicable when the specified gizmo is active") }, gizmos_shortcuts });
Shortcuts object_list_shortcuts = {
{ "P", L("Set selected items as Ptrintable/Unprintable") },
{ "0", L("Set default extruder for the selected items") },
{ "1-9", L("Set extruder number for the selected items") },
};
m_full_shortcuts.push_back({ { _L("Objects List"), "" }, object_list_shortcuts });
}
else {
Shortcuts commands_shortcuts = {

View file

@ -206,7 +206,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
// declare events
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) {
#if ENABLE_PROJECT_DIRTY_STATE
if (m_plater != nullptr)
m_plater->save_project_if_dirty();
if (event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) {
#else
if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) {
#endif // ENABLE_PROJECT_DIRTY_STATE
event.Veto();
return;
}
@ -487,8 +494,14 @@ void MainFrame::update_title()
// m_plater->get_project_filename() produces file name including path, but excluding extension.
// Don't try to remove the extension, it would remove part of the file name after the last dot!
wxString project = from_path(into_path(m_plater->get_project_filename()).filename());
#if ENABLE_PROJECT_DIRTY_STATE
wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
if (!dirty_marker.empty() || !project.empty())
title = dirty_marker + project + " - ";
#else
if (!project.empty())
title += (project + " - ");
#endif // ENABLE_PROJECT_DIRTY_STATE
}
std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID;
@ -531,9 +544,12 @@ void MainFrame::init_tabpanel()
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
#if ENABLE_VALIDATE_CUSTOM_GCODE
Tab* old_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(e.GetOldSelection()));
if (old_tab)
old_tab->validate_custom_gcodes();
if (int old_selection = e.GetOldSelection();
old_selection != wxNOT_FOUND && old_selection < m_tabpanel->GetPageCount()) {
Tab* old_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(old_selection));
if (old_tab)
old_tab->validate_custom_gcodes();
}
#endif // ENABLE_VALIDATE_CUSTOM_GCODE
wxWindow* panel = m_tabpanel->GetCurrentPage();
@ -672,10 +688,36 @@ bool MainFrame::can_start_new_project() const
return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty());
}
#if ENABLE_PROJECT_DIRTY_STATE
bool MainFrame::can_save() const
{
return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->get_project_filename().empty() && m_plater->is_project_dirty();
}
bool MainFrame::can_save_as() const
{
return (m_plater != nullptr) && !m_plater->model().objects.empty();
}
void MainFrame::save_project()
{
save_project_as(m_plater->get_project_filename(".3mf"));
}
void MainFrame::save_project_as(const wxString& filename)
{
bool ret = (m_plater != nullptr) ? m_plater->export_3mf(into_path(filename)) : false;
if (ret) {
// wxGetApp().update_saved_preset_from_current_preset();
m_plater->reset_project_dirty_after_save();
}
}
#else
bool MainFrame::can_save() const
{
return (m_plater != nullptr) && !m_plater->model().objects.empty();
}
#endif // ENABLE_PROJECT_DIRTY_STATE
bool MainFrame::can_export_model() const
{
@ -984,16 +1026,27 @@ void MainFrame::init_menubar_as_editor()
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId());
#if ENABLE_PROJECT_DIRTY_STATE
append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"),
[this](wxCommandEvent&) { save_project(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#else
append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#endif // ENABLE_PROJECT_DIRTY_STATE
#ifdef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"),
#else
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"),
#endif // __APPLE__
#if ENABLE_PROJECT_DIRTY_STATE
[this](wxCommandEvent&) { save_project_as(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save_as(); }, this);
#else
[this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#endif // ENABLE_PROJECT_DIRTY_STATE
fileMenu->AppendSeparator();
@ -1518,7 +1571,11 @@ void MainFrame::export_config()
// Load a config file containing a Print, Filament & Printer preset.
void MainFrame::load_config_file()
{
#if ENABLE_PROJECT_DIRTY_STATE
if (!wxGetApp().check_and_save_current_preset_changes())
#else
if (!wxGetApp().check_unsaved_changes())
#endif // ENABLE_PROJECT_DIRTY_STATE
return;
wxFileDialog dlg(this, _L("Select configuration to load:"),
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
@ -1547,7 +1604,11 @@ bool MainFrame::load_config_file(const std::string &path)
void MainFrame::export_configbundle(bool export_physical_printers /*= false*/)
{
#if ENABLE_PROJECT_DIRTY_STATE
if (!wxGetApp().check_and_save_current_preset_changes())
#else
if (!wxGetApp().check_unsaved_changes())
#endif // ENABLE_PROJECT_DIRTY_STATE
return;
// validate current configuration in case it's dirty
auto err = wxGetApp().preset_bundle->full_config().validate();
@ -1579,7 +1640,11 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/)
// but that behavior was not documented and likely buggy.
void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/)
{
#if ENABLE_PROJECT_DIRTY_STATE
if (!wxGetApp().check_and_save_current_preset_changes())
#else
if (!wxGetApp().check_unsaved_changes())
#endif // ENABLE_PROJECT_DIRTY_STATE
return;
if (file.IsEmpty()) {
wxFileDialog dlg(this, _L("Select configuration to load:"),

View file

@ -91,7 +91,9 @@ class MainFrame : public DPIFrame
void on_value_changed(wxCommandEvent&);
bool can_start_new_project() const;
#if !ENABLE_PROJECT_DIRTY_STATE
bool can_save() const;
#endif // !ENABLE_PROJECT_DIRTY_STATE
bool can_export_model() const;
bool can_export_toolpaths() const;
bool can_export_supports() const;
@ -184,6 +186,13 @@ public:
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
void on_config_changed(DynamicPrintConfig* cfg) const ;
#if ENABLE_PROJECT_DIRTY_STATE
bool can_save() const;
bool can_save_as() const;
void save_project();
void save_project_as(const wxString& filename = wxString());
#endif // ENABLE_PROJECT_DIRTY_STATE
void add_to_recent_projects(const wxString& filename);
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }

View file

@ -254,7 +254,7 @@ void NotificationManager::PopNotification::count_spaces()
void NotificationManager::PopNotification::count_lines()
{
std::string text = m_text1 + " " + m_hypertext;
std::string text = m_text1;
size_t last_end = 0;
m_lines_count = 0;
@ -302,6 +302,14 @@ void NotificationManager::PopNotification::count_lines()
}
m_lines_count++;
}
// hypertext calculation
if (!m_hypertext.empty()) {
int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0;
if (ImGui::CalcTextSize((text.substr(prev_end, last_end - prev_end) + m_hypertext).c_str()).x > m_window_width - m_window_width_offset) {
m_endlines.push_back(last_end);
m_lines_count++;
}
}
}
void NotificationManager::PopNotification::init()
@ -312,7 +320,7 @@ void NotificationManager::PopNotification::init()
count_spaces();
count_lines();
if (m_lines_count == 3)
m_multiline = true;
m_notification_start = GLCanvas3D::timestamp_now();
@ -342,39 +350,48 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons
int last_end = 0;
float starting_y = m_line_height/2;
float shift_y = m_line_height;
std::string line;
for (size_t i = 0; i < m_lines_count; i++) {
std::string line = m_text1.substr(last_end , m_endlines[i] - last_end);
if(i < m_lines_count - 1)
last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
line.clear();
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
imgui.text(line.c_str());
if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) {
line = m_text1.substr(last_end, m_endlines[i] - last_end);
last_end = m_endlines[i];
if (m_text1.size() > m_endlines[i])
last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
imgui.text(line.c_str());
}
}
//hyperlink text
if (!m_hypertext.empty())
{
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
if (!m_hypertext.empty()) {
render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
}
} else {
// line1
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
if (m_text1.size() >= m_endlines[0]) {
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
}
// line2
std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x)
{
line = line.substr(0, line.length() - 6);
line += "..";
}else
line += " ";
std::string line;
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2);
imgui.text(line.c_str());
if (m_text1.size() >= m_endlines[1]) {
line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) {
line = line.substr(0, line.length() - 6);
line += "..";
} else
line += " ";
imgui.text(line.c_str());
}
// "More" hypertext
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
}
} else {
//text 1
@ -382,15 +399,17 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons
float cursor_x = x_offset;
if(m_lines_count > 1) {
// line1
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
// line2
if (m_text1.length() > m_endlines[0]) {
std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
if (m_text1.length() >= m_endlines[0]) { // could be equal than substr takes whole string
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(cursor_y);
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
}
// line2
ImGui::SetCursorPosX(x_offset);
cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
ImGui::SetCursorPosY(cursor_y);
if (m_text1.length() > m_endlines[0]) { // must be greater otherwise theres nothing to show and m_text1[m_endlines[0]] is beyond last letter
std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0));
imgui.text(line.c_str());
cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x;
}
@ -401,8 +420,7 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons
cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x;
}
//hyperlink text
if (!m_hypertext.empty())
{
if (!m_hypertext.empty()) {
render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext);
}
@ -712,15 +730,18 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper&
float starting_y = m_line_height / 2;//10;
float shift_y = m_line_height;// -m_line_height / 20;
for (size_t i = 0; i < m_lines_count; i++) {
std::string line = m_text1.substr(last_end, m_endlines[i] - last_end);
if (i < m_lines_count - 1)
last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
imgui.text(line.c_str());
//hyperlink text
if ( i == 0 ) {
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(0, last_end).c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder."));
if (m_text1.size() >= m_endlines[i]) {
std::string line = m_text1.substr(last_end, m_endlines[i] - last_end);
last_end = m_endlines[i];
if (m_text1.size() > m_endlines[i])
last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
imgui.text(line.c_str());
//hyperlink text
if ( i == 0 ) {
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder."));
}
}
}

View file

@ -82,7 +82,13 @@ enum class NotificationType
// Notification emitted by Print::validate
PrintValidateWarning,
// Notification telling user to quit SLA supports manual editing
QuitSLAManualMode
QuitSLAManualMode,
// Desktop integration basic info
DesktopIntegrationSuccess,
DesktopIntegrationFail,
UndoDesktopIntegrationSuccess,
UndoDesktopIntegrationFail
};
class NotificationManager
@ -514,6 +520,14 @@ private:
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
{NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10,
_u8L("This model doesn't allow to automatically add the color changes") },
{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10,
_u8L("Desktop integration was successful.") },
{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10,
_u8L("Desktop integration failed.") },
{NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10,
_u8L("Undo desktop integration was successful.") },
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotification, 10,
_u8L("Undo desktop integration failed.") },
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification

View file

@ -1180,6 +1180,15 @@ int ObjectDataViewModel::GetExtruderNumber(const wxDataViewItem& item) const
return atoi(node->m_extruder.c_str());
}
wxString ObjectDataViewModel::GetColumnType(unsigned int col) const
{
if (col == colName || col == colExtruder)
return wxT("DataViewBitmapText");
if (col == colPrint || col == colEditing)
return wxT("DataViewBitmap");
return wxT("string");
}
void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const
{
wxASSERT(item.IsOk());

View file

@ -316,7 +316,7 @@ public:
// helper methods to change the model
unsigned int GetColumnCount() const override { return 3;}
wxString GetColumnType(unsigned int col) const override{ return wxT("string"); }
wxString GetColumnType(unsigned int col) const override;
void GetValue( wxVariant &variant,
const wxDataViewItem &item,

View file

@ -228,7 +228,7 @@ public:
int config_type() const throw() { return m_config_type; }
const t_opt_map& opt_map() const throw() { return m_opt_map; }
void set_config_category_and_type(const wxString &category, int type) { this->m_config_category = category; this->m_config_type = type; }
void set_config_category_and_type(const wxString &category, int type) { m_config_category = category; m_config_type = type; }
void set_config(DynamicPrintConfig* config) { m_config = config; m_modelconfig = nullptr; }
Option get_option(const std::string& opt_key, int opt_index = -1);
Line create_single_option_line(const std::string& title, const wxString& path = wxEmptyString, int idx = -1) /*const*/{

View file

@ -81,6 +81,9 @@
#include "InstanceCheck.hpp"
#include "NotificationManager.hpp"
#include "PresetComboBoxes.hpp"
#if ENABLE_PROJECT_DIRTY_STATE
#include "ProjectDirtyStateManager.hpp"
#endif // ENABLE_PROJECT_DIRTY_STATE
#ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp"
@ -1172,10 +1175,10 @@ void Sidebar::update_sliced_info_sizer()
new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower"));
wxString info_text = is_wipe_tower ?
wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef,
(ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef,
ps.total_wipe_tower_filament / /*1000*/koef) :
wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef);
wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / koef,
(ps.total_used_filament - ps.total_wipe_tower_filament) / koef,
ps.total_wipe_tower_filament / koef) :
wxString::Format("%.2f", ps.total_used_filament / koef);
p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label);
koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f;
@ -1203,7 +1206,7 @@ void Sidebar::update_sliced_info_sizer()
filament_weight = ps.total_weight;
else {
double filament_density = filament_preset->config.opt_float("filament_density", 0);
filament_weight = filament.second * filament_density * 2.4052f * 0.001; // assumes 1.75mm filament diameter;
filament_weight = filament.second * filament_density/* *2.4052f*/ * 0.001; // assumes 1.75mm filament diameter;
new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament.first + 1);
info_text += wxString::Format("\n%.2f", filament_weight);
@ -1357,7 +1360,8 @@ void Sidebar::update_ui_from_settings()
update_sliced_info_sizer();
// update Cut gizmo, if it's open
p->plater->canvas3D()->update_gizmos_on_off_state();
p->plater->canvas3D()->request_extra_frame();
p->plater->set_current_canvas_as_dirty();
p->plater->get_current_canvas3D()->request_extra_frame();
}
std::vector<PlaterPresetComboBox*>& Sidebar::combos_filament()
@ -1395,7 +1399,13 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
this->MSWUpdateDragImageOnLeave();
#endif // WIN32
#if ENABLE_PROJECT_DIRTY_STATE
bool res = (m_plater != nullptr) ? m_plater->load_files(filenames) : false;
wxGetApp().mainframe->update_title();
return res;
#else
return (m_plater != nullptr) ? m_plater->load_files(filenames) : false;
#endif // ENABLE_PROJECT_DIRTY_STATE
}
// State to manage showing after export notifications and device ejecting
@ -1439,6 +1449,10 @@ struct Plater::priv
Preview *preview;
NotificationManager* notification_manager { nullptr };
#if ENABLE_PROJECT_DIRTY_STATE
ProjectDirtyStateManager dirty_state;
#endif // ENABLE_PROJECT_DIRTY_STATE
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
@ -1509,6 +1523,31 @@ struct Plater::priv
priv(Plater *q, MainFrame *main_frame);
~priv();
#if ENABLE_PROJECT_DIRTY_STATE
bool is_project_dirty() const { return dirty_state.is_dirty(); }
void update_project_dirty_from_presets() { dirty_state.update_from_presets(); }
bool save_project_if_dirty() {
if (dirty_state.is_dirty()) {
MainFrame* mainframe = wxGetApp().mainframe;
if (mainframe->can_save_as()) {
wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
int res = dlg.ShowModal();
if (res == wxID_YES)
mainframe->save_project_as(wxGetApp().plater()->get_project_filename());
else if (res == wxID_CANCEL)
return false;
}
}
return true;
}
void reset_project_dirty_after_save() { dirty_state.reset_after_save(); }
void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); }
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_project_state_debug_window() const { dirty_state.render_debug_window(); }
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
#endif // ENABLE_PROJECT_DIRTY_STATE
enum class UpdateParams {
FORCE_FULL_SCREEN_REFRESH = 1,
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
@ -1554,7 +1593,11 @@ 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);
#if ENABLE_ALLOW_NEGATIVE_Z
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false);
#else
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
#endif // ENABLE_ALLOW_NEGATIVE_Z
wxString get_export_file(GUI::FileType file_type);
const Selection& get_selection() const;
@ -1588,8 +1631,8 @@ struct Plater::priv
void redo();
void undo_redo_to(size_t time_to_load);
void suppress_snapshots() { this->m_prevent_snapshots++; }
void allow_snapshots() { this->m_prevent_snapshots--; }
void suppress_snapshots() { m_prevent_snapshots++; }
void allow_snapshots() { m_prevent_snapshots--; }
void process_validation_warning(const std::string& warning) const;
@ -2294,11 +2337,19 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
return obj_idxs;
}
#if ENABLE_ALLOW_NEGATIVE_Z
for (ModelObject* model_object : model.objects) {
if (!type_3mf && !type_zip_amf)
model_object->center_around_origin(false);
model_object->ensure_on_bed(is_project_file);
}
#else
for (ModelObject* model_object : model.objects) {
if (!type_3mf && !type_zip_amf)
model_object->center_around_origin(false);
model_object->ensure_on_bed();
}
#endif // ENABLE_ALLOW_NEGATIVE_Z
// check multi-part object adding for the SLA-printing
if (printer_technology == ptSLA) {
@ -2312,7 +2363,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
if (one_by_one) {
#if ENABLE_ALLOW_NEGATIVE_Z
auto loaded_idxs = load_model_objects(model.objects, is_project_file);
#else
auto loaded_idxs = load_model_objects(model.objects);
#endif // ENABLE_ALLOW_NEGATIVE_Z
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.
@ -2364,7 +2419,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// #define AUTOPLACEMENT_ON_LOAD
#if ENABLE_ALLOW_NEGATIVE_Z
std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z)
#else
std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects)
#endif // ENABLE_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();
@ -2441,7 +2500,11 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
}
#endif // ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG
#if ENABLE_ALLOW_NEGATIVE_Z
object->ensure_on_bed(allow_negative_z);
#else
object->ensure_on_bed();
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
#ifdef AUTOPLACEMENT_ON_LOAD
@ -2597,8 +2660,7 @@ int Plater::priv::get_selected_volume_idx() const
void Plater::priv::selection_changed()
{
// if the selection is not valid to allow for layer editing, we need to turn off the tool if it is running
bool enable_layer_editing = layers_height_allowed();
if (!enable_layer_editing && view3D->is_layers_editing_enabled()) {
if (!layers_height_allowed() && view3D->is_layers_editing_enabled()) {
SimpleEvent evt(EVT_GLTOOLBAR_LAYERSEDITING);
on_action_layersediting(evt);
}
@ -2735,10 +2797,11 @@ void Plater::priv::split_object()
Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one solid part."));
else
{
if (current_model_object->volumes.size() != new_objects.size())
// If we splited object which is contain some parts/modifiers then all non-solid parts (modifiers) were deleted
if (current_model_object->volumes.size() > 1 && current_model_object->volumes.size() != new_objects.size())
notification_manager->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotification,
_u8L("All non-solid parts (modifiers) was deleted"));
_u8L("All non-solid parts (modifiers) were deleted"));
Plater::TakeSnapshot snapshot(q, _L("Split to Objects"));
@ -3054,21 +3117,19 @@ void Plater::priv::reload_from_disk()
int volume_idx;
// operators needed by std::algorithms
bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
bool operator < (const SelectedVolume& other) const { return object_idx < other.object_idx || (object_idx == other.object_idx && volume_idx < other.volume_idx); }
bool operator == (const SelectedVolume& other) const { return object_idx == other.object_idx && volume_idx == other.volume_idx; }
};
std::vector<SelectedVolume> selected_volumes;
// collects selected ModelVolumes
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
for (unsigned int idx : selected_volumes_idxs)
{
for (unsigned int idx : selected_volumes_idxs) {
const GLVolume* v = selection.get_volume(idx);
int v_idx = v->volume_idx();
if (v_idx >= 0)
{
if (v_idx >= 0) {
int o_idx = v->object_idx();
if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
if (0 <= o_idx && o_idx < (int)model.objects.size())
selected_volumes.push_back({ o_idx, v_idx });
}
}
@ -3078,13 +3139,11 @@ void Plater::priv::reload_from_disk()
// collects paths of files to load
std::vector<fs::path> input_paths;
std::vector<fs::path> missing_input_paths;
for (const SelectedVolume& v : selected_volumes)
{
for (const SelectedVolume& v : selected_volumes) {
const ModelObject* object = model.objects[v.object_idx];
const ModelVolume* volume = object->volumes[v.volume_idx];
if (!volume->source.input_file.empty())
{
if (!volume->source.input_file.empty()) {
if (fs::exists(volume->source.input_file))
input_paths.push_back(volume->source.input_file);
else
@ -3097,8 +3156,7 @@ void Plater::priv::reload_from_disk()
std::sort(missing_input_paths.begin(), missing_input_paths.end());
missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end());
while (!missing_input_paths.empty())
{
while (!missing_input_paths.empty()) {
// ask user to select the missing file
fs::path search = missing_input_paths.back();
wxString title = _L("Please select the file to reload");
@ -3112,21 +3170,18 @@ void Plater::priv::reload_from_disk()
std::string sel_filename_path = dialog.GetPath().ToUTF8().data();
std::string sel_filename = fs::path(sel_filename_path).filename().string();
if (boost::algorithm::iequals(search.filename().string(), sel_filename))
{
if (boost::algorithm::iequals(search.filename().string(), sel_filename)) {
input_paths.push_back(sel_filename_path);
missing_input_paths.pop_back();
fs::path sel_path = fs::path(sel_filename_path).remove_filename().string();
std::vector<fs::path>::iterator it = missing_input_paths.begin();
while (it != missing_input_paths.end())
{
while (it != missing_input_paths.end()) {
// try to use the path of the selected file with all remaining missing files
fs::path repathed_filename = sel_path;
repathed_filename /= it->filename();
if (fs::exists(repathed_filename))
{
if (fs::exists(repathed_filename)) {
input_paths.push_back(repathed_filename.string());
it = missing_input_paths.erase(it);
}
@ -3134,8 +3189,7 @@ void Plater::priv::reload_from_disk()
++it;
}
}
else
{
else {
wxString message = _L("It is not allowed to change the file to reload") + " (" + from_u8(search.filename().string()) + ").\n" + _L("Do you want to retry") + " ?";
wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() != wxID_YES)
@ -3149,8 +3203,7 @@ void Plater::priv::reload_from_disk()
std::vector<wxString> fail_list;
// load one file at a time
for (size_t i = 0; i < input_paths.size(); ++i)
{
for (size_t i = 0; i < input_paths.size(); ++i) {
const auto& path = input_paths[i].string();
wxBusyCursor wait;
@ -3160,8 +3213,7 @@ void Plater::priv::reload_from_disk()
try
{
new_model = Model::read_from_file(path, nullptr, true, false);
for (ModelObject* model_object : new_model.objects)
{
for (ModelObject* model_object : new_model.objects) {
model_object->center_around_origin();
model_object->ensure_on_bed();
}
@ -3173,34 +3225,31 @@ void Plater::priv::reload_from_disk()
}
// update the selected volumes whose source is the current file
for (const SelectedVolume& sel_v : selected_volumes)
{
for (const SelectedVolume& sel_v : selected_volumes) {
ModelObject* old_model_object = model.objects[sel_v.object_idx];
ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx];
#if ENABLE_ALLOW_NEGATIVE_Z
bool sinking = old_model_object->bounding_box().min.z() < 0.0;
#endif // ENABLE_ALLOW_NEGATIVE_Z
bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string());
bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string());
if (has_source || has_name)
{
if (has_source || has_name) {
int new_volume_idx = -1;
int new_object_idx = -1;
if (has_source)
{
if (has_source) {
// take idxs from source
new_volume_idx = old_volume->source.volume_idx;
new_object_idx = old_volume->source.object_idx;
}
else
{
else {
// take idxs from the 1st matching volume
for (size_t o = 0; o < new_model.objects.size(); ++o)
{
for (size_t o = 0; o < new_model.objects.size(); ++o) {
ModelObject* obj = new_model.objects[o];
bool found = false;
for (size_t v = 0; v < obj->volumes.size(); ++v)
{
if (obj->volumes[v]->name == old_volume->name)
{
for (size_t v = 0; v < obj->volumes.size(); ++v) {
if (obj->volumes[v]->name == old_volume->name) {
new_volume_idx = (int)v;
new_object_idx = (int)o;
found = true;
@ -3212,19 +3261,16 @@ void Plater::priv::reload_from_disk()
}
}
if ((new_object_idx < 0) && ((int)new_model.objects.size() <= new_object_idx))
{
if (new_object_idx < 0 && (int)new_model.objects.size() <= new_object_idx) {
fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
continue;
}
ModelObject* new_model_object = new_model.objects[new_object_idx];
if ((new_volume_idx < 0) && ((int)new_model.objects.size() <= new_volume_idx))
{
if (new_volume_idx < 0 && (int)new_model.objects.size() <= new_volume_idx) {
fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
continue;
}
if (new_volume_idx < (int)new_model_object->volumes.size())
{
if (new_volume_idx < (int)new_model_object->volumes.size()) {
old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
ModelVolume* new_volume = old_model_object->volumes.back();
new_volume->set_new_unique_id();
@ -3241,7 +3287,10 @@ void Plater::priv::reload_from_disk()
new_volume->seam_facets.assign(old_volume->seam_facets);
std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
old_model_object->ensure_on_bed();
#if ENABLE_ALLOW_NEGATIVE_Z
if (!sinking)
#endif // ENABLE_ALLOW_NEGATIVE_Z
old_model_object->ensure_on_bed();
sla::reproject_points_and_holes(old_model_object);
}
@ -3249,11 +3298,9 @@ void Plater::priv::reload_from_disk()
}
}
if (!fail_list.empty())
{
if (!fail_list.empty()) {
wxString message = _L("Unable to reload:") + "\n";
for (const wxString& s : fail_list)
{
for (const wxString& s : fail_list) {
message += s + "\n";
}
wxMessageDialog dlg(q, message, _L("Error during reload"), wxOK | wxOK_DEFAULT | wxICON_WARNING);
@ -3264,8 +3311,7 @@ void Plater::priv::reload_from_disk()
update();
// new GLVolumes have been created at this point, so update their printable state
for (size_t i = 0; i < model.objects.size(); ++i)
{
for (size_t i = 0; i < model.objects.size(); ++i) {
view3D->get_canvas3d()->update_instance_printable_state_for_object(i);
}
}
@ -3285,8 +3331,7 @@ void Plater::priv::reload_all_from_disk()
reload_from_disk();
// restore previous selection
selection.clear();
for (unsigned int idx : curr_idxs)
{
for (unsigned int idx : curr_idxs) {
selection.add(idx, false);
}
}
@ -4006,7 +4051,7 @@ void Plater::priv::reset_gcode_toolpaths()
bool Plater::priv::can_set_instance_to_object() const
{
const int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1);
return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->instances.size() > 1;
}
bool Plater::priv::can_split(bool to_objects) const
@ -4020,7 +4065,12 @@ bool Plater::priv::layers_height_allowed() const
return false;
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
#if ENABLE_ALLOW_NEGATIVE_Z
return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->bounding_box().max.z() > 0.0 &&
config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
#else
return 0 <= obj_idx && obj_idx < (int)model.objects.size() && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
bool Plater::priv::can_mirror() const
@ -4213,9 +4263,9 @@ int Plater::priv::get_active_snapshot_index()
void Plater::priv::take_snapshot(const std::string& snapshot_name)
{
if (this->m_prevent_snapshots > 0)
if (m_prevent_snapshots > 0)
return;
assert(this->m_prevent_snapshots >= 0);
assert(m_prevent_snapshots >= 0);
UndoRedo::SnapshotData snapshot_data;
snapshot_data.printer_technology = this->printer_technology;
if (this->view3D->is_layers_editing_enabled())
@ -4245,6 +4295,11 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name)
}
this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data);
this->undo_redo_stack().release_least_recently_used();
#if ENABLE_PROJECT_DIRTY_STATE
dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot);
#endif // ENABLE_PROJECT_DIRTY_STATE
// Save the last active preset name of a particular printer technology.
((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
@ -4285,8 +4340,13 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
if (printer_technology_changed) {
// Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA";
#if ENABLE_PROJECT_DIRTY_STATE
if (!wxGetApp().check_and_save_current_preset_changes(format_wxstr(_L(
"%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt)))
#else
if (! wxGetApp().check_unsaved_changes(format_wxstr(_L(
"%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt)))
#endif // ENABLE_PROJECT_DIRTY_STATE
// Don't switch the profiles.
return;
}
@ -4375,6 +4435,10 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active)
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
}
#if ENABLE_PROJECT_DIRTY_STATE
dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo);
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */)
@ -4458,9 +4522,16 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame)
// Initialization performed in the private c-tor
}
Plater::~Plater()
{
}
#if ENABLE_PROJECT_DIRTY_STATE
bool Plater::is_project_dirty() const { return p->is_project_dirty(); }
void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); }
bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); }
void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); }
void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); }
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); }
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
#endif // ENABLE_PROJECT_DIRTY_STATE
Sidebar& Plater::sidebar() { return *p->sidebar; }
Model& Plater::model() { return p->model; }
@ -4471,12 +4542,30 @@ SLAPrint& Plater::sla_print() { return p->sla_print; }
void Plater::new_project()
{
#if ENABLE_PROJECT_DIRTY_STATE
if (!p->save_project_if_dirty())
return;
#endif // ENABLE_PROJECT_DIRTY_STATE
p->select_view_3D("3D");
#if ENABLE_PROJECT_DIRTY_STATE
take_snapshot(_L("New Project"));
Plater::SuppressSnapshots suppress(this);
reset();
reset_project_dirty_initial_presets();
update_project_dirty_from_presets();
#else
wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void Plater::load_project()
{
#if ENABLE_PROJECT_DIRTY_STATE
if (!p->save_project_if_dirty())
return;
#endif // ENABLE_PROJECT_DIRTY_STATE
// Ask user for a project file name.
wxString input_file;
wxGetApp().load_project(this, input_file);
@ -4500,8 +4589,16 @@ void Plater::load_project(const wxString& filename)
std::vector<size_t> res = load_files(input_paths);
// if res is empty no data has been loaded
#if ENABLE_PROJECT_DIRTY_STATE
if (!res.empty()) {
p->set_project_filename(filename);
reset_project_dirty_initial_presets();
update_project_dirty_from_presets();
}
#else
if (!res.empty())
p->set_project_filename(filename);
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void Plater::add_model(bool imperial_units/* = false*/)
@ -4532,7 +4629,13 @@ void Plater::add_model(bool imperial_units/* = false*/)
}
Plater::TakeSnapshot snapshot(this, snapshot_label);
#if ENABLE_PROJECT_DIRTY_STATE
std::vector<size_t> res = load_files(paths, true, false, imperial_units);
if (!res.empty())
wxGetApp().mainframe->update_title();
#else
load_files(paths, true, false, imperial_units);
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void Plater::import_sl1_archive()
@ -5247,24 +5350,39 @@ void Plater::export_amf()
}
}
#if ENABLE_PROJECT_DIRTY_STATE
bool Plater::export_3mf(const boost::filesystem::path& output_path)
#else
void Plater::export_3mf(const boost::filesystem::path& output_path)
#endif // ENABLE_PROJECT_DIRTY_STATE
{
if (p->model.objects.empty()
|| canvas3D()->get_gizmos_manager().is_in_editing_mode(true))
#if ENABLE_PROJECT_DIRTY_STATE
return false;
#else
return;
#endif // ENABLE_PROJECT_DIRTY_STATE
wxString path;
bool export_config = true;
if (output_path.empty())
{
if (output_path.empty()) {
path = p->get_export_file(FT_3MF);
#if ENABLE_PROJECT_DIRTY_STATE
if (path.empty()) { return false; }
#else
if (path.empty()) { return; }
#endif // ENABLE_PROJECT_DIRTY_STATE
}
else
path = from_path(output_path);
if (!path.Lower().EndsWith(".3mf"))
#if ENABLE_PROJECT_DIRTY_STATE
return false;
#else
return;
#endif // ENABLE_PROJECT_DIRTY_STATE
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
const std::string path_u8 = into_u8(path);
@ -5272,6 +5390,19 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
ThumbnailData thumbnail_data;
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
#if ENABLE_PROJECT_DIRTY_STATE
bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data);
if (ret) {
// Success
p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path));
p->set_project_filename(path);
}
else {
// Failure
p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path));
}
return ret;
#else
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) {
// Success
p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path));
@ -5281,6 +5412,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
// Failure
p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path));
}
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void Plater::reload_from_disk()
@ -5737,7 +5869,7 @@ wxString Plater::get_project_filename(const wxString& extension) const
void Plater::set_project_filename(const wxString& filename)
{
return p->set_project_filename(filename);
p->set_project_filename(filename);
}
bool Plater::is_export_gcode_scheduled() const
@ -5817,6 +5949,14 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology)
//FIXME for SLA synchronize
//p->background_process.apply(Model)!
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
if (printer_technology == ptSLA) {
for (ModelObject* model_object : p->model.objects) {
model_object->ensure_on_bed();
}
}
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export");
p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer");
@ -5836,7 +5976,15 @@ void Plater::changed_object(int obj_idx)
return;
// recenter and re - align to Z = 0
auto model_object = p->model.objects[obj_idx];
#if ENABLE_ALLOW_NEGATIVE_Z
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
model_object->ensure_on_bed(this->p->printer_technology != ptSLA);
#else
model_object->ensure_on_bed(true);
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
#else
model_object->ensure_on_bed();
#endif // ENABLE_ALLOW_NEGATIVE_Z
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.
@ -5854,11 +6002,18 @@ void Plater::changed_objects(const std::vector<size_t>& object_idxs)
if (object_idxs.empty())
return;
for (size_t obj_idx : object_idxs)
{
for (size_t obj_idx : object_idxs) {
#if ENABLE_ALLOW_NEGATIVE_Z
if (obj_idx < p->model.objects.size()) {
if (p->model.objects[obj_idx]->bounding_box().min.z() >= 0.0)
// re - align to Z = 0
p->model.objects[obj_idx]->ensure_on_bed();
}
#else
if (obj_idx < p->model.objects.size())
// recenter and re - align to Z = 0
p->model.objects[obj_idx]->ensure_on_bed();
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
if (this->p->printer_technology == ptSLA) {
// Update the SLAPrint from the current Model, so that the reload_scene()
@ -6141,6 +6296,9 @@ bool Plater::can_mirror() const { return p->can_mirror(); }
bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); }
#if ENABLE_PROJECT_DIRTY_STATE
const UndoRedo::Stack& Plater::undo_redo_stack_active() const { return p->undo_redo_stack(); }
#endif // ENABLE_PROJECT_DIRTY_STATE
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }

View file

@ -128,7 +128,18 @@ public:
Plater(const Plater &) = delete;
Plater &operator=(Plater &&) = delete;
Plater &operator=(const Plater &) = delete;
~Plater();
~Plater() = default;
#if ENABLE_PROJECT_DIRTY_STATE
bool is_project_dirty() const;
void update_project_dirty_from_presets();
bool save_project_if_dirty();
void reset_project_dirty_after_save();
void reset_project_dirty_initial_presets();
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_project_state_debug_window() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
#endif // ENABLE_PROJECT_DIRTY_STATE
Sidebar& sidebar();
Model& model();
@ -198,7 +209,11 @@ public:
void export_gcode(bool prefer_removable);
void export_stl(bool extended = false, bool selection_only = false);
void export_amf();
#if ENABLE_PROJECT_DIRTY_STATE
bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
#else
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
#endif // ENABLE_PROJECT_DIRTY_STATE
void reload_from_disk();
void reload_all_from_disk();
bool has_toolpaths_to_export() const;
@ -228,6 +243,9 @@ public:
// For the memory statistics.
const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const;
void clear_undo_redo_stack_main();
#if ENABLE_PROJECT_DIRTY_STATE
const Slic3r::UndoRedo::Stack& undo_redo_stack_active() const;
#endif // ENABLE_PROJECT_DIRTY_STATE
// Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo.
void enter_gizmos_stack();
void leave_gizmos_stack();

View file

@ -134,7 +134,7 @@ void PresetComboBox::OnSelect(wxCommandEvent& evt)
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX)
this->SetSelection(this->m_last_selected);
this->SetSelection(m_last_selected);
else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty())) {
m_last_selected = selected_item;
on_selection_changed(selected_item);
@ -698,7 +698,7 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) {
this->SetSelection(this->m_last_selected);
this->SetSelection(m_last_selected);
evt.StopPropagation();
if (marker == LABEL_ITEM_MARKER)
return;
@ -715,8 +715,8 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
}
return;
}
else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || this->m_last_selected != selected_item || m_collection->current_is_dirty())
this->m_last_selected = selected_item;
else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || m_last_selected != selected_item || m_collection->current_is_dirty())
m_last_selected = selected_item;
evt.Skip();
}
@ -973,7 +973,7 @@ void TabPresetComboBox::OnSelect(wxCommandEvent &evt)
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) {
this->SetSelection(this->m_last_selected);
this->SetSelection(m_last_selected);
if (marker == LABEL_ITEM_WIZARD_PRINTERS)
wxTheApp->CallAfter([this]() {
wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS);

View file

@ -0,0 +1,415 @@
#include "libslic3r/libslic3r.h"
#include "ProjectDirtyStateManager.hpp"
#include "ImGuiWrapper.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "../Utils/UndoRedo.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <algorithm>
#include <assert.h>
#if ENABLE_PROJECT_DIRTY_STATE
namespace Slic3r {
namespace GUI {
enum class EStackType
{
Main,
Gizmo
};
// returns the current active snapshot (the topmost snapshot in the undo part of the stack) in the given stack
static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
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) ?
&snapshots[idx] : nullptr;
assert(ret != nullptr);
return ret;
}
// returns the last saveable snapshot (the topmost snapshot in the undo part of the stack that can be saved) in the given stack
static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack,
const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) {
// returns true if the given snapshot is not saveable
auto skip_main = [&gizmos, last_save_main, &stack](const UndoRedo::Snapshot& snapshot) {
auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) {
if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
if (gizmos.current)
return true;
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1)));
if (gizmos.is_used_and_modified(*leaving_snapshot))
return true;
}
}
return false;
};
if (snapshot.name == _utf8("New Project"))
return true;
else if (snapshot.name == _utf8("Reset Project"))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Load Project")))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Selection")))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
if (last_save_main != snapshot.timestamp + 1 && !is_gizmo_with_modifications(snapshot))
return true;
}
else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) {
if (last_save_main != snapshot.timestamp && !gizmos.is_used_and_modified(snapshot))
return true;
}
return false;
};
// returns true if the given snapshot is not saveable
auto skip_gizmo = [](const UndoRedo::Snapshot& snapshot) {
// put here any needed condition to skip the snapshot
return false;
};
const UndoRedo::Snapshot* curr = get_active_snapshot(stack);
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
size_t shift = 1;
while (curr->timestamp > 0 && ((type == EStackType::Main && skip_main(*curr)) || (type == EStackType::Gizmo && skip_gizmo(*curr)))) {
const UndoRedo::Snapshot* temp = curr;
curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift)));
shift = (curr == temp) ? shift + 1 : 1;
}
if (type == EStackType::Main) {
if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) {
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving")))
curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1)));
}
}
return curr->timestamp > 0 ? curr : nullptr;
}
// returns the name of the gizmo contained in the given string
static std::string extract_gizmo_name(const std::string& s) {
static const std::array<std::string, 2> prefixes = { _utf8("Entering"), _utf8("Leaving") };
std::string ret;
for (const std::string& prefix : prefixes) {
if (boost::starts_with(s, prefix))
ret = s.substr(prefix.length() + 1);
if (!ret.empty())
break;
}
return ret;
}
void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snapshot& snapshot)
{
const std::string name = extract_gizmo_name(snapshot.name);
auto it = used.find(name);
if (it == used.end())
it = used.insert({ name, { {} } }).first;
it->second.modified_timestamps.push_back(snapshot.timestamp);
}
void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack)
{
const std::vector<UndoRedo::Snapshot>& snapshots = main_stack.snapshots();
for (auto& item : used) {
auto it = item.second.modified_timestamps.begin();
while (it != item.second.modified_timestamps.end()) {
size_t timestamp = *it;
auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; });
if (snapshot_it == snapshots.end())
it = item.second.modified_timestamps.erase(it);
else
++it;
}
}
}
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const
{
for (auto& [name, gizmo] : used) {
if (!gizmo.modified_timestamps.empty())
return true;
}
return false;
}
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
// returns true if the given snapshot is contained in any of the gizmos caches
bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const
{
for (const auto& item : used) {
for (size_t i : item.second.modified_timestamps) {
if (i == snapshot.timestamp)
return true;
}
}
return false;
}
void ProjectDirtyStateManager::DirtyState::Gizmos::reset()
{
used.clear();
}
void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type)
{
if (!wxGetApp().initialized())
return;
const Plater* plater = wxGetApp().plater();
if (plater == nullptr)
return;
const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
if (&main_stack == &active_stack)
update_from_undo_redo_main_stack(type, main_stack);
else
update_from_undo_redo_gizmo_stack(type, active_stack);
wxGetApp().mainframe->update_title();
}
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) {
m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
}
m_state.presets |= wxGetApp().has_unsaved_preset_changes();
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::reset_after_save()
{
const Plater* plater = wxGetApp().plater();
const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
if (&main_stack == &active_stack) {
const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main);
m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
}
else {
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)
m_last_save.main = main_active_snapshot->timestamp + 1;
}
const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos, m_last_save.main);
m_last_save.gizmo = saveable_snapshot->timestamp;
}
reset_initial_presets();
m_state.reset();
wxGetApp().mainframe->update_title();
}
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) {
m_initial_presets[type] = name;
}
}
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void ProjectDirtyStateManager::render_debug_window() const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
auto color = [](bool value) {
return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) /* orange */: ImVec4(1.0f, 1.0f, 1.0f, 1.0f) /* white */;
};
auto bool_to_text = [](bool value) {
return value ? "true" : "false";
};
auto append_bool_item = [color, bool_to_text, &imgui](const std::string& name, bool value) {
imgui.text_colored(color(value), name);
ImGui::SameLine();
imgui.text_colored(color(value), bool_to_text(value));
};
auto append_int_item = [&imgui](const std::string& name, int value) {
imgui.text(name);
ImGui::SameLine();
imgui.text(std::to_string(value));
};
auto append_snapshot_item = [&imgui](const std::string& label, const UndoRedo::Snapshot* snapshot) {
imgui.text(label);
ImGui::SameLine(100);
if (snapshot != nullptr)
imgui.text(snapshot->name + " (" + std::to_string(snapshot->timestamp) + ")");
else
imgui.text("-");
};
imgui.begin(std::string("Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
if (ImGui::CollapsingHeader("Dirty state", ImGuiTreeNodeFlags_DefaultOpen)) {
append_bool_item("Overall:", is_dirty());
ImGui::Separator();
append_bool_item("Plater:", m_state.plater);
append_bool_item("Presets:", m_state.presets);
append_bool_item("Current gizmo:", m_state.gizmos.current);
}
if (ImGui::CollapsingHeader("Last save timestamps", ImGuiTreeNodeFlags_DefaultOpen)) {
append_int_item("Main:", m_last_save.main);
append_int_item("Current gizmo:", m_last_save.gizmo);
}
const UndoRedo::Stack& main_stack = wxGetApp().plater()->undo_redo_stack_main();
const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack);
const UndoRedo::Snapshot* main_last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main);
const std::vector<UndoRedo::Snapshot>& main_snapshots = main_stack.snapshots();
if (ImGui::CollapsingHeader("Main snapshots", ImGuiTreeNodeFlags_DefaultOpen)) {
append_snapshot_item("Active:", main_active_snapshot);
append_snapshot_item("Last saveable:", main_last_saveable_snapshot);
}
if (ImGui::CollapsingHeader("Main undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) {
for (const UndoRedo::Snapshot& snapshot : main_snapshots) {
bool active = main_active_snapshot->timestamp == snapshot.timestamp;
imgui.text_colored(color(active), snapshot.name);
ImGui::SameLine(150);
imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")");
if (&snapshot == main_last_saveable_snapshot) {
ImGui::SameLine();
imgui.text_colored(color(active), " (S)");
}
if (m_last_save.main > 0 && m_last_save.main == snapshot.timestamp) {
ImGui::SameLine();
imgui.text_colored(color(active), " (LS)");
}
}
}
const UndoRedo::Stack& active_stack = wxGetApp().plater()->undo_redo_stack_active();
if (&active_stack != &main_stack) {
if (ImGui::CollapsingHeader("Gizmo undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) {
const UndoRedo::Snapshot* active_active_snapshot = get_active_snapshot(active_stack);
const std::vector<UndoRedo::Snapshot>& active_snapshots = active_stack.snapshots();
for (const UndoRedo::Snapshot& snapshot : active_snapshots) {
bool active = active_active_snapshot->timestamp == snapshot.timestamp;
imgui.text_colored(color(active), snapshot.name);
ImGui::SameLine(150);
imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")");
}
}
}
if (m_state.gizmos.any_used_modified()) {
if (ImGui::CollapsingHeader("Gizmos", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(10.0f);
for (const auto& [name, gizmo] : m_state.gizmos.used) {
if (!gizmo.modified_timestamps.empty()) {
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
std::string modified_timestamps;
for (size_t i = 0; i < gizmo.modified_timestamps.size(); ++i) {
if (i > 0)
modified_timestamps += " | ";
modified_timestamps += std::to_string(gizmo.modified_timestamps[i]);
}
imgui.text(modified_timestamps);
}
}
}
ImGui::Unindent(10.0f);
}
}
imgui.end();
}
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
{
m_state.plater = false;
if (type == UpdateType::TakeSnapshot) {
if (m_last_save.main != 0) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [this](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == m_last_save.main; });
if (snapshot_it == snapshots.end())
m_last_save.main = 0;
}
m_state.gizmos.remove_obsolete_used(stack);
}
const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
if (active_snapshot->name == _utf8("New Project") ||
active_snapshot->name == _utf8("Reset Project") ||
boost::starts_with(active_snapshot->name, _utf8("Load Project")))
return;
if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) {
if (type == UpdateType::UndoRedoTo) {
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot->timestamp + 1)));
if (m_state.gizmos.is_used_and_modified(*leaving_snapshot)) {
m_state.plater = (leaving_snapshot != nullptr && leaving_snapshot->timestamp != m_last_save.main);
return;
}
}
}
m_state.gizmos.current = false;
m_last_save.gizmo = 0;
}
else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) {
if (m_state.gizmos.current)
m_state.gizmos.add_used(*active_snapshot);
m_state.gizmos.current = false;
m_last_save.gizmo = 0;
}
const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main);
m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main);
}
void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
{
m_state.gizmos.current = false;
const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
if (active_snapshot->name == "Gizmos-Initial")
return;
const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main);
m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo);
}
} // namespace GUI
} // namespace Slic3r
#endif // ENABLE_PROJECT_DIRTY_STATE

View file

@ -0,0 +1,96 @@
#ifndef slic3r_ProjectDirtyStateManager_hpp_
#define slic3r_ProjectDirtyStateManager_hpp_
#include "libslic3r/Preset.hpp"
#if ENABLE_PROJECT_DIRTY_STATE
namespace Slic3r {
namespace UndoRedo {
class Stack;
struct Snapshot;
} // namespace UndoRedo
namespace GUI {
class ProjectDirtyStateManager
{
public:
enum class UpdateType : unsigned char
{
TakeSnapshot,
UndoRedoTo
};
struct DirtyState
{
struct Gizmos
{
struct Gizmo
{
std::vector<size_t> modified_timestamps;
};
bool current{ false };
std::map<std::string, Gizmo> used;
void add_used(const UndoRedo::Snapshot& snapshot);
void remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack);
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
bool any_used_modified() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const;
void reset();
};
bool plater{ false };
bool presets{ false };
Gizmos gizmos;
bool is_dirty() const { return plater || presets || gizmos.current; }
void reset() {
plater = false;
presets = false;
gizmos.current = false;
}
};
private:
struct LastSaveTimestamps
{
size_t main{ 0 };
size_t gizmo{ 0 };
void reset() {
main = 0;
gizmo = 0;
}
};
DirtyState m_state;
LastSaveTimestamps m_last_save;
// keeps track of initial selected presets
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
public:
bool is_dirty() const { return m_state.is_dirty(); }
void update_from_undo_redo_stack(UpdateType type);
void update_from_presets();
void reset_after_save();
void reset_initial_presets();
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_debug_window() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
private:
void update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack);
void update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack);
};
} // namespace GUI
} // namespace Slic3r
#endif // ENABLE_PROJECT_DIRTY_STATE
#endif // slic3r_ProjectDirtyStateManager_hpp_

View file

@ -7,6 +7,7 @@
#include <boost/nowide/convert.hpp>
#include "wx/dataview.h"
#include "wx/numformatter.h"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
@ -45,6 +46,11 @@ static char marker_by_type(Preset::Type type, PrinterTechnology pt)
}
}
std::string Option::opt_key() const
{
return boost::nowide::narrow(key).substr(2);
}
void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const
{
*label_ = marked_label.c_str();

View file

@ -53,7 +53,7 @@ struct Option {
std::wstring category;
std::wstring category_local;
std::string opt_key() const { return boost::nowide::narrow(key).substr(2); }
std::string opt_key() const;
};
struct FoundOption {

View file

@ -12,6 +12,9 @@
#include "Plater.hpp"
#include "libslic3r/Model.hpp"
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
#include "libslic3r/PresetBundle.hpp"
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
#include <GL/glew.h>
@ -62,13 +65,11 @@ bool Selection::Clipboard::is_sla_compliant() const
if (m_mode == Selection::Volume)
return false;
for (const ModelObject* o : m_model->objects)
{
for (const ModelObject* o : m_model->objects) {
if (o->is_multiparts())
return false;
for (const ModelVolume* v : o->volumes)
{
for (const ModelVolume* v : o->volumes) {
if (v->is_modifier())
return false;
}
@ -82,7 +83,8 @@ Selection::Clipboard::Clipboard()
m_model.reset(new Model);
}
void Selection::Clipboard::reset() {
void Selection::Clipboard::reset()
{
m_model->clear_objects();
}
@ -153,7 +155,7 @@ void Selection::set_model(Model* model)
void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained)
{
if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx))
if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx)
return;
const GLVolume* volume = (*m_volumes)[volume_idx];
@ -171,9 +173,8 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec
needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier;
needs_reset |= is_any_modifier() && !volume->is_modifier;
if (!already_contained || needs_reset)
{
wxGetApp().plater()->take_snapshot(_(L("Selection-Add")));
if (!already_contained || needs_reset) {
wxGetApp().plater()->take_snapshot(_L("Selection-Add"));
if (needs_reset)
clear();
@ -189,7 +190,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec
{
case Volume:
{
if ((volume->volume_idx() >= 0) && (is_empty() || (volume->instance_idx() == get_instance_idx())))
if (volume->volume_idx() >= 0 && (is_empty() || volume->instance_idx() == get_instance_idx()))
do_add_volume(volume_idx);
break;
@ -208,13 +209,13 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec
void Selection::remove(unsigned int volume_idx)
{
if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx))
if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx)
return;
if (!contains_volume(volume_idx))
return;
wxGetApp().plater()->take_snapshot(_(L("Selection-Remove")));
wxGetApp().plater()->take_snapshot(_L("Selection-Remove"));
GLVolume* volume = (*m_volumes)[volume_idx];
@ -246,7 +247,7 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection)
(as_single_selection && matches(volume_idxs)))
return;
wxGetApp().plater()->take_snapshot(_(L("Selection-Add Object")));
wxGetApp().plater()->take_snapshot(_L("Selection-Add Object"));
// resets the current list if needed
if (as_single_selection)
@ -265,7 +266,7 @@ void Selection::remove_object(unsigned int object_idx)
if (!m_valid)
return;
wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Object")));
wxGetApp().plater()->take_snapshot(_L("Selection-Remove Object"));
do_remove_object(object_idx);
@ -278,12 +279,12 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx,
if (!m_valid)
return;
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx);
const std::vector<unsigned int> volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx);
if ((!as_single_selection && contains_all_volumes(volume_idxs)) ||
(as_single_selection && matches(volume_idxs)))
return;
wxGetApp().plater()->take_snapshot(_(L("Selection-Add Instance")));
wxGetApp().plater()->take_snapshot(_L("Selection-Add Instance"));
// resets the current list if needed
if (as_single_selection)
@ -302,7 +303,7 @@ void Selection::remove_instance(unsigned int object_idx, unsigned int instance_i
if (!m_valid)
return;
wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Instance")));
wxGetApp().plater()->take_snapshot(_L("Selection-Remove Instance"));
do_remove_instance(object_idx, instance_idx);
@ -337,10 +338,9 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx)
if (!m_valid)
return;
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
GLVolume* v = (*m_volumes)[i];
if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx))
if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx)
do_remove_volume(i);
}
@ -362,8 +362,7 @@ void Selection::add_volumes(EMode mode, const std::vector<unsigned int>& volume_
clear();
m_mode = mode;
for (unsigned int i : volume_idxs)
{
for (unsigned int i : volume_idxs) {
if (i < (unsigned int)m_volumes->size())
do_add_volume(i);
}
@ -378,8 +377,7 @@ void Selection::remove_volumes(EMode mode, const std::vector<unsigned int>& volu
return;
m_mode = mode;
for (unsigned int i : volume_idxs)
{
for (unsigned int i : volume_idxs) {
if (i < (unsigned int)m_volumes->size())
do_remove_volume(i);
}
@ -394,8 +392,7 @@ void Selection::add_all()
return;
unsigned int count = 0;
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
if (!(*m_volumes)[i]->is_wipe_tower)
++count;
}
@ -408,8 +405,7 @@ void Selection::add_all()
m_mode = Instance;
clear();
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
if (!(*m_volumes)[i]->is_wipe_tower)
do_add_volume(i);
}
@ -459,8 +455,7 @@ void Selection::clear()
if (m_list.empty())
return;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
(*m_volumes)[i]->selected = false;
}
@ -526,16 +521,15 @@ bool Selection::is_single_full_instance() const
return false;
int object_idx = m_valid ? get_object_idx() : -1;
if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx))
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
return false;
int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx();
std::set<int> volumes_idxs;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
const GLVolume* v = (*m_volumes)[i];
if ((object_idx != v->object_idx()) || (instance_idx != v->instance_idx()))
if (object_idx != v->object_idx() || instance_idx != v->instance_idx())
return false;
int volume_idx = v->volume_idx();
@ -548,8 +542,8 @@ bool Selection::is_single_full_instance() const
bool Selection::is_from_single_object() const
{
int idx = get_object_idx();
return (0 <= idx) && (idx < 1000);
const int idx = get_object_idx();
return 0 <= idx && idx < 1000;
}
bool Selection::is_sla_compliant() const
@ -557,8 +551,7 @@ bool Selection::is_sla_compliant() const
if (m_mode == Volume)
return false;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
if ((*m_volumes)[i]->is_modifier)
return false;
}
@ -568,8 +561,7 @@ bool Selection::is_sla_compliant() const
bool Selection::contains_all_volumes(const std::vector<unsigned int>& volume_idxs) const
{
for (unsigned int i : volume_idxs)
{
for (unsigned int i : volume_idxs) {
if (m_list.find(i) == m_list.end())
return false;
}
@ -579,8 +571,7 @@ bool Selection::contains_all_volumes(const std::vector<unsigned int>& volume_idx
bool Selection::contains_any_volume(const std::vector<unsigned int>& volume_idxs) const
{
for (unsigned int i : volume_idxs)
{
for (unsigned int i : volume_idxs) {
if (m_list.find(i) != m_list.end())
return true;
}
@ -592,8 +583,7 @@ bool Selection::matches(const std::vector<unsigned int>& volume_idxs) const
{
unsigned int count = 0;
for (unsigned int i : volume_idxs)
{
for (unsigned int i : volume_idxs) {
if (m_list.find(i) != m_list.end())
++count;
else
@ -618,8 +608,7 @@ int Selection::get_object_idx() const
int Selection::get_instance_idx() const
{
if (m_cache.content.size() == 1)
{
if (m_cache.content.size() == 1) {
const InstanceIdxsList& idxs = m_cache.content.begin()->second;
if (idxs.size() == 1)
return *idxs.begin();
@ -676,25 +665,20 @@ void Selection::translate(const Vec3d& displacement, bool local)
EMode translation_type = m_mode;
for (unsigned int i : m_list)
{
if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower)
{
for (unsigned int i : m_list) {
if (m_mode == Volume || (*m_volumes)[i]->is_wipe_tower) {
if (local)
(*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement);
else
{
Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement;
else {
const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement;
(*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement);
}
}
else if (m_mode == Instance)
{
else if (m_mode == Instance) {
if (is_from_fully_selected_instance(i))
(*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement);
else
{
Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement;
else {
const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement;
(*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement);
translation_type = Volume;
}
@ -722,18 +706,14 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
if (!is_wipe_tower()) {
int rot_axis_max = 0;
if (rotation.isApprox(Vec3d::Zero()))
{
for (unsigned int i : m_list)
{
if (rotation.isApprox(Vec3d::Zero())) {
for (unsigned int i : m_list) {
GLVolume &volume = *(*m_volumes)[i];
if (m_mode == Instance)
{
if (m_mode == Instance) {
volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation());
volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position());
}
else if (m_mode == Volume)
{
else if (m_mode == Volume) {
volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation());
volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position());
}
@ -750,14 +730,14 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
// For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it.
std::vector<int> object_instance_first(m_model->objects.size(), -1);
auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) {
int first_volume_idx = object_instance_first[volume.object_idx()];
const int first_volume_idx = object_instance_first[volume.object_idx()];
if (rot_axis_max != 2 && first_volume_idx != -1) {
// Generic rotation, but no rotation around the Z axis.
// Always do a local rotation (do not consider the selection to be a rigid body).
assert(is_approx(rotation.z(), 0.0));
const GLVolume &first_volume = *(*m_volumes)[first_volume_idx];
const Vec3d &rotation = first_volume.get_instance_rotation();
double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation());
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation());
volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff));
}
else {
@ -767,7 +747,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation();
if (rot_axis_max == 2 && transformation_type.joint()) {
// Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis.
double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation);
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation);
volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
}
volume.set_instance_rotation(new_rotation);
@ -775,19 +755,16 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
}
};
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
GLVolume &volume = *(*m_volumes)[i];
if (is_single_full_instance())
rotate_instance(volume, i);
else if (is_single_volume() || is_single_modifier())
{
else if (is_single_volume() || is_single_modifier()) {
if (transformation_type.independent())
volume.set_volume_rotation(volume.get_volume_rotation() + rotation);
else
{
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
else {
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
volume.set_volume_rotation(new_rotation);
}
}
@ -795,15 +772,13 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
{
if (m_mode == Instance)
rotate_instance(volume, i);
else if (m_mode == Volume)
{
else if (m_mode == Volume) {
// extracts rotations from the composed transformation
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
if (transformation_type.joint())
{
Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center;
Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot);
if (transformation_type.joint()) {
const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center;
const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot);
volume.set_volume_offset(local_pivot + offset);
}
volume.set_volume_rotation(new_rotation);
@ -824,8 +799,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
// make sure the wipe tower rotates around its center, not origin
// we can assume that only Z rotation changes
Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset();
Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0, 0, 1)) * center_local;
const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset();
const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local;
volume.set_volume_rotation(rotation);
volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new);
}
@ -843,8 +818,7 @@ void Selection::flattening_rotate(const Vec3d& normal)
if (!m_valid)
return;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
// Normal transformed from the object coordinate space to the world coordinate space.
const auto &voldata = m_cache.volumes_data[i];
Vec3d tnormal = (Geometry::assemble_transform(
@ -870,12 +844,10 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
if (!m_valid)
return;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
GLVolume &volume = *(*m_volumes)[i];
if (is_single_full_instance()) {
if (transformation_type.relative())
{
if (transformation_type.relative()) {
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
@ -885,8 +857,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
volume.set_instance_scaling_factor(new_scale);
}
else
{
else {
if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) {
// Non-uniform scaling. Transform the scaling factors into the local coordinate system.
// This is only possible, if the instance rotation is mulitples of ninety degrees.
@ -899,11 +870,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
}
else if (is_single_volume() || is_single_modifier())
volume.set_volume_scaling_factor(scale);
else
{
else {
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
if (m_mode == Instance)
{
if (m_mode == Instance) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
@ -912,13 +881,11 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
volume.set_instance_scaling_factor(new_scale);
}
else if (m_mode == Volume)
{
else if (m_mode == Volume) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
if (transformation_type.joint())
{
if (transformation_type.joint()) {
Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center);
volume.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset);
}
@ -933,35 +900,36 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
else if (m_mode == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
ensure_on_bed();
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA)
ensure_on_bed();
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
this->set_bounding_boxes_dirty();
}
void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config)
{
if (is_empty() || (m_mode == Volume))
if (is_empty() || m_mode == Volume)
return;
// adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings
Vec3d box_size = get_bounding_box().size() + 0.01 * Vec3d::Ones();
const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config.option("bed_shape"));
if (opt != nullptr)
{
if (opt != nullptr) {
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
BoundingBoxf3 print_volume(Vec3d(unscale<double>(bed_box_2D.min(0)), unscale<double>(bed_box_2D.min(1)), 0.0), Vec3d(unscale<double>(bed_box_2D.max(0)), unscale<double>(bed_box_2D.max(1)), config.opt_float("max_print_height")));
BoundingBoxf3 print_volume({ unscale<double>(bed_box_2D.min(0)), unscale<double>(bed_box_2D.min(1)), 0.0 }, { unscale<double>(bed_box_2D.max(0)), unscale<double>(bed_box_2D.max(1)), config.opt_float("max_print_height") });
Vec3d print_volume_size = print_volume.size();
double sx = (box_size(0) != 0.0) ? print_volume_size(0) / box_size(0) : 0.0;
double sy = (box_size(1) != 0.0) ? print_volume_size(1) / box_size(1) : 0.0;
double sz = (box_size(2) != 0.0) ? print_volume_size(2) / box_size(2) : 0.0;
if ((sx != 0.0) && (sy != 0.0) && (sz != 0.0))
if (sx != 0.0 && sy != 0.0 && sz != 0.0)
{
double s = std::min(sx, std::min(sy, sz));
if (s != 1.0)
{
wxGetApp().plater()->take_snapshot(_(L("Scale To Fit")));
if (s != 1.0) {
wxGetApp().plater()->take_snapshot(_L("Scale To Fit"));
TransformationType type;
type.set_world();
@ -991,8 +959,7 @@ void Selection::mirror(Axis axis)
bool single_full_instance = is_single_full_instance();
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
if (single_full_instance)
(*m_volumes)[i]->set_instance_mirror(axis, -(*m_volumes)[i]->get_instance_mirror(axis));
else if (m_mode == Volume)
@ -1014,8 +981,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement)
if (!m_valid)
return;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
GLVolume* v = (*m_volumes)[i];
if (v->object_idx() == (int)object_idx)
v->set_instance_offset(v->get_instance_offset() + displacement);
@ -1024,8 +990,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement)
std::set<unsigned int> done; // prevent processing volumes twice
done.insert(m_list.begin(), m_list.end());
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
if (done.size() == m_volumes->size())
break;
@ -1034,8 +999,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement)
continue;
// Process unselected volumes of the object.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j)
{
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
if (done.size() == m_volumes->size())
break;
@ -1059,18 +1023,16 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
if (!m_valid)
return;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
GLVolume* v = (*m_volumes)[i];
if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx))
if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx)
v->set_instance_offset(v->get_instance_offset() + displacement);
}
std::set<unsigned int> done; // prevent processing volumes twice
done.insert(m_list.begin(), m_list.end());
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
if (done.size() == m_volumes->size())
break;
@ -1079,8 +1041,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
continue;
// Process unselected volumes of the object.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j)
{
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
if (done.size() == m_volumes->size())
break;
@ -1088,7 +1049,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
continue;
GLVolume* v = (*m_volumes)[j];
if ((v->object_idx() != object_idx) || (v->instance_idx() != (int)instance_idx))
if (v->object_idx() != object_idx || v->instance_idx() != (int)instance_idx)
continue;
v->set_instance_offset(v->get_instance_offset() + displacement);
@ -1803,18 +1764,16 @@ void Selection::render_synchronized_volumes() const
float color[3] = { 1.0f, 1.0f, 0.0f };
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i];
int object_idx = volume->object_idx();
int volume_idx = volume->volume_idx();
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j)
{
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
if (i == j)
continue;
const GLVolume* v = (*m_volumes)[j];
if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx))
if (v->object_idx() != object_idx || v->volume_idx() != volume_idx)
continue;
render_bounding_box(v->transformed_convex_hull_bounding_box(), color);
@ -2087,9 +2046,9 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
#ifndef NDEBUG
static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
{
Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
Vec3d axis = angle_axis.axis();
double angle = angle_axis.angle();
const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
const Vec3d axis = angle_axis.axis();
const double angle = angle_axis.angle();
if (std::abs(angle) < 1e-8)
return true;
assert(std::abs(axis.x()) < 1e-8);
@ -2126,24 +2085,22 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
std::set<unsigned int> done; // prevent processing volumes twice
done.insert(m_list.begin(), m_list.end());
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
if (done.size() == m_volumes->size())
break;
const GLVolume* volume = (*m_volumes)[i];
int object_idx = volume->object_idx();
const int object_idx = volume->object_idx();
if (object_idx >= 1000)
continue;
int instance_idx = volume->instance_idx();
const int instance_idx = volume->instance_idx();
const Vec3d& rotation = volume->get_instance_rotation();
const Vec3d& scaling_factor = volume->get_instance_scaling_factor();
const Vec3d& mirror = volume->get_instance_mirror();
// Process unselected instances.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j)
{
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
if (done.size() == m_volumes->size())
break;
@ -2151,24 +2108,36 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
continue;
GLVolume* v = (*m_volumes)[j];
if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx))
if (v->object_idx() != object_idx || v->instance_idx() == instance_idx)
continue;
assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()));
switch (sync_rotation_type) {
case SYNC_ROTATION_NONE:
case SYNC_ROTATION_NONE: {
#if ENABLE_ALLOW_NEGATIVE_Z
// z only rotation -> synch instance z
// The X,Y rotations should be synchronized from start to end of the rotation.
assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation()));
#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA
v->set_instance_offset(Z, volume->get_instance_offset().z());
break;
#else
// z only rotation -> keep instance z
// The X,Y rotations should be synchronized from start to end of the rotation.
assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation()));
break;
#endif // ENABLE_ALLOW_NEGATIVE_Z
}
case SYNC_ROTATION_FULL:
// rotation comes from place on face -> force given z
v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2)));
v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() });
break;
case SYNC_ROTATION_GENERAL:
// generic rotation -> update instance z with the delta of the rotation.
double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation());
v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff));
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation());
v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff });
break;
}
@ -2186,27 +2155,25 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
void Selection::synchronize_unselected_volumes()
{
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i];
int object_idx = volume->object_idx();
const int object_idx = volume->object_idx();
if (object_idx >= 1000)
continue;
int volume_idx = volume->volume_idx();
const int volume_idx = volume->volume_idx();
const Vec3d& offset = volume->get_volume_offset();
const Vec3d& rotation = volume->get_volume_rotation();
const Vec3d& scaling_factor = volume->get_volume_scaling_factor();
const Vec3d& mirror = volume->get_volume_mirror();
// Process unselected volumes.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j)
{
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
if (j == i)
continue;
GLVolume* v = (*m_volumes)[j];
if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx))
if (v->object_idx() != object_idx || v->volume_idx() != volume_idx)
continue;
v->set_volume_offset(offset);
@ -2222,10 +2189,8 @@ void Selection::ensure_on_bed()
typedef std::map<std::pair<int, int>, double> InstancesToZMap;
InstancesToZMap instances_min_z;
for (GLVolume* volume : *m_volumes)
{
if (!volume->is_wipe_tower && !volume->is_modifier)
{
for (GLVolume* volume : *m_volumes) {
if (!volume->is_wipe_tower && !volume->is_modifier) {
double min_z = volume->transformed_convex_hull_bounding_box().min(2);
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
InstancesToZMap::iterator it = instances_min_z.find(instance);
@ -2236,8 +2201,7 @@ void Selection::ensure_on_bed()
}
}
for (GLVolume* volume : *m_volumes)
{
for (GLVolume* volume : *m_volumes) {
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
InstancesToZMap::iterator it = instances_min_z.find(instance);
if (it != instances_min_z.end())

View file

@ -129,7 +129,7 @@ private:
TransformCache m_instance;
public:
VolumeCache() {}
VolumeCache() = default;
VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform);
const Vec3d& get_volume_position() const { return m_volume.position; }

View file

@ -940,7 +940,7 @@ void Tab::update_visibility()
page->update_visibility(m_mode, page.get() == m_active_page);
rebuild_page_tree();
if (this->m_type == Preset::TYPE_SLA_PRINT)
if (m_type == Preset::TYPE_SLA_PRINT)
update_description_lines();
Layout();
@ -1217,9 +1217,8 @@ void Tab::apply_config_from_cache()
// to update number of "filament" selection boxes when the number of extruders change.
void Tab::on_presets_changed()
{
if (wxGetApp().plater() == nullptr) {
if (wxGetApp().plater() == nullptr)
return;
}
// Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
wxGetApp().plater()->sidebar().update_presets(m_type);
@ -1237,6 +1236,10 @@ void Tab::on_presets_changed()
// clear m_dependent_tabs after first update from select_preset()
// to avoid needless preset loading from update() function
m_dependent_tabs.clear();
#if ENABLE_PROJECT_DIRTY_STATE
wxGetApp().plater()->update_project_dirty_from_presets();
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup)
@ -2113,10 +2116,16 @@ wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticTex
return sizer;
}
#if ENABLE_PROJECT_DIRTY_STATE
bool Tab::saved_preset_is_dirty() const { return m_presets->saved_is_dirty(); }
void Tab::update_saved_preset_from_current_preset() { m_presets->update_saved_preset_from_current_preset(); }
bool Tab::current_preset_is_dirty() const { return m_presets->current_is_dirty(); }
#else
bool Tab::current_preset_is_dirty()
{
return m_presets->current_is_dirty();
}
#endif // ENABLE_PROJECT_DIRTY_STATE
void TabPrinter::build()
{
@ -3141,8 +3150,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
if (preset_name.empty()) {
if (delete_current) {
// Find an alternate preset to be selected after the current preset is deleted.
const std::deque<Preset> &presets = this->m_presets->get_presets();
size_t idx_current = this->m_presets->get_idx_selected();
const std::deque<Preset> &presets = m_presets->get_presets();
size_t idx_current = m_presets->get_idx_selected();
// Find the next visible preset.
size_t idx_new = idx_current + 1;
if (idx_new < presets.size())

View file

@ -270,7 +270,11 @@ public:
Preset::Type type() const { return m_type; }
// The tab is already constructed.
bool completed() const { return m_completed; }
virtual bool supports_printer_technology(const PrinterTechnology tech) = 0;
#if ENABLE_PROJECT_DIRTY_STATE
virtual bool supports_printer_technology(const PrinterTechnology tech) const = 0;
#else
virtual bool supports_printer_technology(const PrinterTechnology tech) = 0;
#endif // ENABLE_PROJECT_DIRTY_STATE
void create_preset_tab();
void add_scaled_button(wxWindow* parent, ScalableButton** btn, const std::string& icon_name,
@ -333,7 +337,13 @@ public:
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
void toggle_option(const std::string& opt_key, bool toggle, int opt_index = -1);
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString);
#if ENABLE_PROJECT_DIRTY_STATE
bool current_preset_is_dirty() const;
bool saved_preset_is_dirty() const;
void update_saved_preset_from_current_preset();
#else
bool current_preset_is_dirty();
#endif // ENABLE_PROJECT_DIRTY_STATE
DynamicPrintConfig* get_config() { return m_config; }
PresetCollection* get_presets() { return m_presets; }
@ -377,8 +387,8 @@ class TabPrint : public Tab
{
public:
TabPrint(wxNotebook* parent) :
// Tab(parent, _(L("Print Settings")), L("print")) {}
Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {}
// Tab(parent, _L("Print Settings"), L("print")) {}
Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_PRINT) {}
~TabPrint() {}
void build() override;
@ -387,7 +397,11 @@ public:
void toggle_options() override;
void update() override;
void clear_pages() override;
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; }
#if ENABLE_PROJECT_DIRTY_STATE
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
#else
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; }
#endif // ENABLE_PROJECT_DIRTY_STATE
private:
ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr;
@ -407,8 +421,8 @@ private:
std::map<std::string, wxCheckBox*> m_overrides_options;
public:
TabFilament(wxNotebook* parent) :
// Tab(parent, _(L("Filament Settings")), L("filament")) {}
Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {}
// Tab(parent, _L("Filament Settings"), L("filament")) {}
Tab(parent, _L("Filament Settings"), Slic3r::Preset::TYPE_FILAMENT) {}
~TabFilament() {}
void build() override;
@ -417,7 +431,11 @@ public:
void toggle_options() override;
void update() override;
void clear_pages() override;
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; }
#if ENABLE_PROJECT_DIRTY_STATE
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
#else
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; }
#endif // ENABLE_PROJECT_DIRTY_STATE
};
class TabPrinter : public Tab
@ -450,7 +468,7 @@ public:
// TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), L("printer")) {}
TabPrinter(wxNotebook* parent) :
Tab(parent, _(L("Printer Settings")), Slic3r::Preset::TYPE_PRINTER) {}
Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {}
~TabPrinter() {}
void build() override;
@ -472,7 +490,11 @@ public:
void init_options_list() override;
void msw_rescale() override;
void sys_color_changed() override;
bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; }
#if ENABLE_PROJECT_DIRTY_STATE
bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; }
#else
bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; }
#endif // ENABLE_PROJECT_DIRTY_STATE
wxSizer* create_bed_shape_widget(wxWindow* parent);
void cache_extruder_cnt();
@ -483,8 +505,8 @@ class TabSLAMaterial : public Tab
{
public:
TabSLAMaterial(wxNotebook* parent) :
// Tab(parent, _(L("Material Settings")), L("sla_material")) {}
Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {}
// Tab(parent, _L("Material Settings"), L("sla_material")) {}
Tab(parent, _L("Material Settings"), Slic3r::Preset::TYPE_SLA_MATERIAL) {}
~TabSLAMaterial() {}
void build() override;
@ -492,15 +514,19 @@ public:
void toggle_options() override {};
void update() override;
void init_options_list() override;
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; }
#if ENABLE_PROJECT_DIRTY_STATE
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; }
#else
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; }
#endif // ENABLE_PROJECT_DIRTY_STATE
};
class TabSLAPrint : public Tab
{
public:
TabSLAPrint(wxNotebook* parent) :
// Tab(parent, _(L("Print Settings")), L("sla_print")) {}
Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {}
// Tab(parent, _L("Print Settings"), L("sla_print")) {}
Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_SLA_PRINT) {}
~TabSLAPrint() {}
ogStaticText* m_support_object_elevation_description_line = nullptr;
@ -511,7 +537,11 @@ public:
void toggle_options() override;
void update() override;
void clear_pages() override;
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; }
#if ENABLE_PROJECT_DIRTY_STATE
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; }
#else
bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; }
#endif // ENABLE_PROJECT_DIRTY_STATE
};
} // GUI

View file

@ -1694,6 +1694,9 @@ void DiffPresetDialog::update_compatibility(const std::string& preset_name, Pres
technology_changed = old_printer_technology != new_printer_technology;
}
// select preset
presets->select_preset_by_name(preset_name, false);
// Mark the print & filament enabled if they are compatible with the currently selected preset.
// The following method should not discard changes of current print or filament presets on change of a printer profile,
// if they are compatible with the current printer.

View file

@ -755,7 +755,7 @@ namespace UndoRedo {
template<typename T> std::shared_ptr<const T>& ImmutableObjectHistory<T>::shared_ptr(StackImpl &stack)
{
if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) {
if (m_shared_object.get() == nullptr && ! m_serialized.empty()) {
// Deserialize the object.
std::istringstream iss(m_serialized);
{
@ -897,7 +897,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU
this->load_mutable_object<Slic3r::GUI::GLGizmosManager>(gizmos.id(), gizmos);
// Sort the volumes so that we may use binary search.
std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end());
this->m_active_snapshot_time = timestamp;
m_active_snapshot_time = timestamp;
assert(this->valid());
}