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
ec9c3891cf
79 changed files with 3357 additions and 2245 deletions
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
@ -687,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -788,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
|
||||
|
@ -4051,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
|
||||
{
|
||||
|
@ -4068,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) {
|
||||
|
@ -4174,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();
|
||||
};
|
||||
|
||||
|
@ -4199,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;
|
||||
};
|
||||
|
||||
|
@ -4223,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))
|
||||
|
@ -4254,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) {
|
||||
|
@ -4276,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
|
||||
|
@ -4287,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; }
|
||||
|
@ -4296,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; }
|
||||
}
|
||||
|
@ -4310,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
|
||||
|
@ -4334,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"));
|
||||
|
@ -4366,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"));
|
||||
|
@ -4392,6 +4532,10 @@ void GCodeViewer::render_legend() const
|
|||
}
|
||||
}
|
||||
}
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
if (need_scrollable)
|
||||
ImGui::EndChild();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -4417,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;
|
||||
|
@ -4430,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)
|
||||
{
|
||||
|
@ -4445,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;
|
||||
}
|
||||
|
@ -4463,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();
|
||||
|
||||
|
@ -4482,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();
|
||||
|
||||
|
@ -4498,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();
|
||||
|
@ -4514,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: {
|
||||
|
@ -4537,6 +4712,11 @@ void GCodeViewer::render_legend() const
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_SCROLLABLE_LEGEND
|
||||
if (need_scrollable)
|
||||
ImGui::EndChild();
|
||||
#endif // ENABLE_SCROLLABLE_LEGEND
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4680,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();
|
||||
|
@ -4693,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;
|
||||
|
@ -4708,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();
|
||||
}
|
||||
|
@ -4727,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; }
|
||||
|
|
|
@ -696,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
|
||||
|
|
|
@ -3162,7 +3162,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();
|
||||
|
||||
|
@ -3185,7 +3185,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();
|
||||
|
||||
|
@ -4785,8 +4785,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());
|
||||
|
@ -4794,6 +4802,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();
|
||||
}
|
||||
|
@ -4834,8 +4846,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());
|
||||
|
||||
|
@ -5940,7 +5951,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 ) {
|
||||
|
@ -5963,7 +5974,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)
|
||||
|
@ -5971,7 +5982,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.
|
||||
|
@ -5980,8 +5991,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));
|
||||
}
|
||||
}
|
||||
|
@ -6206,7 +6217,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));
|
||||
|
|
|
@ -474,6 +474,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"
|
||||
|
||||
|
@ -633,8 +635,17 @@ void GUI_App::post_init()
|
|||
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
|
||||
this->mainframe->load_config_file(this->init_params->load_configs.back());
|
||||
// If loading a 3MF file, the config is loaded from the last one.
|
||||
if (! this->init_params->input_files.empty())
|
||||
this->plater()->load_files(this->init_params->input_files, true, true);
|
||||
if (!this->init_params->input_files.empty()) {
|
||||
const std::vector<size_t> res = this->plater()->load_files(this->init_params->input_files, true, true);
|
||||
if (!res.empty() && this->init_params->input_files.size() == 1) {
|
||||
// Update application titlebar when opening a project file
|
||||
const std::string& filename = this->init_params->input_files.front();
|
||||
if (boost::algorithm::iends_with(filename, ".amf") ||
|
||||
boost::algorithm::iends_with(filename, ".amf.xml") ||
|
||||
boost::algorithm::iends_with(filename, ".3mf"))
|
||||
this->plater()->set_project_filename(filename);
|
||||
}
|
||||
}
|
||||
if (! this->init_params->extra_config.empty())
|
||||
this->mainframe->load_config(this->init_params->extra_config);
|
||||
}
|
||||
|
@ -1632,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 +
|
||||
|
@ -1672,6 +1687,11 @@ 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
|
||||
|
@ -2121,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,
|
||||
|
@ -276,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
|
||||
|
|
|
@ -2476,28 +2476,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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -643,7 +643,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
|
|||
if (sla_print_technology)
|
||||
m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times);
|
||||
else {
|
||||
auto print_mode_stat = m_gcode_result->time_statistics.modes.front();
|
||||
auto print_mode_stat = m_gcode_result->print_statistics.modes.front();
|
||||
m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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*/{
|
||||
|
|
|
@ -1175,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;
|
||||
|
@ -1206,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);
|
||||
|
@ -1360,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()
|
||||
|
@ -1626,8 +1627,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;
|
||||
|
||||
|
@ -1722,7 +1723,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;
|
||||
|
||||
|
@ -1798,15 +1799,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);
|
||||
|
@ -3850,17 +3843,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
|
||||
|
@ -4244,9 +4237,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())
|
||||
|
@ -5850,7 +5843,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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
@ -3150,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())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue