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
15 changed files with 224 additions and 177 deletions
+2 -9
View File
@@ -809,20 +809,13 @@ void priv::set_skip_for_out_of_aoi(std::vector<bool> &skip_indicies,
}); // END parallel for
// inspect all triangles, when it is out of bounding box
// NOTE: std::vector<bool> is bit packed, thus setting its items from multiple threads is a
// read-modify-write race on the shared words and silently loses flags. Collect the flags into
// a byte per triangle, where the chunks do not share memory, and merge them afterwards.
std::vector<unsigned char> skip_triangle(its.indices.size(), 0);
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
[&its, &is_on_sides, &skip_triangle](const tbb::blocked_range<size_t> &range) {
[&its, &is_on_sides, &skip_indicies](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
if (is_all_on_one_side(its.indices[i], is_on_sides))
skip_triangle[i] = 1;
skip_indicies[i] = true;
}
}); // END parallel for
for (size_t i = 0; i < skip_triangle.size(); ++i)
if (skip_triangle[i])
skip_indicies[i] = true;
}
indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its,
+45 -56
View File
@@ -1378,66 +1378,55 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
return out;
};
// The layers are processed in groups of "granularity" layers. A layer projects its shells up to "granularity"
// layers away, thus a group may write into the slots of its neighbor groups. The even and the odd groups
// therefore write into two disjoint halves of the output vectors (the 2nd half is offset by num_layers) and
// both halves are merged below. The group index has to be derived from the layer index and not from the extent
// of the TBB sub-range: tbb::blocked_range bisects at midpoints, thus a sub-range neither starts at a multiple
// of the grain size nor covers a whole group, and two sub-ranges of one group would append into a single
// ExPolygons concurrently. Iterating over the groups keeps every group on a single thread, in ascending order.
const size_t num_groups = (num_layers + size_t(granularity) - 1) / size_t(granularity);
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_groups, 1), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
for (size_t group_idx = range.begin(); group_idx < range.end(); ++ group_idx) {
const size_t layer_idx_offset = (group_idx & 1) * num_layers;
const size_t layer_idx_begin = group_idx * size_t(granularity);
const size_t layer_idx_end = std::min(num_layers, layer_idx_begin + size_t(granularity));
for (size_t layer_idx = layer_idx_begin; layer_idx < layer_idx_end; ++ layer_idx) {
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
//offset -= stat.extrusion_width ;
offset -= (stat.extrusion_spacing + stat.extrusion_width);
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
}
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
size_t group_idx = range.begin() / granularity;
size_t layer_idx_offset = (group_idx & 1) * num_layers;
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
//offset -= stat.extrusion_width ;
offset -= (stat.extrusion_spacing + stat.extrusion_width);
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
//offset -= stat.extrusion_width;
offset -= (stat.extrusion_spacing + stat.extrusion_width);
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
//offset -= stat.extrusion_width;
offset -= (stat.extrusion_spacing + stat.extrusion_width);
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
}
}
}
}
});
+1 -15
View File
@@ -424,16 +424,8 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
[this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); });
// Layer range for which the collisions will be calculated.
// Another thread may have advanced getMaxCalculatedLayer() past max_layer_idx after this calculation
// was requested. Bail out in that case, otherwise the layer range would be negative and allocating
// it would throw std::length_error out of a parallel task.
const LayerIndex start_layer = 1 + m_collision_cache.getMaxCalculatedLayer(radius);
if (start_layer > max_layer_idx) {
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
return;
}
LayerPolygonCache data;
data.allocate(start_layer, max_layer_idx + 1);
data.allocate(m_collision_cache.getMaxCalculatedLayer(radius) + 1, max_layer_idx + 1);
const bool calculate_placable = m_support_rests_on_model && radius == 0;
LayerPolygonCache data_placeable;
@@ -812,12 +804,6 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPa
const coord_t radius = keys[key_idx].first;
const LayerIndex max_required_layer = keys[key_idx].second;
const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius));
if (min_layer_bottom > max_required_layer) {
// Another thread has calculated this range in the meantime. Continuing would make
// buffer_size negative and allocating it would throw std::length_error.
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
continue;
}
const size_t buffer_size = max_required_layer + 1 - min_layer_bottom;
std::vector<Polygons> data(buffer_size, Polygons{});
std::vector<Polygons> data_min;
+57 -59
View File
@@ -12,7 +12,6 @@
#include <thread>
#include <tbb/parallel_for.h>
#include <tbb/task_arena.h>
#include <tbb/task_scheduler_observer.h>
#include "Thread.hpp"
#include "Utils.hpp"
@@ -213,71 +212,70 @@ bool is_main_thread_active()
return get_main_thread_id() == boost::this_thread::get_id();
}
// Name the current TBB worker thread and set its locale to "C", so that the G-code generator
// produces "." as a decimal separator. Called once per worker thread, before it runs its first task.
static void setup_tbb_worker_thread()
{
static std::atomic<size_t> s_worker_idx{ 0 };
std::ostringstream name;
name << "slic3r_tbb_" << (1 + s_worker_idx.fetch_add(1, std::memory_order_relaxed));
set_current_thread_name(name.str().c_str());
#ifdef _WIN32
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
std::setlocale(LC_ALL, "C");
#else
// We are leaking some memory here, because the newlocale() produced memory will never be released.
// This is not a problem though, as there will be a maximum one worker thread created per physical thread.
uselocale(newlocale(
#ifdef __APPLE__
LC_ALL_MASK
#else // some Unix / Linux / BSD
LC_ALL
#endif
, "C", nullptr));
#endif
}
// Sets up the TBB worker threads of the arena of the thread, which activated the observation.
// A worker sets itself up on entry to the arena, before it executes its first task, thus unlike a barrier
// inside a parallel_for, this does not depend on TBB running any number of tasks simultaneously.
class TBBWorkerThreadSetupObserver : public tbb::task_scheduler_observer
{
public:
TBBWorkerThreadSetupObserver() { this->observe(true); }
void on_scheduler_entry(bool is_worker) override
{
// Leave the external threads (the calling / UI thread) alone, their name and locale must not be modified here.
if (! is_worker)
return;
// A worker thread enters an arena many times, while its name and locale have to be set just once.
static thread_local bool initialized = false;
if (initialized)
return;
initialized = true;
setup_tbb_worker_thread();
}
};
// Name the threads of the Intel TBB thread pool by an index and set their locale to "C"
// for the G-code generator to produce "." as a decimal separator.
// Formerly all the worker threads were caught inside a single parallel_for, which was held on a condition
// variable barrier until max_concurrency() of its chunks were running. TBB guarantees no such simultaneity,
// thus the barrier was able to block the slicing threads indefinitely. The TBB scheduler observer below
// sets each worker up on its own, thus no two chunks have to run at the same time.
// Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID.
// Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator.
void name_tbb_thread_pool_threads_set_locale()
{
static bool initialized = false;
if (initialized)
return;
initialized = true;
// see GH issue #5661 PrusaSlicer hangs on Linux when run with non standard task affinity
// TBB will respect the task affinity mask on Linux and spawn less threads than std::thread::hardware_concurrency().
// const size_t nthreads_hw = std::thread::hardware_concurrency();
const size_t nthreads_hw = tbb::this_task_arena::max_concurrency();
size_t nthreads = nthreads_hw;
#ifdef SLIC3R_PROFILE
// Shiny profiler is not thread safe, thus disable parallelization.
disable_multi_threading();
nthreads = 1;
#endif
// An observer is local to the arena of the thread which activates it, thus one observer is registered
// per calling thread. Being function local and thread local, it is also initialized exactly once per
// thread without a race. It is intentionally never destroyed, as it has to stay alive as long as the
// TBB scheduler may notify it, which includes the shutdown of the process.
static thread_local tbb::task_scheduler_observer *observer = new TBBWorkerThreadSetupObserver();
(void)observer;
size_t nthreads_running(0);
std::condition_variable cv;
std::mutex cv_m;
auto master_thread_id = std::this_thread::get_id();
tbb::parallel_for(
tbb::blocked_range<size_t>(0, nthreads, 1),
[&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) {
assert(range.begin() + 1 == range.end());
if (std::unique_lock<std::mutex> lk(cv_m); ++nthreads_running == nthreads) {
lk.unlock();
// All threads are spinning.
// Wake them up.
cv.notify_all();
} else {
// Wait for the last thread to wake the others.
cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;});
}
auto thread_id = std::this_thread::get_id();
if (thread_id == master_thread_id) {
// The calling thread runs the 0'th task.
assert(range.begin() == 0);
} else {
assert(range.begin() > 0);
std::ostringstream name;
name << "slic3r_tbb_" << range.begin();
set_current_thread_name(name.str().c_str());
// Set locales of the worker thread to "C".
#ifdef _WIN32
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
std::setlocale(LC_ALL, "C");
#else
// We are leaking some memory here, because the newlocale() produced memory will never be released.
// This is not a problem though, as there will be a maximum one worker thread created per physical thread.
uselocale(newlocale(
#ifdef __APPLE__
LC_ALL_MASK
#else // some Unix / Linux / BSD
LC_ALL
#endif
, "C", nullptr));
#endif
}
});
}
}
+1 -8
View File
@@ -28,10 +28,6 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index
area_sum_to_triangle_idx[area_sum] = t_idx;
}
if (area_sum_to_triangle_idx.empty())
// No triangle to sample from.
return {};
std::mt19937_64 mersenne_engine { 27644437 };
// random numbers on interval [0, 1)
std::uniform_real_distribution<double> fdistribution;
@@ -54,10 +50,7 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index
tbb::blocked_range<size_t> r) {
for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) {
double t_sample = random_samples[s_idx].x() * area_sum;
// The keys of area_sum_to_triangle_idx are accumulated areas in double precision, while area_sum
// is a float, thus t_sample may reach or exceed the largest key and upper_bound() may return end().
auto t_it = area_sum_to_triangle_idx.upper_bound(t_sample);
size_t t_idx = (t_it == area_sum_to_triangle_idx.end() ? std::prev(t_it) : t_it)->second;
size_t t_idx = area_sum_to_triangle_idx.upper_bound(t_sample)->second;
double sq_u = std::sqrt(random_samples[s_idx].y());
double v = random_samples[s_idx].z();
+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>
@@ -121,12 +122,21 @@ private:
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
std::array<wxMenuItem*, mtCount> items_increase;
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();
+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;
}