Compare commits

..
Author SHA1 Message Date
ExPikaPaka dbe342c978 Fix data races in the parallel slicing paths
Multi-material segmentation derived the output slot from range.begin() divided
by the granularity, which assumes tbb::blocked_range splits on grainsize
multiples. It bisects at midpoints, so two sub-ranges of one group get the same
slot and append into the same ExPolygons at once. With ten layers and a
granularity of two, the sub-ranges [2,3) and [3,5) collide. The granularity is
two or more whenever the shell layers are three or more, which is the default.
The loop now iterates groups, so the slot follows the element index and the
partitioner cannot cut a group in half.

CutSurface wrote std::vector<bool> elements from a parallel_for. The bits share
words across chunk boundaries, so flags were lost.

name_tbb_thread_pool_threads_set_locale() held every chunk of a parallel_for on
a condition variable until max_concurrency() of them were running. TBB does not
promise that, and there is no timeout, so the slicing thread could wait forever.
A task_scheduler_observer sets each worker up as it joins the arena, which needs
no simultaneity and covers workers the chunked version never reached.

TriangleSetSampling dereferenced upper_bound() without checking for end(), which
is reachable because the sum is a float and the keys are doubles.

TreeModelVolumes re-read getMaxCalculatedLayer() after releasing the lock, so the
layer difference could go negative and become a huge size_t. The two sibling
functions already carry the guard this adds.
2026-10-01 09:15:16 +02:00
11 changed files with 354 additions and 208 deletions
+9 -2
View File
@@ -809,13 +809,20 @@ void priv::set_skip_for_out_of_aoi(std::vector<bool> &skip_indicies,
}); // END parallel for }); // END parallel for
// inspect all triangles, when it is out of bounding box // 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()), tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
[&its, &is_on_sides, &skip_indicies](const tbb::blocked_range<size_t> &range) { [&its, &is_on_sides, &skip_triangle](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) { for (size_t i = range.begin(); i < range.end(); ++i) {
if (is_all_on_one_side(its.indices[i], is_on_sides)) if (is_all_on_one_side(its.indices[i], is_on_sides))
skip_indicies[i] = true; skip_triangle[i] = 1;
} }
}); // END parallel for }); // 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, indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its,
+56 -45
View File
@@ -1378,55 +1378,66 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
return out; return out;
}; };
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, // The layers are processed in groups of "granularity" layers. A layer projects its shells up to "granularity"
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom, // layers away, thus a group may write into the slots of its neighbor groups. The even and the odd groups
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) { // therefore write into two disjoint halves of the output vectors (the 2nd half is offset by num_layers) and
size_t group_idx = range.begin() / granularity; // both halves are merged below. The group index has to be derived from the layer index and not from the extent
size_t layer_idx_offset = (group_idx & 1) * num_layers; // of the TBB sub-range: tbb::blocked_range bisects at midpoints, thus a sub-range neither starts at a multiple
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { // of the grain size nor covers a whole group, and two sub-ranges of one group would append into a single
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) { // ExPolygons concurrently. Iterating over the groups keeps every group on a single thread, in ascending order.
throw_on_cancel_callback(); const size_t num_groups = (num_layers + size_t(granularity) - 1) / size_t(granularity);
LayerColorStat stat = layer_color_stat(layer_idx, color_idx); 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,
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) &throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { &shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
// Clean up thin projections. They are not printable anyways. for (size_t group_idx = range.begin(); group_idx < range.end(); ++ group_idx) {
top_ex = opening_ex(top_ex, stat.small_region_threshold); const size_t layer_idx_offset = (group_idx & 1) * num_layers;
if (! top_ex.empty()) { const size_t layer_idx_begin = group_idx * size_t(granularity);
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); const size_t layer_idx_end = std::min(num_layers, layer_idx_begin + size_t(granularity));
float offset = 0.f; for (size_t layer_idx = layer_idx_begin; layer_idx < layer_idx_end; ++ layer_idx) {
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) { throw_on_cancel_callback();
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
//offset -= stat.extrusion_width ; if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
offset -= (stat.extrusion_spacing + stat.extrusion_width); if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); // Clean up thin projections. They are not printable anyways.
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (last.empty()) if (! top_ex.empty()) {
break; append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); 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 (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()) {
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { // Clean up thin projections. They are not printable anyways.
// Clean up thin projections. They are not printable anyways. bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); if (! bottom_ex.empty()) {
if (! bottom_ex.empty()) { append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); float offset = 0.f;
float offset = 0.f; ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
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) {
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
//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_width; offset -= (stat.extrusion_spacing + stat.extrusion_width);
offset -= (stat.extrusion_spacing + stat.extrusion_width); layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
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);
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); if (last.empty())
if (last.empty()) break;
break; append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); }
} }
} }
} }
} }
} }
}); });
+7 -24
View File
@@ -19,7 +19,7 @@ void PrintTryCancel::operator()()
m_print->throw_if_canceled(); m_print->throw_if_canceled();
} }
std::atomic<size_t> PrintStateBase::g_last_timestamp{0}; size_t PrintStateBase::g_last_timestamp = 0;
// Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects. // Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects.
void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const
@@ -107,26 +107,11 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin
return path; return path;
} }
void PrintBase::set_status_callback(status_callback_type cb)
{
std::scoped_lock<std::mutex> lock(m_status_callback_mutex);
m_status_callback = std::move(cb);
}
// Returns a copy, so that the callback is invoked with m_status_callback_mutex released: the callback
// may block on the UI thread, which in turn may be assigning a new callback.
PrintBase::status_callback_type PrintBase::status_callback() const
{
std::scoped_lock<std::mutex> lock(m_status_callback_mutex);
return m_status_callback;
}
//BBS: move set_status from hpp to cpp //BBS: move set_status from hpp to cpp
void PrintBase::set_status(int percent, const std::string &message, unsigned int flags, int warning_step) const void PrintBase::set_status(int percent, const std::string &message, unsigned int flags, int warning_step) const
{ {
status_callback_type status_callback = this->status_callback(); if (m_status_callback)
if (status_callback) m_status_callback(SlicingStatus(percent, message, flags, warning_step));
status_callback(SlicingStatus(percent, message, flags, warning_step));
else else
BOOST_LOG_TRIVIAL(debug) <<boost::format("Percent %1%: %2%\n")%percent %message.c_str(); BOOST_LOG_TRIVIAL(debug) <<boost::format("Percent %1%: %2%\n")%percent %message.c_str();
} }
@@ -134,10 +119,9 @@ void PrintBase::set_status(int percent, const std::string &message, unsigned in
void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel warning_level, void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel warning_level,
const std::string &message, const PrintObjectBase* print_object, PrintStateBase::SlicingNotificationType message_id) const std::string &message, const PrintObjectBase* print_object, PrintStateBase::SlicingNotificationType message_id)
{ {
status_callback_type status_callback = this->status_callback(); if (this->m_status_callback) {
if (status_callback) {
auto status = print_object ? SlicingStatus(*print_object, step, message, message_id, warning_level) : SlicingStatus(*this, step, message, message_id, warning_level); auto status = print_object ? SlicingStatus(*print_object, step, message, message_id, warning_level) : SlicingStatus(*this, step, message, message_id, warning_level);
status_callback(status); m_status_callback(status);
} }
else if (! message.empty()) else if (! message.empty())
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Print warning: %1%\n")% message.c_str(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Print warning: %1%\n")% message.c_str();
@@ -148,9 +132,8 @@ void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel wa
const std::string& message, PrintObjectBase &object, PrintStateBase::SlicingNotificationType message_id) const std::string& message, PrintObjectBase &object, PrintStateBase::SlicingNotificationType message_id)
{ {
//BBS: add object it into slicing status //BBS: add object it into slicing status
status_callback_type status_callback = this->status_callback(); if (this->m_status_callback) {
if (status_callback) { m_status_callback(SlicingStatus(object, step, message, message_id, warning_level));
status_callback(SlicingStatus(object, step, message, message_id, warning_level));
} }
else if (!message.empty()) else if (!message.empty())
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", PrintObject warning: %1%\n")% message.c_str(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", PrintObject warning: %1%\n")% message.c_str();
+8 -11
View File
@@ -99,9 +99,10 @@ public:
}; };
protected: protected:
// The last timestamp is shared between all the Print & SLAPrint instances, and Orca keeps one Print //FIXME last timestamp is shared between Print & SLAPrint,
// per PartPlate, so it is incremented under different state mutexes: it has to be atomic. // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp
static std::atomic<size_t> g_last_timestamp; // is not synchronized!
static size_t g_last_timestamp;
}; };
// To be instantiated over PrintStep or PrintObjectStep enums. // To be instantiated over PrintStep or PrintObjectStep enums.
@@ -472,12 +473,11 @@ public:
}; };
typedef std::function<void(const SlicingStatus&)> status_callback_type; typedef std::function<void(const SlicingStatus&)> status_callback_type;
// Default status console print out in the form of percent => message. // Default status console print out in the form of percent => message.
void set_status_default() { this->set_status_callback(nullptr); } void set_status_default() { m_status_callback = nullptr; }
// No status output or callback whatsoever, useful mostly for automatic tests. // No status output or callback whatsoever, useful mostly for automatic tests.
void set_status_silent() { this->set_status_callback([](const SlicingStatus&){}); } void set_status_silent() { m_status_callback = [](const SlicingStatus&){}; }
// Register a custom status callback. Called from the UI thread while the worker thread may be // Register a custom status callback.
// invoking the previous callback, therefore guarded by m_status_callback_mutex. void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
void set_status_callback(status_callback_type cb);
// Calls a registered callback to update the status, or print out the default message. // Calls a registered callback to update the status, or print out the default message.
void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT, int warning_step = -1) const; void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT, int warning_step = -1) const;
@@ -563,10 +563,7 @@ protected:
std::string m_plate_name; std::string m_plate_name;
// Callback to be evoked regularly to update state of the UI thread. // Callback to be evoked regularly to update state of the UI thread.
// Guarded by m_status_callback_mutex, always invoke the copy returned by status_callback().
status_callback_type m_status_callback; status_callback_type m_status_callback;
mutable std::mutex m_status_callback_mutex;
status_callback_type status_callback() const;
private: private:
std::atomic<CancelStatus> m_cancel_status; std::atomic<CancelStatus> m_cancel_status;
+15 -1
View File
@@ -424,8 +424,16 @@ 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(); }); [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. // 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; LayerPolygonCache data;
data.allocate(m_collision_cache.getMaxCalculatedLayer(radius) + 1, max_layer_idx + 1); data.allocate(start_layer, max_layer_idx + 1);
const bool calculate_placable = m_support_rests_on_model && radius == 0; const bool calculate_placable = m_support_rests_on_model && radius == 0;
LayerPolygonCache data_placeable; LayerPolygonCache data_placeable;
@@ -804,6 +812,12 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPa
const coord_t radius = keys[key_idx].first; const coord_t radius = keys[key_idx].first;
const LayerIndex max_required_layer = keys[key_idx].second; const LayerIndex max_required_layer = keys[key_idx].second;
const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius)); 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; const size_t buffer_size = max_required_layer + 1 - min_layer_bottom;
std::vector<Polygons> data(buffer_size, Polygons{}); std::vector<Polygons> data(buffer_size, Polygons{});
std::vector<Polygons> data_min; std::vector<Polygons> data_min;
+59 -57
View File
@@ -12,6 +12,7 @@
#include <thread> #include <thread>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
#include <tbb/task_arena.h> #include <tbb/task_arena.h>
#include <tbb/task_scheduler_observer.h>
#include "Thread.hpp" #include "Thread.hpp"
#include "Utils.hpp" #include "Utils.hpp"
@@ -212,70 +213,71 @@ bool is_main_thread_active()
return get_main_thread_id() == boost::this_thread::get_id(); return get_main_thread_id() == boost::this_thread::get_id();
} }
// Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID. // Name the current TBB worker thread and set its locale to "C", so that the G-code generator
// Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator. // 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.
void name_tbb_thread_pool_threads_set_locale() 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 #ifdef SLIC3R_PROFILE
// Shiny profiler is not thread safe, thus disable parallelization. // Shiny profiler is not thread safe, thus disable parallelization.
disable_multi_threading(); disable_multi_threading();
nthreads = 1;
#endif #endif
size_t nthreads_running(0); // An observer is local to the arena of the thread which activates it, thus one observer is registered
std::condition_variable cv; // per calling thread. Being function local and thread local, it is also initialized exactly once per
std::mutex cv_m; // thread without a race. It is intentionally never destroyed, as it has to stay alive as long as the
auto master_thread_id = std::this_thread::get_id(); // TBB scheduler may notify it, which includes the shutdown of the process.
tbb::parallel_for( static thread_local tbb::task_scheduler_observer *observer = new TBBWorkerThreadSetupObserver();
tbb::blocked_range<size_t>(0, nthreads, 1), (void)observer;
[&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
}
});
} }
} }
+8 -1
View File
@@ -28,6 +28,10 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index
area_sum_to_triangle_idx[area_sum] = t_idx; 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 }; std::mt19937_64 mersenne_engine { 27644437 };
// random numbers on interval [0, 1) // random numbers on interval [0, 1)
std::uniform_real_distribution<double> fdistribution; std::uniform_real_distribution<double> fdistribution;
@@ -50,7 +54,10 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index
tbb::blocked_range<size_t> r) { tbb::blocked_range<size_t> r) {
for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) { for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) {
double t_sample = random_samples[s_idx].x() * area_sum; double t_sample = random_samples[s_idx].x() * area_sum;
size_t t_idx = area_sum_to_triangle_idx.upper_bound(t_sample)->second; // 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;
double sq_u = std::sqrt(random_samples[s_idx].y()); double sq_u = std::sqrt(random_samples[s_idx].y());
double v = random_samples[s_idx].z(); double v = random_samples[s_idx].z();
-5
View File
@@ -6693,11 +6693,6 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
void ObjectList::OnEditingDone(wxDataViewEvent &event) void ObjectList::OnEditingDone(wxDataViewEvent &event)
{ {
// ~wxDataViewCtrl ends the in-place editing, so this handler runs while ~Plater is already tearing
// the Plater down. Nothing below may touch the Plater or the plates any more.
if (wxGetApp().is_closing())
return;
if (event.GetColumn() != colName) if (event.GetColumn() != colName)
return; return;
-13
View File
@@ -3572,8 +3572,6 @@ void PartPlate::update_slice_result_valid_state(bool valid)
//update current slice context into backgroud slicing process //update current slice context into backgroud slicing process
void PartPlate::update_slice_context(BackgroundSlicingProcess & process) void PartPlate::update_slice_context(BackgroundSlicingProcess & process)
{ {
//this callback outlives the call, so it is dropped again in PartPlateList::clear() and
//PartPlateList::delete_plate() before the plate is destroyed
auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus& status) { auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus& status) {
Slic3r::SlicingStatusEvent *event = new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status); Slic3r::SlicingStatusEvent *event = new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status);
//BBS: GUI refactor: add plate info befor message //BBS: GUI refactor: add plate info befor message
@@ -4545,14 +4543,7 @@ void PartPlateList::clear(bool delete_plates, bool release_print_list, bool exce
else else
plate->clear(); plate->clear();
if (delete_plates) if (delete_plates)
{
//the slicing status callback installed by update_slice_context() captures the plate, so drop it
//while the Print is still alive: the prints are only released below, after this loop, and are
//not released at all when release_print_list is false.
if (Print* print = plate->fff_print())
print->set_status_default();
delete plate; delete plate;
}
} }
if (delete_plates) if (delete_plates)
@@ -4895,10 +4886,6 @@ int PartPlateList::delete_plate(int index)
//destroy the print object //destroy the print object
int print_index; int print_index;
plate->get_print(nullptr, nullptr, &print_index); plate->get_print(nullptr, nullptr, &print_index);
//the slicing status callback installed by update_slice_context() captures the plate, and destroy_print()
//frees the Print, so drop the callback here, the last point where both are still alive.
if (Print* print = plate->fff_print())
print->set_status_default();
destroy_print(print_index); destroy_print(print_index);
delete plate; delete plate;
+192 -47
View File
@@ -103,6 +103,7 @@
#include "Selection.hpp" #include "Selection.hpp"
#include "GLToolbar.hpp" #include "GLToolbar.hpp"
#include "GUI_Preview.hpp" #include "GUI_Preview.hpp"
#include "UVEditorCanvas.hpp"
#include "3DBed.hpp" #include "3DBed.hpp"
#include "PartPlate.hpp" #include "PartPlate.hpp"
#include "Camera.hpp" #include "Camera.hpp"
@@ -1797,11 +1798,14 @@ bool Sidebar::priv::switch_diameter_to(const wxString &diameter)
Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
// The combo lists printer variants, and the variant of a mixed-nozzle machine ("0.4+0.6") is no // The combo lists printer variants, and the variant of a mixed-nozzle machine ("0.4+0.6") is no
// single extruder's diameter, so the preset's own variant answers first. // single extruder's diameter, so the preset's own variant answers first.
if (printer_preset.config.opt_string("printer_variant") == diameter.ToStdString()) { const std::string &printer_variant = printer_preset.config.opt_string("printer_variant");
if (printer_variant == diameter.ToStdString()) {
return true; return true;
} }
// A named variant ("0.4 High Flow") shares its diameter with the standard profile, which selecting
// the plain diameter switches back to, so only a preset naming no variant is kept by its diameter.
auto* nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printer_preset.config.option("nozzle_diameter")); auto* nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printer_preset.config.option("nozzle_diameter"));
if (nozzle_diameter && nozzle_diameter->size() > 0) { if (printer_variant.empty() && nozzle_diameter && nozzle_diameter->size() > 0) {
auto current_nozzle_dia = get_diameter_string(nozzle_diameter->values[0]); auto current_nozzle_dia = get_diameter_string(nozzle_diameter->values[0]);
// If the selected diameter is the same as current nozzle, don't switch profiles // If the selected diameter is the same as current nozzle, don't switch profiles
if (current_nozzle_dia == diameter.ToStdString()) { if (current_nozzle_dia == diameter.ToStdString()) {
@@ -2236,12 +2240,14 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
std::string machine_print_name = obj->get_show_printer_type(); std::string machine_print_name = obj->get_show_printer_type();
PresetBundle *preset_bundle = wxGetApp().preset_bundle; PresetBundle *preset_bundle = wxGetApp().preset_bundle;
std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle); std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle);
Preset* machine_preset = get_printer_preset(obj); const bool optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type);
if (!machine_preset) { const bool optional_target_model = DevPrinterConfigUtil::is_optional_printer_model_id(target_model_id);
Preset* machine_preset = optional_printer_model ? nullptr : get_printer_preset(obj);
if (!optional_printer_model && !optional_target_model && !machine_preset) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty";
return false; return false;
} }
if (machine_print_name != target_model_id) { if (!optional_printer_model && !optional_target_model && machine_print_name != target_model_id) {
MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n" MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n"
"Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO); "Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO);
if (dlg.ShowModal() == wxID_NO) { if (dlg.ShowModal() == wxID_NO) {
@@ -2433,6 +2439,11 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj)
return; return;
} }
if (DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type)) {
clear_all_sync_status();
return;
}
bool printer_synced = false; bool printer_synced = false;
// 1. update printer status // 1. update printer status
const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
@@ -3926,13 +3937,18 @@ void Sidebar::update_presets(Preset::Type preset_type)
combo_flow->Show(combo_flow->GetCount() > 0); combo_flow->Show(combo_flow->GetCount() > 0);
}; };
auto update_extruder_diameter = [&diameters, &nozzle_diameter](int extruder_index,ExtruderGroup & extruder) { auto update_extruder_diameter = [&diameters, &nozzle_diameter, &diameter](int extruder_index,ExtruderGroup & extruder) {
extruder.combo_diameter->Clear(); extruder.combo_diameter->Clear();
if (extruder_index >= int(nozzle_diameter->values.size())) if (extruder_index >= int(nozzle_diameter->values.size()))
return; return;
int select = -1; int select = -1;
// ORCA get the actual nozzle diameter from printer config // ORCA get the actual nozzle diameter from printer config
auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]); auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]);
// Named variants such as "0.4HS" and "0.4 High Flow" share a physical diameter.
// Retain the variant selection unless the diameter was customized.
const bool keep_variant = diameter.substr(0, diameter.find_first_not_of("0123456789.")) == nozzle_dia &&
std::find(diameters.begin(), diameters.end(), diameter) != diameters.end();
const std::string &selected_variant = keep_variant ? diameter : nozzle_dia;
// ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias // ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias
if(!diameters.empty() && diameters[0].empty() && !nozzle_dia.empty()){ if(!diameters.empty() && diameters[0].empty() && !nozzle_dia.empty()){
diameters[0] = nozzle_dia; diameters[0] = nozzle_dia;
@@ -3942,7 +3958,7 @@ void Sidebar::update_presets(Preset::Type preset_type)
diameters.push_back(nozzle_dia); diameters.push_back(nozzle_dia);
} }
for (size_t i = 0; i < diameters.size(); ++i) { for (size_t i = 0; i < diameters.size(); ++i) {
if (diameters[i] == nozzle_dia) if (diameters[i] == selected_variant)
select = extruder.combo_diameter->GetCount(); select = extruder.combo_diameter->GetCount();
extruder.combo_diameter->Append(diameters[i], {}); extruder.combo_diameter->Append(diameters[i], {});
} }
@@ -6066,11 +6082,30 @@ void Sidebar::load_ams_list(MachineObject* obj)
filament_ams_list = build_filament_ams_list(obj); filament_ams_list = build_filament_ams_list(obj);
} }
bool device_change = false;
const std::string& device = obj ? obj->get_dev_id() : ""; const std::string& device = obj ? obj->get_dev_id() : "";
if (p->ams_list_device != device) { const bool same_device = p->ams_list_device == device;
// Keep sync metadata out of the device payload, but preserve it across a
// subscription refresh when the physical filament in a slot is unchanged.
// Otherwise the refreshed configs differ only by the missing
// filament_changed key, causing combo boxes to rebuild and lose their
// transient post-sync badges.
auto &previous_filament_ams_list = wxGetApp().preset_bundle->filament_ams_list;
for (auto &entry : filament_ams_list) {
auto previous = previous_filament_ams_list.find(entry.first);
const auto *previous_changed = previous == previous_filament_ams_list.end() ? nullptr :
dynamic_cast<const ConfigOptionBool *>(previous->second.option("filament_changed"));
if (!same_device || previous_changed == nullptr ||
previous->second.opt_string("filament_id", 0u) != entry.second.opt_string("filament_id", 0u)) {
continue;
}
entry.second.set_key_value("filament_changed",
new ConfigOptionBool{previous_changed->value});
}
bool device_change = !same_device;
if (device_change) {
p->ams_list_device = device; p->ams_list_device = device;
device_change = true;
} }
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size();
if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change) if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change)
@@ -6080,9 +6115,27 @@ void Sidebar::load_ams_list(MachineObject* obj)
wxGetApp().preset_bundle->filament_ams_list = filament_ams_list; wxGetApp().preset_bundle->filament_ams_list = filament_ams_list;
for (auto c : p->combos_filament){ for (auto c : p->combos_filament){
c->set_sync_badge(false);
c->update(); c->update();
if (device_change) { }
c->ShowBadge(false);//change printer,then clear badge
if (!device_change) {
size_t combo_index = 0;
for (const auto &entry : filament_ams_list) {
const auto &tray = entry.second;
const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
tray.opt_bool("filament_slot_placeholder", 0u);
if (!has_filament && !is_placeholder) {
continue;
}
if (combo_index >= p->combos_filament.size()) {
break;
}
const auto *filament_changed = dynamic_cast<const ConfigOptionBool *>(tray.option("filament_changed"));
p->combos_filament[combo_index]->set_sync_badge(
has_filament && !is_placeholder && filament_changed != nullptr && filament_changed->value);
++combo_index;
} }
} }
@@ -6256,18 +6309,32 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") : auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") :
_L("Filament type and color information have been synchronized, but slot information is not included."); _L("Filament type and color information have been synchronized, but slot information is not included.");
c->SetToolTip(tip); c->SetToolTip(tip);
c->ShowBadge(true); c->set_sync_badge(true);
}; };
{ // badge ams filament { // badge ams filament
clear_combos_filament_badge(); clear_combos_filament_badge();
if (sync_result.direct_sync) { if (sync_result.direct_sync) {
// Orca: PresetBundle::sync_ams_list rebuilds combos_filament // A placeholder contributes a preserved project filament to the
// 1:1 from the AMS trays that produce a combo (loaded trays + placeholders; non-placeholder // overwrite result, but it is not AMS-sourced and must not get a
// empty trays are skipped), so every resulting combo is AMS-sourced and gets a badge. The // sync badge. Non-placeholder empty trays are omitted entirely.
// previous per-tray index walked the full filament_ams_list (including the skipped empties), size_t combo_index = 0;
// so an empty slot before a loaded one dropped the badge for the trailing filaments. for (const auto &entry : wxGetApp().preset_bundle->filament_ams_list) {
for (auto &c : p->combos_filament) { const auto &tray = entry.second;
badge_combox_filament(c); const bool has_filament = !tray.opt_string("filament_id", 0u).empty();
const bool is_placeholder = tray.has("filament_slot_placeholder") &&
tray.opt_bool("filament_slot_placeholder", 0u);
if (!has_filament && !is_placeholder) {
continue;
}
if (combo_index >= p->combos_filament.size()) {
break;
}
if (is_placeholder) {
p->combos_filament[combo_index]->set_sync_badge(false);
} else {
badge_combox_filament(p->combos_filament[combo_index]);
}
++combo_index;
} }
} }
} }
@@ -6535,6 +6602,11 @@ template<typename T> void setup_dialog_position(T& info)
void Sidebar::pop_sync_nozzle_and_ams_dialog() { void Sidebar::pop_sync_nozzle_and_ams_dialog() {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog";
auto agent = wxGetApp().getAgent();
if (!agent || agent->get_filament_sync_mode() == FilamentSyncMode::none) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament synchronization is not supported; skipping dialog";
return;
}
wxTheApp->CallAfter([this]() { wxTheApp->CallAfter([this]() {
SyncNozzleAndAmsDialog::InputInfo temp_na_info; SyncNozzleAndAmsDialog::InputInfo temp_na_info;
wxPoint big_btn_pt; wxPoint big_btn_pt;
@@ -6666,17 +6738,14 @@ void Sidebar::clear_combos_filament_badge()
{ {
auto &combos_filament = p->combos_filament; auto &combos_filament = p->combos_filament;
for (auto &c : combos_filament) { // clear flag for (auto &c : combos_filament) { // clear flag
c->ShowBadge(false); c->set_sync_badge(false);
} }
} }
void Sidebar::udpate_combos_filament_badge() { void Sidebar::udpate_combos_filament_badge() {
auto &combos_filament = p->combos_filament; auto &combos_filament = p->combos_filament;
for (auto &c : combos_filament) { for (auto &c : combos_filament) {
auto selection = c->GetSelection(); c->update_badge_according_flag();
auto select_flag = c->GetFlag(selection);
auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS;
c->ShowBadge(ok);
} }
} }
@@ -7048,6 +7117,13 @@ struct Plater::priv
GLToolbar collapse_toolbar; GLToolbar collapse_toolbar;
Preview *preview; Preview *preview;
AssembleView* assemble_view { nullptr }; AssembleView* assemble_view { nullptr };
// Docked/resizable 2D pane showing GLGizmoTextureDisplacement's LSCM unwrap of a painted
// patch; a sibling AUI pane alongside "sidebar"/"main", not part of the view3D/preview/
// assemble_view sizer - see its registration below and Plater::get_uv_editor_canvas(). The
// pane hosts the panel (toolbar + canvas + status line); uv_editor_canvas is its inner canvas,
// cached so the gizmo can reach it directly.
UVEditorPanel* uv_editor_panel { nullptr };
UVEditorCanvas* uv_editor_canvas { nullptr };
bool first_enter_assemble{ true }; bool first_enter_assemble{ true };
std::unique_ptr<NotificationManager> notification_manager; std::unique_ptr<NotificationManager> notification_manager;
@@ -7297,6 +7373,8 @@ struct Plater::priv
void undo(); void undo();
void redo(); void redo();
// True, and tells the user, while a background job is working on the model - see the definition.
bool undo_redo_blocked_by_job();
void undo_redo_to(size_t time_to_load); void undo_redo_to(size_t time_to_load);
// BBS: backup // BBS: backup
@@ -7750,6 +7828,26 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
.BottomDockable(false) .BottomDockable(false)
.BestSize(wxSize(39 * wxGetApp().em_unit(), 90 * wxGetApp().em_unit()))); .BestSize(wxSize(39 * wxGetApp().em_unit(), 90 * wxGetApp().em_unit())));
// UV editor pane for GLGizmoTextureDisplacement's LSCM unwrap preview - a resizable/dockable
// sibling of "sidebar"/"main" like everything else registered on this same AUI manager, not a
// change to the view3D/preview/assemble_view sizer above. Hidden by default: only relevant
// while that gizmo is active with a layer using the "Unwrap (LSCM)" projection method (see
// Plater::show_uv_editor()), so it stays out of the way of everyone else's window layout.
uv_editor_panel = new UVEditorPanel(q);
uv_editor_canvas = uv_editor_panel->canvas();
m_aui_mgr.AddPane(uv_editor_panel, wxAuiPaneInfo()
.Name("uv_editor")
.Caption(_L("UV Editor"))
.Right()
.Hide()
.BestSize(wxSize(40 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit())));
// Closing the pane with its own X has to reach the gizmo, or its next update would simply show the pane again.
q->Bind(wxEVT_AUI_PANE_CLOSE, [this](wxAuiManagerEvent &evt) {
evt.Skip();
if (evt.GetPane() != nullptr && evt.GetPane()->window == uv_editor_panel && uv_editor_canvas != nullptr)
uv_editor_canvas->run_command(UVEditorCanvas::Command::PaneClosed);
});
auto* panel_sizer = new wxBoxSizer(wxHORIZONTAL); auto* panel_sizer = new wxBoxSizer(wxHORIZONTAL);
panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0); panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0);
panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0); panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0);
@@ -7791,6 +7889,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
BOOST_LOG_TRIVIAL(info) << "Removed floating AUI state from saved window layout for Wayland"; BOOST_LOG_TRIVIAL(info) << "Removed floating AUI state from saved window layout for Wayland";
} }
// The UV editor is a transient, gizmo-driven pane (see show_uv_editor()); a saved layout
// from a session that happened to close with it open would otherwise restore it visible on
// startup, with nothing painted in it. Force it hidden here so it only ever appears when the
// texture-displacement gizmo asks for it.
if (wxAuiPaneInfo &uv_pane = m_aui_mgr.GetPane("uv_editor"); uv_pane.IsOk())
uv_pane.Hide();
sidebar_layout.is_collapsed = !sidebar.IsShown(); sidebar_layout.is_collapsed = !sidebar.IsShown();
} }
@@ -10639,15 +10744,13 @@ void Plater::priv::reset(bool apply_presets_change, bool reload_presets)
m_worker.cancel_all(); m_worker.cancel_all();
// Stop and reset the Print content. m_worker.cancel_all() only stops the UI jobs, so this has to
// happen before reinit() deletes the plates together with the Print the slicing thread may still
// be working on.
this->background_process.reset();
//BBS: clear the partplate list's object before object cleared //BBS: clear the partplate list's object before object cleared
partplate_list.reinit(); partplate_list.reinit();
partplate_list.update_slice_context_to_current_plate(background_process); partplate_list.update_slice_context_to_current_plate(background_process);
preview->update_gcode_result(partplate_list.get_current_slice_result()); preview->update_gcode_result(partplate_list.get_current_slice_result());
// Stop and reset the Print content.
this->background_process.reset();
model.clear_objects(); model.clear_objects();
// clear_objects() only drops the ModelObjects; the CAD recipe is Model-level state and would // clear_objects() only drops the ModelObjects; the CAD recipe is Model-level state and would
// otherwise be written into every project saved for the rest of the session. // otherwise be written into every project saved for the rest of the session.
@@ -12663,7 +12766,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
sidebar->auto_calc_flushing_volumes(idx); sidebar->auto_calc_flushing_volumes(idx);
} }
auto select_flag = combo->GetFlag(selection); auto select_flag = combo->GetFlag(selection);
combo->ShowBadge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS); combo->set_sync_badge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS);
q->on_filament_change(idx); q->on_filament_change(idx);
} }
bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
@@ -14935,8 +15038,25 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
} }
// A background job holds the model it is working on: the texture displacement bake, for one, hands its
// result to the volume when it finishes, and it was queued against the geometry as it was at the time.
// Undoing while it runs restores an older state under it - a different transform, a different mesh -
// and the result then lands on geometry it was never computed for. Undo and redo therefore wait for
// the job, and say so rather than doing nothing.
bool Plater::priv::undo_redo_blocked_by_job()
{
if (m_worker.is_idle())
return false;
notification_manager->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
_u8L("Cannot undo or redo while an operation is running. Stop it first."));
return true;
}
void Plater::priv::undo() void Plater::priv::undo()
{ {
if (this->undo_redo_blocked_by_job())
return;
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots(); const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
// BBS: undo-redo until modify record // BBS: undo-redo until modify record
@@ -14954,6 +15074,8 @@ void Plater::priv::undo()
void Plater::priv::redo() void Plater::priv::redo()
{ {
if (this->undo_redo_blocked_by_job())
return;
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots(); const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
// BBS: undo-redo until modify record // BBS: undo-redo until modify record
@@ -14992,11 +15114,6 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
// Make sure that no updating function calls take_snapshot until we are done. // Make sure that no updating function calls take_snapshot until we are done.
SuppressSnapshots snapshot_supressor(q); SuppressSnapshots snapshot_supressor(q);
// Loading a snapshot deletes every PartPlate, which the slicing thread keeps dereferencing (its
// current plate and the status callback). Cancel it and wait for it to finish before the jump,
// update_after_undo_redo() re-applies the background process to the rebuilt plates afterwards.
this->background_process.stop();
bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active(); bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active();
PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology;
bool printer_technology_changed = this->printer_technology != new_printer_technology; bool printer_technology_changed = this->printer_technology != new_printer_technology;
@@ -16604,7 +16721,7 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config; auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
/// --- scale --- /// -- scale --
// model is created for a 0.4 nozzle, scale z with nozzle size. // model is created for a 0.4 nozzle, scale z with nozzle size.
const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter"); const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter");
std::vector<int> extruder_types = printer_config->option<ConfigOptionEnumsGeneric>("extruder_type")->values; std::vector<int> extruder_types = printer_config->option<ConfigOptionEnumsGeneric>("extruder_type")->values;
@@ -21081,6 +21198,33 @@ GLCanvas3D* Plater::get_assmeble_canvas3D()
return nullptr; return nullptr;
} }
UVEditorCanvas* Plater::get_uv_editor_canvas()
{
return p->uv_editor_canvas;
}
void Plater::show_uv_editor(bool show)
{
if (p->uv_editor_panel == nullptr)
return;
const wxAuiPaneInfo &pane = p->m_aui_mgr.GetPane(p->uv_editor_panel);
if (!pane.IsOk() || pane.IsShown() == show)
return;
// Deferred, because GLGizmoTextureDisplacement calls this from its ImGui panel - that is, from
// the middle of the 3D canvas's GL frame. Showing an AUI pane re-lays out the window and
// delivers the resulting size/paint events synchronously, and the UV canvas painting itself
// makes its own surface current in the app's *shared* GL context, which mid-frame is the one
// the 3D canvas is drawing into. Doing the layout once the frame is over avoids that entirely.
CallAfter([this, show]() {
wxAuiPaneInfo &deferred_pane = p->m_aui_mgr.GetPane(p->uv_editor_panel);
if (!deferred_pane.IsOk() || deferred_pane.IsShown() == show)
return;
deferred_pane.Show(show);
p->m_aui_mgr.Update();
});
}
GLCanvas3D* Plater::get_current_canvas3D(bool exclude_preview) GLCanvas3D* Plater::get_current_canvas3D(bool exclude_preview)
{ {
return p->get_current_canvas3D(exclude_preview); return p->get_current_canvas3D(exclude_preview);
@@ -21342,9 +21486,14 @@ bool Plater::is_same_printer_for_connected_and_selected(bool popup_warning)
} }
if (!check_printer_initialized(obj, true, popup_warning)) if (!check_printer_initialized(obj, true, popup_warning))
return false; return false;
Preset * machine_preset = get_printer_preset(obj); const std::string machine_model = obj->printer_type;
if (!machine_preset) PresetBundle *preset_bundle = wxGetApp().preset_bundle;
const std::string selected_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string();
if (!DevPrinterConfigUtil::is_optional_printer_model_id(machine_model) &&
!DevPrinterConfigUtil::is_optional_printer_model_id(selected_model) &&
!get_printer_preset(obj)) {
return false; return false;
}
if (wxGetApp().is_blocking_printing()) { if (wxGetApp().is_blocking_printing()) {
if (popup_warning) { if (popup_warning) {
@@ -22429,11 +22578,6 @@ int Plater::delete_plate(int plate_index)
if (plate_index == -1) if (plate_index == -1)
index = p->partplate_list.get_curr_plate_index(); index = p->partplate_list.get_curr_plate_index();
// Orca: delete_plate() destroys the plate's Print and GCodeResult, which the slicing thread is
// still working on, so it has to be stopped first. can_delete_plate() also refuses while slicing,
// but the plate grabber in the 3D scene does not go through it.
p->background_process.stop();
take_snapshot("delete partplate"); take_snapshot("delete partplate");
ret = p->partplate_list.delete_plate(index); ret = p->partplate_list.delete_plate(index);
@@ -22763,7 +22907,7 @@ bool Plater::can_delete() const { return p->can_delete(); }
bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_delete_all() const { return p->can_delete_all(); }
bool Plater::can_add_model() const { return !is_background_process_slicing(); } bool Plater::can_add_model() const { return !is_background_process_slicing(); }
bool Plater::can_add_plate() const { return !is_background_process_slicing() && p->can_add_plate(); } bool Plater::can_add_plate() const { return !is_background_process_slicing() && p->can_add_plate(); }
bool Plater::can_delete_plate() const { return !is_background_process_slicing() && p->can_delete_plate(); } bool Plater::can_delete_plate() const { return p->can_delete_plate(); }
bool Plater::can_increase_instances() const { return p->can_increase_instances(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); } bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); }
bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); } bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); }
@@ -22820,8 +22964,9 @@ bool Plater::can_copy_to_clipboard() const
return true; return true;
} }
bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_undo_snapshot(); } // The job check keeps the buttons in step with priv::undo()/redo(), which refuse while one runs.
bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_undo_snapshot(); }
bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
//BBS //BBS
bool Plater::can_fillcolor() const { return p->can_fillcolor(); } bool Plater::can_fillcolor() const { return p->can_fillcolor(); }
-2
View File
@@ -1025,8 +1025,6 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU
std::vector<std::string> previous_gcode_paths; std::vector<std::string> previous_gcode_paths;
plate_list.get_sliced_result(previous_slice_result, previous_gcode_paths); plate_list.get_sliced_result(previous_slice_result, previous_gcode_paths);
// The plates are dereferenced by the slicing thread, which the caller
// (Plater::priv::undo_redo_to) has stopped before loading the snapshot.
plate_list.reset(false); plate_list.reset(false);
this->load_mutable_object<Slic3r::GUI::PartPlateList>(plate_list.id(), plate_list); this->load_mutable_object<Slic3r::GUI::PartPlateList>(plate_list.id(), plate_list);
plate_list.rebuild_plates_after_deserialize(previous_slice_result, previous_gcode_paths); plate_list.rebuild_plates_after_deserialize(previous_slice_result, previous_gcode_paths);