mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 06:11:02 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c017629d11 |
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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> ¬ification = *it;
|
||||
if (notification->get_type() == type) {
|
||||
it = m_pop_notifications.erase(it);
|
||||
break;
|
||||
} else
|
||||
++it;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user