mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 14:20:58 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbe342c978 |
@@ -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,
|
||||||
|
|||||||
@@ -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));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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(); }
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user