mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-20 13:17:54 -06:00
Fixed conflicts after merge with master
This commit is contained in:
commit
f786d9c96e
174 changed files with 9811 additions and 5525 deletions
|
@ -194,7 +194,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c
|
|||
const BoundingBox& bed_bbox = poly.contour.bounding_box();
|
||||
calc_gridlines(poly, bed_bbox);
|
||||
|
||||
m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour;
|
||||
m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0];
|
||||
|
||||
reset();
|
||||
m_texture.reset();
|
||||
|
|
|
@ -145,7 +145,7 @@ void BackgroundSlicingProcess::process_fff()
|
|||
// Passing the timestamp
|
||||
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psSlicingFinished).timestamp));
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb);
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
|
@ -221,21 +221,14 @@ void BackgroundSlicingProcess::process_sla()
|
|||
|
||||
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
|
||||
|
||||
ThumbnailsList thumbnails = this->render_thumbnails(
|
||||
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
|
||||
|
||||
Zipper zipper(export_path);
|
||||
m_sla_archive.export_print(zipper, *m_sla_print);
|
||||
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
ThumbnailsList thumbnails;
|
||||
m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
|
||||
// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true, true); // renders also supports and pad
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
}
|
||||
}
|
||||
|
||||
m_sla_archive.export_print(zipper, *m_sla_print); // true, false, true, true); // renders also supports and pad
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
zipper.finalize();
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
|
||||
|
@ -362,16 +355,19 @@ bool BackgroundSlicingProcess::start()
|
|||
return true;
|
||||
}
|
||||
|
||||
// To be called on the UI thread.
|
||||
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());
|
||||
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
|
||||
// Cancel any task planned by the background thread on UI thread.
|
||||
cancel_ui_task(m_ui_task);
|
||||
m_print->cancel();
|
||||
// Wait until the background processing stops by being canceled.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
|
||||
|
@ -383,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;
|
||||
}
|
||||
|
||||
|
@ -396,7 +392,7 @@ bool BackgroundSlicingProcess::reset()
|
|||
return stopped;
|
||||
}
|
||||
|
||||
// To be called by Print::apply() through the Print::m_cancel_callback to stop the background
|
||||
// To be called by Print::apply() on the UI thread through the Print::m_cancel_callback to stop the background
|
||||
// processing before changing any data of running or finalized milestones.
|
||||
// This function shall not trigger any UI update through the wxWidgets event.
|
||||
void BackgroundSlicingProcess::stop_internal()
|
||||
|
@ -408,6 +404,8 @@ void BackgroundSlicingProcess::stop_internal()
|
|||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
assert(m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED);
|
||||
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
|
||||
// Cancel any task planned by the background thread on UI thread.
|
||||
cancel_ui_task(m_ui_task);
|
||||
// At this point of time the worker thread may be blocking on m_print->state_mutex().
|
||||
// Set the print state to canceled before unlocking the state_mutex(), so when the worker thread wakes up,
|
||||
// it throws the CanceledException().
|
||||
|
@ -424,6 +422,60 @@ void BackgroundSlicingProcess::stop_internal()
|
|||
m_print->set_cancel_callback([](){});
|
||||
}
|
||||
|
||||
// Execute task from background thread on the UI thread. Returns true if processed, false if cancelled.
|
||||
bool BackgroundSlicingProcess::execute_ui_task(std::function<void()> task)
|
||||
{
|
||||
bool running = false;
|
||||
if (m_mutex.try_lock()) {
|
||||
// Cancellation is either not in process, or already canceled and waiting for us to finish.
|
||||
// There must be no UI task planned.
|
||||
assert(! m_ui_task);
|
||||
if (! m_print->canceled()) {
|
||||
running = true;
|
||||
m_ui_task = std::make_shared<UITask>();
|
||||
}
|
||||
m_mutex.unlock();
|
||||
} else {
|
||||
// Cancellation is in process.
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (running) {
|
||||
std::shared_ptr<UITask> ctx = m_ui_task;
|
||||
GUI::wxGetApp().mainframe->m_plater->CallAfter([task, ctx]() {
|
||||
// Running on the UI thread, thus ctx->state does not need to be guarded with mutex against ::cancel_ui_task().
|
||||
assert(ctx->state == UITask::Planned || ctx->state == UITask::Canceled);
|
||||
if (ctx->state == UITask::Planned) {
|
||||
task();
|
||||
std::unique_lock<std::mutex> lck(ctx->mutex);
|
||||
ctx->state = UITask::Finished;
|
||||
}
|
||||
// Wake up the worker thread from the UI thread.
|
||||
ctx->condition.notify_all();
|
||||
});
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(ctx->mutex);
|
||||
ctx->condition.wait(lock, [&ctx]{ return ctx->state == UITask::Finished || ctx->state == UITask::Canceled; });
|
||||
}
|
||||
result = ctx->state == UITask::Finished;
|
||||
m_ui_task.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// To be called on the UI thread from ::stop() and ::stop_internal().
|
||||
void BackgroundSlicingProcess::cancel_ui_task(std::shared_ptr<UITask> task)
|
||||
{
|
||||
if (task) {
|
||||
std::unique_lock<std::mutex> lck(task->mutex);
|
||||
task->state = UITask::Canceled;
|
||||
lck.unlock();
|
||||
task->condition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::empty() const
|
||||
{
|
||||
assert(m_print != nullptr);
|
||||
|
@ -444,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.
|
||||
|
@ -546,19 +598,14 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
} else {
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
|
||||
ThumbnailsList thumbnails = this->render_thumbnails(
|
||||
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
|
||||
// true, false, true, true); // renders also supports and pad
|
||||
Zipper zipper{source_path.string()};
|
||||
m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string());
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
ThumbnailsList thumbnails;
|
||||
m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
|
||||
// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true, true); // renders also supports and pad
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
}
|
||||
}
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
zipper.finalize();
|
||||
}
|
||||
|
||||
|
@ -569,4 +616,13 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job));
|
||||
}
|
||||
|
||||
// Executed by the background thread, to start a task on the UI thread.
|
||||
ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParams ¶ms)
|
||||
{
|
||||
ThumbnailsList thumbnails;
|
||||
if (m_thumbnail_cb)
|
||||
this->execute_ui_task([this, ¶ms, &thumbnails](){ thumbnails = m_thumbnail_cb(params); });
|
||||
return thumbnails;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
@ -212,6 +212,23 @@ private:
|
|||
std::condition_variable m_condition;
|
||||
State m_state = STATE_INITIAL;
|
||||
|
||||
// For executing tasks from the background thread on UI thread synchronously (waiting for result) using wxWidgets CallAfter().
|
||||
// When the background proces is canceled, the UITask has to be invalidated as well, so that it will not be
|
||||
// executed on the UI thread referencing invalid data.
|
||||
struct UITask {
|
||||
enum State {
|
||||
Planned,
|
||||
Finished,
|
||||
Canceled,
|
||||
};
|
||||
State state = Planned;
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
};
|
||||
// Only one UI task may be planned by the background thread to be executed on the UI thread, as the background
|
||||
// thread is blocking until the UI thread calculation finishes.
|
||||
std::shared_ptr<UITask> m_ui_task;
|
||||
|
||||
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
|
||||
mutable tbb::mutex m_step_state_mutex;
|
||||
bool set_step_started(BackgroundSlicingProcessStep step);
|
||||
|
@ -222,6 +239,12 @@ private:
|
|||
// If the background processing stop was requested, throw CanceledException.
|
||||
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
|
||||
void prepare_upload();
|
||||
// To be executed at the background thread.
|
||||
ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms);
|
||||
// Execute task from background thread on the UI thread synchronously. Returns true if processed, false if cancelled before executing the task.
|
||||
bool execute_ui_task(std::function<void()> task);
|
||||
// To be called from inside m_mutex to cancel a planned UI task.
|
||||
static void cancel_ui_task(std::shared_ptr<BackgroundSlicingProcess::UITask> task);
|
||||
|
||||
// wxWidgets command ID to be sent to the plater to inform that the slicing is finished, and the G-code export will continue.
|
||||
int m_event_slicing_completed_id = 0;
|
||||
|
|
|
@ -45,7 +45,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||
// layer_height shouldn't be equal to zero
|
||||
if (config->opt_float("layer_height") < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("Zero layer height is not valid.\n\nThe layer height will be reset to 0.01."));
|
||||
const wxString msg_text = _(L("Layer height is not valid.\n\nThe layer height will be reset to 0.01."));
|
||||
wxMessageDialog dialog(nullptr, msg_text, _(L("Layer height")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
|
@ -55,9 +55,9 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||
is_msg_dlg_already_exist = false;
|
||||
}
|
||||
|
||||
if (fabs(config->option<ConfigOptionFloatOrPercent>("first_layer_height")->value - 0) < EPSILON)
|
||||
if (config->option<ConfigOptionFloatOrPercent>("first_layer_height")->value < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01."));
|
||||
const wxString msg_text = _(L("First layer height is not valid.\n\nThe first layer height will be reset to 0.01."));
|
||||
wxMessageDialog dialog(nullptr, msg_text, _(L("First layer height")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
408
src/slic3r/GUI/DesktopIntegrationDialog.cpp
Normal file
408
src/slic3r/GUI/DesktopIntegrationDialog.cpp
Normal 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__
|
39
src/slic3r/GUI/DesktopIntegrationDialog.hpp
Normal file
39
src/slic3r/GUI/DesktopIntegrationDialog.hpp
Normal 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__
|
|
@ -17,6 +17,7 @@
|
|||
#include "GLCanvas3D.hpp"
|
||||
#include "GLToolbar.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
#include "GUI_ObjectManipulation.hpp"
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
@ -143,6 +144,9 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const
|
|||
case EMoveType::Custom_GCode:
|
||||
case EMoveType::Retract:
|
||||
case EMoveType::Unretract:
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Seam:
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Extrude: {
|
||||
// use rounding to reduce the number of generated paths
|
||||
#if ENABLE_SPLITTED_VERTEX_BUFFER
|
||||
|
@ -540,6 +544,9 @@ const std::vector<GCodeViewer::Color> GCodeViewer::Extrusion_Role_Colors {{
|
|||
const std::vector<GCodeViewer::Color> GCodeViewer::Options_Colors {{
|
||||
{ 0.803f, 0.135f, 0.839f }, // Retractions
|
||||
{ 0.287f, 0.679f, 0.810f }, // Unretractions
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
{ 0.900f, 0.900f, 0.900f }, // Seams
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
{ 0.758f, 0.744f, 0.389f }, // ToolChanges
|
||||
{ 0.856f, 0.582f, 0.546f }, // ColorChanges
|
||||
{ 0.322f, 0.942f, 0.512f }, // PausePrints
|
||||
|
@ -582,11 +589,20 @@ GCodeViewer::GCodeViewer()
|
|||
case EMoveType::Pause_Print:
|
||||
case EMoveType::Custom_GCode:
|
||||
case EMoveType::Retract:
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Unretract:
|
||||
case EMoveType::Seam: {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
|
||||
buffer.vertices.format = VBuffer::EFormat::Position;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
case EMoveType::Unretract: {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
|
||||
buffer.vertices.format = VBuffer::EFormat::Position;
|
||||
break;
|
||||
}
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Wipe:
|
||||
case EMoveType::Extrude: {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
|
||||
|
@ -672,13 +688,13 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print&
|
|||
wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty());
|
||||
}
|
||||
|
||||
m_time_statistics = gcode_result.time_statistics;
|
||||
m_print_statistics = gcode_result.print_statistics;
|
||||
|
||||
if (m_time_estimate_mode != PrintEstimatedTimeStatistics::ETimeMode::Normal) {
|
||||
float time = m_time_statistics.modes[static_cast<size_t>(m_time_estimate_mode)].time;
|
||||
if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) {
|
||||
float time = m_print_statistics.modes[static_cast<size_t>(m_time_estimate_mode)].time;
|
||||
if (time == 0.0f ||
|
||||
short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time)))
|
||||
m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal;
|
||||
short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time)))
|
||||
m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -773,7 +789,7 @@ void GCodeViewer::reset()
|
|||
m_layers.reset();
|
||||
m_layers_z_range = { 0, 0 };
|
||||
m_roles = std::vector<ExtrusionRole>();
|
||||
m_time_statistics.reset();
|
||||
m_print_statistics.reset();
|
||||
#if ENABLE_GCODE_WINDOW
|
||||
m_sequential_view.gcode_window.reset();
|
||||
#endif // ENABLE_GCODE_WINDOW
|
||||
|
@ -796,10 +812,18 @@ void GCodeViewer::render() const
|
|||
case EMoveType::Pause_Print:
|
||||
case EMoveType::Custom_GCode:
|
||||
case EMoveType::Retract:
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Unretract:
|
||||
case EMoveType::Seam: {
|
||||
buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110";
|
||||
break;
|
||||
}
|
||||
#else
|
||||
case EMoveType::Unretract: {
|
||||
buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110";
|
||||
break;
|
||||
}
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Wipe:
|
||||
case EMoveType::Extrude: {
|
||||
buffer.shader = "gouraud_light";
|
||||
|
@ -938,6 +962,9 @@ unsigned int GCodeViewer::get_options_visibility_flags() const
|
|||
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe));
|
||||
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract));
|
||||
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract));
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam));
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change));
|
||||
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change));
|
||||
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print));
|
||||
|
@ -958,6 +985,9 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags)
|
|||
set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Wipe)));
|
||||
set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Retractions)));
|
||||
set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Unretractions)));
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Seams)));
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolChanges)));
|
||||
set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ColorChanges)));
|
||||
set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast<unsigned int>(Preview::OptionType::PausePrints)));
|
||||
|
@ -1595,13 +1625,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
|||
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
|
||||
const std::array<IBufferType, 8> first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 });
|
||||
const std::array<IBufferType, 8> non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 });
|
||||
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
|
||||
|
||||
bool is_first_segment = (last_path.vertices_count() == 1);
|
||||
if (is_first_segment || vbuffer_size == 0) {
|
||||
#else
|
||||
if (last_path.vertices_count() == 1 || vbuffer_size == 0) {
|
||||
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
|
||||
// 1st segment or restart into a new vertex buffer
|
||||
// ===============================================
|
||||
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
|
||||
if (last_path.vertices_count() == 1)
|
||||
if (is_first_segment)
|
||||
// starting cap triangles
|
||||
append_starting_cap_triangles(indices, first_seg_v_offsets);
|
||||
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
|
||||
|
@ -1679,7 +1711,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
|||
#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
|
||||
if (next != nullptr && (curr.type != next->type || !last_path.matches(*next)))
|
||||
// ending cap triangles
|
||||
append_ending_cap_triangles(indices, non_first_seg_v_offsets);
|
||||
append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets);
|
||||
#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS
|
||||
|
||||
last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position };
|
||||
|
@ -1714,7 +1746,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
|||
// for the gcode viewer we need to take in account all moves to correctly size the printbed
|
||||
m_paths_bounding_box.merge(move.position.cast<double>());
|
||||
else {
|
||||
#if ENABLE_START_GCODE_VISUALIZATION
|
||||
if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f)
|
||||
#else
|
||||
if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f)
|
||||
#endif // ENABLE_START_GCODE_VISUALIZATION
|
||||
m_paths_bounding_box.merge(move.position.cast<double>());
|
||||
}
|
||||
}
|
||||
|
@ -3163,6 +3199,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
|||
case EMoveType::Custom_GCode: { color = Options_Colors[static_cast<unsigned int>(EOptionsColors::CustomGCodes)]; break; }
|
||||
case EMoveType::Retract: { color = Options_Colors[static_cast<unsigned int>(EOptionsColors::Retractions)]; break; }
|
||||
case EMoveType::Unretract: { color = Options_Colors[static_cast<unsigned int>(EOptionsColors::Unretractions)]; break; }
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Seam: { color = Options_Colors[static_cast<unsigned int>(EOptionsColors::Seams)]; break; }
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
case EMoveType::Extrude: {
|
||||
if (!top_layer_only ||
|
||||
m_sequential_view.current.last == global_endpoints.last ||
|
||||
|
@ -4013,14 +4052,25 @@ void GCodeViewer::render_legend() const
|
|||
if (!m_legend_enabled)
|
||||
return;
|
||||
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
|
||||
imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::SetNextWindowBgAlpha(0.6f);
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
const float max_height = 0.75f * static_cast<float>(cnv_size.get_height());
|
||||
const float child_height = 0.3333f * max_height;
|
||||
ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height });
|
||||
imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove);
|
||||
#else
|
||||
imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
enum class EItemType : unsigned char
|
||||
{
|
||||
|
@ -4030,95 +4080,126 @@ void GCodeViewer::render_legend() const
|
|||
Line
|
||||
};
|
||||
|
||||
const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
|
||||
const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType ||
|
||||
(m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()));
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
float icon_size = ImGui::GetTextLineHeight();
|
||||
float percent_bar_size = 2.0f * ImGui::GetTextLineHeight();
|
||||
const float icon_size = ImGui::GetTextLineHeight();
|
||||
const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight();
|
||||
|
||||
auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label,
|
||||
bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 2>& offsets = { 0.0f, 0.0f },
|
||||
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
auto append_item = [this, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label,
|
||||
#else
|
||||
auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label,
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 4>& offsets = { 0.0f, 0.0f, 0.0f, 0.0f },
|
||||
double used_filament_m = 0.0, double used_filament_g = 0.0,
|
||||
std::function<void()> callback = nullptr) {
|
||||
if (!visible)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
switch (type) {
|
||||
default:
|
||||
case EItemType::Rect: {
|
||||
draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
|
||||
ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
|
||||
break;
|
||||
}
|
||||
case EItemType::Circle: {
|
||||
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
|
||||
if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") {
|
||||
draw_list->AddCircleFilled(center, 0.5f * icon_size,
|
||||
ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
|
||||
float radius = 0.5f * icon_size;
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
|
||||
radius = 0.5f * icon_size * 0.01f * 33.0f;
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
|
||||
}
|
||||
else
|
||||
draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
|
||||
if (!visible)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
|
||||
|
||||
break;
|
||||
}
|
||||
case EItemType::Hexagon: {
|
||||
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
|
||||
draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6);
|
||||
break;
|
||||
}
|
||||
case EItemType::Line: {
|
||||
draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// draw text
|
||||
ImGui::Dummy({ icon_size, icon_size });
|
||||
ImGui::SameLine();
|
||||
if (callback != nullptr) {
|
||||
if (ImGui::MenuItem(label.c_str()))
|
||||
callback();
|
||||
else {
|
||||
// show tooltip
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (!visible)
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show"));
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
if (!visible)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
|
||||
|
||||
// to avoid the tooltip to change size when moving the mouse
|
||||
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
||||
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
|
||||
}
|
||||
}
|
||||
|
||||
if (!time.empty()) {
|
||||
ImGui::SameLine(offsets[0]);
|
||||
imgui.text(time);
|
||||
ImGui::SameLine(offsets[1]);
|
||||
pos = ImGui::GetCursorScreenPos();
|
||||
float width = std::max(1.0f, percent_bar_size * percent / max_percent);
|
||||
draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f },
|
||||
ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT));
|
||||
ImGui::Dummy({ percent_bar_size, icon_size });
|
||||
ImGui::SameLine();
|
||||
char buf[64];
|
||||
::sprintf(buf, "%.1f%%", 100.0f * percent);
|
||||
ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
|
||||
}
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
switch (type) {
|
||||
default:
|
||||
case EItemType::Rect: {
|
||||
draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
|
||||
ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
|
||||
break;
|
||||
}
|
||||
case EItemType::Circle: {
|
||||
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
|
||||
if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") {
|
||||
draw_list->AddCircleFilled(center, 0.5f * icon_size,
|
||||
ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
|
||||
float radius = 0.5f * icon_size;
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
|
||||
radius = 0.5f * icon_size * 0.01f * 33.0f;
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
|
||||
}
|
||||
else
|
||||
imgui.text(label);
|
||||
draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
|
||||
|
||||
if (!visible)
|
||||
ImGui::PopStyleVar();
|
||||
break;
|
||||
}
|
||||
case EItemType::Hexagon: {
|
||||
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
|
||||
draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6);
|
||||
break;
|
||||
}
|
||||
case EItemType::Line: {
|
||||
draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// draw text
|
||||
ImGui::Dummy({ icon_size, icon_size });
|
||||
ImGui::SameLine();
|
||||
if (callback != nullptr) {
|
||||
if (ImGui::MenuItem(label.c_str()))
|
||||
callback();
|
||||
else {
|
||||
// show tooltip
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (!visible)
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show"));
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
if (!visible)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
|
||||
|
||||
// to avoid the tooltip to change size when moving the mouse
|
||||
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
||||
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
|
||||
}
|
||||
}
|
||||
|
||||
if (!time.empty()) {
|
||||
ImGui::SameLine(offsets[0]);
|
||||
imgui.text(time);
|
||||
ImGui::SameLine(offsets[1]);
|
||||
pos = ImGui::GetCursorScreenPos();
|
||||
const float width = std::max(1.0f, percent_bar_size * percent / max_percent);
|
||||
draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f },
|
||||
ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT));
|
||||
ImGui::Dummy({ percent_bar_size, icon_size });
|
||||
ImGui::SameLine();
|
||||
char buf[64];
|
||||
::sprintf(buf, "%.1f%%", 100.0f * percent);
|
||||
ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
|
||||
ImGui::SameLine(offsets[2]);
|
||||
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m);
|
||||
imgui.text(buf);
|
||||
ImGui::SameLine(offsets[3]);
|
||||
::sprintf(buf, "%.2f g", used_filament_g);
|
||||
imgui.text(buf);
|
||||
}
|
||||
}
|
||||
else {
|
||||
imgui.text(label);
|
||||
if (used_filament_m > 0.0) {
|
||||
char buf[64];
|
||||
ImGui::SameLine(offsets[0]);
|
||||
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m);
|
||||
imgui.text(buf);
|
||||
ImGui::SameLine(offsets[1]);
|
||||
::sprintf(buf, "%.2f g", used_filament_g);
|
||||
imgui.text(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (!visible)
|
||||
ImGui::PopStyleVar();
|
||||
};
|
||||
|
||||
auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) {
|
||||
|
@ -4136,19 +4217,20 @@ void GCodeViewer::render_legend() const
|
|||
append_range_item(0, range.min, decimals);
|
||||
}
|
||||
else {
|
||||
float step_size = range.step_size();
|
||||
const float step_size = range.step_size();
|
||||
for (int i = static_cast<int>(Range_Colors.size()) - 1; i >= 0; --i) {
|
||||
append_range_item(i, range.min + static_cast<float>(i) * step_size, decimals);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto append_headers = [&imgui](const std::array<std::string, 3>& texts, const std::array<float, 2>& offsets) {
|
||||
imgui.text(texts[0]);
|
||||
ImGui::SameLine(offsets[0]);
|
||||
imgui.text(texts[1]);
|
||||
ImGui::SameLine(offsets[1]);
|
||||
imgui.text(texts[2]);
|
||||
auto append_headers = [&imgui](const std::array<std::string, 5>& texts, const std::array<float, 4>& offsets) {
|
||||
size_t i = 0;
|
||||
for (; i < offsets.size(); i++) {
|
||||
imgui.text(texts[i]);
|
||||
ImGui::SameLine(offsets[i]);
|
||||
}
|
||||
imgui.text(texts[i]);
|
||||
ImGui::Separator();
|
||||
};
|
||||
|
||||
|
@ -4161,11 +4243,12 @@ void GCodeViewer::render_legend() const
|
|||
};
|
||||
|
||||
auto calculate_offsets = [max_width](const std::vector<std::string>& labels, const std::vector<std::string>& times,
|
||||
const std::array<std::string, 2>& titles, float extra_size = 0.0f) {
|
||||
const std::array<std::string, 4>& titles, float extra_size = 0.0f) {
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
std::array<float, 2> ret = { 0.0f, 0.0f };
|
||||
std::array<float, 4> ret = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x;
|
||||
ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x;
|
||||
for (size_t i = 1; i < titles.size(); i++)
|
||||
ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x;
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
@ -4185,8 +4268,8 @@ void GCodeViewer::render_legend() const
|
|||
if (lower_b == zs.end())
|
||||
continue;
|
||||
|
||||
double current_z = *lower_b;
|
||||
double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b);
|
||||
const double current_z = *lower_b;
|
||||
const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b);
|
||||
|
||||
// to avoid duplicate values, check adding values
|
||||
if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z))
|
||||
|
@ -4216,16 +4299,27 @@ void GCodeViewer::render_legend() const
|
|||
return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm");
|
||||
};
|
||||
|
||||
auto role_time_and_percent = [ time_mode](ExtrusionRole role) {
|
||||
auto role_time_and_percent = [time_mode](ExtrusionRole role) {
|
||||
auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair<ExtrusionRole, float>& item) { return role == item.first; });
|
||||
return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f);
|
||||
};
|
||||
|
||||
auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) {
|
||||
auto it = m_print_statistics.used_filaments_per_role.find(role);
|
||||
if (it == m_print_statistics.used_filaments_per_role.end())
|
||||
return std::make_pair(0.0, 0.0);
|
||||
|
||||
double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0;
|
||||
return std::make_pair(it->second.first * koef, it->second.second);
|
||||
};
|
||||
|
||||
// data used to properly align items in columns when showing time
|
||||
std::array<float, 2> offsets = { 0.0f, 0.0f };
|
||||
std::array<float, 4> offsets = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
std::vector<std::string> labels;
|
||||
std::vector<std::string> times;
|
||||
std::vector<float> percents;
|
||||
std::vector<double> used_filaments_m;
|
||||
std::vector<double> used_filaments_g;
|
||||
float max_percent = 0.0f;
|
||||
|
||||
if (m_view_type == EViewType::FeatureType) {
|
||||
|
@ -4238,10 +4332,73 @@ void GCodeViewer::render_legend() const
|
|||
times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : "");
|
||||
percents.push_back(percent);
|
||||
max_percent = std::max(max_percent, percent);
|
||||
auto [used_filament_m, used_filament_g] = used_filament_per_role(role);
|
||||
used_filaments_m.push_back(used_filament_m);
|
||||
used_filaments_g.push_back(used_filament_g);
|
||||
}
|
||||
}
|
||||
|
||||
offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size);
|
||||
std::string longest_percentage_string;
|
||||
for (double item : percents) {
|
||||
char buffer[64];
|
||||
::sprintf(buffer, "%.2f %%", item);
|
||||
if (::strlen(buffer) > longest_percentage_string.length())
|
||||
longest_percentage_string = buffer;
|
||||
}
|
||||
longest_percentage_string += " ";
|
||||
if (_u8L("Percentage").length() > longest_percentage_string.length())
|
||||
longest_percentage_string = _u8L("Percentage");
|
||||
|
||||
std::string longest_used_filament_string;
|
||||
for (double item : used_filaments_m) {
|
||||
char buffer[64];
|
||||
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item);
|
||||
if (::strlen(buffer) > longest_used_filament_string.length())
|
||||
longest_used_filament_string = buffer;
|
||||
}
|
||||
|
||||
offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size);
|
||||
}
|
||||
|
||||
// get used filament (meters and grams) from used volume in respect to the active extruder
|
||||
auto get_used_filament_from_volume = [imperial_units](double volume, int extruder_id) {
|
||||
const std::vector<std::string>& filament_presets = wxGetApp().preset_bundle->filament_presets;
|
||||
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
|
||||
|
||||
double koef = imperial_units ? 1.0/ObjectManipulation::in_to_mm : 0.001;
|
||||
|
||||
std::pair<double, double> ret = { 0.0, 0.0 };
|
||||
if (const Preset* filament_preset = filaments.find_preset(filament_presets[extruder_id], false)) {
|
||||
double filament_radius = 0.5 * filament_preset->config.opt_float("filament_diameter", 0);
|
||||
double s = PI * sqr(filament_radius);
|
||||
ret.first = volume / s * koef;
|
||||
double filament_density = filament_preset->config.opt_float("filament_density", 0);
|
||||
ret.second = volume * filament_density * 0.001;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
if (m_view_type == EViewType::Tool) {
|
||||
// calculate used filaments data
|
||||
for (size_t extruder_id : m_extruder_ids) {
|
||||
if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end())
|
||||
continue;
|
||||
double volume = m_print_statistics.volumes_per_extruder.at(extruder_id);
|
||||
|
||||
auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
|
||||
used_filaments_m.push_back(used_filament_m);
|
||||
used_filaments_g.push_back(used_filament_g);
|
||||
}
|
||||
|
||||
std::string longest_used_filament_string;
|
||||
for (double item : used_filaments_m) {
|
||||
char buffer[64];
|
||||
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item);
|
||||
if (::strlen(buffer) > longest_used_filament_string.length())
|
||||
longest_used_filament_string = buffer;
|
||||
}
|
||||
|
||||
offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size);
|
||||
}
|
||||
|
||||
// extrusion paths section -> title
|
||||
|
@ -4249,7 +4406,7 @@ void GCodeViewer::render_legend() const
|
|||
{
|
||||
case EViewType::FeatureType:
|
||||
{
|
||||
append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets);
|
||||
append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets);
|
||||
break;
|
||||
}
|
||||
case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; }
|
||||
|
@ -4258,7 +4415,11 @@ void GCodeViewer::render_legend() const
|
|||
case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; }
|
||||
case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; }
|
||||
case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; }
|
||||
case EViewType::Tool: { imgui.title(_u8L("Tool")); break; }
|
||||
case EViewType::Tool:
|
||||
{
|
||||
append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets);
|
||||
break;
|
||||
}
|
||||
case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; }
|
||||
default: { break; }
|
||||
}
|
||||
|
@ -4272,9 +4433,9 @@ void GCodeViewer::render_legend() const
|
|||
ExtrusionRole role = m_roles[i];
|
||||
if (role >= erCount)
|
||||
continue;
|
||||
bool visible = is_visible(role);
|
||||
const bool visible = is_visible(role);
|
||||
append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast<unsigned int>(role)], labels[i],
|
||||
visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() {
|
||||
visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() {
|
||||
Extrusions* extrusions = const_cast<Extrusions*>(&m_extrusions);
|
||||
extrusions->role_visibility_flags = visible ? extrusions->role_visibility_flags & ~(1 << role) : extrusions->role_visibility_flags | (1 << role);
|
||||
// update buffers' render paths
|
||||
|
@ -4296,16 +4457,33 @@ void GCodeViewer::render_legend() const
|
|||
case EViewType::Tool:
|
||||
{
|
||||
// shows only extruders actually used
|
||||
for (unsigned char i : m_extruder_ids) {
|
||||
append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1));
|
||||
size_t i = 0;
|
||||
for (unsigned char extruder_id : m_extruder_ids) {
|
||||
append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1),
|
||||
true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EViewType::ColorPrint:
|
||||
{
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
|
||||
size_t total_items = 1;
|
||||
for (unsigned char i : m_extruder_ids) {
|
||||
total_items += color_print_ranges(i, custom_gcode_per_print_z).size();
|
||||
}
|
||||
|
||||
const bool need_scrollable = static_cast<float>(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height;
|
||||
|
||||
// add scrollable region, if needed
|
||||
if (need_scrollable)
|
||||
ImGui::BeginChild("color_prints", { -1.0f, child_height }, false);
|
||||
#else
|
||||
const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
if (m_extruders_count == 1) { // single extruder use case
|
||||
std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(0, custom_gcode_per_print_z);
|
||||
const std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(0, custom_gcode_per_print_z);
|
||||
const int items_cnt = static_cast<int>(cp_values.size());
|
||||
if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode
|
||||
append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color"));
|
||||
|
@ -4328,7 +4506,7 @@ void GCodeViewer::render_legend() const
|
|||
else { // multi extruder use case
|
||||
// shows only extruders actually used
|
||||
for (unsigned char i : m_extruder_ids) {
|
||||
std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(i, custom_gcode_per_print_z);
|
||||
const std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(i, custom_gcode_per_print_z);
|
||||
const int items_cnt = static_cast<int>(cp_values.size());
|
||||
if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode
|
||||
append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color"));
|
||||
|
@ -4354,6 +4532,10 @@ void GCodeViewer::render_legend() const
|
|||
}
|
||||
}
|
||||
}
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
if (need_scrollable)
|
||||
ImGui::EndChild();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -4379,10 +4561,11 @@ void GCodeViewer::render_legend() const
|
|||
Color color1;
|
||||
Color color2;
|
||||
Times times;
|
||||
std::pair<double, double> used_filament {0.0f, 0.0f};
|
||||
};
|
||||
using PartialTimes = std::vector<PartialTime>;
|
||||
|
||||
auto generate_partial_times = [this](const TimesList& times) {
|
||||
auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector<double>& used_filaments) {
|
||||
PartialTimes items;
|
||||
|
||||
std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
|
||||
|
@ -4392,6 +4575,7 @@ void GCodeViewer::render_legend() const
|
|||
last_color[i] = m_tool_colors[i];
|
||||
}
|
||||
int last_extruder_id = 1;
|
||||
int color_change_idx = 0;
|
||||
for (const auto& time_rec : times) {
|
||||
switch (time_rec.first)
|
||||
{
|
||||
|
@ -4407,14 +4591,14 @@ void GCodeViewer::render_legend() const
|
|||
case CustomGCode::ColorChange: {
|
||||
auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
|
||||
if (it != custom_gcode_per_print_z.end()) {
|
||||
items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second });
|
||||
items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) });
|
||||
items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second });
|
||||
last_color[it->extruder - 1] = decode_color(it->color);
|
||||
last_extruder_id = it->extruder;
|
||||
custom_gcode_per_print_z.erase(it);
|
||||
}
|
||||
else
|
||||
items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second });
|
||||
items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) });
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -4425,7 +4609,7 @@ void GCodeViewer::render_legend() const
|
|||
return items;
|
||||
};
|
||||
|
||||
auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array<float, 2>& offsets, const Times& times) {
|
||||
auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array<float, 4>& offsets, const Times& times) {
|
||||
imgui.text(_u8L("Color change"));
|
||||
ImGui::SameLine();
|
||||
|
||||
|
@ -4444,7 +4628,7 @@ void GCodeViewer::render_legend() const
|
|||
imgui.text(short_time(get_time_dhms(times.second - times.first)));
|
||||
};
|
||||
|
||||
auto append_print = [&imgui](const Color& color, const std::array<float, 2>& offsets, const Times& times) {
|
||||
auto append_print = [&imgui, imperial_units](const Color& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) {
|
||||
imgui.text(_u8L("Print"));
|
||||
ImGui::SameLine();
|
||||
|
||||
|
@ -4460,9 +4644,19 @@ void GCodeViewer::render_legend() const
|
|||
imgui.text(short_time(get_time_dhms(times.second)));
|
||||
ImGui::SameLine(offsets[1]);
|
||||
imgui.text(short_time(get_time_dhms(times.first)));
|
||||
if (used_filament.first > 0.0f) {
|
||||
char buffer[64];
|
||||
ImGui::SameLine(offsets[2]);
|
||||
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first);
|
||||
imgui.text(buffer);
|
||||
|
||||
ImGui::SameLine(offsets[3]);
|
||||
::sprintf(buffer, "%.2f g", used_filament.second);
|
||||
imgui.text(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times);
|
||||
PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change);
|
||||
if (!partial_times.empty()) {
|
||||
labels.clear();
|
||||
times.clear();
|
||||
|
@ -4476,15 +4670,34 @@ void GCodeViewer::render_legend() const
|
|||
}
|
||||
times.push_back(short_time(get_time_dhms(item.times.second)));
|
||||
}
|
||||
offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size);
|
||||
|
||||
|
||||
std::string longest_used_filament_string;
|
||||
for (const PartialTime& item : partial_times) {
|
||||
if (item.used_filament.first > 0.0f) {
|
||||
char buffer[64];
|
||||
::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first);
|
||||
if (::strlen(buffer) > longest_used_filament_string.length())
|
||||
longest_used_filament_string = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size);
|
||||
|
||||
ImGui::Spacing();
|
||||
append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets);
|
||||
append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets);
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
const bool need_scrollable = static_cast<float>(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height;
|
||||
if (need_scrollable)
|
||||
// add scrollable region
|
||||
ImGui::BeginChild("events", { -1.0f, child_height }, false);
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
for (const PartialTime& item : partial_times) {
|
||||
switch (item.type)
|
||||
{
|
||||
case PartialTime::EType::Print: {
|
||||
append_print(item.color1, offsets, item.times);
|
||||
append_print(item.color1, offsets, item.times, item.used_filament);
|
||||
break;
|
||||
}
|
||||
case PartialTime::EType::Pause: {
|
||||
|
@ -4499,6 +4712,11 @@ void GCodeViewer::render_legend() const
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
if (need_scrollable)
|
||||
ImGui::EndChild();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4557,7 +4775,12 @@ void GCodeViewer::render_legend() const
|
|||
available(EMoveType::Pause_Print) ||
|
||||
available(EMoveType::Retract) ||
|
||||
available(EMoveType::Tool_change) ||
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
available(EMoveType::Unretract) ||
|
||||
available(EMoveType::Seam);
|
||||
#else
|
||||
available(EMoveType::Unretract);
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
};
|
||||
|
||||
auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) {
|
||||
|
@ -4575,6 +4798,9 @@ void GCodeViewer::render_legend() const
|
|||
// items
|
||||
add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions"));
|
||||
add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions"));
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams"));
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes"));
|
||||
add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes"));
|
||||
add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses"));
|
||||
|
@ -4634,10 +4860,14 @@ void GCodeViewer::render_legend() const
|
|||
}
|
||||
|
||||
// total estimated printing time section
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
if (show_estimated_time) {
|
||||
#else
|
||||
if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType ||
|
||||
(m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) {
|
||||
|
||||
ImGui::Spacing();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
ImGui::Separator();
|
||||
|
@ -4647,12 +4877,12 @@ void GCodeViewer::render_legend() const
|
|||
ImGui::AlignTextToFramePadding();
|
||||
switch (m_time_estimate_mode)
|
||||
{
|
||||
case PrintEstimatedTimeStatistics::ETimeMode::Normal:
|
||||
case PrintEstimatedStatistics::ETimeMode::Normal:
|
||||
{
|
||||
imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:");
|
||||
break;
|
||||
}
|
||||
case PrintEstimatedTimeStatistics::ETimeMode::Stealth:
|
||||
case PrintEstimatedStatistics::ETimeMode::Stealth:
|
||||
{
|
||||
imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:");
|
||||
break;
|
||||
|
@ -4662,18 +4892,18 @@ void GCodeViewer::render_legend() const
|
|||
ImGui::SameLine();
|
||||
imgui.text(short_time(get_time_dhms(time_mode.time)));
|
||||
|
||||
auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedTimeStatistics::ETimeMode mode) {
|
||||
auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) {
|
||||
bool show = false;
|
||||
for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) {
|
||||
for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) {
|
||||
if (i != static_cast<size_t>(mode) &&
|
||||
short_time(get_time_dhms(m_time_statistics.modes[static_cast<size_t>(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) {
|
||||
short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) {
|
||||
show = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (show && m_time_statistics.modes[static_cast<size_t>(mode)].roles_times.size() > 0) {
|
||||
if (show && m_print_statistics.modes[static_cast<size_t>(mode)].roles_times.size() > 0) {
|
||||
if (imgui.button(label)) {
|
||||
*const_cast<PrintEstimatedTimeStatistics::ETimeMode*>(&m_time_estimate_mode) = mode;
|
||||
*const_cast<PrintEstimatedStatistics::ETimeMode*>(&m_time_estimate_mode) = mode;
|
||||
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
||||
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
|
||||
}
|
||||
|
@ -4681,12 +4911,12 @@ void GCodeViewer::render_legend() const
|
|||
};
|
||||
|
||||
switch (m_time_estimate_mode) {
|
||||
case PrintEstimatedTimeStatistics::ETimeMode::Normal: {
|
||||
show_mode_button(_L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth);
|
||||
case PrintEstimatedStatistics::ETimeMode::Normal: {
|
||||
show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth);
|
||||
break;
|
||||
}
|
||||
case PrintEstimatedTimeStatistics::ETimeMode::Stealth: {
|
||||
show_mode_button(_L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal);
|
||||
case PrintEstimatedStatistics::ETimeMode::Stealth: {
|
||||
show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal);
|
||||
break;
|
||||
}
|
||||
default : { assert(false); break; }
|
||||
|
|
|
@ -46,6 +46,9 @@ class GCodeViewer
|
|||
{
|
||||
Retractions,
|
||||
Unretractions,
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
Seams,
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
ToolChanges,
|
||||
ColorChanges,
|
||||
PausePrints,
|
||||
|
@ -693,8 +696,8 @@ 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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "libslic3r/libslic3r.h"
|
||||
#include "GLCanvas3D.hpp"
|
||||
|
||||
#include "admesh/stl.h"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
|
@ -1672,8 +1671,10 @@ void GLCanvas3D::render()
|
|||
if (m_picking_enabled)
|
||||
m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast<coord_t>());
|
||||
|
||||
_render_current_gizmo();
|
||||
// sidebar hints need to be rendered before the gizmos because the depth buffer
|
||||
// could be invalidated by the following gizmo render methods
|
||||
_render_selection_sidebar_hints();
|
||||
_render_current_gizmo();
|
||||
#if ENABLE_RENDER_PICKING_PASS
|
||||
}
|
||||
#endif // ENABLE_RENDER_PICKING_PASS
|
||||
|
@ -1712,6 +1713,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
|
||||
|
@ -3146,7 +3152,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();
|
||||
|
||||
|
@ -3169,7 +3175,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();
|
||||
|
||||
|
@ -4829,8 +4835,16 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
|||
if (m_canvas == nullptr && m_context == nullptr)
|
||||
return;
|
||||
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
const std::array<unsigned int, 2> new_size = { w, h };
|
||||
if (m_old_size == new_size)
|
||||
return;
|
||||
|
||||
m_old_size = new_size;
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
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());
|
||||
|
@ -4838,6 +4852,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();
|
||||
}
|
||||
|
@ -4878,8 +4896,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());
|
||||
|
||||
|
@ -5048,8 +5065,9 @@ void GLCanvas3D::_render_background() const
|
|||
if (!m_volumes.empty())
|
||||
use_error_color &= _is_any_volume_outside();
|
||||
else {
|
||||
BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
|
||||
use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_paths_bounding_box()) : false;
|
||||
const BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
|
||||
const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box();
|
||||
use_error_color &= (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) ? !test_volume.contains(paths_volume) : false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5983,7 +6001,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 ) {
|
||||
|
@ -6006,7 +6024,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)
|
||||
|
@ -6014,7 +6032,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.
|
||||
|
@ -6023,8 +6041,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));
|
||||
}
|
||||
}
|
||||
|
@ -6249,7 +6267,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));
|
||||
|
|
|
@ -471,6 +471,10 @@ private:
|
|||
Model* m_model;
|
||||
BackgroundSlicingProcess *m_process;
|
||||
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
std::array<unsigned int, 2> m_old_size{ 0, 0 };
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
// Screen is only refreshed from the OnIdle handler if it is dirty.
|
||||
bool m_dirty;
|
||||
bool m_initialized;
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
@ -244,10 +246,11 @@ private:
|
|||
|
||||
// credits infornation
|
||||
credits = title + " " +
|
||||
_L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" +
|
||||
_L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" +
|
||||
_L("Developed by Prusa Research.")+ "\n\n" +
|
||||
title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" +
|
||||
_L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" +
|
||||
_L("Artwork model by Nora Al-Badri and Jan Nikolai Nelles");
|
||||
_L("Artwork model by M Boyer");
|
||||
|
||||
title_font = version_font = credits_font = init_font;
|
||||
}
|
||||
|
@ -632,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);
|
||||
}
|
||||
|
@ -904,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();
|
||||
|
@ -1623,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 +
|
||||
|
@ -1663,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,
|
||||
|
@ -1680,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");
|
||||
|
@ -1781,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();
|
||||
|
@ -1794,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;
|
||||
|
@ -2055,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()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
@ -1457,12 +1460,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)
|
||||
|
@ -2467,28 +2473,22 @@ void ObjectList::unselect_objects()
|
|||
m_prevent_list_events = false;
|
||||
}
|
||||
|
||||
void ObjectList::select_current_object(int idx)
|
||||
void ObjectList::select_object_item(bool is_msr_gizmo)
|
||||
{
|
||||
m_prevent_list_events = true;
|
||||
UnselectAll();
|
||||
if (idx >= 0)
|
||||
Select(m_objects_model->GetItemById(idx));
|
||||
part_selection_changed();
|
||||
m_prevent_list_events = false;
|
||||
}
|
||||
if (wxDataViewItem item = GetSelection()) {
|
||||
ItemType type = m_objects_model->GetItemType(item);
|
||||
bool is_volume_item = type == itVolume || type == itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume;
|
||||
if (is_msr_gizmo && is_volume_item || type == itObject)
|
||||
return;
|
||||
|
||||
void ObjectList::select_current_volume(int idx, int vol_idx)
|
||||
{
|
||||
if (vol_idx < 0) {
|
||||
select_current_object(idx);
|
||||
return;
|
||||
if (wxDataViewItem obj_item = m_objects_model->GetTopParent(item)) {
|
||||
m_prevent_list_events = true;
|
||||
UnselectAll();
|
||||
Select(obj_item);
|
||||
part_selection_changed();
|
||||
m_prevent_list_events = false;
|
||||
}
|
||||
}
|
||||
m_prevent_list_events = true;
|
||||
UnselectAll();
|
||||
if (idx >= 0)
|
||||
Select(m_objects_model->GetItemByVolumeId(idx, vol_idx));
|
||||
part_selection_changed();
|
||||
m_prevent_list_events = false;
|
||||
}
|
||||
|
||||
static void update_selection(wxDataViewItemArray& sels, ObjectList::SELECTION_MODE mode, ObjectDataViewModel* model)
|
||||
|
@ -2893,7 +2893,8 @@ void ObjectList::update_selections()
|
|||
{
|
||||
const auto item = GetSelection();
|
||||
if (selection.is_single_full_object()) {
|
||||
if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject)
|
||||
if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject &&
|
||||
m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx() )
|
||||
return;
|
||||
sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
|
||||
}
|
||||
|
|
|
@ -291,10 +291,9 @@ public:
|
|||
// #ys_FIXME_to_delete
|
||||
// Unselect all objects in the list on c++ side
|
||||
void unselect_objects();
|
||||
// Select current object in the list on c++ side
|
||||
void select_current_object(int idx);
|
||||
// Select current volume in the list on c++ side
|
||||
void select_current_volume(int idx, int vol_idx);
|
||||
// Select object item in the ObjectList, when some gizmo is activated
|
||||
// "is_msr_gizmo" indicates if Move/Scale/Rotate gizmo was activated
|
||||
void select_object_item(bool is_msr_gizmo);
|
||||
|
||||
// Remove objects/sub-object from the list
|
||||
void remove();
|
||||
|
|
|
@ -235,7 +235,7 @@ bool Preview::init(wxWindow* parent, Model* model)
|
|||
_L("Ironing") + "|1|" +
|
||||
_L("Bridge infill") + "|1|" +
|
||||
_L("Gap fill") + "|1|" +
|
||||
_L("Skirt") + "|1|" +
|
||||
_L("Skirt/Brim") + "|1|" +
|
||||
_L("Support material") + "|1|" +
|
||||
_L("Support material interface") + "|1|" +
|
||||
_L("Wipe tower") + "|1|" +
|
||||
|
@ -250,6 +250,9 @@ bool Preview::init(wxWindow* parent, Model* model)
|
|||
get_option_type_string(OptionType::Wipe) + "|0|" +
|
||||
get_option_type_string(OptionType::Retractions) + "|0|" +
|
||||
get_option_type_string(OptionType::Unretractions) + "|0|" +
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
get_option_type_string(OptionType::Seams) + "|0|" +
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
get_option_type_string(OptionType::ToolChanges) + "|0|" +
|
||||
get_option_type_string(OptionType::ColorChanges) + "|0|" +
|
||||
get_option_type_string(OptionType::PausePrints) + "|0|" +
|
||||
|
@ -640,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);
|
||||
}
|
||||
|
||||
|
@ -1008,6 +1011,9 @@ wxString Preview::get_option_type_string(OptionType type) const
|
|||
case OptionType::Wipe: { return _L("Wipe"); }
|
||||
case OptionType::Retractions: { return _L("Retractions"); }
|
||||
case OptionType::Unretractions: { return _L("Deretractions"); }
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
case OptionType::Seams: { return _L("Seams"); }
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
case OptionType::ToolChanges: { return _L("Tool changes"); }
|
||||
case OptionType::ColorChanges: { return _L("Color changes"); }
|
||||
case OptionType::PausePrints: { return _L("Print pauses"); }
|
||||
|
|
|
@ -116,6 +116,9 @@ public:
|
|||
Wipe,
|
||||
Retractions,
|
||||
Unretractions,
|
||||
#if ENABLE_SEAMS_VISUALIZATION
|
||||
Seams,
|
||||
#endif // ENABLE_SEAMS_VISUALIZATION
|
||||
ToolChanges,
|
||||
ColorChanges,
|
||||
PausePrints,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
|
@ -1084,8 +1085,10 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos)
|
|||
return;
|
||||
|
||||
size_t idx = get_gizmo_idx_from_mouse(mouse_pos);
|
||||
if (idx != Undefined && m_gizmos[idx]->is_activable() && m_hover == idx)
|
||||
if (idx != Undefined && m_gizmos[idx]->is_activable() && m_hover == idx) {
|
||||
activate_gizmo(m_current == idx ? Undefined : (EType)idx);
|
||||
wxGetApp().obj_list()->select_object_item((EType)idx <= Rotate);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos)
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -672,10 +685,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 +1023,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();
|
||||
|
||||
|
@ -1483,10 +1533,10 @@ void MainFrame::repair_stl()
|
|||
output_file = dlg.GetPath();
|
||||
}
|
||||
|
||||
auto tmesh = new Slic3r::TriangleMesh();
|
||||
tmesh->ReadSTLFile(input_file.ToUTF8().data());
|
||||
tmesh->repair();
|
||||
tmesh->WriteOBJFile(output_file.ToUTF8().data());
|
||||
Slic3r::TriangleMesh tmesh;
|
||||
tmesh.ReadSTLFile(input_file.ToUTF8().data());
|
||||
tmesh.repair();
|
||||
tmesh.WriteOBJFile(output_file.ToUTF8().data());
|
||||
Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair"));
|
||||
}
|
||||
|
||||
|
@ -1518,7 +1568,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 +1601,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 +1637,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:"),
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*/{
|
||||
|
|
|
@ -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"
|
||||
|
@ -976,7 +979,6 @@ void Sidebar::sys_color_changed()
|
|||
for (PlaterPresetComboBox* combo : p->combos_filament)
|
||||
combo->msw_rescale();
|
||||
|
||||
p->frequently_changed_parameters->msw_rescale();
|
||||
p->object_list->msw_rescale();
|
||||
p->object_list->sys_color_changed();
|
||||
p->object_manipulation->sys_color_changed();
|
||||
|
@ -1173,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;
|
||||
|
@ -1204,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);
|
||||
|
@ -1358,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()
|
||||
|
@ -1396,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
|
||||
|
@ -1440,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 };
|
||||
|
||||
|
@ -1510,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,
|
||||
|
@ -1593,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;
|
||||
|
||||
|
@ -1689,7 +1727,7 @@ struct Plater::priv
|
|||
bool can_split(bool to_objects) const;
|
||||
|
||||
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
ThumbnailsList generate_thumbnails(const ThumbnailsParams& params);
|
||||
|
||||
void bring_instance_forward() const;
|
||||
|
||||
|
@ -1765,15 +1803,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
background_process.set_fff_print(&fff_print);
|
||||
background_process.set_sla_print(&sla_print);
|
||||
background_process.set_gcode_result(&gcode_result);
|
||||
background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
{
|
||||
std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) {
|
||||
generate_thumbnails(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background);
|
||||
});
|
||||
std::future<void> result = task.get_future();
|
||||
wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); });
|
||||
result.wait();
|
||||
});
|
||||
background_process.set_thumbnail_cb([this](const ThumbnailsParams& params) { return this->generate_thumbnails(params); });
|
||||
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
|
||||
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
|
||||
background_process.set_export_began_event(EVT_EXPORT_BEGAN);
|
||||
|
@ -3818,17 +3848,17 @@ void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsig
|
|||
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background);
|
||||
}
|
||||
|
||||
void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
ThumbnailsList Plater::priv::generate_thumbnails(const ThumbnailsParams& params)
|
||||
{
|
||||
thumbnails.clear();
|
||||
for (const Vec2d& size : sizes)
|
||||
{
|
||||
ThumbnailsList thumbnails;
|
||||
for (const Vec2d& size : params.sizes) {
|
||||
thumbnails.push_back(ThumbnailData());
|
||||
Point isize(size); // round to ints
|
||||
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, show_bed, transparent_background);
|
||||
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), params.printable_only, params.parts_only, params.show_bed, params.transparent_background);
|
||||
if (!thumbnails.back().is_valid())
|
||||
thumbnails.pop_back();
|
||||
}
|
||||
return thumbnails;
|
||||
}
|
||||
|
||||
wxString Plater::priv::get_project_filename(const wxString& extension) const
|
||||
|
@ -4217,9 +4247,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())
|
||||
|
@ -4249,6 +4279,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();
|
||||
|
@ -4289,8 +4324,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;
|
||||
}
|
||||
|
@ -4379,6 +4419,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 */)
|
||||
|
@ -4462,9 +4506,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; }
|
||||
|
@ -4475,12 +4526,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);
|
||||
|
@ -4504,8 +4573,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*/)
|
||||
|
@ -4536,7 +4613,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()
|
||||
|
@ -5095,6 +5178,30 @@ void Plater::export_stl(bool extended, bool selection_only)
|
|||
if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
|
||||
return;
|
||||
|
||||
// Following lambda generates a combined mesh for export with normals pointing outwards.
|
||||
auto mesh_to_export = [](const ModelObject* mo, bool instances) -> TriangleMesh {
|
||||
TriangleMesh mesh;
|
||||
for (const ModelVolume *v : mo->volumes)
|
||||
if (v->is_model_part()) {
|
||||
TriangleMesh vol_mesh(v->mesh());
|
||||
vol_mesh.repair();
|
||||
vol_mesh.transform(v->get_matrix(), true);
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
mesh.repair();
|
||||
if (instances) {
|
||||
TriangleMesh vols_mesh(mesh);
|
||||
mesh = TriangleMesh();
|
||||
for (const ModelInstance *i : mo->instances) {
|
||||
TriangleMesh m = vols_mesh;
|
||||
m.transform(i->get_matrix(), true);
|
||||
mesh.merge(m);
|
||||
}
|
||||
}
|
||||
mesh.repair();
|
||||
return mesh;
|
||||
};
|
||||
|
||||
TriangleMesh mesh;
|
||||
if (p->printer_technology == ptFFF) {
|
||||
if (selection_only) {
|
||||
|
@ -5102,20 +5209,21 @@ void Plater::export_stl(bool extended, bool selection_only)
|
|||
if (selection.get_mode() == Selection::Instance)
|
||||
{
|
||||
if (selection.is_single_full_object())
|
||||
mesh = model_object->mesh();
|
||||
mesh = mesh_to_export(model_object, true);
|
||||
else
|
||||
mesh = model_object->full_raw_mesh();
|
||||
mesh = mesh_to_export(model_object, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
mesh = model_object->volumes[volume->volume_idx()]->mesh();
|
||||
mesh.transform(volume->get_volume_transformation().get_matrix());
|
||||
mesh.transform(volume->get_volume_transformation().get_matrix(), true);
|
||||
mesh.translate(-model_object->origin_translation.cast<float>());
|
||||
}
|
||||
}
|
||||
else {
|
||||
mesh = p->model.mesh();
|
||||
for (const ModelObject *o : p->model.objects)
|
||||
mesh.merge(mesh_to_export(o, true));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -5226,24 +5334,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);
|
||||
|
@ -5251,6 +5374,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));
|
||||
|
@ -5260,6 +5396,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()
|
||||
|
@ -5716,7 +5853,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
|
||||
|
@ -6143,6 +6280,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(); }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -86,7 +86,8 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
|||
|
||||
// Print config values
|
||||
double layer_height = print_config.opt_float("layer_height");
|
||||
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
|
||||
assert(! print_config.option<ConfigOptionFloatOrPercent>("first_layer_height")->percent);
|
||||
double first_layer_height = print_config.opt_float("first_layer_height");
|
||||
double support_material_speed = print_config.opt_float("support_material_speed");
|
||||
double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed);
|
||||
double bridge_speed = print_config.opt_float("bridge_speed");
|
||||
|
|
412
src/slic3r/GUI/ProjectDirtyStateManager.cpp
Normal file
412
src/slic3r/GUI/ProjectDirtyStateManager.cpp
Normal file
|
@ -0,0 +1,412 @@
|
|||
#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();
|
||||
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
|
||||
|
96
src/slic3r/GUI/ProjectDirtyStateManager.hpp
Normal file
96
src/slic3r/GUI/ProjectDirtyStateManager.hpp
Normal 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_
|
||||
|
|
@ -1178,7 +1178,7 @@ void Selection::render_center(bool gizmo_is_dragging) const
|
|||
if (!m_valid || is_empty() || m_quadric == nullptr)
|
||||
return;
|
||||
|
||||
Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center();
|
||||
const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center();
|
||||
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
|
@ -1247,7 +1247,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const
|
|||
} else {
|
||||
glsafe(::glTranslated(center(0), center(1), center(2)));
|
||||
if (requires_local_axes()) {
|
||||
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
|
||||
const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
|
||||
glsafe(::glMultMatrixd(orient_matrix.data()));
|
||||
}
|
||||
}
|
||||
|
@ -1935,7 +1935,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
|
|||
if (pos == std::string::npos)
|
||||
return;
|
||||
|
||||
double min_z = std::stod(field.substr(pos + 1));
|
||||
const double min_z = std::stod(field.substr(pos + 1));
|
||||
|
||||
// extract type
|
||||
field = field.substr(0, pos);
|
||||
|
@ -1943,7 +1943,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
|
|||
if (pos == std::string::npos)
|
||||
return;
|
||||
|
||||
int type = std::stoi(field.substr(pos + 1));
|
||||
const int type = std::stoi(field.substr(pos + 1));
|
||||
|
||||
const BoundingBoxf3& box = get_bounding_box();
|
||||
|
||||
|
@ -1954,8 +1954,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
|
|||
|
||||
// view dependend order of rendering to keep correct transparency
|
||||
bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward();
|
||||
float z1 = camera_on_top ? min_z : max_z;
|
||||
float z2 = camera_on_top ? max_z : min_z;
|
||||
const float z1 = camera_on_top ? min_z : max_z;
|
||||
const float z2 = camera_on_top ? max_z : min_z;
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue