mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 21:42:43 +00:00
Merge branch 'main' into feature/bambu_printer_housekeeping
# Conflicts: # tests/slic3rutils/CMakeLists.txt
This commit is contained in:
+280
-16
@@ -1,15 +1,25 @@
|
||||
#include "Plater.hpp"
|
||||
#include "../Utils/NetworkAgent.hpp"
|
||||
#include "../Utils/NetworkAgentFactory.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <numeric>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <slic3r/plugin/PluginDescriptor.hpp>
|
||||
#include <slic3r/plugin/PluginManager.hpp>
|
||||
#include <slic3r/plugin/PluginResolver.hpp>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/iterator/counting_iterator.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
@@ -21,6 +31,7 @@
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
@@ -37,6 +48,8 @@
|
||||
#include <wx/debug.h>
|
||||
#include <wx/busyinfo.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/evtloop.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/wrapsizer.h>
|
||||
#ifdef _WIN32
|
||||
#include <wx/richtooltip.h>
|
||||
@@ -277,6 +290,21 @@ void Plater::show_illegal_characters_warning(wxWindow* parent)
|
||||
show_error(parent, _L("Invalid name, the following characters are not allowed:") + " <>:/\\|?*\"");
|
||||
}
|
||||
|
||||
void Plater::mark_plate_toolbar_image_dirty()
|
||||
{
|
||||
m_b_plate_toolbar_image_dirty = true;
|
||||
}
|
||||
|
||||
bool Plater::is_plate_toolbar_image_dirty() const
|
||||
{
|
||||
return m_b_plate_toolbar_image_dirty;
|
||||
}
|
||||
|
||||
void Plater::clear_plate_toolbar_image_dirty()
|
||||
{
|
||||
m_b_plate_toolbar_image_dirty = false;
|
||||
}
|
||||
|
||||
static std::map<BedType, std::string> bed_type_thumbnails = {
|
||||
{BedType::btPC, "bed_cool" },
|
||||
{BedType::btEP, "bed_engineering" },
|
||||
@@ -3199,6 +3227,7 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
|
||||
auto p_mainframe = wxGetApp().mainframe;
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
const bool use_native_device_tab = preset_bundle.use_bbl_device_tab() || NetworkAgentFactory::is_current_printer_agent_plugin();
|
||||
|
||||
if (preset_bundle.use_bbl_network()) {
|
||||
//only show connection button for not-BBL printer
|
||||
@@ -3235,7 +3264,8 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
print_btn_type = preset_bundle.is_bbl_vendor() ? MainFrame::PrintSelectType::ePrintPlate : MainFrame::PrintSelectType::eSendGcode;
|
||||
}
|
||||
|
||||
p_mainframe->load_printer_url(url, apikey);
|
||||
if (!use_native_device_tab)
|
||||
p_mainframe->load_printer_url(url, apikey);
|
||||
|
||||
|
||||
p_mainframe->set_print_button_to_default(print_btn_type);
|
||||
@@ -3305,8 +3335,7 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
update_printer_thumbnail();
|
||||
}
|
||||
|
||||
// Orca:: show device tab based on vendor type
|
||||
p_mainframe->show_device(preset_bundle.use_bbl_device_tab());
|
||||
p_mainframe->show_device(use_native_device_tab);
|
||||
p_mainframe->m_tabpanel->SetSelection(p_mainframe->m_tabpanel->GetSelection());
|
||||
}
|
||||
|
||||
@@ -5201,6 +5230,14 @@ struct Plater::priv
|
||||
bool m_ignore_event{false};
|
||||
bool m_slice_all{false};
|
||||
bool m_is_slicing {false};
|
||||
// Missing-plugin set signatures (sorted full refs joined by '\n'), one per notification. They
|
||||
// gate plugin-load re-validation and avoid needlessly recreating the notification when the set
|
||||
// is unchanged. Whether missing plugins block slicing is derived directly from PluginResolver
|
||||
// (has_missing_plugins()), not cached here.
|
||||
std::string m_local_missing_shown_sig;
|
||||
std::string m_cloud_missing_shown_sig;
|
||||
std::string m_inactive_shown_sig;
|
||||
std::string m_broken_shown_sig;
|
||||
bool auto_reslice_pending {false};
|
||||
bool auto_reslice_after_cancel {false};
|
||||
bool m_is_publishing {false};
|
||||
@@ -5992,6 +6029,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_OPEN_SPEED_DIAL, [this](SimpleEvent&) {
|
||||
if (this->q->is_view3D_shown())
|
||||
wxGetApp().open_speed_dial();
|
||||
});
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int>& evt)
|
||||
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
|
||||
@@ -6326,10 +6367,8 @@ void Plater::priv::update(unsigned int flags)
|
||||
//BBS assemble view
|
||||
this->assemble_view->reload_scene(false, flags);
|
||||
|
||||
if (current_panel && is_preview_shown()) {
|
||||
q->force_update_all_plate_thumbnails();
|
||||
//update_fff_scene_only_shells(true);
|
||||
}
|
||||
// todo: better to mark thumbnail dirty here
|
||||
q->mark_plate_toolbar_image_dirty();
|
||||
|
||||
if (force_background_processing_restart)
|
||||
this->restart_background_process(update_status);
|
||||
@@ -7884,6 +7923,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
}
|
||||
q->schedule_background_process(true);
|
||||
q->mark_plate_toolbar_image_dirty();
|
||||
return obj_idxs;
|
||||
}
|
||||
|
||||
@@ -8263,6 +8303,8 @@ void Plater::priv::object_list_changed()
|
||||
main_frame->update_slice_print_status(MainFrame::eEventObjectUpdate, can_slice);
|
||||
|
||||
wxGetApp().params_panel()->notify_object_config_changed();
|
||||
|
||||
q->mark_plate_toolbar_image_dirty();
|
||||
}
|
||||
|
||||
void Plater::priv::select_curr_plate_all()
|
||||
@@ -9166,6 +9208,8 @@ void Plater::priv::update_fff_scene()
|
||||
view3D->reload_scene(true);
|
||||
//BBS: add assemble view related logic
|
||||
assemble_view->reload_scene(true);
|
||||
|
||||
q->mark_plate_toolbar_image_dirty();
|
||||
}
|
||||
|
||||
//BBS: add print project related logic
|
||||
@@ -10074,9 +10118,6 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice)
|
||||
preview->get_canvas3d()->enable_select_plate_toolbar(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
preview->get_canvas3d()->enable_select_plate_toolbar(false);
|
||||
}
|
||||
|
||||
if (current_panel == panel)
|
||||
{
|
||||
@@ -10935,6 +10976,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":finished, reload print soon");
|
||||
m_is_slicing = false;
|
||||
this->preview->reload_print(false);
|
||||
q->mark_plate_toolbar_image_dirty();
|
||||
/* BBS if in publishing progress */
|
||||
if (m_is_publishing) {
|
||||
if (m_publish_dlg && !m_publish_dlg->was_cancelled()) {
|
||||
@@ -11146,8 +11188,13 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
|
||||
update_sidebar();
|
||||
int old_sel = e.GetOldSelection();
|
||||
if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->use_bbl_device_tab() && new_sel == MainFrame::tpMonitor) {
|
||||
if (!Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
const bool is_printer_agent_plugin = NetworkAgentFactory::is_current_printer_agent_plugin();
|
||||
const bool use_native_device_tab = wxGetApp().preset_bundle &&
|
||||
(wxGetApp().preset_bundle->use_bbl_device_tab() || is_printer_agent_plugin);
|
||||
if (use_native_device_tab && new_sel == MainFrame::tpMonitor) {
|
||||
// BBL network module is only required for BBL-vendor printers.
|
||||
// Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it.
|
||||
if (!is_printer_agent_plugin && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
e.Veto();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") % old_sel % new_sel;
|
||||
if (q) {
|
||||
@@ -13929,8 +13976,13 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i
|
||||
_obj->config.set_key_value("seam_slope_type", new ConfigOptionEnum<SeamScarfType>(SeamScarfType::None));
|
||||
_obj->config.set_key_value("gap_fill_target", new ConfigOptionEnum<GapFillTarget>(GapFillTarget::gftNowhere));
|
||||
print_config->set_key_value("max_volumetric_extrusion_rate_slope", new ConfigOptionFloat(0));
|
||||
// ORCA: print the top surface spiral from the center outwards, so the tiles are comparable.
|
||||
_obj->config.set_key_value("top_surface_fill_order", new ConfigOptionEnum<SurfaceFillOrder>(SurfaceFillOrder::Outward));
|
||||
// ORCA: request the calibration's special toolpath order (chords first, center spiral
|
||||
// last and inside-out) so opposing directions collide into the tactile lip the test
|
||||
// reads. The special order only applies while the fill order is Default, so reset the
|
||||
// profile's fill order on the calibration objects; changing the setting on the object
|
||||
// afterwards deliberately overrides the special order.
|
||||
_obj->config.set_key_value("calib_flowrate_topinfill_special_order", new ConfigOptionBool(true));
|
||||
_obj->config.set_key_value("top_surface_fill_order", new ConfigOptionEnum<SurfaceFillOrder>(SurfaceFillOrder::Default));
|
||||
|
||||
// extract flowrate from name, filename format: flowrate_xxx
|
||||
std::string obj_name = _obj->name;
|
||||
@@ -14667,6 +14719,7 @@ void Plater::invalid_all_plate_thumbnails()
|
||||
plate->thumbnail_data.reset();
|
||||
plate->no_light_thumbnail_data.reset();
|
||||
}
|
||||
mark_plate_toolbar_image_dirty();
|
||||
}
|
||||
|
||||
void Plater::force_update_all_plate_thumbnails()
|
||||
@@ -14677,7 +14730,6 @@ void Plater::force_update_all_plate_thumbnails()
|
||||
invalid_all_plate_thumbnails();
|
||||
update_all_plate_thumbnails(true);
|
||||
}
|
||||
get_preview_canvas3D()->update_plate_thumbnails();
|
||||
}
|
||||
|
||||
// BBS: backup
|
||||
@@ -16749,7 +16801,19 @@ void Plater::reslice()
|
||||
// and notify user that he should leave it first.
|
||||
if (get_view3D_canvas3D()->get_gizmos_manager().is_in_editing_mode(true))
|
||||
return;
|
||||
|
||||
|
||||
// Enforce the missing-plugin block at the slicing choke point: menu/keyboard/queued triggers can
|
||||
// carry a stale enabled state while plugins load asynchronously. refresh_missing_plugin_block
|
||||
// rebuilds the missing sets and notifications from the active presets without running
|
||||
// Print::validate; all other validation keeps upstream behavior and is surfaced by
|
||||
// update_background_process() below.
|
||||
if (refresh_missing_plugin_block()) {
|
||||
p->partplate_list.get_curr_plate()->update_slice_ready_status(false);
|
||||
p->main_frame->update_slice_print_status(MainFrame::eEventPlateUpdate, false);
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": required plugins missing; slicing blocked.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the running (and queued) UI jobs and only proceed if they actually
|
||||
// get stopped.
|
||||
unsigned timeout_ms = 10000;
|
||||
@@ -18680,12 +18744,212 @@ void Plater::validate_current_plate(bool& model_fits, bool& validate_error)
|
||||
}*/
|
||||
}
|
||||
|
||||
// Missing-plugin validation (both technologies): block slicing while the active preset(s)
|
||||
// reference plugin capabilities that are not installed/loadable here. The helper rebuilds the
|
||||
// missing sets, manages the notifications, and reports whether slicing is blocked. plugins_block_changed
|
||||
// is set when the block toggled, so the Slice button can be refreshed below.
|
||||
bool plugins_block_changed = false;
|
||||
if (refresh_missing_plugin_block(&plugins_block_changed)) {
|
||||
model_fits = false;
|
||||
validate_error = true;
|
||||
}
|
||||
|
||||
PartPlate* part_plate = p->partplate_list.get_curr_plate();
|
||||
part_plate->update_slice_ready_status(model_fits);
|
||||
|
||||
// The toolbar Slice button is normally refreshed only by the canvas
|
||||
// (EVT_GLCANVAS_ENABLE_ACTION_BUTTONS) on geometry updates. When the missing-plugin block toggles
|
||||
// without a geometry change (e.g. a plugin finishing loading, or a setting edit that drops the
|
||||
// last missing plugin), refresh it here — AFTER update_slice_ready_status set the plate's
|
||||
// can_slice() flag that get_enable_slice_status() reads — so the button doesn't lag until the
|
||||
// next bed click.
|
||||
if (plugins_block_changed && !p->background_process.running())
|
||||
p->main_frame->update_slice_print_status(MainFrame::eEventObjectUpdate, model_fits);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool Plater::refresh_missing_plugin_block(bool* block_toggled)
|
||||
{
|
||||
// PluginResolver owns the per-preset-type missing sets; rebuild them from each preset's own
|
||||
// "plugins" manifest so the state is always fresh. A plugin is resolved when it is
|
||||
// installed/loaded, or when no active setting references it any more (refresh drops it then).
|
||||
// The block is derived solely from PluginResolver; snapshot it before the refresh to detect a
|
||||
// toggle (so the caller can refresh the Slice button).
|
||||
const bool was_blocked = has_missing_plugins() || has_inactive_plugins() || has_broken_plugins();
|
||||
refresh_missing_plugins(*wxGetApp().preset_bundle);
|
||||
|
||||
const auto missing_refs = [](const std::vector<MissingPlugin>& missing) {
|
||||
std::vector<std::string> refs;
|
||||
refs.reserve(missing.size());
|
||||
for (const MissingPlugin& m : missing)
|
||||
refs.emplace_back(create_full_ref(m.ref));
|
||||
return refs;
|
||||
};
|
||||
|
||||
const auto signature = [&missing_refs](const std::vector<MissingPlugin>& missing) {
|
||||
std::vector<std::string> refs = missing_refs(missing);
|
||||
std::sort(refs.begin(), refs.end());
|
||||
std::string sig;
|
||||
for (const std::string& r : refs) { sig += r; sig += '\n'; }
|
||||
return sig;
|
||||
};
|
||||
|
||||
// Show/refresh the non-closable notification for one missing set. Only (re)create it when the
|
||||
// set changes; pushing every validate would close+recreate it (flicker, reset hover) since
|
||||
// validate runs on many triggers. shown_sig also gates plugin-load re-validation.
|
||||
const auto update = [&](NotificationType type, const std::vector<MissingPlugin>& missing,
|
||||
std::string* shown_sig, const std::string& header,
|
||||
const std::string& resolve_label,
|
||||
std::function<bool(wxEvtHandler*)> resolve_action) {
|
||||
if (missing.empty()) {
|
||||
if (!shown_sig->empty()) {
|
||||
p->notification_manager->close_notification_of_type(type);
|
||||
shown_sig->clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const std::string sig = signature(missing);
|
||||
if (*shown_sig != sig) {
|
||||
std::vector<JumpTo> body;
|
||||
for (const auto& m : missing)
|
||||
body.emplace_back(JumpTo{m.ref.capability_name, m.opt, m.opt_type});
|
||||
|
||||
p->notification_manager->push_plugin_missing_notification(
|
||||
type, header, resolve_label, std::move(body), std::move(resolve_action));
|
||||
*shown_sig = sig;
|
||||
}
|
||||
};
|
||||
|
||||
const std::vector<MissingPlugin> missing_cloud = get_missing_cloud_plugins();
|
||||
const std::vector<MissingPlugin> missing_local = get_missing_local_plugins();
|
||||
const std::vector<std::string> missing_cloud_refs = missing_refs(missing_cloud);
|
||||
const std::vector<std::string> missing_local_refs = missing_refs(missing_local);
|
||||
|
||||
update(NotificationType::OrcaCloudPluginMissingError, missing_cloud,
|
||||
&p->m_cloud_missing_shown_sig,
|
||||
_u8L("OrcaCloud plugins required by the current preset are not installed:"),
|
||||
_u8L("Install Plugins"),
|
||||
[this, missing_cloud_refs](wxEvtHandler*) { install_missing_cloud_plugins(missing_cloud_refs); return false; });
|
||||
// "Find on OrcaCloud" is only a suggestion: it opens the browser but cannot resolve the missing
|
||||
// plugin in-session, so it never closes the notification or unblocks slicing. The user resolves a
|
||||
// local plugin by installing it or by changing the setting that needs it.
|
||||
update(NotificationType::OrcaLocalPluginMissingError, missing_local,
|
||||
&p->m_local_missing_shown_sig,
|
||||
_u8L("Local plugins required by the current preset are missing:"),
|
||||
_u8L("Find on OrcaCloud"),
|
||||
[missing_local_refs](wxEvtHandler*) { open_missing_plugins_on_cloud(missing_local_refs); return false; });
|
||||
|
||||
const std::vector<MissingPlugin> inactive = get_inactive_plugins();
|
||||
const std::vector<MissingPlugin> broken = get_broken_plugins();
|
||||
const std::vector<std::string> inactive_refs = missing_refs(inactive);
|
||||
const std::vector<std::string> broken_refs = missing_refs(broken);
|
||||
|
||||
update(NotificationType::OrcaPluginInactiveError, inactive,
|
||||
&p->m_inactive_shown_sig,
|
||||
_u8L("Plugins required by the current preset are not activated:"),
|
||||
_u8L("Activate Now"),
|
||||
[this, inactive_refs](wxEvtHandler*) { enable_inactive_plugins(inactive_refs); return false; });
|
||||
update(NotificationType::OrcaPluginCapabilityUnavailableError, broken,
|
||||
&p->m_broken_shown_sig,
|
||||
_u8L("The installed plugin does not provide the required capability — it may be outdated:"),
|
||||
_u8L("Find on OrcaCloud"),
|
||||
[broken_refs](wxEvtHandler*) { open_missing_plugins_on_cloud(broken_refs); return false; });
|
||||
|
||||
const bool blocked = has_missing_plugins() || has_inactive_plugins() || has_broken_plugins();
|
||||
if (block_toggled)
|
||||
*block_toggled = (was_blocked != blocked);
|
||||
return blocked;
|
||||
}
|
||||
|
||||
void Plater::revalidate_current_plate_if_plugins_missing()
|
||||
{
|
||||
// Only do work while a missing-plugin notification is up, so the plugin-load hook does not
|
||||
// trigger a full validation for every plugin that loads during normal startup/use.
|
||||
if (p->m_local_missing_shown_sig.empty() && p->m_cloud_missing_shown_sig.empty() &&
|
||||
p->m_inactive_shown_sig.empty() && p->m_broken_shown_sig.empty())
|
||||
return;
|
||||
bool model_fits = true, validate_error = false;
|
||||
validate_current_plate(model_fits, validate_error);
|
||||
}
|
||||
|
||||
void Plater::install_missing_cloud_plugins(const std::vector<std::string>& cloud_refs)
|
||||
{
|
||||
if (cloud_refs.empty())
|
||||
return;
|
||||
|
||||
// Shared between the UI-thread dialog/timer and the resolver's worker thread.
|
||||
struct InstallProgressState
|
||||
{
|
||||
std::atomic<bool> cancel{false};
|
||||
std::atomic<bool> finished{false};
|
||||
std::atomic<bool> torn_down{false};
|
||||
std::mutex mtx;
|
||||
std::string message;
|
||||
};
|
||||
auto state = std::make_shared<InstallProgressState>();
|
||||
state->message = _u8L("Preparing to install plugins...");
|
||||
|
||||
wxWindow* parent = wxGetApp().mainframe;
|
||||
auto* dialog = new wxProgressDialog(_L("Installing plugins"), from_u8(state->message), 100,
|
||||
parent, wxPD_APP_MODAL | wxPD_CAN_ABORT);
|
||||
dialog->Pulse();
|
||||
|
||||
// UI-thread timer: animate the pulse, observe the Cancel button, and tear down when the worker
|
||||
// signals completion. The timer is deleted via CallAfter so it is never freed inside its own
|
||||
// handler.
|
||||
auto* timer = new wxTimer();
|
||||
timer->Bind(wxEVT_TIMER, [this, dialog, timer, state](wxTimerEvent&) {
|
||||
std::string msg;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mtx);
|
||||
msg = state->message;
|
||||
}
|
||||
// Once cancellation is requested, the in-flight plugin still has to finish; reflect that.
|
||||
if (state->cancel)
|
||||
msg = _u8L("Cancelling — finishing the current plugin...");
|
||||
|
||||
if (!dialog->Pulse(from_u8(msg)))
|
||||
state->cancel = true;
|
||||
|
||||
// Tear down exactly once: Stop() prevents further ticks, but guard so a stale queued tick
|
||||
// can never double-Destroy the dialog or double-delete the timer.
|
||||
if (state->finished && !state->torn_down.exchange(true)) {
|
||||
timer->Stop();
|
||||
dialog->Destroy();
|
||||
wxGetApp().CallAfter([timer]() { delete timer; });
|
||||
revalidate_current_plate_if_plugins_missing();
|
||||
}
|
||||
});
|
||||
timer->Start(100);
|
||||
|
||||
PluginInstallProgress progress;
|
||||
progress.on_plugin_begin = [state](const std::string& name, std::size_t /*index*/, std::size_t /*total*/) {
|
||||
std::lock_guard<std::mutex> lock(state->mtx);
|
||||
state->message = (boost::format(_u8L("Installing %1%...")) % name).str();
|
||||
};
|
||||
progress.is_cancelled = [state]() { return state->cancel.load(); };
|
||||
progress.on_finished = [state]() { state->finished = true; };
|
||||
|
||||
resolve_missing_plugins(cloud_refs, std::move(progress));
|
||||
}
|
||||
|
||||
void Plater::enable_inactive_plugins(const std::vector<std::string>& refs)
|
||||
{
|
||||
if (refs.empty())
|
||||
return;
|
||||
// Local and instant — load the plugin and/or enable the capability. The plugin-load callback
|
||||
// re-validates the plate and clears (or reclassifies) the notification; no progress dialog needed.
|
||||
resolve_inactive_plugins(refs);
|
||||
}
|
||||
|
||||
bool Plater::plugins_block_slicing() const
|
||||
{
|
||||
// Single source of truth: slicing is blocked while PluginResolver still has unresolved plugin
|
||||
// references — missing (download), inactive (activate), or broken (capability unavailable).
|
||||
return has_missing_plugins() || has_inactive_plugins() || has_broken_plugins();
|
||||
}
|
||||
|
||||
void Plater::open_platesettings_dialog(wxCommandEvent& evt) {
|
||||
int plate_index = evt.GetInt();
|
||||
PlateSettingsDialog dlg(this, _L("Plate Settings"), evt.GetString() == "only_layer_sequence");
|
||||
|
||||
Reference in New Issue
Block a user