Compare commits

..
Author SHA1 Message Date
ExPikaPaka c017629d11 Release GL textures, menus and handlers the GUI was leaking
The filament legend held its texture id in a function local static while the
loader it calls generates a new texture every time, and BitmapCache deletes none,
so a dual nozzle printer leaked one texture per filament per frame while the
preview was open, along with an SVG read and a rasterize per frame. The same
pattern sat in menu_item_with_icon. Textures are now cached on what actually
varies and released with the rest of the ImGui resources. The canvas reuses
frames by hashing the draw commands, which include the texture id, so a fresh id
every frame also kept frame skipping from ever triggering.

TriangleSelectorPatch did not release its VAO and buffers in the destructor,
so every gizmo close leaked them. The base class already issues GL deletes on
that same path, so this adds no new context requirement.

bind_event_handlers() installed three lambdas that unbind_event_handlers() could
not remove, because wx matches handlers by functor address. They are member
functions now, so bind and unbind are symmetric, and the view switch stops
stacking handlers.

The five selection dependent menus were allocated with new on every popup and
never owned. PopupMenu is synchronous and none of them is used as a submenu, so
a single owner is enough.

remove_notification_of_type stopped at the first match, so clear_all() left the
later instances of multi-instance types alive with object ids from a project that
is gone. The measure gizmo cleared its raycaster map but not the map holding one
full mesh decomposition per volume.
2026-10-01 09:15:48 +02:00
16 changed files with 325 additions and 132 deletions
+7 -24
View File
@@ -19,7 +19,7 @@ void PrintTryCancel::operator()()
m_print->throw_if_canceled();
}
std::atomic<size_t> PrintStateBase::g_last_timestamp{0};
size_t PrintStateBase::g_last_timestamp = 0;
// Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects.
void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const
@@ -107,26 +107,11 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin
return path;
}
void PrintBase::set_status_callback(status_callback_type cb)
{
std::scoped_lock<std::mutex> lock(m_status_callback_mutex);
m_status_callback = std::move(cb);
}
// Returns a copy, so that the callback is invoked with m_status_callback_mutex released: the callback
// may block on the UI thread, which in turn may be assigning a new callback.
PrintBase::status_callback_type PrintBase::status_callback() const
{
std::scoped_lock<std::mutex> lock(m_status_callback_mutex);
return m_status_callback;
}
//BBS: move set_status from hpp to cpp
void PrintBase::set_status(int percent, const std::string &message, unsigned int flags, int warning_step) const
{
status_callback_type status_callback = this->status_callback();
if (status_callback)
status_callback(SlicingStatus(percent, message, flags, warning_step));
if (m_status_callback)
m_status_callback(SlicingStatus(percent, message, flags, warning_step));
else
BOOST_LOG_TRIVIAL(debug) <<boost::format("Percent %1%: %2%\n")%percent %message.c_str();
}
@@ -134,10 +119,9 @@ void PrintBase::set_status(int percent, const std::string &message, unsigned in
void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel warning_level,
const std::string &message, const PrintObjectBase* print_object, PrintStateBase::SlicingNotificationType message_id)
{
status_callback_type status_callback = this->status_callback();
if (status_callback) {
if (this->m_status_callback) {
auto status = print_object ? SlicingStatus(*print_object, step, message, message_id, warning_level) : SlicingStatus(*this, step, message, message_id, warning_level);
status_callback(status);
m_status_callback(status);
}
else if (! message.empty())
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Print warning: %1%\n")% message.c_str();
@@ -148,9 +132,8 @@ void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel wa
const std::string& message, PrintObjectBase &object, PrintStateBase::SlicingNotificationType message_id)
{
//BBS: add object it into slicing status
status_callback_type status_callback = this->status_callback();
if (status_callback) {
status_callback(SlicingStatus(object, step, message, message_id, warning_level));
if (this->m_status_callback) {
m_status_callback(SlicingStatus(object, step, message, message_id, warning_level));
}
else if (!message.empty())
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", PrintObject warning: %1%\n")% message.c_str();
+8 -11
View File
@@ -99,9 +99,10 @@ public:
};
protected:
// The last timestamp is shared between all the Print & SLAPrint instances, and Orca keeps one Print
// per PartPlate, so it is incremented under different state mutexes: it has to be atomic.
static std::atomic<size_t> g_last_timestamp;
//FIXME last timestamp is shared between Print & SLAPrint,
// and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp
// is not synchronized!
static size_t g_last_timestamp;
};
// To be instantiated over PrintStep or PrintObjectStep enums.
@@ -472,12 +473,11 @@ public:
};
typedef std::function<void(const SlicingStatus&)> status_callback_type;
// Default status console print out in the form of percent => message.
void set_status_default() { this->set_status_callback(nullptr); }
void set_status_default() { m_status_callback = nullptr; }
// No status output or callback whatsoever, useful mostly for automatic tests.
void set_status_silent() { this->set_status_callback([](const SlicingStatus&){}); }
// Register a custom status callback. Called from the UI thread while the worker thread may be
// invoking the previous callback, therefore guarded by m_status_callback_mutex.
void set_status_callback(status_callback_type cb);
void set_status_silent() { m_status_callback = [](const SlicingStatus&){}; }
// Register a custom status callback.
void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
// Calls a registered callback to update the status, or print out the default message.
void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT, int warning_step = -1) const;
@@ -563,10 +563,7 @@ protected:
std::string m_plate_name;
// Callback to be evoked regularly to update state of the UI thread.
// Guarded by m_status_callback_mutex, always invoke the copy returned by status_callback().
status_callback_type m_status_callback;
mutable std::mutex m_status_callback_mutex;
status_callback_type status_callback() const;
private:
std::atomic<CancelStatus> m_cancel_status;
+2
View File
@@ -55,6 +55,8 @@ public:
static bool parse_color(const std::string& scolor, unsigned char* rgb_out);
static bool parse_color4(const std::string& scolor, unsigned char* rgba_out);
// Rasterizes the SVG into a freshly generated GL texture; the caller owns it and has to delete
// it (ImGuiWrapper::svg_texture() caches the result for the whole session).
static bool load_from_svg_file_change_color(const std::string &filename, unsigned width, unsigned height, ImTextureID &texture_id, const char *hexColor);
+28 -11
View File
@@ -3245,7 +3245,8 @@ void GLCanvas3D::load_sla_preview()
void GLCanvas3D::bind_event_handlers()
{
if (m_canvas != nullptr) {
// Every view switch binds, so binding twice would run each handler twice per event.
if (m_canvas != nullptr && !m_event_handlers_bound) {
m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
@@ -3255,9 +3256,9 @@ void GLCanvas3D::bind_event_handlers()
m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
m_toolbar_highlighter.set_timer_owner(m_canvas, 0);
m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); });
m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, &GLCanvas3D::on_toolbar_highlighter_timer, this);
m_gizmo_highlighter.set_timer_owner(m_canvas, 0);
m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); });
m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, &GLCanvas3D::on_gizmo_highlighter_timer, this);
m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
@@ -3272,14 +3273,7 @@ void GLCanvas3D::bind_event_handlers()
m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
m_canvas->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& evt) {
// The key-up that would commit a keyboard edit goes to whatever took the focus.
if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit();
ImGui::SetWindowFocus(nullptr);
render();
evt.Skip();
});
m_canvas->Bind(wxEVT_KILL_FOCUS, &GLCanvas3D::on_kill_focus, this);
m_event_handlers_bound = true;
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
@@ -3317,6 +3311,9 @@ void GLCanvas3D::unbind_event_handlers()
m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
m_canvas->Unbind(wxEVT_KILL_FOCUS, &GLCanvas3D::on_kill_focus, this);
m_canvas->Unbind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, &GLCanvas3D::on_toolbar_highlighter_timer, this);
m_canvas->Unbind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, &GLCanvas3D::on_gizmo_highlighter_timer, this);
m_event_handlers_bound = false;
m_canvas->Unbind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
@@ -4893,6 +4890,26 @@ void GLCanvas3D::on_set_focus(wxFocusEvent& evt)
m_is_touchpad_navigation = wxGetApp().app_config->get_bool("camera_navigation_style");
}
void GLCanvas3D::on_kill_focus(wxFocusEvent& evt)
{
// The key-up that would commit a keyboard edit goes to whatever took the focus.
if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit();
ImGui::SetWindowFocus(nullptr);
render();
evt.Skip();
}
void GLCanvas3D::on_toolbar_highlighter_timer(wxTimerEvent& evt)
{
m_toolbar_highlighter.blink();
}
void GLCanvas3D::on_gizmo_highlighter_timer(wxTimerEvent& evt)
{
m_gizmo_highlighter.blink();
}
bool GLCanvas3D::clicked_button_matches_action(const wxMouseEvent& evt, const MouseAction action, const std::map<MouseButton, MouseAction>& mappings) const
{
MouseButton clicked = MouseButton::None;
+3
View File
@@ -1133,6 +1133,9 @@ public:
void on_gesture(wxGestureEvent& evt);
void on_paint(wxPaintEvent& evt);
void on_set_focus(wxFocusEvent& evt);
void on_kill_focus(wxFocusEvent& evt);
void on_toolbar_highlighter_timer(wxTimerEvent& evt);
void on_gizmo_highlighter_timer(wxTimerEvent& evt);
void force_set_focus();
enum class MouseButton { None, Left, Middle, Right };
+12 -6
View File
@@ -2057,9 +2057,15 @@ wxMenu* MenuFactory::instance_menu()
return &m_instance_menu;
}
MenuWithSeparators* MenuFactory::new_transient_menu()
{
m_transient_menu = std::make_unique<MenuWithSeparators>();
return m_transient_menu.get();
}
wxMenu* MenuFactory::layer_menu()
{
MenuWithSeparators* menu = new MenuWithSeparators();
MenuWithSeparators* menu = new_transient_menu();
append_menu_item_settings(menu);
return menu;
@@ -2085,13 +2091,13 @@ wxMenu* MenuFactory::multi_selection_menu()
}
if (all_plates) {
wxMenu* menu = new MenuWithSeparators();
wxMenu* menu = new_transient_menu();
append_menu_item_replace_all_with_stl(menu);
return menu;
}
if (undefined_type)
return nullptr;
wxMenu* menu = new MenuWithSeparators();
wxMenu* menu = new_transient_menu();
if (!multi_volume) {
int index = 0;
if (obj_list()->can_merge_to_multipart_object()) {
@@ -2165,7 +2171,7 @@ wxMenu* MenuFactory::assemble_multi_selection_menu()
// show this menu only for Objects(Instances mixed with Objects)/Volumes selection
return nullptr;
wxMenu* menu = new MenuWithSeparators();
wxMenu* menu = new_transient_menu();
append_menu_item_set_visible(menu);
//append_menu_item_fix_through_cgal(menu);
//append_menu_item_simplify(menu);
@@ -2211,7 +2217,7 @@ wxMenu* MenuFactory::plate_menu()
wxMenu* MenuFactory::assemble_object_menu()
{
wxMenu* menu = new MenuWithSeparators();
wxMenu* menu = new_transient_menu();
// Set Visible
append_menu_item_set_visible(menu);
// Delete
@@ -2231,7 +2237,7 @@ wxMenu* MenuFactory::assemble_object_menu()
wxMenu* MenuFactory::assemble_part_menu()
{
wxMenu* menu = new MenuWithSeparators();
wxMenu* menu = new_transient_menu();
append_menu_item_set_visible(menu);
append_menu_item_delete(menu);
+10
View File
@@ -2,6 +2,7 @@
#define slic3r_GUI_Factories_hpp_
#include <map>
#include <memory>
#include <vector>
#include <array>
#include <cstddef>
@@ -120,6 +121,12 @@ private:
MenuWithSeparators m_assemble_part_menu;
wxMenu m_filament_action_menu;
// The selection dependent menus are rebuilt for every popup, so they cannot be members that
// outlive a build like the ones above; this owns the current one and destroys the previous.
// One slot is enough because PopupMenu() is synchronous: the menu a caller was handed is gone
// from the screen before anything can ask for the next one.
std::unique_ptr<MenuWithSeparators> m_transient_menu;
// Removed/Prepended Items according to the view mode
@@ -127,6 +134,9 @@ private:
std::array<wxMenuItem*, mtCount> items_decrease;
std::array<wxMenuItem*, mtCount> items_set_number_of_copies;
// Replaces m_transient_menu with an empty menu and returns it.
MenuWithSeparators* new_transient_menu();
void create_default_menu();
void create_common_object_menu(wxMenu *menu);
void create_object_menu();
-5
View File
@@ -6693,11 +6693,6 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
void ObjectList::OnEditingDone(wxDataViewEvent &event)
{
// ~wxDataViewCtrl ends the in-place editing, so this handler runs while ~Plater is already tearing
// the Plater down. Nothing below may touch the Plater or the plates any more.
if (wxGetApp().is_closing())
return;
if (event.GetColumn() != colName)
return;
+4
View File
@@ -2272,6 +2272,10 @@ void GLGizmoMeasure::update_measurement_result()
void GLGizmoMeasure::reset_all_pick()
{
std::map<GLVolume*, std::shared_ptr<PickRaycaster>>().swap(m_mesh_raycaster_map);
// register_single_mesh_pick() fills both maps in lockstep, so the measurings have to go with
// the raycasters; otherwise the entries keyed on the GLVolumes of the previous selection stay
// behind for the rest of the session.
std::map<GLVolume*, std::shared_ptr<Measure::Measuring>>().swap(m_mesh_measure_map);
reset_gripper_pick(GripperType::UNDEFINE,true);
}
+4 -3
View File
@@ -27,8 +27,7 @@ enum class PainterGizmoType {
FDM_SUPPORTS,
SEAM,
MM_SEGMENTATION,
FUZZY_SKIN,
TEXTURE_DISPLACEMENT
FUZZY_SKIN
};
class TriangleSelectorGUI : public TriangleSelector {
@@ -102,7 +101,9 @@ class TriangleSelectorPatch : public TriangleSelectorGUI {
public:
explicit TriangleSelectorPatch(const TriangleMesh& mesh, const std::vector<ColorRGBA> ebt_colors, float edge_limit = 0.6f)
: TriangleSelectorGUI(mesh, edge_limit), m_ebt_colors(ebt_colors) {}
virtual ~TriangleSelectorPatch() = default;
// Releases the VAO and the per-patch VBOs built by finalize_triangle_indices(). The base class
// already deletes GL buffers from its GLModel members here, so this needs no context of its own.
virtual ~TriangleSelectorPatch() { release_geometry(); }
// Render current selection. Transformation matrices are supposed
// to be already set.
+45 -9
View File
@@ -361,6 +361,7 @@ ImGuiWrapper::~ImGuiWrapper()
{
//destroy_fonts_texture();
destroy_font();
destroy_svg_textures();
ImGui::DestroyContext();
}
@@ -542,6 +543,12 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt)
return ret;
}
// SVG icons rasterized into GL textures, keyed on file name, size and recolor. Cleared as a whole
// from new_frame() once it grows past MAX_SVG_TEXTURES, which is safe there: the previous frame has
// been rendered and the frame about to be recorded asks for every icon it draws again.
static std::map<std::string, ImTextureID> s_svg_textures;
static const size_t MAX_SVG_TEXTURES = 256;
void ImGuiWrapper::new_frame()
{
if (m_new_frame_open) {
@@ -552,6 +559,11 @@ void ImGuiWrapper::new_frame()
init_font(true);
}
// Recolored icons accumulate one texture per color the session has shown; drop them before
// anything references them again. This frame recreates the handful it actually draws.
if (s_svg_textures.size() > MAX_SVG_TEXTURES)
destroy_svg_textures();
ImGuiIO& io = ImGui::GetIO();
ImGui::NewFrame();
@@ -1804,8 +1816,7 @@ bool menu_item_with_icon(const char *label, const char *shortcut, ImVec2 icon_si
if (icon_color != 0)
ImGui::RenderFrame(icon_pos, icon_pos + icon_size, icon_color);
else {
static ImTextureID transparent;
IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/transparent.svg", icon_size.x, icon_size.y, transparent);
ImTextureID transparent = ImGuiWrapper::svg_texture(Slic3r::resources_dir() + "/images/transparent.svg", icon_size.x, icon_size.y);
window->DrawList->AddImage(transparent, icon_pos, icon_pos + icon_size, { 0,0 }, { 1,1 }, ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, 1.f)));
}
}
@@ -2631,11 +2642,7 @@ void ImGuiWrapper::push_toolbar_style(const float scale)
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.00f)); // 10
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f)); // 11
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, COL_GREEN_LIGHT); // 12
// The checkbox/radio frame behind this is drawn fully transparent (see FrameBg above,
// alpha 0), showing the light window background through it - a white check mark there is
// invisible. Dark mode doesn't have this problem (its window background is dark), so only
// this branch needs a check mark color with real contrast against a light background.
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.f, 156 / 255.f, 136 / 255.f, 1.00f));//13
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));//13
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
@@ -3389,6 +3396,36 @@ bool ImGuiWrapper::display_initialized() const
return io.DisplaySize.x >= 0.0f && io.DisplaySize.y >= 0.0f;
}
ImTextureID ImGuiWrapper::svg_texture(const std::string& filename, unsigned width, unsigned height, const char* hex_color)
{
std::string key = filename + "|" + std::to_string(width) + "x" + std::to_string(height);
if (hex_color != nullptr)
key += std::string("|") + hex_color;
const auto it = s_svg_textures.find(key);
if (it != s_svg_textures.end())
return it->second;
ImTextureID texture_id = nullptr;
const bool loaded = (hex_color != nullptr) ?
BitmapCache::load_from_svg_file_change_color(filename, width, height, texture_id, hex_color) :
IMTexture::load_from_svg_file(filename, width, height, texture_id);
if (!loaded)
return nullptr;
s_svg_textures.emplace(std::move(key), texture_id);
return texture_id;
}
void ImGuiWrapper::destroy_svg_textures()
{
for (const auto& texture : s_svg_textures) {
GLuint texture_id = (GLuint)(intptr_t)texture.second;
glsafe(::glDeleteTextures(1, &texture_id));
}
s_svg_textures.clear();
}
void ImGuiWrapper::destroy_font()
{
if (m_font_texture != 0) {
@@ -3468,7 +3505,6 @@ void ImGuiWrapper::filament_group(const std::string& filament_type, const char*
//ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
std::string id = std::to_string(static_cast<unsigned int> (filament_id + 1));
ImDrawList* draw_list = ImGui::GetWindowDrawList();
static ImTextureID transparent;
ImVec2 text_size = ImGui::CalcTextSize(filament_type.c_str());
// BBS image sizing based on text width (DPI scaling)
float img_width = ImGui::CalcTextSize("ABC").x;
@@ -3481,7 +3517,7 @@ void ImGuiWrapper::filament_group(const std::string& filament_type, const char*
if (rgba[3] == 0x00) {
svg_path = "/images/outlined_rect_transparent.svg";
}
BitmapCache::load_from_svg_file_change_color(Slic3r::resources_dir() + svg_path, img_size.x, img_size.y, transparent, hex_color);
ImTextureID transparent = svg_texture(Slic3r::resources_dir() + svg_path, img_size.x, img_size.y, hex_color);
ImGui::BeginGroup();
{
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
+8
View File
@@ -104,6 +104,14 @@ public:
// Hash of every draw list's vertices, indices and commands.
static ImGuiID draw_data_signature(const ImDrawData* draw_data);
// A GL texture holding an SVG icon rasterized at width x height, optionally recolored.
// Rasterizing an SVG is far too expensive to redo for every frame that draws the icon, and the
// texture the previous frame generated would leak, so the result is kept until the frame that
// finds the cache overgrown drops it (and rebuilds only what it still draws).
static ImTextureID svg_texture(const std::string& filename, unsigned width, unsigned height, const char* hex_color = nullptr);
// Deletes every texture svg_texture() handed out. Requires a current GL context.
static void destroy_svg_textures();
float scaled(float x) const { return x * m_font_size; }
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
/// <summary>
+2 -1
View File
@@ -2225,11 +2225,12 @@ void NotificationManager::close_and_delete_self(PopNotification * self)
}
void NotificationManager::remove_notification_of_type(const NotificationType type) {
// Seven notification types may have several instances alive at once, so erase every match:
// stopping at the first one leaves the rest (and the ObjectIDs they hold) behind.
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
std::unique_ptr<PopNotification> &notification = *it;
if (notification->get_type() == type) {
it = m_pop_notifications.erase(it);
break;
} else
++it;
}
-13
View File
@@ -3572,8 +3572,6 @@ void PartPlate::update_slice_result_valid_state(bool valid)
//update current slice context into backgroud slicing process
void PartPlate::update_slice_context(BackgroundSlicingProcess & process)
{
//this callback outlives the call, so it is dropped again in PartPlateList::clear() and
//PartPlateList::delete_plate() before the plate is destroyed
auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus& status) {
Slic3r::SlicingStatusEvent *event = new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status);
//BBS: GUI refactor: add plate info befor message
@@ -4545,14 +4543,7 @@ void PartPlateList::clear(bool delete_plates, bool release_print_list, bool exce
else
plate->clear();
if (delete_plates)
{
//the slicing status callback installed by update_slice_context() captures the plate, so drop it
//while the Print is still alive: the prints are only released below, after this loop, and are
//not released at all when release_print_list is false.
if (Print* print = plate->fff_print())
print->set_status_default();
delete plate;
}
}
if (delete_plates)
@@ -4895,10 +4886,6 @@ int PartPlateList::delete_plate(int index)
//destroy the print object
int print_index;
plate->get_print(nullptr, nullptr, &print_index);
//the slicing status callback installed by update_slice_context() captures the plate, and destroy_print()
//frees the Print, so drop the callback here, the last point where both are still alive.
if (Print* print = plate->fff_print())
print->set_status_default();
destroy_print(print_index);
delete plate;
+192 -47
View File
@@ -103,6 +103,7 @@
#include "Selection.hpp"
#include "GLToolbar.hpp"
#include "GUI_Preview.hpp"
#include "UVEditorCanvas.hpp"
#include "3DBed.hpp"
#include "PartPlate.hpp"
#include "Camera.hpp"
@@ -1797,11 +1798,14 @@ bool Sidebar::priv::switch_diameter_to(const wxString &diameter)
Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
// The combo lists printer variants, and the variant of a mixed-nozzle machine ("0.4+0.6") is no
// single extruder's diameter, so the preset's own variant answers first.
if (printer_preset.config.opt_string("printer_variant") == diameter.ToStdString()) {
const std::string &printer_variant = printer_preset.config.opt_string("printer_variant");
if (printer_variant == diameter.ToStdString()) {
return true;
}
// A named variant ("0.4 High Flow") shares its diameter with the standard profile, which selecting
// the plain diameter switches back to, so only a preset naming no variant is kept by its diameter.
auto* nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printer_preset.config.option("nozzle_diameter"));
if (nozzle_diameter && nozzle_diameter->size() > 0) {
if (printer_variant.empty() && nozzle_diameter && nozzle_diameter->size() > 0) {
auto current_nozzle_dia = get_diameter_string(nozzle_diameter->values[0]);
// If the selected diameter is the same as current nozzle, don't switch profiles
if (current_nozzle_dia == diameter.ToStdString()) {
@@ -2236,12 +2240,14 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
std::string machine_print_name = obj->get_show_printer_type();
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle);
Preset* machine_preset = get_printer_preset(obj);
if (!machine_preset) {
const bool optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type);
const bool optional_target_model = DevPrinterConfigUtil::is_optional_printer_model_id(target_model_id);
Preset* machine_preset = optional_printer_model ? nullptr : get_printer_preset(obj);
if (!optional_printer_model && !optional_target_model && !machine_preset) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty";
return false;
}
if (machine_print_name != target_model_id) {
if (!optional_printer_model && !optional_target_model && machine_print_name != target_model_id) {
MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n"
"Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO);
if (dlg.ShowModal() == wxID_NO) {
@@ -2433,6 +2439,11 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
return;
}
if (DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type)) {
clear_all_sync_status();
return;
}
bool printer_synced = false;
// 1. update printer status
const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
@@ -3926,13 +3937,18 @@ void Sidebar::update_presets(Preset::Type preset_type)
combo_flow->Show(combo_flow->GetCount() > 0);
};
auto update_extruder_diameter = [&diameters, &nozzle_diameter](int extruder_index,ExtruderGroup & extruder) {
auto update_extruder_diameter = [&diameters, &nozzle_diameter, &diameter](int extruder_index,ExtruderGroup & extruder) {
extruder.combo_diameter->Clear();
if (extruder_index >= int(nozzle_diameter->values.size()))
return;
int select = -1;
// ORCA get the actual nozzle diameter from printer config
auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]);
// Named variants such as "0.4HS" and "0.4 High Flow" share a physical diameter.
// Retain the variant selection unless the diameter was customized.
const bool keep_variant = diameter.substr(0, diameter.find_first_not_of("0123456789.")) == nozzle_dia &&
std::find(diameters.begin(), diameters.end(), diameter) != diameters.end();
const std::string &selected_variant = keep_variant ? diameter : nozzle_dia;
// ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias
if(!diameters.empty() && diameters[0].empty() && !nozzle_dia.empty()){
diameters[0] = nozzle_dia;
@@ -3942,7 +3958,7 @@ void Sidebar::update_presets(Preset::Type preset_type)
diameters.push_back(nozzle_dia);
}
for (size_t i = 0; i < diameters.size(); ++i) {
if (diameters[i] == nozzle_dia)
if (diameters[i] == selected_variant)
select = extruder.combo_diameter->GetCount();
extruder.combo_diameter->Append(diameters[i], {});
}
@@ -6066,11 +6082,30 @@ void Sidebar::load_ams_list(MachineObject* obj)
filament_ams_list = build_filament_ams_list(obj);
}
bool device_change = false;
const std::string& device = obj ? obj->get_dev_id() : "";
if (p->ams_list_device != device) {
const bool same_device = p->ams_list_device == device;
// Keep sync metadata out of the device payload, but preserve it across a
// subscription refresh when the physical filament in a slot is unchanged.
// Otherwise the refreshed configs differ only by the missing
// filament_changed key, causing combo boxes to rebuild and lose their
// transient post-sync badges.
auto &previous_filament_ams_list = wxGetApp().preset_bundle->filament_ams_list;
for (auto &entry : filament_ams_list) {
auto previous = previous_filament_ams_list.find(entry.first);
const auto *previous_changed = previous == previous_filament_ams_list.end() ? nullptr :
dynamic_cast<const ConfigOptionBool *>(previous->second.option("filament_changed"));
if (!same_device || previous_changed == nullptr ||
previous->second.opt_string("filament_id", 0u) != entry.second.opt_string("filament_id", 0u)) {
continue;
}
entry.second.set_key_value("filament_changed",
new ConfigOptionBool{previous_changed->value});
}
bool device_change = !same_device;
if (device_change) {
p->ams_list_device = device;
device_change = true;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size();
if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change)
@@ -6080,9 +6115,27 @@ void Sidebar::load_ams_list(MachineObject* obj)
wxGetApp().preset_bundle->filament_ams_list = filament_ams_list;
for (auto c : p->combos_filament){
c->set_sync_badge(false);
c->update();
if (device_change) {
c->ShowBadge(false);//change printer,then clear badge
}
if (!device_change) {
size_t combo_index = 0;
for (const auto &entry : filament_ams_list) {
const auto &tray = entry.second;
const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
tray.opt_bool("filament_slot_placeholder", 0u);
if (!has_filament && !is_placeholder) {
continue;
}
if (combo_index >= p->combos_filament.size()) {
break;
}
const auto *filament_changed = dynamic_cast<const ConfigOptionBool *>(tray.option("filament_changed"));
p->combos_filament[combo_index]->set_sync_badge(
has_filament && !is_placeholder && filament_changed != nullptr && filament_changed->value);
++combo_index;
}
}
@@ -6256,18 +6309,32 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") :
_L("Filament type and color information have been synchronized, but slot information is not included.");
c->SetToolTip(tip);
c->ShowBadge(true);
c->set_sync_badge(true);
};
{ // badge ams filament
clear_combos_filament_badge();
if (sync_result.direct_sync) {
// Orca: PresetBundle::sync_ams_list rebuilds combos_filament
// 1:1 from the AMS trays that produce a combo (loaded trays + placeholders; non-placeholder
// empty trays are skipped), so every resulting combo is AMS-sourced and gets a badge. The
// previous per-tray index walked the full filament_ams_list (including the skipped empties),
// so an empty slot before a loaded one dropped the badge for the trailing filaments.
for (auto &c : p->combos_filament) {
badge_combox_filament(c);
// A placeholder contributes a preserved project filament to the
// overwrite result, but it is not AMS-sourced and must not get a
// sync badge. Non-placeholder empty trays are omitted entirely.
size_t combo_index = 0;
for (const auto &entry : wxGetApp().preset_bundle->filament_ams_list) {
const auto &tray = entry.second;
const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
tray.opt_bool("filament_slot_placeholder", 0u);
if (!has_filament && !is_placeholder) {
continue;
}
if (combo_index >= p->combos_filament.size()) {
break;
}
if (is_placeholder) {
p->combos_filament[combo_index]->set_sync_badge(false);
} else {
badge_combox_filament(p->combos_filament[combo_index]);
}
++combo_index;
}
}
}
@@ -6535,6 +6602,11 @@ template<typename T> void setup_dialog_position(T& info)
void Sidebar::pop_sync_nozzle_and_ams_dialog() {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog";
auto agent = wxGetApp().getAgent();
if (!agent || agent->get_filament_sync_mode() == FilamentSyncMode::none) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament synchronization is not supported; skipping dialog";
return;
}
wxTheApp->CallAfter([this]() {
SyncNozzleAndAmsDialog::InputInfo temp_na_info;
wxPoint big_btn_pt;
@@ -6666,17 +6738,14 @@ void Sidebar::clear_combos_filament_badge()
{
auto &combos_filament = p->combos_filament;
for (auto &c : combos_filament) { // clear flag
c->ShowBadge(false);
c->set_sync_badge(false);
}
}
void Sidebar::udpate_combos_filament_badge() {
auto &combos_filament = p->combos_filament;
for (auto &c : combos_filament) {
auto selection = c->GetSelection();
auto select_flag = c->GetFlag(selection);
auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS;
c->ShowBadge(ok);
c->update_badge_according_flag();
}
}
@@ -7048,6 +7117,13 @@ struct Plater::priv
GLToolbar collapse_toolbar;
Preview *preview;
AssembleView* assemble_view { nullptr };
// Docked/resizable 2D pane showing GLGizmoTextureDisplacement's LSCM unwrap of a painted
// patch; a sibling AUI pane alongside "sidebar"/"main", not part of the view3D/preview/
// assemble_view sizer - see its registration below and Plater::get_uv_editor_canvas(). The
// pane hosts the panel (toolbar + canvas + status line); uv_editor_canvas is its inner canvas,
// cached so the gizmo can reach it directly.
UVEditorPanel* uv_editor_panel { nullptr };
UVEditorCanvas* uv_editor_canvas { nullptr };
bool first_enter_assemble{ true };
std::unique_ptr<NotificationManager> notification_manager;
@@ -7297,6 +7373,8 @@ struct Plater::priv
void undo();
void redo();
// True, and tells the user, while a background job is working on the model - see the definition.
bool undo_redo_blocked_by_job();
void undo_redo_to(size_t time_to_load);
// BBS: backup
@@ -7750,6 +7828,26 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
.BottomDockable(false)
.BestSize(wxSize(39 * wxGetApp().em_unit(), 90 * wxGetApp().em_unit())));
// UV editor pane for GLGizmoTextureDisplacement's LSCM unwrap preview - a resizable/dockable
// sibling of "sidebar"/"main" like everything else registered on this same AUI manager, not a
// change to the view3D/preview/assemble_view sizer above. Hidden by default: only relevant
// while that gizmo is active with a layer using the "Unwrap (LSCM)" projection method (see
// Plater::show_uv_editor()), so it stays out of the way of everyone else's window layout.
uv_editor_panel = new UVEditorPanel(q);
uv_editor_canvas = uv_editor_panel->canvas();
m_aui_mgr.AddPane(uv_editor_panel, wxAuiPaneInfo()
.Name("uv_editor")
.Caption(_L("UV Editor"))
.Right()
.Hide()
.BestSize(wxSize(40 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit())));
// Closing the pane with its own X has to reach the gizmo, or its next update would simply show the pane again.
q->Bind(wxEVT_AUI_PANE_CLOSE, [this](wxAuiManagerEvent &evt) {
evt.Skip();
if (evt.GetPane() != nullptr && evt.GetPane()->window == uv_editor_panel && uv_editor_canvas != nullptr)
uv_editor_canvas->run_command(UVEditorCanvas::Command::PaneClosed);
});
auto* panel_sizer = new wxBoxSizer(wxHORIZONTAL);
panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0);
panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0);
@@ -7791,6 +7889,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
BOOST_LOG_TRIVIAL(info) << "Removed floating AUI state from saved window layout for Wayland";
}
// The UV editor is a transient, gizmo-driven pane (see show_uv_editor()); a saved layout
// from a session that happened to close with it open would otherwise restore it visible on
// startup, with nothing painted in it. Force it hidden here so it only ever appears when the
// texture-displacement gizmo asks for it.
if (wxAuiPaneInfo &uv_pane = m_aui_mgr.GetPane("uv_editor"); uv_pane.IsOk())
uv_pane.Hide();
sidebar_layout.is_collapsed = !sidebar.IsShown();
}
@@ -10639,15 +10744,13 @@ void Plater::priv::reset(bool apply_presets_change, bool reload_presets)
m_worker.cancel_all();
// Stop and reset the Print content. m_worker.cancel_all() only stops the UI jobs, so this has to
// happen before reinit() deletes the plates together with the Print the slicing thread may still
// be working on.
this->background_process.reset();
//BBS: clear the partplate list's object before object cleared
partplate_list.reinit();
partplate_list.update_slice_context_to_current_plate(background_process);
preview->update_gcode_result(partplate_list.get_current_slice_result());
// Stop and reset the Print content.
this->background_process.reset();
model.clear_objects();
// clear_objects() only drops the ModelObjects; the CAD recipe is Model-level state and would
// otherwise be written into every project saved for the rest of the session.
@@ -12663,7 +12766,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
sidebar->auto_calc_flushing_volumes(idx);
}
auto select_flag = combo->GetFlag(selection);
combo->ShowBadge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS);
combo->set_sync_badge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS);
q->on_filament_change(idx);
}
bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
@@ -14935,8 +15038,25 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
}
// A background job holds the model it is working on: the texture displacement bake, for one, hands its
// result to the volume when it finishes, and it was queued against the geometry as it was at the time.
// Undoing while it runs restores an older state under it - a different transform, a different mesh -
// and the result then lands on geometry it was never computed for. Undo and redo therefore wait for
// the job, and say so rather than doing nothing.
bool Plater::priv::undo_redo_blocked_by_job()
{
if (m_worker.is_idle())
return false;
notification_manager->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
_u8L("Cannot undo or redo while an operation is running. Stop it first."));
return true;
}
void Plater::priv::undo()
{
if (this->undo_redo_blocked_by_job())
return;
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
// BBS: undo-redo until modify record
@@ -14954,6 +15074,8 @@ void Plater::priv::undo()
void Plater::priv::redo()
{
if (this->undo_redo_blocked_by_job())
return;
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
// BBS: undo-redo until modify record
@@ -14992,11 +15114,6 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
// Make sure that no updating function calls take_snapshot until we are done.
SuppressSnapshots snapshot_supressor(q);
// Loading a snapshot deletes every PartPlate, which the slicing thread keeps dereferencing (its
// current plate and the status callback). Cancel it and wait for it to finish before the jump,
// update_after_undo_redo() re-applies the background process to the rebuilt plates afterwards.
this->background_process.stop();
bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active();
PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology;
bool printer_technology_changed = this->printer_technology != new_printer_technology;
@@ -16604,7 +16721,7 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
/// --- scale ---
/// -- scale --
// model is created for a 0.4 nozzle, scale z with nozzle size.
const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter");
std::vector<int> extruder_types = printer_config->option<ConfigOptionEnumsGeneric>("extruder_type")->values;
@@ -21081,6 +21198,33 @@ GLCanvas3D* Plater::get_assmeble_canvas3D()
return nullptr;
}
UVEditorCanvas* Plater::get_uv_editor_canvas()
{
return p->uv_editor_canvas;
}
void Plater::show_uv_editor(bool show)
{
if (p->uv_editor_panel == nullptr)
return;
const wxAuiPaneInfo &pane = p->m_aui_mgr.GetPane(p->uv_editor_panel);
if (!pane.IsOk() || pane.IsShown() == show)
return;
// Deferred, because GLGizmoTextureDisplacement calls this from its ImGui panel - that is, from
// the middle of the 3D canvas's GL frame. Showing an AUI pane re-lays out the window and
// delivers the resulting size/paint events synchronously, and the UV canvas painting itself
// makes its own surface current in the app's *shared* GL context, which mid-frame is the one
// the 3D canvas is drawing into. Doing the layout once the frame is over avoids that entirely.
CallAfter([this, show]() {
wxAuiPaneInfo &deferred_pane = p->m_aui_mgr.GetPane(p->uv_editor_panel);
if (!deferred_pane.IsOk() || deferred_pane.IsShown() == show)
return;
deferred_pane.Show(show);
p->m_aui_mgr.Update();
});
}
GLCanvas3D* Plater::get_current_canvas3D(bool exclude_preview)
{
return p->get_current_canvas3D(exclude_preview);
@@ -21342,9 +21486,14 @@ bool Plater::is_same_printer_for_connected_and_selected(bool popup_warning)
}
if (!check_printer_initialized(obj, true, popup_warning))
return false;
Preset * machine_preset = get_printer_preset(obj);
if (!machine_preset)
const std::string machine_model = obj->printer_type;
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
const std::string selected_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string();
if (!DevPrinterConfigUtil::is_optional_printer_model_id(machine_model) &&
!DevPrinterConfigUtil::is_optional_printer_model_id(selected_model) &&
!get_printer_preset(obj)) {
return false;
}
if (wxGetApp().is_blocking_printing()) {
if (popup_warning) {
@@ -22429,11 +22578,6 @@ int Plater::delete_plate(int plate_index)
if (plate_index == -1)
index = p->partplate_list.get_curr_plate_index();
// Orca: delete_plate() destroys the plate's Print and GCodeResult, which the slicing thread is
// still working on, so it has to be stopped first. can_delete_plate() also refuses while slicing,
// but the plate grabber in the 3D scene does not go through it.
p->background_process.stop();
take_snapshot("delete partplate");
ret = p->partplate_list.delete_plate(index);
@@ -22763,7 +22907,7 @@ bool Plater::can_delete() const { return p->can_delete(); }
bool Plater::can_delete_all() const { return p->can_delete_all(); }
bool Plater::can_add_model() const { return !is_background_process_slicing(); }
bool Plater::can_add_plate() const { return !is_background_process_slicing() && p->can_add_plate(); }
bool Plater::can_delete_plate() const { return !is_background_process_slicing() && p->can_delete_plate(); }
bool Plater::can_delete_plate() const { return p->can_delete_plate(); }
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); }
bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); }
@@ -22820,8 +22964,9 @@ bool Plater::can_copy_to_clipboard() const
return true;
}
bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_undo_snapshot(); }
bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_redo_snapshot(); }
// The job check keeps the buttons in step with priv::undo()/redo(), which refuse while one runs.
bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_undo_snapshot(); }
bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
//BBS
bool Plater::can_fillcolor() const { return p->can_fillcolor(); }
-2
View File
@@ -1025,8 +1025,6 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU
std::vector<std::string> previous_gcode_paths;
plate_list.get_sliced_result(previous_slice_result, previous_gcode_paths);
// The plates are dereferenced by the slicing thread, which the caller
// (Plater::priv::undo_redo_to) has stopped before loading the snapshot.
plate_list.reset(false);
this->load_mutable_object<Slic3r::GUI::PartPlateList>(plate_list.id(), plate_list);
plate_list.rebuild_plates_after_deserialize(previous_slice_result, previous_gcode_paths);