Compare commits

..
Author SHA1 Message Date
Rodrigo Faselli 3baacc6b75 Add memory header to GUI_Factories.cpp 2026-10-06 10:25:49 -03:00
Rodrigo Faselli 1a0af2fe57 Merge branch 'main' into fix/gui-resource-leaks 2026-10-06 10:18:20 -03:00
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
35 changed files with 200 additions and 124 deletions
-10
View File
@@ -352,16 +352,6 @@ void FillSpiralInset::_fill_surface_single(const FillParams& params,
assert(params.use_arachne);
assert(this->print_config != nullptr && this->print_object_config != nullptr);
// Internal solid infill must not add Arachne's standalone thin walls: one of those walls can
// become the extra centre point after the spiral has finished. Top and bottom surfaces keep
// Arachne, including the centre plug handled by generate_spiral_insets().
if (params.extrusion_role == erSolidInfill) {
Polylines polylines;
this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines);
append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled<coord_t>(this->spacing)));
return;
}
// Only a solid surface is worth the variable width walls; a sparse one falls back to plain loops.
if (params.density <= 0.9999f || params.dont_adjust) {
Polylines polylines;
-1
View File
@@ -16,7 +16,6 @@
namespace Slic3r::Geometry {
using VD = VoronoiDiagram;
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
-1
View File
@@ -20,7 +20,6 @@
namespace Slic3r::Geometry {
using VD = VoronoiDiagram;
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesIt = ColoredLines::iterator;
+16 -14
View File
@@ -10,6 +10,8 @@
#include "libslic3r/Point.hpp"
#include "libslic3r/libslic3r.h"
using VD = Slic3r::Geometry::VoronoiDiagram;
namespace Slic3r::Geometry {
// Represent trapezoid Voronoi cell around segment.
@@ -17,8 +19,8 @@ template<typename PT> struct SegmentCellRange
{
const PT source_segment_start_point; // The start point of the source segment of this cell.
const PT source_segment_end_point; // The end point of the source segment of this cell.
const VoronoiDiagram::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
const VoronoiDiagram::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends.
const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends.
SegmentCellRange() = delete;
explicit SegmentCellRange(const PT &source_segment_start_point, const PT &source_segment_end_point)
@@ -32,8 +34,8 @@ template<typename PT> struct SegmentCellRange
template<typename PT> struct PointCellRange
{
const PT source_point; // The source point of this cell.
const VoronoiDiagram::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
const VoronoiDiagram::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends.
const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends.
PointCellRange() = delete;
explicit PointCellRange(const PT &source_point) : source_point(source_point) {}
@@ -44,20 +46,20 @@ template<typename PT> struct PointCellRange
class VoronoiUtils
{
public:
static Vec2i64 to_point(const VoronoiDiagram::vertex_type *vertex);
static Vec2i64 to_point(const VD::vertex_type *vertex);
static Vec2i64 to_point(const VoronoiDiagram::vertex_type &vertex);
static Vec2i64 to_point(const VD::vertex_type &vertex);
static bool is_finite(const VoronoiDiagram::vertex_type &vertex);
static bool is_finite(const VD::vertex_type &vertex);
static VoronoiDiagram::vertex_type make_rotated_vertex(VoronoiDiagram::vertex_type &vertex, double angle);
static VD::vertex_type make_rotated_vertex(VD::vertex_type &vertex, double angle);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename std::iterator_traits<SegmentIterator>::reference>::type
get_source_segment(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
get_source_segment(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
@@ -71,7 +73,7 @@ public:
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Arachne::PolygonsPointIndex>::type
get_source_point_index(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
get_source_point_index(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
/**
* Discretize a parabola based on (approximate) step size.
@@ -111,7 +113,7 @@ public:
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Geometry::SegmentCellRange<
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
compute_segment_cell_range(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
/**
* Compute the range of line segments that surround a cell of the skeletal
@@ -138,19 +140,19 @@ public:
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Geometry::PointCellRange<
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
compute_point_cell_range(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
compute_point_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename T> static bool is_in_range(double value)
{
return double(std::numeric_limits<T>::lowest()) <= value && value <= double(std::numeric_limits<T>::max());
}
template<typename T> static bool is_in_range(const VoronoiDiagram::vertex_type &vertex)
template<typename T> static bool is_in_range(const VD::vertex_type &vertex)
{
return VoronoiUtils::is_finite(vertex) && is_in_range<T>(vertex.x()) && is_in_range<T>(vertex.y());
}
template<typename T> static bool is_in_range(const VoronoiDiagram::edge_type &edge)
template<typename T> static bool is_in_range(const VD::edge_type &edge)
{
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr)
return false;
@@ -63,7 +63,6 @@ namespace Slic3r { enum class EnforcerBlockerType : int8_t; }
namespace Slic3r {
using boost::polygon::voronoi_diagram;
using VD = Geometry::VoronoiDiagram;
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return {coord_t(point->x()), coord_t(point->y())}; }
-2
View File
@@ -80,8 +80,6 @@
#include <vector>
#include <utility>
namespace fs = boost::filesystem;
// Mark string for localization and translate.
#define L(s) Slic3r::I18N::translate(s)
+4 -2
View File
@@ -11,6 +11,8 @@
#include <boost/filesystem/path.hpp>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r {
class BBLProject;
@@ -206,7 +208,7 @@ public:
std::string task_profile_id; /* profile id*/
std::string task_name; /* task name, generally filename as task name */
std::string task_file; /* local full file path of 3mf or gcode */
boost::filesystem::path task_path; /* local path of 3mf or gcode */
fs::path task_path; /* local path of 3mf or gcode */
std::string task_gcode_in_3mf; /* gcode in 3mf */
std::string task_create_time; /* time created by cloud */
std::string task_thumbnail_url; /* url of task thumbnail */
@@ -324,7 +326,7 @@ public:
std::string project_url_md5; /* md5 of project url file */
std::string project_name;
std::string project_3mf_file;
boost::filesystem::path project_path;
fs::path project_path;
std::string project_content;
std::string project_country_code;
-1
View File
@@ -41,7 +41,6 @@
#define TOPBAR_TITLE_WIDTH 300
using namespace Slic3r;
using namespace Slic3r::GUI;
enum CUSTOM_ID
{
+2
View File
@@ -18,6 +18,8 @@ class wxMouseCaptureLostEvent;
class wxMouseEvent;
class wxWindow;
using namespace Slic3r::GUI;
class CenteredTitle : public wxControl
{
public:
+2 -2
View File
@@ -218,7 +218,7 @@ std::string BackgroundSlicingProcess::output_filepath_for_project(const boost::f
void BackgroundSlicingProcess::process_fff()
{
assert(m_print == m_fff_print);
PresetBundle& preset_bundle = *GUI::wxGetApp().preset_bundle;
PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
m_fff_print->is_BBL_printer() = preset_bundle.is_bbl_vendor();
// BBS: add the logic to process from an existed gcode file
if (m_print->finished()) {
@@ -727,7 +727,7 @@ StringObjectException BackgroundSlicingProcess::validate(std::vector<StringObjec
assert(m_print != nullptr);
assert(m_print == m_fff_print);
m_fff_print->is_BBL_printer() = GUI::wxGetApp().preset_bundle->is_bbl_vendor();
m_fff_print->is_BBL_printer() = wxGetApp().preset_bundle->is_bbl_vendor();
return m_print->validate(warnings, collison_polygons, height_polygons);
}
+2
View File
@@ -58,6 +58,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);
+3 -1
View File
@@ -43,6 +43,8 @@
#include <wx/types.h>
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
@@ -86,7 +88,7 @@ struct Bundle
// cache or its profile JSONs, whichever is usable.
// Returns false if not loaded. Reason for that is logged as boost::log error.
//BBS: set BBL as default
bool load(boost::filesystem::path dir, const std::string &vendor_name, bool is_in_resources, bool is_bbl_bundle = false);
bool load(fs::path dir, const std::string &vendor_name, bool is_in_resources, bool is_bbl_bundle = false);
const std::string& vendor_id() const { return vendor_profile->id; }
};
+5 -5
View File
@@ -3622,7 +3622,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
if (extruder_id + 1 != static_cast<unsigned char>(item.extruder))
continue;
if (item.type != CustomGCode::ColorChange)
if (item.type != ColorChange)
continue;
if (!zs_built) {
@@ -4789,10 +4789,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
ImGui::SameLine();
switch (custom_gcode.type) {
case CustomGCode::PausePrint: imgui.text(cgcode_pause_str); break;
case CustomGCode::Template: imgui.text(cgcode_template_str); break;
case CustomGCode::ToolChange: imgui.text(cgcode_toolchange_str); break;
case CustomGCode::Custom: imgui.text(cgcode_custom_str); break;
case PausePrint: imgui.text(cgcode_pause_str); break;
case Template: imgui.text(cgcode_template_str); break;
case ToolChange: imgui.text(cgcode_toolchange_str); break;
case Custom: imgui.text(cgcode_custom_str); break;
default: imgui.text(cgcode_unknown_str); break;
}
ImGui::SameLine(max_len);
+30 -20
View File
@@ -184,12 +184,12 @@ static Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR_DARK = { 0.329f, 0.329f, 0.353f,
static Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f };
static Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR_DARK = { 0.753f, 0.192f, 0.039f, 1.0f };
void Slic3r::GUI::GLCanvas3D::update_render_colors()
void GLCanvas3D::update_render_colors()
{
DEFAULT_BG_LIGHT_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_3D_Background]);
}
void Slic3r::GUI::GLCanvas3D::load_render_colors()
void GLCanvas3D::load_render_colors()
{
RenderColor::colors[RenderCol_3D_Background] = ImGuiWrapper::to_ImVec4(DEFAULT_BG_LIGHT_COLOR);
}
@@ -3416,7 +3416,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);
@@ -3426,9 +3427,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);
@@ -3443,14 +3444,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);
@@ -3488,6 +3482,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);
@@ -5005,6 +5002,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;
@@ -7970,16 +7987,9 @@ void GLCanvas3D::_render_fps_overlay(int fps) const
const float margin = 10.0f * get_scale();
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
ImVec2 pos(display_size.x - margin, margin);
// Last frame's size; zero until the overlay has been shown once.
const ImGuiWindow* self = ImGui::FindWindowByName("###fps_overlay");
const float left = pos.x - (self != nullptr ? self->Size.x : 0.0f);
// The Preview legend takes the top-right corner.
if (const ImGuiWindow* legend = ImGui::FindWindowByName("Legend"); m_canvas_type == ECanvasType::CanvasPreview && legend != nullptr && legend->Active)
pos = ImVec2(legend->Pos.x - margin, legend->Pos.y);
// The toolbar row can reach the corner on a narrow canvas; stack the overlay below it then.
else if (m_main_toolbar.is_enabled() &&
get_main_toolbar_offset() + m_main_toolbar.get_width() + m_separator_toolbar.get_width() + m_gizmos.get_scaled_total_width() + m_assemble_view_toolbar.get_width() > left)
pos.y = std::max(m_main_toolbar.get_height(), m_gizmos.get_scaled_total_height()) + margin;
ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.35f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * get_scale());
+3
View File
@@ -1203,6 +1203,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 };
-1
View File
@@ -309,7 +309,6 @@ typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS2)(
#include <gtk/gtk.h>
#endif
namespace fs = boost::filesystem;
using namespace std::literals;
namespace pt = boost::property_tree;
+13 -6
View File
@@ -25,6 +25,7 @@
#include <string>
#include <map>
#include <vector>
#include <memory>
#include "libslic3r/PrintConfig.hpp"
#include <wx/gdicmn.h>
#include "slic3r/GUI/wxExtensions.hpp"
@@ -2081,9 +2082,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;
@@ -2109,13 +2116,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()) {
@@ -2189,7 +2196,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);
@@ -2235,7 +2242,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
@@ -2255,7 +2262,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 <string>
#include <vector>
#include <array>
@@ -121,6 +122,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
@@ -128,6 +135,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();
+1 -1
View File
@@ -396,7 +396,7 @@ void Preview::sys_color_changed()
// m_layers_slider->sys_color_changed();
}
void Preview::on_tick_changed(CustomGCode::Type type)
void Preview::on_tick_changed(Type type)
{
//if (type == Type::PausePrint) {
// m_schedule_background_process();
+4
View File
@@ -2317,6 +2317,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
@@ -37,8 +37,7 @@ enum class PainterGizmoType {
FDM_SUPPORTS,
SEAM,
MM_SEGMENTATION,
FUZZY_SKIN,
TEXTURE_DISPLACEMENT
FUZZY_SKIN
};
class TriangleSelectorGUI : public TriangleSelector {
@@ -112,7 +111,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.
-2
View File
@@ -46,8 +46,6 @@
namespace Slic3r {
using namespace CustomGCode;
namespace GUI {
// equal to 25 mm2
+13 -12
View File
@@ -20,6 +20,7 @@ struct IMGUI_API ImRect;
namespace Slic3r {
using namespace CustomGCode;
class PrintObject;
class Layer;
@@ -88,8 +89,8 @@ public:
void SetSliderValues(const std::vector<double> &values);
void SetSliderAlternateValues(const std::vector<double> &values) { m_alternate_values = values; }
CustomGCode::Info GetTicksValues() const;
void SetTicksValues(const CustomGCode::Info &custom_gcode_per_print_z);
Info GetTicksValues() const;
void SetTicksValues(const Info &custom_gcode_per_print_z);
void SetLayersTimes(const std::vector<float> &layers_times, float total_time);
void SetLayersTimes(const std::vector<double> &layers_times);
@@ -97,8 +98,8 @@ public:
void SetDrawMode(DrawMode mode) { m_draw_mode = mode; }
//BBS
void SetExtraStyle(long style) { m_extra_style = style; }
void SetManipulationMode(CustomGCode::Mode mode) { m_mode = mode; }
CustomGCode::Mode GetManipulationMode() const { return m_mode; }
void SetManipulationMode(Mode mode) { m_mode = mode; }
Mode GetManipulationMode() const { return m_mode; }
void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder, bool can_change_color);
void SetExtruderColors(const std::vector<std::string> &extruder_colors);
@@ -116,8 +117,8 @@ public:
void UseDefaultColors(bool def_colors_on) { m_ticks.set_default_colors(def_colors_on); }
void on_mouse_wheel(wxMouseEvent& evt);
void post_ticks_changed_event(CustomGCode::Type type = CustomGCode::Unknown);
bool check_ticks_changed_event(CustomGCode::Type type);
void post_ticks_changed_event(Type type = Unknown);
bool check_ticks_changed_event(Type type);
bool switch_one_layer_mode();
void show_go_to_layer(bool show) { m_show_go_to_layer_dialog = show; }
@@ -129,9 +130,9 @@ public:
bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; }
void reset_post_tick_event(bool val = false) {
m_is_need_post_tick_changed_event = val;
m_tick_change_event_type = CustomGCode::Type::Unknown;
m_tick_change_event_type = Type::Unknown;
}
CustomGCode::Type get_post_tick_event_type() { return m_tick_change_event_type; }
Type get_post_tick_event_type() { return m_tick_change_event_type; }
float m_scale = 1.0;
void set_scale(float scale = 1.0);
@@ -142,7 +143,7 @@ public:
protected:
void add_custom_gcode(std::string custom_gcode);
void add_code_as_tick(CustomGCode::Type type, int selected_extruder = -1);
void add_code_as_tick(Type type, int selected_extruder = -1);
void delete_tick(const TickCode& tick);
void do_go_to_layer(size_t layer_number); //menu
void correct_lower_value();
@@ -155,7 +156,7 @@ protected:
void render_edit_menu(const TickCode& tick); //menu
void draw_background_and_groove(const ImRect& bg_rect, const ImRect& groove);
void draw_colored_band(const ImRect& groove, const ImRect& slideable_region);
void draw_custom_label_block(const ImVec2 anchor, CustomGCode::Type type);
void draw_custom_label_block(const ImVec2 anchor, Type type);
void draw_ticks(const ImRect& slideable_region);
void draw_tick_on_mouse_position(const ImRect& slideable_region);
void show_tooltip(const TickCode& tick); //menu
@@ -216,7 +217,7 @@ private:
void *m_delete_icon_id;
DrawMode m_draw_mode = dmRegular;
CustomGCode::Mode m_mode = CustomGCode::SingleExtruder;
Mode m_mode = SingleExtruder;
int m_only_extruder = -1;
long m_style;
@@ -231,7 +232,7 @@ private:
bool m_can_change_color;
std::string m_print_obj_idxs;
bool m_is_need_post_tick_changed_event { false };
CustomGCode::Type m_tick_change_event_type;
Type m_tick_change_event_type;
std::vector<double> m_alternate_values;
+44 -4
View File
@@ -385,6 +385,7 @@ ImGuiWrapper::~ImGuiWrapper()
{
//destroy_fonts_texture();
destroy_font();
destroy_svg_textures();
ImGui::DestroyContext();
}
@@ -566,6 +567,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) {
@@ -576,6 +583,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();
@@ -1828,8 +1840,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)));
}
}
@@ -3409,6 +3420,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) {
@@ -3488,7 +3529,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;
@@ -3501,7 +3541,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
@@ -108,6 +108,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>
+5 -3
View File
@@ -12,6 +12,8 @@
#include "Job.hpp"
#include "slic3r/GUI/DeviceCore/DevStorage.h"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
@@ -24,9 +26,9 @@ class PrintPrepareData
public:
bool is_from_plater = true;
int plate_idx;
boost::filesystem::path _3mf_path;
boost::filesystem::path _3mf_config_path;
boost::filesystem::path _temp_path;
fs::path _3mf_path;
fs::path _3mf_config_path;
fs::path _temp_path;
PrintPrepareData() {
plate_idx = 0;
}
+2 -1
View File
@@ -2250,11 +2250,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;
}
+3 -3
View File
@@ -8111,13 +8111,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_COLLAPSE_SIDEBAR, [this](SimpleEvent&) { this->q->collapse_sidebar(!this->q->is_sidebar_collapsed()); });
preview->get_wxglcanvas()->Bind(EVT_CUSTOMEVT_TICKSCHANGED, [this](wxCommandEvent& event) {
CustomGCode::Type tick_event_type = (CustomGCode::Type)event.GetInt();
Type tick_event_type = (Type)event.GetInt();
Model& model = wxGetApp().plater()->model();
//BBS: replace model custom gcode with current plate custom gcode
model.plates_custom_gcodes[model.curr_plate_index] = preview->get_canvas3d()->get_gcode_viewer().get_layers_slider()->GetTicksValues();
// BBS set to invalid state only
if (tick_event_type == CustomGCode::Type::ToolChange || tick_event_type == CustomGCode::Type::Custom || tick_event_type == CustomGCode::Type::Template || tick_event_type == CustomGCode::Type::PausePrint) {
if (tick_event_type == Type::ToolChange || tick_event_type == Type::Custom || tick_event_type == Type::Template || tick_event_type == Type::PausePrint) {
PartPlate *plate = this->q->get_partplate_list().get_curr_plate();
if (plate) {
plate->update_slice_result_valid_state(false);
@@ -20544,7 +20544,7 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r
// update customize gcode
for (auto item = p->model.plates_custom_gcodes.begin(); item != p->model.plates_custom_gcodes.end(); ++item) {
auto iter = std::remove_if(item->second.gcodes.begin(), item->second.gcodes.end(), [filament_id](const CustomGCode::Item& gcode_item) {
auto iter = std::remove_if(item->second.gcodes.begin(), item->second.gcodes.end(), [filament_id](const Item& gcode_item) {
return (gcode_item.type == CustomGCode::Type::ToolChange && gcode_item.extruder == filament_id + 1);
});
if (replace_filament_id == -1)
+1 -1
View File
@@ -2177,7 +2177,7 @@ void GUI::CalibrateFilamentComboBox::OnSelect(wxCommandEvent &evt)
wxPostEvent(m_parent, e);
}
void GUI::PlaterPresetComboBox::sys_color_changed()
void PlaterPresetComboBox::sys_color_changed()
{
PresetComboBox::sys_color_changed();
if (clr_picker) {
+2 -2
View File
@@ -1752,8 +1752,8 @@ void ElegooPrintHostSendDialog::init() {
}
{
auto radioBoxA = new RadioBox(this);
auto radioBoxB = new RadioBox(this);
auto radioBoxA = new ::RadioBox(this);
auto radioBoxB = new ::RadioBox(this);
if (m_BedType == BedType::btPC)
radioBoxB->SetValue(true);
else
+1 -1
View File
@@ -2928,7 +2928,7 @@ void TabPrint::build()
optgroup->append_single_option_line("skin_infill_line_width", "strength_settings_patterns#locked-zag");
optgroup->append_single_option_line("skeleton_infill_line_width", "strength_settings_patterns#locked-zag");
optgroup->append_single_option_line("symmetric_infill_y_axis", "strength_settings_infill#symmetric-infill-y-axis");
optgroup->append_single_option_line("infill_complete_top", "strength_settings_patterns#fill-pattern-tops");
optgroup->append_single_option_line("infill_complete_top", "strength_settings_infill#infill-complete-top");
optgroup->append_single_option_line("infill_shift_step", "strength_settings_patterns#cross-hatch");
optgroup->append_single_option_line("lateral_lattice_angle_1", "strength_settings_patterns#lateral-lattice");
optgroup->append_single_option_line("lateral_lattice_angle_2", "strength_settings_patterns#lateral-lattice");
-3
View File
@@ -6,9 +6,6 @@
#include <set>
namespace Slic3r {
using namespace CustomGCode;
namespace GUI {
std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder)
{
+9 -8
View File
@@ -7,6 +7,7 @@
#include <vector>
namespace Slic3r {
using namespace CustomGCode;
namespace GUI {
struct TickCode
@@ -15,7 +16,7 @@ struct TickCode
bool operator>(const TickCode& other) const { return other.tick < this->tick; }
int tick = 0;
CustomGCode::Type type = CustomGCode::ColorChange;
Type type = ColorChange;
int extruder = 0;
std::string color;
std::string extra;
@@ -30,22 +31,22 @@ class TickCodeInfo
std::vector<std::string>* m_colors{ nullptr };// reference to IMSlider::m_extruder_colors
std::string get_color_for_tick(TickCode tick, CustomGCode::Type type, const int extruder);
std::string get_color_for_tick(TickCode tick, Type type, const int extruder);
public:
std::set<TickCode> ticks{};
CustomGCode::Mode mode = CustomGCode::Undef;
Mode mode = Undef;
bool empty() const { return ticks.empty(); }
void set_pause_print_msg(const std::string& message) { pause_print_msg = message; }
bool add_tick(const int tick, CustomGCode::Type type, int extruder, double print_z);
bool add_tick(const int tick, Type type, int extruder, double print_z);
bool edit_tick(std::set<TickCode>::iterator it, double print_z);
void switch_code(CustomGCode::Type type_from, CustomGCode::Type type_to);
bool switch_code_for_tick(std::set<TickCode>::iterator it, CustomGCode::Type type_to, const int extruder);
void erase_all_ticks_with_code(CustomGCode::Type type);
void switch_code(Type type_from, Type type_to);
bool switch_code_for_tick(std::set<TickCode>::iterator it, Type type_to, const int extruder);
void erase_all_ticks_with_code(Type type);
bool has_tick_with_code(CustomGCode::Type type);
bool has_tick_with_code(Type type);
bool has_tick(int tick);
void suppress_plus(bool suppress) { m_suppress_plus = suppress; }
+1 -1
View File
@@ -115,7 +115,7 @@ static void set_auth(Http& http, const std::string& access_token) { http.header(
static bool should_open_in_external_browser()
{
const auto& app = GUI::wxGetApp();
const auto& app = wxGetApp();
if (app.preset_bundle->use_bbl_device_tab()) {
// When using bbl device tab, we always need to open external browser
+12 -12
View File
@@ -565,7 +565,7 @@ void progress_close(int id)
});
}
void plater_notification(GUI::NotificationManager::NotificationLevel notification_level, const std::string& text,
void plater_notification(NotificationManager::NotificationLevel notification_level, const std::string& text,
const std::string& hypertext, py::object on_click)
{
const std::string plugin_key = PluginAuditManager::instance().current_plugin();
@@ -600,7 +600,7 @@ void plater_notification(GUI::NotificationManager::NotificationLevel notificatio
}
run_on_ui_blocking([notification_level, text, hypertext, callback = std::move(callback)]() mutable {
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::CustomNotification, notification_level, text,
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification, notification_level, text,
hypertext, std::move(callback));
});
}
@@ -713,16 +713,16 @@ void PluginHostUi::RegisterBindings(pybind11::module_& host)
py::arg("maximum") = 100, py::arg("style") = wxPD_APP_MODAL | wxPD_AUTO_HIDE,
"Create a native progress dialog and return a ProgressDialog handle.");
py::enum_<GUI::NotificationManager::NotificationLevel>(ui, "NotificationLevel")
.value("ProgressBarNotificationLevel", GUI::NotificationManager::NotificationLevel::ProgressBarNotificationLevel)
.value("HintNotificationLevel", GUI::NotificationManager::NotificationLevel::HintNotificationLevel)
.value("RegularNotificationLevel", GUI::NotificationManager::NotificationLevel::RegularNotificationLevel)
.value("PrintInfoNotificationLevel", GUI::NotificationManager::NotificationLevel::PrintInfoNotificationLevel)
.value("PrintInfoShortNotificationLevel", GUI::NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel)
.value("ImportantNotificationLevel", GUI::NotificationManager::NotificationLevel::ImportantNotificationLevel)
.value("WarningNotificationLevel", GUI::NotificationManager::NotificationLevel::WarningNotificationLevel)
.value("SeriousWarningNotificationLevel", GUI::NotificationManager::NotificationLevel::SeriousWarningNotificationLevel)
.value("ErrorNotificationLevel", GUI::NotificationManager::NotificationLevel::ErrorNotificationLevel)
py::enum_<NotificationManager::NotificationLevel>(ui, "NotificationLevel")
.value("ProgressBarNotificationLevel", NotificationManager::NotificationLevel::ProgressBarNotificationLevel)
.value("HintNotificationLevel", NotificationManager::NotificationLevel::HintNotificationLevel)
.value("RegularNotificationLevel", NotificationManager::NotificationLevel::RegularNotificationLevel)
.value("PrintInfoNotificationLevel", NotificationManager::NotificationLevel::PrintInfoNotificationLevel)
.value("PrintInfoShortNotificationLevel", NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel)
.value("ImportantNotificationLevel", NotificationManager::NotificationLevel::ImportantNotificationLevel)
.value("WarningNotificationLevel", NotificationManager::NotificationLevel::WarningNotificationLevel)
.value("SeriousWarningNotificationLevel", NotificationManager::NotificationLevel::SeriousWarningNotificationLevel)
.value("ErrorNotificationLevel", NotificationManager::NotificationLevel::ErrorNotificationLevel)
.export_values();
ui.def("push_notification", &plater_notification, py::arg("notification_level"), py::arg("text"),