Merge branch 'master' into fs_dir_per_glyph

This commit is contained in:
Filip Sykala - NTB T15p 2023-05-26 09:23:20 +02:00
commit 6751bba96e
69 changed files with 40034 additions and 36486 deletions

View File

@ -2186,11 +2186,11 @@ msgid "Filament"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2361,11 +2361,11 @@ msgid "Filament"
msgstr "Філамент" msgstr "Філамент"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2404,11 +2404,11 @@ msgid "Filament"
msgstr "Filament" msgstr "Filament"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2195,11 +2195,11 @@ msgid "Filament"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2358,11 +2358,11 @@ msgid "Filament"
msgstr "Filament" msgstr "Filament"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2258,11 +2258,11 @@ msgid "Filament"
msgstr "필라멘트" msgstr "필라멘트"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2285,11 +2285,11 @@ msgid "Filament"
msgstr "필라멘트 설정을 선택" msgstr "필라멘트 설정을 선택"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2285,11 +2285,11 @@ msgid "Filament"
msgstr "필라멘트 설정을 선택" msgstr "필라멘트 설정을 선택"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2363,11 +2363,11 @@ msgid "Filament"
msgstr "Filament" msgstr "Filament"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

File diff suppressed because it is too large Load Diff

View File

@ -2361,11 +2361,11 @@ msgid "Filament"
msgstr "Filamento" msgstr "Filamento"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2396,11 +2396,11 @@ msgid "Filament"
msgstr "Профиль прутка" msgstr "Профиль прутка"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2366,11 +2366,11 @@ msgid "Filament"
msgstr "Filament" msgstr "Filament"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2311,11 +2311,11 @@ msgid "Filament"
msgstr "Філамент" msgstr "Філамент"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2275,11 +2275,11 @@ msgid "Filament"
msgstr "耗材" msgstr "耗材"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -2271,11 +2271,11 @@ msgid "Filament"
msgstr "線材" msgstr "線材"
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode" msgid "Hide Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954 #: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode" msgid "Show Custom G-code"
msgstr "" msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967 #: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

@ -20,7 +20,7 @@
namespace Slic3r { namespace Slic3r {
static constexpr const float NarrowInfillAreaThresholdMM = 3.f; //static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
struct SurfaceFillParams struct SurfaceFillParams
{ {

View File

@ -106,15 +106,15 @@ ThickPolylines make_fill_polylines(
coord_t length_filter = scale_(4); coord_t length_filter = scale_(4);
size_t skips_allowed = 2; size_t skips_allowed = 2;
size_t min_removal_conut = 5; size_t min_removal_conut = 5;
for (int section_idx = 0; section_idx < polygon_sections.size(); section_idx++) { for (int section_idx = 0; section_idx < int(polygon_sections.size()); ++ section_idx) {
for (int line_idx = 0; line_idx < polygon_sections[section_idx].size(); line_idx++) { for (int line_idx = 0; line_idx < int(polygon_sections[section_idx].size()); ++ line_idx) {
if (const Line &line = polygon_sections[section_idx][line_idx]; line.a != line.b && line.length() < length_filter) { if (const Line &line = polygon_sections[section_idx][line_idx]; line.a != line.b && line.length() < length_filter) {
std::set<std::pair<int, int>> to_remove{{section_idx, line_idx}}; std::set<std::pair<int, int>> to_remove{{section_idx, line_idx}};
std::vector<Node> to_visit{{section_idx, line_idx}}; std::vector<Node> to_visit{{section_idx, line_idx}};
bool initial_touches_long_lines = false; bool initial_touches_long_lines = false;
if (section_idx > 0) { if (section_idx > 0) {
for (int prev_line_idx = 0; prev_line_idx < polygon_sections[section_idx - 1].size(); prev_line_idx++) { for (int prev_line_idx = 0; prev_line_idx < int(polygon_sections[section_idx - 1].size()); ++ prev_line_idx) {
if (const Line &nl = polygon_sections[section_idx - 1][prev_line_idx]; if (const Line &nl = polygon_sections[section_idx - 1][prev_line_idx];
nl.a != nl.b && segments_overlap(line.a.y(), line.b.y(), nl.a.y(), nl.b.y())) { nl.a != nl.b && segments_overlap(line.a.y(), line.b.y(), nl.a.y(), nl.b.y())) {
initial_touches_long_lines = true; initial_touches_long_lines = true;
@ -127,7 +127,7 @@ ThickPolylines make_fill_polylines(
const Line &curr_l = polygon_sections[curr.section_idx][curr.line_idx]; const Line &curr_l = polygon_sections[curr.section_idx][curr.line_idx];
if (curr.neighbours_explored) { if (curr.neighbours_explored) {
bool is_valid_for_removal = (curr_l.length() < length_filter) && bool is_valid_for_removal = (curr_l.length() < length_filter) &&
((int(to_remove.size()) - curr.skips_taken > min_removal_conut) || ((int(to_remove.size()) - curr.skips_taken > int(min_removal_conut)) ||
(curr.neighbours.empty() && !initial_touches_long_lines)); (curr.neighbours.empty() && !initial_touches_long_lines));
if (!is_valid_for_removal) { if (!is_valid_for_removal) {
for (const auto &n : curr.neighbours) { for (const auto &n : curr.neighbours) {
@ -144,9 +144,9 @@ ThickPolylines make_fill_polylines(
} else { } else {
to_visit.back().neighbours_explored = true; to_visit.back().neighbours_explored = true;
int curr_index = to_visit.size() - 1; int curr_index = to_visit.size() - 1;
bool can_use_skip = curr_l.length() <= length_filter && curr.skips_taken < skips_allowed; bool can_use_skip = curr_l.length() <= length_filter && curr.skips_taken < int(skips_allowed);
if (curr.section_idx + 1 < polygon_sections.size()) { if (curr.section_idx + 1 < int(polygon_sections.size())) {
for (int lidx = 0; lidx < polygon_sections[curr.section_idx + 1].size(); lidx++) { for (int lidx = 0; lidx < int(polygon_sections[curr.section_idx + 1].size()); ++ lidx) {
if (const Line &nl = polygon_sections[curr.section_idx + 1][lidx]; if (const Line &nl = polygon_sections[curr.section_idx + 1][lidx];
nl.a != nl.b && segments_overlap(curr_l.a.y(), curr_l.b.y(), nl.a.y(), nl.b.y()) && nl.a != nl.b && segments_overlap(curr_l.a.y(), curr_l.b.y(), nl.a.y(), nl.b.y()) &&
(nl.length() < length_filter || can_use_skip)) { (nl.length() < length_filter || can_use_skip)) {

View File

@ -3909,7 +3909,7 @@ void GCodeProcessor::post_process()
std::stringstream ss(cmd.substr(1)); std::stringstream ss(cmd.substr(1));
int tool_number = -1; int tool_number = -1;
ss >> tool_number; ss >> tool_number;
if (tool_number != -1) if (tool_number != -1) {
if (tool_number < 0 || (int)m_extruder_temps_config.size() <= tool_number) { if (tool_number < 0 || (int)m_extruder_temps_config.size() <= tool_number) {
// found an invalid value, clamp it to a valid one // found an invalid value, clamp it to a valid one
tool_number = std::clamp<int>(0, m_extruder_temps_config.size() - 1, tool_number); tool_number = std::clamp<int>(0, m_extruder_temps_config.size() - 1, tool_number);
@ -3922,6 +3922,7 @@ void GCodeProcessor::post_process()
if (m_print != nullptr) if (m_print != nullptr)
m_print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning); m_print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning);
} }
}
export_lines.insert_lines(backtrace, cmd, export_lines.insert_lines(backtrace, cmd,
// line inserter // line inserter
[tool_number, this](unsigned int id, float time, float time_diff) { [tool_number, this](unsigned int id, float time, float time_diff) {

View File

@ -1364,14 +1364,9 @@ std::vector<std::vector<float>> WipeTower::extract_wipe_volumes(const PrintConfi
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders)); wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
// Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview. // Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview.
for (unsigned int i = 0; i<number_of_extruders; ++i) { for (unsigned int i = 0; i<number_of_extruders; ++i)
for (unsigned int j = 0; j<number_of_extruders; ++j) { for (unsigned int j = 0; j<number_of_extruders; ++j)
float w = wipe_volumes[i][j]; wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j], config.filament_minimal_purge_on_wipe_tower.get_at(j));
if (wipe_volumes[i][j] < config.filament_minimal_purge_on_wipe_tower.get_at(j))
wipe_volumes[i][j] = config.filament_minimal_purge_on_wipe_tower.get_at(j);
}
}
return wipe_volumes; return wipe_volumes;
} }

View File

@ -84,16 +84,16 @@ void Layer::make_slices()
co.MiterLimit = scaled<double>(3.); co.MiterLimit = scaled<double>(3.);
// Use the default zero edge merging distance. For this kind of safety offset the accuracy of normal direction is not important. // Use the default zero edge merging distance. For this kind of safety offset the accuracy of normal direction is not important.
// co.ShortestEdgeLength = delta * ClipperOffsetShortestEdgeFactor; // co.ShortestEdgeLength = delta * ClipperOffsetShortestEdgeFactor;
static constexpr const double accept_area_threshold_ccw = sqr(scaled<double>(0.1 * delta)); // static constexpr const double accept_area_threshold_ccw = sqr(scaled<double>(0.1 * delta));
// Such a small hole should not survive the shrinkage, it should grow over // Such a small hole should not survive the shrinkage, it should grow over
static constexpr const double accept_area_threshold_cw = sqr(scaled<double>(0.2 * delta)); // static constexpr const double accept_area_threshold_cw = sqr(scaled<double>(0.2 * delta));
for (const ExPolygon &expoly : expolygons) { for (const ExPolygon &expoly : expolygons) {
contours.clear(); contours.clear();
co.Clear(); co.Clear();
co.AddPath(expoly.contour.points, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); co.AddPath(expoly.contour.points, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
co.Execute(contours, - delta); co.Execute(contours, - delta);
size_t num_prev = out.size(); // size_t num_prev = out.size();
if (! contours.empty()) { if (! contours.empty()) {
holes.clear(); holes.clear();
for (const Polygon &hole : expoly.holes) { for (const Polygon &hole : expoly.holes) {
@ -447,7 +447,7 @@ static void connect_layer_slices(
for (int i = int(other_layer.lslices_ex.size()) - 1; i >= 0; -- i) for (int i = int(other_layer.lslices_ex.size()) - 1; i >= 0; -- i)
if (contour_aabb.overlap(other_layer.lslices_ex[i].bbox)) if (contour_aabb.overlap(other_layer.lslices_ex[i].bbox))
// it is potentially slow, but should be executed rarely // it is potentially slow, but should be executed rarely
if (Polygons overlap = intersection(contour_poly, other_layer.lslices[i]); ! overlap.empty()) if (Polygons overlap = intersection(contour_poly, other_layer.lslices[i]); ! overlap.empty()) {
if (other_has_duplicates) { if (other_has_duplicates) {
// Find the contour with the largest overlap. It is expected that the other overlap will be very small. // Find the contour with the largest overlap. It is expected that the other overlap will be very small.
double a = area(overlap); double a = area(overlap);
@ -460,6 +460,7 @@ static void connect_layer_slices(
i_largest = i; i_largest = i;
break; break;
} }
}
assert(i_largest >= 0); assert(i_largest >= 0);
return i_largest; return i_largest;
} }
@ -500,10 +501,10 @@ static void connect_layer_slices(
#endif // NDEBUG #endif // NDEBUG
// Scatter the links, but don't sort them yet. // Scatter the links, but don't sort them yet.
for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice) for (int32_t islice = 0; islice < int32_t(below.lslices_ex.size()); ++ islice)
for (LayerSlice::Link &link : below.lslices_ex[islice].overlaps_above) for (LayerSlice::Link &link : below.lslices_ex[islice].overlaps_above)
above.lslices_ex[link.slice_idx].overlaps_below.push_back({ islice, link.area }); above.lslices_ex[link.slice_idx].overlaps_below.push_back({ islice, link.area });
for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) for (int32_t islice = 0; islice < int32_t(above.lslices_ex.size()); ++ islice)
for (LayerSlice::Link &link : above.lslices_ex[islice].overlaps_below) for (LayerSlice::Link &link : above.lslices_ex[islice].overlaps_below)
below.lslices_ex[link.slice_idx].overlaps_above.push_back({ islice, link.area }); below.lslices_ex[link.slice_idx].overlaps_above.push_back({ islice, link.area });
// Sort the links. // Sort the links.
@ -935,7 +936,7 @@ void Layer::sort_perimeters_into_islands(
island.fill_region_id = LayerIsland::fill_region_composite_id; island.fill_region_id = LayerIsland::fill_region_composite_id;
for (uint32_t fill_idx : fill_range) { for (uint32_t fill_idx : fill_range) {
if (const int fill_regon_id = map_expolygon_to_region_and_fill[fill_idx].region_id; if (const int fill_regon_id = map_expolygon_to_region_and_fill[fill_idx].region_id;
fill_regon_id == -1 || (island.fill_region_id != LayerIsland::fill_region_composite_id && island.fill_region_id != fill_regon_id)) { fill_regon_id == -1 || (island.fill_region_id != LayerIsland::fill_region_composite_id && int(island.fill_region_id) != fill_regon_id)) {
island.fill_region_id = LayerIsland::fill_region_composite_id; island.fill_region_id = LayerIsland::fill_region_composite_id;
break; break;
} else } else

View File

@ -376,7 +376,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Expand the top / bottom / bridge surfaces into the shell thickness solid infills. // Expand the top / bottom / bridge surfaces into the shell thickness solid infills.
double layer_thickness; double layer_thickness;
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, {stInternalSolid, stTop}, layer_thickness)); ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, {stInternalSolid}, layer_thickness));
SurfaceCollection bridges; SurfaceCollection bridges;
{ {

View File

@ -23,6 +23,8 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/nowide/iostream.hpp> #include <boost/nowide/iostream.hpp>
#include <tbb/concurrent_vector.h>
#include "SVG.hpp" #include "SVG.hpp"
#include <Eigen/Dense> #include <Eigen/Dense>
#include "GCodeWriter.hpp" #include "GCodeWriter.hpp"
@ -1055,12 +1057,18 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
// This method is used by the auto arrange function. // This method is used by the auto arrange function.
Polygon ModelObject::convex_hull_2d(const Transform3d& trafo_instance) const Polygon ModelObject::convex_hull_2d(const Transform3d& trafo_instance) const
{ {
Points pts; tbb::concurrent_vector<Polygon> chs;
for (const ModelVolume* v : volumes) { chs.reserve(volumes.size());
if (v->is_model_part()) tbb::parallel_for(tbb::blocked_range<size_t>(0, volumes.size()), [&](const tbb::blocked_range<size_t>& range) {
append(pts, its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast<float>(), 0.0f).points); for (size_t i = range.begin(); i < range.end(); ++i) {
const ModelVolume* v = volumes[i];
chs.emplace_back(its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast<float>(), 0.0f));
} }
return Geometry::convex_hull(std::move(pts)); });
Polygons polygons;
polygons.assign(chs.begin(), chs.end());
return Geometry::convex_hull(polygons);
} }
void ModelObject::center_around_origin(bool include_modifiers) void ModelObject::center_around_origin(bool include_modifiers)

View File

@ -188,7 +188,7 @@ public:
void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
std::vector<coordf_t> get() const throw() { return m_data; } const std::vector<coordf_t>& get() const throw() { return m_data; }
bool empty() const throw() { return m_data.empty(); } bool empty() const throw() { return m_data.empty(); }
void set(const std::vector<coordf_t> &data) { if (m_data != data) { m_data = data; this->touch(); } } void set(const std::vector<coordf_t> &data) { if (m_data != data) { m_data = data; this->touch(); } }
void set(std::vector<coordf_t> &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } } void set(std::vector<coordf_t> &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } }

View File

@ -582,7 +582,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
} }
// Prefer non-overhang point as a starting point. // Prefer non-overhang point as a starting point.
for (const std::pair<Point, PointInfo> pt : point_occurrence) for (const std::pair<Point, PointInfo> &pt : point_occurrence)
if (pt.second.occurrence == 1) { if (pt.second.occurrence == 1) {
start_point = pt.first; start_point = pt.first;
if (!pt.second.is_overhang) { if (!pt.second.is_overhang) {
@ -743,7 +743,7 @@ ExtrusionPaths sort_extra_perimeters(const ExtrusionPaths& extra_perims, int ind
} }
std::vector<bool> processed(extra_perims.size(), false); std::vector<bool> processed(extra_perims.size(), false);
for (size_t path_idx = 0; path_idx < index_of_first_unanchored; path_idx++) { for (int path_idx = 0; path_idx < index_of_first_unanchored; path_idx++) {
processed[path_idx] = true; processed[path_idx] = true;
} }

View File

@ -301,7 +301,7 @@ void ThickPolyline::start_at_index(int index)
{ {
assert(index >= 0 && index < this->points.size()); assert(index >= 0 && index < this->points.size());
assert(this->points.front() == this->points.back() && this->width.front() == this->width.back()); assert(this->points.front() == this->points.back() && this->width.front() == this->width.back());
if (index != 0 && index != (this->points.size() - 1) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) { if (index != 0 && index + 1 != int(this->points.size()) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) {
this->points.pop_back(); this->points.pop_back();
assert(this->points.size() * 2 == this->width.size()); assert(this->points.size() * 2 == this->width.size());
std::rotate(this->points.begin(), this->points.begin() + index, this->points.end()); std::rotate(this->points.begin(), this->points.begin() + index, this->points.end());

View File

@ -469,11 +469,13 @@ std::string Print::validate(std::string* warning) const
return _u8L("The supplied settings will cause an empty print."); return _u8L("The supplied settings will cause an empty print.");
if (m_config.complete_objects) { if (m_config.complete_objects) {
if (! sequential_print_horizontal_clearance_valid(*this)) if (!sequential_print_horizontal_clearance_valid(*this, const_cast<Polygons*>(&m_sequential_print_clearance_contours)))
return _u8L("Some objects are too close; your extruder will collide with them."); return _u8L("Some objects are too close; your extruder will collide with them.");
if (! sequential_print_vertical_clearance_valid(*this)) if (!sequential_print_vertical_clearance_valid(*this))
return _u8L("Some objects are too tall and cannot be printed without extruder collisions."); return _u8L("Some objects are too tall and cannot be printed without extruder collisions.");
} }
else
const_cast<Polygons*>(&m_sequential_print_clearance_contours)->clear();
if (m_config.avoid_crossing_perimeters && m_config.avoid_crossing_curled_overhangs) { if (m_config.avoid_crossing_perimeters && m_config.avoid_crossing_curled_overhangs) {
return _u8L("Avoid crossing perimeters option and avoid crossing curled overhangs option cannot be both enabled together."); return _u8L("Avoid crossing perimeters option and avoid crossing curled overhangs option cannot be both enabled together.");
@ -1227,7 +1229,7 @@ void Print::alert_when_supports_needed()
} }
std::string translated_list = expansion_rule; std::string translated_list = expansion_rule;
for (int i = 0; i < translated_elements.size() - 1; i++) { for (int i = 0; i < int(translated_elements.size()) - 1; ++ i) {
auto first_elem = translated_list.find("%1%"); auto first_elem = translated_list.find("%1%");
assert(first_elem != translated_list.npos); assert(first_elem != translated_list.npos);
translated_list.replace(first_elem, 3, translated_elements[i]); translated_list.replace(first_elem, 3, translated_elements[i]);
@ -1235,7 +1237,7 @@ void Print::alert_when_supports_needed()
// expand the translated list by another application of the same rule // expand the translated list by another application of the same rule
auto second_elem = translated_list.find("%2%"); auto second_elem = translated_list.find("%2%");
assert(second_elem != translated_list.npos); assert(second_elem != translated_list.npos);
if (i < translated_elements.size() - 2) { if (i < int(translated_elements.size()) - 2) {
translated_list.replace(second_elem, 3, expansion_rule); translated_list.replace(second_elem, 3, expansion_rule);
} else { } else {
translated_list.replace(second_elem, 3, translated_elements[i + 1]); translated_list.replace(second_elem, 3, translated_elements[i + 1]);

View File

@ -609,6 +609,7 @@ public:
const PrintRegion& get_print_region(size_t idx) const { return *m_print_regions[idx]; } const PrintRegion& get_print_region(size_t idx) const { return *m_print_regions[idx]; }
const ToolOrdering& get_tool_ordering() const { return m_wipe_tower_data.tool_ordering; } const ToolOrdering& get_tool_ordering() const { return m_wipe_tower_data.tool_ordering; }
const Polygons& get_sequential_print_clearance_contours() const { return m_sequential_print_clearance_contours; }
static bool sequential_print_horizontal_clearance_valid(const Print& print, Polygons* polygons = nullptr); static bool sequential_print_horizontal_clearance_valid(const Print& print, Polygons* polygons = nullptr);
protected: protected:
@ -658,6 +659,9 @@ private:
// Estimated print time, filament consumed. // Estimated print time, filament consumed.
PrintStatistics m_print_statistics; PrintStatistics m_print_statistics;
// Cache to store sequential print clearance contours
Polygons m_sequential_print_clearance_contours;
// To allow GCode to set the Print's GCodeExport step status. // To allow GCode to set the Print's GCodeExport step status.
friend class GCode; friend class GCode;
// To allow GCodeProcessor to emit warnings. // To allow GCodeProcessor to emit warnings.

View File

@ -1678,7 +1678,7 @@ void PrintObject::bridge_over_infill()
} }
} }
} }
unsupported_area = closing(unsupported_area, SCALED_EPSILON); unsupported_area = closing(unsupported_area, float(SCALED_EPSILON));
// By expanding the lower layer solids, we avoid making bridges from the tiny internal overhangs that are (very likely) supported by previous layer solids // By expanding the lower layer solids, we avoid making bridges from the tiny internal overhangs that are (very likely) supported by previous layer solids
// NOTE that we cannot filter out polygons worth bridging by their area, because sometimes there is a very small internal island that will grow into large hole // NOTE that we cannot filter out polygons worth bridging by their area, because sometimes there is a very small internal island that will grow into large hole
lower_layer_solids = shrink(lower_layer_solids, 1 * spacing); // first remove thin regions that will not support anything lower_layer_solids = shrink(lower_layer_solids, 1 * spacing); // first remove thin regions that will not support anything
@ -1703,7 +1703,7 @@ void PrintObject::bridge_over_infill()
worth_bridging.push_back(p); worth_bridging.push_back(p);
} }
} }
worth_bridging = intersection(closing(worth_bridging, SCALED_EPSILON), s->expolygon); worth_bridging = intersection(closing(worth_bridging, float(SCALED_EPSILON)), s->expolygon);
candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0)); candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0));
#ifdef DEBUG_BRIDGE_OVER_INFILL #ifdef DEBUG_BRIDGE_OVER_INFILL
@ -1860,7 +1860,7 @@ void PrintObject::bridge_over_infill()
// cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another // cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another
std::vector<std::vector<size_t>> clustered_layers_for_threads; std::vector<std::vector<size_t>> clustered_layers_for_threads;
float target_flow_height_factor = 0.9; float target_flow_height_factor = 0.9f;
{ {
std::vector<size_t> layers_with_candidates; std::vector<size_t> layers_with_candidates;
std::map<size_t, Polygons> layer_area_covered_by_candidates; std::map<size_t, Polygons> layer_area_covered_by_candidates;
@ -1937,9 +1937,9 @@ void PrintObject::bridge_over_infill()
} }
} }
layers_sparse_infill = union_ex(layers_sparse_infill); layers_sparse_infill = union_ex(layers_sparse_infill);
layers_sparse_infill = closing_ex(layers_sparse_infill, SCALED_EPSILON); layers_sparse_infill = closing_ex(layers_sparse_infill, float(SCALED_EPSILON));
not_sparse_infill = union_ex(not_sparse_infill); not_sparse_infill = union_ex(not_sparse_infill);
not_sparse_infill = closing_ex(not_sparse_infill, SCALED_EPSILON); not_sparse_infill = closing_ex(not_sparse_infill, float(SCALED_EPSILON));
return diff(layers_sparse_infill, not_sparse_infill); return diff(layers_sparse_infill, not_sparse_infill);
}; };
@ -2276,8 +2276,8 @@ void PrintObject::bridge_over_infill()
lightning_area.insert(lightning_area.end(), l.begin(), l.end()); lightning_area.insert(lightning_area.end(), l.begin(), l.end());
} }
} }
total_fill_area = closing(total_fill_area, SCALED_EPSILON); total_fill_area = closing(total_fill_area, float(SCALED_EPSILON));
expansion_area = closing(expansion_area, SCALED_EPSILON); expansion_area = closing(expansion_area, float(SCALED_EPSILON));
expansion_area = intersection(expansion_area, deep_infill_area); expansion_area = intersection(expansion_area, deep_infill_area);
Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing));
Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5); Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5);
@ -2595,7 +2595,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c
if (layer_height_profile.empty()) { if (layer_height_profile.empty()) {
// use the constructor because the assignement is crashing on ASAN OsX // use the constructor because the assignement is crashing on ASAN OsX
layer_height_profile = std::vector<coordf_t>(model_object.layer_height_profile.get()); layer_height_profile = model_object.layer_height_profile.get();
// layer_height_profile = model_object.layer_height_profile; // layer_height_profile = model_object.layer_height_profile;
// The layer height returned is sampled with high density for the UI layer height painting // The layer height returned is sampled with high density for the UI layer height painting
// and smoothing tool to work. // and smoothing tool to work.

View File

@ -186,7 +186,7 @@ std::vector<coordf_t> layer_height_profile_from_ranges(
auto last_z = [&layer_height_profile]() { auto last_z = [&layer_height_profile]() {
return layer_height_profile.empty() ? 0. : *(layer_height_profile.end() - 2); return layer_height_profile.empty() ? 0. : *(layer_height_profile.end() - 2);
}; };
auto lh_append = [&layer_height_profile, last_z](coordf_t z, coordf_t layer_height) { auto lh_append = [&layer_height_profile](coordf_t z, coordf_t layer_height) {
if (! layer_height_profile.empty()) { if (! layer_height_profile.empty()) {
bool last_z_matches = is_approx(*(layer_height_profile.end() - 2), z); bool last_z_matches = is_approx(*(layer_height_profile.end() - 2), z);
bool last_h_matches = is_approx(layer_height_profile.back(), layer_height); bool last_h_matches = is_approx(layer_height_profile.back(), layer_height);

View File

@ -26,6 +26,433 @@ namespace Slic3r
namespace FFFTreeSupport namespace FFFTreeSupport
{ {
// Single slice through a single branch or trough a number of branches.
struct Slice
{
// All polygons collected for this slice.
Polygons polygons;
// All bottom contacts collected for this slice.
Polygons bottom_contacts;
// How many branches were merged in this slice? Used to decide whether ClipperLib union is needed.
size_t num_branches{ 0 };
};
struct Element
{
// Current position of the centerline including the Z coordinate, unscaled.
Vec3f position;
float radius;
// Index of this layer, including the raft layers.
LayerIndex layer_idx;
// Limits where the centerline could be placed at the current layer Z.
Polygons influence_area;
// Locked node should not be moved. Locked nodes are at the top of an object or at the tips of branches.
bool locked;
// Previous position, for Laplacian smoothing, unscaled.
Vec3f prev_position;
// For sphere tracing and other collision detection optimizations.
Vec3f last_collision;
double last_collision_depth;
struct CollisionSphere {
// Minimum Z for which the sphere collision will be evaluated.
// Limited by the minimum sloping angle and by the bottom of the tree.
float min_z{ -std::numeric_limits<float>::max() };
// Maximum Z for which the sphere collision will be evaluated.
// Limited by the minimum sloping angle and by the tip of the current branch.
float max_z{ std::numeric_limits<float>::max() };
// Span of layers to test collision of this sphere against.
uint32_t layer_begin;
uint32_t layer_end;
};
CollisionSphere collision_sphere;
};
struct Branch;
struct Bifurcation
{
Branch *branch;
double area;
};
// Single branch of a tree.
struct Branch
{
std::vector<Element> path;
using Bifurcations =
#ifdef NDEBUG
// To reduce memory allocation in release mode.
boost::container::small_vector<Bifurcation, 4>;
#else // NDEBUG
// To ease debugging.
std::vector<Bifurcation>;
#endif // NDEBUG
Bifurcations up;
Bifurcation down;
// How many of the thick up branches are considered continuation of the trunk?
// These will be smoothed out together.
size_t num_up_trunk;
bool has_root() const { return this->down.branch == nullptr; }
bool has_tip() const { return this->up.empty(); }
};
struct Tree
{
// Branches: Store of all branches.
// The first branch is the root of the tree.
Slic3r::deque<Branch> branches;
Branch& root() { return branches.front(); }
const Branch& root() const { return branches.front(); }
// Result of slicing the branches.
std::vector<Slice> slices;
// First layer index of the first slice in the vector above.
LayerIndex first_layer_id{ -1 };
};
using Forest = std::vector<Tree>;
using Trees = std::vector<Tree>;
Element to_tree_element(const TreeSupportSettings &config, const SlicingParameters &slicing_params, SupportElement &element, bool is_root)
{
Element out;
out.position = to_3d(unscaled<float>(element.state.result_on_layer), float(layer_z(slicing_params, config, element.state.layer_idx)));
out.radius = support_element_radius(config, element);
out.layer_idx = element.state.layer_idx;
out.influence_area = std::move(element.influence_area);
out.locked = (is_root && element.state.layer_idx > 0) || element.state.locked();
return out;
}
// Convert move bounds into a forest of trees, each tree made of a graph of branches and bifurcation points.
// Destroys move_bounds.
Forest make_forest(const TreeSupportSettings &config, const SlicingParameters &slicing_params, std::vector<SupportElements> &&move_bounds)
{
struct TreeVisitor {
void visit_recursive(std::vector<SupportElements> &move_bounds, SupportElement &start_element, Branch *parent_branch, Tree &out) const {
assert(! start_element.state.marked && ! start_element.parents.empty());
// Collect elements up to a bifurcation above.
start_element.state.marked = true;
// For each branch bifurcating from this point:
// SupportElements &layer = move_bounds[start_element.state.layer_idx];
SupportElements &layer_above = move_bounds[start_element.state.layer_idx + 1];
for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++ parent_idx) {
Branch branch;
if (parent_branch)
// Duplicate the last element of the trunk below.
// If this branch has a smaller diameter than the trunk below, its centerline will not be aligned with the centerline of the trunk.
branch.path.emplace_back(parent_branch->path.back());
branch.path.emplace_back(to_tree_element(config, slicing_params, start_element, parent_branch == nullptr));
// Traverse each branch until it branches again.
SupportElement &first_parent = layer_above[start_element.parents[parent_idx]];
assert(! first_parent.state.marked);
assert(branch.path.back().layer_idx + 1 == first_parent.state.layer_idx);
branch.path.emplace_back(to_tree_element(config, slicing_params, first_parent, false));
if (first_parent.parents.size() < 2)
first_parent.state.marked = true;
SupportElement *next_branch = nullptr;
if (first_parent.parents.size() == 1) {
for (SupportElement *parent = &first_parent;;) {
assert(parent->state.marked);
SupportElement &next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()];
assert(! next_parent.state.marked);
assert(branch.path.back().layer_idx + 1 == next_parent.state.layer_idx);
branch.path.emplace_back(to_tree_element(config, slicing_params, next_parent, false));
if (next_parent.parents.size() > 1) {
// Branching point was reached.
next_branch = &next_parent;
break;
}
next_parent.state.marked = true;
if (next_parent.parents.size() == 0)
// Tip is reached.
break;
parent = &next_parent;
}
} else if (first_parent.parents.size() > 1)
// Branching point was reached.
next_branch = &first_parent;
assert(branch.path.size() >= 2);
assert(next_branch == nullptr || ! next_branch->state.marked);
out.branches.emplace_back(std::move(branch));
Branch *pbranch = &out.branches.back();
if (parent_branch) {
parent_branch->up.push_back({ pbranch });
pbranch->down = { parent_branch };
}
if (next_branch)
this->visit_recursive(move_bounds, *next_branch, pbranch, out);
}
if (parent_branch) {
// Update initial radii of thin branches merging with a trunk.
auto it_up_max_r = std::max_element(parent_branch->up.begin(), parent_branch->up.end(),
[](const Bifurcation &l, const Bifurcation &r){ return l.branch->path[1].radius < r.branch->path[1].radius; });
const float r1 = it_up_max_r->branch->path[1].radius;
const float radius_increment = unscaled<float>(config.branch_radius_increase_per_layer);
for (auto it = parent_branch->up.begin(); it != parent_branch->up.end(); ++ it)
if (it != it_up_max_r) {
Element &el = it->branch->path.front();
Element &el2 = it->branch->path[1];
if (! is_approx(r1, el2.radius))
el.radius = std::min(el.radius, el2.radius + radius_increment);
}
// Sort children of parent_branch by decreasing radius.
std::sort(parent_branch->up.begin(), parent_branch->up.end(),
[](const Bifurcation &l, const Bifurcation &r){ return l.branch->path.front().radius > r.branch->path.front().radius; });
// Update number of branches to be considered a continuation of the trunk during smoothing.
{
const float r_trunk = 0.75 * it_up_max_r->branch->path.front().radius;
parent_branch->num_up_trunk = 0;
for (const Bifurcation& up : parent_branch->up)
if (up.branch->path.front().radius < r_trunk)
break;
else
++ parent_branch->num_up_trunk;
}
}
}
const TreeSupportSettings &config;
const SlicingParameters &slicing_params;
};
TreeVisitor visitor{ config, slicing_params };
for (SupportElements &elements : move_bounds)
for (SupportElement &el : elements)
el.state.marked = false;
Trees trees;
for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) {
for (SupportElement &start_element : move_bounds[layer_idx]) {
if (! start_element.state.marked && ! start_element.parents.empty()) {
#if 0
{
// Verify that this node is a root, such that there is no element in the layer below
// that points to it.
int ielement = &start_element - move_bounds.data();
int found = 0;
if (layer_idx > 0) {
for (auto &el : move_bounds[layer_idx - 1]) {
for (auto iparent : el.parents)
if (iparent == ielement)
++ found;
}
if (found != 0)
printf("Found: %d\n", found);
}
}
#endif
trees.push_back({});
visitor.visit_recursive(move_bounds, start_element, nullptr, trees.back());
assert(! trees.back().branches.empty());
assert(! trees.back().branches.front().path.empty());
#if 0
// Debugging: Only build trees with specific properties.
if (start_element.state.lost) {
}
else if (start_element.state.verylost) {
}
else
trees.pop_back();
#endif
}
}
}
#if 1
move_bounds.clear();
#else
for (SupportElements &elements : move_bounds)
for (SupportElement &el : elements)
el.state.marked = false;
#endif
return trees;
}
// Move bounds were propagated top to bottom. At each joint of branches the move bounds were reduced significantly.
// Now reflect the reduction of tree space by propagating the reduction of tree centerline space
// bottom-up starting with the bottom-most joint.
void trim_influence_areas_bottom_up(Forest &forest, const float dxy_dlayer)
{
struct Trimmer {
static void trim_recursive(Branch &branch, const float delta_r, const float dxy_dlayer) {
assert(delta_r >= 0);
if (delta_r > 0)
branch.path.front().influence_area = offset(branch.path.front().influence_area, delta_r);
for (size_t i = 1; i < branch.path.size(); ++ i)
branch.path[i].influence_area = intersection(branch.path[i].influence_area, offset(branch.path[i - 1].influence_area, dxy_dlayer));
const float r0 = branch.path.back().radius;
for (Bifurcation &up : branch.up) {
up.branch->path.front().influence_area = branch.path.back().influence_area;
trim_recursive(*up.branch, r0 - up.branch->path.front().radius, dxy_dlayer);
}
}
};
for (Tree &tree : forest) {
Branch &root = tree.root();
const float r0 = root.path.back().radius;
for (Bifurcation &up : root.up)
Trimmer::trim_recursive(*up.branch, r0 - up.branch->path.front().radius, dxy_dlayer);
}
}
// Straighten up and smooth centerlines inside their influence areas.
void smooth_trees_inside_influence_areas(Branch &root, bool is_root)
{
// Smooth the subtree:
//
// Apply laplacian and bilaplacian smoothing inside a branch,
// apply laplacian smoothing only at a bifurcation point.
//
// Applying a bilaplacian smoothing inside a branch should ensure curvature of the brach to be lower
// than the radius at each particular point of the centerline,
// while omitting bilaplacian smoothing at bifurcation points will create sharp bifurcations.
// Sharp bifurcations have a smaller volume, but just a tiny bit larger surfaces than smooth bifurcations
// where each continuation of the trunk satifies the path radius > centerline element radius.
const size_t num_iterations = 100;
struct StackElement {
Branch &branch;
size_t idx_up;
};
std::vector<StackElement> stack;
auto adjust_position = [](Element &el, Vec2f new_pos) {
Point new_pos_scaled = scaled<coord_t>(new_pos);
if (! contains(el.influence_area, new_pos_scaled)) {
int64_t min_dist = std::numeric_limits<int64_t>::max();
Point min_proj_scaled;
for (const Polygon& polygon : el.influence_area) {
Point proj_scaled = polygon.point_projection(new_pos_scaled);
if (int64_t dist = (proj_scaled - new_pos_scaled).cast<int64_t>().squaredNorm(); dist < min_dist) {
min_dist = dist;
min_proj_scaled = proj_scaled;
}
}
new_pos = unscaled<float>(min_proj_scaled);
}
el.position.head<2>() = new_pos;
};
for (size_t iter = 0; iter < num_iterations; ++ iter) {
// 1) Back-up the current positions.
stack.push_back({ root, 0 });
while (! stack.empty()) {
StackElement &state = stack.back();
if (state.idx_up == state.branch.num_up_trunk) {
// Process this path.
for (auto &el : state.branch.path)
el.prev_position = el.position;
stack.pop_back();
} else {
// Open another up node of this branch.
stack.push_back({ *state.branch.up[state.idx_up].branch, 0 });
++ state.idx_up;
}
}
// 2) Calculate new position.
stack.push_back({ root, 0 });
while (! stack.empty()) {
StackElement &state = stack.back();
if (state.idx_up == state.branch.num_up_trunk) {
// Process this path.
for (size_t i = 1; i + 1 < state.branch.path.size(); ++ i)
if (auto &el = state.branch.path[i]; ! el.locked) {
// Laplacian smoothing with 0.5 weight.
const Vec3f &p0 = state.branch.path[i - 1].prev_position;
const Vec3f &p1 = el.prev_position;
const Vec3f &p2 = state.branch.path[i + 1].prev_position;
adjust_position(el, 0.5 * p1.head<2>() + 0.25 * (p0.head<2>() + p2.head<2>()));
#if 0
// Only apply bilaplacian smoothing if the current curvature is smaller than el.radius.
// Interpolate p0, p1, p2 with a circle.
// First project p0, p1, p2 into a common plane.
const Vec3f n = (p1 - p0).cross(p2 - p1);
const Vec3f y = Vec3f(n.y(), n.x(), 0).normalized();
const Vec2f q0{ p0.z(), p0.dot(y) };
const Vec2f q1{ p1.z(), p1.dot(y) };
const Vec2f q2{ p2.z(), p2.dot(y) };
// Interpolate q0, q1, q2 with a circle, calculate its radius.
Vec2f b = q1 - q0;
Vec2f c = q2 - q0;
float lb = b.squaredNorm();
float lc = c.squaredNorm();
if (float d = b.x() * c.y() - b.y() * c.x(); std::abs(d) > EPSILON) {
Vec2f v = lc * b - lb * c;
float r2 = 0.25f * v.squaredNorm() / sqr(d);
if (r2 )
}
#endif
}
{
// Laplacian smoothing with 0.5 weight, branching point.
float weight = 0;
Vec2f new_pos = Vec2f::Zero();
for (size_t i = 0; i < state.branch.num_up_trunk; ++i) {
const Element &el = state.branch.up[i].branch->path.front();
new_pos += el.prev_position.head<2>();
weight += el.radius;
}
{
const Element &el = state.branch.path[state.branch.path.size() - 2];
new_pos += el.prev_position.head<2>();
weight *= 2.f;
}
adjust_position(state.branch.path.back(), 0.5f * state.branch.path.back().prev_position.head<2>() + 0.5f * weight * new_pos);
}
stack.pop_back();
} else {
// Open another up node of this branch.
stack.push_back({ *state.branch.up[state.idx_up].branch, 0 });
++ state.idx_up;
}
}
}
// Also smoothen start of the path.
if (Element &first = root.path.front(); ! first.locked) {
Element &second = root.path[1];
Vec2f new_pos = 0.75f * first.prev_position.head<2>() + 0.25f * second.prev_position.head<2>();
if (is_root)
// Let the root of the tree float inside its influence area.
adjust_position(first, new_pos);
else {
// Keep the start of a thin branch inside the trunk.
const Element &trunk = root.down.branch->path.back();
const float rdif = trunk.radius - root.path.front().radius;
assert(rdif >= 0);
Vec2f vdif = new_pos - trunk.prev_position.head<2>();
float ldif = vdif.squaredNorm();
if (ldif > sqr(rdif))
// Clamp new position.
new_pos = trunk.prev_position.head<2>() + vdif * rdif / sqrt(ldif);
first.position.head<2>() = new_pos;
}
}
}
void smooth_trees_inside_influence_areas(Forest &forest)
{
// Parallel for!
for (Tree &tree : forest)
smooth_trees_inside_influence_areas(tree.root(), true);
}
#if 0
// Test whether two circles, each on its own plane in 3D intersect. // Test whether two circles, each on its own plane in 3D intersect.
// Circles are considered intersecting, if the lowest point on one circle is below the other circle's plane. // Circles are considered intersecting, if the lowest point on one circle is below the other circle's plane.
// Assumption: The two planes are oriented the same way. // Assumption: The two planes are oriented the same way.
@ -46,6 +473,7 @@ static bool circles_intersect(
assert(n1.dot(p2) >= n1.dot(lowest_point2)); assert(n1.dot(p2) >= n1.dot(lowest_point2));
return n1.dot(lowest_point2) <= 0; return n1.dot(lowest_point2) <= 0;
} }
#endif
template<bool flip_normals> template<bool flip_normals>
void triangulate_fan(indexed_triangle_set &its, int ifan, int ibegin, int iend) void triangulate_fan(indexed_triangle_set &its, int ifan, int ibegin, int iend)
@ -174,7 +602,8 @@ static std::pair<float, float> extrude_branch(
// char fname[2048]; // char fname[2048];
// static int irun = 0; // static int irun = 0;
float zmin, zmax; float zmin = 0;
float zmax = 0;
for (size_t ipath = 1; ipath < path.size(); ++ ipath) { for (size_t ipath = 1; ipath < path.size(); ++ ipath) {
const SupportElement &prev = *path[ipath - 1]; const SupportElement &prev = *path[ipath - 1];
@ -390,7 +819,7 @@ static void organic_smooth_branches_avoid_collisions(
collision_sphere.prev_position = collision_sphere.position; collision_sphere.prev_position = collision_sphere.position;
std::atomic<size_t> num_moved{ 0 }; std::atomic<size_t> num_moved{ 0 };
tbb::parallel_for(tbb::blocked_range<size_t>(0, collision_spheres.size()), tbb::parallel_for(tbb::blocked_range<size_t>(0, collision_spheres.size()),
[&collision_spheres, &layer_collision_cache, &slicing_params, &config, &move_bounds, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range<size_t> range) { [&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range<size_t> range) {
for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id) for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id)
if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) { if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) {
// Calculate collision of multiple 2D layers against a collision sphere. // Calculate collision of multiple 2D layers against a collision sphere.
@ -426,7 +855,7 @@ static void organic_smooth_branches_avoid_collisions(
} }
// Laplacian smoothing // Laplacian smoothing
Vec2d avg{ 0, 0 }; Vec2d avg{ 0, 0 };
const SupportElements &above = move_bounds[collision_sphere.element.state.layer_idx + 1]; //const SupportElements &above = move_bounds[collision_sphere.element.state.layer_idx + 1];
const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1]; const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1];
double weight = 0.; double weight = 0.;
for (auto iparent : collision_sphere.element.parents) { for (auto iparent : collision_sphere.element.parents) {
@ -599,7 +1028,7 @@ void organic_draw_branches(
std::vector<std::pair<SupportElement*, int>> map_downwards_new; std::vector<std::pair<SupportElement*, int>> map_downwards_new;
linear_data_layers.emplace_back(0); linear_data_layers.emplace_back(0);
for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) {
SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr;
map_downwards_new.clear(); map_downwards_new.clear();
std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto& l, auto& r) { return l.first < r.first; }); std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto& l, auto& r) { return l.first < r.first; });
SupportElements &layer = move_bounds[layer_idx]; SupportElements &layer = move_bounds[layer_idx];
@ -675,7 +1104,7 @@ void organic_draw_branches(
// Collect elements up to a bifurcation above. // Collect elements up to a bifurcation above.
start_element.state.marked = true; start_element.state.marked = true;
// For each branch bifurcating from this point: // For each branch bifurcating from this point:
SupportElements &layer = move_bounds[start_element.state.layer_idx]; //SupportElements &layer = move_bounds[start_element.state.layer_idx];
SupportElements &layer_above = move_bounds[start_element.state.layer_idx + 1]; SupportElements &layer_above = move_bounds[start_element.state.layer_idx + 1];
bool root = out.branches.empty(); bool root = out.branches.empty();
for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++ parent_idx) { for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++ parent_idx) {
@ -741,7 +1170,7 @@ void organic_draw_branches(
TreeVisitor::visit_recursive(move_bounds, start_element, trees.back()); TreeVisitor::visit_recursive(move_bounds, start_element, trees.back());
assert(!trees.back().branches.empty()); assert(!trees.back().branches.empty());
//FIXME debugging //FIXME debugging
#if 1 #if 0
if (start_element.state.lost) { if (start_element.state.lost) {
} }
else if (start_element.state.verylost) { else if (start_element.state.verylost) {
@ -758,7 +1187,7 @@ void organic_draw_branches(
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
tbb::parallel_for(tbb::blocked_range<size_t>(0, trees.size(), 1), tbb::parallel_for(tbb::blocked_range<size_t>(0, trees.size(), 1),
[&trees, &volumes, &config, &slicing_params, &move_bounds, &interface_placer, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range<size_t> &range) { [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range<size_t> &range) {
indexed_triangle_set partial_mesh; indexed_triangle_set partial_mesh;
std::vector<float> slice_z; std::vector<float> slice_z;
std::vector<Polygons> bottom_contacts; std::vector<Polygons> bottom_contacts;
@ -816,7 +1245,7 @@ void organic_draw_branches(
double support_area_min_radius = M_PI * sqr(double(config.branch_radius)); double support_area_min_radius = M_PI * sqr(double(config.branch_radius));
double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius); double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius);
// Only propagate until the rest area is smaller than this threshold. // Only propagate until the rest area is smaller than this threshold.
double support_area_min = 0.1 * support_area_min_radius; //double support_area_min = 0.1 * support_area_min_radius;
for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) { for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) {
rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false));
double rest_support_area = area(rest_support); double rest_support_area = area(rest_support);
@ -895,11 +1324,11 @@ void organic_draw_branches(
Slice &dst = tree.slices[i - new_begin]; Slice &dst = tree.slices[i - new_begin];
if (++ dst.num_branches > 1) { if (++ dst.num_branches > 1) {
append(dst.polygons, std::move(src)); append(dst.polygons, std::move(src));
if (j < bottom_contacts.size()) if (j < int(bottom_contacts.size()))
append(dst.bottom_contacts, std::move(bottom_contacts[j])); append(dst.bottom_contacts, std::move(bottom_contacts[j]));
} else { } else {
dst.polygons = std::move(std::move(src)); dst.polygons = std::move(std::move(src));
if (j < bottom_contacts.size()) if (j < int(bottom_contacts.size()))
dst.bottom_contacts = std::move(bottom_contacts[j]); dst.bottom_contacts = std::move(bottom_contacts[j]);
} }
} }

View File

@ -298,7 +298,7 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
else { else {
SupportGeneratorLayersPtr out(in1.size() + in2.size(), nullptr); SupportGeneratorLayersPtr out(in1.size() + in2.size(), nullptr);
std::merge(in1.begin(), in1.end(), in2.begin(), in2.end(), out.begin(), [](auto* l, auto* r) { return l->print_z < r->print_z; }); std::merge(in1.begin(), in1.end(), in2.begin(), in2.end(), out.begin(), [](auto* l, auto* r) { return l->print_z < r->print_z; });
return std::move(out); return out;
} }
}; };
interface_layers = merge_remove_empty(interface_layers, top_interface_layers); interface_layers = merge_remove_empty(interface_layers, top_interface_layers);
@ -664,7 +664,7 @@ static inline void tree_supports_generate_paths(
// Draw the perimeters. // Draw the perimeters.
Polylines polylines; Polylines polylines;
polylines.reserve(expoly.holes.size() + 1); polylines.reserve(expoly.holes.size() + 1);
for (size_t idx_loop = 0; idx_loop < expoly.num_contours(); ++ idx_loop) { for (int idx_loop = 0; idx_loop < int(expoly.num_contours()); ++ idx_loop) {
// Open the loop with a seam. // Open the loop with a seam.
const Polygon &loop = expoly.contour_or_hole(idx_loop); const Polygon &loop = expoly.contour_or_hole(idx_loop);
Polyline pl(loop.points); Polyline pl(loop.points);
@ -680,11 +680,11 @@ static inline void tree_supports_generate_paths(
ClipperLib_Z::Path *closest_contour = nullptr; ClipperLib_Z::Path *closest_contour = nullptr;
Vec2d closest_point; Vec2d closest_point;
int closest_point_idx = -1; int closest_point_idx = -1;
double closest_point_t; double closest_point_t = 0.;
double d2min = std::numeric_limits<double>::max(); double d2min = std::numeric_limits<double>::max();
Vec2d seam_pt = pl.back().cast<double>(); Vec2d seam_pt = pl.back().cast<double>();
for (ClipperLib_Z::Path &path : anchor_candidates) for (ClipperLib_Z::Path &path : anchor_candidates)
for (int i = 0; i < path.size(); ++ i) { for (int i = 0; i < int(path.size()); ++ i) {
int j = next_idx_modulo(i, path); int j = next_idx_modulo(i, path);
if (path[i].z() == idx_loop || path[j].z() == idx_loop) { if (path[i].z() == idx_loop || path[j].z() == idx_loop) {
Vec2d pi(path[i].x(), path[i].y()); Vec2d pi(path[i].x(), path[i].y());

View File

@ -240,7 +240,7 @@ private:
*/ */
std::optional<std::reference_wrapper<const Polygons>> getArea(const TreeModelVolumes::RadiusLayerPair &key) const { std::optional<std::reference_wrapper<const Polygons>> getArea(const TreeModelVolumes::RadiusLayerPair &key) const {
std::lock_guard<std::mutex> guard(m_mutex); std::lock_guard<std::mutex> guard(m_mutex);
if (key.second >= m_data.size()) if (key.second >= LayerIndex(m_data.size()))
return std::optional<std::reference_wrapper<const Polygons>>{}; return std::optional<std::reference_wrapper<const Polygons>>{};
const auto &layer = m_data[key.second]; const auto &layer = m_data[key.second];
auto it = layer.find(key.first); auto it = layer.find(key.first);
@ -250,7 +250,7 @@ private:
// Get a collision area at a given layer for a radius that is a lower or equial to the key radius. // Get a collision area at a given layer for a radius that is a lower or equial to the key radius.
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> get_lower_bound_area(const TreeModelVolumes::RadiusLayerPair &key) const { std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> get_lower_bound_area(const TreeModelVolumes::RadiusLayerPair &key) const {
std::lock_guard<std::mutex> guard(m_mutex); std::lock_guard<std::mutex> guard(m_mutex);
if (key.second >= m_data.size()) if (key.second >= LayerIndex(m_data.size()))
return {}; return {};
const auto &layer = m_data[key.second]; const auto &layer = m_data[key.second];
if (layer.empty()) if (layer.empty())

View File

@ -211,7 +211,7 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
//FIXME this is a fudge constant! //FIXME this is a fudge constant!
auto enforcer_overhang_offset = scaled<double>(config.support_tree_tip_diameter.value); auto enforcer_overhang_offset = scaled<double>(config.support_tree_tip_diameter.value);
size_t num_overhang_layers = support_auto ? num_object_layers : std::max(size_t(support_enforce_layers), enforcers_layers.size()); size_t num_overhang_layers = support_auto ? num_object_layers : std::min(num_object_layers, std::max(size_t(support_enforce_layers), enforcers_layers.size()));
tbb::parallel_for(tbb::blocked_range<LayerIndex>(1, num_overhang_layers), tbb::parallel_for(tbb::blocked_range<LayerIndex>(1, num_overhang_layers),
[&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers,
support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, &throw_on_cancel, &out] support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, &throw_on_cancel, &out]
@ -387,6 +387,7 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
return result; return result;
} }
#if 0
/*! /*!
* \brief Converts lines in internal format into a Polygons object representing these lines. * \brief Converts lines in internal format into a Polygons object representing these lines.
* *
@ -405,6 +406,7 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
validate_range(result); validate_range(result);
return result; return result;
} }
#endif
/*! /*!
* \brief Evaluates if a point has to be added now. Required for a split_lines call in generate_initial_areas(). * \brief Evaluates if a point has to be added now. Required for a split_lines call in generate_initial_areas().
@ -788,7 +790,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
else else
do_final_difference = true; do_final_difference = true;
} }
if (steps + (distance < last_step_offset_without_check || distance % step_size != 0) < min_amount_offset && min_amount_offset > 1) { if (steps + (distance < last_step_offset_without_check || (distance % step_size) != 0) < int(min_amount_offset) && min_amount_offset > 1) {
// yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1 // yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1
// reduce the stepsize to ensure it is offset the required amount of times // reduce the stepsize to ensure it is offset the required amount of times
step_size = distance / min_amount_offset; step_size = distance / min_amount_offset;
@ -1175,7 +1177,7 @@ void sample_overhang_area(
} }
assert(dtt_roof <= layer_idx); assert(dtt_roof <= layer_idx);
if (int(dtt_roof) >= layer_idx && large_horizontal_roof) if (dtt_roof >= layer_idx && large_horizontal_roof)
// Reached buildplate when generating contact, interface and base interface layers. // Reached buildplate when generating contact, interface and base interface layers.
interface_placer.add_roof_build_plate(std::move(overhang_area), dtt_roof); interface_placer.add_roof_build_plate(std::move(overhang_area), dtt_roof);
else { else {
@ -1282,7 +1284,7 @@ static void generate_initial_areas(
tbb::parallel_for(tbb::blocked_range<size_t>(0, raw_overhangs.size()), tbb::parallel_for(tbb::blocked_range<size_t>(0, raw_overhangs.size()),
[&volumes, &config, &raw_overhangs, &mesh_group_settings, [&volumes, &config, &raw_overhangs, &mesh_group_settings,
min_xy_dist, force_tip_to_roof, roof_enabled, num_support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, min_xy_dist, roof_enabled, num_support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length,
&rich_interface_placer, &throw_on_cancel](const tbb::blocked_range<size_t> &range) { &rich_interface_placer, &throw_on_cancel](const tbb::blocked_range<size_t> &range) {
for (size_t raw_overhang_idx = range.begin(); raw_overhang_idx < range.end(); ++ raw_overhang_idx) { for (size_t raw_overhang_idx = range.begin(); raw_overhang_idx < range.end(); ++ raw_overhang_idx) {
size_t layer_idx = raw_overhangs[raw_overhang_idx].first; size_t layer_idx = raw_overhangs[raw_overhang_idx].first;
@ -2611,13 +2613,13 @@ static void remove_deleted_elements(std::vector<SupportElements> &move_bounds)
std::iota(map_current.begin(), map_current.end(), 0); std::iota(map_current.begin(), map_current.end(), 0);
} }
// Delete all "deleted" elements from the end of the layer vector. // Delete all "deleted" elements from the end of the layer vector.
while (i < layer.size() && layer.back().state.deleted) { while (i < int32_t(layer.size()) && layer.back().state.deleted) {
layer.pop_back(); layer.pop_back();
// Mark as deleted in the map. // Mark as deleted in the map.
map_current[layer.size()] = -1; map_current[layer.size()] = -1;
} }
assert(i == layer.size() || i + 1 < layer.size()); assert(i == layer.size() || i + 1 < layer.size());
if (i + 1 < layer.size()) { if (i + 1 < int32_t(layer.size())) {
element = std::move(layer.back()); element = std::move(layer.back());
layer.pop_back(); layer.pop_back();
// Mark the current element as deleted. // Mark the current element as deleted.
@ -2667,7 +2669,7 @@ static void create_nodes_from_area(
for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) {
auto &layer = move_bounds[layer_idx]; auto &layer = move_bounds[layer_idx];
auto *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; auto *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr;
if (layer_above) if (layer_above)
for (SupportElement &elem : *layer_above) for (SupportElement &elem : *layer_above)
elem.state.marked = false; elem.state.marked = false;
@ -2809,7 +2811,7 @@ static void generate_branch_areas(
const Point movement = draw_area.child_element->state.result_on_layer - draw_area.element->state.result_on_layer; const Point movement = draw_area.child_element->state.result_on_layer - draw_area.element->state.result_on_layer;
movement_directions.emplace_back(movement, radius); movement_directions.emplace_back(movement, radius);
} }
const SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; const SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr;
for (int32_t parent_idx : draw_area.element->parents) { for (int32_t parent_idx : draw_area.element->parents) {
const SupportElement &parent = (*layer_above)[parent_idx]; const SupportElement &parent = (*layer_above)[parent_idx];
const Point movement = parent.state.result_on_layer - draw_area.element->state.result_on_layer; const Point movement = parent.state.result_on_layer - draw_area.element->state.result_on_layer;
@ -3255,7 +3257,7 @@ static void draw_areas(
std::vector<std::pair<SupportElement*, SupportElement*>> map_downwards_old; std::vector<std::pair<SupportElement*, SupportElement*>> map_downwards_old;
std::vector<std::pair<SupportElement*, SupportElement*>> map_downwards_new; std::vector<std::pair<SupportElement*, SupportElement*>> map_downwards_new;
for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) {
SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr;
map_downwards_new.clear(); map_downwards_new.clear();
linear_data_layers.emplace_back(linear_data.size()); linear_data_layers.emplace_back(linear_data.size());
std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto &l, auto &r) { return l.first < r.first; }); std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto &l, auto &r) { return l.first < r.first; });
@ -3472,6 +3474,17 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
if (support_params.has_base_interfaces() || has_raft) if (support_params.has_base_interfaces() || has_raft)
base_interface_layers.assign(num_support_layers, nullptr); base_interface_layers.assign(num_support_layers, nullptr);
auto remove_undefined_layers = [&bottom_contacts, &top_contacts, &interface_layers, &base_interface_layers, &intermediate_layers]() {
auto doit = [](SupportGeneratorLayersPtr& layers) {
layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end());
};
doit(bottom_contacts);
doit(top_contacts);
doit(interface_layers);
doit(base_interface_layers);
doit(intermediate_layers);
};
InterfacePlacer interface_placer{ InterfacePlacer interface_placer{
print_object.slicing_parameters(), support_params, config, print_object.slicing_parameters(), support_params, config,
// Outputs // Outputs
@ -3523,14 +3536,7 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
throw_on_cancel); throw_on_cancel);
} }
auto remove_undefined_layers = [](SupportGeneratorLayersPtr& layers) { remove_undefined_layers();
layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end());
};
remove_undefined_layers(bottom_contacts);
remove_undefined_layers(top_contacts);
remove_undefined_layers(interface_layers);
remove_undefined_layers(base_interface_layers);
remove_undefined_layers(intermediate_layers);
std::tie(interface_layers, base_interface_layers) = generate_interface_layers(print_object.config(), support_params, std::tie(interface_layers, base_interface_layers) = generate_interface_layers(print_object.config(), support_params,
bottom_contacts, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); bottom_contacts, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage);
@ -3553,7 +3559,9 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
// BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; // BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)";
move_bounds.clear(); move_bounds.clear();
} else if (generate_raft_contact(print_object, config, interface_placer) < 0) } else if (generate_raft_contact(print_object, config, interface_placer) >= 0) {
remove_undefined_layers();
} else
// No raft. // No raft.
continue; continue;

View File

@ -78,14 +78,12 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
} }
TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params) TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params)
: angle(mesh_group_settings.support_tree_angle), : support_line_width(mesh_group_settings.support_line_width),
angle_slow(mesh_group_settings.support_tree_angle_slow),
support_line_width(mesh_group_settings.support_line_width),
layer_height(mesh_group_settings.layer_height), layer_height(mesh_group_settings.layer_height),
branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), branch_radius(mesh_group_settings.support_tree_branch_diameter / 2),
min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance
maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits<coord_t>::max()), maximum_move_distance((mesh_group_settings.support_tree_angle < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle) * layer_height) : std::numeric_limits<coord_t>::max()),
maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits<coord_t>::max()), maximum_move_distance_slow((mesh_group_settings.support_tree_angle_slow < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle_slow) * layer_height) : std::numeric_limits<coord_t>::max()),
support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0),
tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large
branch_radius_increase_per_layer(tan(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height), branch_radius_increase_per_layer(tan(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height),
@ -155,7 +153,7 @@ TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mes
// Layers between the raft contacts and bottom of the object. // Layers between the raft contacts and bottom of the object.
auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height)); auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height));
double step = dist_to_go / nsteps; double step = dist_to_go / nsteps;
for (size_t i = 0; i < nsteps; ++ i) { for (int i = 0; i < nsteps; ++ i) {
z += step; z += step;
this->raft_layers.emplace_back(z); this->raft_layers.emplace_back(z);
} }

View File

@ -443,8 +443,6 @@ public:
#endif #endif
private: private:
double angle;
double angle_slow;
// std::vector<coord_t> known_z; // std::vector<coord_t> known_z;
}; };

View File

@ -26,6 +26,8 @@
#include <boost/nowide/cstdio.hpp> #include <boost/nowide/cstdio.hpp>
#include <boost/predef/other/endian.h> #include <boost/predef/other/endian.h>
#include <tbb/concurrent_vector.h>
#include <Eigen/Core> #include <Eigen/Core>
#include <Eigen/Dense> #include <Eigen/Dense>
@ -871,11 +873,38 @@ void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, c
} }
template<typename TransformVertex> template<typename TransformVertex>
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const TransformVertex &transform_fn, const float z) Polygon its_convex_hull_2d_above(const indexed_triangle_set& its, const TransformVertex& transform_fn, const float z)
{ {
Points all_pts; auto collect_mesh_projection_points_above = [&](const tbb::blocked_range<size_t>& range) {
its_collect_mesh_projection_points_above(its, transform_fn, z, all_pts); Points pts;
return Geometry::convex_hull(std::move(all_pts)); pts.reserve(range.size() * 4); // there can be up to 4 vertices per triangle
for (size_t i = range.begin(); i < range.end(); ++i) {
const stl_triangle_vertex_indices& tri = its.indices[i];
const Vec3f tri_pts[3] = { transform_fn(its.vertices[tri(0)]), transform_fn(its.vertices[tri(1)]), transform_fn(its.vertices[tri(2)]) };
int iprev = 2;
for (int iedge = 0; iedge < 3; ++iedge) {
const Vec3f& p1 = tri_pts[iprev];
const Vec3f& p2 = tri_pts[iedge];
if ((p1.z() < z && p2.z() > z) || (p2.z() < z && p1.z() > z)) {
// Edge crosses the z plane. Calculate intersection point with the plane.
const float t = (z - p1.z()) / (p2.z() - p1.z());
pts.emplace_back(scaled<coord_t>(p1.x() + (p2.x() - p1.x()) * t), scaled<coord_t>(p1.y() + (p2.y() - p1.y()) * t));
}
if (p2.z() >= z)
pts.emplace_back(scaled<coord_t>(p2.x()), scaled<coord_t>(p2.y()));
iprev = iedge;
}
}
return Geometry::convex_hull(std::move(pts));
};
tbb::concurrent_vector<Polygon> chs;
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()), [&](const tbb::blocked_range<size_t>& range) {
chs.push_back(collect_mesh_projection_points_above(range));
});
const Polygons polygons(chs.begin(), chs.end());
return Geometry::convex_hull(polygons);
} }
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z) Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z)

View File

@ -833,64 +833,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
} }
} }
bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, ModelInstanceEPrintVolumeState *out_state) const
{
const Model& model = GUI::wxGetApp().plater()->model();
auto volume_below = [](GLVolume& volume) -> bool
{ return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_below_printbed(); };
// Volume is partially below the print bed, thus a pre-calculated convex hull cannot be used.
auto volume_sinking = [](GLVolume& volume) -> bool
{ return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_sinking(); };
// Cached bounding box of a volume above the print bed.
auto volume_bbox = [volume_sinking](GLVolume& volume) -> BoundingBoxf3
{ return volume_sinking(volume) ? volume.transformed_non_sinking_bounding_box() : volume.transformed_convex_hull_bounding_box(); };
// Cached 3D convex hull of a volume above the print bed.
auto volume_convex_mesh = [volume_sinking, &model](GLVolume& volume) -> const TriangleMesh&
{ return volume_sinking(volume) ? model.objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : *volume.convex_hull(); };
ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside;
bool contained_min_one = false;
for (GLVolume* volume : this->volumes)
if (! volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (! volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) {
BuildVolume::ObjectState state;
if (volume_below(*volume))
state = BuildVolume::ObjectState::Below;
else {
switch (build_volume.type()) {
case BuildVolume::Type::Rectangle:
//FIXME this test does not evaluate collision of a build volume bounding box with non-convex objects.
state = build_volume.volume_state_bbox(volume_bbox(*volume));
break;
case BuildVolume::Type::Circle:
case BuildVolume::Type::Convex:
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
case BuildVolume::Type::Custom:
state = build_volume.object_state(volume_convex_mesh(*volume).its, volume->world_matrix().cast<float>(), volume_sinking(*volume));
break;
default:
// Ignore, don't produce any collision.
state = BuildVolume::ObjectState::Inside;
break;
}
assert(state != BuildVolume::ObjectState::Below);
}
volume->is_outside = state != BuildVolume::ObjectState::Inside;
if (volume->printable) {
if (overall_state == ModelInstancePVS_Inside && volume->is_outside)
overall_state = ModelInstancePVS_Fully_Outside;
if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding)
overall_state = ModelInstancePVS_Partly_Outside;
contained_min_one |= !volume->is_outside;
}
}
if (out_state != nullptr)
*out_state = overall_state;
return contained_min_one;
}
void GLVolumeCollection::reset_outside_state() void GLVolumeCollection::reset_outside_state()
{ {
for (GLVolume* volume : this->volumes) { for (GLVolume* volume : this->volumes) {

View File

@ -450,9 +450,6 @@ public:
void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; }
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null
bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const;
void reset_outside_state(); void reset_outside_state();
void update_colors_by_extruder(const DynamicPrintConfig* config); void update_colors_by_extruder(const DynamicPrintConfig* config);

View File

@ -3575,9 +3575,6 @@ void GCodeViewer::render_legend(float& legend_height)
int old_view_type = static_cast<int>(get_view_type()); int old_view_type = static_cast<int>(get_view_type());
int view_type = old_view_type; int view_type = old_view_type;
if (!m_legend_resizer.dirty)
ImGui::SetNextItemWidth(-1.0f);
ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f }); ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f });
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f }); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f });
imgui.combo(std::string(), { _u8L("Feature type"), imgui.combo(std::string(), { _u8L("Feature type"),
@ -3590,7 +3587,7 @@ void GCodeViewer::render_legend(float& legend_height)
_u8L("Layer time (linear)"), _u8L("Layer time (linear)"),
_u8L("Layer time (logarithmic)"), _u8L("Layer time (logarithmic)"),
_u8L("Tool"), _u8L("Tool"),
_u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest); _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f);
ImGui::PopStyleColor(2); ImGui::PopStyleColor(2);
if (old_view_type != view_type) { if (old_view_type != view_type) {
@ -3961,7 +3958,7 @@ void GCodeViewer::render_legend(float& legend_height)
const auto custom_it = std::find(m_roles.begin(), m_roles.end(), GCodeExtrusionRole::Custom); const auto custom_it = std::find(m_roles.begin(), m_roles.end(), GCodeExtrusionRole::Custom);
if (custom_it != m_roles.end()) { if (custom_it != m_roles.end()) {
const bool custom_visible = is_visible(GCodeExtrusionRole::Custom); const bool custom_visible = is_visible(GCodeExtrusionRole::Custom);
const wxString btn_text = custom_visible ? _u8L("Hide Custom GCode") : _u8L("Show Custom GCode"); const wxString btn_text = custom_visible ? _u8L("Hide Custom G-code") : _u8L("Show Custom G-code");
ImGui::Separator(); ImGui::Separator();
if (imgui.button(btn_text, ImVec2(-1.0f, 0.0f), true)) { if (imgui.button(btn_text, ImVec2(-1.0f, 0.0f), true)) {
m_extrusions.role_visibility_flags = custom_visible ? m_extrusions.role_visibility_flags & ~(1 << int(GCodeExtrusionRole::Custom)) : m_extrusions.role_visibility_flags = custom_visible ? m_extrusions.role_visibility_flags & ~(1 << int(GCodeExtrusionRole::Custom)) :

View File

@ -132,6 +132,9 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
delete m_slicing_parameters; delete m_slicing_parameters;
m_slicing_parameters = nullptr; m_slicing_parameters = nullptr;
m_layers_texture.valid = false; m_layers_texture.valid = false;
m_layer_height_profile.clear();
m_layer_height_profile_modified = false;
} }
void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id)
@ -141,6 +144,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id)
// Changing maximum height of an object will invalidate the layer heigth editing profile. // Changing maximum height of an object will invalidate the layer heigth editing profile.
// m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently.
const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast<float>(model_object_new->max_z()); const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast<float>(model_object_new->max_z());
if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z ||
(model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) {
m_layer_height_profile.clear(); m_layer_height_profile.clear();
@ -589,6 +593,7 @@ void GLCanvas3D::LayersEditing::generate_layer_height_texture()
m_layer_height_profile_modified = false; m_layer_height_profile_modified = false;
update = true; update = true;
} }
// Update if the layer height profile was changed, or when the texture is not valid. // Update if the layer height profile was changed, or when the texture is not valid.
if (! update && ! m_layers_texture.data.empty() && m_layers_texture.cells > 0) if (! update && ! m_layers_texture.data.empty() && m_layers_texture.cells > 0)
// Texture is valid, don't update. // Texture is valid, don't update.
@ -615,8 +620,8 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas)
if (m_layer_height_profile_modified) { if (m_layer_height_profile_modified) {
wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit")); wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit"));
const_cast<ModelObject*>(m_model_object)->layer_height_profile.set(m_layer_height_profile); const_cast<ModelObject*>(m_model_object)->layer_height_profile.set(m_layer_height_profile);
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
wxGetApp().obj_list()->update_info_items(last_object_id); wxGetApp().obj_list()->update_info_items(last_object_id);
wxGetApp().plater()->schedule_background_process();
} }
} }
m_layer_height_profile_modified = false; m_layer_height_profile_modified = false;
@ -880,20 +885,22 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
} }
void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons) void GLCanvas3D::SequentialPrintClearance::set_contours(const ContoursList& contours, bool generate_fill)
{ {
m_perimeter.reset(); m_contours.clear();
m_instances.clear();
m_fill.reset(); m_fill.reset();
if (polygons.empty())
if (contours.empty())
return; return;
if (m_render_fill) { if (generate_fill) {
GLModel::Geometry fill_data; GLModel::Geometry fill_data;
fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f }; fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f };
// vertices + indices // vertices + indices
const ExPolygons polygons_union = union_ex(polygons); const ExPolygons polygons_union = union_ex(contours.contours);
unsigned int vertices_counter = 0; unsigned int vertices_counter = 0;
for (const ExPolygon& poly : polygons_union) { for (const ExPolygon& poly : polygons_union) {
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(poly); const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(poly);
@ -906,17 +913,48 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons
fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
} }
} }
m_fill.init_from(std::move(fill_data)); m_fill.init_from(std::move(fill_data));
} }
m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting for (size_t i = 0; i < contours.contours.size(); ++i) {
GLModel& model = m_contours.emplace_back(GLModel());
model.init_from(contours.contours[i], 0.025f); // add a small positive z to avoid z-fighting
}
if (contours.trafos.has_value()) {
// create the requested instances
for (const auto& instance : *contours.trafos) {
m_instances.emplace_back(instance.first, instance.second);
}
}
else {
// no instances have been specified
// create one instance for every polygon
for (size_t i = 0; i < contours.contours.size(); ++i) {
m_instances.emplace_back(i, Transform3f::Identity());
}
}
}
void GLCanvas3D::SequentialPrintClearance::update_instances_trafos(const std::vector<Transform3d>& trafos)
{
if (trafos.size() == m_instances.size()) {
for (size_t i = 0; i < trafos.size(); ++i) {
m_instances[i].second = trafos[i];
}
}
else
assert(false);
} }
void GLCanvas3D::SequentialPrintClearance::render() void GLCanvas3D::SequentialPrintClearance::render()
{ {
const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f }; const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f };
const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f }; const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f };
const ColorRGBA NO_FILL_EVALUATING_COLOR = { 1.0f, 1.0f, 0.0f, 1.0f };
if (m_contours.empty() || m_instances.empty())
return;
GLShaderProgram* shader = wxGetApp().get_shader("flat"); GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr) if (shader == nullptr)
@ -933,10 +971,35 @@ void GLCanvas3D::SequentialPrintClearance::render()
glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); if (!m_evaluating)
m_perimeter.render();
m_fill.render(); m_fill.render();
#if ENABLE_GL_CORE_PROFILE
if (OpenGLManager::get_gl_info().is_core_profile()) {
shader->stop_using();
shader = wxGetApp().get_shader("dashed_thick_lines");
if (shader == nullptr)
return;
shader->start_using();
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
const std::array<int, 4>& viewport = camera.get_viewport();
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
shader->set_uniform("width", 1.0f);
shader->set_uniform("gap_size", 0.0f);
}
else
#endif // ENABLE_GL_CORE_PROFILE
glsafe(::glLineWidth(2.0f));
for (const auto& [id, trafo] : m_instances) {
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo);
assert(id < m_contours.size());
m_contours[id].set_color((!m_evaluating && m_fill.is_initialized()) ? FILL_COLOR : m_evaluating ? NO_FILL_EVALUATING_COLOR : NO_FILL_COLOR);
m_contours[id].render();
}
glsafe(::glDisable(GL_BLEND)); glsafe(::glDisable(GL_BLEND));
glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_DEPTH_TEST));
@ -1240,7 +1303,7 @@ void GLCanvas3D::SLAView::render_switch_button()
imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
const float icon_size = 1.5 * ImGui::GetTextLineHeight(); const float icon_size = 1.5 * ImGui::GetTextLineHeight();
if (imgui.draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true, if (imgui.draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true,
[this, &imgui, sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) { [&imgui, sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) {
const wchar_t icon_id = (sel_instance->second == ESLAViewType::Original) ? ImGui::SlaViewProcessed : ImGui::SlaViewOriginal; const wchar_t icon_id = (sel_instance->second == ESLAViewType::Original) ? ImGui::SlaViewProcessed : ImGui::SlaViewOriginal;
imgui.draw_icon(window, pos, size, icon_id); imgui.draw_icon(window, pos, size, icon_id);
})) { })) {
@ -1450,14 +1513,97 @@ void GLCanvas3D::reset_volumes()
_set_warning_notification(EWarning::ObjectOutside, false); _set_warning_notification(EWarning::ObjectOutside, false);
} }
ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(bool selection_only) const
{ {
ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside; ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside;
if (m_initialized) if (m_initialized && !m_volumes.empty())
m_volumes.check_outside_state(m_bed.build_volume(), &state); check_volumes_outside_state(m_bed.build_volume(), &state, selection_only);
return state; return state;
} }
bool GLCanvas3D::check_volumes_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state, bool selection_only) const
{
auto volume_below = [](GLVolume& volume) -> bool
{ return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_below_printbed(); };
// Volume is partially below the print bed, thus a pre-calculated convex hull cannot be used.
auto volume_sinking = [](GLVolume& volume) -> bool
{ return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_sinking(); };
// Cached bounding box of a volume above the print bed.
auto volume_bbox = [volume_sinking](GLVolume& volume) -> BoundingBoxf3
{ return volume_sinking(volume) ? volume.transformed_non_sinking_bounding_box() : volume.transformed_convex_hull_bounding_box(); };
// Cached 3D convex hull of a volume above the print bed.
auto volume_convex_mesh = [this, volume_sinking](GLVolume& volume) -> const TriangleMesh&
{ return volume_sinking(volume) ? m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : *volume.convex_hull(); };
auto volumes_to_process_idxs = [this, selection_only]() {
std::vector<unsigned int> ret;
if (!selection_only || m_selection.is_empty()) {
ret = std::vector<unsigned int>(m_volumes.volumes.size());
std::iota(ret.begin(), ret.end(), 0);
}
else {
const GUI::Selection::IndicesList& selected_volume_idxs = m_selection.get_volume_idxs();
ret.assign(selected_volume_idxs.begin(), selected_volume_idxs.end());
}
return ret;
};
ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside;
bool contained_min_one = false;
const std::vector<unsigned int> volumes_idxs = volumes_to_process_idxs();
for (unsigned int vol_idx : volumes_idxs) {
GLVolume* volume = m_volumes.volumes[vol_idx];
if (!volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (!volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) {
BuildVolume::ObjectState state;
if (volume_below(*volume))
state = BuildVolume::ObjectState::Below;
else {
switch (build_volume.type()) {
case BuildVolume::Type::Rectangle:
//FIXME this test does not evaluate collision of a build volume bounding box with non-convex objects.
state = build_volume.volume_state_bbox(volume_bbox(*volume));
break;
case BuildVolume::Type::Circle:
case BuildVolume::Type::Convex:
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
case BuildVolume::Type::Custom:
state = build_volume.object_state(volume_convex_mesh(*volume).its, volume->world_matrix().cast<float>(), volume_sinking(*volume));
break;
default:
// Ignore, don't produce any collision.
state = BuildVolume::ObjectState::Inside;
break;
}
assert(state != BuildVolume::ObjectState::Below);
}
volume->is_outside = state != BuildVolume::ObjectState::Inside;
if (volume->printable) {
if (overall_state == ModelInstancePVS_Inside && volume->is_outside)
overall_state = ModelInstancePVS_Fully_Outside;
if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding)
overall_state = ModelInstancePVS_Partly_Outside;
contained_min_one |= !volume->is_outside;
}
}
}
for (unsigned int vol_idx = 0; vol_idx < m_volumes.volumes.size(); ++vol_idx) {
if (std::find(volumes_idxs.begin(), volumes_idxs.end(), vol_idx) == volumes_idxs.end()) {
if (!m_volumes.volumes[vol_idx]->is_outside) {
contained_min_one = true;
break;
}
}
}
if (out_state != nullptr)
*out_state = overall_state;
return contained_min_one;
}
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx)
{ {
if (current_printer_technology() != ptSLA) if (current_printer_technology() != ptSLA)
@ -2464,7 +2610,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
// checks for geometry outside the print volume to render it accordingly // checks for geometry outside the print volume to render it accordingly
if (!m_volumes.empty()) { if (!m_volumes.empty()) {
ModelInstanceEPrintVolumeState state; ModelInstanceEPrintVolumeState state;
const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); const bool contained_min_one = check_volumes_outside_state(m_bed.build_volume(), &state, !force_full_scene_refresh);
const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside);
const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside);
@ -3430,17 +3576,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// Not only detection of some modifiers !!! // Not only detection of some modifiers !!!
if (evt.Dragging()) { if (evt.Dragging()) {
GLGizmosManager::EType c = m_gizmos.get_current_type(); GLGizmosManager::EType c = m_gizmos.get_current_type();
if (current_printer_technology() == ptFFF &&
fff_print()->config().complete_objects){
if (c == GLGizmosManager::EType::Move || if (c == GLGizmosManager::EType::Move ||
c == GLGizmosManager::EType::Scale || c == GLGizmosManager::EType::Scale ||
c == GLGizmosManager::EType::Rotate ) c == GLGizmosManager::EType::Rotate) {
update_sequential_clearance();
} else {
if (c == GLGizmosManager::EType::Move ||
c == GLGizmosManager::EType::Scale ||
c == GLGizmosManager::EType::Rotate)
show_sinking_contours(); show_sinking_contours();
if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects)
update_sequential_clearance(true);
} }
} }
else if (evt.LeftUp() && else if (evt.LeftUp() &&
@ -3643,7 +3784,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
trafo_type.set_relative(); trafo_type.set_relative();
m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type);
if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects)
update_sequential_clearance(); update_sequential_clearance(false);
wxGetApp().obj_manipul()->set_dirty(); wxGetApp().obj_manipul()->set_dirty();
m_dirty = true; m_dirty = true;
} }
@ -3949,7 +4090,10 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
if (wipe_tower_origin != Vec3d::Zero()) if (wipe_tower_origin != Vec3d::Zero())
post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin)));
reset_sequential_print_clearance(); if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) {
update_sequential_clearance(true);
m_sequential_print_clearance.m_evaluating = true;
}
m_dirty = true; m_dirty = true;
} }
@ -4034,6 +4178,11 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
if (!done.empty()) if (!done.empty())
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) {
update_sequential_clearance(true);
m_sequential_print_clearance.m_evaluating = true;
}
m_dirty = true; m_dirty = true;
} }
@ -4106,6 +4255,11 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
if (!done.empty()) if (!done.empty())
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED));
if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) {
update_sequential_clearance(true);
m_sequential_print_clearance.m_evaluating = true;
}
m_dirty = true; m_dirty = true;
} }
@ -4373,16 +4527,33 @@ void GLCanvas3D::mouse_up_cleanup()
m_canvas->ReleaseMouse(); m_canvas->ReleaseMouse();
} }
void GLCanvas3D::update_sequential_clearance() void GLCanvas3D::update_sequential_clearance(bool force_contours_generation)
{ {
if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects) if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects)
return; return;
if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) if (m_layers_editing.is_enabled())
return; return;
auto instance_transform_from_volumes = [this](int object_idx, int instance_idx) {
for (const GLVolume* v : m_volumes.volumes) {
if (v->object_idx() == object_idx && v->instance_idx() == instance_idx)
return v->get_instance_transformation();
}
assert(false);
return Geometry::Transformation();
};
auto is_object_outside_printbed = [this](int object_idx) {
for (const GLVolume* v : m_volumes.volumes) {
if (v->object_idx() == object_idx && v->is_outside)
return true;
}
return false;
};
// collects instance transformations from volumes // collects instance transformations from volumes
// first define temporary cache // first: define temporary cache
unsigned int instances_count = 0; unsigned int instances_count = 0;
std::vector<std::vector<std::optional<Geometry::Transformation>>> instance_transforms; std::vector<std::vector<std::optional<Geometry::Transformation>>> instance_transforms;
for (size_t obj = 0; obj < m_model->objects.size(); ++obj) { for (size_t obj = 0; obj < m_model->objects.size(); ++obj) {
@ -4397,67 +4568,94 @@ void GLCanvas3D::update_sequential_clearance()
if (instances_count == 1) if (instances_count == 1)
return; return;
// second fill temporary cache with data from volumes // second: fill temporary cache with data from volumes
for (const GLVolume* v : m_volumes.volumes) { for (const GLVolume* v : m_volumes.volumes) {
if (v->is_modifier || v->is_wipe_tower) if (v->is_wipe_tower)
continue; continue;
auto& transform = instance_transforms[v->object_idx()][v->instance_idx()]; const int object_idx = v->object_idx();
const int instance_idx = v->instance_idx();
auto& transform = instance_transforms[object_idx][instance_idx];
if (!transform.has_value()) if (!transform.has_value())
transform = v->get_instance_transformation(); transform = instance_transform_from_volumes(object_idx, instance_idx);
} }
// helper function to calculate the transformation to be applied to the sequential print clearance contours
auto instance_trafo = [](const Transform3d& hull_trafo, const Geometry::Transformation& inst_trafo) {
Vec3d offset = inst_trafo.get_offset() - hull_trafo.translation();
offset.z() = 0.0;
return Geometry::translation_transform(offset) *
Geometry::rotation_transform(Geometry::rotation_diff_z(hull_trafo, inst_trafo.get_matrix()) * Vec3d::UnitZ());
};
// calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) // calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid())
// this is done only the first time this method is called while moving the mouse, // this is done only the first time this method is called while moving the mouse,
// the results are then cached for following displacements // the results are then cached for following displacements
if (m_sequential_print_clearance_first_displacement) { if (force_contours_generation || m_sequential_print_clearance_first_displacement) {
m_sequential_print_clearance.m_hull_2d_cache.clear(); m_sequential_print_clearance.m_evaluating = false;
m_sequential_print_clearance.m_hulls_2d_cache.clear();
const float shrink_factor = static_cast<float>(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON)); const float shrink_factor = static_cast<float>(scale_(0.5 * fff_print()->config().extruder_clearance_radius.value - EPSILON));
const double mitter_limit = scale_(0.1); const double mitter_limit = scale_(0.1);
m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size()); m_sequential_print_clearance.m_hulls_2d_cache.reserve(m_model->objects.size());
for (size_t i = 0; i < m_model->objects.size(); ++i) { for (size_t i = 0; i < m_model->objects.size(); ++i) {
ModelObject* model_object = m_model->objects[i]; ModelObject* model_object = m_model->objects[i];
ModelInstance* model_instance0 = model_object->instances.front(); Geometry::Transformation trafo = instance_transform_from_volumes((int)i, 0);
Geometry::Transformation trafo = model_instance0->get_transformation(); trafo.set_offset({ 0.0, 0.0, trafo.get_offset().z() });
trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() }); Pointf3s& new_hull_2d = m_sequential_print_clearance.m_hulls_2d_cache.emplace_back(std::make_pair(Pointf3s(), trafo.get_matrix())).first;
const Polygon hull_2d = offset(model_object->convex_hull_2d(trafo.get_matrix()), if (is_object_outside_printbed((int)i))
continue;
Polygon hull_2d = model_object->convex_hull_2d(trafo.get_matrix());
if (!hull_2d.empty()) {
// Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects
// exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision.
shrink_factor, const Polygons offset_res = offset(hull_2d, shrink_factor, jtRound, mitter_limit);
jtRound, mitter_limit).front(); if (!offset_res.empty())
hull_2d = offset_res.front();
}
Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s()); new_hull_2d.reserve(hull_2d.points.size());
cache_hull_2d.reserve(hull_2d.points.size());
const Transform3d inv_trafo = trafo.get_matrix().inverse();
for (const Point& p : hull_2d.points) { for (const Point& p : hull_2d.points) {
cache_hull_2d.emplace_back(inv_trafo * Vec3d(unscale<double>(p.x()), unscale<double>(p.y()), 0.0)); new_hull_2d.emplace_back(Vec3d(unscale<double>(p.x()), unscale<double>(p.y()), 0.0));
} }
} }
m_sequential_print_clearance_first_displacement = false;
}
// calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid()) ContoursList contours;
Polygons polygons; contours.contours.reserve(instance_transforms.size());
polygons.reserve(instances_count); contours.trafos = std::vector<std::pair<size_t, Transform3d>>();
(*contours.trafos).reserve(instances_count);
for (size_t i = 0; i < instance_transforms.size(); ++i) { for (size_t i = 0; i < instance_transforms.size(); ++i) {
const auto& [hull, hull_trafo] = m_sequential_print_clearance.m_hulls_2d_cache[i];
Points hull_pts;
hull_pts.reserve(hull.size());
for (size_t j = 0; j < hull.size(); ++j) {
hull_pts.emplace_back(scaled<double>(hull[j].x()), scaled<double>(hull[j].y()));
}
contours.contours.emplace_back(Geometry::convex_hull(std::move(hull_pts)));
const auto& instances = instance_transforms[i]; const auto& instances = instance_transforms[i];
for (const auto& instance : instances) { for (const auto& instance : instances) {
const Transform3d& trafo = instance->get_matrix(); (*contours.trafos).emplace_back(i, instance_trafo(hull_trafo, *instance));
const Pointf3s& hull_2d = m_sequential_print_clearance.m_hull_2d_cache[i];
Points inst_pts;
inst_pts.reserve(hull_2d.size());
for (size_t j = 0; j < hull_2d.size(); ++j) {
const Vec3d p = trafo * hull_2d[j];
inst_pts.emplace_back(scaled<double>(p.x()), scaled<double>(p.y()));
}
polygons.emplace_back(Geometry::convex_hull(std::move(inst_pts)));
} }
} }
// sends instances 2d hulls to be rendered set_sequential_print_clearance_contours(contours, false);
set_sequential_print_clearance_visible(true); m_sequential_print_clearance_first_displacement = false;
set_sequential_print_clearance_render_fill(false); }
set_sequential_print_clearance_polygons(polygons); else {
if (!m_sequential_print_clearance.empty()) {
std::vector<Transform3d> trafos;
trafos.reserve(instances_count);
for (size_t i = 0; i < instance_transforms.size(); ++i) {
const auto& [hull, hull_trafo] = m_sequential_print_clearance.m_hulls_2d_cache[i];
const auto& instances = instance_transforms[i];
for (const auto& instance : instances) {
trafos.emplace_back(instance_trafo(hull_trafo, *instance));
}
}
m_sequential_print_clearance.update_instances_trafos(trafos);
}
}
} }
bool GLCanvas3D::is_object_sinking(int object_idx) const bool GLCanvas3D::is_object_sinking(int object_idx) const
@ -5937,7 +6135,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
} }
} }
if (m_requires_check_outside_state) { if (m_requires_check_outside_state) {
m_volumes.check_outside_state(build_volume, nullptr); check_volumes_outside_state(build_volume, nullptr);
m_requires_check_outside_state = false; m_requires_check_outside_state = false;
} }
} }
@ -6032,15 +6230,20 @@ void GLCanvas3D::_render_selection()
void GLCanvas3D::_render_sequential_clearance() void GLCanvas3D::_render_sequential_clearance()
{ {
if (m_layers_editing.is_enabled() || m_gizmos.is_dragging()) if (current_printer_technology() != ptFFF || !fff_print()->config().complete_objects)
return;
if (m_layers_editing.is_enabled())
return; return;
switch (m_gizmos.get_current_type()) switch (m_gizmos.get_current_type())
{ {
case GLGizmosManager::EType::Flatten: case GLGizmosManager::EType::Flatten:
case GLGizmosManager::EType::Cut: case GLGizmosManager::EType::Cut:
case GLGizmosManager::EType::Hollow: case GLGizmosManager::EType::MmuSegmentation:
case GLGizmosManager::EType::SlaSupports: case GLGizmosManager::EType::Measure:
case GLGizmosManager::EType::Emboss:
case GLGizmosManager::EType::Simplify:
case GLGizmosManager::EType::FdmSupports: case GLGizmosManager::EType::FdmSupports:
case GLGizmosManager::EType::Seam: { return; } case GLGizmosManager::EType::Seam: { return; }
default: { break; } default: { break; }

View File

@ -619,23 +619,35 @@ public:
return ret; return ret;
} }
struct ContoursList
{
// list of unique contours
Polygons contours;
// if defined: list of transforms to apply to contours
std::optional<std::vector<std::pair<size_t, Transform3d>>> trafos;
bool empty() const { return contours.empty(); }
};
private: private:
void load_arrange_settings(); void load_arrange_settings();
class SequentialPrintClearance class SequentialPrintClearance
{ {
GLModel m_fill; GLModel m_fill;
GLModel m_perimeter; // list of unique contours
bool m_render_fill{ true }; std::vector<GLModel> m_contours;
bool m_visible{ false }; // list of transforms used to render the contours
std::vector<std::pair<size_t, Transform3d>> m_instances;
bool m_evaluating{ false };
std::vector<Pointf3s> m_hull_2d_cache; std::vector<std::pair<Pointf3s, Transform3d>> m_hulls_2d_cache;
public: public:
void set_polygons(const Polygons& polygons); void set_contours(const ContoursList& contours, bool generate_fill);
void set_render_fill(bool render_fill) { m_render_fill = render_fill; } void update_instances_trafos(const std::vector<Transform3d>& trafos);
void set_visible(bool visible) { m_visible = visible; }
void render(); void render();
bool empty() const { return m_contours.empty(); }
friend class GLCanvas3D; friend class GLCanvas3D;
}; };
@ -725,7 +737,10 @@ public:
unsigned int get_volumes_count() const; unsigned int get_volumes_count() const;
const GLVolumeCollection& get_volumes() const { return m_volumes; } const GLVolumeCollection& get_volumes() const { return m_volumes; }
void reset_volumes(); void reset_volumes();
ModelInstanceEPrintVolumeState check_volumes_outside_state() const; ModelInstanceEPrintVolumeState check_volumes_outside_state(bool selection_only = true) const;
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null
bool check_volumes_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state, bool selection_only = true) const;
void init_gcode_viewer() { m_gcode_viewer.init(); } void init_gcode_viewer() { m_gcode_viewer.init(); }
void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } void reset_gcode_toolpaths() { m_gcode_viewer.reset(); }
@ -959,24 +974,24 @@ public:
} }
void reset_sequential_print_clearance() { void reset_sequential_print_clearance() {
m_sequential_print_clearance.set_visible(false); m_sequential_print_clearance.m_evaluating = false;
m_sequential_print_clearance.set_render_fill(false); m_sequential_print_clearance.set_contours(ContoursList(), false);
m_sequential_print_clearance.set_polygons(Polygons());
} }
void set_sequential_print_clearance_visible(bool visible) { void set_sequential_print_clearance_contours(const ContoursList& contours, bool generate_fill) {
m_sequential_print_clearance.set_visible(visible); m_sequential_print_clearance.set_contours(contours, generate_fill);
} }
void set_sequential_print_clearance_render_fill(bool render_fill) { bool is_sequential_print_clearance_empty() const {
m_sequential_print_clearance.set_render_fill(render_fill); return m_sequential_print_clearance.empty();
} }
void set_sequential_print_clearance_polygons(const Polygons& polygons) { bool is_sequential_print_clearance_evaluating() const {
m_sequential_print_clearance.set_polygons(polygons); return m_sequential_print_clearance.m_evaluating;
} }
void update_sequential_clearance(); void update_sequential_clearance(bool force_contours_generation);
void set_sequential_clearance_as_evaluating() { m_sequential_print_clearance.m_evaluating = true; }
const Print* fff_print() const; const Print* fff_print() const;
const SLAPrint* sla_print() const; const SLAPrint* sla_print() const;

View File

@ -596,6 +596,38 @@ void GLModel::init_from(const indexed_triangle_set& its)
} }
} }
void GLModel::init_from(const Polygon& polygon, float z)
{
if (is_initialized()) {
// call reset() if you want to reuse this model
assert(false);
return;
}
Geometry& data = m_render_data.geometry;
data.format = { Geometry::EPrimitiveType::Lines, Geometry::EVertexLayout::P3 };
const size_t segments_count = polygon.points.size();
data.reserve_vertices(2 * segments_count);
data.reserve_indices(2 * segments_count);
// vertices + indices
unsigned int vertices_counter = 0;
for (size_t i = 0; i < segments_count; ++i) {
const Point& p0 = polygon.points[i];
const Point& p1 = (i == segments_count - 1) ? polygon.points.front() : polygon.points[i + 1];
data.add_vertex(Vec3f(unscale<float>(p0.x()), unscale<float>(p0.y()), z));
data.add_vertex(Vec3f(unscale<float>(p1.x()), unscale<float>(p1.y()), z));
vertices_counter += 2;
data.add_line(vertices_counter - 2, vertices_counter - 1);
}
// update bounding box
for (size_t i = 0; i < vertices_count(); ++i) {
m_bounding_box.merge(data.extract_position_3(i).cast<double>());
}
}
void GLModel::init_from(const Polygons& polygons, float z) void GLModel::init_from(const Polygons& polygons, float z)
{ {
if (is_initialized()) { if (is_initialized()) {

View File

@ -227,6 +227,7 @@ namespace GUI {
void init_from(const TriangleMesh& mesh); void init_from(const TriangleMesh& mesh);
#endif // ENABLE_SMOOTH_NORMALS #endif // ENABLE_SMOOTH_NORMALS
void init_from(const indexed_triangle_set& its); void init_from(const indexed_triangle_set& its);
void init_from(const Polygon& polygon, float z);
void init_from(const Polygons& polygons, float z); void init_from(const Polygons& polygons, float z);
bool init_from_file(const std::string& filename); bool init_from_file(const std::string& filename);

View File

@ -926,8 +926,8 @@ void ObjectList::OnContextMenu(wxDataViewEvent& evt)
// Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list // Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true; bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true;
if (!evt_context_menu) // if (!evt_context_menu)
canvas->mouse_up_cleanup(); // canvas->mouse_up_cleanup();
list_manipulation(mouse_pos, evt_context_menu); list_manipulation(mouse_pos, evt_context_menu);
} }
@ -4016,8 +4016,10 @@ void ObjectList::update_selections_on_canvas()
selection.add_volumes(mode, volume_idxs, single_selection); selection.add_volumes(mode, volume_idxs, single_selection);
} }
wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
wxGetApp().plater()->canvas3D()->render(); canvas->update_gizmos_on_off_state();
canvas->check_volumes_outside_state();
canvas->render();
} }
void ObjectList::select_item(const wxDataViewItem& item) void ObjectList::select_item(const wxDataViewItem& item)

View File

@ -22,6 +22,7 @@ namespace GUI {
static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW();
static const ColorRGBA UPPER_PART_COLOR = ColorRGBA::CYAN(); static const ColorRGBA UPPER_PART_COLOR = ColorRGBA::CYAN();
static const ColorRGBA LOWER_PART_COLOR = ColorRGBA::MAGENTA(); static const ColorRGBA LOWER_PART_COLOR = ColorRGBA::MAGENTA();
static const ColorRGBA MODIFIER_COLOR = ColorRGBA(0.75f, 0.75f, 0.75f, 0.5f);
// connector colors // connector colors
static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW(); static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW();
@ -1405,7 +1406,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor
m_parts.clear(); m_parts.clear();
for (const ModelVolume* volume : volumes) { for (const ModelVolume* volume : volumes) {
assert(volume != nullptr); assert(volume != nullptr);
m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true}); m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true, !volume->is_model_part()});
m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f }); m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f });
m_parts.back().glmodel.init_from(volume->mesh()); m_parts.back().glmodel.init_from(volume->mesh());
@ -1484,13 +1485,19 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_mo
const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05; const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05;
for (size_t id=0; id<m_parts.size(); ++id) { for (size_t id=0; id<m_parts.size(); ++id) {
if (normal && (( is_looking_forward && m_parts[id].selected) || if (!m_parts[id].is_modifier && normal && ((is_looking_forward && m_parts[id].selected) ||
(!is_looking_forward && !m_parts[id].selected) ) ) (!is_looking_forward && !m_parts[id].selected) ) )
continue; continue;
const Vec3d volume_offset = model_object()->volumes[id]->get_offset(); const Vec3d volume_offset = model_object()->volumes[id]->get_offset();
shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset));
m_parts[id].glmodel.set_color(m_parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR); if (m_parts[id].is_modifier) {
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
}
m_parts[id].glmodel.set_color(m_parts[id].is_modifier ? MODIFIER_COLOR : (m_parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR));
m_parts[id].glmodel.render(); m_parts[id].glmodel.render();
if (m_parts[id].is_modifier)
glsafe(::glDisable(GL_BLEND));
} }
shader->stop_using(); shader->stop_using();
@ -1551,7 +1558,7 @@ bool GLGizmoCut3D::PartSelection::is_one_object() const
if (m_parts.size() < 2) if (m_parts.size() < 2)
return true; return true;
return std::all_of(m_parts.begin(), m_parts.end(), [this](const Part& part) { return std::all_of(m_parts.begin(), m_parts.end(), [this](const Part& part) {
return part.selected == m_parts.front().selected; return part.is_modifier || part.selected == m_parts.front().selected;
}); });
} }
@ -1568,7 +1575,7 @@ void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos)
std::vector<std::pair<size_t, double>> hits_id_and_sqdist; std::vector<std::pair<size_t, double>> hits_id_and_sqdist;
for (size_t id=0; id<m_parts.size(); ++id) { for (size_t id=0; id<m_parts.size(); ++id) {
const Vec3d volume_offset = model_object()->volumes[id]->get_offset(); // const Vec3d volume_offset = model_object()->volumes[id]->get_offset();
Transform3d tr = translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[id]->get_offset()); Transform3d tr = translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[id]->get_offset());
if (m_parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) { if (m_parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) {
hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast<double>())).squaredNorm()); hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast<double>())).squaredNorm());
@ -2535,29 +2542,66 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
}; };
const size_t cut_parts_cnt = m_part_selection.parts().size(); const size_t cut_parts_cnt = m_part_selection.parts().size();
bool has_modifiers = false;
// Distribute SolidParts to the Upper/Lower object
for (size_t id = 0; id < cut_parts_cnt; ++id) { for (size_t id = 0; id < cut_parts_cnt; ++id) {
if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower)) if (m_part_selection.parts()[id].is_modifier)
has_modifiers = true; // modifiers will be added later to the related parts
else if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower))
obj->add_volume(*(cut_mo->volumes[id])); obj->add_volume(*(cut_mo->volumes[id]));
} }
if (has_modifiers) {
// Distribute Modifiers to the Upper/Lower object
auto upper_bb = upper ? upper->instance_bounding_box(instance_idx) : BoundingBoxf3();
auto lower_bb = lower ? lower->instance_bounding_box(instance_idx) : BoundingBoxf3();
const Transform3d inst_matrix = cut_mo->instances[instance_idx]->get_transformation().get_matrix();
for (size_t id = 0; id < cut_parts_cnt; ++id)
if (m_part_selection.parts()[id].is_modifier) {
ModelVolume* vol = cut_mo->volumes[id];
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
// Don't add modifiers which are not intersecting with solid parts
if (upper_bb.intersects(bb))
upper->add_volume(*vol);
if (lower_bb.intersects(bb))
lower->add_volume(*vol);
}
}
ModelVolumePtrs& volumes = cut_mo->volumes; ModelVolumePtrs& volumes = cut_mo->volumes;
if (volumes.size() == cut_parts_cnt) if (volumes.size() == cut_parts_cnt) {
// Means that object is cut without connectors
// Just add Upper and Lower objects to cut_object_ptrs and invalidate any cut information
add_cut_objects(cut_object_ptrs, upper, lower); add_cut_objects(cut_object_ptrs, upper, lower);
}
else if (volumes.size() > cut_parts_cnt) { else if (volumes.size() > cut_parts_cnt) {
// Means that object is cut with connectors
// All volumes are distributed to Upper / Lower object,
// So we dont need them anymore
for (size_t id = 0; id < cut_parts_cnt; id++) for (size_t id = 0; id < cut_parts_cnt; id++)
delete *(volumes.begin() + id); delete *(volumes.begin() + id);
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
// Perform cut just to get connectors
const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes);
assert(create_dowels_as_separate_object ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); assert(create_dowels_as_separate_object ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
// Connectors from upper object
for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
upper->add_volume(*volume, volume->type()); upper->add_volume(*volume, volume->type());
// Connectors from lower object
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
lower->add_volume(*volume, volume->type()); lower->add_volume(*volume, volume->type());
// Add Upper and Lower objects to cut_object_ptrs with saved cut information
add_cut_objects(cut_object_ptrs, upper, lower, false); add_cut_objects(cut_object_ptrs, upper, lower, false);
// Add Dowel-connectors as separate objects to cut_object_ptrs
if (cut_connectors_obj.size() >= 3) if (cut_connectors_obj.size() >= 3)
for (size_t id = 2; id < cut_connectors_obj.size(); id++) for (size_t id = 2; id < cut_connectors_obj.size(); id++)
cut_object_ptrs.push_back(cut_connectors_obj[id]); cut_object_ptrs.push_back(cut_connectors_obj[id]);
@ -2567,8 +2611,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
{ {
for (ModelObject* mo : cut_object_ptrs) { for (ModelObject* mo : cut_object_ptrs) {
TriangleMesh mesh; TriangleMesh mesh;
// Merge all SolidPart but not Connectors
for (const ModelVolume* mv : mo->volumes) { for (const ModelVolume* mv : mo->volumes) {
if (mv->is_model_part()) { if (mv->is_model_part() && !mv->is_cut_connector()) {
TriangleMesh m = mv->mesh(); TriangleMesh m = mv->mesh();
m.transform(mv->get_matrix()); m.transform(mv->get_matrix());
mesh.merge(m); mesh.merge(m);
@ -2576,13 +2621,17 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
} }
if (! mesh.empty()) { if (! mesh.empty()) {
ModelVolume* new_volume = mo->add_volume(mesh); ModelVolume* new_volume = mo->add_volume(mesh);
for (int i=int(mo->volumes.size())-2; i>=0; --i) new_volume->name = mo->name;
if (mo->volumes[i]->type() == ModelVolumeType::MODEL_PART) // Delete all merged SolidPart but not Connectors
for (int i=int(mo->volumes.size())-2; i>=0; --i) {
const ModelVolume* mv = mo->volumes[i];
if (mv->is_model_part() && !mv->is_cut_connector())
mo->delete_volume(i); mo->delete_volume(i);
} }
} }
} }
} }
}
else else
cut_object_ptrs = mo->cut(instance_idx, cut_matrix, attributes); cut_object_ptrs = mo->cut(instance_idx, cut_matrix, attributes);

View File

@ -149,6 +149,7 @@ class GLGizmoCut3D : public GLGizmoBase
GLModel glmodel; GLModel glmodel;
MeshRaycaster raycaster; MeshRaycaster raycaster;
bool selected; bool selected;
bool is_modifier;
}; };
void render(const Vec3d* normal, GLModel& sphere_model); void render(const Vec3d* normal, GLModel& sphere_model);

View File

@ -355,7 +355,7 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) {
ap.apply(); ap.apply();
} }
m_plater->update(); m_plater->update((unsigned int)Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH);
wxGetApp().obj_manipul()->set_dirty(); wxGetApp().obj_manipul()->set_dirty();
if (!m_unarranged.empty()) { if (!m_unarranged.empty()) {

View File

@ -2218,7 +2218,6 @@ void NotificationManager::push_version_notification(NotificationType type, Notif
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
// NoNewReleaseAvailable must not show if alfa / beta is on. // NoNewReleaseAvailable must not show if alfa / beta is on.
NotificationType nttype = notification->get_type();
if (type == NotificationType::NoNewReleaseAvailable if (type == NotificationType::NoNewReleaseAvailable
&& (notification->get_type() == NotificationType::NewAlphaAvailable && (notification->get_type() == NotificationType::NewAlphaAvailable
|| notification->get_type() == NotificationType::NewBetaAvailable)) { || notification->get_type() == NotificationType::NewBetaAvailable)) {

View File

@ -1765,11 +1765,6 @@ struct Plater::priv
void render_project_state_debug_window() const { dirty_state.render_debug_window(); } void render_project_state_debug_window() const { dirty_state.render_debug_window(); }
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
enum class UpdateParams {
FORCE_FULL_SCREEN_REFRESH = 1,
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
};
void update(unsigned int flags = 0); void update(unsigned int flags = 0);
void select_view(const std::string& direction); void select_view(const std::string& direction);
void select_view_3D(const std::string& name); void select_view_3D(const std::string& name);
@ -3055,8 +3050,10 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx)
sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx); sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx);
model.delete_object(obj_idx); model.delete_object(obj_idx);
update(); update();
object_list_changed(); object_list_changed();
return true; return true;
} }
@ -3253,7 +3250,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
if (view3D->is_layers_editing_enabled()) if (view3D->is_layers_editing_enabled())
view3D->get_wxglcanvas()->Refresh(); view3D->get_wxglcanvas()->Refresh();
if (background_process.empty()) if (invalidated == Print::APPLY_STATUS_CHANGED || background_process.empty())
view3D->get_canvas3d()->reset_sequential_print_clearance(); view3D->get_canvas3d()->reset_sequential_print_clearance();
if (invalidated == Print::APPLY_STATUS_INVALIDATED) { if (invalidated == Print::APPLY_STATUS_INVALIDATED) {
@ -3292,9 +3289,10 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// or hide the old one. // or hide the old one.
process_validation_warning(warning); process_validation_warning(warning);
if (printer_technology == ptFFF) { if (printer_technology == ptFFF) {
view3D->get_canvas3d()->reset_sequential_print_clearance(); GLCanvas3D* canvas = view3D->get_canvas3d();
view3D->get_canvas3d()->set_as_dirty(); canvas->reset_sequential_print_clearance();
view3D->get_canvas3d()->request_extra_frame(); canvas->set_as_dirty();
canvas->request_extra_frame();
} }
} }
else { else {
@ -3303,18 +3301,31 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
notification_manager->push_validate_error_notification(err); notification_manager->push_validate_error_notification(err);
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
if (printer_technology == ptFFF) { if (printer_technology == ptFFF) {
const Print* print = background_process.fff_print(); GLCanvas3D* canvas = view3D->get_canvas3d();
Polygons polygons; if (canvas->is_sequential_print_clearance_empty() || canvas->is_sequential_print_clearance_evaluating()) {
if (print->config().complete_objects) GLCanvas3D::ContoursList contours;
Print::sequential_print_horizontal_clearance_valid(*print, &polygons); contours.contours = background_process.fff_print()->get_sequential_print_clearance_contours();
view3D->get_canvas3d()->set_sequential_print_clearance_visible(true); canvas->set_sequential_print_clearance_contours(contours, true);
view3D->get_canvas3d()->set_sequential_print_clearance_render_fill(true); canvas->set_as_dirty();
view3D->get_canvas3d()->set_sequential_print_clearance_polygons(polygons); canvas->request_extra_frame();
}
} }
} }
} }
else { else {
if (invalidated == Print::APPLY_STATUS_UNCHANGED && !background_process.empty()) { if (invalidated == Print::APPLY_STATUS_UNCHANGED && !background_process.empty()) {
if (printer_technology == ptFFF) {
// Object manipulation with gizmos may end up in a null transformation.
// In this case, we need to trigger the completion of the sequential print clearance contours evaluation
GLCanvas3D* canvas = view3D->get_canvas3d();
if (canvas->is_sequential_print_clearance_evaluating()) {
GLCanvas3D::ContoursList contours;
contours.contours = background_process.fff_print()->get_sequential_print_clearance_contours();
canvas->set_sequential_print_clearance_contours(contours, true);
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
std::string warning; std::string warning;
std::string err = background_process.validate(&warning); std::string err = background_process.validate(&warning);
if (!err.empty()) if (!err.empty())
@ -4416,7 +4427,6 @@ void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
void Plater::priv::on_3dcanvas_mouse_dragging_started(SimpleEvent&) void Plater::priv::on_3dcanvas_mouse_dragging_started(SimpleEvent&)
{ {
view3D->get_canvas3d()->reset_sequential_print_clearance();
} }
// Update the scene from the background processing, // Update the scene from the background processing,
@ -6005,7 +6015,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
return true; return true;
} }
void Plater::update() { p->update(); } void Plater::update(unsigned int flags) { p->update(flags); }
Worker &Plater::get_ui_job_worker() { return p->m_worker; } Worker &Plater::get_ui_job_worker() { return p->m_worker; }
@ -7031,7 +7041,6 @@ void Plater::force_filament_colors_update()
if (extruders_filaments.size() > 1 && if (extruders_filaments.size() > 1 &&
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == extruders_filaments.size()) p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == extruders_filaments.size())
{ {
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors; std::vector<std::string> filament_colors;
filament_colors.reserve(extruders_filaments.size()); filament_colors.reserve(extruders_filaments.size());

View File

@ -185,7 +185,12 @@ public:
const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; } const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; }
void update(); enum class UpdateParams {
FORCE_FULL_SCREEN_REFRESH = 1,
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
};
void update(unsigned int flags = 0);
// Get the worker handling the UI jobs (arrange, fill bed, etc...) // Get the worker handling the UI jobs (arrange, fill bed, etc...)
// Here is an example of starting up an ad-hoc job: // Here is an example of starting up an ad-hoc job:

View File

@ -1558,6 +1558,11 @@ void Selection::erase()
wxGetApp().obj_list()->delete_from_model_and_list(items); wxGetApp().obj_list()->delete_from_model_and_list(items);
ensure_not_below_bed(); ensure_not_below_bed();
} }
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
canvas->set_sequential_clearance_as_evaluating();
canvas->set_as_dirty();
canvas->request_extra_frame();
} }
void Selection::render(float scale_factor) void Selection::render(float scale_factor)
@ -1630,7 +1635,6 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
else if (is_single_volume_or_modifier()) { else if (is_single_volume_or_modifier()) {
if (!wxGetApp().obj_manipul()->is_world_coordinates()) { if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
if (wxGetApp().obj_manipul()->is_local_coordinates()) { if (wxGetApp().obj_manipul()->is_local_coordinates()) {
const GLVolume* v = (*m_volumes)[*m_list.begin()];
orient_matrix = get_bounding_box_in_current_reference_system().second; orient_matrix = get_bounding_box_in_current_reference_system().second;
orient_matrix.translation() = Vec3d::Zero(); orient_matrix.translation() = Vec3d::Zero();
} }

View File

@ -2983,7 +2983,7 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders)
update(); update();
}); });
auto has_changes = [this, extruder_idx]() { auto has_changes = [this]() {
auto dirty_options = m_presets->current_dirty_options(true); auto dirty_options = m_presets->current_dirty_options(true);
#if 1 #if 1
dirty_options.erase(std::remove_if(dirty_options.begin(), dirty_options.end(), dirty_options.erase(std::remove_if(dirty_options.begin(), dirty_options.end(),

View File

@ -104,7 +104,7 @@ bool Mainsail::test(wxString& msg) const
res = false; res = false;
msg = format_error(body, error, status); msg = format_error(body, error, status);
}) })
.on_complete([&, this](std::string body, unsigned) { .on_complete([&](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got server/info: %2%") % name % body; BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got server/info: %2%") % name % body;
try { try {

View File

@ -49,7 +49,7 @@ using Slic3r::GUI::Config::SnapshotDB;
namespace Slic3r { namespace Slic3r {
static const char *INDEX_FILENAME = "index.idx"; //static const char *INDEX_FILENAME = "index.idx";
static const char *TMP_EXTENSION = ".download"; static const char *TMP_EXTENSION = ".download";
namespace { namespace {